From ef5f01dd943ce09175f4e5c1544be772f15da099 Mon Sep 17 00:00:00 2001 From: tmat Date: Wed, 25 May 2022 11:17:57 -0700 Subject: [PATCH] Fix syntax node associated with BoundTryStatement created from using syntax --- .../AsyncExceptionHandlerRewriter.cs | 28 +- .../IteratorMethodToStateMachineRewriter.cs | 9 - .../LocalRewriter_UsingStatement.cs | 18 +- .../MethodToStateMachineRewriter.cs | 8 +- .../ResumableStateMachineStateAllocator.cs | 9 +- .../Portable/Syntax/SyntaxBindingUtilities.cs | 27 ++ .../EditAndContinueStateMachineTests.cs | 351 ++++++++------- .../EditAndContinue/LocalSlotMappingTests.cs | 422 +++++++++++++++++- 8 files changed, 657 insertions(+), 215 deletions(-) create mode 100644 src/Compilers/CSharp/Portable/Syntax/SyntaxBindingUtilities.cs diff --git a/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncExceptionHandlerRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncExceptionHandlerRewriter.cs index e070c6be33c39..d8c903fd0d847 100644 --- a/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncExceptionHandlerRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncExceptionHandlerRewriter.cs @@ -135,13 +135,7 @@ public override BoundNode VisitTryStatement(BoundTryStatement node) var tryStatementSyntax = node.Syntax; // If you add a syntax kind to the assertion below, please also ensure // that the scenario has been tested with Edit-and-Continue. - Debug.Assert( - tryStatementSyntax.IsKind(SyntaxKind.TryStatement) || - tryStatementSyntax.IsKind(SyntaxKind.UsingStatement) || - tryStatementSyntax.IsKind(SyntaxKind.ForEachStatement) || - tryStatementSyntax.IsKind(SyntaxKind.ForEachVariableStatement) || - tryStatementSyntax.IsKind(SyntaxKind.LocalDeclarationStatement) || - tryStatementSyntax.IsKind(SyntaxKind.LockStatement)); + Debug.Assert(SyntaxBindingUtilities.BindsToTryStatement(tryStatementSyntax)); BoundStatement finalizedRegion; BoundBlock rewrittenFinally; @@ -711,7 +705,7 @@ public override BoundNode VisitLocalFunctionStatement(BoundLocalFunctionStatemen private AwaitFinallyFrame PushFrame(BoundTryStatement statement) { - var newFrame = new AwaitFinallyFrame(_currentAwaitFinallyFrame, _analysis.Labels(statement), (StatementSyntax)statement.Syntax); + var newFrame = new AwaitFinallyFrame(_currentAwaitFinallyFrame, _analysis.Labels(statement), statement.Syntax); _currentAwaitFinallyFrame = newFrame; return newFrame; } @@ -890,7 +884,7 @@ private sealed class AwaitFinallyFrame public readonly HashSet LabelsOpt; // the try or using-await statement the frame is associated with - private readonly StatementSyntax _statementSyntaxOpt; + private readonly SyntaxNode _syntaxOpt; // proxy labels for branches leaving the frame. // we build this on demand once we encounter leaving branches. @@ -909,20 +903,16 @@ public AwaitFinallyFrame() // root frame } - public AwaitFinallyFrame(AwaitFinallyFrame parent, HashSet labelsOpt, StatementSyntax statementSyntax) + public AwaitFinallyFrame(AwaitFinallyFrame parent, HashSet labelsOpt, SyntaxNode syntax) { Debug.Assert(parent != null); - Debug.Assert(statementSyntax != null); + Debug.Assert(syntax != null); - Debug.Assert(statementSyntax.Kind() == SyntaxKind.TryStatement || - (statementSyntax.Kind() == SyntaxKind.UsingStatement && ((UsingStatementSyntax)statementSyntax).AwaitKeyword != default) || - (statementSyntax.Kind() == SyntaxKind.ForEachStatement && ((CommonForEachStatementSyntax)statementSyntax).AwaitKeyword != default) || - (statementSyntax.Kind() == SyntaxKind.ForEachVariableStatement && ((CommonForEachStatementSyntax)statementSyntax).AwaitKeyword != default) || - (statementSyntax.Kind() == SyntaxKind.LocalDeclarationStatement && ((LocalDeclarationStatementSyntax)statementSyntax).AwaitKeyword != default)); + Debug.Assert(SyntaxBindingUtilities.BindsToTryStatement(syntax)); this.ParentOpt = parent; this.LabelsOpt = labelsOpt; - _statementSyntaxOpt = statementSyntax; + _syntaxOpt = syntax; } public bool IsRoot() @@ -983,8 +973,8 @@ public LabelSymbol ProxyReturnIfNeeded( returnValue = this.returnValue; if (returnValue == null) { - Debug.Assert(_statementSyntaxOpt != null); - this.returnValue = returnValue = new SynthesizedLocal(containingMethod, TypeWithAnnotations.Create(valueOpt.Type), SynthesizedLocalKind.AsyncMethodReturnValue, _statementSyntaxOpt); + Debug.Assert(_syntaxOpt != null); + this.returnValue = returnValue = new SynthesizedLocal(containingMethod, TypeWithAnnotations.Create(valueOpt.Type), SynthesizedLocalKind.AsyncMethodReturnValue, _syntaxOpt); } } diff --git a/src/Compilers/CSharp/Portable/Lowering/IteratorRewriter/IteratorMethodToStateMachineRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/IteratorRewriter/IteratorMethodToStateMachineRewriter.cs index 1b515e05f2eff..16c01b8fece1f 100644 --- a/src/Compilers/CSharp/Portable/Lowering/IteratorRewriter/IteratorMethodToStateMachineRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/IteratorRewriter/IteratorMethodToStateMachineRewriter.cs @@ -460,15 +460,6 @@ private IteratorFinallyFrame PushFrame(BoundTryStatement statement) { var syntax = statement.Syntax; - // these nodes have to be tracked by the IDE EnC analyzer - Debug.Assert( - syntax.IsKind(SyntaxKind.TryStatement) || - syntax.IsKind(SyntaxKind.UsingStatement) || - syntax.IsKind(SyntaxKind.LocalDeclarationStatement) || - syntax.IsKind(SyntaxKind.ForEachStatement) || - syntax.IsKind(SyntaxKind.ForEachVariableStatement) || - syntax.IsKind(SyntaxKind.LockStatement), $"Unexpected syntax: {syntax.Kind()}"); - if (slotAllocatorOpt?.TryGetPreviousStateMachineState(syntax, out var finalizeState) != true) { finalizeState = _nextFinalizeState--; diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UsingStatement.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UsingStatement.cs index 4a7083be57f42..efcc05bd85c3a 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UsingStatement.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UsingStatement.cs @@ -186,7 +186,7 @@ private BoundBlock MakeExpressionUsingStatement(BoundUsingStatement node, BoundB expressionStatement = _instrumenter.InstrumentUsingTargetCapture(node, expressionStatement); } - BoundStatement tryFinally = RewriteUsingStatementTryFinally(usingSyntax, tryBlock, boundTemp, usingSyntax.AwaitKeyword, node.AwaitOpt, node.PatternDisposeInfoOpt); + BoundStatement tryFinally = RewriteUsingStatementTryFinally(usingSyntax, usingSyntax, tryBlock, boundTemp, usingSyntax.AwaitKeyword, node.AwaitOpt, node.PatternDisposeInfoOpt); // { ResourceType temp = expr; try { ... } finally { ... } } return new BoundBlock( @@ -229,7 +229,7 @@ private BoundBlock RewriteDeclarationUsingStatement( if (boundLocal.ConstantValue == ConstantValue.Null) { //localSymbol will be declared by an enclosing block - return BoundBlock.SynthesizedNoLocals(usingSyntax, rewrittenDeclaration, tryBlock); + return BoundBlock.SynthesizedNoLocals(declarationSyntax, rewrittenDeclaration, tryBlock); } if (localType.IsDynamic()) @@ -250,10 +250,10 @@ private BoundBlock RewriteDeclarationUsingStatement( BoundAssignmentOperator tempAssignment; BoundLocal boundTemp = _factory.StoreToTemp(tempInit, out tempAssignment, kind: SynthesizedLocalKind.Using); - BoundStatement tryFinally = RewriteUsingStatementTryFinally(usingSyntax, tryBlock, boundTemp, awaitKeywordOpt, awaitOpt, patternDisposeInfo); + BoundStatement tryFinally = RewriteUsingStatementTryFinally(usingSyntax, declarationSyntax, tryBlock, boundTemp, awaitKeywordOpt, awaitOpt, patternDisposeInfo); return new BoundBlock( - syntax: usingSyntax, + syntax: declarationSyntax, locals: ImmutableArray.Create(boundTemp.LocalSymbol), //localSymbol will be declared by an enclosing block statements: ImmutableArray.Create( rewrittenDeclaration, @@ -262,14 +262,15 @@ private BoundBlock RewriteDeclarationUsingStatement( } else { - BoundStatement tryFinally = RewriteUsingStatementTryFinally(usingSyntax, tryBlock, boundLocal, awaitKeywordOpt, awaitOpt, patternDisposeInfo); + BoundStatement tryFinally = RewriteUsingStatementTryFinally(usingSyntax, declarationSyntax, tryBlock, boundLocal, awaitKeywordOpt, awaitOpt, patternDisposeInfo); // localSymbol will be declared by an enclosing block - return BoundBlock.SynthesizedNoLocals(usingSyntax, rewrittenDeclaration, tryFinally); + return BoundBlock.SynthesizedNoLocals(declarationSyntax, rewrittenDeclaration, tryFinally); } } private BoundStatement RewriteUsingStatementTryFinally( + SyntaxNode typeSyntax, SyntaxNode syntax, BoundBlock tryBlock, BoundLocal local, @@ -362,7 +363,7 @@ private BoundStatement RewriteUsingStatementTryFinally( disposedExpression = local; } - BoundExpression disposeCall = GenerateDisposeCall(syntax, disposedExpression, patternDisposeInfo, awaitOpt, awaitKeywordOpt); + BoundExpression disposeCall = GenerateDisposeCall(typeSyntax, syntax, disposedExpression, patternDisposeInfo, awaitOpt, awaitKeywordOpt); // local.Dispose(); or await variant BoundStatement disposeStatement = new BoundExpressionStatement(syntax, disposeCall); @@ -419,6 +420,7 @@ private BoundStatement RewriteUsingStatementTryFinally( } private BoundExpression GenerateDisposeCall( + SyntaxNode typeSyntax, SyntaxNode syntax, BoundExpression disposedExpression, MethodArgumentInfo? disposeInfo, @@ -434,7 +436,7 @@ private BoundExpression GenerateDisposeCall( if (awaitOpt is null) { // IDisposable.Dispose() - Binder.TryGetSpecialTypeMember(_compilation, SpecialMember.System_IDisposable__Dispose, syntax, _diagnostics, out disposeMethod); + Binder.TryGetSpecialTypeMember(_compilation, SpecialMember.System_IDisposable__Dispose, typeSyntax, _diagnostics, out disposeMethod); } else { diff --git a/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/MethodToStateMachineRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/MethodToStateMachineRewriter.cs index cc45975ad8bf0..f9920cb65fbc8 100644 --- a/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/MethodToStateMachineRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/MethodToStateMachineRewriter.cs @@ -211,13 +211,7 @@ protected void AddResumableState(ResumableStateMachineStateAllocator allocator, protected void AddStateDebugInfo(SyntaxNode node, int stateNumber) { - Debug.Assert( - ResumableStateMachineStateAllocator.IsResumableStateSyntaxNode(node) || - node.IsKind(SyntaxKind.TryStatement) || - node.IsKind(SyntaxKind.UsingStatement) || - node.IsKind(SyntaxKind.LocalDeclarationStatement) || - node.IsKind(SyntaxKind.LockStatement) || - node.IsKind(SyntaxKind.ForEachStatement)); + Debug.Assert(SyntaxBindingUtilities.BindsToResumableStateMachineState(node) || SyntaxBindingUtilities.BindsToTryStatement(node), $"Unexpected syntax: {node.Kind()}"); int syntaxOffset = CurrentMethod.CalculateLocalSyntaxOffset(node.SpanStart, node.SyntaxTree); _stateDebugInfoBuilder.Add(new StateMachineStateDebugInfo(syntaxOffset, stateNumber)); diff --git a/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/ResumableStateMachineStateAllocator.cs b/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/ResumableStateMachineStateAllocator.cs index 6148177d4bd7f..0ee9ddde4ba26 100644 --- a/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/ResumableStateMachineStateAllocator.cs +++ b/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/ResumableStateMachineStateAllocator.cs @@ -43,16 +43,9 @@ public ResumableStateMachineStateAllocator(VariableSlotAllocator? slotAllocator, _nextState = slotAllocator?.GetFirstUnusedStateMachineState(increasing) ?? firstState; } - public static bool IsResumableStateSyntaxNode(SyntaxNode node) - => node.IsKind(SyntaxKind.YieldReturnStatement) || - node.IsKind(SyntaxKind.AwaitExpression) || - node is CommonForEachStatementSyntax { AwaitKeyword.IsMissing: false } - or LocalDeclarationStatementSyntax { AwaitKeyword.IsMissing: false } - or UsingStatementSyntax { AwaitKeyword.IsMissing: false }; - public int AllocateState(SyntaxNode awaitOrYieldReturnSyntax) { - Debug.Assert(IsResumableStateSyntaxNode(awaitOrYieldReturnSyntax)); + Debug.Assert(SyntaxBindingUtilities.BindsToResumableStateMachineState(awaitOrYieldReturnSyntax)); int direction = _increasing ? +1 : -1; diff --git a/src/Compilers/CSharp/Portable/Syntax/SyntaxBindingUtilities.cs b/src/Compilers/CSharp/Portable/Syntax/SyntaxBindingUtilities.cs new file mode 100644 index 0000000000000..90414a715be02 --- /dev/null +++ b/src/Compilers/CSharp/Portable/Syntax/SyntaxBindingUtilities.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// 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.Text; + +namespace Microsoft.CodeAnalysis.CSharp.Syntax +{ + internal static class SyntaxBindingUtilities + { + public static bool BindsToResumableStateMachineState(SyntaxNode node) + => node.IsKind(SyntaxKind.YieldReturnStatement) || + node.IsKind(SyntaxKind.AwaitExpression) || + node is CommonForEachStatementSyntax { AwaitKeyword.IsMissing: false } + or VariableDeclaratorSyntax { Parent.Parent: UsingStatementSyntax { AwaitKeyword.IsMissing: false } or LocalDeclarationStatementSyntax { AwaitKeyword.IsMissing: false } } + or UsingStatementSyntax { Expression: not null, AwaitKeyword.IsMissing: false }; + + public static bool BindsToTryStatement(SyntaxNode node) + => node is VariableDeclaratorSyntax { Parent.Parent: UsingStatementSyntax { } or LocalDeclarationStatementSyntax { UsingKeyword.IsMissing: false } } + or UsingStatementSyntax { Expression: not null } + or CommonForEachStatementSyntax + or TryStatementSyntax + or LockStatementSyntax; + } +} diff --git a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueStateMachineTests.cs b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueStateMachineTests.cs index ab9c48e359609..47373c07bc230 100644 --- a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueStateMachineTests.cs +++ b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueStateMachineTests.cs @@ -4594,15 +4594,16 @@ class C { static IEnumerable F() { - using var x = D(); + using IDisposable x1 = D(), x2 = D(); - using (var y = D()) - foreach (var z in E()) - foreach (var (u, w) in E()) - lock (x) - { - yield return 1; - } + using (D()) + using (IDisposable y1 = D(), y2 = D()) + foreach (var z in E()) + foreach (var (u, w) in E()) + lock (D()) + { + yield return 1; + } } static IDisposable D() => null; @@ -4616,16 +4617,17 @@ class C { static IEnumerable F() { - using var x = D(); + using IDisposable x1 = D(), x2 = D(); - using (var y = D()) - foreach (var z in E()) - foreach (var (u, w) in E()) - lock (x) - { - yield return 1; - yield return 2; - } + using (D()) + using (IDisposable y1 = D(), y2 = D()) + foreach (var z in E()) + foreach (var (u, w) in E()) + lock (D()) + { + yield return 1; + yield return 2; + } } static IDisposable D() => null; @@ -4658,23 +4660,29 @@ static IEnumerable F() - - - - - - - - - + + + + + + + + + + + + - - - - - - + + + + + + + + + @@ -4683,7 +4691,7 @@ static IEnumerable F() diff1.VerifyIL("C.d__0.System.Collections.IEnumerator.MoveNext", @" { - // Code size 433 (0x1b1) + // Code size 526 (0x20e) .maxstack 2 .locals init (bool V_0, int V_1, @@ -4700,156 +4708,189 @@ .locals init (bool V_0, IL_0022) IL_0019: br.s IL_0027 IL_001b: br.s IL_002e - IL_001d: br IL_0110 - IL_0022: br IL_012d + IL_001d: br IL_0148 + IL_0022: br IL_0165 IL_0027: ldc.i4.0 IL_0028: stloc.0 - IL_0029: leave IL_01af + IL_0029: leave IL_020c IL_002e: ldarg.0 IL_002f: ldc.i4.m1 IL_0030: stfld ""int C.d__0.<>1__state"" IL_0035: nop IL_0036: ldarg.0 IL_0037: call ""System.IDisposable C.D()"" - IL_003c: stfld ""System.IDisposable C.d__0.5__1"" + IL_003c: stfld ""System.IDisposable C.d__0.5__1"" IL_0041: ldarg.0 IL_0042: ldc.i4.s -3 IL_0044: stfld ""int C.d__0.<>1__state"" IL_0049: ldarg.0 IL_004a: call ""System.IDisposable C.D()"" - IL_004f: stfld ""System.IDisposable C.d__0.5__2"" + IL_004f: stfld ""System.IDisposable C.d__0.5__2"" IL_0054: ldarg.0 IL_0055: ldc.i4.s -4 IL_0057: stfld ""int C.d__0.<>1__state"" - IL_005c: nop - IL_005d: ldarg.0 - IL_005e: call ""System.Collections.Generic.IEnumerable> C.E()"" - IL_0063: callvirt ""System.Collections.Generic.IEnumerator> System.Collections.Generic.IEnumerable>.GetEnumerator()"" - IL_0068: stfld ""System.Collections.Generic.IEnumerator> C.d__0.<>s__3"" - IL_006d: ldarg.0 - IL_006e: ldc.i4.s -5 - IL_0070: stfld ""int C.d__0.<>1__state"" - IL_0075: br IL_016e + IL_005c: ldarg.0 + IL_005d: call ""System.IDisposable C.D()"" + IL_0062: stfld ""System.IDisposable C.d__0.<>s__3"" + IL_0067: ldarg.0 + IL_0068: ldc.i4.s -5 + IL_006a: stfld ""int C.d__0.<>1__state"" + IL_006f: ldarg.0 + IL_0070: call ""System.IDisposable C.D()"" + IL_0075: stfld ""System.IDisposable C.d__0.5__4"" IL_007a: ldarg.0 - IL_007b: ldarg.0 - IL_007c: ldfld ""System.Collections.Generic.IEnumerator> C.d__0.<>s__3"" - IL_0081: callvirt ""System.ValueTuple System.Collections.Generic.IEnumerator>.Current.get"" - IL_0086: stfld ""System.ValueTuple C.d__0.5__4"" - IL_008b: nop - IL_008c: ldarg.0 - IL_008d: call ""System.Collections.Generic.IEnumerable> C.E()"" - IL_0092: callvirt ""System.Collections.Generic.IEnumerator> System.Collections.Generic.IEnumerable>.GetEnumerator()"" - IL_0097: stfld ""System.Collections.Generic.IEnumerator> C.d__0.<>s__5"" - IL_009c: ldarg.0 - IL_009d: ldc.i4.s -6 - IL_009f: stfld ""int C.d__0.<>1__state"" - IL_00a4: br IL_0144 - IL_00a9: ldarg.0 - IL_00aa: ldfld ""System.Collections.Generic.IEnumerator> C.d__0.<>s__5"" - IL_00af: callvirt ""System.ValueTuple System.Collections.Generic.IEnumerator>.Current.get"" - IL_00b4: stloc.2 - IL_00b5: ldarg.0 - IL_00b6: ldloc.2 - IL_00b7: ldfld ""int System.ValueTuple.Item1"" - IL_00bc: stfld ""int C.d__0.5__10"" - IL_00c1: ldarg.0 - IL_00c2: ldloc.2 - IL_00c3: ldfld ""int System.ValueTuple.Item2"" - IL_00c8: stfld ""int C.d__0.5__11"" - IL_00cd: ldarg.0 - IL_00ce: ldarg.0 - IL_00cf: ldfld ""System.IDisposable C.d__0.5__1"" - IL_00d4: stfld ""System.IDisposable C.d__0.<>s__8"" - IL_00d9: ldarg.0 - IL_00da: ldc.i4.0 - IL_00db: stfld ""bool C.d__0.<>s__9"" - IL_00e0: ldarg.0 - IL_00e1: ldc.i4.s -7 - IL_00e3: stfld ""int C.d__0.<>1__state"" - IL_00e8: ldarg.0 - IL_00e9: ldfld ""System.IDisposable C.d__0.<>s__8"" + IL_007b: ldc.i4.s -6 + IL_007d: stfld ""int C.d__0.<>1__state"" + IL_0082: ldarg.0 + IL_0083: call ""System.IDisposable C.D()"" + IL_0088: stfld ""System.IDisposable C.d__0.5__5"" + IL_008d: ldarg.0 + IL_008e: ldc.i4.s -7 + IL_0090: stfld ""int C.d__0.<>1__state"" + IL_0095: nop + IL_0096: ldarg.0 + IL_0097: call ""System.Collections.Generic.IEnumerable> C.E()"" + IL_009c: callvirt ""System.Collections.Generic.IEnumerator> System.Collections.Generic.IEnumerable>.GetEnumerator()"" + IL_00a1: stfld ""System.Collections.Generic.IEnumerator> C.d__0.<>s__6"" + IL_00a6: ldarg.0 + IL_00a7: ldc.i4.s -8 + IL_00a9: stfld ""int C.d__0.<>1__state"" + IL_00ae: br IL_01a6 + IL_00b3: ldarg.0 + IL_00b4: ldarg.0 + IL_00b5: ldfld ""System.Collections.Generic.IEnumerator> C.d__0.<>s__6"" + IL_00ba: callvirt ""System.ValueTuple System.Collections.Generic.IEnumerator>.Current.get"" + IL_00bf: stfld ""System.ValueTuple C.d__0.5__7"" + IL_00c4: nop + IL_00c5: ldarg.0 + IL_00c6: call ""System.Collections.Generic.IEnumerable> C.E()"" + IL_00cb: callvirt ""System.Collections.Generic.IEnumerator> System.Collections.Generic.IEnumerable>.GetEnumerator()"" + IL_00d0: stfld ""System.Collections.Generic.IEnumerator> C.d__0.<>s__8"" + IL_00d5: ldarg.0 + IL_00d6: ldc.i4.s -9 + IL_00d8: stfld ""int C.d__0.<>1__state"" + IL_00dd: br IL_017c + IL_00e2: ldarg.0 + IL_00e3: ldfld ""System.Collections.Generic.IEnumerator> C.d__0.<>s__8"" + IL_00e8: callvirt ""System.ValueTuple System.Collections.Generic.IEnumerator>.Current.get"" + IL_00ed: stloc.2 IL_00ee: ldarg.0 - IL_00ef: ldflda ""bool C.d__0.<>s__9"" - IL_00f4: call ""void System.Threading.Monitor.Enter(object, ref bool)"" - IL_00f9: nop - IL_00fa: nop - IL_00fb: ldarg.0 - IL_00fc: ldc.i4.1 - IL_00fd: stfld ""int C.d__0.<>2__current"" - IL_0102: ldarg.0 - IL_0103: ldc.i4.1 - IL_0104: stfld ""int C.d__0.<>1__state"" - IL_0109: ldc.i4.1 - IL_010a: stloc.0 - IL_010b: leave IL_01af - IL_0110: ldarg.0 - IL_0111: ldc.i4.s -7 - IL_0113: stfld ""int C.d__0.<>1__state"" + IL_00ef: ldloc.2 + IL_00f0: ldfld ""int System.ValueTuple.Item1"" + IL_00f5: stfld ""int C.d__0.5__13"" + IL_00fa: ldarg.0 + IL_00fb: ldloc.2 + IL_00fc: ldfld ""int System.ValueTuple.Item2"" + IL_0101: stfld ""int C.d__0.5__14"" + IL_0106: ldarg.0 + IL_0107: call ""System.IDisposable C.D()"" + IL_010c: stfld ""System.IDisposable C.d__0.<>s__11"" + IL_0111: ldarg.0 + IL_0112: ldc.i4.0 + IL_0113: stfld ""bool C.d__0.<>s__12"" IL_0118: ldarg.0 - IL_0119: ldc.i4.2 - IL_011a: stfld ""int C.d__0.<>2__current"" - IL_011f: ldarg.0 - IL_0120: ldc.i4.2 - IL_0121: stfld ""int C.d__0.<>1__state"" - IL_0126: ldc.i4.1 - IL_0127: stloc.0 - IL_0128: leave IL_01af - IL_012d: ldarg.0 - IL_012e: ldc.i4.s -7 - IL_0130: stfld ""int C.d__0.<>1__state"" - IL_0135: nop - IL_0136: ldarg.0 - IL_0137: call ""void C.d__0.<>m__Finally5()"" - IL_013c: nop - IL_013d: ldarg.0 - IL_013e: ldnull - IL_013f: stfld ""System.IDisposable C.d__0.<>s__8"" - IL_0144: ldarg.0 - IL_0145: ldfld ""System.Collections.Generic.IEnumerator> C.d__0.<>s__5"" - IL_014a: callvirt ""bool System.Collections.IEnumerator.MoveNext()"" - IL_014f: brtrue IL_00a9 - IL_0154: ldarg.0 - IL_0155: call ""void C.d__0.<>m__Finally4()"" - IL_015a: nop - IL_015b: ldarg.0 - IL_015c: ldnull - IL_015d: stfld ""System.Collections.Generic.IEnumerator> C.d__0.<>s__5"" - IL_0162: ldarg.0 - IL_0163: ldflda ""System.ValueTuple C.d__0.5__4"" - IL_0168: initobj ""System.ValueTuple"" + IL_0119: ldc.i4.s -10 + IL_011b: stfld ""int C.d__0.<>1__state"" + IL_0120: ldarg.0 + IL_0121: ldfld ""System.IDisposable C.d__0.<>s__11"" + IL_0126: ldarg.0 + IL_0127: ldflda ""bool C.d__0.<>s__12"" + IL_012c: call ""void System.Threading.Monitor.Enter(object, ref bool)"" + IL_0131: nop + IL_0132: nop + IL_0133: ldarg.0 + IL_0134: ldc.i4.1 + IL_0135: stfld ""int C.d__0.<>2__current"" + IL_013a: ldarg.0 + IL_013b: ldc.i4.1 + IL_013c: stfld ""int C.d__0.<>1__state"" + IL_0141: ldc.i4.1 + IL_0142: stloc.0 + IL_0143: leave IL_020c + IL_0148: ldarg.0 + IL_0149: ldc.i4.s -10 + IL_014b: stfld ""int C.d__0.<>1__state"" + IL_0150: ldarg.0 + IL_0151: ldc.i4.2 + IL_0152: stfld ""int C.d__0.<>2__current"" + IL_0157: ldarg.0 + IL_0158: ldc.i4.2 + IL_0159: stfld ""int C.d__0.<>1__state"" + IL_015e: ldc.i4.1 + IL_015f: stloc.0 + IL_0160: leave IL_020c + IL_0165: ldarg.0 + IL_0166: ldc.i4.s -10 + IL_0168: stfld ""int C.d__0.<>1__state"" + IL_016d: nop IL_016e: ldarg.0 - IL_016f: ldfld ""System.Collections.Generic.IEnumerator> C.d__0.<>s__3"" - IL_0174: callvirt ""bool System.Collections.IEnumerator.MoveNext()"" - IL_0179: brtrue IL_007a - IL_017e: ldarg.0 - IL_017f: call ""void C.d__0.<>m__Finally3()"" - IL_0184: nop - IL_0185: ldarg.0 - IL_0186: ldnull - IL_0187: stfld ""System.Collections.Generic.IEnumerator> C.d__0.<>s__3"" + IL_016f: call ""void C.d__0.<>m__Finally8()"" + IL_0174: nop + IL_0175: ldarg.0 + IL_0176: ldnull + IL_0177: stfld ""System.IDisposable C.d__0.<>s__11"" + IL_017c: ldarg.0 + IL_017d: ldfld ""System.Collections.Generic.IEnumerator> C.d__0.<>s__8"" + IL_0182: callvirt ""bool System.Collections.IEnumerator.MoveNext()"" + IL_0187: brtrue IL_00e2 IL_018c: ldarg.0 - IL_018d: call ""void C.d__0.<>m__Finally2()"" + IL_018d: call ""void C.d__0.<>m__Finally7()"" IL_0192: nop IL_0193: ldarg.0 IL_0194: ldnull - IL_0195: stfld ""System.IDisposable C.d__0.5__2"" - IL_019a: ldc.i4.0 - IL_019b: stloc.0 - IL_019c: br.s IL_019e - IL_019e: ldarg.0 - IL_019f: call ""void C.d__0.<>m__Finally1()"" - IL_01a4: nop - IL_01a5: leave.s IL_01af + IL_0195: stfld ""System.Collections.Generic.IEnumerator> C.d__0.<>s__8"" + IL_019a: ldarg.0 + IL_019b: ldflda ""System.ValueTuple C.d__0.5__7"" + IL_01a0: initobj ""System.ValueTuple"" + IL_01a6: ldarg.0 + IL_01a7: ldfld ""System.Collections.Generic.IEnumerator> C.d__0.<>s__6"" + IL_01ac: callvirt ""bool System.Collections.IEnumerator.MoveNext()"" + IL_01b1: brtrue IL_00b3 + IL_01b6: ldarg.0 + IL_01b7: call ""void C.d__0.<>m__Finally6()"" + IL_01bc: nop + IL_01bd: ldarg.0 + IL_01be: ldnull + IL_01bf: stfld ""System.Collections.Generic.IEnumerator> C.d__0.<>s__6"" + IL_01c4: ldarg.0 + IL_01c5: call ""void C.d__0.<>m__Finally5()"" + IL_01ca: nop + IL_01cb: ldarg.0 + IL_01cc: call ""void C.d__0.<>m__Finally4()"" + IL_01d1: nop + IL_01d2: ldarg.0 + IL_01d3: ldnull + IL_01d4: stfld ""System.IDisposable C.d__0.5__4"" + IL_01d9: ldarg.0 + IL_01da: ldnull + IL_01db: stfld ""System.IDisposable C.d__0.5__5"" + IL_01e0: ldarg.0 + IL_01e1: call ""void C.d__0.<>m__Finally3()"" + IL_01e6: nop + IL_01e7: ldarg.0 + IL_01e8: ldnull + IL_01e9: stfld ""System.IDisposable C.d__0.<>s__3"" + IL_01ee: ldc.i4.0 + IL_01ef: stloc.0 + IL_01f0: br.s IL_01f2 + IL_01f2: ldarg.0 + IL_01f3: call ""void C.d__0.<>m__Finally2()"" + IL_01f8: nop + IL_01f9: br.s IL_01fb + IL_01fb: ldarg.0 + IL_01fc: call ""void C.d__0.<>m__Finally1()"" + IL_0201: nop + IL_0202: leave.s IL_020c } fault { - IL_01a7: ldarg.0 - IL_01a8: call ""void C.d__0.Dispose()"" - IL_01ad: nop - IL_01ae: endfinally + IL_0204: ldarg.0 + IL_0205: call ""void C.d__0.Dispose()"" + IL_020a: nop + IL_020b: endfinally } - IL_01af: ldloc.0 - IL_01b0: ret + IL_020c: ldloc.0 + IL_020d: ret }"); } diff --git a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/LocalSlotMappingTests.cs b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/LocalSlotMappingTests.cs index 21b020ad0cdb1..9c7848bdefe4c 100644 --- a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/LocalSlotMappingTests.cs +++ b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/LocalSlotMappingTests.cs @@ -403,16 +403,13 @@ static void M() [Fact] public void Using() { - var source = WithWindowsLineBreaks( -@"class C : System.IDisposable + var source = WithWindowsLineBreaks(@" +using System; + +class C { - public void Dispose() - { - } - static System.IDisposable F() - { - return new C(); - } + static IDisposable F() => null; + static void M() { using (F()) @@ -503,6 +500,413 @@ .locals init (System.IDisposable V_0, }"); } + [Fact] + public void Using_VariableSwap() + { + var source0 = MarkedSource(@" +using System; + +class C +{ + static IDisposable F() => null; + + static void M() + { + using (IDisposable u = F(), v = F()) { } + } +}"); + var source1 = MarkedSource(@" +using System; + +class C +{ + static IDisposable F() => null; + + static void M() + { + using (IDisposable v = F(), u = F()) { } + } +}"); + var compilation0 = CreateCompilation(source0.Tree, options: TestOptions.DebugDll); + var compilation1 = compilation0.WithSource(source1.Tree); + + var v0 = CompileAndVerify(compilation0); + var symReader = v0.CreateSymReader(); + + using var md0 = ModuleMetadata.CreateFromImage(v0.EmittedAssemblyData); + var method0 = compilation0.GetMember("C.M"); + var method1 = compilation1.GetMember("C.M"); + + var generation0 = EmitBaseline.CreateInitialBaseline(md0, symReader.GetEncMethodDebugInfo); + + var diff1 = compilation1.EmitDifference( + generation0, + ImmutableArray.Create(SemanticEdit.Create(SemanticEditKind.Update, method0, method1, GetSyntaxMapFromMarkers(source0, source1), preserveLocalVariables: true))); + + v0.VerifyPdb("C.M", @" + + + + + + + + + + + + + + + + + + + + + + +", options: PdbValidationOptions.ExcludeSequencePoints); + + diff1.VerifyIL("C.M", @" +{ + // Code size 42 (0x2a) + .maxstack 1 + .locals init (System.IDisposable V_0, //u + System.IDisposable V_1) //v + IL_0000: nop + IL_0001: call ""System.IDisposable C.F()"" + IL_0006: stloc.1 + .try + { + IL_0007: call ""System.IDisposable C.F()"" + IL_000c: stloc.0 + .try + { + IL_000d: nop + IL_000e: nop + IL_000f: leave.s IL_001c + } + finally + { + IL_0011: ldloc.0 + IL_0012: brfalse.s IL_001b + IL_0014: ldloc.0 + IL_0015: callvirt ""void System.IDisposable.Dispose()"" + IL_001a: nop + IL_001b: endfinally + } + IL_001c: leave.s IL_0029 + } + finally + { + IL_001e: ldloc.1 + IL_001f: brfalse.s IL_0028 + IL_0021: ldloc.1 + IL_0022: callvirt ""void System.IDisposable.Dispose()"" + IL_0027: nop + IL_0028: endfinally + } + IL_0029: ret +} +"); + } + + [Fact] + public void Using_VariableDeclaration_VariableSwap() + { + var source0 = MarkedSource(@" +using System; + +class C +{ + static IDisposable F() => null; + + static void M() + { + using IDisposable u = F(), v = F(); + } +}"); + var source1 = MarkedSource(@" +using System; + +class C +{ + static IDisposable F() => null; + + static void M() + { + using IDisposable v = F(), u = F(); + } +}"); + var compilation0 = CreateCompilation(source0.Tree, options: TestOptions.DebugDll); + var compilation1 = compilation0.WithSource(source1.Tree); + + var v0 = CompileAndVerify(compilation0); + var symReader = v0.CreateSymReader(); + + using var md0 = ModuleMetadata.CreateFromImage(v0.EmittedAssemblyData); + var method0 = compilation0.GetMember("C.M"); + var method1 = compilation1.GetMember("C.M"); + + var generation0 = EmitBaseline.CreateInitialBaseline(md0, symReader.GetEncMethodDebugInfo); + + var diff1 = compilation1.EmitDifference( + generation0, + ImmutableArray.Create(SemanticEdit.Create(SemanticEditKind.Update, method0, method1, GetSyntaxMapFromMarkers(source0, source1), preserveLocalVariables: true))); + + v0.VerifyPdb("C.M", @" + + + + + + + + + + + + + + + + + + + + +", options: PdbValidationOptions.ExcludeSequencePoints); + + diff1.VerifyIL("C.M", @" +{ + // Code size 38 (0x26) + .maxstack 1 + .locals init (System.IDisposable V_0, //u + System.IDisposable V_1) //v + IL_0000: nop + IL_0001: call ""System.IDisposable C.F()"" + IL_0006: stloc.1 + .try + { + IL_0007: call ""System.IDisposable C.F()"" + IL_000c: stloc.0 + .try + { + IL_000d: leave.s IL_0025 + } + finally + { + IL_000f: ldloc.0 + IL_0010: brfalse.s IL_0019 + IL_0012: ldloc.0 + IL_0013: callvirt ""void System.IDisposable.Dispose()"" + IL_0018: nop + IL_0019: endfinally + } + } + finally + { + IL_001a: ldloc.1 + IL_001b: brfalse.s IL_0024 + IL_001d: ldloc.1 + IL_001e: callvirt ""void System.IDisposable.Dispose()"" + IL_0023: nop + IL_0024: endfinally + } + IL_0025: ret +} +"); + } + + [Fact] + public void AwaitUsing_VariableSwap() + { + var source0 = MarkedSource(@" +using System; +using System.Threading.Tasks; + +class C +{ + static IAsyncDisposable F() => null; + + static async Task M() + { + await using (IAsyncDisposable u = F(), v = F()) { } + } +}"); + var source1 = MarkedSource(@" +using System; +using System.Threading.Tasks; + +class C +{ + static IAsyncDisposable F() => null; + + static async Task M() + { + await using (IAsyncDisposable v = F(), u = F()) { } + } +}"); + var asyncStreamsTree = Parse(AsyncStreamsTypes); + var compilation0 = CreateCompilationWithTasksExtensions(new[] { source0.Tree, asyncStreamsTree }, options: TestOptions.DebugDll); + var compilation1 = compilation0.WithSource(new[] { source1.Tree, asyncStreamsTree }); + + var v0 = CompileAndVerify(compilation0); + var symReader = v0.CreateSymReader(); + + using var md0 = ModuleMetadata.CreateFromImage(v0.EmittedAssemblyData); + var method0 = compilation0.GetMember("C.M"); + var method1 = compilation1.GetMember("C.M"); + + var generation0 = EmitBaseline.CreateInitialBaseline(md0, symReader.GetEncMethodDebugInfo); + + var diff1 = compilation1.EmitDifference( + generation0, + ImmutableArray.Create(SemanticEdit.Create(SemanticEditKind.Update, method0, method1, GetSyntaxMapFromMarkers(source0, source1), preserveLocalVariables: true))); + + v0.VerifyPdb("C.M", @" + + + + + + + + + + + + + + + + + + + + + + + + +"); + v0.VerifyLocalSignature("C.d__1.System.Runtime.CompilerServices.IAsyncStateMachine.MoveNext", @" +.locals init (int V_0, + object V_1, + System.Runtime.CompilerServices.ValueTaskAwaiter V_2, + System.Threading.Tasks.ValueTask V_3, + C.d__1 V_4, + System.Exception V_5, + System.Runtime.CompilerServices.ValueTaskAwaiter V_6) +"); + + diff1.VerifyLocalSignature("C.d__1.System.Runtime.CompilerServices.IAsyncStateMachine.MoveNext", @" +.locals init (int V_0, + object V_1, + System.Runtime.CompilerServices.ValueTaskAwaiter V_2, + System.Threading.Tasks.ValueTask V_3, + C.d__1 V_4, + System.Exception V_5, + System.Runtime.CompilerServices.ValueTaskAwaiter V_6) +"); + } + + [Fact] + public void AwaitUsing_VariableDeclaration_VariableSwap() + { + var source0 = MarkedSource(@" +using System; +using System.Threading.Tasks; + +class C +{ + static IAsyncDisposable F() => null; + + static async Task M() + { + await using IAsyncDisposable u = F(), v = F(); + } +}"); + var source1 = MarkedSource(@" +using System; +using System.Threading.Tasks; + +class C +{ + static IAsyncDisposable F() => null; + + static async Task M() + { + await using IAsyncDisposable v = F(), u = F(); + } +}"); + var asyncStreamsTree = Parse(AsyncStreamsTypes); + var compilation0 = CreateCompilationWithTasksExtensions(new[] { source0.Tree, asyncStreamsTree }, options: TestOptions.DebugDll); + var compilation1 = compilation0.WithSource(new[] { source1.Tree, asyncStreamsTree }); + + var v0 = CompileAndVerify(compilation0); + var symReader = v0.CreateSymReader(); + + using var md0 = ModuleMetadata.CreateFromImage(v0.EmittedAssemblyData); + var method0 = compilation0.GetMember("C.M"); + var method1 = compilation1.GetMember("C.M"); + + var generation0 = EmitBaseline.CreateInitialBaseline(md0, symReader.GetEncMethodDebugInfo); + + var diff1 = compilation1.EmitDifference( + generation0, + ImmutableArray.Create(SemanticEdit.Create(SemanticEditKind.Update, method0, method1, GetSyntaxMapFromMarkers(source0, source1), preserveLocalVariables: true))); + + v0.VerifyPdb("C.M", @" + + + + + + + + + + + + + + + + + + + + + + + + +"); + v0.VerifyLocalSignature("C.d__1.System.Runtime.CompilerServices.IAsyncStateMachine.MoveNext", @" +.locals init (int V_0, + object V_1, + System.Runtime.CompilerServices.ValueTaskAwaiter V_2, + System.Threading.Tasks.ValueTask V_3, + C.d__1 V_4, + System.Exception V_5, + int V_6, + System.Runtime.CompilerServices.ValueTaskAwaiter V_7) +"); + + diff1.VerifyLocalSignature("C.d__1.System.Runtime.CompilerServices.IAsyncStateMachine.MoveNext", @" +.locals init (int V_0, + object V_1, + System.Runtime.CompilerServices.ValueTaskAwaiter V_2, + System.Threading.Tasks.ValueTask V_3, + C.d__1 V_4, + System.Exception V_5, + int V_6, + System.Runtime.CompilerServices.ValueTaskAwaiter V_7) +"); + } + [Fact] public void Lock() {