From f2d0be5c8bf0a071f617c74b717d99b3e2d541a3 Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Wed, 12 Feb 2020 00:05:23 -0800 Subject: [PATCH 1/4] Allow data flow to customize starting state for normalization Certain data flow analyses use "normalization" to adjust an existing state variable for more variables declared after that state was created. The default behavior is to extend that state to the "top" state, meaning the starting state for that data flow analysis. This is usually the right extension. However, some analysis requires extending the variables the other way, to the bottom state. This PR adds functionality for that customization and also fixes #41600. --- .../AbstractFlowPass_LocalFunctions.cs | 6 +- .../Portable/FlowAnalysis/ControlFlowPass.cs | 2 +- .../DefiniteAssignment.LocalFunctions.cs | 13 +- .../FlowAnalysis/DefiniteAssignment.cs | 20 ++- .../FlowAnalysis/LocalDataFlowPass.cs | 14 ++- .../Portable/FlowAnalysis/NullableWalker.cs | 8 +- .../FlowAnalysis/RegionAnalysisTests.cs | 118 ++++++++++++++++++ 7 files changed, 164 insertions(+), 17 deletions(-) diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass_LocalFunctions.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass_LocalFunctions.cs index b8678c7e3adb7..17546d686e9ca 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass_LocalFunctions.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass_LocalFunctions.cs @@ -34,10 +34,10 @@ internal abstract class AbstractLocalFunctionState /// public TLocalState StateFromTop; - public AbstractLocalFunctionState(TLocalState unreachableState) + public AbstractLocalFunctionState(TLocalState stateFromBottom, TLocalState stateFromTop) { - StateFromBottom = unreachableState.Clone(); - StateFromTop = unreachableState.Clone(); + StateFromBottom = stateFromBottom; + StateFromTop = stateFromTop; } public bool Visited = false; diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/ControlFlowPass.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/ControlFlowPass.cs index c820425a44ebb..67bef799bcffa 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/ControlFlowPass.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/ControlFlowPass.cs @@ -67,7 +67,7 @@ public bool Reachable internal sealed class LocalFunctionState : AbstractLocalFunctionState { public LocalFunctionState(LocalState unreachableState) - : base(unreachableState) + : base(unreachableState.Clone(), unreachableState.Clone()) { } } diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/DefiniteAssignment.LocalFunctions.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/DefiniteAssignment.LocalFunctions.cs index 758aa29a66d0e..d3d72d94fa5de 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/DefiniteAssignment.LocalFunctions.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/DefiniteAssignment.LocalFunctions.cs @@ -12,14 +12,17 @@ internal partial class DefiniteAssignmentPass internal sealed class LocalFunctionState : AbstractLocalFunctionState { public BitVector ReadVars = BitVector.Empty; - public ref LocalState WrittenVars => ref StateFromTop; - public LocalFunctionState(LocalState unreachableState) - : base(unreachableState) + public LocalFunctionState(LocalState stateFromBottom, LocalState stateFromTop) + : base(stateFromBottom, stateFromTop) { } } - protected override LocalFunctionState CreateLocalFunctionState() => new LocalFunctionState(UnreachableState()); + protected override LocalFunctionState CreateLocalFunctionState() + => new LocalFunctionState( + // The bottom state should assume all variables, even new ones, are assigned + new LocalState(BitVector.AllSet(nextVariableSlot), normalizeToBottom: true), + UnreachableState()); protected override void VisitLocalFunctionUse( LocalFunctionSymbol localFunc, @@ -166,7 +169,7 @@ protected override LocalFunctionState LocalFunctionStart(LocalFunctionState star // assignment errors if any of the captured variables is not assigned // on a particular branch. - var savedState = new LocalFunctionState(UnreachableState()); + var savedState = CreateLocalFunctionState(); savedState.ReadVars = startState.ReadVars.Clone(); startState.ReadVars.Clear(); return savedState; diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/DefiniteAssignment.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/DefiniteAssignment.cs index 9a2ab03631bed..e638df5820208 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/DefiniteAssignment.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/DefiniteAssignment.cs @@ -741,9 +741,18 @@ protected override void Normalize(ref LocalState state) { var id = variableBySlot[i]; int slot = id.ContainingSlot; - state.Assigned[i] = (slot > 0) && + + bool assign = (slot > 0) && state.Assigned[slot] && variableBySlot[slot].Symbol.GetTypeOrReturnType().TypeKind == TypeKind.Struct; + + if (state.NormalizeToBottom) + { + // NormalizeToBottom means new variables are assumed to be assigned (bottom state) + assign |= slot == 0; + } + + state.Assigned[i] = assign; } } @@ -2157,16 +2166,19 @@ protected override bool Join(ref LocalState self, ref LocalState other) } #if REFERENCE_STATE - internal class LocalState : ILocalState + internal class LocalState : ILocalDataFlowState #else - internal struct LocalState : ILocalState + internal struct LocalState : ILocalDataFlowState #endif { internal BitVector Assigned; - internal LocalState(BitVector assigned) + public bool NormalizeToBottom { get; } + + internal LocalState(BitVector assigned, bool normalizeToBottom = false) { this.Assigned = assigned; + NormalizeToBottom = normalizeToBottom; Debug.Assert(!assigned.IsNull); } diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/LocalDataFlowPass.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/LocalDataFlowPass.cs index 4355365b52df2..e9891d2067eb8 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/LocalDataFlowPass.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/LocalDataFlowPass.cs @@ -14,9 +14,18 @@ namespace Microsoft.CodeAnalysis.CSharp /// Does a data flow analysis for state attached to local variables and fields of struct locals. /// internal abstract partial class LocalDataFlowPass : AbstractFlowPass - where TLocalState : AbstractFlowPass.ILocalState + where TLocalState : LocalDataFlowPass.ILocalDataFlowState where TLocalFunctionState : AbstractFlowPass.AbstractLocalFunctionState { + internal interface ILocalDataFlowState : ILocalState + { + /// + /// True if new variables introduced in should be set + /// to the bottom state. False if they should be set to the top state. + /// + bool NormalizeToBottom { get; } + } + /// /// A mapping from local variables to the index of their slot in a flow analysis local state. /// @@ -172,6 +181,9 @@ private int GetSlotDepth(int slot) return depth; } + /// + /// Sets the starting state for any newly declared variables in the LocalDataFlowPass. + /// protected abstract void Normalize(ref TLocalState state); /// diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs index a1e70fc691e31..615c03ff0e1b9 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs @@ -8459,9 +8459,9 @@ protected override bool Join(ref LocalState self, ref LocalState other) [DebuggerDisplay("{GetDebuggerDisplay(), nq}")] #if REFERENCE_STATE - internal class LocalState : ILocalState + internal class LocalState : ILocalDataFlowState #else - internal struct LocalState : ILocalState + internal struct LocalState : ILocalDataFlowState #endif { // The representation of a state is a bit vector with two bits per slot: @@ -8473,6 +8473,8 @@ internal struct LocalState : ILocalState public bool Reachable => _state[0]; + public bool NormalizeToBottom { get; } = false; + public static LocalState ReachableState(int capacity) { if (capacity < 1) @@ -8555,7 +8557,7 @@ internal sealed class LocalFunctionState : AbstractLocalFunctionState /// public LocalState StartingState; public LocalFunctionState(LocalState unreachableState) - : base(unreachableState) + : base(unreachableState.Clone(), unreachableState.Clone()) { StartingState = unreachableState; } diff --git a/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/RegionAnalysisTests.cs b/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/RegionAnalysisTests.cs index d1418dfc28109..6e4f5689028a1 100644 --- a/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/RegionAnalysisTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/RegionAnalysisTests.cs @@ -4131,6 +4131,124 @@ static void Main(string[] args) #region "lambda" + [Fact] + public void DataFlowAnalysisLocalFunctions10() + { + var dataFlow = CompileAndAnalyzeDataFlowExpression(@" +class C +{ + public void M() + { + bool Dummy(params object[] x) {return true;} + + try {} + catch when (/**/TakeOutParam(out var x1)/**/ && x1 > 0) + { + Dummy(x1); + } + + var x4 = 11; + Dummy(x4); + + try {} + catch when (TakeOutParam(out var x4) && x4 > 0) + { + Dummy(x4); + } + + try {} + catch when (x6 && TakeOutParam(out var x6)) + { + Dummy(x6); + } + + try {} + catch when (TakeOutParam(out var x7) && x7 > 0) + { + var x7 = 12; + Dummy(x7); + } + + try {} + catch when (TakeOutParam(out var x8) && x8 > 0) + { + Dummy(x8); + } + + System.Console.WriteLine(x8); + + try {} + catch when (TakeOutParam(out var x9) && x9 > 0) + { + Dummy(x9); + try {} + catch when (TakeOutParam(out var x9) && x9 > 0) // 2 + { + Dummy(x9); + } + } + + try {} + catch when (TakeOutParam(y10, out var x10)) + { + var y10 = 12; + Dummy(y10); + } + + // try {} + // catch when (TakeOutParam(y11, out var x11) + // { + // let y11 = 12; + // Dummy(y11); + // } + + try {} + catch when (Dummy(TakeOutParam(out var x14), + TakeOutParam(out var x14), // 2 + x14)) + { + Dummy(x14); + } + + try {} + catch (System.Exception x15) + when (Dummy(TakeOutParam(out var x15), x15)) + { + Dummy(x15); + } + + static bool TakeOutParam(out int x) + { + x = 123; + return true; + } + static bool TakeOutParam(object y, out int x) + { + x = 123; + return true; + } + + } +} +"); + Assert.True(dataFlow.Succeeded); + Assert.Null(GetSymbolNamesJoined(dataFlow.Captured)); + Assert.Null(GetSymbolNamesJoined(dataFlow.CapturedInside)); + Assert.Null(GetSymbolNamesJoined(dataFlow.CapturedOutside)); + Assert.Equal("x1", GetSymbolNamesJoined(dataFlow.VariablesDeclared)); + Assert.Null(GetSymbolNamesJoined(dataFlow.DataFlowsIn)); + Assert.Equal("x1", GetSymbolNamesJoined(dataFlow.DataFlowsOut)); + Assert.Equal("this", GetSymbolNamesJoined(dataFlow.DefinitelyAssignedOnEntry)); + Assert.Equal("this, x1, x", GetSymbolNamesJoined(dataFlow.DefinitelyAssignedOnExit)); + Assert.Null(GetSymbolNamesJoined(dataFlow.ReadInside)); + Assert.Equal("x1", GetSymbolNamesJoined(dataFlow.WrittenInside)); + Assert.Equal("this, x1, x4, x4, x6, x7, x7, x8, x9, x9, y10, x14, x15, x, x", + GetSymbolNamesJoined(dataFlow.ReadOutside)); + Assert.Equal("this, x, x4, x4, x6, x7, x7, x8, x9, x9, x10, " + + "y10, x14, x14, x15, x15, x, y, x", + GetSymbolNamesJoined(dataFlow.WrittenOutside)); + } + [Fact] [WorkItem(39946, "https://github.com/dotnet/roslyn/issues/39946")] public void DataFlowAnalysisLocalFunctions9() From 869353b32a1716f3fde9976f40ae48068b2a87b9 Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Wed, 19 Feb 2020 01:27:12 -0800 Subject: [PATCH 2/4] Filter out only the captured variables There's no strict design, but it seems reasonable that region analysis consumers would only expect captured variables to show up in the input/output variables when calling a local function. --- .../AbstractFlowPass_LocalFunctions.cs | 7 ++--- .../DefiniteAssignment.LocalFunctions.cs | 27 +++++++++++++++---- .../Portable/FlowAnalysis/NullableWalker.cs | 2 +- .../FlowAnalysis/RegionAnalysisTests.cs | 8 +++--- .../Core/Portable/Collections/BitVector.cs | 17 +++++++++++- 5 files changed, 45 insertions(+), 16 deletions(-) diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass_LocalFunctions.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass_LocalFunctions.cs index 17546d686e9ca..189dcb318e20f 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass_LocalFunctions.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass_LocalFunctions.cs @@ -155,7 +155,8 @@ private bool RecordStateChange( TLocalFunctionState currentState, ref TLocalState stateAtReturn) { - bool anyChanged = Join(ref currentState.StateFromTop, ref stateAtReturn); + bool anyChanged = LocalFunctionEnd(savedState, currentState, ref stateAtReturn); + anyChanged |= Join(ref currentState.StateFromTop, ref stateAtReturn); if (NonMonotonicState.HasValue) { @@ -166,10 +167,6 @@ private bool RecordStateChange( 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); return anyChanged; } diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/DefiniteAssignment.LocalFunctions.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/DefiniteAssignment.LocalFunctions.cs index d3d72d94fa5de..86e4edb433f6c 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/DefiniteAssignment.LocalFunctions.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/DefiniteAssignment.LocalFunctions.cs @@ -13,6 +13,9 @@ internal sealed class LocalFunctionState : AbstractLocalFunctionState { public BitVector ReadVars = BitVector.Empty; + public BitVector CapturedMask = BitVector.Null; + public BitVector InvertedCapturedMask = BitVector.Null; + public LocalFunctionState(LocalState stateFromBottom, LocalState stateFromTop) : base(stateFromBottom, stateFromTop) { } @@ -115,10 +118,10 @@ private void RecordReadInLocalFunction(int slot) } } - private BitVector GetCapturedBitmask(ref BitVector state) + private BitVector GetCapturedBitmask() { - BitVector mask = BitVector.Empty; - for (int slot = 1; slot < state.Capacity; slot++) + BitVector mask = BitVector.AllSet(1); + for (int slot = 1; slot < nextVariableSlot; slot++) { if (IsCapturedInLocalFunction(slot)) { @@ -184,10 +187,24 @@ protected override bool LocalFunctionEnd( LocalFunctionState currentState, ref LocalState stateAtReturn) { + if (currentState.CapturedMask.IsNull) + { + currentState.CapturedMask = GetCapturedBitmask(); + currentState.InvertedCapturedMask = currentState.CapturedMask.Clone(); + currentState.InvertedCapturedMask.Invert(); + } + // Filter the modified state variables to only captured variables + stateAtReturn.Assigned.IntersectWith(currentState.CapturedMask); + if (NonMonotonicState.HasValue) + { + var state = NonMonotonicState.Value; + state.Assigned.UnionWith(currentState.InvertedCapturedMask); + NonMonotonicState = state; + } + // Build a list of variables that are both captured and read before assignment - var capturedMask = GetCapturedBitmask(ref currentState.ReadVars); var capturedAndRead = currentState.ReadVars; - capturedAndRead.IntersectWith(capturedMask); + capturedAndRead.IntersectWith(currentState.CapturedMask); // Union and check to see if there are any changes return savedState.ReadVars.UnionWith(capturedAndRead); diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs index 615c03ff0e1b9..ba1420ad5f0fb 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs @@ -8473,7 +8473,7 @@ internal struct LocalState : ILocalDataFlowState public bool Reachable => _state[0]; - public bool NormalizeToBottom { get; } = false; + public bool NormalizeToBottom => false; public static LocalState ReachableState(int capacity) { diff --git a/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/RegionAnalysisTests.cs b/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/RegionAnalysisTests.cs index 6e4f5689028a1..b7312f3945e1f 100644 --- a/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/RegionAnalysisTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/RegionAnalysisTests.cs @@ -4239,13 +4239,13 @@ static bool TakeOutParam(object y, out int x) Assert.Null(GetSymbolNamesJoined(dataFlow.DataFlowsIn)); Assert.Equal("x1", GetSymbolNamesJoined(dataFlow.DataFlowsOut)); Assert.Equal("this", GetSymbolNamesJoined(dataFlow.DefinitelyAssignedOnEntry)); - Assert.Equal("this, x1, x", GetSymbolNamesJoined(dataFlow.DefinitelyAssignedOnExit)); + Assert.Equal("this, x1", GetSymbolNamesJoined(dataFlow.DefinitelyAssignedOnExit)); Assert.Null(GetSymbolNamesJoined(dataFlow.ReadInside)); Assert.Equal("x1", GetSymbolNamesJoined(dataFlow.WrittenInside)); - Assert.Equal("this, x1, x4, x4, x6, x7, x7, x8, x9, x9, y10, x14, x15, x, x", + Assert.Equal("this, x1, x4, x4, x6, x7, x7, x8, x9, x9, y10, x14, x15, x, x", GetSymbolNamesJoined(dataFlow.ReadOutside)); - Assert.Equal("this, x, x4, x4, x6, x7, x7, x8, x9, x9, x10, " + - "y10, x14, x14, x15, x15, x, y, x", + Assert.Equal("this, x, x4, x4, x6, x7, x7, x8, x9, x9, x10, " + + "y10, x14, x14, x15, x15, x, y, x", GetSymbolNamesJoined(dataFlow.WrittenOutside)); } diff --git a/src/Compilers/Core/Portable/Collections/BitVector.cs b/src/Compilers/Core/Portable/Collections/BitVector.cs index 867978b51fd4d..98965db3a12c9 100644 --- a/src/Compilers/Core/Portable/Collections/BitVector.cs +++ b/src/Compilers/Core/Portable/Collections/BitVector.cs @@ -22,7 +22,7 @@ internal struct BitVector : IEquatable // Cannot expose the following two field publicly because this structure is mutable // and might become not null/empty, unless we restrict access to it. - private static readonly Word[] s_emptyArray = Array.Empty(); + private static Word[] s_emptyArray => Array.Empty(); private static readonly BitVector s_nullValue = default; private static readonly BitVector s_emptyValue = new BitVector(0, s_emptyArray, 0); @@ -222,6 +222,21 @@ public BitVector Clone() return new BitVector(_bits0, newBits, _capacity); } + /// + /// Invert all the bits in the vector. + /// + public void Invert() + { + _bits0 = ~_bits0; + if (!(_bits is null)) + { + for (int i = 0; i < _bits.Length; i++) + { + _bits[i] = ~_bits[i]; + } + } + } + /// /// Is the given bit array null? /// From 64a4c6a68b707e59d2d57f800dbde4586cddc68e Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Thu, 20 Feb 2020 16:33:53 -0800 Subject: [PATCH 3/4] Respond to PR comments --- .../CSharp/Portable/FlowAnalysis/DefiniteAssignment.cs | 4 ++-- .../CSharp/Test/Semantic/FlowAnalysis/RegionAnalysisTests.cs | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/DefiniteAssignment.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/DefiniteAssignment.cs index e638df5820208..718be23bdf4f7 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/DefiniteAssignment.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/DefiniteAssignment.cs @@ -746,10 +746,10 @@ protected override void Normalize(ref LocalState state) state.Assigned[slot] && variableBySlot[slot].Symbol.GetTypeOrReturnType().TypeKind == TypeKind.Struct; - if (state.NormalizeToBottom) + if (state.NormalizeToBottom && slot == 0) { // NormalizeToBottom means new variables are assumed to be assigned (bottom state) - assign |= slot == 0; + assign = true; } state.Assigned[i] = assign; diff --git a/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/RegionAnalysisTests.cs b/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/RegionAnalysisTests.cs index b7312f3945e1f..60ccfa0a418c9 100644 --- a/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/RegionAnalysisTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/RegionAnalysisTests.cs @@ -4132,6 +4132,7 @@ static void Main(string[] args) #region "lambda" [Fact] + [WorkItem(41802, "https://github.com/dotnet/roslyn/pull/41802")] public void DataFlowAnalysisLocalFunctions10() { var dataFlow = CompileAndAnalyzeDataFlowExpression(@" From e794da59d416cac83d72199b1872b5d4df83a7ff Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Tue, 3 Mar 2020 10:53:28 -0800 Subject: [PATCH 4/4] Respond to PR comments --- .../FlowAnalysis/DefiniteAssignment.LocalFunctions.cs | 7 ++----- .../Test/Semantic/FlowAnalysis/RegionAnalysisTests.cs | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/DefiniteAssignment.LocalFunctions.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/DefiniteAssignment.LocalFunctions.cs index 86e4edb433f6c..71c6a113f825b 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/DefiniteAssignment.LocalFunctions.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/DefiniteAssignment.LocalFunctions.cs @@ -120,13 +120,10 @@ private void RecordReadInLocalFunction(int slot) private BitVector GetCapturedBitmask() { - BitVector mask = BitVector.AllSet(1); + BitVector mask = BitVector.AllSet(nextVariableSlot); for (int slot = 1; slot < nextVariableSlot; slot++) { - if (IsCapturedInLocalFunction(slot)) - { - mask[slot] = true; - } + mask[slot] = IsCapturedInLocalFunction(slot); } return mask; diff --git a/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/RegionAnalysisTests.cs b/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/RegionAnalysisTests.cs index 60ccfa0a418c9..bada442325b3d 100644 --- a/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/RegionAnalysisTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/RegionAnalysisTests.cs @@ -4132,7 +4132,7 @@ static void Main(string[] args) #region "lambda" [Fact] - [WorkItem(41802, "https://github.com/dotnet/roslyn/pull/41802")] + [WorkItem(41600, "https://github.com/dotnet/roslyn/pull/41600")] public void DataFlowAnalysisLocalFunctions10() { var dataFlow = CompileAndAnalyzeDataFlowExpression(@"