Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

.NET 9.0.200 broke ICSharpCode.Decompiler due to changed IL generation for yield return #3396

Open
foriequal0 opened this issue Feb 12, 2025 · 4 comments
Labels
Bug Decompiler The decompiler engine itself

Comments

@foriequal0
Copy link

foriequal0 commented Feb 12, 2025

Input code

public sealed class Target
{
    private IEnumerable<int> GetEnumerable()
    {
        yield return 1;
    }
}

Erroneous output

9.0.103

public sealed class Target
{
        private IEnumerable<int> GetEnumerable ()
        {
                yield return 1;
        }
}

9.0.200

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices;

public sealed class Target
{
        [CompilerGenerated]
        private sealed class <GetEnumerable>d__0 : IEnumerable<int>, IEnumerable, IEnumerator<int>, IEnumerator, IDisposable
        {
                private int <>1__state;

                private int <>2__current;

                private int <>l__initialThreadId;

                public Target <>4__this;

                int IEnumerator<int>.Current {
                        [DebuggerHidden]
                        get {
                                return <>2__current;
                        }
                }

                object IEnumerator.Current {
                        [DebuggerHidden]
                        get {
                                return <>2__current;
                        }
                }

                [DebuggerHidden]
                public <GetEnumerable>d__0 (int <>1__state)
                {
                        this.<>1__state = <>1__state;
                        <>l__initialThreadId = Environment.CurrentManagedThreadId;
                }

                [DebuggerHidden]
                void IDisposable.Dispose ()
                {
                        <>1__state = -2;
                }

                private bool MoveNext ()
                {
                        switch (<>1__state) {
                        default:
                                return false;
                        case 0:
                                <>1__state = -1;
                                <>2__current = 1;
                                <>1__state = 1;
                                return true;
                        case 1:
                                <>1__state = -1;
                                return false;
                        }
                }

                bool IEnumerator.MoveNext ()
                {
                        //ILSpy generated this explicit interface implementation from .override directive in MoveNext
                        return this.MoveNext ();
                }

                [DebuggerHidden]
                void IEnumerator.Reset ()
                {
                        throw new NotSupportedException ();
                }

                [DebuggerHidden]
                IEnumerator<int> IEnumerable<int>.GetEnumerator ()
                {
                        <GetEnumerable>d__0 result;
                        if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId) {
                                <>1__state = 0;
                                result = this;
                        } else {
                                result = new <GetEnumerable>d__0 (0) {
                                        <>4__this = <>4__this
                                };
                        }
                        return result;
                }

                [DebuggerHidden]
                IEnumerator IEnumerable.GetEnumerator ()
                {
                        return ((IEnumerable<int>)this).GetEnumerator ();
                }
        }

        [IteratorStateMachine (typeof(<GetEnumerable>d__0))]
        private IEnumerable<int> GetEnumerable ()
        {
                //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
                return new <GetEnumerable>d__0 (-2) {
                        <>4__this = this
                };
        }
}

Details

  • Product in use: ICSharpCode.Decompiler nuget package
  • Version in use: 9.0.0.7889
  • Any other relevant information to the issue, or your interest in contributing a fix.

Generated IL is slightly changed after 9.0.200

9.0.103

.class public sealed auto ansi beforefieldinit
  Target
    extends [System.Runtime]System.Object
{

  .class nested private sealed auto ansi beforefieldinit
    '<GetEnumerable>d__0'
      extends [System.Runtime]System.Object
      implements
        class [System.Runtime]System.Collections.Generic.IEnumerable`1<int32>,
        [System.Runtime]System.Collections.IEnumerable,
        class [System.Runtime]System.Collections.Generic.IEnumerator`1<int32>,
        [System.Runtime]System.Collections.IEnumerator,
        [System.Runtime]System.IDisposable
  {
    .custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor()
      = (01 00 00 00 )

    .field private int32 '<>1__state'

    .field private int32 '<>2__current'

    .field private int32 '<>l__initialThreadId'

    .field public class Target '<>4__this'

    .method public hidebysig specialname rtspecialname instance void
      .ctor(
        int32 '<>1__state'
      ) cil managed
    {
      .custom instance void [System.Runtime]System.Diagnostics.DebuggerHiddenAttribute::.ctor()
        = (01 00 00 00 )
      .maxstack 8

      IL_0000: ldarg.0      // this
      IL_0001: call         instance void [System.Runtime]System.Object::.ctor()
      IL_0006: nop
      IL_0007: ldarg.0      // this
      IL_0008: ldarg.1      // '<>1__state'
      IL_0009: stfld        int32 Target/'<GetEnumerable>d__0'::'<>1__state'
      IL_000e: ldarg.0      // this
      IL_000f: call         int32 [System.Runtime]System.Environment::get_CurrentManagedThreadId()
      IL_0014: stfld        int32 Target/'<GetEnumerable>d__0'::'<>l__initialThreadId'
      IL_0019: ret

    } // end of method '<GetEnumerable>d__0'::.ctor

    .method private final hidebysig virtual newslot instance void
      System.IDisposable.Dispose() cil managed
    {
      .custom instance void [System.Runtime]System.Diagnostics.DebuggerHiddenAttribute::.ctor()
        = (01 00 00 00 )
      .override method instance void [System.Runtime]System.IDisposable::Dispose()
      .maxstack 8

      IL_0000: ret

    } // end of method '<GetEnumerable>d__0'::System.IDisposable.Dispose

    .method private final hidebysig virtual newslot instance bool
      MoveNext() cil managed
    {
      .override method instance bool [System.Runtime]System.Collections.IEnumerator::MoveNext()
      .maxstack 2
      .locals init (
        [0] int32 V_0
      )

      IL_0000: ldarg.0      // this
      IL_0001: ldfld        int32 Target/'<GetEnumerable>d__0'::'<>1__state'
      IL_0006: stloc.0      // V_0
      IL_0007: ldloc.0      // V_0
      IL_0008: brfalse.s    IL_0012
      IL_000a: br.s         IL_000c
      IL_000c: ldloc.0      // V_0
      IL_000d: ldc.i4.1
      IL_000e: beq.s        IL_0014
      IL_0010: br.s         IL_0016
      IL_0012: br.s         IL_0018
      IL_0014: br.s         IL_0030
      IL_0016: ldc.i4.0
      IL_0017: ret
      IL_0018: ldarg.0      // this
      IL_0019: ldc.i4.m1
      IL_001a: stfld        int32 Target/'<GetEnumerable>d__0'::'<>1__state'

      // [27 5 - 27 6]
      IL_001f: nop

      // [28 9 - 28 24]
      IL_0020: ldarg.0      // this
      IL_0021: ldc.i4.1
      IL_0022: stfld        int32 Target/'<GetEnumerable>d__0'::'<>2__current'
      IL_0027: ldarg.0      // this
      IL_0028: ldc.i4.1
      IL_0029: stfld        int32 Target/'<GetEnumerable>d__0'::'<>1__state'
      IL_002e: ldc.i4.1
      IL_002f: ret

      IL_0030: ldarg.0      // this
      IL_0031: ldc.i4.m1
      IL_0032: stfld        int32 Target/'<GetEnumerable>d__0'::'<>1__state'

      // [29 5 - 29 6]
      IL_0037: ldc.i4.0
      IL_0038: ret

    } // end of method '<GetEnumerable>d__0'::MoveNext

    .method private final hidebysig virtual newslot specialname instance int32
      'System.Collections.Generic.IEnumerator<System.Int32>.get_Current'() cil managed
    {
      .custom instance void [System.Runtime]System.Diagnostics.DebuggerHiddenAttribute::.ctor()
        = (01 00 00 00 )
      .override method instance !0/*T*/ class [System.Runtime]System.Collections.Generic.IEnumerator`1<int32>::get_Current()
      .maxstack 8

      IL_0000: ldarg.0      // this
      IL_0001: ldfld        int32 Target/'<GetEnumerable>d__0'::'<>2__current'
      IL_0006: ret

    } // end of method '<GetEnumerable>d__0'::'System.Collections.Generic.IEnumerator<System.Int32>.get_Current'

    .method private final hidebysig virtual newslot instance void
      System.Collections.IEnumerator.Reset() cil managed
    {
      .custom instance void [System.Runtime]System.Diagnostics.DebuggerHiddenAttribute::.ctor()
        = (01 00 00 00 )
      .override method instance void [System.Runtime]System.Collections.IEnumerator::Reset()
      .maxstack 8

      IL_0000: newobj       instance void [System.Runtime]System.NotSupportedException::.ctor()
      IL_0005: throw

    } // end of method '<GetEnumerable>d__0'::System.Collections.IEnumerator.Reset

    .method private final hidebysig virtual newslot specialname instance object
      System.Collections.IEnumerator.get_Current() cil managed
    {
      .custom instance void [System.Runtime]System.Diagnostics.DebuggerHiddenAttribute::.ctor()
        = (01 00 00 00 )
      .override method instance object [System.Runtime]System.Collections.IEnumerator::get_Current()
      .maxstack 8

      IL_0000: ldarg.0      // this
      IL_0001: ldfld        int32 Target/'<GetEnumerable>d__0'::'<>2__current'
      IL_0006: box          [System.Runtime]System.Int32
      IL_000b: ret

    } // end of method '<GetEnumerable>d__0'::System.Collections.IEnumerator.get_Current

    .method private final hidebysig virtual newslot instance class [System.Runtime]System.Collections.Generic.IEnumerator`1<int32>
      'System.Collections.Generic.IEnumerable<System.Int32>.GetEnumerator'() cil managed
    {
      .custom instance void [System.Runtime]System.Diagnostics.DebuggerHiddenAttribute::.ctor()
        = (01 00 00 00 )
      .param [0]
        .custom instance void [System.Runtime]System.Runtime.CompilerServices.NullableAttribute::.ctor(unsigned int8)
          = (01 00 01 00 00 ) // .....
          // unsigned int8(1) // 0x01
      .override method instance class [System.Runtime]System.Collections.Generic.IEnumerator`1<!0/*T*/> class [System.Runtime]System.Collections.Generic.IEnumerable`1<int32>::GetEnumerator()
      .maxstack 2
      .locals init (
        [0] class Target/'<GetEnumerable>d__0' V_0
      )

      IL_0000: ldarg.0      // this
      IL_0001: ldfld        int32 Target/'<GetEnumerable>d__0'::'<>1__state'
      IL_0006: ldc.i4.s     -2 // 0xfe
      IL_0008: bne.un.s     IL_0022
      IL_000a: ldarg.0      // this
      IL_000b: ldfld        int32 Target/'<GetEnumerable>d__0'::'<>l__initialThreadId'
      IL_0010: call         int32 [System.Runtime]System.Environment::get_CurrentManagedThreadId()
      IL_0015: bne.un.s     IL_0022
      IL_0017: ldarg.0      // this
      IL_0018: ldc.i4.0
      IL_0019: stfld        int32 Target/'<GetEnumerable>d__0'::'<>1__state'
      IL_001e: ldarg.0      // this
      IL_001f: stloc.0      // V_0
      IL_0020: br.s         IL_0035
      IL_0022: ldc.i4.0
      IL_0023: newobj       instance void Target/'<GetEnumerable>d__0'::.ctor(int32)
      IL_0028: stloc.0      // V_0
      IL_0029: ldloc.0      // V_0
      IL_002a: ldarg.0      // this
      IL_002b: ldfld        class Target Target/'<GetEnumerable>d__0'::'<>4__this'
      IL_0030: stfld        class Target Target/'<GetEnumerable>d__0'::'<>4__this'
      IL_0035: ldloc.0      // V_0
      IL_0036: ret

    } // end of method '<GetEnumerable>d__0'::'System.Collections.Generic.IEnumerable<System.Int32>.GetEnumerator'

    .method private final hidebysig virtual newslot instance class [System.Runtime]System.Collections.IEnumerator
      System.Collections.IEnumerable.GetEnumerator() cil managed
    {
      .custom instance void [System.Runtime]System.Diagnostics.DebuggerHiddenAttribute::.ctor()
        = (01 00 00 00 )
      .param [0]
        .custom instance void [System.Runtime]System.Runtime.CompilerServices.NullableAttribute::.ctor(unsigned int8)
          = (01 00 01 00 00 ) // .....
          // unsigned int8(1) // 0x01
      .override method instance class [System.Runtime]System.Collections.IEnumerator [System.Runtime]System.Collections.IEnumerable::GetEnumerator()
      .maxstack 8

      IL_0000: ldarg.0      // this
      IL_0001: call         instance class [System.Runtime]System.Collections.Generic.IEnumerator`1<int32> Target/'<GetEnumerable>d__0'::'System.Collections.Generic.IEnumerable<System.Int32>.GetEnumerator'()
      IL_0006: ret

    } // end of method '<GetEnumerable>d__0'::System.Collections.IEnumerable.GetEnumerator

    .property instance int32 'System.Collections.Generic.IEnumerator<System.Int32>.Current'()
    {
      .get instance int32 Target/'<GetEnumerable>d__0'::'System.Collections.Generic.IEnumerator<System.Int32>.get_Current'()
    } // end of property '<GetEnumerable>d__0'::'System.Collections.Generic.IEnumerator<System.Int32>.Current'

    .property instance object 'System.Collections.IEnumerator.Current'()
    {
      .get instance object Target/'<GetEnumerable>d__0'::System.Collections.IEnumerator.get_Current()
    } // end of property '<GetEnumerable>d__0'::System.Collections.IEnumerator.Current
  } // end of class '<GetEnumerable>d__0'

  .method private hidebysig instance class [System.Runtime]System.Collections.Generic.IEnumerable`1<int32>
    GetEnumerable() cil managed
  {
    .custom instance void [System.Runtime]System.Runtime.CompilerServices.NullableContextAttribute::.ctor(unsigned int8)
      = (01 00 01 00 00 ) // .....
      // unsigned int8(1) // 0x01
    .custom instance void [System.Runtime]System.Runtime.CompilerServices.IteratorStateMachineAttribute::.ctor(class [System.Runtime]System.Type)
      = (
        01 00 1a 54 61 72 67 65 74 2b 3c 47 65 74 45 6e // ...Target+<GetEn
        75 6d 65 72 61 62 6c 65 3e 64 5f 5f 30 00 00    // umerable>d__0..
      )
      // type(class Target/'<GetEnumerable>d__0')
    .maxstack 8

    IL_0000: ldc.i4.s     -2 // 0xfe
    IL_0002: newobj       instance void Target/'<GetEnumerable>d__0'::.ctor(int32)
    IL_0007: dup
    IL_0008: ldarg.0      // this
    IL_0009: stfld        class Target Target/'<GetEnumerable>d__0'::'<>4__this'
    IL_000e: ret

  } // end of method Target::GetEnumerable

  .method public hidebysig specialname rtspecialname instance void
    .ctor() cil managed
  {
    .maxstack 8

    IL_0000: ldarg.0      // this
    IL_0001: call         instance void [System.Runtime]System.Object::.ctor()
    IL_0006: nop
    IL_0007: ret

  } // end of method Target::.ctor
} // end of class Target

9.0.200

// Type: Target 
// Assembly: ConsoleApp1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
// MVID: 03D82503-62C1-4247-8ED3-D013EB2E59BB
// Location: E:\workspace\CSharpDecompilerRepro\Solution1\ConsoleApp1\bin\Debug\net9.0\ConsoleApp1.dll
// Sequence point data and variable names from e:\workspace\csharpdecompilerrepro\solution1\consoleapp1\bin\debug\net9.0\consoleapp1.pdb

.class public sealed auto ansi beforefieldinit
  Target
    extends [System.Runtime]System.Object
{

  .class nested private sealed auto ansi beforefieldinit
    '<GetEnumerable>d__0'
      extends [System.Runtime]System.Object
      implements
        class [System.Runtime]System.Collections.Generic.IEnumerable`1<int32>,
        [System.Runtime]System.Collections.IEnumerable,
        class [System.Runtime]System.Collections.Generic.IEnumerator`1<int32>,
        [System.Runtime]System.Collections.IEnumerator,
        [System.Runtime]System.IDisposable
  {
    .custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor()
      = (01 00 00 00 )

    .field private int32 '<>1__state'

    .field private int32 '<>2__current'

    .field private int32 '<>l__initialThreadId'

    .field public class Target '<>4__this'

    .method public hidebysig specialname rtspecialname instance void
      .ctor(
        int32 '<>1__state'
      ) cil managed
    {
      .custom instance void [System.Runtime]System.Diagnostics.DebuggerHiddenAttribute::.ctor()
        = (01 00 00 00 )
      .maxstack 8

      IL_0000: ldarg.0      // this
      IL_0001: call         instance void [System.Runtime]System.Object::.ctor()
      IL_0006: nop
      IL_0007: ldarg.0      // this
      IL_0008: ldarg.1      // '<>1__state'
      IL_0009: stfld        int32 Target/'<GetEnumerable>d__0'::'<>1__state'
      IL_000e: ldarg.0      // this
      IL_000f: call         int32 [System.Runtime]System.Environment::get_CurrentManagedThreadId()
      IL_0014: stfld        int32 Target/'<GetEnumerable>d__0'::'<>l__initialThreadId'
      IL_0019: ret

    } // end of method '<GetEnumerable>d__0'::.ctor

    .method private final hidebysig virtual newslot instance void
      System.IDisposable.Dispose() cil managed
    {
      .custom instance void [System.Runtime]System.Diagnostics.DebuggerHiddenAttribute::.ctor()
        = (01 00 00 00 )
      .override method instance void [System.Runtime]System.IDisposable::Dispose()
      .maxstack 8

      IL_0000: ldarg.0      // this
      IL_0001: ldc.i4.s     -2 // 0xfe
      IL_0003: stfld        int32 Target/'<GetEnumerable>d__0'::'<>1__state'
      IL_0008: ret

    } // end of method '<GetEnumerable>d__0'::System.IDisposable.Dispose

    .method private final hidebysig virtual newslot instance bool
      MoveNext() cil managed
    {
      .override method instance bool [System.Runtime]System.Collections.IEnumerator::MoveNext()
      .maxstack 2
      .locals init (
        [0] int32 V_0
      )

      IL_0000: ldarg.0      // this
      IL_0001: ldfld        int32 Target/'<GetEnumerable>d__0'::'<>1__state'
      IL_0006: stloc.0      // V_0
      IL_0007: ldloc.0      // V_0
      IL_0008: brfalse.s    IL_0012
      IL_000a: br.s         IL_000c
      IL_000c: ldloc.0      // V_0
      IL_000d: ldc.i4.1
      IL_000e: beq.s        IL_0014
      IL_0010: br.s         IL_0016
      IL_0012: br.s         IL_0018
      IL_0014: br.s         IL_0030
      IL_0016: ldc.i4.0
      IL_0017: ret
      IL_0018: ldarg.0      // this
      IL_0019: ldc.i4.m1
      IL_001a: stfld        int32 Target/'<GetEnumerable>d__0'::'<>1__state'

      // [27 5 - 27 6]
      IL_001f: nop

      // [28 9 - 28 24]
      IL_0020: ldarg.0      // this
      IL_0021: ldc.i4.1
      IL_0022: stfld        int32 Target/'<GetEnumerable>d__0'::'<>2__current'
      IL_0027: ldarg.0      // this
      IL_0028: ldc.i4.1
      IL_0029: stfld        int32 Target/'<GetEnumerable>d__0'::'<>1__state'
      IL_002e: ldc.i4.1
      IL_002f: ret

      IL_0030: ldarg.0      // this
      IL_0031: ldc.i4.m1
      IL_0032: stfld        int32 Target/'<GetEnumerable>d__0'::'<>1__state'

      // [29 5 - 29 6]
      IL_0037: ldc.i4.0
      IL_0038: ret

    } // end of method '<GetEnumerable>d__0'::MoveNext

    .method private final hidebysig virtual newslot specialname instance int32
      'System.Collections.Generic.IEnumerator<System.Int32>.get_Current'() cil managed
    {
      .custom instance void [System.Runtime]System.Diagnostics.DebuggerHiddenAttribute::.ctor()
        = (01 00 00 00 )
      .override method instance !0/*T*/ class [System.Runtime]System.Collections.Generic.IEnumerator`1<int32>::get_Current()
      .maxstack 8

      IL_0000: ldarg.0      // this
      IL_0001: ldfld        int32 Target/'<GetEnumerable>d__0'::'<>2__current'
      IL_0006: ret

    } // end of method '<GetEnumerable>d__0'::'System.Collections.Generic.IEnumerator<System.Int32>.get_Current'

    .method private final hidebysig virtual newslot instance void
      System.Collections.IEnumerator.Reset() cil managed
    {
      .custom instance void [System.Runtime]System.Diagnostics.DebuggerHiddenAttribute::.ctor()
        = (01 00 00 00 )
      .override method instance void [System.Runtime]System.Collections.IEnumerator::Reset()
      .maxstack 8

      IL_0000: newobj       instance void [System.Runtime]System.NotSupportedException::.ctor()
      IL_0005: throw

    } // end of method '<GetEnumerable>d__0'::System.Collections.IEnumerator.Reset

    .method private final hidebysig virtual newslot specialname instance object
      System.Collections.IEnumerator.get_Current() cil managed
    {
      .custom instance void [System.Runtime]System.Diagnostics.DebuggerHiddenAttribute::.ctor()
        = (01 00 00 00 )
      .override method instance object [System.Runtime]System.Collections.IEnumerator::get_Current()
      .maxstack 8

      IL_0000: ldarg.0      // this
      IL_0001: ldfld        int32 Target/'<GetEnumerable>d__0'::'<>2__current'
      IL_0006: box          [System.Runtime]System.Int32
      IL_000b: ret

    } // end of method '<GetEnumerable>d__0'::System.Collections.IEnumerator.get_Current

    .method private final hidebysig virtual newslot instance class [System.Runtime]System.Collections.Generic.IEnumerator`1<int32>
      'System.Collections.Generic.IEnumerable<System.Int32>.GetEnumerator'() cil managed
    {
      .custom instance void [System.Runtime]System.Diagnostics.DebuggerHiddenAttribute::.ctor()
        = (01 00 00 00 )
      .param [0]
        .custom instance void [System.Runtime]System.Runtime.CompilerServices.NullableAttribute::.ctor(unsigned int8)
          = (01 00 01 00 00 ) // .....
          // unsigned int8(1) // 0x01
      .override method instance class [System.Runtime]System.Collections.Generic.IEnumerator`1<!0/*T*/> class [System.Runtime]System.Collections.Generic.IEnumerable`1<int32>::GetEnumerator()
      .maxstack 2
      .locals init (
        [0] class Target/'<GetEnumerable>d__0' V_0
      )

      IL_0000: ldarg.0      // this
      IL_0001: ldfld        int32 Target/'<GetEnumerable>d__0'::'<>1__state'
      IL_0006: ldc.i4.s     -2 // 0xfe
      IL_0008: bne.un.s     IL_0022
      IL_000a: ldarg.0      // this
      IL_000b: ldfld        int32 Target/'<GetEnumerable>d__0'::'<>l__initialThreadId'
      IL_0010: call         int32 [System.Runtime]System.Environment::get_CurrentManagedThreadId()
      IL_0015: bne.un.s     IL_0022
      IL_0017: ldarg.0      // this
      IL_0018: ldc.i4.0
      IL_0019: stfld        int32 Target/'<GetEnumerable>d__0'::'<>1__state'
      IL_001e: ldarg.0      // this
      IL_001f: stloc.0      // V_0
      IL_0020: br.s         IL_0035
      IL_0022: ldc.i4.0
      IL_0023: newobj       instance void Target/'<GetEnumerable>d__0'::.ctor(int32)
      IL_0028: stloc.0      // V_0
      IL_0029: ldloc.0      // V_0
      IL_002a: ldarg.0      // this
      IL_002b: ldfld        class Target Target/'<GetEnumerable>d__0'::'<>4__this'
      IL_0030: stfld        class Target Target/'<GetEnumerable>d__0'::'<>4__this'
      IL_0035: ldloc.0      // V_0
      IL_0036: ret

    } // end of method '<GetEnumerable>d__0'::'System.Collections.Generic.IEnumerable<System.Int32>.GetEnumerator'

    .method private final hidebysig virtual newslot instance class [System.Runtime]System.Collections.IEnumerator
      System.Collections.IEnumerable.GetEnumerator() cil managed
    {
      .custom instance void [System.Runtime]System.Diagnostics.DebuggerHiddenAttribute::.ctor()
        = (01 00 00 00 )
      .param [0]
        .custom instance void [System.Runtime]System.Runtime.CompilerServices.NullableAttribute::.ctor(unsigned int8)
          = (01 00 01 00 00 ) // .....
          // unsigned int8(1) // 0x01
      .override method instance class [System.Runtime]System.Collections.IEnumerator [System.Runtime]System.Collections.IEnumerable::GetEnumerator()
      .maxstack 8

      IL_0000: ldarg.0      // this
      IL_0001: call         instance class [System.Runtime]System.Collections.Generic.IEnumerator`1<int32> Target/'<GetEnumerable>d__0'::'System.Collections.Generic.IEnumerable<System.Int32>.GetEnumerator'()
      IL_0006: ret

    } // end of method '<GetEnumerable>d__0'::System.Collections.IEnumerable.GetEnumerator

    .property instance int32 'System.Collections.Generic.IEnumerator<System.Int32>.Current'()
    {
      .get instance int32 Target/'<GetEnumerable>d__0'::'System.Collections.Generic.IEnumerator<System.Int32>.get_Current'()
    } // end of property '<GetEnumerable>d__0'::'System.Collections.Generic.IEnumerator<System.Int32>.Current'

    .property instance object 'System.Collections.IEnumerator.Current'()
    {
      .get instance object Target/'<GetEnumerable>d__0'::System.Collections.IEnumerator.get_Current()
    } // end of property '<GetEnumerable>d__0'::System.Collections.IEnumerator.Current
  } // end of class '<GetEnumerable>d__0'

  .method private hidebysig instance class [System.Runtime]System.Collections.Generic.IEnumerable`1<int32>
    GetEnumerable() cil managed
  {
    .custom instance void [System.Runtime]System.Runtime.CompilerServices.NullableContextAttribute::.ctor(unsigned int8)
      = (01 00 01 00 00 ) // .....
      // unsigned int8(1) // 0x01
    .custom instance void [System.Runtime]System.Runtime.CompilerServices.IteratorStateMachineAttribute::.ctor(class [System.Runtime]System.Type)
      = (
        01 00 1a 54 61 72 67 65 74 2b 3c 47 65 74 45 6e // ...Target+<GetEn
        75 6d 65 72 61 62 6c 65 3e 64 5f 5f 30 00 00    // umerable>d__0..
      )
      // type(class Target/'<GetEnumerable>d__0')
    .maxstack 8

    IL_0000: ldc.i4.s     -2 // 0xfe
    IL_0002: newobj       instance void Target/'<GetEnumerable>d__0'::.ctor(int32)
    IL_0007: dup
    IL_0008: ldarg.0      // this
    IL_0009: stfld        class Target Target/'<GetEnumerable>d__0'::'<>4__this'
    IL_000e: ret

  } // end of method Target::GetEnumerable

  .method public hidebysig specialname rtspecialname instance void
    .ctor() cil managed
  {
    .maxstack 8

    IL_0000: ldarg.0      // this
    IL_0001: call         instance void [System.Runtime]System.Object::.ctor()
    IL_0006: nop
    IL_0007: ret

  } // end of method Target::.ctor
} // end of class Target

diff for you

--- 9.0.103
+++ 9.0.200
@@ -60,7 +60,10 @@
       .override method instance void [System.Runtime]System.IDisposable::Dispose()
       .maxstack 8
 
-      IL_0000: ret
+      IL_0000: ldarg.0      // this
+      IL_0001: ldc.i4.s     -2 // 0xfe
+      IL_0003: stfld        int32 Target/'<GetEnumerable>d__0'::'<>1__state'
+      IL_0008: ret
 
     } // end of method '<GetEnumerable>d__0'::System.IDisposable.Dispose

I think it would be equivalent to this

--- 9.0.103
+++ 9.0.200
@@ -41,6 +41,7 @@
                 [DebuggerHidden]
                 void IDisposable.Dispose ()
                 {
+                        <>1__state = -2;
                 }
 
                 private bool MoveNext ()

If there are captured enumerators, then they set default them.

public sealed class Target
{
    private IEnumerable<int> GetEnumerable(List<int> a, List<int> b)
    {
        foreach (var x in a)
        {
            yield return x;
        }

        foreach (var x in b)
        {
            yield return x;
        }
    }
}
--- 9.0.103
+++ 9.0.200
@@ -111,7 +111,16 @@
         IL_0043: endfinally
       } // end of finally
       IL_0044: br.s         IL_0046
-      IL_0046: ret
+      IL_0046: ldarg.0      // this
+      IL_0047: ldflda       valuetype [System.Collections]System.Collections.Generic.List`1/Enumerator<int32> Target/'<GetEnumerable>d__0'::'<>s__1'
+      IL_004c: initobj      valuetype [System.Collections]System.Collections.Generic.List`1/Enumerator<int32>
+      IL_0052: ldarg.0      // this
+      IL_0053: ldflda       valuetype [System.Collections]System.Collections.Generic.List`1/Enumerator<int32> Target/'<GetEnumerable>d__0'::'<>s__3'
+      IL_0058: initobj      valuetype [System.Collections]System.Collections.Generic.List`1/Enumerator<int32>
+      IL_005e: ldarg.0      // this
+      IL_005f: ldc.i4.s     -2 // 0xfe
+      IL_0061: stfld        int32 Target/'<GetEnumerable>d__0'::'<>1__state'
+      IL_0066: ret
 
     } // end of method '<GetEnumerable>d__0'::System.IDisposable.Dispose
 

equivalent to

--- 9.0.103
+++ 9.0.200
@@ -73,6 +73,9 @@
                                 }
                                 break;
                         }
+                        <>s__1 = default(List<int>.Enumerator);
+                        <>s__3 = default(List<int>.Enumerator);
+                        <>1__state = -2;
                 }
 
                 private bool MoveNext ()
@foriequal0 foriequal0 added Bug Decompiler The decompiler engine itself labels Feb 12, 2025
@foriequal0
Copy link
Author

<>1__state = -2 is introduced with this dotnet/roslyn#76090

@foriequal0
Copy link
Author

<>s__1 = default(List<int>.Enumerator);

is introduced with this dotnet/roslyn#75908

@siegfriedpammer
Copy link
Member

siegfriedpammer commented Feb 12, 2025

Thank you for reporting! However, we will have to postpone fixing this issue until there is a newer NuGet release of Roslyn, so that we can add it to our unit tests and fix all the various changes in the C# compiler code-gen. This way we can make sure that we don't break older compiler versions and have a larger set of test cases available with no additional work to do.

@foriequal0
Copy link
Author

foriequal0 commented Feb 12, 2025

Thanks.

I could pass ILPretty test by inserting the following case statement after this case statement:

case StObj stobj when mode == StateRangeAnalysisMode.IteratorMoveNext:
{
if (stobj.MatchStFld(out var target, out var field, out var value)
&& target.MatchLdThis() && field.MemberDefinition == stateField && value.MatchLdcI4(-1))
{
// Mono resets the state field during MoveNext();
// don't consider this user code.
return stateRange;
}
else
{
goto default;
}
}

				case StObj stobj when mode == StateRangeAnalysisMode.IteratorDispose:
				{
					if (stobj.MatchStFld(out var target, out var field, out var value) && target.MatchLdThis())
					{
						if (field.IsCompilerGeneratedOrIsInCompilerGeneratedClass() && (value.MatchDefaultValue(out _) || value.MatchLdNull()))
						{
							// >= .net 9.0.200 clears hoisted locals
							// don't consider this user code.
							return stateRange;
						}

						if (field.MemberDefinition == stateField && value.MatchLdcI4(-2))
						{
							// >= .net 9.0.200 sets the state field during Dispose();
							// don't consider this user code.
							return stateRange;
						}
					}

					goto default;
				}

But I'm unsure about the field.IsCompilerGeneratedOrIsInCompilerGeneratedClass() (I don't know how to determine whether the field is hoisted locals or not), and (value.MatchDefaultValue(out _) || value.MatchLdNull()) (it doesn't clear int to 0, but I'm not sure why), so I'll just leave this as a note.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug Decompiler The decompiler engine itself
Projects
None yet
Development

No branches or pull requests

2 participants