From 5c5782720c0a262ec3f5b5ea645d7029bac0bfe6 Mon Sep 17 00:00:00 2001 From: mary-georgiou-sonarsource Date: Wed, 19 Jun 2024 13:53:47 +0200 Subject: [PATCH] Support flow captures --- .../RoslynLiveVariableAnalysis.cs | 103 ++++++++++--- ...nLiveVariableAnalysisTest.LocalFunction.cs | 4 +- .../RoslynLiveVariableAnalysisTest.cs | 138 ++++++++++-------- 3 files changed, 163 insertions(+), 82 deletions(-) diff --git a/analyzers/src/SonarAnalyzer.CFG/LiveVariableAnalysis/RoslynLiveVariableAnalysis.cs b/analyzers/src/SonarAnalyzer.CFG/LiveVariableAnalysis/RoslynLiveVariableAnalysis.cs index 70944b96a9b..da982ad7b85 100644 --- a/analyzers/src/SonarAnalyzer.CFG/LiveVariableAnalysis/RoslynLiveVariableAnalysis.cs +++ b/analyzers/src/SonarAnalyzer.CFG/LiveVariableAnalysis/RoslynLiveVariableAnalysis.cs @@ -18,12 +18,14 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +using System.Collections.Concurrent; using SonarAnalyzer.CFG.Roslyn; namespace SonarAnalyzer.CFG.LiveVariableAnalysis; public sealed class RoslynLiveVariableAnalysis : LiveVariableAnalysisBase { + private readonly ConcurrentDictionary> flowCaptureOperations = []; private readonly Dictionary> blockPredecessors = []; private readonly Dictionary> blockSuccessors = []; @@ -41,6 +43,7 @@ public RoslynLiveVariableAnalysis(ControlFlowGraph cfg, CancellationToken cancel { BuildBranches(block); } + LiveCaptureAnalysis(cfg); Analyze(); } @@ -70,10 +73,34 @@ protected override IEnumerable Successors(BasicBlock block) => protected override State ProcessBlock(BasicBlock block) { var ret = new RoslynState(this); - ret.ProcessBlock(Cfg, block); + ret.ProcessBlock(Cfg, block, flowCaptureOperations); return ret; } + private void LiveCaptureAnalysis(ControlFlowGraph cfg) + { + foreach (var operation in cfg.Blocks.Where(x => x.IsEnclosedIn(ControlFlowRegionKind.LocalLifetime)).SelectMany(x => x.Operations).ToExecutionOrder()) + { + if (IFlowCaptureOperationWrapper.IsInstance(operation.Instance)) + { + var flowCapture = IFlowCaptureOperationWrapper.FromOperation(operation.Instance); + if (!IFlowCaptureReferenceOperationWrapper.IsInstance(flowCapture.Value)) + { + _ = flowCaptureOperations.TryGetValue(flowCapture.Id, out var captureOperations); + var value = captureOperations ?? []; + value.Add(flowCapture.Value); + flowCaptureOperations.TryAdd(flowCapture.Id, value); + } + else + { + var flowCaptureReference = IFlowCaptureReferenceOperationWrapper.FromOperation(flowCapture.Value); + _ = flowCaptureOperations.TryGetValue(flowCaptureReference.Id, out var captureOperations); + flowCaptureOperations.TryAdd(flowCapture.Id, captureOperations); + } + } + } + } + private void BuildBranches(BasicBlock block) { foreach (var successor in block.Successors) @@ -177,35 +204,65 @@ private sealed class RoslynState : State public RoslynState(RoslynLiveVariableAnalysis owner) => this.owner = owner; - public void ProcessBlock(ControlFlowGraph cfg, BasicBlock block) + public void ProcessBlock(ControlFlowGraph cfg, BasicBlock block, ConcurrentDictionary> flowCaptureOperations = null) { - foreach (var operation in block.OperationsAndBranchValue.ToReversedExecutionOrder()) + if (flowCaptureOperations is null) { - // Everything that is added to this switch needs to be considered inside ProcessCaptured as well - switch (operation.Instance.Kind) + flowCaptureOperations = new(); + } + foreach (var op in block.OperationsAndBranchValue.ToReversedExecutionOrder()) + { + var operation = op.Instance; + if (IFlowCaptureOperationWrapper.IsInstance(operation) + && flowCaptureOperations.TryGetValue(IFlowCaptureOperationWrapper.FromOperation(operation).Id, out var captureOperations)) { - case OperationKindEx.LocalReference: - ProcessParameterOrLocalReference(ILocalReferenceOperationWrapper.FromOperation(operation.Instance)); - break; - case OperationKindEx.ParameterReference: - ProcessParameterOrLocalReference(IParameterReferenceOperationWrapper.FromOperation(operation.Instance)); - break; - case OperationKindEx.SimpleAssignment: - ProcessSimpleAssignment(ISimpleAssignmentOperationWrapper.FromOperation(operation.Instance)); - break; - case OperationKindEx.FlowAnonymousFunction: - ProcessFlowAnonymousFunction(cfg, IFlowAnonymousFunctionOperationWrapper.FromOperation(operation.Instance)); - break; - case OperationKindEx.Invocation: - ProcessLocalFunction(cfg, IInvocationOperationWrapper.FromOperation(operation.Instance).TargetMethod); - break; - case OperationKindEx.MethodReference: - ProcessLocalFunction(cfg, IMethodReferenceOperationWrapper.FromOperation(operation.Instance).Method); - break; + foreach (var captureOpeation in captureOperations) + { + ProcessOperation(cfg, captureOpeation); + } + } + else if (IFlowCaptureReferenceOperationWrapper.IsInstance(operation) + && flowCaptureOperations.TryGetValue(IFlowCaptureReferenceOperationWrapper.FromOperation(operation).Id, out captureOperations)) + { + foreach (var captureOpeation in captureOperations) + { + ProcessOperation(cfg, captureOpeation); + } + + } + else + { + ProcessOperation(cfg, operation); } } } + private void ProcessOperation(ControlFlowGraph cfg, IOperation operation) + { + // Everything that is added to this switch needs to be considered inside ProcessCaptured as well + switch (operation.Kind) + { + case OperationKindEx.LocalReference: + ProcessParameterOrLocalReference(ILocalReferenceOperationWrapper.FromOperation(operation)); + break; + case OperationKindEx.ParameterReference: + ProcessParameterOrLocalReference(IParameterReferenceOperationWrapper.FromOperation(operation)); + break; + case OperationKindEx.SimpleAssignment: + ProcessSimpleAssignment(ISimpleAssignmentOperationWrapper.FromOperation(operation)); + break; + case OperationKindEx.FlowAnonymousFunction: + ProcessFlowAnonymousFunction(cfg, IFlowAnonymousFunctionOperationWrapper.FromOperation(operation)); + break; + case OperationKindEx.Invocation: + ProcessLocalFunction(cfg, IInvocationOperationWrapper.FromOperation(operation).TargetMethod); + break; + case OperationKindEx.MethodReference: + ProcessLocalFunction(cfg, IMethodReferenceOperationWrapper.FromOperation(operation).Method); + break; + } + } + private void ProcessParameterOrLocalReference(IOperationWrapper reference) { if (owner.ParameterOrLocalSymbol(reference.WrappedOperation) is { } symbol) diff --git a/analyzers/tests/SonarAnalyzer.Test/LiveVariableAnalysis/RoslynLiveVariableAnalysisTest.LocalFunction.cs b/analyzers/tests/SonarAnalyzer.Test/LiveVariableAnalysis/RoslynLiveVariableAnalysisTest.LocalFunction.cs index 80ee21068bc..8527ff36202 100644 --- a/analyzers/tests/SonarAnalyzer.Test/LiveVariableAnalysis/RoslynLiveVariableAnalysisTest.LocalFunction.cs +++ b/analyzers/tests/SonarAnalyzer.Test/LiveVariableAnalysis/RoslynLiveVariableAnalysisTest.LocalFunction.cs @@ -256,8 +256,8 @@ int LocalFunction(int arg) var context = CreateContextCS(code, "LocalFunction"); context.ValidateEntry(LiveIn("arg"), LiveOut("arg")); context.Validate("variable", LiveIn("arg"), LiveOut("arg")); - context.Validate("LocalFunction(arg - 1)", LiveIn("arg")); - context.Validate("0"); + context.Validate("LocalFunction(arg - 1)", LiveIn("arg"), LiveOut("arg")); + context.Validate("0", LiveIn("arg"), LiveOut("arg")); } [TestMethod] diff --git a/analyzers/tests/SonarAnalyzer.Test/LiveVariableAnalysis/RoslynLiveVariableAnalysisTest.cs b/analyzers/tests/SonarAnalyzer.Test/LiveVariableAnalysis/RoslynLiveVariableAnalysisTest.cs index 787c4c54c75..8069b41f81f 100644 --- a/analyzers/tests/SonarAnalyzer.Test/LiveVariableAnalysis/RoslynLiveVariableAnalysisTest.cs +++ b/analyzers/tests/SonarAnalyzer.Test/LiveVariableAnalysis/RoslynLiveVariableAnalysisTest.cs @@ -1021,8 +1021,8 @@ public void FlowCaptrure_NullCoalescingAssignment() var context = CreateContextCS(code, additionalParameters: "string stringParam"); context.ValidateEntry(LiveIn("stringParam"), LiveOut("stringParam")); context.Validate(context.Cfg.Blocks[1], null, LiveIn("stringParam"), LiveOut("stringParam")); - context.Validate(context.Cfg.Blocks[2], null, LiveIn("stringParam")); - context.Validate(context.Cfg.Blocks[3], null); + context.Validate(context.Cfg.Blocks[2], null, LiveIn("stringParam"), LiveOut("stringParam")); + context.Validate(context.Cfg.Blocks[3], null, LiveIn("stringParam")); context.ValidateExit(); } @@ -1034,10 +1034,10 @@ public void FlowCaptrure_NullCoalescingOperator() * #0 is null * / \ * / \ - * Block 3 Block 4 + * Block 2 Block 3 * #1=#0 #1="Hello" * \ / - * Block 5 + * Block 4 * s=#1 * | * Exit @@ -1048,8 +1048,7 @@ public void FlowCaptrure_NullCoalescingOperator() context.Validate(context.Cfg.Blocks[1], null, LiveIn("stringParam"), LiveOut("stringParam")); context.Validate(context.Cfg.Blocks[2], null, LiveIn("stringParam"), LiveOut("stringParam")); context.Validate(context.Cfg.Blocks[3], null, LiveIn("stringParam"), LiveOut("stringParam")); - context.Validate(context.Cfg.Blocks[4], null); - context.Validate(context.Cfg.Blocks[5], null, LiveIn("stringParam")); + context.Validate(context.Cfg.Blocks[4], null, LiveIn("stringParam")); context.ValidateExit(); } @@ -1057,25 +1056,26 @@ public void FlowCaptrure_NullCoalescingOperator() public void FlowCaptrure_NullConditionalOperators() { /* Block 1 - * #0=stringParam - * #0 is null + * #1=stringParam + * #1 is null * / \ + * F T * / \ * Block 2 Block 3 - * #1=Length #1="Hello" + * #0=Length #0=stringParam (DefaultValueOperation) * \ / * Block 4 * result=#1 * | * Exit */ - const string code = """int? anInt = stringParam.?Length;"""; + const string code = """int? anInt = stringParam?.Length;"""; var context = CreateContextCS(code, additionalParameters: "string stringParam"); context.ValidateEntry(LiveIn("stringParam"), LiveOut("stringParam")); context.Validate(context.Cfg.Blocks[1], null, LiveIn("stringParam"), LiveOut("stringParam")); - context.Validate(context.Cfg.Blocks[2], null); - context.Validate(context.Cfg.Blocks[3], null, LiveIn("stringParam"), LiveOut("stringParam")); - context.Validate(context.Cfg.Blocks[4], null, LiveIn("stringParam")); + context.Validate(context.Cfg.Blocks[2], null, LiveIn("stringParam")); + context.Validate(context.Cfg.Blocks[3], null); // FIXME: LiveIn("stringParam") and LiveOut("StringParam") do not appear here due to DefaultValueOperation + context.Validate(context.Cfg.Blocks[4], null); context.ValidateExit(); } @@ -1133,7 +1133,7 @@ public void FlowCaptrure_TernaryOperatorReuseCaptures() */ const string code = """ string s = boolParameter ? st : "Hello"; - string q = boolParameter2 ? st2 : "Hello"; + string q = boolParameter ? st2 : "Hello"; """; var context = CreateContextCS(code, additionalParameters: "string st, string st2"); context.ValidateEntry(LiveIn("boolParameter", "st", "st2"), LiveOut("boolParameter", "st", "st2")); @@ -1144,7 +1144,7 @@ public void FlowCaptrure_TernaryOperatorReuseCaptures() context.Validate(context.Cfg.Blocks[5], null, LiveIn("boolParameter", "st2"), LiveOut("st2")); context.Validate(context.Cfg.Blocks[6], null, LiveIn("st2"), LiveOut("st2")); context.Validate(context.Cfg.Blocks[7], null, LiveIn("st2"), LiveOut("st2")); - context.Validate(context.Cfg.Blocks[7], null, LiveIn("st2")); + context.Validate(context.Cfg.Blocks[8], null, LiveIn("st2")); context.ValidateExit(); } @@ -1155,24 +1155,26 @@ public void FlowCaptrure_NullCoalescingOperator_ConsequentCalls() * #0=s1 * #0 is null * / \ + * F T * / \ - * Block 2 Block 3 +--F--> Block 4 --------+ - * #1=#0 #2=s2 | #1=#2 | - * \ / | | - * Block 6 <-+ +--T--> Block 5-------->| - * s=#1 | #1="Hello" | - * | +-------------------------+ - * Exit + * Block 2 Block 3 -+---F--> Block 4 --------+ + * #1=#0 #2=s2 | #1=#2 | + * \ #2 is null | | + * \ / | | + * Block 6 <-+ +--T--> Block 5--------->| + * s=#1 | #1="Hello" | + * | +---------------------------+ + * Exit */ const string code = """string s = s1 ?? s2 ?? "Hello";"""; var context = CreateContextCS(code, additionalParameters: "string s1, string s2"); context.ValidateEntry(LiveIn("s1", "s2"), LiveOut("s1", "s2")); - context.Validate(context.Cfg.Blocks[1], null, LiveIn("s1", "s2"), LiveOut("s1, s2")); - context.Validate(context.Cfg.Blocks[2], null, LiveIn("s1", "s2"), LiveOut("s1")); - context.Validate(context.Cfg.Blocks[3], null, LiveIn("s1", "s2"), LiveOut("s2")); - context.Validate(context.Cfg.Blocks[4], null, LiveIn("s2"), LiveOut("s2")); - context.Validate(context.Cfg.Blocks[5], null, LiveIn("s2")); - context.Validate(context.Cfg.Blocks[6], null, LiveIn("s1, s2")); + context.Validate(context.Cfg.Blocks[1], null, LiveIn("s1", "s2"), LiveOut("s1", "s2")); + context.Validate(context.Cfg.Blocks[2], null, LiveIn("s1"), LiveOut("s1")); + context.Validate(context.Cfg.Blocks[3], null, LiveIn("s1", "s2"), LiveOut("s1", "s2")); + context.Validate(context.Cfg.Blocks[4], null, LiveIn("s1", "s2"), LiveOut("s1")); + context.Validate(context.Cfg.Blocks[5], null, LiveIn("s1"), LiveOut("s1")); + context.Validate(context.Cfg.Blocks[6], null, LiveIn("s1")); context.ValidateExit(); } @@ -1181,8 +1183,20 @@ public void FlowCaptrure_NullCoalescingOperator_ConsequentCalls_Assignment() { const string code = """string s = s1 ??= s2 = s3 ??= s4 ?? "End";"""; var context = CreateContextCS(code, additionalParameters: "string s1, string s2, string s3, string s4"); - context.ValidateEntry(LiveIn("s1", "s2"), LiveOut("s1", "s2")); - context.Validate(context.Cfg.Blocks[1], null); + context.ValidateEntry(LiveIn("s1", "s2", "s3", "s4"), LiveOut("s1", "s2", "s3", "s4")); + context.Validate(context.Cfg.Blocks[1], null, LiveIn("s1", "s2", "s3", "s4"), LiveOut("s1", "s2", "s3", "s4")); // 1: Parents: Entry, Children: 2 + context.Validate(context.Cfg.Blocks[2], null, LiveIn("s1", "s2", "s3", "s4"), LiveOut("s1", "s2", "s3", "s4")); // 2: Parents: 1, Children: 2 + context.Validate(context.Cfg.Blocks[3], null, LiveIn("s1"), LiveOut("s1")); // 3: Parents: 2, Children: 13 + context.Validate(context.Cfg.Blocks[4], null, LiveIn("s1", "s2", "s3", "s4"), LiveOut("s1", "s2", "s3", "s4")); // 4: Parents: 2, Children: 5 + context.Validate(context.Cfg.Blocks[5], null, LiveIn("s1", "s2", "s3", "s4"), LiveOut("s1", "s2", "s3", "s4")); // 5: Parents: 4, Children: 6 + context.Validate(context.Cfg.Blocks[6], null, LiveIn("s1", "s2", "s3", "s4"), LiveOut("s1", "s2", "s3", "s4")); // 6: Parents: 5, Children: 7, 8 + context.Validate(context.Cfg.Blocks[7], null, LiveIn("s1", "s2", "s3"), LiveOut("s1", "s2", "s3")); // 7: Parents: 6, Children: 12 + context.Validate(context.Cfg.Blocks[9], null, LiveIn("s1", "s2", "s3", "s4"), LiveOut("s1", "s2", "s3", "s4")); // 8: Parents: 6, Children: 9, 10 + context.Validate(context.Cfg.Blocks[9], null, LiveIn("s1", "s2", "s3", "s4"), LiveOut("s1", "s2", "s3", "s4")); // 9: Parents: 8, Children: 11 + context.Validate(context.Cfg.Blocks[10], null, LiveIn("s1", "s2", "s3", "s4"), LiveOut("s1", "s2", "s3", "s4")); // 10: Parents: 8, Children: 11 + context.Validate(context.Cfg.Blocks[11], null, LiveIn("s1", "s2", "s3", "s4"), LiveOut("s1", "s2", "s3")); // 11: Parents: 9, 10, Children: 12 + context.Validate(context.Cfg.Blocks[12], null, LiveIn("s1", "s2", "s3"), LiveOut("s1")); // 12: Parents: 7, 11, Children: 13 + context.Validate(context.Cfg.Blocks[13], null, LiveIn("s1")); // 13: Parents: 3, 12, Children: Exit context.ValidateExit(); } @@ -1210,7 +1224,7 @@ public void FlowCaptrure_NullCoalescingOperator_Nested() * | #1="Hello" #1=#2" * | | | * +------------>Block9<---------+---------+ - * s1=#1 + * s=#1 * | * Exit */ @@ -1218,14 +1232,14 @@ public void FlowCaptrure_NullCoalescingOperator_Nested() var context = CreateContextCS(code, additionalParameters: "string s1, string s2, string s3"); context.ValidateEntry(LiveIn("s1", "s2", "s3"), LiveOut("s1", "s2", "s3")); context.Validate(context.Cfg.Blocks[1], null, LiveIn("s1", "s2", "s3"), LiveOut("s1", "s2", "s3")); - context.Validate(context.Cfg.Blocks[2], null, LiveIn("s1", "s2", "s3"), LiveOut("s1")); - context.Validate(context.Cfg.Blocks[3], null, LiveIn("s1", "s2", "s3"), LiveOut("s3")); - context.Validate(context.Cfg.Blocks[4], null, LiveIn("s3"), LiveOut("s3")); - context.Validate(context.Cfg.Blocks[5], null, LiveIn("s3"), LiveOut("s3")); - context.Validate(context.Cfg.Blocks[6], null, LiveIn("s3"), LiveOut("s3")); - context.Validate(context.Cfg.Blocks[7], null, LiveIn("s3"), LiveOut("s3")); - context.Validate(context.Cfg.Blocks[8], null, LiveIn("s3")); - context.Validate(context.Cfg.Blocks[9], null, LiveIn("s1, s3")); + context.Validate(context.Cfg.Blocks[2], null, LiveIn("s1"), LiveOut("s1")); + context.Validate(context.Cfg.Blocks[3], null, LiveIn("s1", "s2", "s3"), LiveOut("s1", "s3")); + context.Validate(context.Cfg.Blocks[4], null, LiveIn("s1", "s3"), LiveOut("s1", "s3")); + context.Validate(context.Cfg.Blocks[5], null, LiveIn("s1", "s3"), LiveOut("s1", "s3")); + context.Validate(context.Cfg.Blocks[6], null, LiveIn("s1", "s3"), LiveOut("s1", "s3")); + context.Validate(context.Cfg.Blocks[7], null, LiveIn("s1", "s3"), LiveOut("s1")); + context.Validate(context.Cfg.Blocks[8], null, LiveIn("s1"), LiveOut("s1")); + context.Validate(context.Cfg.Blocks[9], null, LiveIn("s1")); context.ValidateExit(); } @@ -1251,11 +1265,11 @@ public void FlowCaptrure_NullCoalescingOperator_Overwrite() const string code = """s1 = (s1 = "overwrite") ?? "value";"""; var context = CreateContextCS(code, additionalParameters: "string s1"); context.ValidateEntry(LiveIn("s1"), LiveOut("s1")); - context.Validate(context.Cfg.Blocks[1], null, LiveIn("s1"), LiveOut("s1")); - context.Validate(context.Cfg.Blocks[2], null, LiveIn("s1"), LiveOut("s1")); - context.Validate(context.Cfg.Blocks[3], null); - context.Validate(context.Cfg.Blocks[4], null); - context.Validate(context.Cfg.Blocks[5], null); + context.Validate(context.Cfg.Blocks[1], null, LiveIn("s1")); + context.Validate(context.Cfg.Blocks[2], null); + context.Validate(context.Cfg.Blocks[3], null, LiveOut("s1")); + context.Validate(context.Cfg.Blocks[4], null, LiveOut("s1")); + context.Validate(context.Cfg.Blocks[5], null, LiveIn("s1")); context.ValidateExit(); } @@ -1271,15 +1285,15 @@ public void FlowCaptrure_SwitchStatement() }; """; var context = CreateContextCS(code, additionalParameters: "int i, string stringParam"); - context.ValidateEntry(LiveIn("i"), LiveOut("i")); - context.Validate(context.Cfg.Blocks[1], null, LiveIn("i"), LiveOut("i")); // if i == 0 - context.Validate(context.Cfg.Blocks[2], null, LiveIn("i"), LiveOut("i")); // #0 = stringParam - context.Validate(context.Cfg.Blocks[3], null, LiveIn("i"), LiveOut("i")); // if i == 1 - context.Validate(context.Cfg.Blocks[4], null, LiveIn("i"), LiveOut("i")); // #0 = "Something" - context.Validate(context.Cfg.Blocks[5], null, LiveIn("i"), LiveOut("i")); // if i != 0 && i != 1 - context.Validate(context.Cfg.Blocks[6], null, LiveIn("i"), LiveOut("i")); // #0 = "Everything" - context.Validate(context.Cfg.Blocks[7], null, LiveIn("i")); // to no destination block - context.Validate(context.Cfg.Blocks[8], null, LiveIn("i")); // result = #0 + context.ValidateEntry(LiveIn("i", "stringParam"), LiveOut("i", "stringParam")); + context.Validate(context.Cfg.Blocks[1], null, LiveIn("i", "stringParam"), LiveOut("i", "stringParam")); // if i == 0 + context.Validate(context.Cfg.Blocks[2], null, LiveIn("stringParam"), LiveOut("stringParam")); // #0 = stringParam + context.Validate(context.Cfg.Blocks[3], null, LiveIn("i", "stringParam"), LiveOut("i", "stringParam")); // if i == 1 + context.Validate(context.Cfg.Blocks[4], null, LiveIn("stringParam"), LiveOut("stringParam")); // #0 = "Something" + context.Validate(context.Cfg.Blocks[5], null, LiveIn("i", "stringParam"), LiveOut("stringParam")); // if i != 0 && i != 1 + context.Validate(context.Cfg.Blocks[6], null, LiveIn("stringParam"), LiveOut("stringParam")); // #0 = "Everything" + context.Validate(context.Cfg.Blocks[7], null); // to no destination block + context.Validate(context.Cfg.Blocks[8], null, LiveIn("stringParam")); // result = #0 context.ValidateExit(); } @@ -1294,16 +1308,26 @@ public void FlowCaptrure_ForEachCompundAssignment() } """; var context = CreateContextCS(code, additionalParameters: "System.Collections.Generic.List list"); - context.ValidateEntry(); + context.ValidateEntry(LiveIn("list"), LiveOut("list")); + context.Validate(context.Cfg.Blocks[1], null, LiveIn("list"), LiveOut("list", "sum")); + context.Validate(context.Cfg.Blocks[2], null, LiveIn("list", "sum"), LiveOut("sum")); + context.Validate(context.Cfg.Blocks[3], null, LiveIn("sum"), LiveOut("sum")); + context.Validate(context.Cfg.Blocks[4], null, LiveIn("sum"), LiveOut("sum")); + context.Validate(context.Cfg.Blocks[5], null); context.ValidateExit(); } [TestMethod] public void FlowCaptrure_ImplicDictionaryCreation() { - const string code = """Dictionary dict = new() { ["Key"] = 0, ["Lorem"] = 1, [key] = value }; """; - var context = CreateContextCS(code, additionalParameters: "List list, string key, int value"); - context.ValidateEntry(); + const string code = """System.Collections.Generic.Dictionary dict = new() { ["Key"] = 0, ["Lorem"] = 1, [key] = value }; """; + var context = CreateContextCS(code, additionalParameters: "string key, int value"); + context.ValidateEntry(LiveIn("key", "value"), LiveOut("key", "value")); + context.Validate(context.Cfg.Blocks[1], null, LiveIn("key", "value"), LiveOut("key", "value")); + context.Validate(context.Cfg.Blocks[2], null, LiveIn("key", "value"), LiveOut("key", "value")); + context.Validate(context.Cfg.Blocks[3], null, LiveIn("key", "value"), LiveOut("key", "value")); + context.Validate(context.Cfg.Blocks[4], null, LiveIn("key", "value")); + context.Validate(context.Cfg.Blocks[5], null); context.ValidateExit(); }