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

Track enclosing symbol in data flow analysis, which might be a field or property. #28252

Merged
merged 2 commits into from
Jul 3, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,9 @@ private int RootSlot(int slot)

public override BoundNode VisitLocalFunctionStatement(BoundLocalFunctionStatement localFunc)
{
var oldMethodOrLambda = this.currentMethodOrLambda;
var oldSymbol = this.currentSymbol;
var localFuncSymbol = localFunc.Symbol;
this.currentMethodOrLambda = localFuncSymbol;
this.currentSymbol = localFuncSymbol;

var oldPending = SavePending(); // we do not support branches into a lambda

Expand Down Expand Up @@ -205,14 +205,14 @@ public override BoundNode VisitLocalFunctionStatement(BoundLocalFunctionStatemen
}

this.State = savedState;
this.currentMethodOrLambda = oldMethodOrLambda;
this.currentSymbol = oldSymbol;

return null;
}

private void RecordReadInLocalFunction(int slot)
{
var localFunc = GetNearestLocalFunctionOpt(currentMethodOrLambda);
var localFunc = GetNearestLocalFunctionOpt(currentSymbol);

Debug.Assert(localFunc != null);

Expand Down Expand Up @@ -294,7 +294,7 @@ private bool IsCapturedInLocalFunction(int slot)
// A variable is captured in a local function iff its
// container is higher in the tree than the nearest
// local function
var nearestLocalFunc = GetNearestLocalFunctionOpt(currentMethodOrLambda);
var nearestLocalFunc = GetNearestLocalFunctionOpt(currentSymbol);

return !(nearestLocalFunc is null) && IsCaptured(rootSymbol, nearestLocalFunc);
}
Expand Down
47 changes: 23 additions & 24 deletions src/Compilers/CSharp/Portable/FlowAnalysis/DataFlowPass.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,9 @@ internal partial class DataFlowPass : AbstractFlowPass<DataFlowPass.LocalState>
private BitVector _alreadyReported;

/// <summary>
/// Reflects the enclosing method or lambda at the current location (in the bound tree).
/// Reflects the enclosing member or lambda at the current location (in the bound tree).
/// </summary>
protected MethodSymbol currentMethodOrLambda { get; private set; }
protected Symbol currentSymbol { get; private set; }

/// <summary>
/// A cache for remember which structs are empty.
Expand Down Expand Up @@ -160,7 +160,7 @@ internal DataFlowPass(
{
this.initiallyAssignedVariables = null;
_sourceAssembly = ((object)member == null) ? null : (SourceAssemblySymbol)member.ContainingAssembly;
this.currentMethodOrLambda = member as MethodSymbol;
this.currentSymbol = member;
_unassignedVariableAddressOfSyntaxes = unassignedVariableAddressOfSyntaxes;
bool strict = compilation.FeatureStrictEnabled; // Compiler flag /features:strict removes the relaxed DA checking we have for backward compatibility
_emptyStructTypeCache = new EmptyStructTypeCache(compilation, !strict);
Expand All @@ -179,7 +179,7 @@ internal DataFlowPass(
{
this.initiallyAssignedVariables = initiallyAssignedVariables;
_sourceAssembly = ((object)member == null) ? null : (SourceAssemblySymbol)member.ContainingAssembly;
this.currentMethodOrLambda = member as MethodSymbol;
this.currentSymbol = member;
_unassignedVariableAddressOfSyntaxes = null;
bool strict = compilation.FeatureStrictEnabled; // Compiler flag /features:strict removes the relaxed DA checking we have for backward compatibility
_emptyStructTypeCache = emptyStructs ?? new EmptyStructTypeCache(compilation, !strict);
Expand All @@ -203,7 +203,7 @@ internal DataFlowPass(
{
this.initiallyAssignedVariables = initiallyAssignedVariables;
_sourceAssembly = null;
this.currentMethodOrLambda = member as MethodSymbol;
this.currentSymbol = member;
_unassignedVariableAddressOfSyntaxes = unassignedVariableAddressOfSyntaxes;
_emptyStructTypeCache = new NeverEmptyStructTypeCache();
}
Expand Down Expand Up @@ -262,17 +262,16 @@ protected override ImmutableArray<PendingBranch> RemoveReturns()
{
var result = base.RemoveReturns();

if (currentMethodOrLambda?.IsAsync == true &&
!currentMethodOrLambda.IsImplicitlyDeclared)
if (currentSymbol is MethodSymbol currentMethod && currentMethod.IsAsync && !currentMethod.IsImplicitlyDeclared)
{
var foundAwait = result.Any(pending => pending.Branch?.Kind == BoundKind.AwaitExpression);
if (!foundAwait)
{
// If we're on a LambdaSymbol, then use its 'DiagnosticLocation'. That will be
// much better than using its 'Location' (which is the entire span of the lambda).
var diagnosticLocation = currentMethodOrLambda is LambdaSymbol lambda
var diagnosticLocation = currentSymbol is LambdaSymbol lambda
? lambda.DiagnosticLocation
: currentMethodOrLambda.Locations[0];
: currentSymbol.Locations[0];

Diagnostics.Add(ErrorCode.WRN_AsyncLacksAwaits, diagnosticLocation);
}
Expand All @@ -283,7 +282,7 @@ protected override ImmutableArray<PendingBranch> RemoveReturns()

protected virtual void ReportUnassignedOutParameter(ParameterSymbol parameter, SyntaxNode node, Location location)
{
if (!_requireOutParamsAssigned && topLevelMethod == currentMethodOrLambda)
if (!_requireOutParamsAssigned && ReferenceEquals(topLevelMethod, currentSymbol))
{
return;
}
Expand Down Expand Up @@ -399,13 +398,13 @@ protected void Analyze(ref bool badRegion, DiagnosticBag diagnostics)
/// <param name="rangeVariableUnderlyingParameter">If variable.Kind is RangeVariable, its underlying lambda parameter. Else null.</param>
private void CheckCaptured(Symbol variable, ParameterSymbol rangeVariableUnderlyingParameter = null)
{
if (IsCaptured(rangeVariableUnderlyingParameter ?? variable, currentMethodOrLambda))
if (IsCaptured(rangeVariableUnderlyingParameter ?? variable, currentSymbol))
{
NoteCaptured(variable);
}
}

private static bool IsCaptured(Symbol variable, MethodSymbol containingMethodOrLambda)
private static bool IsCaptured(Symbol variable, Symbol containingSymbol)
{
switch (variable.Kind)
{
Expand Down Expand Up @@ -436,10 +435,10 @@ private static bool IsCaptured(Symbol variable, MethodSymbol containingMethodOrL
// case the variable is not captured by the target function, or null, in which
// case it is.
for (var currentFunction = variable.ContainingSymbol;
currentFunction != null;
(object)currentFunction != null;
currentFunction = currentFunction.ContainingSymbol)
{
if (currentFunction == containingMethodOrLambda)
if (ReferenceEquals(currentFunction, containingSymbol))
{
return false;
}
Expand Down Expand Up @@ -888,7 +887,7 @@ protected int MakeSlot(BoundExpression node)
{
var propAccess = (BoundPropertyAccess)node;

if (Binder.AccessingAutoPropertyFromConstructor(propAccess, this.currentMethodOrLambda))
if (Binder.AccessingAutoPropertyFromConstructor(propAccess, this.currentSymbol))
{
var propSymbol = propAccess.PropertySymbol;
var backingField = (propSymbol as SourcePropertySymbol)?.BackingField;
Expand Down Expand Up @@ -1090,7 +1089,7 @@ private bool IsAssigned(BoundExpression node, out int unassignedSlot)
case BoundKind.PropertyAccess:
{
var propertyAccess = (BoundPropertyAccess)node;
if (Binder.AccessingAutoPropertyFromConstructor(propertyAccess, this.currentMethodOrLambda))
if (Binder.AccessingAutoPropertyFromConstructor(propertyAccess, this.currentSymbol))
{
var property = propertyAccess.PropertySymbol;
var backingField = (property as SourcePropertySymbol)?.BackingField;
Expand Down Expand Up @@ -1443,7 +1442,7 @@ private void EnterParameters(ImmutableArray<ParameterSymbol> parameters)

protected virtual void EnterParameter(ParameterSymbol parameter)
{
if (parameter.RefKind == RefKind.Out && !this.currentMethodOrLambda.IsAsync) // out parameters not allowed in async
if (parameter.RefKind == RefKind.Out && !(this.currentSymbol is MethodSymbol currentMethod && currentMethod.IsAsync)) // out parameters not allowed in async
Copy link
Member

@jcouv jcouv Jul 3, 2018

Choose a reason for hiding this comment

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

is [](start = 73, length = 2)

recursive pattern? ;-) #Resolved

{
int slot = GetOrCreateSlot(parameter);
if (slot > 0) SetSlotState(slot, initiallyAssignedVariables?.Contains(parameter) == true);
Expand Down Expand Up @@ -1750,9 +1749,9 @@ public override BoundNode VisitLocal(BoundLocal node)
LocalSymbol localSymbol = node.LocalSymbol;
CheckAssigned(localSymbol, node.Syntax);

if (localSymbol.IsFixed &&
(this.currentMethodOrLambda.MethodKind == MethodKind.AnonymousFunction ||
this.currentMethodOrLambda.MethodKind == MethodKind.LocalFunction) &&
if (localSymbol.IsFixed && this.currentSymbol is MethodSymbol currentMethod &&
(currentMethod.MethodKind == MethodKind.AnonymousFunction ||
currentMethod.MethodKind == MethodKind.LocalFunction) &&
_capturedVariables.Contains(localSymbol))
{
Diagnostics.Add(ErrorCode.ERR_FixedLocalInLambda, new SourceLocation(node.Syntax), localSymbol);
Expand Down Expand Up @@ -1830,8 +1829,8 @@ public override BoundNode VisitMethodGroup(BoundMethodGroup node)

public override BoundNode VisitLambda(BoundLambda node)
{
var oldMethodOrLambda = this.currentMethodOrLambda;
this.currentMethodOrLambda = node.Symbol;
var oldSymbol = this.currentSymbol;
this.currentSymbol = node.Symbol;

var oldPending = SavePending(); // we do not support branches into a lambda

Expand Down Expand Up @@ -1866,7 +1865,7 @@ public override BoundNode VisitLambda(BoundLambda node)

this.State = stateAfterLambda;

this.currentMethodOrLambda = oldMethodOrLambda;
this.currentSymbol = oldSymbol;
return null;
}

Expand Down Expand Up @@ -2189,7 +2188,7 @@ public override BoundNode VisitFieldAccess(BoundFieldAccess node)
public override BoundNode VisitPropertyAccess(BoundPropertyAccess node)
{
var result = base.VisitPropertyAccess(node);
if (Binder.AccessingAutoPropertyFromConstructor(node, this.currentMethodOrLambda))
if (Binder.AccessingAutoPropertyFromConstructor(node, this.currentSymbol))
{
var property = node.PropertySymbol;
var backingField = (property as SourcePropertySymbol)?.BackingField;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ private ReadWriteWalker(CSharpCompilation compilation, Symbol member, BoundNode

protected override void EnterRegion()
{
for (MethodSymbol m = this.currentMethodOrLambda; (object)m != null; m = m.ContainingSymbol as MethodSymbol)
for (var m = this.currentSymbol as MethodSymbol; (object)m != null; m = m.ContainingSymbol as MethodSymbol)
{
foreach (var p in m.Parameters)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6152,6 +6152,83 @@ event EventHandler MyEvent
Assert.Equal("this, value", GetSymbolNamesJoined(dataFlowAnalysisResults.WrittenOutside));
}

[Fact]
[WorkItem(27969, "https://github.com/dotnet/roslyn/issues/27969")]
public void CodeInInitializer01()
{
var analysisResults = CompileAndAnalyzeDataFlowExpression(@"
using System;
class C
{
object P { get; } = Create(nameof(P), /*<bind>*/x => true/*</bind>*/);

static object Create(string name, Func<string, bool> f) => throw null;
}
");
var dataFlowAnalysisResults = analysisResults;
Assert.Equal("x", GetSymbolNamesJoined(dataFlowAnalysisResults.VariablesDeclared));
Assert.Equal(null, GetSymbolNamesJoined(dataFlowAnalysisResults.AlwaysAssigned));
Assert.Equal(null, GetSymbolNamesJoined(dataFlowAnalysisResults.Captured));
Assert.Equal(null, GetSymbolNamesJoined(dataFlowAnalysisResults.CapturedInside));
Assert.Equal(null, GetSymbolNamesJoined(dataFlowAnalysisResults.CapturedOutside));
Assert.Equal(null, GetSymbolNamesJoined(dataFlowAnalysisResults.DataFlowsIn));
Assert.Equal(null, GetSymbolNamesJoined(dataFlowAnalysisResults.DataFlowsOut));
Assert.Equal(null, GetSymbolNamesJoined(dataFlowAnalysisResults.ReadInside));
Assert.Equal(null, GetSymbolNamesJoined(dataFlowAnalysisResults.ReadOutside));
Assert.Equal("x", GetSymbolNamesJoined(dataFlowAnalysisResults.WrittenInside));
Assert.Equal(null, GetSymbolNamesJoined(dataFlowAnalysisResults.WrittenOutside));
}

[Fact]
[WorkItem(27969, "https://github.com/dotnet/roslyn/issues/27969")]
public void CodeInInitializer02()
{
var analysisResults = CompileAndAnalyzeDataFlowExpression(@"
using System;
class C
{
object P { get; } = Create(P, /*<bind>*/x => true/*</bind>*/);

static object Create(object name, Func<string, bool> f) => throw null;
}
");
Copy link
Member

@cston cston Jul 3, 2018

Choose a reason for hiding this comment

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

Is this test distinct from 01? #Resolved

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, it uses P instead of nameof(P).


In reply to: 199827816 [](ancestors = 199827816)

var dataFlowAnalysisResults = analysisResults;
Assert.Equal("x", GetSymbolNamesJoined(dataFlowAnalysisResults.VariablesDeclared));
Copy link
Member

Choose a reason for hiding this comment

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

Assert.Equal [](start = 12, length = 12)

Not related to this PR: consider adding a helper method in this test file to do the assert plus the GetSymbolNamesJoined, so that GetSymbolNamesJoined doesn't have to be repeated.

It could look like: ValidateSymbolNames("x", dataFlowAnalysisResults.VariablesDeclared)

Copy link
Member Author

Choose a reason for hiding this comment

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

Filed #28273


In reply to: 199961497 [](ancestors = 199961497)

Assert.Equal(null, GetSymbolNamesJoined(dataFlowAnalysisResults.AlwaysAssigned));
Assert.Equal(null, GetSymbolNamesJoined(dataFlowAnalysisResults.Captured));
Assert.Equal(null, GetSymbolNamesJoined(dataFlowAnalysisResults.CapturedInside));
Assert.Equal(null, GetSymbolNamesJoined(dataFlowAnalysisResults.CapturedOutside));
Assert.Equal(null, GetSymbolNamesJoined(dataFlowAnalysisResults.DataFlowsIn));
Assert.Equal(null, GetSymbolNamesJoined(dataFlowAnalysisResults.DataFlowsOut));
Assert.Equal(null, GetSymbolNamesJoined(dataFlowAnalysisResults.ReadInside));
Assert.Equal(null, GetSymbolNamesJoined(dataFlowAnalysisResults.ReadOutside));
Assert.Equal("x", GetSymbolNamesJoined(dataFlowAnalysisResults.WrittenInside));
Assert.Equal(null, GetSymbolNamesJoined(dataFlowAnalysisResults.WrittenOutside));
}

[Fact]
[WorkItem(19845, "https://github.com/dotnet/roslyn/issues/19845")]
public void CodeInInitializer03()
{
var analysisResults = CompileAndAnalyzeDataFlowExpression(@"
class C {
static int X { get; set; }
int Y = /*<bind>*/X/*</bind>*/;
}");
var dataFlowAnalysisResults = analysisResults;
Assert.Equal(null, GetSymbolNamesJoined(dataFlowAnalysisResults.VariablesDeclared));
Assert.Equal(null, GetSymbolNamesJoined(dataFlowAnalysisResults.AlwaysAssigned));
Assert.Equal(null, GetSymbolNamesJoined(dataFlowAnalysisResults.Captured));
Assert.Equal(null, GetSymbolNamesJoined(dataFlowAnalysisResults.CapturedInside));
Assert.Equal(null, GetSymbolNamesJoined(dataFlowAnalysisResults.CapturedOutside));
Assert.Equal(null, GetSymbolNamesJoined(dataFlowAnalysisResults.DataFlowsIn));
Assert.Equal(null, GetSymbolNamesJoined(dataFlowAnalysisResults.DataFlowsOut));
Assert.Equal(null, GetSymbolNamesJoined(dataFlowAnalysisResults.ReadInside));
Assert.Equal(null, GetSymbolNamesJoined(dataFlowAnalysisResults.ReadOutside));
Assert.Equal(null, GetSymbolNamesJoined(dataFlowAnalysisResults.WrittenInside));
Assert.Equal(null, GetSymbolNamesJoined(dataFlowAnalysisResults.WrittenOutside));
}

#endregion
}
}