From 9f78ab633f6c75efb8f5c48cb38829015b671ba6 Mon Sep 17 00:00:00 2001 From: AlekseyTs Date: Thu, 18 Jan 2018 14:44:04 -0800 Subject: [PATCH] An initial prototype to build a flow graph (#24263) --- .../CSharpOperationFactory_Methods.cs | 8 +- ...perationTests_IBinaryOperatorExpression.cs | 192 +++++- .../IOperationTests_IConditionalOperation.cs | 94 +++ .../IOperationTests_IIfStatement.cs | 227 ++++++- ...OperationTests_IUnaryOperatorExpression.cs | 89 ++- .../Portable/Compilation/SemanticModel.cs | 8 + .../Generated/Operations.xml.Generated.cs | 60 +- .../Core/Portable/Operations/BasicBlock.cs | 65 ++ .../Operations/ControlFlowGraphBuilder.cs | 563 ++++++++++++++++++ .../Operations/IFlowCaptureOperation.cs | 24 + .../Core/Portable/Operations/Operation.cs | 3 +- .../Portable/Operations/OperationCloner.cs | 9 +- .../Core/Portable/Operations/OperationKind.cs | 2 + .../Portable/Operations/OperationVisitor.cs | 10 + .../Core/Portable/PublicAPI.Unshipped.txt | 18 + .../Test/Utilities/CSharp/CSharpTestBase.cs | 85 +++ .../Compilation/OperationTreeVerifier.cs | 11 + 17 files changed, 1444 insertions(+), 24 deletions(-) create mode 100644 src/Compilers/Core/Portable/Operations/BasicBlock.cs create mode 100644 src/Compilers/Core/Portable/Operations/ControlFlowGraphBuilder.cs create mode 100644 src/Compilers/Core/Portable/Operations/IFlowCaptureOperation.cs diff --git a/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory_Methods.cs b/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory_Methods.cs index 002f8249e0e..0e5c4f3eb6f 100644 --- a/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory_Methods.cs +++ b/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory_Methods.cs @@ -297,7 +297,7 @@ internal static UnaryOperatorKind DeriveUnaryOperatorKind(CSharp.UnaryOperatorKi internal static BinaryOperatorKind DeriveBinaryOperatorKind(CSharp.BinaryOperatorKind operatorKind) { - switch (operatorKind & CSharp.BinaryOperatorKind.OpMask) + switch (operatorKind.OperatorWithLogical()) { case CSharp.BinaryOperatorKind.Addition: return BinaryOperatorKind.Add; @@ -346,6 +346,12 @@ internal static BinaryOperatorKind DeriveBinaryOperatorKind(CSharp.BinaryOperato case CSharp.BinaryOperatorKind.GreaterThan: return BinaryOperatorKind.GreaterThan; + + case CSharp.BinaryOperatorKind.LogicalAnd: + return BinaryOperatorKind.ConditionalAnd; + + case CSharp.BinaryOperatorKind.LogicalOr: + return BinaryOperatorKind.ConditionalOr; } return BinaryOperatorKind.None; diff --git a/src/Compilers/CSharp/Test/Semantic/IOperation/IOperationTests_IBinaryOperatorExpression.cs b/src/Compilers/CSharp/Test/Semantic/IOperation/IOperationTests_IBinaryOperatorExpression.cs index 157600de285..fdc1f7f50da 100644 --- a/src/Compilers/CSharp/Test/Semantic/IOperation/IOperationTests_IBinaryOperatorExpression.cs +++ b/src/Compilers/CSharp/Test/Semantic/IOperation/IOperationTests_IBinaryOperatorExpression.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Test.Utilities; @@ -431,5 +431,195 @@ void M(int a, int b, int c, int d, int e, int f, int g, int h, int i, int j, int VerifyOperationTreeAndDiagnosticsForTest(source, expectedOperationTree, expectedDiagnostics); } + + [Fact] + public void LogicalOrFlow_01() + { + string source = @" +class P +{ + void M(bool a, bool b) +/**/{ + GetArray()[0] = a || b; + }/**/ + + static bool[] GetArray() => null; +} +"; + string expectedGraph = @" +Block[0] - Entry + Statements (0) + Next Block[1] +Block[1] - Block + Predecessors (1) + [0] + Statements (1) + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Boolean, IsImplicit) (Syntax: 'GetArray()[0]') + Left: + IFlowCaptureOperation: 0 (IsInitialization: True) (OperationKind.FlowCapture, Type: System.Boolean, IsImplicit) (Syntax: 'GetArray()[0]') + Right: + IArrayElementReferenceOperation (OperationKind.ArrayElementReference, Type: System.Boolean) (Syntax: 'GetArray()[0]') + Array reference: + IInvocationOperation (System.Boolean[] P.GetArray()) (OperationKind.Invocation, Type: System.Boolean[]) (Syntax: 'GetArray()') + Instance Receiver: + null + Arguments(0) + Indices(1): + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 0) (Syntax: '0') + + Jump if True to Block[3] + IParameterReferenceOperation: a (OperationKind.ParameterReference, Type: System.Boolean) (Syntax: 'a') + + Next Block[2] +Block[2] - Block + Predecessors (1) + [1] + Statements (1) + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Boolean, IsImplicit) (Syntax: 'b') + Left: + IFlowCaptureOperation: 1 (IsInitialization: True) (OperationKind.FlowCapture, Type: System.Boolean, IsImplicit) (Syntax: 'b') + Right: + IParameterReferenceOperation: b (OperationKind.ParameterReference, Type: System.Boolean) (Syntax: 'b') + + Next Block[4] +Block[3] - Block + Predecessors (1) + [1] + Statements (1) + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Boolean, Constant: True, IsImplicit) (Syntax: 'a') + Left: + IFlowCaptureOperation: 1 (IsInitialization: True) (OperationKind.FlowCapture, Type: System.Boolean, IsImplicit) (Syntax: 'a') + Right: + ILiteralOperation (OperationKind.Literal, Type: System.Boolean, Constant: True, IsImplicit) (Syntax: 'a') + + Next Block[4] +Block[4] - Block + Predecessors (2) + [2] + [3] + Statements (1) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'GetArray()[0] = a || b;') + Expression: + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Boolean) (Syntax: 'GetArray()[0] = a || b') + Left: + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: System.Boolean, IsImplicit) (Syntax: 'GetArray()[0]') + Right: + IFlowCaptureOperation: 1 (OperationKind.FlowCapture, Type: System.Boolean, IsImplicit) (Syntax: 'a || b') + + Next Block[5] +Block[5] - Exit + Predecessors (1) + [4] + Statements (0) +"; + var expectedDiagnostics = DiagnosticDescription.None; + + VerifyFlowGraphAndDiagnosticsForTest(source, expectedGraph, expectedDiagnostics); + } + + [Fact] + public void LogicalOrFlow_02() + { + string source = @" +class P +{ + void M(bool a, bool b, bool c) +/**/{ + GetArray()[0] = c && (a || b); + }/**/ + + static bool[] GetArray() => null; +} +"; + string expectedGraph = @" +Block[0] - Entry + Statements (0) + Next Block[1] +Block[1] - Block + Predecessors (1) + [0] + Statements (1) + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Boolean, IsImplicit) (Syntax: 'GetArray()[0]') + Left: + IFlowCaptureOperation: 0 (IsInitialization: True) (OperationKind.FlowCapture, Type: System.Boolean, IsImplicit) (Syntax: 'GetArray()[0]') + Right: + IArrayElementReferenceOperation (OperationKind.ArrayElementReference, Type: System.Boolean) (Syntax: 'GetArray()[0]') + Array reference: + IInvocationOperation (System.Boolean[] P.GetArray()) (OperationKind.Invocation, Type: System.Boolean[]) (Syntax: 'GetArray()') + Instance Receiver: + null + Arguments(0) + Indices(1): + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 0) (Syntax: '0') + + Jump if False to Block[5] + IParameterReferenceOperation: c (OperationKind.ParameterReference, Type: System.Boolean) (Syntax: 'c') + + Next Block[2] +Block[2] - Block + Predecessors (1) + [1] + Statements (0) + Jump if True to Block[4] + IParameterReferenceOperation: a (OperationKind.ParameterReference, Type: System.Boolean) (Syntax: 'a') + + Next Block[3] +Block[3] - Block + Predecessors (1) + [2] + Statements (1) + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Boolean, IsImplicit) (Syntax: 'b') + Left: + IFlowCaptureOperation: 1 (IsInitialization: True) (OperationKind.FlowCapture, Type: System.Boolean, IsImplicit) (Syntax: 'b') + Right: + IParameterReferenceOperation: b (OperationKind.ParameterReference, Type: System.Boolean) (Syntax: 'b') + + Next Block[6] +Block[4] - Block + Predecessors (1) + [2] + Statements (1) + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Boolean, Constant: True, IsImplicit) (Syntax: 'a') + Left: + IFlowCaptureOperation: 1 (IsInitialization: True) (OperationKind.FlowCapture, Type: System.Boolean, IsImplicit) (Syntax: 'a') + Right: + ILiteralOperation (OperationKind.Literal, Type: System.Boolean, Constant: True, IsImplicit) (Syntax: 'a') + + Next Block[6] +Block[5] - Block + Predecessors (1) + [1] + Statements (1) + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Boolean, Constant: False, IsImplicit) (Syntax: 'c') + Left: + IFlowCaptureOperation: 1 (IsInitialization: True) (OperationKind.FlowCapture, Type: System.Boolean, IsImplicit) (Syntax: 'c') + Right: + ILiteralOperation (OperationKind.Literal, Type: System.Boolean, Constant: False, IsImplicit) (Syntax: 'c') + + Next Block[6] +Block[6] - Block + Predecessors (3) + [3] + [4] + [5] + Statements (1) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'GetArray()[ ... & (a || b);') + Expression: + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Boolean) (Syntax: 'GetArray()[ ... && (a || b)') + Left: + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: System.Boolean, IsImplicit) (Syntax: 'GetArray()[0]') + Right: + IFlowCaptureOperation: 1 (OperationKind.FlowCapture, Type: System.Boolean, IsImplicit) (Syntax: 'c && (a || b)') + + Next Block[7] +Block[7] - Exit + Predecessors (1) + [6] + Statements (0) +"; + var expectedDiagnostics = DiagnosticDescription.None; + + VerifyFlowGraphAndDiagnosticsForTest(source, expectedGraph, expectedDiagnostics); + } } } diff --git a/src/Compilers/CSharp/Test/Semantic/IOperation/IOperationTests_IConditionalOperation.cs b/src/Compilers/CSharp/Test/Semantic/IOperation/IOperationTests_IConditionalOperation.cs index 9434decd5d8..7e05af7f078 100644 --- a/src/Compilers/CSharp/Test/Semantic/IOperation/IOperationTests_IConditionalOperation.cs +++ b/src/Compilers/CSharp/Test/Semantic/IOperation/IOperationTests_IConditionalOperation.cs @@ -67,5 +67,99 @@ private void M() VerifyOperationTreeAndDiagnosticsForTest(source, expectedOperationTree, expectedDiagnostics); } + + [Fact] + public void ConditionalExpressionFlow_01() + { + string source = @" +class P +{ + void M(bool a, bool b) +/**/{ + GetArray()[0] = a && b ? 1 : 2; + }/**/ + + static int[] GetArray() => null; +} +"; + string expectedGraph = @" +Block[0] - Entry + Statements (0) + Next Block[1] +Block[1] - Block + Predecessors (1) + [0] + Statements (1) + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32, IsImplicit) (Syntax: 'GetArray()[0]') + Left: + IFlowCaptureOperation: 0 (IsInitialization: True) (OperationKind.FlowCapture, Type: System.Int32, IsImplicit) (Syntax: 'GetArray()[0]') + Right: + IArrayElementReferenceOperation (OperationKind.ArrayElementReference, Type: System.Int32) (Syntax: 'GetArray()[0]') + Array reference: + IInvocationOperation (System.Int32[] P.GetArray()) (OperationKind.Invocation, Type: System.Int32[]) (Syntax: 'GetArray()') + Instance Receiver: + null + Arguments(0) + Indices(1): + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 0) (Syntax: '0') + + Jump if False to Block[4] + IParameterReferenceOperation: a (OperationKind.ParameterReference, Type: System.Boolean) (Syntax: 'a') + + Next Block[2] +Block[2] - Block + Predecessors (1) + [1] + Statements (0) + Jump if False to Block[4] + IParameterReferenceOperation: b (OperationKind.ParameterReference, Type: System.Boolean) (Syntax: 'b') + + Next Block[3] +Block[3] - Block + Predecessors (1) + [2] + Statements (1) + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32, IsImplicit) (Syntax: '1') + Left: + IFlowCaptureOperation: 1 (IsInitialization: True) (OperationKind.FlowCapture, Type: System.Int32, IsImplicit) (Syntax: 'a && b ? 1 : 2') + Right: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1) (Syntax: '1') + + Next Block[5] +Block[4] - Block + Predecessors (2) + [1] + [2] + Statements (1) + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32, IsImplicit) (Syntax: '2') + Left: + IFlowCaptureOperation: 1 (IsInitialization: True) (OperationKind.FlowCapture, Type: System.Int32, IsImplicit) (Syntax: 'a && b ? 1 : 2') + Right: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 2) (Syntax: '2') + + Next Block[5] +Block[5] - Block + Predecessors (2) + [3] + [4] + Statements (1) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'GetArray()[ ... b ? 1 : 2;') + Expression: + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32) (Syntax: 'GetArray()[ ... & b ? 1 : 2') + Left: + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: System.Int32, IsImplicit) (Syntax: 'GetArray()[0]') + Right: + IFlowCaptureOperation: 1 (OperationKind.FlowCapture, Type: System.Int32, IsImplicit) (Syntax: 'a && b ? 1 : 2') + + Next Block[6] +Block[6] - Exit + Predecessors (1) + [5] + Statements (0) +"; + var expectedDiagnostics = DiagnosticDescription.None; + + VerifyFlowGraphAndDiagnosticsForTest(source, expectedGraph, expectedDiagnostics); + } } } diff --git a/src/Compilers/CSharp/Test/Semantic/IOperation/IOperationTests_IIfStatement.cs b/src/Compilers/CSharp/Test/Semantic/IOperation/IOperationTests_IIfStatement.cs index 4a7eccdffde..812f87dc8db 100644 --- a/src/Compilers/CSharp/Test/Semantic/IOperation/IOperationTests_IIfStatement.cs +++ b/src/Compilers/CSharp/Test/Semantic/IOperation/IOperationTests_IIfStatement.cs @@ -324,7 +324,7 @@ private void M() string expectedOperationTree = @" IConditionalOperation (OperationKind.Conditional, Type: null) (Syntax: 'if (m >= n ... }') Condition: - IBinaryOperation (BinaryOperatorKind.And) (OperationKind.BinaryOperator, Type: System.Boolean) (Syntax: 'm >= n && m >= p') + IBinaryOperation (BinaryOperatorKind.ConditionalAnd) (OperationKind.BinaryOperator, Type: System.Boolean) (Syntax: 'm >= n && m >= p') Left: IBinaryOperation (BinaryOperatorKind.GreaterThanOrEqual) (OperationKind.BinaryOperator, Type: System.Boolean) (Syntax: 'm >= n') Left: @@ -1122,7 +1122,7 @@ class C Condition: IUnaryOperation (UnaryOperatorKind.True) (OperationKind.UnaryOperator, Type: System.Boolean, IsImplicit) (Syntax: 'd.GetType() ... ).Equals(x)') Operand: - IBinaryOperation (BinaryOperatorKind.And) (OperationKind.BinaryOperator, Type: dynamic) (Syntax: 'd.GetType() ... ).Equals(x)') + IBinaryOperation (BinaryOperatorKind.ConditionalAnd) (OperationKind.BinaryOperator, Type: dynamic) (Syntax: 'd.GetType() ... ).Equals(x)') Left: IBinaryOperation (BinaryOperatorKind.Equals) (OperationKind.BinaryOperator, Type: dynamic) (Syntax: 'd.GetType() == t') Left: @@ -1164,5 +1164,228 @@ Type Arguments(0) VerifyOperationTreeAndDiagnosticsForTest(source, expectedOperationTree, expectedDiagnostics); } + + [Fact] + public void IfFlow_01() + { + string source = @" +class P +{ + void M() +/**/{ + bool condition = false; + if (true) + { + condition = true; + } + }/**/ +} +"; + string expectedGraph = @" +Block[0] - Entry + Statements (0) + Next Block[1] +Block[1] - Block + Predecessors (1) + [0] + Statements (1) + IVariableDeclarationGroupOperation (1 declarations) (OperationKind.VariableDeclarationGroup, Type: null) (Syntax: 'bool condition = false;') + IVariableDeclarationOperation (1 declarators) (OperationKind.VariableDeclaration, Type: null) (Syntax: 'bool condition = false') + Declarators: + IVariableDeclaratorOperation (Symbol: System.Boolean condition) (OperationKind.VariableDeclarator, Type: null) (Syntax: 'condition = false') + Initializer: + IVariableInitializerOperation (OperationKind.VariableInitializer, Type: null) (Syntax: '= false') + ILiteralOperation (OperationKind.Literal, Type: System.Boolean, Constant: False) (Syntax: 'false') + Initializer: + null + + Jump if False to Block[3] + ILiteralOperation (OperationKind.Literal, Type: System.Boolean, Constant: True) (Syntax: 'true') + + Next Block[2] +Block[2] - Block + Predecessors (1) + [1] + Statements (1) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'condition = true;') + Expression: + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Boolean) (Syntax: 'condition = true') + Left: + ILocalReferenceOperation: condition (OperationKind.LocalReference, Type: System.Boolean) (Syntax: 'condition') + Right: + ILiteralOperation (OperationKind.Literal, Type: System.Boolean, Constant: True) (Syntax: 'true') + + Next Block[3] +Block[3] - Exit + Predecessors (2) + [1] + [2] + Statements (0) +"; + var expectedDiagnostics = new DiagnosticDescription[] { + // CS0219: The variable 'condition' is assigned but its value is never used + // bool condition = false; + Diagnostic(ErrorCode.WRN_UnreferencedVarAssg, "condition").WithArguments("condition").WithLocation(6, 14) + }; + + VerifyFlowGraphAndDiagnosticsForTest(source, expectedGraph, expectedDiagnostics); + } + + [Fact] + public void IfFlow_02() + { + string source = @" +class P +{ + void M() +/**/{ + bool condition = false; + if (true) + { + ; + } + else + { + condition = true; + } + }/**/ +} +"; + string expectedGraph = @" +Block[0] - Entry + Statements (0) + Next Block[1] +Block[1] - Block + Predecessors (1) + [0] + Statements (1) + IVariableDeclarationGroupOperation (1 declarations) (OperationKind.VariableDeclarationGroup, Type: null) (Syntax: 'bool condition = false;') + IVariableDeclarationOperation (1 declarators) (OperationKind.VariableDeclaration, Type: null) (Syntax: 'bool condition = false') + Declarators: + IVariableDeclaratorOperation (Symbol: System.Boolean condition) (OperationKind.VariableDeclarator, Type: null) (Syntax: 'condition = false') + Initializer: + IVariableInitializerOperation (OperationKind.VariableInitializer, Type: null) (Syntax: '= false') + ILiteralOperation (OperationKind.Literal, Type: System.Boolean, Constant: False) (Syntax: 'false') + Initializer: + null + + Jump if False to Block[3] + ILiteralOperation (OperationKind.Literal, Type: System.Boolean, Constant: True) (Syntax: 'true') + + Next Block[2] +Block[2] - Block + Predecessors (1) + [1] + Statements (1) + IEmptyOperation (OperationKind.Empty, Type: null) (Syntax: ';') + + Next Block[4] +Block[3] - Block + Predecessors (1) + [1] + Statements (1) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'condition = true;') + Expression: + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Boolean) (Syntax: 'condition = true') + Left: + ILocalReferenceOperation: condition (OperationKind.LocalReference, Type: System.Boolean) (Syntax: 'condition') + Right: + ILiteralOperation (OperationKind.Literal, Type: System.Boolean, Constant: True) (Syntax: 'true') + + Next Block[4] +Block[4] - Exit + Predecessors (2) + [2] + [3] + Statements (0) +"; + var expectedDiagnostics = new DiagnosticDescription[] { + // file.cs(13,13): warning CS0162: Unreachable code detected + // condition = true; + Diagnostic(ErrorCode.WRN_UnreachableCode, "condition").WithLocation(13, 13), + // file.cs(6,14): warning CS0219: The variable 'condition' is assigned but its value is never used + // bool condition = false; + Diagnostic(ErrorCode.WRN_UnreferencedVarAssg, "condition").WithArguments("condition").WithLocation(6, 14) + }; + + VerifyFlowGraphAndDiagnosticsForTest(source, expectedGraph, expectedDiagnostics); + } + + [Fact] + public void IfFlow_03() + { + string source = @" +class P +{ + void M(bool a, bool b) +/**/{ + if (a && b) + { + a = false; + } + else + { + b = true; + } + }/**/ +} +"; + string expectedGraph = @" +Block[0] - Entry + Statements (0) + Next Block[1] +Block[1] - Block + Predecessors (1) + [0] + Statements (0) + Jump if False to Block[4] + IParameterReferenceOperation: a (OperationKind.ParameterReference, Type: System.Boolean) (Syntax: 'a') + + Next Block[2] +Block[2] - Block + Predecessors (1) + [1] + Statements (0) + Jump if False to Block[4] + IParameterReferenceOperation: b (OperationKind.ParameterReference, Type: System.Boolean) (Syntax: 'b') + + Next Block[3] +Block[3] - Block + Predecessors (1) + [2] + Statements (1) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'a = false;') + Expression: + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Boolean) (Syntax: 'a = false') + Left: + IParameterReferenceOperation: a (OperationKind.ParameterReference, Type: System.Boolean) (Syntax: 'a') + Right: + ILiteralOperation (OperationKind.Literal, Type: System.Boolean, Constant: False) (Syntax: 'false') + + Next Block[5] +Block[4] - Block + Predecessors (2) + [1] + [2] + Statements (1) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'b = true;') + Expression: + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Boolean) (Syntax: 'b = true') + Left: + IParameterReferenceOperation: b (OperationKind.ParameterReference, Type: System.Boolean) (Syntax: 'b') + Right: + ILiteralOperation (OperationKind.Literal, Type: System.Boolean, Constant: True) (Syntax: 'true') + + Next Block[5] +Block[5] - Exit + Predecessors (2) + [3] + [4] + Statements (0) +"; + var expectedDiagnostics = DiagnosticDescription.None; + + VerifyFlowGraphAndDiagnosticsForTest(source, expectedGraph, expectedDiagnostics); + } } } diff --git a/src/Compilers/CSharp/Test/Semantic/IOperation/IOperationTests_IUnaryOperatorExpression.cs b/src/Compilers/CSharp/Test/Semantic/IOperation/IOperationTests_IUnaryOperatorExpression.cs index 5ca1681294c..9d440352b80 100644 --- a/src/Compilers/CSharp/Test/Semantic/IOperation/IOperationTests_IUnaryOperatorExpression.cs +++ b/src/Compilers/CSharp/Test/Semantic/IOperation/IOperationTests_IUnaryOperatorExpression.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Test.Utilities; @@ -3419,5 +3419,92 @@ void F(C x) VerifyOperationTreeForTest(source, expectedOperationTree); } + + [Fact] + public void LogicalNotFlow_01() + { + string source = @" +class P +{ + void M(bool a, bool b) +/**/{ + GetArray()[0] = !(a || b); + }/**/ + + static bool[] GetArray() => null; +} +"; + string expectedGraph = @" +Block[0] - Entry + Statements (0) + Next Block[1] +Block[1] - Block + Predecessors (1) + [0] + Statements (1) + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Boolean, IsImplicit) (Syntax: 'GetArray()[0]') + Left: + IFlowCaptureOperation: 0 (IsInitialization: True) (OperationKind.FlowCapture, Type: System.Boolean, IsImplicit) (Syntax: 'GetArray()[0]') + Right: + IArrayElementReferenceOperation (OperationKind.ArrayElementReference, Type: System.Boolean) (Syntax: 'GetArray()[0]') + Array reference: + IInvocationOperation (System.Boolean[] P.GetArray()) (OperationKind.Invocation, Type: System.Boolean[]) (Syntax: 'GetArray()') + Instance Receiver: + null + Arguments(0) + Indices(1): + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 0) (Syntax: '0') + + Jump if True to Block[3] + IParameterReferenceOperation: a (OperationKind.ParameterReference, Type: System.Boolean) (Syntax: 'a') + + Next Block[2] +Block[2] - Block + Predecessors (1) + [1] + Statements (1) + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Boolean, IsImplicit) (Syntax: 'b') + Left: + IFlowCaptureOperation: 1 (IsInitialization: True) (OperationKind.FlowCapture, Type: System.Boolean, IsImplicit) (Syntax: 'b') + Right: + IUnaryOperation (UnaryOperatorKind.Not) (OperationKind.UnaryOperator, Type: System.Boolean, IsImplicit) (Syntax: 'b') + Operand: + IParameterReferenceOperation: b (OperationKind.ParameterReference, Type: System.Boolean) (Syntax: 'b') + + Next Block[4] +Block[3] - Block + Predecessors (1) + [1] + Statements (1) + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Boolean, Constant: False, IsImplicit) (Syntax: 'a') + Left: + IFlowCaptureOperation: 1 (IsInitialization: True) (OperationKind.FlowCapture, Type: System.Boolean, IsImplicit) (Syntax: 'a') + Right: + ILiteralOperation (OperationKind.Literal, Type: System.Boolean, Constant: False, IsImplicit) (Syntax: 'a') + + Next Block[4] +Block[4] - Block + Predecessors (2) + [2] + [3] + Statements (1) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'GetArray()[ ... !(a || b);') + Expression: + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Boolean) (Syntax: 'GetArray()[ ... !(a || b)') + Left: + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: System.Boolean, IsImplicit) (Syntax: 'GetArray()[0]') + Right: + IFlowCaptureOperation: 1 (OperationKind.FlowCapture, Type: System.Boolean, IsImplicit) (Syntax: 'a || b') + + Next Block[5] +Block[5] - Exit + Predecessors (1) + [4] + Statements (0) +"; + var expectedDiagnostics = DiagnosticDescription.None; + + VerifyFlowGraphAndDiagnosticsForTest(source, expectedGraph, expectedDiagnostics); + } } } diff --git a/src/Compilers/Core/Portable/Compilation/SemanticModel.cs b/src/Compilers/Core/Portable/Compilation/SemanticModel.cs index b1ebb58fdcb..16b3bcef024 100644 --- a/src/Compilers/Core/Portable/Compilation/SemanticModel.cs +++ b/src/Compilers/Core/Portable/Compilation/SemanticModel.cs @@ -86,6 +86,14 @@ public IOperation GetOperation(SyntaxNode node, CancellationToken cancellationTo protected abstract IOperation GetOperationCore(SyntaxNode node, CancellationToken cancellationToken); + /// + /// PROTOTYPE(dataflow): Add documentation. Also, we should guard this API with a feature flag before we merge it to master. + /// + public ImmutableArray GetControlFlowGraph(Operations.IBlockOperation body) + { + return Operations.ControlFlowGraphBuilder.Create(body); + } + /// /// Returns true if this is a SemanticModel that ignores accessibility rules when answering semantic questions. /// diff --git a/src/Compilers/Core/Portable/Generated/Operations.xml.Generated.cs b/src/Compilers/Core/Portable/Generated/Operations.xml.Generated.cs index 58a71058068..1e2096ad8f9 100644 --- a/src/Compilers/Core/Portable/Generated/Operations.xml.Generated.cs +++ b/src/Compilers/Core/Portable/Generated/Operations.xml.Generated.cs @@ -988,7 +988,7 @@ public override IEnumerable Children { get { - yield break; + return Array.Empty(); } } public override void Accept(OperationVisitor visitor) @@ -1288,7 +1288,7 @@ public override IEnumerable Children { get { - yield break; + return Array.Empty(); } } public override void Accept(OperationVisitor visitor) @@ -1499,7 +1499,7 @@ public override IEnumerable Children { get { - yield break; + return Array.Empty(); } } public override void Accept(OperationVisitor visitor) @@ -1525,7 +1525,7 @@ public override IEnumerable Children { get { - yield break; + return Array.Empty(); } } public override void Accept(OperationVisitor visitor) @@ -1551,7 +1551,7 @@ public override IEnumerable Children { get { - yield break; + return Array.Empty(); } } public override void Accept(OperationVisitor visitor) @@ -2507,7 +2507,7 @@ public override IEnumerable Children { get { - yield break; + return Array.Empty(); } } public override void Accept(OperationVisitor visitor) @@ -3329,7 +3329,7 @@ public override IEnumerable Children { get { - yield break; + return Array.Empty(); } } public override void Accept(OperationVisitor visitor) @@ -3362,7 +3362,7 @@ public override IEnumerable Children { get { - yield break; + return Array.Empty(); } } public override void Accept(OperationVisitor visitor) @@ -3825,7 +3825,7 @@ public override IEnumerable Children { get { - yield break; + return Array.Empty(); } } public override void Accept(OperationVisitor visitor) @@ -3919,7 +3919,7 @@ public override IEnumerable Children { get { - yield break; + return Array.Empty(); } } public override void Accept(OperationVisitor visitor) @@ -4012,7 +4012,7 @@ public override IEnumerable Children { get { - yield break; + return Array.Empty(); } } public override void Accept(OperationVisitor visitor) @@ -4554,7 +4554,7 @@ public override IEnumerable Children { get { - yield break; + return Array.Empty(); } } public override void Accept(OperationVisitor visitor) @@ -4585,7 +4585,7 @@ public override IEnumerable Children { get { - yield break; + return Array.Empty(); } } public override void Accept(OperationVisitor visitor) @@ -4611,7 +4611,7 @@ public override IEnumerable Children { get { - yield break; + return Array.Empty(); } } public override void Accept(OperationVisitor visitor) @@ -4995,7 +4995,7 @@ public override IEnumerable Children { get { - yield break; + return Array.Empty(); } } public override void Accept(OperationVisitor visitor) @@ -6068,7 +6068,7 @@ public override IEnumerable Children { get { - yield break; + return Array.Empty(); } } public override void Accept(OperationVisitor visitor) @@ -6547,4 +6547,32 @@ internal sealed partial class LazyTranslatedQueryExpression : BaseTranslatedQuer } protected override IOperation ExpressionImpl => _lazyExpression.Value; } + + internal sealed partial class FlowCapture : Operation, IFlowCaptureOperation + { + public FlowCapture(int id, SyntaxNode syntax, bool isInitialization, ITypeSymbol type, Optional constantValue) : + base(OperationKind.FlowCapture, semanticModel: null, syntax, type, constantValue, isImplicit: true) + { + Id = id; + IsInitialization = isInitialization; + } + + public int Id { get; } + public bool IsInitialization { get; } + public override IEnumerable Children + { + get + { + return Array.Empty(); + } + } + public override void Accept(OperationVisitor visitor) + { + visitor.VisitFlowCapture(this); + } + public override TResult Accept(OperationVisitor visitor, TArgument argument) + { + return visitor.VisitFlowCapture(this, argument); + } + } } diff --git a/src/Compilers/Core/Portable/Operations/BasicBlock.cs b/src/Compilers/Core/Portable/Operations/BasicBlock.cs new file mode 100644 index 00000000000..5bfe329d430 --- /dev/null +++ b/src/Compilers/Core/Portable/Operations/BasicBlock.cs @@ -0,0 +1,65 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using Microsoft.CodeAnalysis.PooledObjects; + +namespace Microsoft.CodeAnalysis.Operations +{ + /// + /// PROTOTYPE(dataflow): Add documentation + /// + public enum BasicBlockKind + { + Entry, + Exit, + Block + } + + /// + /// PROTOTYPE(dataflow): Add documentation + /// PROTOTYPE(dataflow): We need to figure out how to split it into a builder and + /// a public immutable type. + /// + public sealed class BasicBlock + { + private readonly ImmutableArray.Builder _statements; + private readonly ImmutableHashSet.Builder _predecessors; + + public BasicBlock(BasicBlockKind kind) + { + Kind = kind; + _statements = ImmutableArray.CreateBuilder(); + _predecessors = ImmutableHashSet.CreateBuilder(); + } + + public BasicBlockKind Kind { get; private set; } + public ImmutableArray Statements => _statements.ToImmutable(); + + /// + /// PROTOTYPE(dataflow): Tuple is temporary return type, we probably should use special structure instead. + /// + public (IOperation Condition, bool JumpIfTrue, BasicBlock Destination) Conditional { get; internal set; } + + /// + /// PROTOTYPE(dataflow): During CR there was a suggestion to use different name - "Successor". + /// + public BasicBlock Next { get; internal set; } + + public ImmutableHashSet Predecessors => _predecessors.ToImmutable(); + + internal void AddStatement(IOperation statement) + { + _statements.Add(statement); + } + + internal void AddPredecessor(BasicBlock block) + { + _predecessors.Add(block); + } + + internal void RemovePredecessor(BasicBlock block) + { + _predecessors.Remove(block); + } + } +} diff --git a/src/Compilers/Core/Portable/Operations/ControlFlowGraphBuilder.cs b/src/Compilers/Core/Portable/Operations/ControlFlowGraphBuilder.cs new file mode 100644 index 00000000000..49e71a1cd77 --- /dev/null +++ b/src/Compilers/Core/Portable/Operations/ControlFlowGraphBuilder.cs @@ -0,0 +1,563 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Concurrent; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Linq; +using Microsoft.CodeAnalysis.PooledObjects; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Operations +{ + internal sealed class ControlFlowGraphBuilder : OperationCloner + { + // PROTOTYPE(dataflow): does it have to be a field? + private readonly BasicBlock _entry = new BasicBlock(BasicBlockKind.Entry); + + // PROTOTYPE(dataflow): does it have to be a field? + private readonly BasicBlock _exit = new BasicBlock(BasicBlockKind.Exit); + + private ArrayBuilder _blocks; + private BasicBlock _currentBasicBlock; + private IOperation _currentStatement; + private ArrayBuilder _evalStack; + + // PROTOTYPE(dataflow): does the public API IFlowCaptureOperation.Id specify how identifiers are created or assigned? + // Should we use uint to exclude negative integers? Should we randomize them in any way to avoid dependencies + // being taken? + private int _availableCaptureId = 0; + + private ControlFlowGraphBuilder() + { } + + public static ImmutableArray Create(IBlockOperation body) + { + var builder = new ControlFlowGraphBuilder(); + var blocks = ArrayBuilder.GetInstance(); + builder._blocks = blocks; + builder._evalStack = ArrayBuilder.GetInstance(); + blocks.Add(builder._entry); + + builder.VisitStatement(body); + builder.AppendNewBlock(builder._exit); + + // Do a pass to eliminate blocks without statements and only Next set. + Pack(blocks); + + Debug.Assert(builder._evalStack.Count == 0); + builder._evalStack.Free(); + + return blocks.ToImmutableAndFree(); + } + + private static void Pack(ArrayBuilder blocks) + { + int count = blocks.Count - 1; + for (int i = 1; i < count; i++) + { + BasicBlock block = blocks[i]; + Debug.Assert(block.Next != null); + if (block.Statements.IsEmpty && block.Conditional.Condition == null) + { + BasicBlock next = block.Next; + foreach (BasicBlock predecessor in block.Predecessors) + { + if (predecessor.Next == block) + { + predecessor.Next = next; + next.AddPredecessor(predecessor); + } + + (IOperation condition, bool jumpIfTrue, BasicBlock destination) = predecessor.Conditional; + if (destination == block) + { + predecessor.Conditional = (condition, jumpIfTrue, next); + next.AddPredecessor(predecessor); + } + } + + next.RemovePredecessor(block); + blocks.RemoveAt(i); + i--; + count--; + } + } + } + + private void VisitStatement(IOperation operation) + { + IOperation saveCurrentStatement = _currentStatement; + _currentStatement = operation; + Debug.Assert(_evalStack.Count == 0); + + // PROTOTYPE(dataflow): Ensure that statement's parent is null at this point. + AddStatement(Visit(operation, null)); + Debug.Assert(_evalStack.Count == 0); + _currentStatement = saveCurrentStatement; + } + + private BasicBlock CurrentBasicBlock + { + get + { + if (_currentBasicBlock == null) + { + AppendNewBlock(new BasicBlock(BasicBlockKind.Block)); + } + + return _currentBasicBlock; + } + } + + private void AddStatement(IOperation statement) + { + if (statement == null) + { + return; + } + + // PROTOTYPE(dataflow): Assert that statement's parent is null at this point. + CurrentBasicBlock.AddStatement(statement); + } + + private void AppendNewBlock(BasicBlock block) + { + Debug.Assert(block != null); + BasicBlock prevBlock = _blocks.Last(); + + if (prevBlock.Next == null) + { + LinkBlocks(prevBlock, block); + } + + _blocks.Add(block); + _currentBasicBlock = block; + } + + private static void LinkBlocks(BasicBlock prevBlock, BasicBlock nextBlock) + { + Debug.Assert(prevBlock.Next == null); + prevBlock.Next = nextBlock; + nextBlock.AddPredecessor(prevBlock); + } + + public override IOperation VisitBlock(IBlockOperation operation, object argument) + { + foreach (var statement in operation.Operations) + { + VisitStatement(statement); + } + + return null; + } + + public override IOperation VisitConditional(IConditionalOperation operation, object argument) + { + if (operation == _currentStatement) + { + if (operation.WhenFalse == null) + { + // if (condition) + // consequence; + // + // becomes + // + // GotoIfFalse condition afterif; + // consequence; + // afterif: + + BasicBlock afterIf = null; + VisitConditionalBranch(operation.Condition, ref afterIf, sense: false); + VisitStatement(operation.WhenTrue); + AppendNewBlock(afterIf); + } + else + { + // if (condition) + // consequence; + // else + // alternative + // + // becomes + // + // GotoIfFalse condition alt; + // consequence + // goto afterif; + // alt: + // alternative; + // afterif: + + BasicBlock whenFalse = null; + VisitConditionalBranch(operation.Condition, ref whenFalse, sense: false); + + VisitStatement(operation.WhenTrue); + + var afterIf = new BasicBlock(BasicBlockKind.Block); + LinkBlocks(CurrentBasicBlock, afterIf); + _currentBasicBlock = null; + + AppendNewBlock(whenFalse); + VisitStatement(operation.WhenFalse); + + AppendNewBlock(afterIf); + } + + return null; + } + else + { + // condition ? consequence : alternative + // + // becomes + // + // GotoIfFalse condition alt; + // capture = consequence + // goto afterif; + // alt: + // capture = alternative; + // afterif: + // result - capture + + SpillEvalStack(); + int captureId = _availableCaptureId++; + + BasicBlock whenFalse = null; + VisitConditionalBranch(operation.Condition, ref whenFalse, sense: false); + + AddStatement(new SimpleAssignmentExpression(new FlowCapture(captureId, operation.Syntax, + isInitialization: true, operation.Type, + default(Optional)), + operation.IsRef, + Visit(operation.WhenTrue), + semanticModel: null, + operation.WhenTrue.Syntax, + operation.Type, + default(Optional), + isImplicit: true)); + + var afterIf = new BasicBlock(BasicBlockKind.Block); + LinkBlocks(CurrentBasicBlock, afterIf); + _currentBasicBlock = null; + + AppendNewBlock(whenFalse); + + AddStatement(new SimpleAssignmentExpression(new FlowCapture(captureId, operation.Syntax, + isInitialization: true, operation.Type, + default(Optional)), + operation.IsRef, + Visit(operation.WhenFalse), + semanticModel: null, + operation.WhenFalse.Syntax, + operation.Type, + default(Optional), + isImplicit: true)); + + AppendNewBlock(afterIf); + + return new FlowCapture(captureId, operation.Syntax, isInitialization: false, operation.Type, operation.ConstantValue); + } + } + + private void SpillEvalStack() + { + for (int i = 0; i < _evalStack.Count; i++) + { + IOperation operation = _evalStack[i]; + if (operation.Kind != OperationKind.FlowCapture) + { + int captureId = _availableCaptureId++; + + AddStatement(new SimpleAssignmentExpression(new FlowCapture(captureId, operation.Syntax, + isInitialization: true, operation.Type, + default(Optional)), + isRef: false, // PROTOTYPE(dataflow): Is 'false' always the right value? + operation, + semanticModel: null, + operation.Syntax, + operation.Type, + default(Optional), + isImplicit: true)); + + _evalStack[i] = new FlowCapture(captureId, operation.Syntax, isInitialization: false, operation.Type, operation.ConstantValue); + } + } + } + + public override IOperation VisitSimpleAssignment(ISimpleAssignmentOperation operation, object argument) + { + _evalStack.Push(Visit(operation.Target)); + IOperation value = Visit(operation.Value); + return new SimpleAssignmentExpression(_evalStack.Pop(), operation.IsRef, value, null, operation.Syntax, operation.Type, operation.ConstantValue, operation.IsImplicit); + } + + // PROTOTYPE(dataflow): + //public override IOperation VisitArrayElementReference(IArrayElementReferenceOperation operation, object argument) + //{ + // _evalStack.Push(Visit(operation.ArrayReference)); + // foreach (var index in operation.Indices) + // return new ArrayElementReferenceExpression(Visit(operation.ArrayReference), VisitArray(operation.Indices), ((Operation)operation).SemanticModel, operation.Syntax, operation.Type, operation.ConstantValue, operation.IsImplicit); + //} + + private static bool IsConditional(IBinaryOperation operation) + { + switch (operation.OperatorKind) + { + case BinaryOperatorKind.ConditionalOr: + case BinaryOperatorKind.ConditionalAnd: + return true; + } + + return false; + } + + public override IOperation VisitBinaryOperator(IBinaryOperation operation, object argument) + { + if (IsConditional(operation)) + { + return VisitBinaryConditionalOperator(operation, sense: true); + } + + return base.VisitBinaryOperator(operation, argument); + } + + public override IOperation VisitUnaryOperator(IUnaryOperation operation, object argument) + { + // PROTOTYPE(dataflow): ensure we properly detect logical Not + if (operation.OperatorKind == UnaryOperatorKind.Not) + { + return VisitConditionalExpression(operation.Operand, sense: false); + } + + return base.VisitUnaryOperator(operation, argument); + } + + private IOperation VisitBinaryConditionalOperator(IBinaryOperation binOp, bool sense) + { + bool andOrSense = sense; + + switch (binOp.OperatorKind) + { + case BinaryOperatorKind.ConditionalOr: + Debug.Assert(binOp.LeftOperand.Type.SpecialType == SpecialType.System_Boolean); + Debug.Assert(binOp.RightOperand.Type.SpecialType == SpecialType.System_Boolean); + + // Rewrite (a || b) as ~(~a && ~b) + andOrSense = !andOrSense; + // Fall through + goto case BinaryOperatorKind.ConditionalAnd; + + case BinaryOperatorKind.ConditionalAnd: + Debug.Assert(binOp.LeftOperand.Type.SpecialType == SpecialType.System_Boolean); + Debug.Assert(binOp.RightOperand.Type.SpecialType == SpecialType.System_Boolean); + + // ~(a && b) is equivalent to (~a || ~b) + if (!andOrSense) + { + // generate (~a || ~b) + return VisitShortCircuitingOperator(binOp, sense: sense, stopSense: sense, stopValue: true); + } + else + { + // generate (a && b) + return VisitShortCircuitingOperator(binOp, sense: sense, stopSense: !sense, stopValue: false); + } + + default: + throw ExceptionUtilities.UnexpectedValue(binOp.OperatorKind); + } + } + + private IOperation VisitShortCircuitingOperator(IBinaryOperation condition, bool sense, bool stopSense, bool stopValue) + { + // we generate: + // + // gotoif (a == stopSense) fallThrough + // b == sense + // goto labEnd + // fallThrough: + // stopValue + // labEnd: + // AND OR + // +- ------ ----- + // stopSense | !sense sense + // stopValue | 0 1 + + SpillEvalStack(); + + BasicBlock lazyFallThrough = null; + + VisitConditionalBranch(condition.LeftOperand, ref lazyFallThrough, stopSense); + IOperation resultFromRight = VisitConditionalExpression(condition.RightOperand, sense); + + int captureId; + + if (resultFromRight.Kind == OperationKind.FlowCapture) + { + captureId = ((IFlowCaptureOperation)resultFromRight).Id; + } + else + { + captureId = _availableCaptureId++; + SyntaxNode rightSyntax = condition.RightOperand.Syntax; + AddStatement(new SimpleAssignmentExpression(new FlowCapture(captureId, rightSyntax, + isInitialization: true, condition.Type, + default(Optional)), + isRef: false, + resultFromRight, + semanticModel: null, + rightSyntax, + condition.Type, + default(Optional), + isImplicit: true)); + } + + var labEnd = new BasicBlock(BasicBlockKind.Block); + LinkBlocks(CurrentBasicBlock, labEnd); + _currentBasicBlock = null; + + AppendNewBlock(lazyFallThrough); + + var constantValue = new Optional(stopValue); + SyntaxNode leftSyntax = condition.LeftOperand.Syntax; + AddStatement(new SimpleAssignmentExpression(new FlowCapture(captureId, leftSyntax, + isInitialization: true, condition.Type, + default(Optional)), + isRef: false, + new LiteralExpression(semanticModel: null, leftSyntax, condition.Type, constantValue, isImplicit: true), + semanticModel: null, + leftSyntax, + condition.Type, + constantValue, + isImplicit: true)); + + AppendNewBlock(labEnd); + + return new FlowCapture(captureId, condition.Syntax, isInitialization: false, condition.Type, condition.ConstantValue); + } + + private IOperation VisitConditionalExpression(IOperation condition, bool sense) + { + // PROTOTYPE(dataflow): Do not erase UnaryOperatorKind.Not if ProduceIsSense below will have to add it back. + while (condition.Kind == OperationKind.UnaryOperator) + { + var unOp = (IUnaryOperation)condition; + // PROTOTYPE(dataflow): ensure we properly detect logical Not + if (unOp.OperatorKind != UnaryOperatorKind.Not) + { + break; + } + condition = unOp.Operand; + sense = !sense; + } + + Debug.Assert(condition.Type.SpecialType == SpecialType.System_Boolean); + + if (condition.Kind == OperationKind.BinaryOperator) + { + var binOp = (IBinaryOperation)condition; + if (IsConditional(binOp)) + { + return VisitBinaryConditionalOperator(binOp, sense); + } + } + + return ProduceIsSense(Visit(condition), sense); + } + + private IOperation ProduceIsSense(IOperation condition, bool sense) + { + if (!sense) + { + return new UnaryOperatorExpression(UnaryOperatorKind.Not, + condition, + isLifted: false, // PROTOTYPE(dataflow): Deal with nullable + isChecked: false, + operatorMethod: null, + semanticModel: null, + condition.Syntax, + condition.Type, + constantValue: default, // revert constant value if we have one. + isImplicit: true); + } + + return condition; + } + + private void VisitConditionalBranch(IOperation condition, ref BasicBlock dest, bool sense) + { +oneMoreTime: + + switch (condition.Kind) + { + case OperationKind.BinaryOperator: + var binOp = (IBinaryOperation)condition; + bool testBothArgs = sense; + + switch (binOp.OperatorKind) + { + case BinaryOperatorKind.ConditionalOr: + testBothArgs = !testBothArgs; + // Fall through + goto case BinaryOperatorKind.ConditionalAnd; + + case BinaryOperatorKind.ConditionalAnd: + if (testBothArgs) + { + // gotoif(a != sense) fallThrough + // gotoif(b == sense) dest + // fallThrough: + + BasicBlock fallThrough = null; + + VisitConditionalBranch(binOp.LeftOperand, ref fallThrough, !sense); + VisitConditionalBranch(binOp.RightOperand, ref dest, sense); + + if (fallThrough != null) + { + AppendNewBlock(fallThrough); + } + } + else + { + // gotoif(a == sense) labDest + // gotoif(b == sense) labDest + + VisitConditionalBranch(binOp.LeftOperand, ref dest, sense); + condition = binOp.RightOperand; + goto oneMoreTime; + } + return; + } + + // none of above. + // then it is regular binary expression - Or, And, Xor ... + goto default; + + case OperationKind.UnaryOperator: + var unOp = (IUnaryOperation)condition; + if (unOp.OperatorKind == UnaryOperatorKind.Not && unOp.Operand.Type.SpecialType == SpecialType.System_Boolean) + { + sense = !sense; + condition = unOp.Operand; + goto oneMoreTime; + } + goto default; + + default: + condition = Visit(condition); + dest = dest ?? new BasicBlock(BasicBlockKind.Block); + LinkBlocks(CurrentBasicBlock, (condition, sense, dest)); + _currentBasicBlock = null; + return; + } + } + + private static void LinkBlocks(BasicBlock previous, (IOperation Condition, bool JumpIfTrue, BasicBlock Destination) next) + { + Debug.Assert(previous.Conditional.Condition == null); + next.Destination.AddPredecessor(previous); + previous.Conditional = next; + } + } +} diff --git a/src/Compilers/Core/Portable/Operations/IFlowCaptureOperation.cs b/src/Compilers/Core/Portable/Operations/IFlowCaptureOperation.cs new file mode 100644 index 00000000000..d76a609ac19 --- /dev/null +++ b/src/Compilers/Core/Portable/Operations/IFlowCaptureOperation.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.CodeAnalysis.Operations +{ + /// + /// Represents that an intermediate result is being captured, or the result of capturing is used later on. + /// PROTOTYPE(dataflow): Finalize the design how capturing/referencing intermediate results is represented. + /// + public interface IFlowCaptureOperation : IOperation + { + /// + /// An id used to match references to the same intermediate result. + /// + int Id { get; } + + /// + /// If this is a place where the intermediate result is captured (vs. used), then + /// this property returns true. + /// PROTOTYPE(dataflow): Remove? + /// + bool IsInitialization { get; } + } +} + diff --git a/src/Compilers/Core/Portable/Operations/Operation.cs b/src/Compilers/Core/Portable/Operations/Operation.cs index 41b31d4c668..0eae472db2f 100644 --- a/src/Compilers/Core/Portable/Operations/Operation.cs +++ b/src/Compilers/Core/Portable/Operations/Operation.cs @@ -99,7 +99,8 @@ protected void SetParentOperation(IOperation parent) var result = Interlocked.CompareExchange(ref _parentDoNotAccessDirectly, parent, null); // tree must belong to same semantic model if parent is given - Debug.Assert(parent == null || ((Operation)parent).SemanticModel == SemanticModel); + Debug.Assert(parent == null || ((Operation)parent).SemanticModel == SemanticModel || + ((Operation)parent).SemanticModel == null || SemanticModel == null); // make sure given parent and one we already have is same if we have one already Debug.Assert(result == null || result == parent); diff --git a/src/Compilers/Core/Portable/Operations/OperationCloner.cs b/src/Compilers/Core/Portable/Operations/OperationCloner.cs index 64a9fc464e8..5888990c7d8 100644 --- a/src/Compilers/Core/Portable/Operations/OperationCloner.cs +++ b/src/Compilers/Core/Portable/Operations/OperationCloner.cs @@ -6,7 +6,7 @@ namespace Microsoft.CodeAnalysis.Operations { - internal sealed class OperationCloner : OperationVisitor + internal class OperationCloner : OperationVisitor { private static readonly OperationCloner s_instance = new OperationCloner(); @@ -18,7 +18,7 @@ internal sealed class OperationCloner : OperationVisitor return s_instance.Visit(operation); } - private OperationCloner() + protected OperationCloner() { } @@ -517,5 +517,10 @@ public override IOperation VisitRaiseEvent(IRaiseEventOperation operation, objec { return new RaiseEventStatement(Visit(operation.EventReference), VisitArray(operation.Arguments), ((Operation)operation).SemanticModel, operation.Syntax, operation.Type, operation.ConstantValue, operation.IsImplicit); } + + public override IOperation VisitFlowCapture(IFlowCaptureOperation operation, object argument) + { + return new FlowCapture(operation.Id, operation.Syntax, operation.IsInitialization, operation.Type, operation.ConstantValue); + } } } diff --git a/src/Compilers/Core/Portable/Operations/OperationKind.cs b/src/Compilers/Core/Portable/Operations/OperationKind.cs index 222e8bf0865..ca528100094 100644 --- a/src/Compilers/Core/Portable/Operations/OperationKind.cs +++ b/src/Compilers/Core/Portable/Operations/OperationKind.cs @@ -182,6 +182,8 @@ public enum OperationKind /// Indicates an . DeclarationPattern = 0x56, + FlowCapture = 0x57, + // /// Indicates an . // https://github.com/dotnet/roslyn/issues/21281 //Fixed = , diff --git a/src/Compilers/Core/Portable/Operations/OperationVisitor.cs b/src/Compilers/Core/Portable/Operations/OperationVisitor.cs index 76a5fe9e7f6..3ebd36fe6eb 100644 --- a/src/Compilers/Core/Portable/Operations/OperationVisitor.cs +++ b/src/Compilers/Core/Portable/Operations/OperationVisitor.cs @@ -494,6 +494,11 @@ public virtual void VisitRaiseEvent(IRaiseEventOperation operation) { DefaultVisit(operation); } + + public virtual void VisitFlowCapture(IFlowCaptureOperation operation) + { + DefaultVisit(operation); + } } /// @@ -994,5 +999,10 @@ public virtual TResult VisitRaiseEvent(IRaiseEventOperation operation, TArgument { return DefaultVisit(operation, argument); } + + public virtual TResult VisitFlowCapture(IFlowCaptureOperation operation, TArgument argument) + { + return DefaultVisit(operation, argument); + } } } diff --git a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt index 7efb1a07267..966a38434c4 100644 --- a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt +++ b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt @@ -92,6 +92,7 @@ Microsoft.CodeAnalysis.OperationKind.EventReference = 30 -> Microsoft.CodeAnalys Microsoft.CodeAnalysis.OperationKind.ExpressionStatement = 15 -> Microsoft.CodeAnalysis.OperationKind Microsoft.CodeAnalysis.OperationKind.FieldInitializer = 72 -> Microsoft.CodeAnalysis.OperationKind Microsoft.CodeAnalysis.OperationKind.FieldReference = 26 -> Microsoft.CodeAnalysis.OperationKind +Microsoft.CodeAnalysis.OperationKind.FlowCapture = 87 -> Microsoft.CodeAnalysis.OperationKind Microsoft.CodeAnalysis.OperationKind.Increment = 66 -> Microsoft.CodeAnalysis.OperationKind Microsoft.CodeAnalysis.OperationKind.InstanceReference = 39 -> Microsoft.CodeAnalysis.OperationKind Microsoft.CodeAnalysis.OperationKind.InterpolatedString = 48 -> Microsoft.CodeAnalysis.OperationKind @@ -145,6 +146,17 @@ Microsoft.CodeAnalysis.Operations.ArgumentKind.DefaultValue = 3 -> Microsoft.Cod Microsoft.CodeAnalysis.Operations.ArgumentKind.Explicit = 1 -> Microsoft.CodeAnalysis.Operations.ArgumentKind Microsoft.CodeAnalysis.Operations.ArgumentKind.None = 0 -> Microsoft.CodeAnalysis.Operations.ArgumentKind Microsoft.CodeAnalysis.Operations.ArgumentKind.ParamArray = 2 -> Microsoft.CodeAnalysis.Operations.ArgumentKind +Microsoft.CodeAnalysis.Operations.BasicBlock +Microsoft.CodeAnalysis.Operations.BasicBlock.BasicBlock(Microsoft.CodeAnalysis.Operations.BasicBlockKind kind) -> void +Microsoft.CodeAnalysis.Operations.BasicBlock.Conditional.get -> (Microsoft.CodeAnalysis.IOperation Condition, bool JumpIfTrue, Microsoft.CodeAnalysis.Operations.BasicBlock Destination) +Microsoft.CodeAnalysis.Operations.BasicBlock.Kind.get -> Microsoft.CodeAnalysis.Operations.BasicBlockKind +Microsoft.CodeAnalysis.Operations.BasicBlock.Next.get -> Microsoft.CodeAnalysis.Operations.BasicBlock +Microsoft.CodeAnalysis.Operations.BasicBlock.Predecessors.get -> System.Collections.Immutable.ImmutableHashSet +Microsoft.CodeAnalysis.Operations.BasicBlock.Statements.get -> System.Collections.Immutable.ImmutableArray +Microsoft.CodeAnalysis.Operations.BasicBlockKind +Microsoft.CodeAnalysis.Operations.BasicBlockKind.Block = 2 -> Microsoft.CodeAnalysis.Operations.BasicBlockKind +Microsoft.CodeAnalysis.Operations.BasicBlockKind.Entry = 0 -> Microsoft.CodeAnalysis.Operations.BasicBlockKind +Microsoft.CodeAnalysis.Operations.BasicBlockKind.Exit = 1 -> Microsoft.CodeAnalysis.Operations.BasicBlockKind Microsoft.CodeAnalysis.Operations.BinaryOperatorKind Microsoft.CodeAnalysis.Operations.BinaryOperatorKind.Add = 1 -> Microsoft.CodeAnalysis.Operations.BinaryOperatorKind Microsoft.CodeAnalysis.Operations.BinaryOperatorKind.And = 10 -> Microsoft.CodeAnalysis.Operations.BinaryOperatorKind @@ -307,6 +319,9 @@ Microsoft.CodeAnalysis.Operations.IFieldInitializerOperation.InitializedFields.g Microsoft.CodeAnalysis.Operations.IFieldReferenceOperation Microsoft.CodeAnalysis.Operations.IFieldReferenceOperation.Field.get -> Microsoft.CodeAnalysis.IFieldSymbol Microsoft.CodeAnalysis.Operations.IFieldReferenceOperation.IsDeclaration.get -> bool +Microsoft.CodeAnalysis.Operations.IFlowCaptureOperation +Microsoft.CodeAnalysis.Operations.IFlowCaptureOperation.Id.get -> int +Microsoft.CodeAnalysis.Operations.IFlowCaptureOperation.IsInitialization.get -> bool Microsoft.CodeAnalysis.Operations.IForEachLoopOperation Microsoft.CodeAnalysis.Operations.IForEachLoopOperation.Collection.get -> Microsoft.CodeAnalysis.IOperation Microsoft.CodeAnalysis.Operations.IForEachLoopOperation.LoopControlVariable.get -> Microsoft.CodeAnalysis.IOperation @@ -489,6 +504,7 @@ Microsoft.CodeAnalysis.Operations.UnaryOperatorKind.Plus = 3 -> Microsoft.CodeAn Microsoft.CodeAnalysis.Operations.UnaryOperatorKind.True = 5 -> Microsoft.CodeAnalysis.Operations.UnaryOperatorKind Microsoft.CodeAnalysis.RefKind.In = 3 -> Microsoft.CodeAnalysis.RefKind Microsoft.CodeAnalysis.RefKind.RefReadOnly = 3 -> Microsoft.CodeAnalysis.RefKind +Microsoft.CodeAnalysis.SemanticModel.GetControlFlowGraph(Microsoft.CodeAnalysis.Operations.IBlockOperation body) -> System.Collections.Immutable.ImmutableArray Microsoft.CodeAnalysis.SemanticModel.GetOperation(Microsoft.CodeAnalysis.SyntaxNode node, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> Microsoft.CodeAnalysis.IOperation Microsoft.CodeAnalysis.SyntaxList.SyntaxList(System.Collections.Generic.IEnumerable nodes) -> void Microsoft.CodeAnalysis.SyntaxList.SyntaxList(TNode node) -> void @@ -568,6 +584,7 @@ virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitEventReference(M virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitExpressionStatement(Microsoft.CodeAnalysis.Operations.IExpressionStatementOperation operation) -> void virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitFieldInitializer(Microsoft.CodeAnalysis.Operations.IFieldInitializerOperation operation) -> void virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitFieldReference(Microsoft.CodeAnalysis.Operations.IFieldReferenceOperation operation) -> void +virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitFlowCapture(Microsoft.CodeAnalysis.Operations.IFlowCaptureOperation operation) -> void virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitForEachLoop(Microsoft.CodeAnalysis.Operations.IForEachLoopOperation operation) -> void virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitForLoop(Microsoft.CodeAnalysis.Operations.IForLoopOperation operation) -> void virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitForToLoop(Microsoft.CodeAnalysis.Operations.IForToLoopOperation operation) -> void @@ -659,6 +676,7 @@ virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.V virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitExpressionStatement(Microsoft.CodeAnalysis.Operations.IExpressionStatementOperation operation, TArgument argument) -> TResult virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitFieldInitializer(Microsoft.CodeAnalysis.Operations.IFieldInitializerOperation operation, TArgument argument) -> TResult virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitFieldReference(Microsoft.CodeAnalysis.Operations.IFieldReferenceOperation operation, TArgument argument) -> TResult +virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitFlowCapture(Microsoft.CodeAnalysis.Operations.IFlowCaptureOperation operation, TArgument argument) -> TResult virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitForEachLoop(Microsoft.CodeAnalysis.Operations.IForEachLoopOperation operation, TArgument argument) -> TResult virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitForLoop(Microsoft.CodeAnalysis.Operations.IForLoopOperation operation, TArgument argument) -> TResult virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitForToLoop(Microsoft.CodeAnalysis.Operations.IForToLoopOperation operation, TArgument argument) -> TResult diff --git a/src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs b/src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs index b5abf12e674..e8ec60cbeb7 100644 --- a/src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs +++ b/src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs @@ -1125,6 +1125,64 @@ protected static void VerifyOperationTreeForTest(CSharpCompilation additionalOperationTreeVerifier?.Invoke(actualOperation, compilation, syntaxNode); } + protected static void VerifyFlowGraphForTest(CSharpCompilation compilation, string expectedFlowGraph) + where TSyntaxNode : SyntaxNode + { + var tree = compilation.SyntaxTrees[0]; + var model = compilation.GetSemanticModel(tree); + SyntaxNode syntaxNode = GetSyntaxNodeOfTypeForBinding(GetSyntaxNodeList(tree)); + + ImmutableArray graph = model.GetControlFlowGraph((Operations.IBlockOperation)model.GetOperation(syntaxNode)); + var map = new Dictionary(); + + for (int i = 0; i < graph.Length; i++) + { + map.Add(graph[i], i); + } + + var stringBuilder = PooledObjects.PooledStringBuilder.GetInstance(); + + for (int i = 0; i < graph.Length; i++) + { + var block = graph[i]; + stringBuilder.Builder.AppendLine($"Block[{i}] - {block.Kind}"); + + var predecessors = block.Predecessors; + + if (!predecessors.IsEmpty) + { + stringBuilder.Builder.AppendLine($" Predecessors ({predecessors.Count})"); + foreach (int j in predecessors.Select(b => map[b]).OrderBy(ii => ii)) + { + stringBuilder.Builder.AppendLine($" [{j}]"); + } + } + + var statements = block.Statements; + stringBuilder.Builder.AppendLine($" Statements ({statements.Length})"); + foreach (var statement in statements) + { + stringBuilder.Builder.AppendLine(OperationTreeVerifier.GetOperationTree(compilation, statement, initialIndent: 8)); + } + + if (block.Conditional.Condition != null) + { + Assert.True(map.TryGetValue(block.Conditional.Destination, out int index)); + stringBuilder.Builder.AppendLine($" Jump if {block.Conditional.JumpIfTrue} to Block[{index}]"); + stringBuilder.Builder.AppendLine(OperationTreeVerifier.GetOperationTree(compilation, block.Conditional.Condition, initialIndent: 8)); + } + + if (block.Next != null) + { + Assert.True(map.TryGetValue(block.Next, out var index)); + stringBuilder.Builder.AppendLine($" Next Block[{index}]"); + } + } + + var actualFlowGraph = stringBuilder.ToStringAndFree(); + OperationTreeVerifier.Verify(expectedFlowGraph, actualFlowGraph); + } + protected static void VerifyOperationTreeForTest( string testSrc, string expectedOperationTree, @@ -1149,6 +1207,17 @@ protected static void VerifyOperationTreeForTest(CSharpCompilation VerifyOperationTreeForTest(compilation, expectedOperationTree, additionalOperationTreeVerifier); } + protected static void VerifyFlowGraphAndDiagnosticsForTest( + CSharpCompilation compilation, + string expectedFlowGraph, + DiagnosticDescription[] expectedDiagnostics) + where TSyntaxNode : SyntaxNode + { + var actualDiagnostics = compilation.GetDiagnostics().Where(d => d.Severity != DiagnosticSeverity.Hidden); + actualDiagnostics.Verify(expectedDiagnostics); + VerifyFlowGraphForTest(compilation, expectedFlowGraph); + } + private static readonly MetadataReference[] s_defaultOperationReferences = new[] { SystemRef, SystemCoreRef, ValueTupleRef, SystemRuntimeFacadeRef }; private static readonly MetadataReference[] s_latestOperationReferences = new[] { SystemRef, SystemCoreRef, ValueTupleRef, SystemRuntimeFacadeRef, MscorlibRef_v46 }; @@ -1169,6 +1238,22 @@ protected static void VerifyOperationTreeForTest(CSharpCompilation VerifyOperationTreeAndDiagnosticsForTest(compilation, expectedOperationTree, expectedDiagnostics, additionalOperationTreeVerifier); } + protected static void VerifyFlowGraphAndDiagnosticsForTest( + string testSrc, + string expectedFlowGraph, + DiagnosticDescription[] expectedDiagnostics, + CSharpCompilationOptions compilationOptions = null, + CSharpParseOptions parseOptions = null, + MetadataReference[] additionalReferences = null, + bool useLatestFrameworkReferences = false) + where TSyntaxNode : SyntaxNode + { + var defaultRefs = useLatestFrameworkReferences ? s_latestOperationReferences : s_defaultOperationReferences; + var references = additionalReferences == null ? defaultRefs : additionalReferences.Concat(defaultRefs); + var compilation = CreateStandardCompilation(testSrc, references, sourceFileName: "file.cs", options: compilationOptions ?? TestOptions.ReleaseDll, parseOptions: parseOptions); + VerifyFlowGraphAndDiagnosticsForTest(compilation, expectedFlowGraph, expectedDiagnostics); + } + protected static MetadataReference VerifyOperationTreeAndDiagnosticsForTestWithIL(string testSrc, string ilSource, string expectedOperationTree, diff --git a/src/Test/Utilities/Portable/Compilation/OperationTreeVerifier.cs b/src/Test/Utilities/Portable/Compilation/OperationTreeVerifier.cs index e450bde8b06..5fbd9d3732e 100644 --- a/src/Test/Utilities/Portable/Compilation/OperationTreeVerifier.cs +++ b/src/Test/Utilities/Portable/Compilation/OperationTreeVerifier.cs @@ -778,6 +778,17 @@ public override void VisitLocalReference(ILocalReferenceOperation operation) LogCommonPropertiesAndNewLine(operation); } + public override void VisitFlowCapture(IFlowCaptureOperation operation) + { + LogString(nameof(IFlowCaptureOperation)); + LogString($": {operation.Id}"); + if (operation.IsInitialization) + { + LogString($" (IsInitialization: {operation.IsInitialization})"); + } + LogCommonPropertiesAndNewLine(operation); + } + public override void VisitParameterReference(IParameterReferenceOperation operation) { LogString(nameof(IParameterReferenceOperation)); -- GitLab