提交 04753508 编写于 作者: A Andy Gocke

Implement definite assignment changes for local functions (#13234)

This PR implements the design changes for flow analysis in local
functions, specifically the decision to allow local functions to be
declared anywhere in the method and only have captured usages considered
at the local function use site, rather than the declaration site.

The key points of the algorithm are as follows:

    1. Visit local functions recursively:
        a. For each new read/write of a captured variable in the local
           function, record it. If the local function has already been used,
           mark the `stateChangedAfterUse` to `true`, indicating that we
           need to visit another pass.
        b. When a local function is used, play back the recorded
           read/writes if the variable is not still captured. Mark the local
           function as used.
    2. Process remaining statements in the parent method, performing
        (1.b) when necessary.
    3. If `stateAfterChanged` has been set to true, visit the entire method
       again (and go to 1).

Fixes #10391.
上级 2b48609b
......@@ -353,6 +353,7 @@
<Compile Include="FlowAnalysis\ControlFlowPass.cs" />
<Compile Include="FlowAnalysis\CSharpDataFlowAnalysis.cs" />
<Compile Include="FlowAnalysis\DataFlowPass.cs" />
<Compile Include="FlowAnalysis\DataFlowPass.LocalFunctions.cs" />
<Compile Include="FlowAnalysis\DataFlowPass.VariableIdentifier.cs" />
<Compile Include="FlowAnalysis\DataFlowsInWalker.cs" />
<Compile Include="FlowAnalysis\DataFlowsOutWalker.cs" />
......
......@@ -218,6 +218,7 @@ protected override void VisitStatement(BoundStatement statement)
case BoundKind.Block:
case BoundKind.ThrowStatement:
case BoundKind.LabeledStatement:
case BoundKind.LocalFunctionStatement:
base.VisitStatement(statement);
break;
case BoundKind.StatementList:
......@@ -232,7 +233,10 @@ protected override void VisitStatement(BoundStatement statement)
private void CheckReachable(BoundStatement statement)
{
if (!this.State.Alive && !this.State.Reported && !statement.WasCompilerGenerated && statement.Syntax.Span.Length != 0)
if (!this.State.Alive &&
!this.State.Reported &&
!statement.WasCompilerGenerated &&
statement.Syntax.Span.Length != 0)
{
var firstToken = statement.Syntax.GetFirstToken();
Diagnostics.Add(ErrorCode.WRN_UnreachableCode, new SourceLocation(firstToken));
......
// 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.Symbols;
using System;
using System.Collections.Immutable;
using System.Diagnostics;
namespace Microsoft.CodeAnalysis.CSharp
{
internal partial class DataFlowPass
{
private readonly SmallDictionary<LocalFunctionSymbol, LocalFuncUsages> _localFuncVarUsages =
new SmallDictionary<LocalFunctionSymbol, LocalFuncUsages>();
private class LocalFuncUsages
{
public BitVector ReadVars = BitVector.Empty;
public BitVector WrittenVars = BitVector.Empty;
public bool LocalFuncVisited { get; set; } = false;
}
/// <summary>
/// At the local function's use site, checks that all variables read
/// are assigned and assigns all variables that are definitely assigned
/// to be definitely assigned.
/// </summary>
private void ReplayReadsAndWrites(LocalFunctionSymbol localFunc,
SyntaxNode syntax,
bool writes)
{
_usedLocalFunctions.Add(localFunc);
// First process the reads
ReplayVarUsage(localFunc,
syntax,
isWrite: false);
// Now the writes
if (writes)
{
ReplayVarUsage(localFunc,
syntax,
isWrite: true);
}
}
private void ReplayVarUsage(LocalFunctionSymbol localFunc,
SyntaxNode syntax,
bool isWrite)
{
LocalFuncUsages usages = GetOrCreateLocalFuncUsages(localFunc);
var state = isWrite ? usages.WrittenVars : usages.ReadVars;
// Start at slot 1 (slot 0 just indicates reachability)
for (int slot = 1; slot < state.Capacity; slot++)
{
if (state[slot])
{
if (isWrite)
{
SetSlotAssigned(slot);
}
else
{
var symbol = variableBySlot[slot].Symbol;
CheckAssigned(symbol, syntax, slot);
}
}
}
usages.LocalFuncVisited = true;
}
private int RootSlot(int slot)
{
while (true)
{
var varInfo = variableBySlot[slot];
if (varInfo.ContainingSlot == 0)
{
return slot;
}
else
{
slot = varInfo.ContainingSlot;
}
}
}
public override BoundNode VisitLocalFunctionStatement(BoundLocalFunctionStatement localFunc)
{
var oldMethodOrLambda = this.currentMethodOrLambda;
this.currentMethodOrLambda = localFunc.Symbol;
var oldPending = SavePending(); // we do not support branches into a lambda
// Local functions don't affect outer state and are analyzed
// with everything unassigned and reachable
var savedState = this.State;
this.State = this.ReachableState();
var usages = GetOrCreateLocalFuncUsages(localFunc.Symbol);
var oldReads = usages.ReadVars;
usages.ReadVars = BitVector.Empty;
if (!localFunc.WasCompilerGenerated) EnterParameters(localFunc.Symbol.Parameters);
var oldPending2 = SavePending();
VisitAlways(localFunc.Body);
RestorePending(oldPending2); // process any forward branches within the lambda body
ImmutableArray<PendingBranch> pendingReturns = RemoveReturns();
RestorePending(oldPending);
LeaveParameters(localFunc.Symbol.Parameters, localFunc.Syntax, null);
LocalState stateAtReturn = this.State;
foreach (PendingBranch pending in pendingReturns)
{
this.State = pending.State;
if (pending.Branch.Kind == BoundKind.ReturnStatement)
{
// ensure out parameters are definitely assigned at each return
LeaveParameters(localFunc.Symbol.Parameters, pending.Branch.Syntax, null);
IntersectWith(ref stateAtReturn, ref this.State);
}
else
{
// other ways of branching out of a lambda are errors, previously reported in control-flow analysis
}
}
// Check for changes to the read and write sets
if (RecordChangedVars(ref usages.WrittenVars,
ref stateAtReturn.Assigned,
ref oldReads,
ref usages.ReadVars) &&
usages.LocalFuncVisited)
{
stateChangedAfterUse = true;
usages.LocalFuncVisited = false;
}
this.State = savedState;
this.currentMethodOrLambda = oldMethodOrLambda;
return null;
}
private void RecordReadInLocalFunction(int slot)
{
var localFunc = GetNearestLocalFunctionOpt(currentMethodOrLambda);
Debug.Assert(localFunc != null);
var usages = GetOrCreateLocalFuncUsages(localFunc);
usages.ReadVars[slot] = true;
}
private bool RecordChangedVars(ref BitVector oldWrites,
ref BitVector newWrites,
ref BitVector oldReads,
ref BitVector newReads)
{
bool anyChanged = RecordCapturedChanges(ref oldWrites, ref newWrites);
anyChanged |= RecordCapturedChanges(ref oldReads, ref newReads);
return anyChanged;
}
private bool RecordCapturedChanges(ref BitVector oldState,
ref BitVector newState)
{
// Build a list of variables that are both captured and assigned
var capturedMask = GetCapturedBitmask(ref newState);
var capturedAndSet = newState;
capturedAndSet.IntersectWith(capturedMask);
// Union and check to see if there are any changes
return oldState.UnionWith(capturedAndSet);
}
private BitVector GetCapturedBitmask(ref BitVector state)
{
BitVector mask = BitVector.Empty;
for (int slot = 1; slot < state.Capacity; slot++)
{
if (IsCapturedInLocalFunction(slot))
{
mask[slot] = true;
}
}
return mask;
}
private bool IsCapturedInLocalFunction(int slot,
ParameterSymbol rangeVariableUnderlyingParameter = null)
{
if (slot <= 0) return false;
// Find the root slot, since that would be the only
// slot, if any, that is captured in a local function
var rootVarInfo = variableBySlot[RootSlot(slot)];
var rootSymbol = rootVarInfo.Symbol;
// 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);
return (object)nearestLocalFunc != null &&
IsCaptured(rootSymbol, nearestLocalFunc, rangeVariableUnderlyingParameter);
}
private LocalFuncUsages GetOrCreateLocalFuncUsages(LocalFunctionSymbol localFunc)
{
LocalFuncUsages usages;
if (!_localFuncVarUsages.TryGetValue(localFunc, out usages))
{
usages = new LocalFuncUsages();
_localFuncVarUsages[localFunc] = usages;
}
return usages;
}
private static LocalFunctionSymbol GetNearestLocalFunctionOpt(Symbol symbol)
{
while (symbol != null)
{
if (symbol.Kind == SymbolKind.Method &&
((MethodSymbol)symbol).MethodKind == MethodKind.LocalFunction)
{
return (LocalFunctionSymbol)symbol;
}
symbol = symbol.ContainingSymbol;
}
return null;
}
}
}
......@@ -91,9 +91,7 @@ public override BoundNode VisitRangeVariable(BoundRangeVariable node)
return null;
}
protected override void ReportUnassigned(
Symbol symbol,
SyntaxNode node)
protected override void ReportUnassigned(Symbol symbol, SyntaxNode node)
{
// TODO: how to handle fields of structs?
if (RegionContains(node.Span) && !(symbol is FieldSymbol))
......
......@@ -51,12 +51,12 @@ internal abstract partial class PreciseAbstractFlowPass<LocalState> : BoundTreeV
private readonly PooledDictionary<LabelSymbol, LocalState> _labels;
/// <summary>
/// Set to true after an analysis scan if the analysis was incomplete due to a backward
/// "goto" branch changing some analysis result. In this case the caller scans again (until
/// Set to true after an analysis scan if the analysis was incomplete due to state changing
/// after it was used by another analysis component. In this case the caller scans again (until
/// this is false). Since the analysis proceeds by monotonically changing the state computed
/// at each label, this must terminate.
/// </summary>
internal bool backwardBranchChanged;
protected bool stateChangedAfterUse;
/// <summary>
/// See property PendingBranches
......@@ -376,11 +376,11 @@ protected ImmutableArray<PendingBranch> Analyze(ref bool badRegion)
this.State = ReachableState();
_pendingBranches.Clear();
if (_trackExceptions) _pendingBranches.Add(new PendingBranch(null, ReachableState()));
this.backwardBranchChanged = false;
this.stateChangedAfterUse = false;
this.Diagnostics.Clear();
returns = this.Scan(ref badRegion);
}
while (this.backwardBranchChanged);
while (this.stateChangedAfterUse);
return returns;
}
......@@ -651,7 +651,7 @@ private void LoopTail(BoundLoopStatement node)
if (IntersectWith(ref oldState, ref this.State))
{
_loopHeadState[node] = oldState;
this.backwardBranchChanged = true;
this.stateChangedAfterUse = true;
}
}
......@@ -848,13 +848,13 @@ protected void RestorePending(SavedPending oldPending)
case BoundKind.LabeledStatement:
{
var label = (BoundLabeledStatement)node;
backwardBranchChanged |= ResolveBranches(label.Label, label);
stateChangedAfterUse |= ResolveBranches(label.Label, label);
}
break;
case BoundKind.LabelStatement:
{
var label = (BoundLabelStatement)node;
backwardBranchChanged |= ResolveBranches(label.Label, label);
stateChangedAfterUse |= ResolveBranches(label.Label, label);
}
break;
case BoundKind.SwitchSection:
......@@ -862,7 +862,7 @@ protected void RestorePending(SavedPending oldPending)
var sec = (BoundSwitchSection)node;
foreach (var label in sec.SwitchLabels)
{
backwardBranchChanged |= ResolveBranches(label.Label, sec);
stateChangedAfterUse |= ResolveBranches(label.Label, sec);
}
}
break;
......@@ -871,7 +871,7 @@ protected void RestorePending(SavedPending oldPending)
var sec = (BoundPatternSwitchSection)node;
foreach (var label in sec.SwitchLabels)
{
backwardBranchChanged |= ResolveBranches(label.Label, sec);
stateChangedAfterUse |= ResolveBranches(label.Label, sec);
}
}
break;
......
// 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.CodeGen;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System;
namespace Microsoft.CodeAnalysis.CSharp
{
......@@ -81,7 +78,7 @@ public override BoundNode VisitConversion(BoundConversion conversion)
private void RemapLocalFunction(
CSharpSyntaxNode syntax,
SyntaxNode syntax,
MethodSymbol symbol,
out BoundExpression receiver,
out MethodSymbol method,
......
......@@ -97,6 +97,7 @@
<Compile Include="CodeGen\CodeGenIncrementTests.cs" />
<Compile Include="CodeGen\CodeGenInterfaceImplementation.cs" />
<Compile Include="CodeGen\CodeGenIterators.cs" />
<Compile Include="CodeGen\CodeGenLocalFunctionTests.cs" />
<Compile Include="CodeGen\CodeGenMscorlib.cs" />
<Compile Include="CodeGen\CodeGenOperators.cs" />
<Compile Include="CodeGen\CodeGenOptimizedNullableOperators.cs" />
......
......@@ -67,6 +67,7 @@
<Compile Include="FlowAnalysis\FlowTestBase.cs" />
<Compile Include="FlowAnalysis\FlowTests.cs" />
<Compile Include="FlowAnalysis\IterationJumpYieldStatementTests.cs" />
<Compile Include="FlowAnalysis\LocalFunctions.cs" />
<Compile Include="FlowAnalysis\PatternsVsRegions.cs" />
<Compile Include="FlowAnalysis\RegionAnalysisTests.cs" />
<Compile Include="FlowAnalysis\StructTests.cs" />
......
using Microsoft.CodeAnalysis.Test.Utilities;
using Xunit;
namespace Microsoft.CodeAnalysis.CSharp.UnitTests
{
[CompilerTrait(CompilerFeature.LocalFunctions)]
public class LocalFunctions : FlowTestBase
{
[Fact]
public void SimpleForwardCall()
{
var comp = CreateCompilationWithMscorlib(@"
class C
{
public static void Main()
{
var x = Local();
int Local() => 2;
}
}");
comp.VerifyDiagnostics();
}
[Fact]
public void DefinedWhenCalled()
{
var comp = CreateCompilationWithMscorlib(@"
class C
{
public static void Main()
{
int x;
bool Local() => x == 0;
x = 0;
Local();
}
}");
comp.VerifyDiagnostics();
}
[Fact]
public void NotDefinedWhenCalled()
{
var comp = CreateCompilationWithMscorlib(@"
class C
{
public static void Main()
{
int x;
bool Local() => x == 0;
Local();
}
}");
comp.VerifyDiagnostics(
// (8,9): error CS0165: Use of unassigned local variable 'x'
// Local();
Diagnostic(ErrorCode.ERR_UseDefViolation, "Local()").WithArguments("x").WithLocation(8, 9)
);
}
[Fact]
public void ChainedDef()
{
var comp = CreateCompilationWithMscorlib(@"
class C
{
public static void Main()
{
int x;
bool Local2() => Local1();
bool Local1() => x == 0;
Local2();
}
}");
comp.VerifyDiagnostics(
// (9,9): error CS0165: Use of unassigned local variable 'x'
// Local2();
Diagnostic(ErrorCode.ERR_UseDefViolation, "Local2()").WithArguments("x").WithLocation(9, 9)
);
}
[Fact]
public void SetInLocalFunc()
{
var comp = CreateCompilationWithMscorlib(@"
class C
{
public static void Main()
{
int x;
void L1()
{
x = 0;
}
bool L2() => x == 0;
L1();
L2();
}
}
");
comp.VerifyDiagnostics();
}
[Fact]
public void SetInLocalFuncMutual()
{
var comp = CreateCompilationWithMscorlib(@"
class C
{
public static void Main()
{
int x;
bool L1()
{
L2();
return x == 0;
}
void L2()
{
x = 0;
L1();
}
L1();
}
}
");
comp.VerifyDiagnostics();
}
[Fact]
public void LongWriteChain()
{
var comp = CreateCompilationWithMscorlib(@"
class C
{
public void M()
{
int x;
bool L1()
{
L2();
return x == 0;
}
bool L2()
{
L3();
return x == 0;
}
bool L3()
{
L4();
return x == 0;
}
void L4()
{
x = 0;
}
L1();
}
}");
comp.VerifyDiagnostics();
}
[Fact]
public void ConvertBeforeDefined()
{
var comp = CreateCompilationWithMscorlib(@"
class C
{
public static void Main()
{
int x;
bool L1() => x == 0;
System.Func<bool> f = L1;
x = 0;
f();
}
}");
comp.VerifyDiagnostics(
// (8,31): error CS0165: Use of unassigned local variable 'x'
// System.Func<bool> f = L1;
Diagnostic(ErrorCode.ERR_UseDefViolation, "L1").WithArguments("x").WithLocation(8, 31));
}
[Fact]
public void NestedCapture()
{
var comp = CreateCompilationWithMscorlib(@"
class C
{
public static void Main()
{
void Local1()
{
int x;
bool Local2() => x == 0;
Local2();
}
Local1();
}
}");
comp.VerifyDiagnostics(
// (10,13): error CS0165: Use of unassigned local variable 'x'
// Local2();
Diagnostic(ErrorCode.ERR_UseDefViolation, "Local2()").WithArguments("x").WithLocation(10, 13));
}
[Fact]
public void UnusedLocalFunc()
{
var comp = CreateCompilationWithMscorlib(@"
class C
{
public static void Main()
{
int x;
bool Local() => x == 0;
}
}");
comp.VerifyDiagnostics(
// (7,14): warning CS0168: The variable 'Local' is declared but never used
// bool Local() => x == 0;
Diagnostic(ErrorCode.WRN_UnreferencedVar, "Local").WithArguments("Local").WithLocation(7, 14));
}
[Fact]
public void UnassignedInStruct()
{
var comp = CreateCompilationWithMscorlib(@"
struct S
{
int _x;
public S(int x)
{
var s = this;
void Local()
{
s._x = _x;
}
Local();
}
}");
comp.VerifyDiagnostics(
// (10,20): error CS1673: Anonymous methods, lambda expressions, and query expressions inside structs cannot access instance members of 'this'. Consider copying 'this' to a local variable outside the anonymous method, lambda expression or query expression and using the local instead.
// s._x = _x;
Diagnostic(ErrorCode.ERR_ThisStructNotInAnonMeth, "_x").WithLocation(10, 20),
// (7,17): error CS0188: The 'this' object cannot be used before all of its fields are assigned to
// var s = this;
Diagnostic(ErrorCode.ERR_UseDefViolationThis, "this").WithArguments("this").WithLocation(7, 17),
// (12,9): error CS0170: Use of possibly unassigned field '_x'
// Local();
Diagnostic(ErrorCode.ERR_UseDefViolationField, "Local()").WithArguments("_x").WithLocation(12, 9),
// (5,12): error CS0171: Field 'S._x' must be fully assigned before control is returned to the caller
// public S(int x)
Diagnostic(ErrorCode.ERR_UnassignedThis, "S").WithArguments("S._x").WithLocation(5, 12));
}
[Fact]
public void AssignWithStruct()
{
var comp = CreateCompilationWithMscorlib(@"
struct S
{
public int x;
public int y;
}
class C
{
public void M1()
{
S s1;
Local();
S s2 = s1; // unassigned
void Local()
{
s1.x = 0;
}
s1.y = 0;
}
public void M2()
{
S s1;
Local();
S s2 = s1; // success
void Local()
{
s1.x = 0;
s1.y = 0;
}
}
public void M3()
{
S s1;
S s2 = s1; // unassigned
Local();
void Local()
{
s1.x = 0;
s1.y = 0;
}
}
void M4()
{
S s1;
Local();
void Local()
{
s1.x = 0;
s1.x += s1.y;
}
}
}");
comp.VerifyDiagnostics(
// (14,16): error CS0165: Use of unassigned local variable 's1'
// S s2 = s1; // unassigned
Diagnostic(ErrorCode.ERR_UseDefViolation, "s1").WithArguments("s1").WithLocation(14, 16),
// (37,16): error CS0165: Use of unassigned local variable 's1'
// S s2 = s1; // unassigned
Diagnostic(ErrorCode.ERR_UseDefViolation, "s1").WithArguments("s1").WithLocation(37, 16),
// (48,9): error CS0170: Use of possibly unassigned field 'y'
// Local();
Diagnostic(ErrorCode.ERR_UseDefViolationField, "Local()").WithArguments("y").WithLocation(48, 9));
}
[Fact]
public void NestedStructProperty()
{
var comp = CreateCompilationWithMscorlib(@"
struct A
{
public int x;
public int y { get; set; }
}
struct B
{
public A a;
public int z;
}
class C
{
void AssignInLocalFunc()
{
A a1;
Local1(); // unassigned
A a2 = a1;
void Local1()
{
a1.x = 0;
a1.y = 0;
}
B b1;
Local2();
B b2 = b1;
void Local2()
{
b1.a.x = 0;
b1.a.y = 0;
b1.z = 0;
}
}
void SkipNestedField()
{
B b1;
Local();
B b2 = b1; // unassigned
void Local()
{
b1.a.x = 0;
b1.z = 0;
}
}
void SkipNestedStruct()
{
B b1;
Local();
B b2 = b1; // unassigned
void Local()
{
b1.z = 0;
}
}
void SkipField()
{
B b1;
Local();
B b2 = b1; // unassigned
void Local()
{
b1.a.x = 0;
b1.a.y = 0;
}
}
}");
comp.VerifyDiagnostics(
// (19,9): error CS0165: Use of unassigned local variable 'a1'
// Local1();
Diagnostic(ErrorCode.ERR_UseDefViolation, "Local1()").WithArguments("a1").WithLocation(19, 9),
// (28,9): error CS0170: Use of possibly unassigned field 'a'
// Local2();
Diagnostic(ErrorCode.ERR_UseDefViolationField, "Local2()").WithArguments("a").WithLocation(28, 9),
// (41,16): error CS0165: Use of unassigned local variable 'b1'
// B b2 = b1; // unassigned
Diagnostic(ErrorCode.ERR_UseDefViolation, "b1").WithArguments("b1").WithLocation(41, 16),
// (52,16): error CS0165: Use of unassigned local variable 'b1'
// B b2 = b1; // unassigned
Diagnostic(ErrorCode.ERR_UseDefViolation, "b1").WithArguments("b1").WithLocation(52, 16),
// (61,9): error CS0170: Use of possibly unassigned field 'a'
// Local();
Diagnostic(ErrorCode.ERR_UseDefViolationField, "Local()").WithArguments("a").WithLocation(61, 9),
// (62,16): error CS0165: Use of unassigned local variable 'b1'
// B b2 = b1; // unassigned
Diagnostic(ErrorCode.ERR_UseDefViolation, "b1").WithArguments("b1").WithLocation(62, 16));
}
[Fact]
public void WriteAndReadInLocalFunc()
{
var comp = CreateCompilationWithMscorlib(@"
class C
{
public void M()
{
int x;
bool b;
Local();
void Local()
{
x = x + 1;
x = 0;
}
b = x == 0;
}
}");
comp.VerifyDiagnostics(
// (8,9): error CS0165: Use of unassigned local variable 'x'
// Local();
Diagnostic(ErrorCode.ERR_UseDefViolation, "Local()").WithArguments("x").WithLocation(8, 9));
}
[Fact]
public void EventReadAndWrite()
{
var comp = CreateCompilationWithMscorlib(@"
using System;
struct S
{
public int x;
public event EventHandler Event;
public void Fire() => Event(null, EventArgs.Empty);
}
class C
{
void PartialAssign()
{
S s1;
Local1();
S s2 = s1;
void Local1()
{
s1.x = 0;
}
}
void FullAssign()
{
S s1;
Local1();
S s2 = s1;
void Local1()
{
s1 = new S();
s1.x = 0;
s1.Event += Handler1;
s1.Fire();
void Handler1(object sender, EventArgs args)
{
s1.x++;
}
}
S s3;
void Local2()
{
s3.x = 0;
s3.Event += Handler2;
void Handler2(object sender, EventArgs args)
{
s1.x++;
s3.x++;
}
}
S s4 = s3;
Local2();
}
}");
comp.VerifyDiagnostics(
// (18,16): error CS0165: Use of unassigned local variable 's1'
// S s2 = s1;
Diagnostic(ErrorCode.ERR_UseDefViolation, "s1").WithArguments("s1").WithLocation(18, 16),
// (54,16): error CS0165: Use of unassigned local variable 's3'
// S s4 = s3;
Diagnostic(ErrorCode.ERR_UseDefViolation, "s3").WithArguments("s3").WithLocation(54, 16));
}
[Fact]
public void CaptureForeachVar()
{
var comp = CreateCompilationWithMscorlib(@"
class C
{
void M()
{
var items = new[] { 0, 1, 2, 3};
foreach (var i in items)
{
void Local()
{
i = 4;
}
Local();
}
}
}");
comp.VerifyDiagnostics(
// (11,17): error CS1656: Cannot assign to 'i' because it is a 'foreach iteration variable'
// i = 4;
Diagnostic(ErrorCode.ERR_AssgReadonlyLocalCause, "i").WithArguments("i", "foreach iteration variable").WithLocation(11, 17));
}
[Fact]
public void CapturePattern()
{
var comp = CreateCompilationWithMscorlib(@"
class C
{
void M()
{
object o = 2;
if (o is int x1 && Local(x1) == 0)
{
}
if (o is int x2 || Local(x2) == 0)
{
}
if (!(o is int x3))
{
void Local2()
{
x3++;
}
Local2();
}
int Local(int i) => i;
}
}");
comp.VerifyDiagnostics(
// (11,34): error CS0165: Use of unassigned local variable 'x2'
// if (o is int x2 || Local(x2) == 0)
Diagnostic(ErrorCode.ERR_UseDefViolation, "x2").WithArguments("x2").WithLocation(11, 34),
// (21,13): error CS0165: Use of unassigned local variable 'x3'
// Local2();
Diagnostic(ErrorCode.ERR_UseDefViolation, "Local2()").WithArguments("x3").WithLocation(21, 13));
}
[Fact]
public void NotAssignedControlFlow()
{
var comp = CreateCompilationWithMscorlib(@"
class C
{
void FullyAssigned()
{
int x;
int y = 0;
void Local()
{
if (y == 0)
x = 0;
else
Local2();
}
void Local2()
{
x = 0;
}
Local();
y = x;
}
void PartiallyAssigned()
{
int x;
int y = 0;
void Local()
{
if (y == 0)
x = 0;
else
Local2();
}
void Local2()
{
//x = 0;
}
Local();
y = x; // unassigned
}
}");
comp.VerifyDiagnostics(
// (38,13): error CS0165: Use of unassigned local variable 'x'
// y = x; // unassigned
Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(38, 13));
}
[Fact]
public void UseConsts()
{
var comp = CreateCompilationWithMscorlib(@"
struct S
{
public const int z = 0;
}
class C
{
const int x = 0;
void M()
{
const int y = 0;
Local();
int Local()
{
const int a = 1;
return a + x + y + S.z;
}
}
}");
comp.VerifyDiagnostics();
}
[Fact]
public void NotAssignedAtAllReturns()
{
var comp = CreateCompilationWithMscorlib(@"
class C
{
void M()
{
int x;
void L1()
{
if ("""".Length == 1)
{
x = 1;
}
else
{
return;
}
}
L1();
var z = x;
}
}");
comp.VerifyDiagnostics(
// (19,17): error CS0165: Use of unassigned local variable 'x'
// var z = x;
Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(19, 17));
}
[Fact]
public void NotAssignedAtThrow()
{
var comp = CreateCompilationWithMscorlib(@"
class C
{
void M()
{
int x1, x2;
void L1()
{
if ("""".Length == 1)
x1 = x2 = 0;
else
throw new System.Exception();
}
try
{
L1();
var y = x1;
}
catch
{
var z = x1;
}
var zz = x2;
}
}");
comp.VerifyDiagnostics(
// (21,21): error CS0165: Use of unassigned local variable 'x1'
// var z = x1;
Diagnostic(ErrorCode.ERR_UseDefViolation, "x1").WithArguments("x1").WithLocation(21, 21),
// (23,18): error CS0165: Use of unassigned local variable 'x2'
// var zz = x2;
Diagnostic(ErrorCode.ERR_UseDefViolation, "x2").WithArguments("x2").WithLocation(23, 18));
}
[Fact]
public void DeadCode()
{
var comp = CreateCompilationWithMscorlib(@"
class C
{
void M()
{
int x;
goto live;
void L1() => x = 0;
live:
L1();
var z = x;
}
void M2()
{
int x;
goto live;
void L1()
{
if ("""".Length == 1)
x = 0;
else
return;
}
live:
L1();
var z = x;
}
}");
comp.VerifyDiagnostics(
// (26,17): error CS0165: Use of unassigned local variable 'x'
// var z = x;
Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(26, 17));
}
[Fact]
public void LocalFunctionFromOtherSwitch()
{
var comp = CreateCompilationWithMscorlib(@"
class C
{
void M()
{
int x;
int y;
switch("""".Length)
{
case 0:
void L1()
{
y = 0;
L2();
}
break;
case 1:
L1();
y = x;
break;
case 2:
void L2()
{
x = y;
}
break;
}
}
}");
comp.VerifyDiagnostics();
}
}
}
......@@ -104,33 +104,50 @@ public void CheckUnion()
{
var r = new Random(seed);
for (int capacity = 0; capacity < maxBits; capacity++)
CheckUnionCore(capacity, r);
{
CheckUnionCore(capacity, capacity, r);
}
for (int i = 0; i < rounds; i++)
{
CheckUnionCore(r.Next(maxBits), r);
CheckUnionCore(r.Next(maxBits), r.Next(maxBits), r);
}
}
private void CheckUnionCore(int capacity, Random r)
private void CheckUnionCore(int capacity1, int capacity2, Random r)
{
BitVector b1 = BitVector.Empty, b2 = BitVector.Empty;
b1.EnsureCapacity(capacity);
b2.EnsureCapacity(capacity);
bool[] a1 = new bool[capacity], a2 = new bool[capacity];
for (int i = 0; i < capacity; i++)
b1.EnsureCapacity(capacity1);
b2.EnsureCapacity(capacity2);
var maxCapacity = Math.Max(capacity1, capacity2);
bool[] a1 = new bool[maxCapacity],
a2 = new bool[maxCapacity];
for (int i = 0; i < capacity1; i++)
{
b1[i] = a1[i] = r.NextBool();
}
for (int i = 0; i < capacity2; i++)
{
b2[i] = a2[i] = r.NextBool();
}
b1.UnionWith(b2);
for (int i = 0; i < capacity; i++)
bool changed = b1.UnionWith(b2);
bool changed2 = false;
for (int i = 0; i < maxCapacity; i++)
{
bool a = a1[i];
a1[i] |= a2[i];
changed2 |= (a != a1[i]);
}
for (int i = 0; i < capacity; i++)
for (int i = 0; i < maxCapacity; i++)
{
Assert.Equal(a1[i], b1[i]);
}
Assert.Equal(changed2, changed);
}
[Fact]
......
......@@ -283,18 +283,34 @@ public bool IntersectWith(BitVector other)
/// <summary>
/// Modify this bit vector by '|'ing each element with the other bit vector.
/// </summary>
/// <param name="other"></param>
public void UnionWith(BitVector other)
/// <returns>
/// True if any bits were set as a result of the union.
/// </returns>
public bool UnionWith(BitVector other)
{
int l = other._bits.Length;
if (l > _bits.Length)
Array.Resize(ref _bits, l + 1);
_bits0 |= other._bits0;
for (int i = 0; i < l; i++)
_bits[i] |= other._bits[i];
bool anyChanged = false;
if (other._capacity > _capacity)
EnsureCapacity(other._capacity);
Word oldbits = _bits0;
_bits0 |= other._bits0;
if (oldbits != _bits0)
anyChanged = true;
for (int i = 0; i < other._bits.Length; i++)
{
oldbits = _bits[i];
_bits[i] |= other._bits[i];
if (_bits[i] != oldbits)
anyChanged = true;
}
Check();
return anyChanged;
}
public bool this[int index]
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册