Skip to content

Commit

Permalink
Handle labels in outer scope in control flow pass (#73234)
Browse files Browse the repository at this point in the history
* Handle labels in outer scope in control flow pass

* Improve tests
  • Loading branch information
jjonescz committed Apr 30, 2024
1 parent 62c9c49 commit 5e95dce
Show file tree
Hide file tree
Showing 3 changed files with 170 additions and 3 deletions.
5 changes: 2 additions & 3 deletions src/Compilers/CSharp/Portable/FlowAnalysis/ControlFlowPass.cs
Original file line number Diff line number Diff line change
Expand Up @@ -353,13 +353,12 @@ public override BoundNode VisitGotoStatement(BoundGotoStatement node)
}
else if (sourceStart > usingStart && targetStart < usingStart)
{
// Backwards jump, so we must have already seen the label, or it must be a switch case label. If it is a switch case label, we know
// Backwards jump, so we must have already seen the label, or it must be a switch case label, or it might be in outer scope. If it is a switch case label, we know
// that either the user received an error for having a using declaration at the top level in a switch statement, or the label is a valid
// target to branch to.
Debug.Assert(_labelsDefined.ContainsKey(node.Label));

// Error if label and using are part of the same block
if (_labelsDefined[node.Label] == usingDecl.block)
if (_labelsDefined.TryGetValue(node.Label, out BoundNode target) && target == usingDecl.block)
{
Diagnostics.Add(ErrorCode.ERR_GoToBackwardJumpOverUsingVar, sourceLocation);
break;
Expand Down
56 changes: 56 additions & 0 deletions src/Compilers/CSharp/Test/Emit/CodeGen/GotoTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,62 @@ .maxstack 2
");
}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/73068")]
public void GotoInLambda_OutOfScope_Backward()
{
var code = """
x:
System.Action a = () =>
{
using System.IDisposable d = null;
goto x;
};
""";
CreateCompilation(code).VerifyEmitDiagnostics(
// (1,1): warning CS0164: This label has not been referenced
// x:
Diagnostic(ErrorCode.WRN_UnreferencedLabel, "x").WithLocation(1, 1),
// (5,5): error CS0159: No such label 'x' within the scope of the goto statement
// goto x;
Diagnostic(ErrorCode.ERR_LabelNotFound, "goto").WithArguments("x").WithLocation(5, 5));
}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/73068")]
public void GotoInLambda_OutOfScope_Forward()
{
var code = """
System.Action a = () =>
{
using System.IDisposable d = null;
goto x;
};
x:;
""";
CreateCompilation(code).VerifyEmitDiagnostics(
// (4,5): error CS0159: No such label 'x' within the scope of the goto statement
// goto x;
Diagnostic(ErrorCode.ERR_LabelNotFound, "goto").WithArguments("x").WithLocation(4, 5),
// (6,1): warning CS0164: This label has not been referenced
// x:;
Diagnostic(ErrorCode.WRN_UnreferencedLabel, "x").WithLocation(6, 1));
}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/73068")]
public void GotoInLambda_NonExistent()
{
var code = """
System.Action a = () =>
{
using System.IDisposable d = null;
goto x;
};
""";
CreateCompilation(code).VerifyEmitDiagnostics(
// (4,10): error CS0159: No such label 'x' within the scope of the goto statement
// goto x;
Diagnostic(ErrorCode.ERR_LabelNotFound, "x").WithArguments("x").WithLocation(4, 10));
}

// Definition same label in different lambdas
[WorkItem(5991, "DevDiv_Projects/Roslyn")]
[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7889,6 +7889,118 @@ Element Values(0)
VerifyFlowGraphAndDiagnosticsForTest<BlockSyntax>(source + s_IAsyncEnumerable + IOperationTests_IForEachLoopStatement.s_ValueTask, expectedGraph, expectedDiagnostics);
}

[CompilerTrait(CompilerFeature.IOperation, CompilerFeature.Dataflow)]
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/73068")]
public void UsingDeclaration_Flow_25()
{
var code = """
class C
{
void M()
/*<bind>*/{
x:
System.Action a = () => {
using System.IDisposable d = null;
goto x;
};
}/*</bind>*/

}
""";
var expectedGraph = """
Block[B0] - Entry
Statements (0)
Next (Regular) Block[B1]
Entering: {R1}
.locals {R1}
{
Locals: [System.Action a]
Block[B1] - Block
Predecessors: [B0]
Statements (1)
ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Action, IsInvalid, IsImplicit) (Syntax: 'a = () => { ... }')
Left:
ILocalReferenceOperation: a (IsDeclaration: True) (OperationKind.LocalReference, Type: System.Action, IsInvalid, IsImplicit) (Syntax: 'a = () => { ... }')
Right:
IDelegateCreationOperation (OperationKind.DelegateCreation, Type: System.Action, IsInvalid, IsImplicit) (Syntax: '() => { ... }')
Target:
IFlowAnonymousFunctionOperation (Symbol: lambda expression) (OperationKind.FlowAnonymousFunction, Type: null, IsInvalid) (Syntax: '() => { ... }')
{
Block[B0#A0] - Entry
Statements (0)
Next (Regular) Block[B1#A0]
Entering: {R1#A0}
.locals {R1#A0}
{
Locals: [System.IDisposable d]
Block[B1#A0] - Block
Predecessors: [B0#A0]
Statements (1)
ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.IDisposable, IsImplicit) (Syntax: 'd = null')
Left:
ILocalReferenceOperation: d (IsDeclaration: True) (OperationKind.LocalReference, Type: System.IDisposable, IsImplicit) (Syntax: 'd = null')
Right:
IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.IDisposable, Constant: null, IsImplicit) (Syntax: 'null')
Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null)
(ImplicitReference)
Operand:
ILiteralOperation (OperationKind.Literal, Type: null, Constant: null) (Syntax: 'null')
Next (Regular) Block[B2#A0]
Entering: {R2#A0} {R3#A0}
.try {R2#A0, R3#A0}
{
Block[B2#A0] - Block
Predecessors: [B1#A0]
Statements (0)
Next (Error) Block[null]
}
.finally {R4#A0}
{
Block[B3#A0] - Block
Predecessors (0)
Statements (0)
Jump if True (Regular) to Block[B5#A0]
IIsNullOperation (OperationKind.IsNull, Type: System.Boolean, IsImplicit) (Syntax: 'd = null')
Operand:
ILocalReferenceOperation: d (OperationKind.LocalReference, Type: System.IDisposable, IsImplicit) (Syntax: 'd = null')
Next (Regular) Block[B4#A0]
Block[B4#A0] - Block
Predecessors: [B3#A0]
Statements (1)
IInvocationOperation (virtual void System.IDisposable.Dispose()) (OperationKind.Invocation, Type: System.Void, IsImplicit) (Syntax: 'd = null')
Instance Receiver:
ILocalReferenceOperation: d (OperationKind.LocalReference, Type: System.IDisposable, IsImplicit) (Syntax: 'd = null')
Arguments(0)
Next (Regular) Block[B5#A0]
Block[B5#A0] - Block
Predecessors: [B3#A0] [B4#A0]
Statements (0)
Next (StructuredExceptionHandling) Block[null]
}
}
Block[B6#A0] - Exit [UnReachable]
Predecessors (0)
Statements (0)
}
Next (Regular) Block[B2]
Leaving: {R1}
}
Block[B2] - Exit
Predecessors: [B1]
Statements (0)
""";
var expectedDiagnostics = new[]
{
// (5,9): warning CS0164: This label has not been referenced
// x:
Diagnostic(ErrorCode.WRN_UnreferencedLabel, "x").WithLocation(5, 9),
// (8,13): error CS0159: No such label 'x' within the scope of the goto statement
// goto x;
Diagnostic(ErrorCode.ERR_LabelNotFound, "goto").WithArguments("x").WithLocation(8, 13)
};
VerifyFlowGraphAndDiagnosticsForTest<BlockSyntax>(code, expectedGraph, expectedDiagnostics);
}

[CompilerTrait(CompilerFeature.IOperation)]
[Fact, WorkItem(32100, "https://github.com/dotnet/roslyn/issues/32100")]
public void UsingDeclaration_SingleDeclaration()
Expand Down

0 comments on commit 5e95dce

Please sign in to comment.