diff --git a/analyzers/src/SonarAnalyzer.CFG/LiveVariableAnalysis/RoslynLiveVariableAnalysis.cs b/analyzers/src/SonarAnalyzer.CFG/LiveVariableAnalysis/RoslynLiveVariableAnalysis.cs index 9eeaa5e3789..3300ea55669 100644 --- a/analyzers/src/SonarAnalyzer.CFG/LiveVariableAnalysis/RoslynLiveVariableAnalysis.cs +++ b/analyzers/src/SonarAnalyzer.CFG/LiveVariableAnalysis/RoslynLiveVariableAnalysis.cs @@ -138,6 +138,10 @@ private void BuildBranches(BasicBlock block) AddPredecessorsOutsideRegion(finallyBlock); } } + if (block.IsEnclosedIn(ControlFlowRegionKind.Catch) && block.Successors.Any(x => x.Semantics == ControlFlowBranchSemantics.Rethrow)) + { + BuildBranchesNestedCatchRethrow(block); + } void AddPredecessorsOutsideRegion(BasicBlock destination) { @@ -161,6 +165,15 @@ private void BuildBranchesFinally(BasicBlock source, ControlFlowRegion finallyRe } } + private void BuildBranchesNestedCatchRethrow(BasicBlock block) + { + var reachableHandlers = block.EnclosingRegion(ControlFlowRegionKind.TryAndCatch).NestedRegion(ControlFlowRegionKind.Try).ReachableHandlers(); + foreach (var catchBlock in reachableHandlers.Where(x => x.Kind == ControlFlowRegionKind.Catch && x.FirstBlockOrdinal > block.Ordinal).SelectMany(x => x.Blocks(Cfg))) + { + AddBranch(block, catchBlock); + } + } + private void AddBranch(BasicBlock source, BasicBlock destination) { blockSuccessors[source.Ordinal].Add(destination); diff --git a/analyzers/tests/SonarAnalyzer.Test/LiveVariableAnalysis/RoslynLiveVariableAnalysisTest.TryCatchFinally.cs b/analyzers/tests/SonarAnalyzer.Test/LiveVariableAnalysis/RoslynLiveVariableAnalysisTest.TryCatchFinally.cs index 8f30ab776c5..03bc3450350 100644 --- a/analyzers/tests/SonarAnalyzer.Test/LiveVariableAnalysis/RoslynLiveVariableAnalysisTest.TryCatchFinally.cs +++ b/analyzers/tests/SonarAnalyzer.Test/LiveVariableAnalysis/RoslynLiveVariableAnalysisTest.TryCatchFinally.cs @@ -603,6 +603,124 @@ public void Catch_Loop_Propagates_LiveIn() context.Validate("Method(5);"); } + [TestMethod] + public void Throw_NestedCatch_LiveOut() + { + var code = """ + var value = 100; + try + { + try + { + Method(value); + Method(0); + } + catch + { + value = 200; + throw; + } + } + catch + { + Method(value); + Method(1); + } + """; + var context = CreateContextCS(code); + context.ValidateEntry(); + context.Validate("Method(0);", LiveIn("value"), LiveOut("value")); + context.Validate("value = 200;", LiveOut("value")); + context.Validate("Method(1);", LiveIn("value")); + context.ValidateExit(); + } + + [TestMethod] + public void Throw_NestedCatch_LiveInInConsecutiveOuterCatch() + { + var code = """ + var value = 100; + try + { + try + { + Method(value); + Method(0); + } + catch + { + value = 200; + throw; + } + } + catch (ArgumentNullException) + { + Method(value); + Method(1); + } + catch (IOException) + { + Method(value); + Method(2); + } + catch (NullReferenceException) + { + Method(value); + Method(3); + } + """; + var context = CreateContextCS(code); + context.ValidateEntry(); + context.Validate("Method(0);", LiveIn("value"), LiveOut("value")); + context.Validate("value = 200;", LiveOut("value")); + context.Validate("Method(1);", LiveIn("value")); + context.Validate("Method(2);", LiveIn("value")); + context.Validate("Method(3);", LiveIn("value")); + context.ValidateExit(); + } + + [TestMethod] + public void Throw_NestedCatch_OuterCatchRethrows_LiveOutOuterCathc() + { + var code = """ + var value = 100; + try + { + try + { + try + { + Method(value); + Method(0); + } + catch + { + value = 200; + throw; + } + } + catch // Outer catch + { + Method(value); + Method(1); + throw; + } + } + catch + { + Method(value); + Method(2); + } + """; + var context = CreateContextCS(code); + context.ValidateEntry(); + context.Validate("Method(0);", LiveIn("value"), LiveOut("value")); + context.Validate("value = 200;", LiveOut("value")); + context.Validate("Method(1);", LiveIn("value"), LiveOut("value")); + context.Validate("Method(2);", LiveIn("value")); + context.ValidateExit(); + } + [TestMethod] public void Finally_LiveIn() { diff --git a/analyzers/tests/SonarAnalyzer.Test/TestCases/DeadStores.RoslynCfg.cs b/analyzers/tests/SonarAnalyzer.Test/TestCases/DeadStores.RoslynCfg.cs index f3c6ad1d180..f1e8a893f80 100644 --- a/analyzers/tests/SonarAnalyzer.Test/TestCases/DeadStores.RoslynCfg.cs +++ b/analyzers/tests/SonarAnalyzer.Test/TestCases/DeadStores.RoslynCfg.cs @@ -1612,7 +1612,7 @@ public void NestedCatchAndRethrow() } catch { - value = 200; // Noncompliant FP + value = 200; // Compliant, catch rethrows and moves to the next catch. throw; } }