diff --git a/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncIteratorMethodToStateMachineRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncIteratorMethodToStateMachineRewriter.cs index 51cd9ae093a78..496c04f657904 100644 --- a/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncIteratorMethodToStateMachineRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncIteratorMethodToStateMachineRewriter.cs @@ -55,13 +55,14 @@ internal AsyncIteratorMethodToStateMachineRewriter( FieldSymbol? instanceIdField, IReadOnlySet hoistedVariables, IReadOnlyDictionary nonReusableLocalProxies, + ImmutableArray nonReusableFieldsForCleanup, SynthesizedLocalOrdinalsDispenser synthesizedLocalOrdinals, ArrayBuilder stateMachineStateDebugInfoBuilder, VariableSlotAllocator? slotAllocatorOpt, int nextFreeHoistedLocalSlot, BindingDiagnosticBag diagnostics) : base(method, methodOrdinal, asyncMethodBuilderMemberCollection, F, - state, builder, instanceIdField, hoistedVariables, nonReusableLocalProxies, synthesizedLocalOrdinals, + state, builder, instanceIdField, hoistedVariables, nonReusableLocalProxies, nonReusableFieldsForCleanup, synthesizedLocalOrdinals, stateMachineStateDebugInfoBuilder, slotAllocatorOpt, nextFreeHoistedLocalSlot, diagnostics) { Debug.Assert(asyncIteratorInfo != null); @@ -88,11 +89,21 @@ internal AsyncIteratorMethodToStateMachineRewriter( return (asyncDispatch != null) ? F.Block(asyncDispatch, iteratorDispatch) : iteratorDispatch; } + + protected override BoundStatement GenerateCleanupForExit(ImmutableArray rootHoistedLocals) + { + // We need to clean nested hoisted local variables too (not just top-level ones) + // as they are not cleaned when exiting a block if we exit using a `yield break` + // or if the caller interrupts the enumeration after we reached a `yield return`. + // So we clean both top-level and nested hoisted local variables + return GenerateAllHoistedLocalsCleanup(); + } #nullable disable protected override BoundStatement GenerateSetResultCall() { // ... _exprReturnLabel: ... // ... this.state = FinishedState; ... + // ... hoisted locals cleanup ... // if (this.combinedTokens != null) { this.combinedTokens.Dispose(); this.combinedTokens = null; } // for enumerables only // _current = default; @@ -319,7 +330,12 @@ public override BoundNode VisitYieldBreakStatement(BoundYieldBreakStatement node protected override BoundStatement MakeAwaitPreamble() { - if (_asyncIteratorInfo.CurrentField.Type.IsManagedTypeNoUseSiteDiagnostics) + var useSiteInfo = new CompoundUseSiteInfo(F.Diagnostics, F.Compilation.Assembly); + var field = _asyncIteratorInfo.CurrentField; + bool isManaged = field.Type.IsManagedType(ref useSiteInfo); + F.Diagnostics.Add(field.GetFirstLocationOrNone(), useSiteInfo); + + if (isManaged) { // _current = default; return GenerateClearCurrent(); diff --git a/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncMethodToStateMachineRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncMethodToStateMachineRewriter.cs index 43993b7fe05da..585ec65c29724 100644 --- a/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncMethodToStateMachineRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncMethodToStateMachineRewriter.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; @@ -72,12 +73,13 @@ internal AsyncMethodToStateMachineRewriter( FieldSymbol? instanceIdField, IReadOnlySet hoistedVariables, IReadOnlyDictionary nonReusableLocalProxies, + ImmutableArray nonReusableFieldsForCleanup, SynthesizedLocalOrdinalsDispenser synthesizedLocalOrdinals, ArrayBuilder stateMachineStateDebugInfoBuilder, VariableSlotAllocator? slotAllocatorOpt, int nextFreeHoistedLocalSlot, BindingDiagnosticBag diagnostics) - : base(F, method, state, instanceIdField, hoistedVariables, nonReusableLocalProxies, synthesizedLocalOrdinals, stateMachineStateDebugInfoBuilder, slotAllocatorOpt, nextFreeHoistedLocalSlot, diagnostics) + : base(F, method, state, instanceIdField, hoistedVariables, nonReusableLocalProxies, nonReusableFieldsForCleanup, synthesizedLocalOrdinals, stateMachineStateDebugInfoBuilder, slotAllocatorOpt, nextFreeHoistedLocalSlot, diagnostics) { _method = method; _asyncMethodBuilderMemberCollection = asyncMethodBuilderMemberCollection; @@ -155,7 +157,7 @@ internal void GenerateMoveNext(BoundStatement body, MethodSymbol moveNextMethod) // [body] rewrittenBody ), - F.CatchBlocks(GenerateExceptionHandling(exceptionLocal, rootScopeHoistedLocals))) + F.CatchBlocks(generateExceptionHandling(exceptionLocal, rootScopeHoistedLocals))) ); // ReturnLabel (for the rewritten return expressions in the user's method body) @@ -176,7 +178,7 @@ internal void GenerateMoveNext(BoundStatement body, MethodSymbol moveNextMethod) // The remaining code is hidden to hide the fact that it can run concurrently with the task's continuation } - bodyBuilder.Add(GenerateHoistedLocalsCleanup(rootScopeHoistedLocals)); + bodyBuilder.Add(GenerateCleanupForExit(rootScopeHoistedLocals)); bodyBuilder.Add(GenerateSetResultCall()); @@ -206,6 +208,42 @@ internal void GenerateMoveNext(BoundStatement body, MethodSymbol moveNextMethod) newBody = F.Instrument(newBody, instrumentation); F.CloseMethod(newBody); + return; + + BoundCatchBlock generateExceptionHandling(LocalSymbol exceptionLocal, ImmutableArray rootHoistedLocals) + { + // catch (Exception ex) + // { + // _state = finishedState; + // + // for each hoisted local: + // <>x__y = default + // + // builder.SetException(ex); OR if (this.combinedTokens != null) this.combinedTokens.Dispose(); _promiseOfValueOrEnd.SetException(ex); /* for async-iterator method */ + // return; + // } + + // _state = finishedState; + BoundStatement assignFinishedState = + F.ExpressionStatement(F.AssignmentExpression(F.Field(F.This(), stateField), F.Literal(StateMachineState.FinishedState))); + + // builder.SetException(ex); OR if (this.combinedTokens != null) this.combinedTokens.Dispose(); _promiseOfValueOrEnd.SetException(ex); + BoundStatement callSetException = GenerateSetExceptionCall(exceptionLocal); + + return new BoundCatchBlock( + F.Syntax, + ImmutableArray.Create(exceptionLocal), + F.Local(exceptionLocal), + exceptionLocal.Type, + exceptionFilterPrologueOpt: null, + exceptionFilterOpt: null, + body: F.Block( + assignFinishedState, // _state = finishedState; + GenerateCleanupForExit(rootHoistedLocals), + callSetException, // builder.SetException(ex); OR _promiseOfValueOrEnd.SetException(ex); + GenerateReturn(false)), // return; + isSynthesizedAsyncCatchAll: true); + } } protected virtual BoundStatement GenerateTopLevelTry(BoundBlock tryBlock, ImmutableArray catchBlocks) @@ -223,48 +261,13 @@ protected virtual BoundStatement GenerateSetResultCall() : ImmutableArray.Empty)); } - protected BoundCatchBlock GenerateExceptionHandling(LocalSymbol exceptionLocal, ImmutableArray hoistedLocals) - { - // catch (Exception ex) - // { - // _state = finishedState; - // - // for each hoisted local: - // <>x__y = default - // - // builder.SetException(ex); OR if (this.combinedTokens != null) this.combinedTokens.Dispose(); _promiseOfValueOrEnd.SetException(ex); /* for async-iterator method */ - // return; - // } - - // _state = finishedState; - BoundStatement assignFinishedState = - F.ExpressionStatement(F.AssignmentExpression(F.Field(F.This(), stateField), F.Literal(StateMachineState.FinishedState))); - - // builder.SetException(ex); OR if (this.combinedTokens != null) this.combinedTokens.Dispose(); _promiseOfValueOrEnd.SetException(ex); - BoundStatement callSetException = GenerateSetExceptionCall(exceptionLocal); - - return new BoundCatchBlock( - F.Syntax, - ImmutableArray.Create(exceptionLocal), - F.Local(exceptionLocal), - exceptionLocal.Type, - exceptionFilterPrologueOpt: null, - exceptionFilterOpt: null, - body: F.Block( - assignFinishedState, // _state = finishedState; - GenerateHoistedLocalsCleanup(hoistedLocals), - callSetException, // builder.SetException(ex); OR _promiseOfValueOrEnd.SetException(ex); - GenerateReturn(false)), // return; - isSynthesizedAsyncCatchAll: true); - } - - protected BoundStatement GenerateHoistedLocalsCleanup(ImmutableArray hoistedLocals) + protected virtual BoundStatement GenerateCleanupForExit(ImmutableArray rootHoistedLocals) { var builder = ArrayBuilder.GetInstance(); // Cleanup all hoisted local variables // so that they can be collected by GC if needed - foreach (var hoistedLocal in hoistedLocals) + foreach (var hoistedLocal in rootHoistedLocals) { var useSiteInfo = new CompoundUseSiteInfo(F.Diagnostics, F.Compilation.Assembly); var isManagedType = hoistedLocal.Type.IsManagedType(ref useSiteInfo); diff --git a/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncRewriter.AsyncIteratorRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncRewriter.AsyncIteratorRewriter.cs index 6bde60bf03281..82a95a335b015 100644 --- a/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncRewriter.AsyncIteratorRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncRewriter.AsyncIteratorRewriter.cs @@ -703,6 +703,7 @@ protected override void GenerateMoveNext(SynthesizedImplementationMethod moveNex instanceIdField: instanceIdField, hoistedVariables: hoistedVariables, nonReusableLocalProxies: nonReusableLocalProxies, + nonReusableFieldsForCleanup: nonReusableFieldsForCleanup, synthesizedLocalOrdinals: synthesizedLocalOrdinals, stateMachineStateDebugInfoBuilder, slotAllocatorOpt: slotAllocatorOpt, diff --git a/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncRewriter.cs index 7e948c47dccab..1d911bc7155d3 100644 --- a/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncRewriter.cs @@ -286,6 +286,7 @@ protected virtual void GenerateMoveNext(SynthesizedImplementationMethod moveNext instanceIdField: instanceIdField, hoistedVariables: hoistedVariables, nonReusableLocalProxies: nonReusableLocalProxies, + nonReusableFieldsForCleanup: nonReusableFieldsForCleanup, synthesizedLocalOrdinals: synthesizedLocalOrdinals, stateMachineStateDebugInfoBuilder, slotAllocatorOpt: slotAllocatorOpt, diff --git a/src/Compilers/CSharp/Portable/Lowering/IteratorRewriter/IteratorMethodToStateMachineRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/IteratorRewriter/IteratorMethodToStateMachineRewriter.cs index 606cde97b5066..f022c87dd9e40 100644 --- a/src/Compilers/CSharp/Portable/Lowering/IteratorRewriter/IteratorMethodToStateMachineRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/IteratorRewriter/IteratorMethodToStateMachineRewriter.cs @@ -61,12 +61,13 @@ internal IteratorMethodToStateMachineRewriter( FieldSymbol? instanceIdField, IReadOnlySet hoistedVariables, IReadOnlyDictionary nonReusableLocalProxies, + ImmutableArray nonReusableFieldsForCleanup, SynthesizedLocalOrdinalsDispenser synthesizedLocalOrdinals, ArrayBuilder stateMachineStateDebugInfoBuilder, VariableSlotAllocator slotAllocatorOpt, int nextFreeHoistedLocalSlot, BindingDiagnosticBag diagnostics) - : base(F, originalMethod, state, instanceIdField, hoistedVariables, nonReusableLocalProxies, synthesizedLocalOrdinals, stateMachineStateDebugInfoBuilder, slotAllocatorOpt, nextFreeHoistedLocalSlot, diagnostics) + : base(F, originalMethod, state, instanceIdField, hoistedVariables, nonReusableLocalProxies, nonReusableFieldsForCleanup, synthesizedLocalOrdinals, stateMachineStateDebugInfoBuilder, slotAllocatorOpt, nextFreeHoistedLocalSlot, diagnostics) { _current = current; @@ -160,7 +161,11 @@ internal void GenerateMoveNextAndDispose(BoundStatement body, SynthesizedImpleme if (rootFrame.knownStates == null) { // nothing to finalize - F.CloseMethod(F.Return()); + var disposeBody = F.Block( + GenerateAllHoistedLocalsCleanup(), + F.Return()); + + F.CloseMethod(disposeBody); } else { @@ -171,6 +176,7 @@ internal void GenerateMoveNextAndDispose(BoundStatement body, SynthesizedImpleme ImmutableArray.Create(stateLocal), F.Assignment(F.Local(stateLocal), F.Field(F.This(), stateField)), EmitFinallyFrame(rootFrame, state), + GenerateAllHoistedLocalsCleanup(), F.Return()); F.CloseMethod(disposeBody); diff --git a/src/Compilers/CSharp/Portable/Lowering/IteratorRewriter/IteratorRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/IteratorRewriter/IteratorRewriter.cs index 9d444a3eeeaa6..815f497c82f42 100644 --- a/src/Compilers/CSharp/Portable/Lowering/IteratorRewriter/IteratorRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/IteratorRewriter/IteratorRewriter.cs @@ -335,6 +335,7 @@ private void GenerateMoveNextAndDispose( instanceIdField, hoistedVariables, nonReusableLocalProxies, + nonReusableFieldsForCleanup, synthesizedLocalOrdinals, stateMachineStateDebugInfoBuilder, slotAllocatorOpt, diff --git a/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/MethodToStateMachineRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/MethodToStateMachineRewriter.cs index 1fe789a5a9a69..4221e9de09565 100644 --- a/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/MethodToStateMachineRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/MethodToStateMachineRewriter.cs @@ -61,7 +61,7 @@ internal abstract class MethodToStateMachineRewriter : MethodToClassRewriter /// Note that there is a dispatch occurring at every try-finally statement, so this /// variable takes on a new set of values inside each try block. /// - private Dictionary> _dispatches = new(); + private Dictionary> _dispatches = new Dictionary>(); /// /// A pool of fields used to hoist locals. They appear in this set when not in scope, @@ -70,15 +70,15 @@ internal abstract class MethodToStateMachineRewriter : MethodToClassRewriter private Dictionary>? _lazyAvailableReusableHoistedFields; /// - /// Fields allocated for temporary variables are given unique names distinguished by a number at the end. - /// This counter ensures they are unique within a given translated method. + /// We collect all the hoisted fields for locals, so that we can clear them so the GC can collect references. /// - private int _nextHoistedFieldId = 1; + private readonly ArrayBuilder _fieldsForCleanup; /// - /// Used to enumerate the instance fields of a struct. + /// Fields allocated for temporary variables are given unique names distinguished by a number at the end. + /// This counter ensures they are unique within a given translated method. /// - private readonly EmptyStructTypeCache _emptyStructTypeCache = EmptyStructTypeCache.CreateNeverEmpty(); + private int _nextHoistedFieldId = 1; /// /// The set of local variables and parameters that were hoisted and need a proxy. @@ -104,6 +104,7 @@ public MethodToStateMachineRewriter( FieldSymbol? instanceIdField, IReadOnlySet hoistedVariables, IReadOnlyDictionary nonReusableLocalProxies, + ImmutableArray nonReusableFieldsForCleanup, SynthesizedLocalOrdinalsDispenser synthesizedLocalOrdinals, ArrayBuilder stateMachineStateDebugInfoBuilder, VariableSlotAllocator? slotAllocatorOpt, @@ -115,6 +116,7 @@ public MethodToStateMachineRewriter( Debug.Assert(originalMethod != null); Debug.Assert(state != null); Debug.Assert(nonReusableLocalProxies != null); + Debug.Assert(!nonReusableFieldsForCleanup.IsDefault); Debug.Assert(diagnostics != null); Debug.Assert(hoistedVariables != null); Debug.Assert(nextFreeHoistedLocalSlot >= 0); @@ -133,6 +135,9 @@ public MethodToStateMachineRewriter( this.proxies.Add(proxy.Key, proxy.Value); } + _fieldsForCleanup = new ArrayBuilder(nonReusableFieldsForCleanup.Length); + _fieldsForCleanup.AddRange(nonReusableFieldsForCleanup); + // create cache local for reference type "this" in Release var thisParameter = originalMethod.ThisParameter; CapturedSymbolReplacement? thisProxy; @@ -428,15 +433,35 @@ internal static bool TryUnwrapBoundStateMachineScope(ref BoundStatement statemen /// private void AddVariableCleanup(ArrayBuilder cleanup, FieldSymbol field) { - if (field.Type.IsManagedTypeNoUseSiteDiagnostics) + var useSiteInfo = new CompoundUseSiteInfo(F.Diagnostics, F.Compilation.Assembly); + bool isManaged = field.Type.IsManagedType(ref useSiteInfo); + F.Diagnostics.Add(field.GetFirstLocationOrNone(), useSiteInfo); + if (isManaged) { cleanup.Add(F.AssignmentExpression(F.Field(F.This(), field), F.NullOrDefault(field.Type))); } } - private StateMachineFieldSymbol GetOrAllocateReusableHoistedField(TypeSymbol type, out bool reused, LocalSymbol local = null) +#nullable enable + protected BoundBlock GenerateAllHoistedLocalsCleanup() { - ArrayBuilder fields; + var variableCleanup = ArrayBuilder.GetInstance(); + + foreach (FieldSymbol fieldSymbol in _fieldsForCleanup) + { + AddVariableCleanup(variableCleanup, fieldSymbol); + } + + var result = F.Block(variableCleanup.SelectAsArray((e, f) => (BoundStatement)f.ExpressionStatement(e), F)); + + variableCleanup.Free(); + + return result; + } + + private StateMachineFieldSymbol GetOrAllocateReusableHoistedField(TypeSymbol type, out bool reused, LocalSymbol? local = null) + { + ArrayBuilder? fields; if (_lazyAvailableReusableHoistedFields != null && _lazyAvailableReusableHoistedFields.TryGetValue(type, out fields) && fields.Count > 0) { var field = fields.Last(); @@ -448,14 +473,21 @@ private StateMachineFieldSymbol GetOrAllocateReusableHoistedField(TypeSymbol typ reused = false; var slotIndex = _nextHoistedFieldId++; + StateMachineFieldSymbol createdField; if (local?.SynthesizedKind == SynthesizedLocalKind.UserDefined) { string fieldName = GeneratedNames.MakeHoistedLocalFieldName(SynthesizedLocalKind.UserDefined, slotIndex, local.Name); - return F.StateMachineField(type, fieldName, SynthesizedLocalKind.UserDefined, slotIndex); + createdField = F.StateMachineField(type, fieldName, SynthesizedLocalKind.UserDefined, slotIndex); + } + else + { + createdField = F.StateMachineField(type, GeneratedNames.ReusableHoistedLocalFieldName(slotIndex)); } - return F.StateMachineField(type, GeneratedNames.ReusableHoistedLocalFieldName(slotIndex)); + _fieldsForCleanup.Add(createdField); + return createdField; } +#nullable disable private void FreeReusableHoistedField(StateMachineFieldSymbol field) { @@ -671,6 +703,7 @@ private BoundExpression HoistExpression( string fieldName = GeneratedNames.MakeHoistedLocalFieldName(kind, slotIndex); hoistedField = F.StateMachineField(expr.Type, fieldName, new LocalSlotDebugInfo(kind, id), slotIndex); + _fieldsForCleanup.Add(hoistedField); } else { diff --git a/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/StateMachineRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/StateMachineRewriter.cs index 4720e12ee1dfb..a6911a94549f4 100644 --- a/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/StateMachineRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/StateMachineRewriter.cs @@ -9,7 +9,6 @@ using Microsoft.CodeAnalysis.CodeGen; using Microsoft.CodeAnalysis.Collections; using Microsoft.CodeAnalysis.CSharp.Symbols; -using Microsoft.CodeAnalysis.Emit; using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; @@ -29,6 +28,7 @@ internal abstract class StateMachineRewriter protected FieldSymbol? stateField; protected FieldSymbol? instanceIdField; protected IReadOnlyDictionary? nonReusableLocalProxies; + protected ImmutableArray nonReusableFieldsForCleanup; protected int nextFreeHoistedLocalSlot; protected IOrderedReadOnlySet? hoistedVariables; protected Dictionary? initialParameters; @@ -123,7 +123,7 @@ protected BoundStatement Rewrite() return new BoundBadStatement(F.Syntax, ImmutableArray.Empty, hasErrors: true); } - CreateNonReusableLocalProxies(variablesToHoist, out this.nonReusableLocalProxies, out this.nextFreeHoistedLocalSlot); + CreateNonReusableLocalProxies(variablesToHoist, out this.nonReusableLocalProxies, out this.nonReusableFieldsForCleanup, out this.nextFreeHoistedLocalSlot); this.hoistedVariables = variablesToHoist; @@ -136,9 +136,11 @@ protected BoundStatement Rewrite() private void CreateNonReusableLocalProxies( IEnumerable variablesToHoist, out IReadOnlyDictionary proxies, + out ImmutableArray nonReusableFieldsForCleanup, out int nextFreeHoistedLocalSlot) { var proxiesBuilder = new Dictionary(); + var nonReusableFieldsForCleanupBuilder = ArrayBuilder.GetInstance(); var typeMap = stateMachineType.TypeMap; bool isDebugBuild = F.Compilation.Options.OptimizationLevel == OptimizationLevel.Debug; @@ -221,6 +223,7 @@ private void CreateNonReusableLocalProxies( string fieldName = GeneratedNames.MakeHoistedLocalFieldName(synthesizedKind, slotIndex, local.Name); field = F.StateMachineField(fieldType, fieldName, new LocalSlotDebugInfo(synthesizedKind, id), slotIndex); + nonReusableFieldsForCleanupBuilder.Add(field); } if (field != null) @@ -262,6 +265,7 @@ private void CreateNonReusableLocalProxies( } proxies = proxiesBuilder; + nonReusableFieldsForCleanup = nonReusableFieldsForCleanupBuilder.ToImmutableAndFree(); } private bool ShouldPreallocateNonReusableProxy(LocalSymbol local) diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncIteratorTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncIteratorTests.cs index 890e20d8e09c2..4459d0c3cc867 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncIteratorTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncIteratorTests.cs @@ -182,7 +182,7 @@ public static async Task Main() var v = CompileAndVerify(comp, expectedOutput: "hello world"); v.VerifyIL("C.d__1.System.Runtime.CompilerServices.IAsyncStateMachine.MoveNext()", @" { - // Code size 254 (0xfe) + // Code size 268 (0x10c) .maxstack 3 .locals init (int V_0, System.Exception V_1) @@ -199,7 +199,7 @@ .locals init (int V_0, IL_0010: ldarg.0 IL_0011: ldfld ""bool C.d__1.<>w__disposeMode"" IL_0016: brfalse.s IL_001d - IL_0018: leave IL_00d4 + IL_0018: leave IL_00db IL_001d: ldarg.0 IL_001e: ldc.i4.m1 IL_001f: dup @@ -267,11 +267,11 @@ .locals init (int V_0, IL_0096: ldarg.0 IL_0097: ldfld ""bool C.d__1.<>w__disposeMode"" IL_009c: brfalse.s IL_00a0 - IL_009e: leave.s IL_00d4 + IL_009e: leave.s IL_00db IL_00a0: ldarg.0 IL_00a1: ldc.i4.1 IL_00a2: stfld ""bool C.d__1.<>w__disposeMode"" - IL_00a7: leave.s IL_00d4 + IL_00a7: leave.s IL_00db } catch System.Exception { @@ -281,35 +281,41 @@ .locals init (int V_0, IL_00ad: stfld ""int C.d__1.<>1__state"" IL_00b2: ldarg.0 IL_00b3: ldnull - IL_00b4: stfld ""string C.d__1.<>2__current"" + IL_00b4: stfld ""object C.d__1.<>s__1"" IL_00b9: ldarg.0 - IL_00ba: ldflda ""System.Runtime.CompilerServices.AsyncIteratorMethodBuilder C.d__1.<>t__builder"" - IL_00bf: call ""void System.Runtime.CompilerServices.AsyncIteratorMethodBuilder.Complete()"" - IL_00c4: nop - IL_00c5: ldarg.0 - IL_00c6: ldflda ""System.Threading.Tasks.Sources.ManualResetValueTaskSourceCore C.d__1.<>v__promiseOfValueOrEnd"" - IL_00cb: ldloc.1 - IL_00cc: call ""void System.Threading.Tasks.Sources.ManualResetValueTaskSourceCore.SetException(System.Exception)"" - IL_00d1: nop - IL_00d2: leave.s IL_00fd + IL_00ba: ldnull + IL_00bb: stfld ""string C.d__1.<>2__current"" + IL_00c0: ldarg.0 + IL_00c1: ldflda ""System.Runtime.CompilerServices.AsyncIteratorMethodBuilder C.d__1.<>t__builder"" + IL_00c6: call ""void System.Runtime.CompilerServices.AsyncIteratorMethodBuilder.Complete()"" + IL_00cb: nop + IL_00cc: ldarg.0 + IL_00cd: ldflda ""System.Threading.Tasks.Sources.ManualResetValueTaskSourceCore C.d__1.<>v__promiseOfValueOrEnd"" + IL_00d2: ldloc.1 + IL_00d3: call ""void System.Threading.Tasks.Sources.ManualResetValueTaskSourceCore.SetException(System.Exception)"" + IL_00d8: nop + IL_00d9: leave.s IL_010b } - IL_00d4: ldarg.0 - IL_00d5: ldc.i4.s -2 - IL_00d7: stfld ""int C.d__1.<>1__state"" - IL_00dc: ldarg.0 - IL_00dd: ldnull - IL_00de: stfld ""string C.d__1.<>2__current"" + IL_00db: ldarg.0 + IL_00dc: ldc.i4.s -2 + IL_00de: stfld ""int C.d__1.<>1__state"" IL_00e3: ldarg.0 - IL_00e4: ldflda ""System.Runtime.CompilerServices.AsyncIteratorMethodBuilder C.d__1.<>t__builder"" - IL_00e9: call ""void System.Runtime.CompilerServices.AsyncIteratorMethodBuilder.Complete()"" - IL_00ee: nop - IL_00ef: ldarg.0 - IL_00f0: ldflda ""System.Threading.Tasks.Sources.ManualResetValueTaskSourceCore C.d__1.<>v__promiseOfValueOrEnd"" - IL_00f5: ldc.i4.0 - IL_00f6: call ""void System.Threading.Tasks.Sources.ManualResetValueTaskSourceCore.SetResult(bool)"" - IL_00fb: nop - IL_00fc: ret - IL_00fd: ret + IL_00e4: ldnull + IL_00e5: stfld ""object C.d__1.<>s__1"" + IL_00ea: ldarg.0 + IL_00eb: ldnull + IL_00ec: stfld ""string C.d__1.<>2__current"" + IL_00f1: ldarg.0 + IL_00f2: ldflda ""System.Runtime.CompilerServices.AsyncIteratorMethodBuilder C.d__1.<>t__builder"" + IL_00f7: call ""void System.Runtime.CompilerServices.AsyncIteratorMethodBuilder.Complete()"" + IL_00fc: nop + IL_00fd: ldarg.0 + IL_00fe: ldflda ""System.Threading.Tasks.Sources.ManualResetValueTaskSourceCore C.d__1.<>v__promiseOfValueOrEnd"" + IL_0103: ldc.i4.0 + IL_0104: call ""void System.Threading.Tasks.Sources.ManualResetValueTaskSourceCore.SetResult(bool)"" + IL_0109: nop + IL_010a: ret + IL_010b: ret }"); } @@ -9448,8 +9454,11 @@ public void AddVariableCleanup_StringLocal() using System.Reflection; var values = C.Produce(); -await foreach (int value in values) { } -System.Console.Write(((string)values.GetType().GetField("5__2", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(values))); +await foreach (int value in values) +{ + System.Console.Write(((string)values.GetType().GetField("5__2", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(values))); +} +System.Console.Write(((string)values.GetType().GetField("5__2", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(values)) is null); class C { @@ -9462,8 +9471,234 @@ public static async System.Collections.Generic.IAsyncEnumerable Produce() } } """; - // Note: hoisted top-level local does not get cleared when exiting normally - CompileAndVerify(src, expectedOutput: ExpectedOutput("value value"), verify: Verification.Skipped, targetFramework: TargetFramework.Net80).VerifyDiagnostics(); + var verifier = CompileAndVerify(src, expectedOutput: ExpectedOutput("value value True"), verify: Verification.Skipped, targetFramework: TargetFramework.Net80).VerifyDiagnostics(); + verifier.VerifyIL("C.d__0.System.Runtime.CompilerServices.IAsyncStateMachine.MoveNext()", """ +{ + // Code size 320 (0x140) + .maxstack 3 + .locals init (int V_0, + System.Runtime.CompilerServices.TaskAwaiter V_1, + C.d__0 V_2, + System.Exception V_3) + IL_0000: ldarg.0 + IL_0001: ldfld "int C.d__0.<>1__state" + IL_0006: stloc.0 + .try + { + IL_0007: ldloc.0 + IL_0008: ldc.i4.s -4 + IL_000a: sub + IL_000b: switch ( + IL_00b5, + IL_0024, + IL_0024, + IL_0024, + IL_007f) + IL_0024: ldarg.0 + IL_0025: ldfld "bool C.d__0.<>w__disposeMode" + IL_002a: brfalse.s IL_0031 + IL_002c: leave IL_0105 + IL_0031: ldarg.0 + IL_0032: ldc.i4.m1 + IL_0033: dup + IL_0034: stloc.0 + IL_0035: stfld "int C.d__0.<>1__state" + IL_003a: ldarg.0 + IL_003b: ldstr "value " + IL_0040: stfld "string C.d__0.5__2" + IL_0045: call "System.Threading.Tasks.Task System.Threading.Tasks.Task.CompletedTask.get" + IL_004a: callvirt "System.Runtime.CompilerServices.TaskAwaiter System.Threading.Tasks.Task.GetAwaiter()" + IL_004f: stloc.1 + IL_0050: ldloca.s V_1 + IL_0052: call "bool System.Runtime.CompilerServices.TaskAwaiter.IsCompleted.get" + IL_0057: brtrue.s IL_009b + IL_0059: ldarg.0 + IL_005a: ldc.i4.0 + IL_005b: dup + IL_005c: stloc.0 + IL_005d: stfld "int C.d__0.<>1__state" + IL_0062: ldarg.0 + IL_0063: ldloc.1 + IL_0064: stfld "System.Runtime.CompilerServices.TaskAwaiter C.d__0.<>u__1" + IL_0069: ldarg.0 + IL_006a: stloc.2 + IL_006b: ldarg.0 + IL_006c: ldflda "System.Runtime.CompilerServices.AsyncIteratorMethodBuilder C.d__0.<>t__builder" + IL_0071: ldloca.s V_1 + IL_0073: ldloca.s V_2 + IL_0075: call "void System.Runtime.CompilerServices.AsyncIteratorMethodBuilder.AwaitUnsafeOnCompletedd__0>(ref System.Runtime.CompilerServices.TaskAwaiter, ref C.d__0)" + IL_007a: leave IL_013f + IL_007f: ldarg.0 + IL_0080: ldfld "System.Runtime.CompilerServices.TaskAwaiter C.d__0.<>u__1" + IL_0085: stloc.1 + IL_0086: ldarg.0 + IL_0087: ldflda "System.Runtime.CompilerServices.TaskAwaiter C.d__0.<>u__1" + IL_008c: initobj "System.Runtime.CompilerServices.TaskAwaiter" + IL_0092: ldarg.0 + IL_0093: ldc.i4.m1 + IL_0094: dup + IL_0095: stloc.0 + IL_0096: stfld "int C.d__0.<>1__state" + IL_009b: ldloca.s V_1 + IL_009d: call "void System.Runtime.CompilerServices.TaskAwaiter.GetResult()" + IL_00a2: ldarg.0 + IL_00a3: ldc.i4.1 + IL_00a4: stfld "int C.d__0.<>2__current" + IL_00a9: ldarg.0 + IL_00aa: ldc.i4.s -4 + IL_00ac: dup + IL_00ad: stloc.0 + IL_00ae: stfld "int C.d__0.<>1__state" + IL_00b3: leave.s IL_0133 + IL_00b5: ldarg.0 + IL_00b6: ldc.i4.m1 + IL_00b7: dup + IL_00b8: stloc.0 + IL_00b9: stfld "int C.d__0.<>1__state" + IL_00be: ldarg.0 + IL_00bf: ldfld "bool C.d__0.<>w__disposeMode" + IL_00c4: brfalse.s IL_00c8 + IL_00c6: leave.s IL_0105 + IL_00c8: ldarg.0 + IL_00c9: ldfld "string C.d__0.5__2" + IL_00ce: call "void System.Console.Write(string)" + IL_00d3: leave.s IL_0105 + } + catch System.Exception + { + IL_00d5: stloc.3 + IL_00d6: ldarg.0 + IL_00d7: ldc.i4.s -2 + IL_00d9: stfld "int C.d__0.<>1__state" + IL_00de: ldarg.0 + IL_00df: ldnull + IL_00e0: stfld "string C.d__0.5__2" + IL_00e5: ldarg.0 + IL_00e6: ldc.i4.0 + IL_00e7: stfld "int C.d__0.<>2__current" + IL_00ec: ldarg.0 + IL_00ed: ldflda "System.Runtime.CompilerServices.AsyncIteratorMethodBuilder C.d__0.<>t__builder" + IL_00f2: call "void System.Runtime.CompilerServices.AsyncIteratorMethodBuilder.Complete()" + IL_00f7: ldarg.0 + IL_00f8: ldflda "System.Threading.Tasks.Sources.ManualResetValueTaskSourceCore C.d__0.<>v__promiseOfValueOrEnd" + IL_00fd: ldloc.3 + IL_00fe: call "void System.Threading.Tasks.Sources.ManualResetValueTaskSourceCore.SetException(System.Exception)" + IL_0103: leave.s IL_013f + } + IL_0105: ldarg.0 + IL_0106: ldc.i4.s -2 + IL_0108: stfld "int C.d__0.<>1__state" + IL_010d: ldarg.0 + IL_010e: ldnull + IL_010f: stfld "string C.d__0.5__2" + IL_0114: ldarg.0 + IL_0115: ldc.i4.0 + IL_0116: stfld "int C.d__0.<>2__current" + IL_011b: ldarg.0 + IL_011c: ldflda "System.Runtime.CompilerServices.AsyncIteratorMethodBuilder C.d__0.<>t__builder" + IL_0121: call "void System.Runtime.CompilerServices.AsyncIteratorMethodBuilder.Complete()" + IL_0126: ldarg.0 + IL_0127: ldflda "System.Threading.Tasks.Sources.ManualResetValueTaskSourceCore C.d__0.<>v__promiseOfValueOrEnd" + IL_012c: ldc.i4.0 + IL_012d: call "void System.Threading.Tasks.Sources.ManualResetValueTaskSourceCore.SetResult(bool)" + IL_0132: ret + IL_0133: ldarg.0 + IL_0134: ldflda "System.Threading.Tasks.Sources.ManualResetValueTaskSourceCore C.d__0.<>v__promiseOfValueOrEnd" + IL_0139: ldc.i4.1 + IL_013a: call "void System.Threading.Tasks.Sources.ManualResetValueTaskSourceCore.SetResult(bool)" + IL_013f: ret +} +"""); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75666")] + public void AddVariableCleanup_StringLocal_YieldBreak() + { + string src = """ +using System.Reflection; + +var values = C.Produce(true); +await foreach (int value in values) +{ + System.Console.Write(((string)values.GetType().GetField("5__2", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(values))); +} +System.Console.Write(((string)values.GetType().GetField("5__2", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(values)) is null); + +class C +{ + public static async System.Collections.Generic.IAsyncEnumerable Produce(bool b) + { + string s = "value "; + await System.Threading.Tasks.Task.CompletedTask; + yield return 42; + System.Console.Write(s); + if (b) yield break; + throw null; + } +} +"""; + CompileAndVerify(src, expectedOutput: ExpectedOutput("value value True"), verify: Verification.Skipped, targetFramework: TargetFramework.Net80).VerifyDiagnostics(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75666")] + public void AddVariableCleanup_StringLocal_ThrownException() + { + string src = """ +using System.Reflection; + +var values = C.Produce(true); +try +{ + await foreach (int value in values) { } +} +catch (System.Exception e) +{ + System.Console.Write(e.Message); +} + +System.Console.Write(((string)values.GetType().GetField("5__2", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(values)) is null); + +class C +{ + public static async System.Collections.Generic.IAsyncEnumerable Produce(bool b) + { + string s = "value "; + await System.Threading.Tasks.Task.CompletedTask; + System.Console.Write(s); + if (b) throw new System.Exception("exception "); + yield break; + } +} +"""; + CompileAndVerify(src, expectedOutput: ExpectedOutput("value exception True"), verify: Verification.Skipped, targetFramework: TargetFramework.Net80).VerifyDiagnostics(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75666")] + public void AddVariableCleanup_StringLocal_EarlyIterationExit() + { + string src = """ +using System.Reflection; + +var values = C.Produce(true); +await foreach (var value in values) +{ + System.Console.Write(((string)values.GetType().GetField("5__2", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(values))); + break; +} +System.Console.Write(((string)values.GetType().GetField("5__2", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(values)) is null); + +class C +{ + public static async System.Collections.Generic.IAsyncEnumerable Produce(bool b) + { + string s = "value "; + await System.Threading.Tasks.Task.CompletedTask; + yield return 42; + _ = s.ToString(); + } +} +"""; + CompileAndVerify(src, expectedOutput: ExpectedOutput("value True"), verify: Verification.Skipped, targetFramework: TargetFramework.Net80).VerifyDiagnostics(); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75666")] @@ -9477,6 +9712,8 @@ public void AddVariableCleanup_NestedStringLocal() var enumerator = values.GetAsyncEnumerator(); assert(await enumerator.MoveNextAsync()); assert(enumerator.Current == 1); +System.Console.Write(((string)values.GetType().GetField("5__2", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(values))); + assert(await enumerator.MoveNextAsync()); assert(enumerator.Current == 2); _ = enumerator.MoveNextAsync(); @@ -9505,7 +9742,124 @@ public static async System.Collections.Generic.IAsyncEnumerable Produce(boo } } """; - // Note: hoisted nested local gets cleared when exiting nested scope normally + CompileAndVerify(src, expectedOutput: ExpectedOutput("value value True"), verify: Verification.Skipped, targetFramework: TargetFramework.Net80).VerifyDiagnostics(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75666")] + public void AddVariableCleanup_NestedStringLocal_YieldBreak() + { + string src = """ +using System.Reflection; + +var values = C.Produce(true); +var enumerator = values.GetAsyncEnumerator(); +assert(await enumerator.MoveNextAsync()); +assert(enumerator.Current == 1); +System.Console.Write(((string)values.GetType().GetField("5__2", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(values))); +assert(!(await enumerator.MoveNextAsync())); + +System.Console.Write(((string)values.GetType().GetField("5__2", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(values)) is null); + +void assert(bool b) +{ + if (!b) throw new System.Exception(); +} + +class C +{ + public static async System.Collections.Generic.IAsyncEnumerable Produce(bool b) + { + while (b) + { + string s = "value "; + yield return 1; + System.Console.Write(s); + await System.Threading.Tasks.Task.CompletedTask; + if (b) yield break; + } + throw null; + } +} +"""; + CompileAndVerify(src, expectedOutput: ExpectedOutput("value value True"), verify: Verification.Skipped, targetFramework: TargetFramework.Net80).VerifyDiagnostics(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75666")] + public void AddVariableCleanup_NestedStringLocal_ThrownException() + { + string src = """ +using System.Reflection; + +var values = C.Produce(true); +var enumerator = values.GetAsyncEnumerator(); +assert(await enumerator.MoveNextAsync()); +assert(enumerator.Current == 1); +try +{ + assert(!(await enumerator.MoveNextAsync())); +} +catch (System.Exception e) +{ + System.Console.Write(e.Message); +} +await enumerator.DisposeAsync(); + +System.Console.Write(((string)values.GetType().GetField("5__2", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(values)) is null); + +void assert(bool b) +{ + if (!b) throw new System.Exception(); +} + +class C +{ + public static async System.Collections.Generic.IAsyncEnumerable Produce(bool b) + { + while (b) + { + string s = "value "; + yield return 1; + System.Console.Write(s); + await System.Threading.Tasks.Task.CompletedTask; + if (b) throw new System.Exception("exception "); + } + throw null; + } +} +"""; + CompileAndVerify(src, expectedOutput: ExpectedOutput("value exception True"), verify: Verification.Skipped, targetFramework: TargetFramework.Net80).VerifyDiagnostics(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75666")] + public void AddVariableCleanup_NestedStringLocal_EarlyIterationExit() + { + string src = """ +using System.Reflection; + +var values = C.Produce(true); +await foreach (var value in values) +{ + System.Console.Write(((string)values.GetType().GetField("5__2", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(values))); + break; +} +System.Console.Write(((string)values.GetType().GetField("5__2", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(values)) is null); + +class C +{ + public static async System.Collections.Generic.IAsyncEnumerable Produce(bool b) + { + while (b) + { + string s = "value "; + yield return 1; + System.Console.Write(s); + await System.Threading.Tasks.Task.CompletedTask; + throw null; + } + throw null; + } +} +"""; CompileAndVerify(src, expectedOutput: ExpectedOutput("value True"), verify: Verification.Skipped, targetFramework: TargetFramework.Net80).VerifyDiagnostics(); } @@ -9743,5 +10097,439 @@ .locals init (int V_0, } """); } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75666")] + public void AddVariableCleanup_NestedStringLocal_InTryFinally_WithThrow_EarlyIterationExit() + { + string src = """ +using System.Reflection; + +var values = C.Produce(true); +try +{ + await foreach (var value in values) { break; } // we interrupt the iteration early +} +catch (System.Exception e) +{ + System.Console.Write(e.Message); +} + +System.Console.Write(((string)values.GetType().GetField("5__2", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(values)) is null); + +class C +{ + public static async System.Collections.Generic.IAsyncEnumerable Produce(bool b) + { + try + { + string s = "value "; + await System.Threading.Tasks.Task.CompletedTask; + yield return 42; + s.ToString(); + throw null; + } + finally + { + throw new System.Exception("exception "); + } + } +} +"""; + CompileAndVerify(src, expectedOutput: ExpectedOutput("exception True"), verify: Verification.Skipped, targetFramework: TargetFramework.Net80).VerifyDiagnostics(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75666")] + public void AddVariableCleanup_StringLocal_IAsyncEnumerator() + { + string src = """ +using System.Reflection; + +var values = C.Produce(); +assert(await values.MoveNextAsync()); +assert(values.Current == 1); +System.Console.Write(((string)values.GetType().GetField("5__2", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(values))); +assert(!(await values.MoveNextAsync())); +await values.DisposeAsync(); + +System.Console.Write(((string)values.GetType().GetField("5__2", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(values)) is null); + +static void assert(bool b) { if (!b) throw new System.Exception(); } + +class C +{ + public static async System.Collections.Generic.IAsyncEnumerator Produce() + { + string s = "value "; + await System.Threading.Tasks.Task.CompletedTask; + yield return 1; + System.Console.Write(s); + } +} +"""; + CompileAndVerify(src, expectedOutput: ExpectedOutput("value value True"), verify: Verification.Skipped, targetFramework: TargetFramework.Net80).VerifyDiagnostics(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75666")] + public void AddVariableCleanup_NotCleanedTooSoon() + { + string src = """ +using System.Reflection; + +var values = C.Produce(); +await foreach (int i in values) +{ + System.Console.Write((values.GetType().GetField("5__2", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(values))); + break; +} +System.Console.Write((values.GetType().GetField("5__2", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(values)) is null); + +class C +{ + public static async System.Collections.Generic.IAsyncEnumerable Produce() + { + try + { + string s = "value "; + try + { + await System.Threading.Tasks.Task.CompletedTask; + yield return 42; + } + finally + { + System.Console.Write(s); + } + } + finally + { + System.Console.Write("outer "); + } + } +} +"""; + CompileAndVerify(src, expectedOutput: ExpectedOutput("value value outer True"), verify: Verification.Skipped, targetFramework: TargetFramework.Net80).VerifyDiagnostics(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75666")] + public void AddVariableCleanup_HoistedFromRefExpression() + { + string src = """ +using System.Reflection; + +var c = new C(); +var values = Program.Produce(c); +await foreach (int i in values) +{ + System.Console.Write((values.GetType().GetField("<>7__wrap3", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(values)) is null); + System.Console.Write($" {i} "); +} +System.Console.Write((values.GetType().GetField("<>7__wrap3", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(values)) is null); + +partial class Program +{ + public static async System.Collections.Generic.IAsyncEnumerable Produce(C x) + { + int i = 0; + foreach (var y in x.F) + { + await System.Threading.Tasks.Task.CompletedTask; + yield return i++; + } + } +} + +class C +{ + public Buffer2 F = default; +} + +[System.Runtime.CompilerServices.InlineArray(2)] +public struct Buffer2 +{ + private T _element0; +} +"""; + CompileAndVerify(src, expectedOutput: ExpectedOutput("False 0 False 1 True"), verify: Verification.Skipped, targetFramework: TargetFramework.Net80).VerifyDiagnostics(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75666")] + public void AddVariableCleanup_HoistedFromRefExpression_Debug() + { + string src = """ +using System.Reflection; + +var c = new C(); +var values = Program.Produce(c); +await foreach (int i in values) +{ + System.Console.Write((values.GetType().GetField("<>s__4", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(values)) is null); + System.Console.Write($" {i} "); +} +System.Console.Write((values.GetType().GetField("<>s__4", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(values)) is null); + +partial class Program +{ + public static async System.Collections.Generic.IAsyncEnumerable Produce(C x) + { + int i = 0; + foreach (var y in x.F) + { + await System.Threading.Tasks.Task.CompletedTask; + yield return i++; + } + } +} + +class C +{ + public Buffer2 F = default; +} + +[System.Runtime.CompilerServices.InlineArray(2)] +public struct Buffer2 +{ + private T _element0; +} +"""; + var verifier = CompileAndVerify(src, expectedOutput: ExpectedOutput("False 0 False 1 True"), + verify: Verification.Skipped, targetFramework: TargetFramework.Net80, options: TestOptions.DebugExe); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("Program.d__1.System.Runtime.CompilerServices.IAsyncStateMachine.MoveNext()", """ +{ + // Code size 449 (0x1c1) + .maxstack 4 + .locals init (int V_0, + System.Runtime.CompilerServices.TaskAwaiter V_1, + Program.d__1 V_2, + int V_3, + System.Exception V_4) + IL_0000: ldarg.0 + IL_0001: ldfld "int Program.d__1.<>1__state" + IL_0006: stloc.0 + .try + { + IL_0007: ldloc.0 + IL_0008: ldc.i4.s -4 + IL_000a: sub + IL_000b: switch ( + IL_0026, + IL_002b, + IL_0032, + IL_0032, + IL_002d) + IL_0024: br.s IL_0032 + IL_0026: br IL_0118 + IL_002b: br.s IL_0032 + IL_002d: br IL_00ce + IL_0032: ldarg.0 + IL_0033: ldfld "bool Program.d__1.<>w__disposeMode" + IL_0038: brfalse.s IL_003f + IL_003a: leave IL_0183 + IL_003f: ldarg.0 + IL_0040: ldc.i4.m1 + IL_0041: dup + IL_0042: stloc.0 + IL_0043: stfld "int Program.d__1.<>1__state" + IL_0048: nop + IL_0049: ldarg.0 + IL_004a: ldc.i4.0 + IL_004b: stfld "int Program.d__1.5__1" + IL_0050: nop + IL_0051: ldarg.0 + IL_0052: ldarg.0 + IL_0053: ldfld "C Program.d__1.x" + IL_0058: stfld "C Program.d__1.<>s__4" + IL_005d: ldarg.0 + IL_005e: ldfld "C Program.d__1.<>s__4" + IL_0063: ldfld "Buffer2 C.F" + IL_0068: pop + IL_0069: ldarg.0 + IL_006a: ldc.i4.0 + IL_006b: stfld "int Program.d__1.<>s__2" + IL_0070: br IL_013a + IL_0075: ldarg.0 + IL_0076: ldarg.0 + IL_0077: ldfld "C Program.d__1.<>s__4" + IL_007c: ldflda "Buffer2 C.F" + IL_0081: ldarg.0 + IL_0082: ldfld "int Program.d__1.<>s__2" + IL_0087: call "ref int .InlineArrayElementRef, int>(ref Buffer2, int)" + IL_008c: ldind.i4 + IL_008d: stfld "int Program.d__1.5__3" + IL_0092: nop + IL_0093: call "System.Threading.Tasks.Task System.Threading.Tasks.Task.CompletedTask.get" + IL_0098: callvirt "System.Runtime.CompilerServices.TaskAwaiter System.Threading.Tasks.Task.GetAwaiter()" + IL_009d: stloc.1 + IL_009e: ldloca.s V_1 + IL_00a0: call "bool System.Runtime.CompilerServices.TaskAwaiter.IsCompleted.get" + IL_00a5: brtrue.s IL_00ea + IL_00a7: ldarg.0 + IL_00a8: ldc.i4.0 + IL_00a9: dup + IL_00aa: stloc.0 + IL_00ab: stfld "int Program.d__1.<>1__state" + IL_00b0: ldarg.0 + IL_00b1: ldloc.1 + IL_00b2: stfld "System.Runtime.CompilerServices.TaskAwaiter Program.d__1.<>u__1" + IL_00b7: ldarg.0 + IL_00b8: stloc.2 + IL_00b9: ldarg.0 + IL_00ba: ldflda "System.Runtime.CompilerServices.AsyncIteratorMethodBuilder Program.d__1.<>t__builder" + IL_00bf: ldloca.s V_1 + IL_00c1: ldloca.s V_2 + IL_00c3: call "void System.Runtime.CompilerServices.AsyncIteratorMethodBuilder.AwaitUnsafeOnCompletedd__1>(ref System.Runtime.CompilerServices.TaskAwaiter, ref Program.d__1)" + IL_00c8: nop + IL_00c9: leave IL_01c0 + IL_00ce: ldarg.0 + IL_00cf: ldfld "System.Runtime.CompilerServices.TaskAwaiter Program.d__1.<>u__1" + IL_00d4: stloc.1 + IL_00d5: ldarg.0 + IL_00d6: ldflda "System.Runtime.CompilerServices.TaskAwaiter Program.d__1.<>u__1" + IL_00db: initobj "System.Runtime.CompilerServices.TaskAwaiter" + IL_00e1: ldarg.0 + IL_00e2: ldc.i4.m1 + IL_00e3: dup + IL_00e4: stloc.0 + IL_00e5: stfld "int Program.d__1.<>1__state" + IL_00ea: ldloca.s V_1 + IL_00ec: call "void System.Runtime.CompilerServices.TaskAwaiter.GetResult()" + IL_00f1: nop + IL_00f2: ldarg.0 + IL_00f3: ldarg.0 + IL_00f4: ldfld "int Program.d__1.5__1" + IL_00f9: stloc.3 + IL_00fa: ldarg.0 + IL_00fb: ldloc.3 + IL_00fc: ldc.i4.1 + IL_00fd: add + IL_00fe: stfld "int Program.d__1.5__1" + IL_0103: ldloc.3 + IL_0104: stfld "int Program.d__1.<>2__current" + IL_0109: ldarg.0 + IL_010a: ldc.i4.s -4 + IL_010c: dup + IL_010d: stloc.0 + IL_010e: stfld "int Program.d__1.<>1__state" + IL_0113: leave IL_01b3 + IL_0118: ldarg.0 + IL_0119: ldc.i4.m1 + IL_011a: dup + IL_011b: stloc.0 + IL_011c: stfld "int Program.d__1.<>1__state" + IL_0121: ldarg.0 + IL_0122: ldfld "bool Program.d__1.<>w__disposeMode" + IL_0127: brfalse.s IL_012b + IL_0129: leave.s IL_0183 + IL_012b: nop + IL_012c: ldarg.0 + IL_012d: ldarg.0 + IL_012e: ldfld "int Program.d__1.<>s__2" + IL_0133: ldc.i4.1 + IL_0134: add + IL_0135: stfld "int Program.d__1.<>s__2" + IL_013a: ldarg.0 + IL_013b: ldfld "int Program.d__1.<>s__2" + IL_0140: ldc.i4.2 + IL_0141: blt IL_0075 + IL_0146: ldarg.0 + IL_0147: ldnull + IL_0148: stfld "C Program.d__1.<>s__4" + IL_014d: leave.s IL_0183 + } + catch System.Exception + { + IL_014f: stloc.s V_4 + IL_0151: ldarg.0 + IL_0152: ldc.i4.s -2 + IL_0154: stfld "int Program.d__1.<>1__state" + IL_0159: ldarg.0 + IL_015a: ldnull + IL_015b: stfld "C Program.d__1.<>s__4" + IL_0160: ldarg.0 + IL_0161: ldc.i4.0 + IL_0162: stfld "int Program.d__1.<>2__current" + IL_0167: ldarg.0 + IL_0168: ldflda "System.Runtime.CompilerServices.AsyncIteratorMethodBuilder Program.d__1.<>t__builder" + IL_016d: call "void System.Runtime.CompilerServices.AsyncIteratorMethodBuilder.Complete()" + IL_0172: nop + IL_0173: ldarg.0 + IL_0174: ldflda "System.Threading.Tasks.Sources.ManualResetValueTaskSourceCore Program.d__1.<>v__promiseOfValueOrEnd" + IL_0179: ldloc.s V_4 + IL_017b: call "void System.Threading.Tasks.Sources.ManualResetValueTaskSourceCore.SetException(System.Exception)" + IL_0180: nop + IL_0181: leave.s IL_01c0 + } + IL_0183: ldarg.0 + IL_0184: ldc.i4.s -2 + IL_0186: stfld "int Program.d__1.<>1__state" + IL_018b: ldarg.0 + IL_018c: ldnull + IL_018d: stfld "C Program.d__1.<>s__4" + IL_0192: ldarg.0 + IL_0193: ldc.i4.0 + IL_0194: stfld "int Program.d__1.<>2__current" + IL_0199: ldarg.0 + IL_019a: ldflda "System.Runtime.CompilerServices.AsyncIteratorMethodBuilder Program.d__1.<>t__builder" + IL_019f: call "void System.Runtime.CompilerServices.AsyncIteratorMethodBuilder.Complete()" + IL_01a4: nop + IL_01a5: ldarg.0 + IL_01a6: ldflda "System.Threading.Tasks.Sources.ManualResetValueTaskSourceCore Program.d__1.<>v__promiseOfValueOrEnd" + IL_01ab: ldc.i4.0 + IL_01ac: call "void System.Threading.Tasks.Sources.ManualResetValueTaskSourceCore.SetResult(bool)" + IL_01b1: nop + IL_01b2: ret + IL_01b3: ldarg.0 + IL_01b4: ldflda "System.Threading.Tasks.Sources.ManualResetValueTaskSourceCore Program.d__1.<>v__promiseOfValueOrEnd" + IL_01b9: ldc.i4.1 + IL_01ba: call "void System.Threading.Tasks.Sources.ManualResetValueTaskSourceCore.SetResult(bool)" + IL_01bf: nop + IL_01c0: ret +} +"""); + } + + [Fact] + public void AddVariableCleanup_Unmanaged_UseSiteError() + { + var missingLibS1 = CreateCompilation(@" +public struct S1 +{ + public int i; +} +", assemblyName: "libS1", targetFramework: TargetFramework.Net80).ToMetadataReference(); + + var libS2 = CreateCompilation(@" +public struct S2 +{ + public S1 s1; +} +", references: [missingLibS1], assemblyName: "libS2", targetFramework: TargetFramework.Net80).ToMetadataReference(); + + var source = @" +class C +{ + public static async System.Collections.Generic.IAsyncEnumerable M(S2 p) + { + S2 local = p; + await System.Threading.Tasks.Task.CompletedTask; + yield return local; + System.Console.Write(local); + } +} +"; + var comp = CreateCompilation(source, references: [libS2], targetFramework: TargetFramework.Net80); + comp.VerifyEmitDiagnostics( + // error CS0012: The type 'S1' is defined in an assembly that is not referenced. You must add a reference to assembly 'libS1, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'. + Diagnostic(ErrorCode.ERR_NoTypeDef).WithArguments("S1", "libS1, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null").WithLocation(1, 1), + // error CS0012: The type 'S1' is defined in an assembly that is not referenced. You must add a reference to assembly 'libS1, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'. + Diagnostic(ErrorCode.ERR_NoTypeDef).WithArguments("S1", "libS1, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null").WithLocation(1, 1), + // error CS0012: The type 'S1' is defined in an assembly that is not referenced. You must add a reference to assembly 'libS1, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'. + Diagnostic(ErrorCode.ERR_NoTypeDef).WithArguments("S1", "libS1, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null").WithLocation(1, 1), + // error CS0012: The type 'S1' is defined in an assembly that is not referenced. You must add a reference to assembly 'libS1, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'. + Diagnostic(ErrorCode.ERR_NoTypeDef).WithArguments("S1", "libS1, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null").WithLocation(1, 1)); + + comp = CreateCompilation(source, options: TestOptions.UnsafeDebugDll, references: [libS2, missingLibS1], targetFramework: TargetFramework.Net80); + comp.VerifyEmitDiagnostics(); + } } } diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenDisplayClassOptimisationTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenDisplayClassOptimisationTests.cs index 03941adc0d1c2..9de4494e5794c 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenDisplayClassOptimisationTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenDisplayClassOptimisationTests.cs @@ -7742,15 +7742,24 @@ 01 00 00 00 ) .override method instance void [mscorlib]System.IDisposable::Dispose() // Method begins at RVA 0x20b1 - // Code size 1 (0x1) + // Code size 22 (0x16) .maxstack 8 - IL_0000: ret + IL_0000: ldarg.0 + IL_0001: ldnull + IL_0002: stfld class C/'<>c__DisplayClass0_0' C/'d__0'::'<>8__1' + IL_0007: ldarg.0 + IL_0008: ldnull + IL_0009: stfld class C/'<>c__DisplayClass0_1' C/'d__0'::'<>8__2' + IL_000e: ldarg.0 + IL_000f: ldnull + IL_0010: stfld class C/'<>c__DisplayClass0_2' C/'d__0'::'<>8__3' + IL_0015: ret } // end of method 'd__0'::System.IDisposable.Dispose .method private final hidebysig newslot virtual instance bool MoveNext () cil managed { .override method instance bool [mscorlib]System.Collections.IEnumerator::MoveNext() - // Method begins at RVA 0x20b4 + // Method begins at RVA 0x20c8 // Code size 342 (0x156) .maxstack 2 .locals init ( @@ -7889,7 +7898,7 @@ .method private final hidebysig specialname newslot virtual 01 00 00 00 ) .override method instance !0 class [mscorlib]System.Collections.Generic.IEnumerator`1::get_Current() - // Method begins at RVA 0x2216 + // Method begins at RVA 0x222a // Code size 7 (0x7) .maxstack 8 IL_0000: ldarg.0 @@ -7903,7 +7912,7 @@ instance void System.Collections.IEnumerator.Reset () cil managed 01 00 00 00 ) .override method instance void [mscorlib]System.Collections.IEnumerator::Reset() - // Method begins at RVA 0x221e + // Method begins at RVA 0x2232 // Code size 6 (0x6) .maxstack 8 IL_0000: newobj instance void [mscorlib]System.NotSupportedException::.ctor() @@ -7916,7 +7925,7 @@ instance object System.Collections.IEnumerator.get_Current () cil managed 01 00 00 00 ) .override method instance object [mscorlib]System.Collections.IEnumerator::get_Current() - // Method begins at RVA 0x2225 + // Method begins at RVA 0x2239 // Code size 12 (0xc) .maxstack 8 IL_0000: ldarg.0 @@ -7931,7 +7940,7 @@ .method private final hidebysig newslot virtual 01 00 00 00 ) .override method instance class [mscorlib]System.Collections.Generic.IEnumerator`1 class [mscorlib]System.Collections.Generic.IEnumerable`1::GetEnumerator() - // Method begins at RVA 0x2234 + // Method begins at RVA 0x2248 // Code size 43 (0x2b) .maxstack 2 .locals init ( @@ -7964,7 +7973,7 @@ instance class [mscorlib]System.Collections.IEnumerator System.Collections.IEnum 01 00 00 00 ) .override method instance class [mscorlib]System.Collections.IEnumerator [mscorlib]System.Collections.IEnumerable::GetEnumerator() - // Method begins at RVA 0x226b + // Method begins at RVA 0x227f // Code size 7 (0x7) .maxstack 8 IL_0000: ldarg.0 diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenIterators.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenIterators.cs index f4ad723ea8864..5ab7b180a4923 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenIterators.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenIterators.cs @@ -3022,7 +3022,14 @@ public static System.Collections.Generic.IEnumerable Produce() } } """; - CompileAndVerify(src, expectedOutput: "42 42").VerifyDiagnostics(); + var verifier = CompileAndVerify(src, expectedOutput: "42 42").VerifyDiagnostics(); + verifier.VerifyIL("C.d__0.System.IDisposable.Dispose()", """ +{ + // Code size 1 (0x1) + .maxstack 0 + IL_0000: ret +} +"""); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75666")] @@ -3032,33 +3039,85 @@ public void AddVariableCleanup_StringLocal() using System.Reflection; var values = C.Produce(); -foreach (int value in values) { } -System.Console.Write(((string)values.GetType().GetField("5__2", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(values))); +foreach (int value in values) +{ + System.Console.Write(((string)values.GetType().GetField("5__2", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(values))); +} +System.Console.Write(((string)values.GetType().GetField("5__2", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(values)) is null); class C { public static System.Collections.Generic.IEnumerable Produce() { - string values2 = "ran"; + string values2 = "ran "; yield return 42; - System.Console.Write($"{values2} "); + System.Console.Write(values2); + } +} +"""; + var verifier = CompileAndVerify(src, expectedOutput: "ran ran True").VerifyDiagnostics(); + verifier.VerifyIL("C.d__0.System.IDisposable.Dispose()", """ +{ + // Code size 8 (0x8) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldnull + IL_0002: stfld "string C.d__0.5__2" + IL_0007: ret +} +"""); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75666")] + public void AddVariableCleanup_StringLocal_YieldBreak() + { + string src = """ +using System.Reflection; + +var values = C.Produce(); +foreach (int value in values) +{ + System.Console.Write(((string)values.GetType().GetField("5__2", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(values))); +} +System.Console.Write(((string)values.GetType().GetField("5__2", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(values)) is null); + +class C +{ + public static System.Collections.Generic.IEnumerable Produce() + { + string s = "ran "; + yield return 42; + System.Console.Write(s); + yield break; } } """; - // Note: top-level hoisted local does not get cleared when exiting normally - CompileAndVerify(src, expectedOutput: "ran ran").VerifyDiagnostics(); + var verifier = CompileAndVerify(src, expectedOutput: "ran ran True").VerifyDiagnostics(); + verifier.VerifyIL("C.d__0.System.IDisposable.Dispose()", """ +{ + // Code size 8 (0x8) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldnull + IL_0002: stfld "string C.d__0.5__2" + IL_0007: ret +} +"""); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75666")] - public void AddVariableCleanup_IntArrayLocalAndForeachLocals() + public void AddVariableCleanup_IntArrayLocal() { string source = """ using System.Linq; using System.Reflection; var values = C.Produce(); -foreach (int value in values) { } -System.Console.Write(((int[])values.GetType().GetField("5__2", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(values)).Length); +foreach (int value in values) +{ + System.Console.Write(((int[])values.GetType().GetField("5__2", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(values)).Length); +} +System.Console.Write(((int[])values.GetType().GetField("5__2", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(values)) is null); class C { @@ -3066,13 +3125,22 @@ public static System.Collections.Generic.IEnumerable Produce() { int[] values2 = Enumerable.Range(0, 100).ToArray(); yield return 42; - System.Console.Write($"{values2.Length} "); + System.Console.Write($" {values2.Length} "); } } """; - // Note: top-level hoisted local does not get cleared when exiting normally var comp = CreateCompilation(source); - CompileAndVerify(comp, expectedOutput: "100 100").VerifyDiagnostics(); + var verifier = CompileAndVerify(comp, expectedOutput: "100 100 True").VerifyDiagnostics(); + verifier.VerifyIL("C.d__0.System.IDisposable.Dispose()", """ +{ + // Code size 8 (0x8) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldnull + IL_0002: stfld "int[] C.d__0.5__2" + IL_0007: ret +} +"""); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75666")] @@ -3087,6 +3155,7 @@ public void AddVariableCleanup_NestedStringLocal() { assert(enumerator.MoveNext()); System.Console.Write($"{enumerator.Current} "); + System.Console.Write(((string)enumerator.GetType().GetField("5__2", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(enumerator))); assert(!enumerator.MoveNext()); System.Console.Write(((string)enumerator.GetType().GetField("5__2", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(enumerator)) is null); } @@ -3114,30 +3183,39 @@ public static System.Collections.Generic.IEnumerable M(bool b) } } """; - // Note: nested hoisted local gets cleared when exiting normally - CompileAndVerify(src, expectedOutput: "42 value True").VerifyDiagnostics(); + var verifier = CompileAndVerify(src, expectedOutput: "42 value value True").VerifyDiagnostics(); + verifier.VerifyIL("C.d__0.System.IDisposable.Dispose()", """ +{ + // Code size 8 (0x8) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldnull + IL_0002: stfld "string C.d__0.5__2" + IL_0007: ret +} +"""); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75666")] - public void AddVariableCleanup_NestedUnmanagedTypeParameterLocal() + public void AddVariableCleanup_NestedStringLocal_YieldBreak() { var src = """ using System.Reflection; -var enumerable = C.M(true, 42); +var enumerable = C.M(true); var enumerator = enumerable.GetEnumerator(); try { assert(enumerator.MoveNext()); System.Console.Write($"{enumerator.Current} "); assert(!enumerator.MoveNext()); - System.Console.Write(" "); - System.Console.Write(((int)enumerator.GetType().GetField("5__2", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(enumerator))); + System.Console.Write(((string)enumerator.GetType().GetField("5__2", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(enumerator))); } finally { enumerator.Dispose(); } +System.Console.Write(((string)enumerator.GetType().GetField("5__2", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(enumerator)) is null); void assert(bool b) { @@ -3146,104 +3224,50 @@ void assert(bool b) public class C { - public static System.Collections.Generic.IEnumerable M(bool b, T t) where T : unmanaged + public static System.Collections.Generic.IEnumerable M(bool b) { while (b) { - T local = t; - yield return 10; - b = false; - System.Console.Write(local); + string s = "value "; + yield return 42; + System.Console.Write(s); + yield break; } } } """; - var verifier = CompileAndVerify(src, expectedOutput: "10 42 42").VerifyDiagnostics(); - verifier.VerifyIL("C.d__0.System.Collections.IEnumerator.MoveNext()", """ + var verifier = CompileAndVerify(src, expectedOutput: "42 value value True").VerifyDiagnostics(); + verifier.VerifyIL("C.d__0.System.IDisposable.Dispose()", """ { - // Code size 94 (0x5e) + // Code size 8 (0x8) .maxstack 2 - .locals init (int V_0) IL_0000: ldarg.0 - IL_0001: ldfld "int C.d__0.<>1__state" - IL_0006: stloc.0 - IL_0007: ldloc.0 - IL_0008: brfalse.s IL_0010 - IL_000a: ldloc.0 - IL_000b: ldc.i4.1 - IL_000c: beq.s IL_0036 - IL_000e: ldc.i4.0 - IL_000f: ret - IL_0010: ldarg.0 - IL_0011: ldc.i4.m1 - IL_0012: stfld "int C.d__0.<>1__state" - IL_0017: br.s IL_0054 - IL_0019: ldarg.0 - IL_001a: ldarg.0 - IL_001b: ldfld "T C.d__0.t" - IL_0020: stfld "T C.d__0.5__2" - IL_0025: ldarg.0 - IL_0026: ldc.i4.s 10 - IL_0028: stfld "int C.d__0.<>2__current" - IL_002d: ldarg.0 - IL_002e: ldc.i4.1 - IL_002f: stfld "int C.d__0.<>1__state" - IL_0034: ldc.i4.1 - IL_0035: ret - IL_0036: ldarg.0 - IL_0037: ldc.i4.m1 - IL_0038: stfld "int C.d__0.<>1__state" - IL_003d: ldarg.0 - IL_003e: ldc.i4.0 - IL_003f: stfld "bool C.d__0.b" - IL_0044: ldarg.0 - IL_0045: ldfld "T C.d__0.5__2" - IL_004a: box "T" - IL_004f: call "void System.Console.Write(object)" - IL_0054: ldarg.0 - IL_0055: ldfld "bool C.d__0.b" - IL_005a: brtrue.s IL_0019 - IL_005c: ldc.i4.0 - IL_005d: ret -} -"""); - verifier.VerifyIL("C.d__0.System.IDisposable.Dispose()", """ -{ - // Code size 1 (0x1) - .maxstack 0 - IL_0000: ret + IL_0001: ldnull + IL_0002: stfld "string C.d__0.5__2" + IL_0007: ret } """); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75666")] - public void AddVariableCleanup_NestedLocalWithStructFromAnotherCompilation() + public void AddVariableCleanup_NestedStringLocal_ThrownException() { - var libSrc = """ -public struct S -{ - public int field; - public override string ToString() => field.ToString(); -} -"""; - var libComp = CreateCompilation(libSrc); var src = """ using System.Reflection; -var enumerable = C.M(true, new S { field = 42 }); +var enumerable = C.M(true); var enumerator = enumerable.GetEnumerator(); try { assert(enumerator.MoveNext()); System.Console.Write($"{enumerator.Current} "); - assert(!enumerator.MoveNext()); - System.Console.Write(" "); - System.Console.Write(((S)enumerator.GetType().GetField("5__2", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(enumerator))); } finally { + System.Console.Write(((string)enumerable.GetType().GetField("5__2", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(enumerable))); enumerator.Dispose(); } +System.Console.Write(((string)enumerable.GetType().GetField("5__2", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(enumerable)) is null); void assert(bool b) { @@ -3252,70 +3276,73 @@ void assert(bool b) public class C { - public static System.Collections.Generic.IEnumerable M(bool b, S s) + public static System.Collections.Generic.IEnumerable M(bool b) { while (b) { - S local = s; - yield return 10; - b = false; - System.Console.Write(local.ToString()); + string s = "value "; + yield return 42; + System.Console.Write(s); + throw new System.Exception(); } } } """; + CompileAndVerify(src, expectedOutput: "42 value True").VerifyDiagnostics(); + } - var verifier = CompileAndVerify(src, expectedOutput: "10 42 42", references: [libComp.EmitToImageReference()]).VerifyDiagnostics(); - verifier.VerifyIL("C.d__0.System.Collections.IEnumerator.MoveNext()", """ + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75666")] + public void AddVariableCleanup_IntParameter() + { + string src = """ +using System.Reflection; + +var values = C.Produce(42); +foreach (int value in values) { } +System.Console.Write(((int)values.GetType().GetField("s", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(values))); +System.Console.Write(((int)values.GetType().GetField("<>3__s", BindingFlags.Public | BindingFlags.Instance).GetValue(values))); + +class C { - // Code size 100 (0x64) - .maxstack 2 - .locals init (int V_0) - IL_0000: ldarg.0 - IL_0001: ldfld "int C.d__0.<>1__state" - IL_0006: stloc.0 - IL_0007: ldloc.0 - IL_0008: brfalse.s IL_0010 - IL_000a: ldloc.0 - IL_000b: ldc.i4.1 - IL_000c: beq.s IL_0036 - IL_000e: ldc.i4.0 - IL_000f: ret - IL_0010: ldarg.0 - IL_0011: ldc.i4.m1 - IL_0012: stfld "int C.d__0.<>1__state" - IL_0017: br.s IL_005a - IL_0019: ldarg.0 - IL_001a: ldarg.0 - IL_001b: ldfld "S C.d__0.s" - IL_0020: stfld "S C.d__0.5__2" - IL_0025: ldarg.0 - IL_0026: ldc.i4.s 10 - IL_0028: stfld "int C.d__0.<>2__current" - IL_002d: ldarg.0 - IL_002e: ldc.i4.1 - IL_002f: stfld "int C.d__0.<>1__state" - IL_0034: ldc.i4.1 - IL_0035: ret - IL_0036: ldarg.0 - IL_0037: ldc.i4.m1 - IL_0038: stfld "int C.d__0.<>1__state" - IL_003d: ldarg.0 - IL_003e: ldc.i4.0 - IL_003f: stfld "bool C.d__0.b" - IL_0044: ldarg.0 - IL_0045: ldflda "S C.d__0.5__2" - IL_004a: constrained. "S" - IL_0050: callvirt "string object.ToString()" - IL_0055: call "void System.Console.Write(string)" - IL_005a: ldarg.0 - IL_005b: ldfld "bool C.d__0.b" - IL_0060: brtrue.s IL_0019 - IL_0062: ldc.i4.0 - IL_0063: ret + public static System.Collections.Generic.IEnumerable Produce(int s) + { + yield return 0; + System.Console.Write($"{s} "); + } +} +"""; + var verifier = CompileAndVerify(src, expectedOutput: "42 4242").VerifyDiagnostics(); + verifier.VerifyIL("C.d__0.System.IDisposable.Dispose()", """ +{ + // Code size 1 (0x1) + .maxstack 0 + IL_0000: ret } """); - verifier.VerifyIL("C.d__0.System.IDisposable.Dispose()", """ + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75666")] + public void AddVariableCleanup_StringParameter() + { + string src = """ +using System.Reflection; + +var values = C.Produce("value "); +foreach (int value in values) { } +System.Console.Write(((string)values.GetType().GetField("s", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(values))); +System.Console.Write(((string)values.GetType().GetField("<>3__s", BindingFlags.Public | BindingFlags.Instance).GetValue(values))); + +class C +{ + public static System.Collections.Generic.IEnumerable Produce(string s) + { + yield return 0; + System.Console.Write(s); + } +} +"""; + var verifier = CompileAndVerify(src, expectedOutput: "value value value").VerifyDiagnostics(); + verifier.VerifyIL("C.d__0.System.IDisposable.Dispose()", """ { // Code size 1 (0x1) .maxstack 0 @@ -3325,50 +3352,773 @@ .maxstack 0 } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75666")] - public void AddVariableCleanup_NestedUnmanagedWithGenericsLocal() + public void AddVariableCleanup_ClosureOverLocal() { - var src = """ + string src = """ using System.Reflection; -var enumerable = C.M(true, 42); -var enumerator = enumerable.GetEnumerator(); -try +var values = C.Produce(); +foreach (int value in values) { } +var closure = values.GetType().GetField("<>8__1", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(values); +System.Console.Write((int)(closure.GetType().GetField("s", BindingFlags.Public | BindingFlags.Instance).GetValue(closure))); + +class C { - assert(enumerator.MoveNext()); - System.Console.Write($"{enumerator.Current} "); - assert(!enumerator.MoveNext()); - System.Console.Write(((S)enumerator.GetType().GetField("5__2", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(enumerator)).field); + public static System.Collections.Generic.IEnumerable Produce() + { + int s = 41; + local(); + yield return 0; + System.Console.Write($"{s} "); + + void local() + { + s++; + } + } } -finally +"""; + var verifier = CompileAndVerify(src, expectedOutput: "42 42").VerifyDiagnostics(); + verifier.VerifyIL("C.d__0.System.IDisposable.Dispose()", """ { - enumerator.Dispose(); + // Code size 1 (0x1) + .maxstack 0 + IL_0000: ret } +"""); + } -void assert(bool b) + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75666")] + public void AddVariableCleanup_ClosureOverThis() + { + string src = """ +using System.Reflection; + +class C { - if (!b) throw new System.Exception(); + int field = 41; + public static void Main() + { + var values = new C().Produce(); + foreach (int value in values) { } + System.Console.Write(((C)values.GetType().GetField("<>4__this", BindingFlags.Public | BindingFlags.Instance).GetValue(values)).field); + } + + private System.Collections.Generic.IEnumerable Produce() + { + local(); + yield return this.field; + + void local() + { + this.field++; + } + } +} +"""; + var verifier = CompileAndVerify(src, expectedOutput: "42").VerifyDiagnostics(); + verifier.VerifyIL("C.d__2.System.IDisposable.Dispose()", """ +{ + // Code size 1 (0x1) + .maxstack 0 + IL_0000: ret } +"""); + } -public struct S where T : unmanaged // UnmanagedWithGenerics + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75666")] + public void AddVariableCleanup_NestedStringLocal_InTryFinally() + { + string src = """ +using System.Reflection; + +var values = C.Produce(); +foreach (int value in values) { - public T field; + System.Console.Write(((string)values.GetType().GetField("5__2", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(values))); } +System.Console.Write(((string)values.GetType().GetField("5__2", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(values)) is null); -public class C +class C { - public static System.Collections.Generic.IEnumerable M(bool b, T t) where T : unmanaged + public static System.Collections.Generic.IEnumerable Produce() { - while (b) + try { - S s = new S { field = t }; - yield return 42; - b = false; - System.Console.Write(s.field); + var s = "value "; + yield return 0; + System.Console.Write(s); + } + finally + { + System.Console.Write("ran "); } } } """; - CompileAndVerify(src, expectedOutput: "42 4242").VerifyDiagnostics(); + var verifier = CompileAndVerify(src, expectedOutput: "value value ran True").VerifyDiagnostics(); + verifier.VerifyIL("C.d__0.System.IDisposable.Dispose()", """ +{ + // Code size 34 (0x22) + .maxstack 2 + .locals init (int V_0) + IL_0000: ldarg.0 + IL_0001: ldfld "int C.d__0.<>1__state" + IL_0006: stloc.0 + IL_0007: ldloc.0 + IL_0008: ldc.i4.s -3 + IL_000a: beq.s IL_0010 + IL_000c: ldloc.0 + IL_000d: ldc.i4.1 + IL_000e: bne.un.s IL_001a + IL_0010: nop + .try + { + IL_0011: leave.s IL_001a + } + finally + { + IL_0013: ldarg.0 + IL_0014: call "void C.d__0.<>m__Finally1()" + IL_0019: endfinally + } + IL_001a: ldarg.0 + IL_001b: ldnull + IL_001c: stfld "string C.d__0.5__2" + IL_0021: ret +} +"""); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75666")] + public void AddVariableCleanup_NestedStringLocal_InTryFinally_WithThrow() + { + string src = """ +using System.Reflection; + +var values = C.Produce(); + +try +{ + foreach (int value in values) + { + System.Console.Write(((string)values.GetType().GetField("5__2", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(values))); + } +} +catch (System.Exception e) +{ + System.Console.Write(e.Message); +} + +System.Console.Write(((string)values.GetType().GetField("5__2", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(values)) is null); + +class C +{ + public static System.Collections.Generic.IEnumerable Produce() + { + try + { + string s = "value "; + yield return 0; + System.Console.Write(s); + } + finally + { + throw new System.Exception("exception "); + } + } +} +"""; + var verifier = CompileAndVerify(src, expectedOutput: "value value exception True").VerifyDiagnostics(); + verifier.VerifyIL("C.d__0.System.IDisposable.Dispose()", """ +{ + // Code size 34 (0x22) + .maxstack 2 + .locals init (int V_0) + IL_0000: ldarg.0 + IL_0001: ldfld "int C.d__0.<>1__state" + IL_0006: stloc.0 + IL_0007: ldloc.0 + IL_0008: ldc.i4.s -3 + IL_000a: beq.s IL_0010 + IL_000c: ldloc.0 + IL_000d: ldc.i4.1 + IL_000e: bne.un.s IL_001a + IL_0010: nop + .try + { + IL_0011: leave.s IL_001a + } + finally + { + IL_0013: ldarg.0 + IL_0014: call "void C.d__0.<>m__Finally1()" + IL_0019: endfinally + } + IL_001a: ldarg.0 + IL_001b: ldnull + IL_001c: stfld "string C.d__0.5__2" + IL_0021: ret +} +"""); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75666")] + public void AddVariableCleanup_NestedStringLocal_InTryFinally_WithThrow_EarlyIterationExit() + { + string src = """ +using System.Reflection; + +var values = C.Produce(); + +try +{ + foreach (int value in values) { break; } // we interrupt the iteration early +} +catch (System.Exception e) +{ + System.Console.Write(e.Message); +} + +System.Console.Write(((string)values.GetType().GetField("5__2", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(values))); + +class C +{ + public static System.Collections.Generic.IEnumerable Produce() + { + try + { + string s = "value"; + yield return 0; + s.ToString(); + throw null; + } + finally + { + throw new System.Exception("exception "); + } + } +} +"""; + // Note: nested hoisted local does not get cleared when an exception is thrown during disposal + CompileAndVerify(src, expectedOutput: "exception value").VerifyDiagnostics(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75666")] + public void AddVariableCleanup_StringLocal_WithThrownException() + { + string src = """ +using System.Reflection; + +var values = C.Produce(true); + +try +{ + foreach (int value in values) { } +} +catch (System.Exception e) +{ + System.Console.Write(e.Message); +} + +System.Console.Write(((string)values.GetType().GetField("5__2", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(values)) is null); + +class C +{ + public static System.Collections.Generic.IEnumerable Produce(bool b) + { + string s = "value "; + if (b) throw new System.Exception("exception "); + yield return 0; + System.Console.Write(s); + } +} +"""; + CompileAndVerify(src, expectedOutput: "exception True").VerifyDiagnostics(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75666")] + public void AddVariableCleanup_NestedUnmanagedTypeParameterLocal() + { + var src = """ +using System.Reflection; + +var enumerable = C.M(true, 42); +var enumerator = enumerable.GetEnumerator(); +try +{ + assert(enumerator.MoveNext()); + System.Console.Write($"{enumerator.Current} "); + assert(!enumerator.MoveNext()); + System.Console.Write(" "); + System.Console.Write(((int)enumerator.GetType().GetField("5__2", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(enumerator))); +} +finally +{ + enumerator.Dispose(); +} + +void assert(bool b) +{ + if (!b) throw new System.Exception(); +} + +public class C +{ + public static System.Collections.Generic.IEnumerable M(bool b, T t) where T : unmanaged + { + while (b) + { + T local = t; + yield return 10; + b = false; + System.Console.Write(local); + } + } +} +"""; + var verifier = CompileAndVerify(src, expectedOutput: "10 42 42").VerifyDiagnostics(); + verifier.VerifyIL("C.d__0.System.IDisposable.Dispose()", """ +{ + // Code size 1 (0x1) + .maxstack 0 + IL_0000: ret +} +"""); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75666")] + public void AddVariableCleanup_NestedLocalWithStructFromAnotherCompilation() + { + var libSrc = """ +public struct S +{ + public int field; + public override string ToString() => field.ToString(); +} +"""; + var libComp = CreateCompilation(libSrc); + var src = """ +using System.Reflection; + +var enumerable = C.M(true, new S { field = 42 }); +var enumerator = enumerable.GetEnumerator(); +try +{ + assert(enumerator.MoveNext()); + System.Console.Write($"{enumerator.Current} "); + assert(!enumerator.MoveNext()); + System.Console.Write(" "); + System.Console.Write(((S)enumerator.GetType().GetField("5__2", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(enumerator))); +} +finally +{ + enumerator.Dispose(); +} + +void assert(bool b) +{ + if (!b) throw new System.Exception(); +} + +public class C +{ + public static System.Collections.Generic.IEnumerable M(bool b, S s) + { + while (b) + { + S local = s; + yield return 10; + b = false; + System.Console.Write(local.ToString()); + } + } +} +"""; + + var verifier = CompileAndVerify(src, expectedOutput: "10 42 42", references: [libComp.EmitToImageReference()]).VerifyDiagnostics(); + verifier.VerifyIL("C.d__0.System.IDisposable.Dispose()", """ +{ + // Code size 1 (0x1) + .maxstack 0 + IL_0000: ret +} +"""); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75666")] + public void AddVariableCleanup_NestedUnmanagedWithGenericsLocal() + { + var src = """ +using System.Reflection; + +var enumerable = C.M(true, 42); +var enumerator = enumerable.GetEnumerator(); +try +{ + assert(enumerator.MoveNext()); + System.Console.Write($"{enumerator.Current} "); + assert(!enumerator.MoveNext()); + System.Console.Write(((S)enumerator.GetType().GetField("5__2", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(enumerator)).field); +} +finally +{ + enumerator.Dispose(); +} + +void assert(bool b) +{ + if (!b) throw new System.Exception(); +} + +public struct S where T : unmanaged // UnmanagedWithGenerics +{ + public T field; +} + +public class C +{ + public static System.Collections.Generic.IEnumerable M(bool b, T t) where T : unmanaged + { + while (b) + { + S s = new S { field = t }; + yield return 42; + b = false; + System.Console.Write(s.field); + } + } +} +"""; + CompileAndVerify(src, expectedOutput: "42 4242").VerifyDiagnostics(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75666")] + public void AddVariableCleanup_StringLocal_EarlyIterationExit() + { + string src = """ +using System.Reflection; + +var values = C.Produce(); + +foreach (int value in values) +{ + System.Console.Write(((string)values.GetType().GetField("5__2", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(values))); + break; +} + +System.Console.Write(((string)values.GetType().GetField("5__2", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(values)) is null); + +class C +{ + public static System.Collections.Generic.IEnumerable Produce() + { + string s = "value "; + yield return 0; + s.ToString(); + throw null; + } +} +"""; + CompileAndVerify(src, expectedOutput: "value True").VerifyDiagnostics(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75666")] + public void AddVariableCleanup_NestedStringLocal_Reused() + { + string src = """ +using System.Reflection; + +var values = C.Produce(); +foreach (int value in values) +{ + System.Console.Write(((string)values.GetType().GetField("5__2", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(values))); +} +System.Console.Write(((string)values.GetType().GetField("5__2", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(values)) is null); + +class C +{ + public static System.Collections.Generic.IEnumerable Produce() + { + { + string values2 = "values2 "; + yield return 42; + System.Console.Write(values2); + } + { + string values3 = "values3 "; + yield return 43; + System.Console.Write(values3); + } + } +} +"""; + var verifier = CompileAndVerify(src, expectedOutput: "values2 values2 values3 values3 True").VerifyDiagnostics(); + verifier.VerifyIL("C.d__0.System.IDisposable.Dispose()", """ +{ + // Code size 8 (0x8) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldnull + IL_0002: stfld "string C.d__0.5__2" + IL_0007: ret +} +"""); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75666")] + public void AddVariableCleanup_Parameters() + { + string src = """ +string s = "ran "; +var values = C.Produce(s); +foreach (int value in values) { } +foreach (int value in values) { } + +class C +{ + public static System.Collections.Generic.IEnumerable Produce(string s) + { + yield return 42; + System.Console.Write(s); + } +} +"""; + var verifier = CompileAndVerify(src, expectedOutput: "ran ran").VerifyDiagnostics(); + verifier.VerifyIL("C.d__0.System.IDisposable.Dispose()", """ +{ + // Code size 1 (0x1) + .maxstack 0 + IL_0000: ret +} +"""); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75666")] + public void AddVariableCleanup_NotCleanedTooSoon() + { + string src = """ +using System.Reflection; + +var values = C.Produce(); +foreach (int value in values) +{ + System.Console.Write(((string)values.GetType().GetField("5__2", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(values))); + break; +} +System.Console.Write(((string)values.GetType().GetField("5__2", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(values)) is null); + +class C +{ + public static System.Collections.Generic.IEnumerable Produce() + { + try + { + string s = "value "; + try + { + yield return 42; + } + finally + { + System.Console.Write(s); + } + } + finally + { + System.Console.Write("outer "); + } + } +} +"""; + var verifier = CompileAndVerify(src, expectedOutput: "value value outer True").VerifyDiagnostics(); + verifier.VerifyIL("C.d__0.System.IDisposable.Dispose()", """ +{ + // Code size 55 (0x37) + .maxstack 2 + .locals init (int V_0) + IL_0000: ldarg.0 + IL_0001: ldfld "int C.d__0.<>1__state" + IL_0006: stloc.0 + IL_0007: ldloc.0 + IL_0008: ldc.i4.s -4 + IL_000a: sub + IL_000b: ldc.i4.1 + IL_000c: ble.un.s IL_0012 + IL_000e: ldloc.0 + IL_000f: ldc.i4.1 + IL_0010: bne.un.s IL_002f + IL_0012: nop + .try + { + IL_0013: ldloc.0 + IL_0014: ldc.i4.s -4 + IL_0016: beq.s IL_001e + IL_0018: ldloc.0 + IL_0019: ldc.i4.1 + IL_001a: beq.s IL_001e + IL_001c: leave.s IL_002f + IL_001e: nop + .try + { + IL_001f: leave.s IL_002f + } + finally + { + IL_0021: ldarg.0 + IL_0022: call "void C.d__0.<>m__Finally2()" + IL_0027: endfinally + } + } + finally + { + IL_0028: ldarg.0 + IL_0029: call "void C.d__0.<>m__Finally1()" + IL_002e: endfinally + } + IL_002f: ldarg.0 + IL_0030: ldnull + IL_0031: stfld "string C.d__0.5__2" + IL_0036: ret +} +"""); + } + + [ConditionalFact(typeof(CoreClrOnly))] + public void AddVariableCleanup_HoistedFromRefExpression() + { + var src = """ +using System.Reflection; + +C c = new C(); +var coll = Test(c); +var enumerator = coll.GetEnumerator(); +try +{ + enumerator.MoveNext(); + System.Console.Write(((C)coll.GetType().GetField("<>7__wrap2", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(coll)) is null); +} +finally +{ + enumerator.Dispose(); +} + +System.Console.Write(((C)coll.GetType().GetField("<>7__wrap2", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(coll)) is null); + +class C +{ + public Buffer4 F = default; +} + +partial class Program +{ + static System.Collections.Generic.IEnumerable Test(C x) + { + foreach (var y in x.F) + { + yield return -1; + } + } +} + +[System.Runtime.CompilerServices.InlineArray(4)] +public struct Buffer4 +{ + private T _element0; +} +"""; + var comp = CreateCompilation(src, targetFramework: TargetFramework.Net80); + var verifier = CompileAndVerify(comp, expectedOutput: "FalseTrue", verify: Verification.Skipped).VerifyDiagnostics(); + verifier.VerifyIL("Program.d__1.System.IDisposable.Dispose()", """ +{ + // Code size 8 (0x8) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldnull + IL_0002: stfld "C Program.d__1.<>7__wrap2" + IL_0007: ret +} +"""); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75666")] + public void AddVariableCleanup_StringLocal_IEnumerator() + { + string src = """ +using System.Reflection; + +var values = C.Produce(); +assert(values.MoveNext()); +assert(values.Current == 42); +assert(!values.MoveNext()); +System.Console.Write(((string)values.GetType().GetField("5__2", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(values))); +values.Dispose(); +System.Console.Write(((string)values.GetType().GetField("5__2", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(values)) is null); + +static void assert(bool b) { if (!b) throw new System.Exception(); } + +class C +{ + public static System.Collections.Generic.IEnumerator Produce() + { + string s = "ran "; + yield return 42; + System.Console.Write(s); + } +} +"""; + var verifier = CompileAndVerify(src, expectedOutput: "ran ran True").VerifyDiagnostics(); + verifier.VerifyIL("C.d__0.System.IDisposable.Dispose()", """ +{ + // Code size 8 (0x8) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldnull + IL_0002: stfld "string C.d__0.5__2" + IL_0007: ret +} +"""); + } + + [Fact] + public void AddVariableCleanup_Unmanaged_UseSiteError() + { + var missingLibS1 = CreateCompilation(@" +public struct S1 +{ + public int i; +} +", assemblyName: "libS1").ToMetadataReference(); + + var libS2 = CreateCompilation(@" +public struct S2 +{ + public S1 s1; +} +", references: [missingLibS1], assemblyName: "libS2").ToMetadataReference(); + + var source = @" +class C +{ + System.Collections.Generic.IEnumerable M1() + { + S2 s2 = M2(); + yield return 42; + System.Console.Write(s2); + } + + S2 M2() => default; +} +"; + var comp = CreateCompilation(source, references: [libS2]); + comp.VerifyEmitDiagnostics( + // error CS0012: The type 'S1' is defined in an assembly that is not referenced. You must add a reference to assembly 'libS1, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'. + Diagnostic(ErrorCode.ERR_NoTypeDef).WithArguments("S1", "libS1, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null").WithLocation(1, 1), + // error CS0012: The type 'S1' is defined in an assembly that is not referenced. You must add a reference to assembly 'libS1, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'. + Diagnostic(ErrorCode.ERR_NoTypeDef).WithArguments("S1", "libS1, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null").WithLocation(1, 1)); + + comp = CreateCompilation(source, options: TestOptions.UnsafeDebugDll, references: [libS2, missingLibS1]); + comp.VerifyEmitDiagnostics(); } } } diff --git a/src/Compilers/CSharp/Test/Emit2/Emit/EditAndContinue/EditAndContinueStateMachineTests.cs b/src/Compilers/CSharp/Test/Emit2/Emit/EditAndContinue/EditAndContinueStateMachineTests.cs index f814e6204c9b2..41169ac72a8ff 100644 --- a/src/Compilers/CSharp/Test/Emit2/Emit/EditAndContinue/EditAndContinueStateMachineTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/Emit/EditAndContinue/EditAndContinueStateMachineTests.cs @@ -5176,7 +5176,7 @@ .maxstack 2 v0.VerifyIL("C.d__0.System.IDisposable.Dispose", @" { - // Code size 33 (0x21) + // Code size 40 (0x28) .maxstack 2 .locals init (int V_0) IL_0000: ldarg.0 @@ -5202,12 +5202,15 @@ .locals init (int V_0) IL_001d: endfinally } IL_001e: br.s IL_0020 - IL_0020: ret + IL_0020: ldarg.0 + IL_0021: ldnull + IL_0022: stfld ""System.IDisposable C.d__0.5__1"" + IL_0027: ret } "); diff1.VerifyIL("C.d__0.System.IDisposable.Dispose", @" { - // Code size 35 (0x23) + // Code size 42 (0x2a) .maxstack 2 .locals init (int V_0) IL_0000: ldarg.0 @@ -5235,7 +5238,10 @@ .locals init (int V_0) IL_001f: endfinally } IL_0020: br.s IL_0022 - IL_0022: ret + IL_0022: ldarg.0 + IL_0023: ldnull + IL_0024: stfld ""System.IDisposable C.d__0.5__1"" + IL_0029: ret } "); diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/UseSiteErrorTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/UseSiteErrorTests.cs index 172b3255fee4b..5bb1106bc6dc9 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/UseSiteErrorTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/UseSiteErrorTests.cs @@ -2728,6 +2728,8 @@ async Task M1() "; var comp = CreateCompilation(source, references: new[] { UnmanagedUseSiteError_Ref2 }); comp.VerifyEmitDiagnostics( + // error CS0012: The type 'S1' is defined in an assembly that is not referenced. You must add a reference to assembly 'libS1, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'. + Diagnostic(ErrorCode.ERR_NoTypeDef).WithArguments("S1", "libS1, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null").WithLocation(1, 1), // error CS0012: The type 'S1' is defined in an assembly that is not referenced. You must add a reference to assembly 'libS1, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'. Diagnostic(ErrorCode.ERR_NoTypeDef).WithArguments("S1", "libS1, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null").WithLocation(1, 1), // error CS0012: The type 'S1' is defined in an assembly that is not referenced. You must add a reference to assembly 'libS1, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'.