diff --git a/analyzers/src/SonarAnalyzer.CFG/LiveVariableAnalysis/RoslynLiveVariableAnalysis.cs b/analyzers/src/SonarAnalyzer.CFG/LiveVariableAnalysis/RoslynLiveVariableAnalysis.cs index 46148734a86..31c908410c2 100644 --- a/analyzers/src/SonarAnalyzer.CFG/LiveVariableAnalysis/RoslynLiveVariableAnalysis.cs +++ b/analyzers/src/SonarAnalyzer.CFG/LiveVariableAnalysis/RoslynLiveVariableAnalysis.cs @@ -24,6 +24,7 @@ namespace SonarAnalyzer.CFG.LiveVariableAnalysis; public sealed class RoslynLiveVariableAnalysis : LiveVariableAnalysisBase { + private readonly Dictionary> flowCaptureOperations = []; private readonly Dictionary> blockPredecessors = []; private readonly Dictionary> blockSuccessors = []; @@ -41,6 +42,7 @@ public RoslynLiveVariableAnalysis(ControlFlowGraph cfg, CancellationToken cancel { BuildBranches(block); } + ResolveCaptures(); Analyze(); } @@ -70,10 +72,45 @@ 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 ResolveCaptures() + { + foreach (var operation in Cfg.Blocks + .Where(x => x.EnclosingRegion.EnclosingRegionOrSelf(ControlFlowRegionKind.LocalLifetime) is not null) + .SelectMany(x => x.OperationsAndBranchValue) + .ToExecutionOrder()) + { + if (IFlowCaptureOperationWrapper.IsInstance(operation.Instance)) + { + var flowCapture = IFlowCaptureOperationWrapper.FromOperation(operation.Instance); + + if (IFlowCaptureReferenceOperationWrapper.IsInstance(flowCapture.Value) + && IFlowCaptureReferenceOperationWrapper.FromOperation(flowCapture.Value) is var captureReference + && flowCaptureOperations.TryGetValue(captureReference.Id, out var capturedOperations)) + { + AppendFlowCaptureReference(flowCapture.Id, capturedOperations.ToArray()); + } + else + { + AppendFlowCaptureReference(flowCapture.Id, flowCapture.Value); + } + } + } + + void AppendFlowCaptureReference(CaptureId id, params IOperation[] operations) + { + if (!flowCaptureOperations.TryGetValue(id, out var list)) + { + list = []; + flowCaptureOperations.Add(id, list); + } + list.AddRange(operations); + } + } + private void BuildBranches(BasicBlock block) { foreach (var successor in block.Successors) @@ -190,35 +227,53 @@ 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, Dictionary> flowCaptureOperations) { - foreach (var operation in block.OperationsAndBranchValue.ToReversedExecutionOrder()) + foreach (var operation in block.OperationsAndBranchValue.ToReversedExecutionOrder().Select(x => x.Instance)) { - // Everything that is added to this switch needs to be considered inside ProcessCaptured as well - switch (operation.Instance.Kind) + if ((IFlowCaptureOperationWrapper.IsInstance(operation) + && flowCaptureOperations.TryGetValue(IFlowCaptureOperationWrapper.FromOperation(operation).Id, out var captured)) + || (IFlowCaptureReferenceOperationWrapper.IsInstance(operation) + && flowCaptureOperations.TryGetValue(IFlowCaptureReferenceOperationWrapper.FromOperation(operation).Id, out captured))) + { + foreach (var capturedOperation in captured) + { + ProcessOperation(cfg, capturedOperation); + } + } + else { - 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; + 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) @@ -295,7 +350,7 @@ private void ProcessLocalFunction(ControlFlowGraph cfg, IMethodSymbol method) var localFunctionCfg = cfg.FindLocalFunctionCfgInScope(localFunction, owner.Cancel); foreach (var block in localFunctionCfg.Blocks.Reverse()) // Simplified approach, ignoring branching and try/catch/finally flows { - ProcessBlock(localFunctionCfg, block); + ProcessBlock(localFunctionCfg, block, []); } } } diff --git a/analyzers/tests/SonarAnalyzer.Test/LiveVariableAnalysis/RoslynLiveVariableAnalysisTest.FlowCaptureOperation.cs b/analyzers/tests/SonarAnalyzer.Test/LiveVariableAnalysis/RoslynLiveVariableAnalysisTest.FlowCaptureOperation.cs new file mode 100644 index 00000000000..13393477668 --- /dev/null +++ b/analyzers/tests/SonarAnalyzer.Test/LiveVariableAnalysis/RoslynLiveVariableAnalysisTest.FlowCaptureOperation.cs @@ -0,0 +1,372 @@ +/* + * SonarAnalyzer for .NET + * Copyright (C) 2015-2024 SonarSource SA + * mailto: contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +namespace SonarAnalyzer.Test.LiveVariableAnalysis; + +public partial class RoslynLiveVariableAnalysisTest +{ + [TestMethod] + public void FlowCaptrure_NullCoalescingAssignment() + { + /* Block 1 + * #0=param + * | + * Block 2 + * #1=#0 + * #1 is null --+ + * | | + * Block 3 | + * #0=Hello | + * | | + * Exit <-----+ + */ + const string code = """param ??= "Hello";"""; + var context = CreateContextCS(code, additionalParameters: "string param"); + context.ValidateEntry(LiveIn("param"), LiveOut("param")); + context.Validate(context.Cfg.Blocks[1], LiveIn("param"), LiveOut("param")); + context.Validate(context.Cfg.Blocks[2], LiveIn("param"), LiveOut("param")); + context.Validate(context.Cfg.Blocks[3], LiveIn("param")); + context.ValidateExit(); + } + + [TestMethod] + public void FlowCaptrure_NullCoalescingOperator() + { + /* Block 1 + * #0=param + * #0 is null + * / \ + * F T + * / \ + * Block 2 Block 3 + * #1=#0 #1="Hello" + * \ / + * Block 4 + * result=#1 + * | + * Exit + */ + const string code = """var result = param ?? "Hello";"""; + var context = CreateContextCS(code, additionalParameters: "string param"); + context.ValidateEntry(LiveIn("param"), LiveOut("param")); + context.Validate(context.Cfg.Blocks[1], LiveIn("param"), LiveOut("param")); + context.Validate(context.Cfg.Blocks[2], LiveIn("param"), LiveOut("param")); + context.Validate(context.Cfg.Blocks[3], LiveIn("param"), LiveOut("param")); + context.Validate(context.Cfg.Blocks[4], LiveIn("param")); + context.ValidateExit(); + } + + [TestMethod] + public void FlowCaptrure_ConditionalAccess() + { + /* Block 1 + * #1=param + * #1 is null + * / \ + * F T + * / \ + * Block 2 Block 3 + * #0=#1.Length #0=default + * \ / + * Block 4 + * result=#1 + * | + * Exit + */ + const string code = """var result = param?.Length;"""; + var context = CreateContextCS(code, additionalParameters: "string param"); + context.ValidateEntry(LiveIn("param"), LiveOut("param")); + context.Validate(context.Cfg.Blocks[1], LiveIn("param"), LiveOut("param")); + context.Validate(context.Cfg.Blocks[2], LiveIn("param")); + context.Validate(context.Cfg.Blocks[3]); + context.Validate(context.Cfg.Blocks[4]); + context.ValidateExit(); + } + + [TestMethod] + public void FlowCaptrure_Ternary() + { + /* Block 1 + * boolParameter + * / \ + * F T + * / \ + * / \ + * Block 2 Block 3 + * #0=StringParam #0="Hello" + * | | + * Block 4 <--------+ + * result=#0 + * | + * Exit + */ + const string code = """var result = boolParameter ? stringParam : "Hello";"""; + var context = CreateContextCS(code, additionalParameters: "string stringParam"); + context.ValidateEntry(LiveIn("boolParameter", "stringParam"), LiveOut("boolParameter", "stringParam")); + context.Validate(context.Cfg.Blocks[1], LiveIn("boolParameter", "stringParam"), LiveOut("stringParam")); + context.Validate(context.Cfg.Blocks[2], LiveIn("stringParam"), LiveOut("stringParam")); + context.Validate(context.Cfg.Blocks[3], LiveIn("stringParam"), LiveOut("stringParam")); + context.Validate(context.Cfg.Blocks[4], LiveIn("stringParam")); + context.ValidateExit(); + } + + [TestMethod] + public void FlowCaptrure_ReuseCaptures() + { + /* Block 1 + * boolParameter + * / \ + * F T + * / \ + * Block 2 Block 3 + * #0=st #0="Hello" + * \ / + * Block 4 + * result1=#0 + * | + * Block5 + * boolParameter + * / \ + * F T + * / \ + * Block 6 Block 7 + * #1=st2 #1="Hello" + * \ / + * Block 8 + * result2=#1 + * | + * Exit + */ + const string code = """ + var result1 = boolParameter ? s1 : "Hello"; + var result2 = boolParameter ? s2 : "Hi"; + """; + var context = CreateContextCS(code, additionalParameters: "string s1, string s2"); + context.ValidateEntry(LiveIn("boolParameter", "s1", "s2"), LiveOut("boolParameter", "s1", "s2")); + context.Validate(context.Cfg.Blocks[1], LiveIn("boolParameter", "s1", "s2"), LiveOut("boolParameter", "s1", "s2")); + context.Validate(context.Cfg.Blocks[2], LiveIn("boolParameter", "s1", "s2"), LiveOut("boolParameter", "s1", "s2")); + context.Validate(context.Cfg.Blocks[3], LiveIn("boolParameter", "s1", "s2"), LiveOut("boolParameter", "s1", "s2")); + context.Validate(context.Cfg.Blocks[4], LiveIn("boolParameter", "s1", "s2"), LiveOut("boolParameter", "s2")); + context.Validate(context.Cfg.Blocks[5], LiveIn("boolParameter", "s2"), LiveOut("s2")); + context.Validate(context.Cfg.Blocks[6], LiveIn("s2"), LiveOut("s2")); + context.Validate(context.Cfg.Blocks[7], LiveIn("s2"), LiveOut("s2")); + context.Validate(context.Cfg.Blocks[8], LiveIn("s2")); + context.ValidateExit(); + } + + [TestMethod] + public void FlowCaptrure_NullCoalescingOperator_ConsequentCalls() + { + /* Block 1 + * #0=s1 + * #0 is null + * / \ + * F T + * / \ + * / \ + * Block 2 Block 3 + * #1=#0 #2=s2 + * | #2 is null + * | / \ + * | F T + * | / \ + * | Block 4 Block 5 + * | #1=#2 #1="Hello" + * | |___________| + * | | + * | | + * +--------->Block 6 + * result=#1 + * | + * Exit + */ + const string code = """var result = 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], LiveIn("s1", "s2"), LiveOut("s1", "s2")); + context.Validate(context.Cfg.Blocks[2], LiveIn("s1", "s2"), LiveOut("s1", "s2")); + context.Validate(context.Cfg.Blocks[3], LiveIn("s1", "s2"), LiveOut("s1", "s2")); + context.Validate(context.Cfg.Blocks[4], LiveIn("s1", "s2"), LiveOut("s1", "s2")); + context.Validate(context.Cfg.Blocks[5], LiveIn("s1", "s2"), LiveOut("s1", "s2")); + context.Validate(context.Cfg.Blocks[6], LiveIn("s1", "s2")); + context.ValidateExit(); + } + + [TestMethod] + public void FlowCaptrure_NullCoalescingOperator_ConsequentCalls_Assignment() + { + const string code = """var result = s1 ??= s2 = s3 ??= s4 ?? "End";"""; + var context = CreateContextCS(code, additionalParameters: "string s1, string s2, string s3, string s4"); + context.ValidateEntry(LiveIn("s1", "s2", "s3", "s4"), LiveOut("s1", "s2", "s3", "s4")); + context.Validate(context.Cfg.Blocks[1], LiveIn("s1", "s2", "s3", "s4"), LiveOut("s1", "s2", "s3", "s4")); // 1: #0=s1 + context.Validate(context.Cfg.Blocks[2], LiveIn("s1", "s2", "s3", "s4"), LiveOut("s1", "s2", "s3", "s4")); // 2: #1=#0; if #1 is null + context.Validate(context.Cfg.Blocks[3], LiveIn("s1"), LiveOut("s1")); // 3: F: #2=#1 + context.Validate(context.Cfg.Blocks[4], LiveIn("s1", "s2", "s3", "s4"), LiveOut("s1", "s2", "s3", "s4")); // 4: T: #3=s2 + context.Validate(context.Cfg.Blocks[5], LiveIn("s1", "s2", "s3", "s4"), LiveOut("s1", "s2", "s3", "s4")); // 5: #4=s3 + context.Validate(context.Cfg.Blocks[6], LiveIn("s1", "s2", "s3", "s4"), LiveOut("s1", "s2", "s3", "s4")); // 6: #5=#4; if #5 is null + context.Validate(context.Cfg.Blocks[7], LiveIn("s1", "s2", "s3"), LiveOut("s1", "s2", "s3")); // 7: F: #6=#5 + context.Validate(context.Cfg.Blocks[8], LiveIn("s1", "s2", "s3", "s4"), LiveOut("s1", "s2", "s3", "s4")); // 8: T: #7=s4; if #7 is null + context.Validate(context.Cfg.Blocks[9], LiveIn("s1", "s2", "s3", "s4"), LiveOut("s1", "s2", "s3", "s4")); // 9: F: #8=#7 + context.Validate(context.Cfg.Blocks[10], LiveIn("s1", "s2", "s3", "s4"), LiveOut("s1", "s2", "s3", "s4")); // 10: #7=null; #8="End" + context.Validate(context.Cfg.Blocks[11], LiveIn("s1", "s2", "s3", "s4"), LiveOut("s1", "s2", "s3")); // 11: #6= (#4=#8) + context.Validate(context.Cfg.Blocks[12], LiveIn("s1", "s2", "s3"), LiveOut("s1")); // 12: #2= (#0 = (#3=#6) ) + context.Validate(context.Cfg.Blocks[13], LiveIn("s1")); // 13: result=#2 + context.ValidateExit(); + } + + [TestMethod] + public void FlowCaptrure_NullCoalescingOperator_Nested() + { + /* Block 1 + * #0=s1 + * #0 is null + * / \ + * F T + * / \ + * / \ + * Block 2 Block 3 + * #1=#0 s2 is null + * | / \ + * | T F + * | / \ + * | Block 4 Block 5 + * | #2=s3 #2="NestedFalse" + * | |___________| + * | | + * | | + * | Block 6 + * | #2 is null + * | / \ + * | F T + * | / \ + * | Block7 Block 8 + * | #1=#2 #1="Hello" + * | |___________| + * | | + * +---------->Block 9 + * result=#1 + * | + * Exit + */ + const string code = """var result = s1 ?? (s2 is null ? s3 : "NestedFalse") ?? "Hello";"""; + 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], LiveIn("s1", "s2", "s3"), LiveOut("s1", "s2", "s3")); + context.Validate(context.Cfg.Blocks[2], LiveIn("s1", "s3"), LiveOut("s1", "s3")); + context.Validate(context.Cfg.Blocks[3], LiveIn("s1", "s2", "s3"), LiveOut("s1", "s3")); + context.Validate(context.Cfg.Blocks[4], LiveIn("s1", "s3"), LiveOut("s1", "s3")); + context.Validate(context.Cfg.Blocks[5], LiveIn("s1", "s3"), LiveOut("s1", "s3")); + context.Validate(context.Cfg.Blocks[6], LiveIn("s1", "s3"), LiveOut("s1", "s3")); + context.Validate(context.Cfg.Blocks[7], LiveIn("s1", "s3"), LiveOut("s1", "s3")); + context.Validate(context.Cfg.Blocks[8], LiveIn("s1", "s3"), LiveOut("s1", "s3")); + context.Validate(context.Cfg.Blocks[9], LiveIn("s1", "s3")); + context.ValidateExit(); + } + + [TestMethod] + public void FlowCaptrure_NullCoalescingOperator_Overwrite() + { + /* Block 1 + * #0=s1 + * | + * Block 2 + * #1=(s1="overwrite") + * #1 is null + * / \ + * F T + * / \ + * Block3 Block4 + * #2=#1 #2="value" + * |___________| + * | + * Block5 + * s1=#2 + */ + 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], LiveIn("s1")); + context.Validate(context.Cfg.Blocks[2]); // This should have LiveIn("s1") and LiveOut("s1") but #1 gets as value all the assignment operation. + context.Validate(context.Cfg.Blocks[3], LiveOut("s1")); // This should have LiveIn("s1") + context.Validate(context.Cfg.Blocks[4], LiveOut("s1")); // This should have LiveIn("s1") + context.Validate(context.Cfg.Blocks[5], LiveIn("s1")); + context.ValidateExit(); + } + + [TestMethod] + public void FlowCaptrure_SwitchStatement() + { + const string code = """ + var result = i switch + { + 0 => param, + 1 => "Something", + _ => "Everything" + }; + """; + var context = CreateContextCS(code, additionalParameters: "int i, string param"); + context.ValidateEntry(LiveIn("i", "param"), LiveOut("i", "param")); + context.Validate(context.Cfg.Blocks[1], LiveIn("i", "param"), LiveOut("i", "param")); // #1 = i; if #1 == 0 + context.Validate(context.Cfg.Blocks[2], LiveIn("param"), LiveOut("param")); // #0 = param + context.Validate(context.Cfg.Blocks[3], LiveIn("i", "param"), LiveOut("i", "param")); // if #1 == 1 + context.Validate(context.Cfg.Blocks[4], LiveIn("param"), LiveOut("param")); // #0 = "Something" + context.Validate(context.Cfg.Blocks[5], LiveIn("i", "param"), LiveOut("param")); // if discard + context.Validate(context.Cfg.Blocks[6], LiveIn("param"), LiveOut("param")); // #0 = "Everything" + context.Validate(context.Cfg.Blocks[7]); // else, unreachable, throws + context.Validate(context.Cfg.Blocks[8], LiveIn("param")); // result = #0 + context.ValidateExit(); + } + + [TestMethod] + public void FlowCaptrure_ForEachCompundAssignment() + { + const string code = """ + int sum = 0; + foreach (var i in array) + { + sum += i; + } + """; + var context = CreateContextCS(code, additionalParameters: "int[] array"); + context.ValidateEntry(LiveIn("array"), LiveOut("array")); + context.Validate(context.Cfg.Blocks[1], LiveIn("array"), LiveOut("array", "sum")); // sum = 0 + context.Validate(context.Cfg.Blocks[2], LiveIn("array", "sum"), LiveOut("sum")); // #0 = array + context.Validate(context.Cfg.Blocks[3], LiveIn("sum"), LiveOut("sum")); // If IEnumerator.MoveNext + context.Validate(context.Cfg.Blocks[4], LiveIn("sum"), LiveOut("sum")); // sum += i + context.Validate(context.Cfg.Blocks[5]); // Finally Region: #1=#0; if #1 is null, should have LiveIn/Liveout: array + context.Validate(context.Cfg.Blocks[6]); // Finally Region: #1.Dispose, should have LiveIn: array + context.Validate(context.Cfg.Blocks[7]); // Finally Region: Empty end of finally + context.ValidateExit(); + } + + [TestMethod] + public void FlowCaptrure_ImplicDictionaryCreation() + { + const string code = """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], LiveIn("key", "value"), LiveOut("key", "value")); + context.Validate(context.Cfg.Blocks[2], LiveIn("key", "value"), LiveOut("key", "value")); + context.Validate(context.Cfg.Blocks[3], LiveIn("key", "value"), LiveOut("key", "value")); + context.Validate(context.Cfg.Blocks[4], LiveIn("key", "value")); + context.Validate(context.Cfg.Blocks[5]); + context.ValidateExit(); + } +} 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.TryCatchFinally.cs b/analyzers/tests/SonarAnalyzer.Test/LiveVariableAnalysis/RoslynLiveVariableAnalysisTest.TryCatchFinally.cs index 7fc945daedf..8f30ab776c5 100644 --- a/analyzers/tests/SonarAnalyzer.Test/LiveVariableAnalysis/RoslynLiveVariableAnalysisTest.TryCatchFinally.cs +++ b/analyzers/tests/SonarAnalyzer.Test/LiveVariableAnalysis/RoslynLiveVariableAnalysisTest.TryCatchFinally.cs @@ -59,7 +59,7 @@ public void Using_LiveInUntilTheEnd(string usingStatement, string suffix) // Finally region context.Validate("ms = new MemoryStream()", LiveIn("ms"), LiveOut("ms")); // Null check context.Validate("ms = new MemoryStream()", LiveIn("ms")); // Actual Dispose - context.Validate(context.Cfg.Blocks[6], null); + context.Validate(context.Cfg.Blocks[6]); } [TestMethod] @@ -819,7 +819,7 @@ public void Finally_WithThrowStatementInTry_LiveOut_CatchAll(string catchAll) {{catchAll}} { Method(2); - variable = 0; + variable = 0; } finally { diff --git a/analyzers/tests/SonarAnalyzer.Test/LiveVariableAnalysis/RoslynLiveVariableAnalysisTest.cs b/analyzers/tests/SonarAnalyzer.Test/LiveVariableAnalysis/RoslynLiveVariableAnalysisTest.cs index 86878d58fa6..d0b2b22c4f6 100644 --- a/analyzers/tests/SonarAnalyzer.Test/LiveVariableAnalysis/RoslynLiveVariableAnalysisTest.cs +++ b/analyzers/tests/SonarAnalyzer.Test/LiveVariableAnalysis/RoslynLiveVariableAnalysisTest.cs @@ -809,8 +809,8 @@ public void ProcessVariableInForeach_Declared_LiveIn_LiveOut() } """; var context = CreateContextCS(code); - context.Validate(context.Cfg.Blocks[1], null, LiveIn("boolParameter", "intParameter"), LiveOut("boolParameter", "intParameter")); - context.Validate(context.Cfg.Blocks[2], null, LiveIn("boolParameter", "intParameter"), LiveOut("boolParameter", "intParameter")); + context.Validate(context.Cfg.Blocks[1], LiveIn("boolParameter", "intParameter"), LiveOut("boolParameter", "intParameter")); + context.Validate(context.Cfg.Blocks[2], LiveIn("boolParameter", "intParameter"), LiveOut("boolParameter", "intParameter")); context.Validate("Method(i, intParameter);", LiveIn("boolParameter", "intParameter"), LiveOut("boolParameter", "intParameter", "i")); context.Validate("Method(i);", LiveIn("boolParameter", "intParameter", "i"), LiveOut("boolParameter", "intParameter")); context.ValidateExit(); @@ -1005,6 +1005,7 @@ private static Context CreateContextCS(string methodBody, string localFunctionNa additionalParameters = additionalParameters is null ? null : ", " + additionalParameters; var code = $$""" using System; + using System.Collections.Generic; using System.IO; using System.Linq; public class Sample @@ -1118,6 +1119,9 @@ public void Validate(string withSyntax, params Expected[] expected) Validate(block, withSyntax, expected); } + public void Validate(BasicBlock block, params Expected[] expected) => + Validate(block, null, expected); + public void Validate(BasicBlock block, string blockSuffix, params Expected[] expected) { var empty = new Expected([], ExpectedKind.None);