未验证 提交 964de174 编写于 作者: A Andy Gocke 提交者: GitHub

Support non-monotonic transfer for local functions (#39570)

Local function flow analysis has always been special-cased for definite
assignment, and specifically for definite assignment with monotonic
assignment. This change implements the same non-monotonic tracking that
we use for try-finally for local functions. This is a conservative
analysis, meaning that certain rare cases may not transfer as much
information as possible. It's currently assumed that these cases are
rare enough to not be frustrating. No safety problems should be present
in the conservative analysis.

Fixes #14400
Fixes #14214
上级 5cae62a1
......@@ -56,7 +56,7 @@ internal partial class AbstractFlowPass<TLocalState, TLocalFunctionState>
/// 3. Meet(Top, X) = X
///
/// </summary>
protected abstract void Meet(ref TLocalState self, ref TLocalState other);
protected abstract bool Meet(ref TLocalState self, ref TLocalState other);
internal interface ILocalState
{
......
......@@ -158,7 +158,7 @@ protected void Unsplit()
// For region analysis, we maintain some extra data.
protected RegionPlace regionPlace; // tells whether we are currently analyzing code before, during, or after the region
protected readonly BoundNode firstInRegion, lastInRegion;
private readonly bool _trackRegions;
protected readonly bool TrackingRegions;
/// <summary>
/// A cache of the state at the backward branch point of each loop. This is not needed
......@@ -204,12 +204,10 @@ protected void Unsplit()
this.firstInRegion = firstInRegion;
this.lastInRegion = lastInRegion;
_loopHeadState = new Dictionary<BoundLoopStatement, TLocalState>(ReferenceEqualityComparer.Instance);
_trackRegions = trackRegions;
TrackingRegions = trackRegions;
_nonMonotonicTransfer = nonMonotonicTransferFunction;
}
protected bool TrackingRegions => _trackRegions;
protected abstract string Dump(TLocalState state);
protected string Dump()
......@@ -324,7 +322,7 @@ protected BoundNode VisitAlways(BoundNode node)
// We scan even expressions, because we must process lambdas contained within them.
if (node != null)
{
if (_trackRegions)
if (TrackingRegions)
{
if (node == this.firstInRegion && this.regionPlace == RegionPlace.Before)
{
......@@ -402,7 +400,7 @@ protected virtual ImmutableArray<PendingBranch> Scan(ref bool badRegion)
Visit(methodMainNode);
this.Unsplit();
RestorePending(oldPending);
if (_trackRegions && regionPlace != RegionPlace.After)
if (TrackingRegions && regionPlace != RegionPlace.After)
{
badRegion = true;
}
......@@ -529,7 +527,7 @@ protected void SetUnreachable()
protected void VisitLvalue(BoundExpression node)
{
if (_trackRegions && node == this.firstInRegion && this.regionPlace == RegionPlace.Before)
if (TrackingRegions && node == this.firstInRegion && this.regionPlace == RegionPlace.Before)
{
EnterRegion();
}
......@@ -587,7 +585,7 @@ protected void VisitLvalue(BoundExpression node)
break;
}
if (_trackRegions && node == this.lastInRegion && this.regionPlace == RegionPlace.Inside)
if (TrackingRegions && node == this.lastInRegion && this.regionPlace == RegionPlace.Inside)
{
LeaveRegion();
}
......@@ -1172,18 +1170,23 @@ public override BoundNode VisitCall(BoundCall node)
private void VisitLocalFunctionUse(LocalFunctionSymbol symbol, SyntaxNode syntax, bool isCall)
{
var localFuncState = GetOrCreateLocalFuncUsages(symbol);
VisitLocalFunctionUse(symbol, localFuncState, syntax);
VisitLocalFunctionUse(symbol, localFuncState, syntax, isCall);
localFuncState.Visited = true;
}
protected virtual void VisitLocalFunctionUse(
LocalFunctionSymbol symbol,
TLocalFunctionState localFunctionState,
SyntaxNode syntax,
bool isCall)
{
if (isCall)
{
Meet(ref this.State, ref localFuncState.State);
Join(ref State, ref localFunctionState.StateFromBottom);
Meet(ref State, ref localFunctionState.StateFromTop);
}
localFuncState.Visited = true;
}
protected virtual void VisitLocalFunctionUse(LocalFunctionSymbol symbol, TLocalFunctionState localFunctionState, SyntaxNode syntax)
{ }
private void VisitReceiverBeforeCall(BoundExpression receiverOpt, MethodSymbol method)
{
if (method is null || method.MethodKind != MethodKind.Constructor)
......@@ -1368,7 +1371,7 @@ public override BoundNode VisitDelegateCreationExpression(BoundDelegateCreationE
{
if ((object)node.MethodOpt != null && node.MethodOpt.RequiresInstanceReceiver)
{
if (_trackRegions)
if (TrackingRegions)
{
if (methodGroup == this.firstInRegion && this.regionPlace == RegionPlace.Before)
{
......@@ -1455,7 +1458,7 @@ public override BoundNode VisitConversion(BoundConversion node)
{
BoundExpression receiver = ((BoundMethodGroup)node.Operand).ReceiverOpt;
// A method group's "implicit this" is only used for instance methods.
if (_trackRegions)
if (TrackingRegions)
{
if (node.Operand == this.firstInRegion && this.regionPlace == RegionPlace.Before)
{
......@@ -1576,16 +1579,16 @@ public override BoundNode VisitTryStatement(BoundTryStatement node)
return null;
}
protected Optional<TLocalState> _tryState;
protected Optional<TLocalState> NonMonotonicState;
private void VisitTryBlockWithAnyTransferFunction(BoundStatement tryBlock, BoundTryStatement node, ref TLocalState tryState)
{
if (_nonMonotonicTransfer)
{
Optional<TLocalState> oldTryState = _tryState;
_tryState = ReachableBottomState();
Optional<TLocalState> oldTryState = NonMonotonicState;
NonMonotonicState = ReachableBottomState();
VisitTryBlock(tryBlock, node, ref tryState);
var tempTryStateValue = _tryState.Value;
var tempTryStateValue = NonMonotonicState.Value;
Join(ref tryState, ref tempTryStateValue);
if (oldTryState.HasValue)
{
......@@ -1594,7 +1597,7 @@ private void VisitTryBlockWithAnyTransferFunction(BoundStatement tryBlock, Bound
oldTryState = oldTryStateValue;
}
_tryState = oldTryState;
NonMonotonicState = oldTryState;
}
else
{
......@@ -1611,10 +1614,10 @@ private void VisitCatchBlockWithAnyTransferFunction(BoundCatchBlock catchBlock,
{
if (_nonMonotonicTransfer)
{
Optional<TLocalState> oldTryState = _tryState;
_tryState = ReachableBottomState();
Optional<TLocalState> oldTryState = NonMonotonicState;
NonMonotonicState = ReachableBottomState();
VisitCatchBlock(catchBlock, ref finallyState);
var tempTryStateValue = _tryState.Value;
var tempTryStateValue = NonMonotonicState.Value;
Join(ref finallyState, ref tempTryStateValue);
if (oldTryState.HasValue)
{
......@@ -1623,7 +1626,7 @@ private void VisitCatchBlockWithAnyTransferFunction(BoundCatchBlock catchBlock,
oldTryState = oldTryStateValue;
}
_tryState = oldTryState;
NonMonotonicState = oldTryState;
}
else
{
......@@ -1651,10 +1654,10 @@ private void VisitFinallyBlockWithAnyTransferFunction(BoundStatement finallyBloc
{
if (_nonMonotonicTransfer)
{
Optional<TLocalState> oldTryState = _tryState;
_tryState = ReachableBottomState();
Optional<TLocalState> oldTryState = NonMonotonicState;
NonMonotonicState = ReachableBottomState();
VisitFinallyBlock(finallyBlock, ref stateMovedUp);
var tempTryStateValue = _tryState.Value;
var tempTryStateValue = NonMonotonicState.Value;
Join(ref stateMovedUp, ref tempTryStateValue);
if (oldTryState.HasValue)
{
......@@ -1663,7 +1666,7 @@ private void VisitFinallyBlockWithAnyTransferFunction(BoundStatement finallyBloc
oldTryState = oldTryStateValue;
}
_tryState = oldTryState;
NonMonotonicState = oldTryState;
}
else
{
......
// 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 System.Diagnostics;
using Microsoft.CodeAnalysis.CSharp.Symbols;
#nullable enable
......@@ -11,6 +12,16 @@ internal partial class AbstractFlowPass<TLocalState, TLocalFunctionState>
{
internal abstract class AbstractLocalFunctionState
{
/// <summary>
/// This is the state from the local function which makes the
/// current state less specific. For example, in nullable analysis
/// this would be captured variables that may be nullable after
/// calling the local function. When a local function is called,
/// this state is <see cref="Join(ref TLocalState, ref TLocalState)"/>
/// with the current state.
/// </summary>
public TLocalState StateFromBottom;
/// <summary>
/// This is the part of the local function transfer function which
/// transfers knowledge additively. For example, in definite
......@@ -19,11 +30,12 @@ internal abstract class AbstractLocalFunctionState
/// state is <see cref="Meet(ref TLocalState, ref TLocalState)"/>
/// with the current state.
/// </summary>
public TLocalState State;
public TLocalState StateFromTop;
public AbstractLocalFunctionState(TLocalState unreachableState)
{
State = unreachableState;
StateFromBottom = unreachableState.Clone();
StateFromTop = unreachableState.Clone();
}
public bool Visited = false;
......@@ -60,6 +72,12 @@ protected TLocalFunctionState GetOrCreateLocalFuncUsages(LocalFunctionSymbol loc
var savedState = this.State;
this.State = this.TopState();
Optional<TLocalState> savedNonMonotonicState = NonMonotonicState;
if (_nonMonotonicTransfer)
{
NonMonotonicState = ReachableBottomState();
}
if (!localFunc.WasCompilerGenerated) EnterParameters(localFuncSymbol.Parameters);
// State changes to captured variables are recorded, as calls to local functions
......@@ -124,6 +142,7 @@ protected TLocalFunctionState GetOrCreateLocalFuncUsages(LocalFunctionSymbol loc
}
this.State = savedState;
NonMonotonicState = savedNonMonotonicState;
this.CurrentSymbol = oldSymbol;
return null;
......@@ -134,7 +153,18 @@ protected TLocalFunctionState GetOrCreateLocalFuncUsages(LocalFunctionSymbol loc
TLocalFunctionState currentState,
ref TLocalState stateAtReturn)
{
bool anyChanged = Join(ref currentState.State, ref stateAtReturn);
bool anyChanged = Join(ref currentState.StateFromTop, ref stateAtReturn);
if (NonMonotonicState.HasValue)
{
var value = NonMonotonicState.Value;
// Since only state moving up gets stored in the non-monotonic state,
// Meet with the stateAtReturn, which records all state changes. If
// a state moved up, then down, the final state should be down.
Meet(ref value, ref stateAtReturn);
anyChanged |= Join(ref currentState.StateFromBottom, ref value);
}
// N.B. Do NOT shortcut this operation. LocalFunctionEnd may have important
// side effects to the local function state
anyChanged |= LocalFunctionEnd(savedState, currentState, ref stateAtReturn);
......
......@@ -71,11 +71,13 @@ public LocalFunctionState(LocalState unreachableState)
protected override LocalFunctionState CreateLocalFunctionState() => new LocalFunctionState(UnreachableState());
protected override void Meet(ref LocalState self, ref LocalState other)
protected override bool Meet(ref LocalState self, ref LocalState other)
{
var old = self;
self.Alive &= other.Alive;
self.Reported &= other.Reported;
Debug.Assert(!self.Alive || !self.Reported);
return self.Alive != old.Alive;
}
protected override bool Join(ref LocalState self, ref LocalState other)
......
......@@ -10,7 +10,7 @@ internal partial class DefiniteAssignmentPass
internal sealed class LocalFunctionState : AbstractLocalFunctionState
{
public BitVector ReadVars = BitVector.Empty;
public ref LocalState WrittenVars => ref State;
public ref LocalState WrittenVars => ref StateFromTop;
public LocalFunctionState(LocalState unreachableState)
: base(unreachableState)
......@@ -19,13 +19,15 @@ public LocalFunctionState(LocalState unreachableState)
protected override LocalFunctionState CreateLocalFunctionState() => new LocalFunctionState(UnreachableState());
/// <summary>
/// At the local function's use site, checks that all variables read are assigned.
/// </summary>
protected override void VisitLocalFunctionUse(LocalFunctionSymbol localFunc, LocalFunctionState localFunctionState, SyntaxNode syntax)
protected override void VisitLocalFunctionUse(
LocalFunctionSymbol localFunc,
LocalFunctionState localFunctionState,
SyntaxNode syntax,
bool isCall)
{
_usedLocalFunctions.Add(localFunc);
// Check variables that were read before being definitely assigned.
var reads = localFunctionState.ReadVars;
// Start at slot 1 (slot 0 just indicates reachability)
......@@ -37,6 +39,8 @@ protected override void VisitLocalFunctionUse(LocalFunctionSymbol localFunc, Loc
CheckIfAssignedDuringLocalFunctionReplay(symbol, syntax, slot);
}
}
base.VisitLocalFunctionUse(localFunc, localFunctionState, syntax, isCall);
}
/// <summary>
......
......@@ -1320,11 +1320,11 @@ private void SetSlotUnassigned(int slot, ref LocalState state)
private void SetSlotUnassigned(int slot)
{
if (_tryState.HasValue)
if (NonMonotonicState.HasValue)
{
var state = _tryState.Value;
var state = NonMonotonicState.Value;
SetSlotUnassigned(slot, ref state);
_tryState = state;
NonMonotonicState = state;
}
SetSlotUnassigned(slot, ref this.State);
......@@ -2104,7 +2104,7 @@ protected void AppendBitName(int bit, StringBuilder builder)
id.Symbol.Name);
}
protected override void Meet(ref LocalState self, ref LocalState other)
protected override bool Meet(ref LocalState self, ref LocalState other)
{
if (self.Assigned.Capacity != other.Assigned.Capacity)
{
......@@ -2112,15 +2112,22 @@ protected override void Meet(ref LocalState self, ref LocalState other)
Normalize(ref other);
}
if (!other.Reachable) self.Assigned[0] = true;
if (!other.Reachable)
{
self.Assigned[0] = true;
return true;
}
bool changed = false;
for (int slot = 1; slot < self.Assigned.Capacity; slot++)
{
if (other.Assigned[slot] && !self.Assigned[slot])
{
SetSlotAssigned(slot, ref self);
changed = true;
}
}
return changed;
}
protected override bool Join(ref LocalState self, ref LocalState other)
......
......@@ -1310,11 +1310,11 @@ private void InheritNullableStateOfMember(int targetContainerSlot, int valueCont
private void SetStateAndTrackForFinally(ref LocalState state, int slot, NullableFlowState newState)
{
state[slot] = newState;
if (newState == NullableFlowState.MaybeNull && _tryState.HasValue)
if (newState == NullableFlowState.MaybeNull && NonMonotonicState.HasValue)
{
var tryState = _tryState.Value;
var tryState = NonMonotonicState.Value;
tryState[slot] = NullableFlowState.MaybeNull;
_tryState = tryState;
NonMonotonicState = tryState;
}
}
......@@ -7872,15 +7872,15 @@ string nameForSlot(int slot)
}
}
protected override void Meet(ref LocalState self, ref LocalState other)
protected override bool Meet(ref LocalState self, ref LocalState other)
{
if (!self.Reachable)
return;
return false;
if (!other.Reachable)
{
self = other.Clone();
return;
return true;
}
if (self.Capacity != other.Capacity)
......@@ -7889,7 +7889,7 @@ protected override void Meet(ref LocalState self, ref LocalState other)
Normalize(ref other);
}
self.Meet(in other);
return self.Meet(in other);
}
protected override bool Join(ref LocalState self, ref LocalState other)
......
......@@ -6156,6 +6156,321 @@ void M(int x)
Assert.Equal("this, x, a, y", GetSymbolNamesJoined(analysis.WrittenOutside));
}
[Fact]
public void AssignmentInsideLocal01()
{
var dataFlowAnalysisResults = CompileAndAnalyzeDataFlowStatements(@"
class Program
{
static void Main()
{
int x = 3, y = 4;
void Local()
{
/*<bind>*/
x = 1;
/*</bind>*/
}
Local();
System.Console.WriteLine(x);
}
}
");
Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.VariablesDeclared));
Assert.Equal("x", GetSymbolNamesJoined(dataFlowAnalysisResults.AlwaysAssigned));
Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.DataFlowsIn));
Assert.Equal("x", GetSymbolNamesJoined(dataFlowAnalysisResults.DataFlowsOut));
Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.ReadInside));
Assert.Equal("x", GetSymbolNamesJoined(dataFlowAnalysisResults.ReadOutside));
Assert.Equal("x", GetSymbolNamesJoined(dataFlowAnalysisResults.WrittenInside));
Assert.Equal("x, y", GetSymbolNamesJoined(dataFlowAnalysisResults.WrittenOutside));
Assert.Equal("x", GetSymbolNamesJoined(dataFlowAnalysisResults.Captured));
}
[Fact]
public void AssignmentInsideLocal02()
{
var dataFlowAnalysisResults = CompileAndAnalyzeDataFlowStatements(@"
class Program
{
static void Main()
{
int x = 3, y = 4;
void Local()
{
/*<bind>*/
if ("""".Length == 1)
{
x = 1;
}
/*</bind>*/
}
Local();
System.Console.WriteLine(x);
}
}
");
Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.VariablesDeclared));
Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.AlwaysAssigned));
Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.DataFlowsIn));
Assert.Equal("x", GetSymbolNamesJoined(dataFlowAnalysisResults.DataFlowsOut));
Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.ReadInside));
Assert.Equal("x", GetSymbolNamesJoined(dataFlowAnalysisResults.ReadOutside));
Assert.Equal("x", GetSymbolNamesJoined(dataFlowAnalysisResults.WrittenInside));
Assert.Equal("x, y", GetSymbolNamesJoined(dataFlowAnalysisResults.WrittenOutside));
Assert.Equal("x", GetSymbolNamesJoined(dataFlowAnalysisResults.Captured));
}
[Fact]
public void AssignmentInsideLocal03()
{
var dataFlowAnalysisResults = CompileAndAnalyzeDataFlowStatements(@"
class Program
{
static void Main()
{
int x = 3, y = 4;
void Local()
{
/*<bind>*/
if (false)
{
x = 1;
}
/*</bind>*/
}
Local();
System.Console.WriteLine(x);
}
}
");
Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.VariablesDeclared));
Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.AlwaysAssigned));
Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.DataFlowsIn));
// This is a conservative approximation, ignoring whether the branch containing
// the assignment is reachable
Assert.Equal("x", GetSymbolNamesJoined(dataFlowAnalysisResults.DataFlowsOut));
Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.ReadInside));
Assert.Equal("x", GetSymbolNamesJoined(dataFlowAnalysisResults.ReadOutside));
Assert.Equal("x", GetSymbolNamesJoined(dataFlowAnalysisResults.WrittenInside));
Assert.Equal("x, y", GetSymbolNamesJoined(dataFlowAnalysisResults.WrittenOutside));
Assert.Equal("x", GetSymbolNamesJoined(dataFlowAnalysisResults.Captured));
}
[Fact]
public void AssignmentInsideLocal04()
{
var dataFlowAnalysisResults = CompileAndAnalyzeDataFlowStatements(@"
class Program
{
static void Main()
{
int x = 3, y = 4;
void Local()
{
/*<bind>*/
x = 1;
/*</bind>*/
x = 1;
}
Local();
System.Console.WriteLine(x);
}
}
");
Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.VariablesDeclared));
Assert.Equal("x", GetSymbolNamesJoined(dataFlowAnalysisResults.AlwaysAssigned));
Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.DataFlowsIn));
Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.DataFlowsOut));
Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.ReadInside));
Assert.Equal("x", GetSymbolNamesJoined(dataFlowAnalysisResults.ReadOutside));
Assert.Equal("x", GetSymbolNamesJoined(dataFlowAnalysisResults.WrittenInside));
Assert.Equal("x, y", GetSymbolNamesJoined(dataFlowAnalysisResults.WrittenOutside));
Assert.Equal("x", GetSymbolNamesJoined(dataFlowAnalysisResults.Captured));
}
[Fact]
[WorkItem(39569, "https://github.com/dotnet/roslyn/issues/39569")]
public void AssignmentInsideLocal05()
{
var dataFlowAnalysisResults = CompileAndAnalyzeDataFlowStatements(@"
class Program
{
static void Main()
{
int x = 3, y = 4;
void Local()
{
x = 1;
}
/*<bind>*/
Local();
/*</bind>*/
System.Console.WriteLine(x);
}
}
");
// Right now region analysis requires bound nodes for each variable and value being
// assigned. This doesn't work with the current local function analysis because we only
// store the slots, not the full boundnode of every assignment (which is impossible
// anyway). This should be:
// Assert.Equal("x", GetSymbolNamesJoined(dataFlowAnalysisResults.DataFlowsOut));
Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.DataFlowsOut));
Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.VariablesDeclared));
Assert.Equal("x", GetSymbolNamesJoined(dataFlowAnalysisResults.AlwaysAssigned));
Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.DataFlowsIn));
Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.ReadInside));
Assert.Equal("x", GetSymbolNamesJoined(dataFlowAnalysisResults.ReadOutside));
Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.WrittenInside));
Assert.Equal("x, y", GetSymbolNamesJoined(dataFlowAnalysisResults.WrittenOutside));
Assert.Equal("x", GetSymbolNamesJoined(dataFlowAnalysisResults.Captured));
}
[Fact]
public void AssignmentInsideLocal06()
{
var dataFlowAnalysisResults = CompileAndAnalyzeDataFlowStatements(@"
class Program
{
static void Main()
{
int x = 3, y = 4;
/*<bind>*/
void Local()
{
x = 1;
}
/*</bind>*/
Local();
System.Console.WriteLine(x);
}
}
");
Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.VariablesDeclared));
Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.AlwaysAssigned));
Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.DataFlowsIn));
Assert.Equal("x", GetSymbolNamesJoined(dataFlowAnalysisResults.DataFlowsOut));
Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.ReadInside));
Assert.Equal("x", GetSymbolNamesJoined(dataFlowAnalysisResults.ReadOutside));
Assert.Equal("x", GetSymbolNamesJoined(dataFlowAnalysisResults.WrittenInside));
Assert.Equal("x, y", GetSymbolNamesJoined(dataFlowAnalysisResults.WrittenOutside));
Assert.Equal("x", GetSymbolNamesJoined(dataFlowAnalysisResults.Captured));
}
[Fact]
public void AssignmentInsideLocal07()
{
var dataFlowAnalysisResults = CompileAndAnalyzeDataFlowStatements(@"
class Program
{
static void Main()
{
int x = 3, y = 4;
/*<bind>*/
void Local()
{
x = 1;
}
Local();
/*</bind>*/
System.Console.WriteLine(x);
}
}
");
Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.VariablesDeclared));
Assert.Equal("x", GetSymbolNamesJoined(dataFlowAnalysisResults.AlwaysAssigned));
Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.DataFlowsIn));
Assert.Equal("x", GetSymbolNamesJoined(dataFlowAnalysisResults.DataFlowsOut));
Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.ReadInside));
Assert.Equal("x", GetSymbolNamesJoined(dataFlowAnalysisResults.ReadOutside));
Assert.Equal("x", GetSymbolNamesJoined(dataFlowAnalysisResults.WrittenInside));
Assert.Equal("x, y", GetSymbolNamesJoined(dataFlowAnalysisResults.WrittenOutside));
Assert.Equal("x", GetSymbolNamesJoined(dataFlowAnalysisResults.Captured));
}
[Fact]
public void AssignmentInsideLocal08()
{
var dataFlowAnalysisResults = CompileAndAnalyzeDataFlowStatements(@"
class Program
{
static void Main()
{
int x = 3, y = 4;
void Local()
{
/*<bind>*/
if ("""".Length == 0)
{
x = 1;
return;
}
else
{
y = 1;
}
/*</bind>*/
}
Local();
System.Console.WriteLine(x);
System.Console.WriteLine(y);
}
}
");
Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.VariablesDeclared));
Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.AlwaysAssigned));
Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.DataFlowsIn));
Assert.Equal("x, y", GetSymbolNamesJoined(dataFlowAnalysisResults.DataFlowsOut));
Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.ReadInside));
Assert.Equal("x, y", GetSymbolNamesJoined(dataFlowAnalysisResults.ReadOutside));
Assert.Equal("x, y", GetSymbolNamesJoined(dataFlowAnalysisResults.WrittenInside));
Assert.Equal("x, y", GetSymbolNamesJoined(dataFlowAnalysisResults.WrittenOutside));
Assert.Equal("x, y", GetSymbolNamesJoined(dataFlowAnalysisResults.Captured));
}
[Fact]
public void AssignmentInsideLocal09()
{
var dataFlowAnalysisResults = CompileAndAnalyzeDataFlowStatements(@"
class Program
{
static void Main()
{
int x = 3, y = 4;
void Local()
{
/*<bind>*/
if ("""".Length == 0)
{
x = 1;
return;
}
else
{
y = 1;
throw new Exception();
}
/*</bind>*/
}
Local();
System.Console.WriteLine(x);
System.Console.WriteLine(y);
}
}
");
Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.VariablesDeclared));
Assert.Equal("x", GetSymbolNamesJoined(dataFlowAnalysisResults.AlwaysAssigned));
Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.DataFlowsIn));
// N.B. This is not as precise as possible. The branch assigning y is unreachable, so
// the result does not technically flow out. This is a conservative approximation.
Assert.Equal("x, y", GetSymbolNamesJoined(dataFlowAnalysisResults.DataFlowsOut));
Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.ReadInside));
Assert.Equal("x, y", GetSymbolNamesJoined(dataFlowAnalysisResults.ReadOutside));
Assert.Equal("x, y", GetSymbolNamesJoined(dataFlowAnalysisResults.WrittenInside));
Assert.Equal("x, y", GetSymbolNamesJoined(dataFlowAnalysisResults.WrittenOutside));
Assert.Equal("x, y", GetSymbolNamesJoined(dataFlowAnalysisResults.Captured));
}
[Fact, WorkItem(25043, "https://github.com/dotnet/roslyn/issues/25043")]
public void FallThroughInSwitch_01()
{
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册