Skip to content

foreach loop reports values of next iteration to debugger #92809

Open
@weltkante

Description

@weltkante

Description

I've got a scenario where the debugger consistently reports wrong values for the loop variable, accessing the foreach loop value of the next iteration. The runtime seems to lag behind one iteration, while the debugger shows value of the variable it would have in the next iteration. This makes debugging confusing and very hard if you happen to trigger it.

The problem happens every run, I've reduced the code triggering the issue below (some code comparing assemblies tripped into this bug, originally using Mono.Cecil, but moving to plain reflection observes the same behavior given the same structure of code)

Reproduction Steps

using System.Diagnostics;
using System.Reflection;

await BugRepro(typeof(System.Collections.Immutable.ImmutableDictionary<,>.Enumerator), null);

// method must be async for the bug to trigger
static async Task BugRepro(Type type, Type? comparingType)
{
    const BindingFlags f = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;

    foreach (var field in type.GetFields(f))
    {
        // repeating the assert from below is necessary to trigger the bug
        if (comparingType?.GetFields(f).SingleOrDefault(x => x.Name == field.Name) is null)
            continue;

        // original code did more stuff but could remove it
    }

    foreach (var field in type.GetFields(f))
    {
        // inspecting debugger variables, this should be false (can even set a breakpoint and step through)
        var fieldContainsGenericParameter = field.FieldType.ContainsGenericParameters;
        if (fieldContainsGenericParameter)
        {
            // inspecting debugger variables, this assert should have triggered
            Debug.Assert(field.Name != "_enumeratingBuilderVersion");

            // inspect debugger variables mentioned above
            Debugger.Break();

            // repeating the assert is necessary to trigger the bug
            Debug.Assert(comparingType?.GetFields(f).SingleOrDefault(x => x.Name == field.Name) is null);
        }
    }
}

Expected behavior

the foreach loop variable shown in the debugger should match its actual value the runtime is using

Actual behavior

the debugger shows the foreach loop variable of the next iteration and bases all its displayed information off of that

in particular the debugger shows us being in the if-block for the field _enumeratingBuilderVersion which has type Int32 for which ContainsGenericParameters is false, yet the runtime assigns fieldContainsGenericParameter true (which it is for the previous field in the iteration)

Regression?

no, repros in Desktop Framework and VS 2019 as well as .NET 7 and 8 under VS 2022

Known Workarounds

none known, once the code is structured to trigger the issue, the debugger seems to consistently report wrong values

Configuration

x64 Windows 10 22H2 19045.3448

.NET Framework 4.8.9181.0
.NET 7.0.11
.NET 8.0.100 rc.1.23455.8

VS 2019 16.11.30
VS 2022 17.8.0 Preview 2.0

Other information

No response

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions