Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

IOperation API extensions to expose low-level operations (v2) #21810

Closed
70 changes: 70 additions & 0 deletions src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1753,6 +1753,76 @@ internal PointerTypeSymbol CreatePointerTypeSymbol(TypeSymbol elementType)

#endregion

#region Operations

/// <summary>
/// Gets the low-level operation corresponding to the method's body.
/// </summary>
/// <param name="method">The method symbol.</param>
/// <param name="cancellationToken">An optional cancellation token.</param>
/// <returns>The low-level operation corresponding to the method's body.</returns>
protected override IOperation GetLowLevelOperationCore(IMethodSymbol method, CancellationToken cancellationToken = default)
{
IOperation result = null;
var csmethod = method.EnsureCSharpSymbolOrNull<IMethodSymbol, MethodSymbol>(nameof(method));

if ((object)csmethod != null && csmethod.IsFromCompilation(this))
{
var body = LowerMethodBody(csmethod);

if (body != null)
{
var operationFactory = new Semantics.CSharpOperationFactory(null);
result = operationFactory.Create(body);
}
}

return result;
}

private BoundStatement LowerMethodBody(MethodSymbol method)
{
BoundStatement result = null;

// We don't want to support synthesized bodies
// (like auto-property accessors, etc.)
if (method is SourceMemberMethodSymbol sourceMethod && sourceMethod.BodySyntax != null)
{
var compilationState = new TypeCompilationState(method.ContainingType, this, null);
var diagnostics = DiagnosticBag.GetInstance();
var body = MethodCompiler.BindMethodBody(method, compilationState, diagnostics);

if (body != null && !body.HasErrors && !diagnostics.HasAnyErrors())
{
const int methodOrdinal = -1;
var dynamicAnalysisSpans = ImmutableArray<SourceSpan>.Empty;

result = LocalRewriter.Rewrite(
this,
method,
methodOrdinal,
method.ContainingType,
body,
compilationState,
previousSubmissionFields: null,
allowOmissionOfConditionalCalls: true,
instrumentForDynamicAnalysis: false,
debugDocumentProvider: null,
dynamicAnalysisSpans: ref dynamicAnalysisSpans,
diagnostics: diagnostics,
sawLambdas: out bool sawLambdas,
sawLocalFunctions: out bool sawLocalFunctions,
sawAwaitInExceptionHandler: out bool sawAwaitInExceptionHandler);
}

diagnostics.Free();
}

return result;
}

#endregion

#region Binding

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,21 @@ private IOperation CreateInternal(BoundNode boundNode)
return CreateBoundPatternSwitchLabelOperation((BoundPatternSwitchLabel)boundNode);
case BoundKind.IsPatternExpression:
return CreateBoundIsPatternExpressionOperation((BoundIsPatternExpression)boundNode);

// To support BoundNodes after lowering phase.
case BoundKind.SequencePoint:
return CreateBoundSequencePointOperation((BoundSequencePoint)boundNode);
case BoundKind.SequencePointWithSpan:
return CreateBoundSequencePointWithSpanOperation((BoundSequencePointWithSpan)boundNode);
case BoundKind.SequencePointExpression:
return CreateBoundSequencePointExpressionOperation((BoundSequencePointExpression)boundNode);
case BoundKind.StatementList:
return CreateBoundStatementListOperation((BoundStatementList)boundNode);
case BoundKind.ConditionalGoto:
return CreateBoundConditionalGotoOperation((BoundConditionalGoto)boundNode);
case BoundKind.Sequence:
return CreateBoundSequenceOperation((BoundSequence)boundNode);

default:
var constantValue = ConvertToOptional((boundNode as BoundExpression)?.ConstantValue);
bool isImplicit = boundNode.WasCompilerGenerated;
Expand Down Expand Up @@ -426,7 +441,7 @@ private IEventAssignmentExpression CreateBoundEventAssignmentOperatorOperation(B
return new LazyEventAssignmentExpression(eventReference, handlerValue, adds, _semanticModel, syntax, type, constantValue, isImplicit);
}

private IParameterReferenceExpression CreateBoundParameterOperation(BoundParameter boundParameter)
private IParameterReferenceExpression CreateBoundParameterOperation(BoundParameter boundParameter)
{
IParameterSymbol parameter = boundParameter.ParameterSymbol;
SyntaxNode syntax = boundParameter.Syntax;
Expand Down Expand Up @@ -1064,7 +1079,7 @@ private IParameterInitializer CreateBoundParameterEqualsValueOperation(BoundPara
private IBlockStatement CreateBoundBlockOperation(BoundBlock boundBlock)
{
Lazy<ImmutableArray<IOperation>> statements =
new Lazy<ImmutableArray<IOperation>>(() => boundBlock.Statements.Select(s => Create(s)).Where(s => s.Kind != OperationKind.None).ToImmutableArray());
new Lazy<ImmutableArray<IOperation>>(() => boundBlock.Statements.Select(s => Create(s)).Where(s => s != null && s.Kind != OperationKind.None).ToImmutableArray());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where are we returning null for a BoundNode? This seems like a bug.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CSharpOperationFactory.Create(BoundNode boundNode) method returns null if the boundNode parameter is null. This could happen when there is an optional operation child, like Instance property of IMemberReferenceExpression.


ImmutableArray<ILocalSymbol> locals = boundBlock.Locals.As<ILocalSymbol>();
SyntaxNode syntax = boundBlock.Syntax;
Expand Down Expand Up @@ -1506,5 +1521,59 @@ private IIsPatternExpression CreateBoundIsPatternExpressionOperation(BoundIsPatt
bool isImplicit = boundIsPatternExpression.WasCompilerGenerated;
return new LazyIsPatternExpression(expression, pattern, _semanticModel, syntax, type, constantValue, isImplicit);
}

private IOperation CreateBoundSequencePointOperation(BoundSequencePoint boundSequencePoint)
{
return Create(boundSequencePoint.StatementOpt);
}

private IOperation CreateBoundSequencePointWithSpanOperation(BoundSequencePointWithSpan boundSequencePointWithSpan)
{
return Create(boundSequencePointWithSpan.StatementOpt);
}

private IOperation CreateBoundSequencePointExpressionOperation(BoundSequencePointExpression boundSequencePointExpression)
{
return Create(boundSequencePointExpression.Expression);
}

private IBlockStatement CreateBoundStatementListOperation(BoundStatementList boundStatementList)
{
Lazy<ImmutableArray<IOperation>> statements =
new Lazy<ImmutableArray<IOperation>>(() => boundStatementList.Statements.Select(s => Create(s)).Where(s => s != null).ToImmutableArray());

ImmutableArray<ILocalSymbol> locals = ImmutableArray<ILocalSymbol>.Empty;
SyntaxNode syntax = boundStatementList.Syntax;
ITypeSymbol type = null;
Optional<object> constantValue = default(Optional<object>);
bool isImplicit = boundStatementList.WasCompilerGenerated;
return new LazyBlockStatement(statements, locals, _semanticModel, syntax, type, constantValue, isImplicit);
}

private IConditionalGotoStatement CreateBoundConditionalGotoOperation(BoundConditionalGoto boundConditionalGoto)
{
Lazy<IOperation> condition = new Lazy<IOperation>(() => Create(boundConditionalGoto.Condition));
ILabelSymbol target = boundConditionalGoto.Label;
bool jumpIfTrue = boundConditionalGoto.JumpIfTrue;
SyntaxNode syntax = boundConditionalGoto.Syntax;
ITypeSymbol type = null;
Optional<object> constantValue = default(Optional<object>);
bool isImplicit = boundConditionalGoto.WasCompilerGenerated;
return new LazyConditionalGotoStatement(condition, target, jumpIfTrue, _semanticModel, syntax, type, constantValue, isImplicit);
}

private ISequenceExpression CreateBoundSequenceOperation(BoundSequence boundSequence)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CreateBoundSequenceOperation [](start = 36, length = 28)

It looks like there are no tests for this node.

{
Lazy<ImmutableArray<IOperation>> expressions =
new Lazy<ImmutableArray<IOperation>>(() => boundSequence.SideEffects.Select(s => Create(s)).Where(s => s != null).ToImmutableArray());

Lazy<IOperation> value = new Lazy<IOperation>(() => Create(boundSequence.Value));
ImmutableArray<ILocalSymbol> locals = boundSequence.Locals.As<ILocalSymbol>();
SyntaxNode syntax = boundSequence.Syntax;
ITypeSymbol type = null;
Optional<object> constantValue = default(Optional<object>);
bool isImplicit = boundSequence.WasCompilerGenerated;
return new LazySequenceExpression(expressions, value, locals, _semanticModel, syntax, type, constantValue, isImplicit);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
// 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.Diagnostics;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Semantics;
using Microsoft.CodeAnalysis.Test.Utilities;
using Roslyn.Test.Utilities;
using Xunit;

namespace Microsoft.CodeAnalysis.CSharp.UnitTests
{
public partial class IOperationTests : SemanticModelTestBase
{
[CompilerTrait(CompilerFeature.IOperation)]
[Fact]
public void GetLowLevelOperation_FromMethod()
{
string source = @"
class C
{
/*<bind>*/
static int Method(int p)
{
return p;
}
/*</bind>*/
}
";
string expectedOperationTree = @"
IBlockStatement (1 statements) (OperationKind.BlockStatement) (Syntax: '{ ... }')
IReturnStatement (OperationKind.ReturnStatement) (Syntax: 'return p;')
ReturnedValue: IParameterReferenceExpression: p (OperationKind.ParameterReferenceExpression, Type: System.Int32) (Syntax: 'p')
";
var expectedDiagnostics = DiagnosticDescription.None;
VerifyOperationTreeAndDiagnosticsForTest<BaseMethodDeclarationSyntax>(source, expectedOperationTree, expectedDiagnostics, useLoweredTree: true);
}

[CompilerTrait(CompilerFeature.IOperation)]
[Fact]
public void GetLowLevelOperation_FromConstructor()
{
string source = @"
class C
{
private int _field = 0;

/*<bind>*/
public C(int p)
{
_field = p;
}
/*</bind>*/
}
";
string expectedOperationTree = @"
IBlockStatement (2 statements) (OperationKind.BlockStatement) (Syntax: 'public C(in ... }')
IExpressionStatement (OperationKind.ExpressionStatement) (Syntax: 'public C(in ... }')
Expression: IInvocationExpression ( System.Object..ctor()) (OperationKind.InvocationExpression, Type: System.Object) (Syntax: 'public C(in ... }')
Instance Receiver: IInstanceReferenceExpression (InstanceReferenceKind.Implicit) (OperationKind.InstanceReferenceExpression, Type: C) (Syntax: 'public C(in ... }')
Arguments(0)
IBlockStatement (1 statements) (OperationKind.BlockStatement) (Syntax: '{ ... }')
IExpressionStatement (OperationKind.ExpressionStatement) (Syntax: '_field = p;')
Expression: ISimpleAssignmentExpression (OperationKind.SimpleAssignmentExpression, Type: System.Int32) (Syntax: '_field = p')
Left: IFieldReferenceExpression: System.Int32 C._field (OperationKind.FieldReferenceExpression, Type: System.Int32) (Syntax: '_field')
Instance Receiver: IInstanceReferenceExpression (InstanceReferenceKind.Implicit) (OperationKind.InstanceReferenceExpression, Type: C) (Syntax: '_field')
Right: IParameterReferenceExpression: p (OperationKind.ParameterReferenceExpression, Type: System.Int32) (Syntax: 'p')
";
var expectedDiagnostics = DiagnosticDescription.None;
VerifyOperationTreeAndDiagnosticsForTest<BaseMethodDeclarationSyntax>(source, expectedOperationTree, expectedDiagnostics, useLoweredTree: true);
}

[CompilerTrait(CompilerFeature.IOperation)]
[Fact]
public void GetLowLevelOperation_FromFinalizer()
{
string source = @"
class C
{
private int _field = 0;

/*<bind>*/
~C()
{
_field += 0;
}
/*</bind>*/
}
";
string expectedOperationTree = @"
IBlockStatement (1 statements) (OperationKind.BlockStatement) (Syntax: '{ ... }')
ITryStatement (OperationKind.TryStatement) (Syntax: '{ ... }')
Body: IBlockStatement (1 statements) (OperationKind.BlockStatement) (Syntax: '{ ... }')
IExpressionStatement (OperationKind.ExpressionStatement) (Syntax: '_field += 0;')
Expression: ISimpleAssignmentExpression (OperationKind.SimpleAssignmentExpression, Type: System.Int32) (Syntax: '_field += 0')
Left: IFieldReferenceExpression: System.Int32 C._field (OperationKind.FieldReferenceExpression, Type: System.Int32) (Syntax: '_field')
Instance Receiver: IInstanceReferenceExpression (InstanceReferenceKind.Implicit) (OperationKind.InstanceReferenceExpression, Type: C) (Syntax: '_field')
Right: IFieldReferenceExpression: System.Int32 C._field (OperationKind.FieldReferenceExpression, Type: System.Int32) (Syntax: '_field')
Instance Receiver: IInstanceReferenceExpression (InstanceReferenceKind.Implicit) (OperationKind.InstanceReferenceExpression, Type: C) (Syntax: '_field')
Catch clauses(0)
Finally: IBlockStatement (1 statements) (OperationKind.BlockStatement) (Syntax: '{ ... }')
IExpressionStatement (OperationKind.ExpressionStatement) (Syntax: '{ ... }')
Expression: IInvocationExpression ( void System.Object.Finalize()) (OperationKind.InvocationExpression, Type: System.Void) (Syntax: '{ ... }')
Instance Receiver: IInstanceReferenceExpression (InstanceReferenceKind.BaseClass) (OperationKind.InstanceReferenceExpression, Type: C) (Syntax: '{ ... }')
Arguments(0)
";
var expectedDiagnostics = DiagnosticDescription.None;
VerifyOperationTreeAndDiagnosticsForTest<BaseMethodDeclarationSyntax>(source, expectedOperationTree, expectedDiagnostics, useLoweredTree: true);
}

[CompilerTrait(CompilerFeature.IOperation)]
[Fact]
public void GetLowLevelOperation_FromPropertyAccessorGet()
{
string source = @"
class C
{
private int _property = 0;

int Property
{
/*<bind>*/
get { return _property; }
/*</bind>*/
}
}
";
string expectedOperationTree = @"
IBlockStatement (1 statements) (OperationKind.BlockStatement) (Syntax: '{ return _property; }')
IReturnStatement (OperationKind.ReturnStatement) (Syntax: 'return _property;')
ReturnedValue: IFieldReferenceExpression: System.Int32 C._property (OperationKind.FieldReferenceExpression, Type: System.Int32) (Syntax: '_property')
Instance Receiver: IInstanceReferenceExpression (InstanceReferenceKind.Implicit) (OperationKind.InstanceReferenceExpression, Type: C) (Syntax: '_property')
";
var expectedDiagnostics = DiagnosticDescription.None;
VerifyOperationTreeAndDiagnosticsForTest<AccessorDeclarationSyntax>(source, expectedOperationTree, expectedDiagnostics, useLoweredTree: true);
}

[CompilerTrait(CompilerFeature.IOperation)]
[Fact]
public void GetLowLevelOperation_FromPropertyAccessorSet()
{
string source = @"
class C
{
private int _property = 0;

int Property
{
get { return _property; }
/*<bind>*/
set { _property = value; }
/*</bind>*/
}
}
";
string expectedOperationTree = @"
IBlockStatement (1 statements) (OperationKind.BlockStatement) (Syntax: '{ _property = value; }')
IExpressionStatement (OperationKind.ExpressionStatement) (Syntax: '_property = value;')
Expression: ISimpleAssignmentExpression (OperationKind.SimpleAssignmentExpression, Type: System.Int32) (Syntax: '_property = value')
Left: IFieldReferenceExpression: System.Int32 C._property (OperationKind.FieldReferenceExpression, Type: System.Int32) (Syntax: '_property')
Instance Receiver: IInstanceReferenceExpression (InstanceReferenceKind.Implicit) (OperationKind.InstanceReferenceExpression, Type: C) (Syntax: '_property')
Right: IParameterReferenceExpression: value (OperationKind.ParameterReferenceExpression, Type: System.Int32) (Syntax: 'value')

";
var expectedDiagnostics = DiagnosticDescription.None;
VerifyOperationTreeAndDiagnosticsForTest<AccessorDeclarationSyntax>(source, expectedOperationTree, expectedDiagnostics, useLoweredTree: true);
}
}
}
Loading