diff --git a/analyzers/src/SonarAnalyzer.CFG/Roslyn/CfgAllPathValidator.cs b/analyzers/src/SonarAnalyzer.CFG/Roslyn/CfgAllPathValidator.cs index 8f630489642..df4afc7323f 100644 --- a/analyzers/src/SonarAnalyzer.CFG/Roslyn/CfgAllPathValidator.cs +++ b/analyzers/src/SonarAnalyzer.CFG/Roslyn/CfgAllPathValidator.cs @@ -23,7 +23,6 @@ namespace SonarAnalyzer.CFG.Roslyn public abstract class CfgAllPathValidator { private readonly ControlFlowGraph cfg; - private readonly Dictionary visitedStatus = new Dictionary(); protected abstract bool IsValid(BasicBlock block); protected abstract bool IsInvalid(BasicBlock block); @@ -31,21 +30,36 @@ public abstract class CfgAllPathValidator protected CfgAllPathValidator(ControlFlowGraph cfg) => this.cfg = cfg; - public bool CheckAllPaths() => - IsBlockOrAllSuccessorsValid(cfg.EntryBlock); - - private bool IsBlockOrAllSuccessorsValid(BasicBlock block) - { - var isValid = !IsInvalid(block) && (IsValid(block) || AreAllSuccessorsValid(block)); - visitedStatus[block] = isValid; - return isValid; - } - - private bool AreAllSuccessorsValid(BasicBlock block) + public bool CheckAllPaths() { - visitedStatus[block] = true; // protects from loops, don't fail the computation if hits itself - return block.SuccessorBlocks.Any() - && block.SuccessorBlocks.All(x => x != cfg.ExitBlock && (visitedStatus.ContainsKey(x) ? visitedStatus[x] : IsBlockOrAllSuccessorsValid(x))); + HashSet visited = []; + var blocks = new Stack(); + blocks.Push(cfg.EntryBlock); + while (blocks.Count > 0) + { + var block = blocks.Pop(); + if (!visited.Add(block)) + { + continue; // We already visited this block. (This protects from endless loops) + } + if (block == cfg.ExitBlock || IsInvalid(block)) + { + return false; + } + if (IsValid(block)) + { + continue; + } + if (block.SuccessorBlocks.IsEmpty) + { + return false; + } + foreach (var successorBlock in block.SuccessorBlocks) + { + blocks.Push(successorBlock); + } + } + return true; } } } diff --git a/analyzers/tests/SonarAnalyzer.Test/Rules/InfiniteRecursionTest.cs b/analyzers/tests/SonarAnalyzer.Test/Rules/InfiniteRecursionTest.cs index d863efe718a..cda3e3fa908 100644 --- a/analyzers/tests/SonarAnalyzer.Test/Rules/InfiniteRecursionTest.cs +++ b/analyzers/tests/SonarAnalyzer.Test/Rules/InfiniteRecursionTest.cs @@ -18,6 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +using System.Text; using SonarAnalyzer.Rules.CSharp; namespace SonarAnalyzer.Test.Rules @@ -60,5 +61,100 @@ public void InfiniteRecursion_CSharp11() => #endif + // https://github.com/SonarSource/sonar-dotnet/issues/8977 + [TestMethod] + public void InfiniteRecursion_RoslynCfg_8977() + { + const int rows = 4_000; + var code = new StringBuilder(); + code.Append(""" + using UInt32Value = System.UInt32; + using StringValue = System.String; + + public class WorksheetPart + { + public Worksheet Worksheet { get; set; } + } + public class Worksheet + { + public MarkupCompatibilityAttributes MCAttributes { get; set; } + public void AddNamespaceDeclaration(string alias, string xmlNamespace) { } + public void Append(SheetData sheetData1) { } + } + public class SheetData + { + public void Append(Row r) { } + } + public class MarkupCompatibilityAttributes + { + public string Ignorable { get; set; } + } + public class Row + { + public UInt32Value RowIndex { get; set; } + public ListValue Spans { get; set; } + public double DyDescent { get; set; } + public void Append(Cell c) { } + } + public class ListValue + { + public string InnerText { get; set; } + } + public class Cell + { + public string CellReference { get; set; } + public UInt32Value StyleIndex { get; set; } + public void Append(CellValue c) { } + } + public class CellValue + { + public string Text { get; set; } + } + + class Program + { + public static void Main() { } + + void GenerateWorksheetPart1Content(WorksheetPart worksheetPart1) + { + Worksheet worksheet1 = new Worksheet() { MCAttributes = new MarkupCompatibilityAttributes() { Ignorable = "x14ac xr xr2 xr3" } }; + worksheet1.AddNamespaceDeclaration("r", "http://schemas.openxmlformats.org/officeDocument/2006/relationships"); + worksheet1.AddNamespaceDeclaration("mc", "http://schemas.openxmlformats.org/markup-compatibility/2006"); + worksheet1.AddNamespaceDeclaration("x14ac", "http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac"); + worksheet1.AddNamespaceDeclaration("xr", "http://schemas.microsoft.com/office/spreadsheetml/2014/revision"); + worksheet1.AddNamespaceDeclaration("xr2", "http://schemas.microsoft.com/office/spreadsheetml/2015/revision2"); + worksheet1.AddNamespaceDeclaration("xr3", "http://schemas.microsoft.com/office/spreadsheetml/2016/revision3"); + + SheetData sheetData1 = new SheetData(); + """); + for (var i = 1; i <= rows; i++) + { + code.Append($$""" + Row row{{i}} = new Row() { RowIndex = (UInt32Value)1U, Spans = new ListValue() { InnerText = "1:1" }, DyDescent = 0.25D }; + + Cell cell{{i}} = new Cell() { CellReference = "A{{i}}", StyleIndex = (UInt32Value)1U }; + CellValue cellValue{{i}} = new CellValue(); + cellValue{{i}}.Text = "{{i}}"; + + cell{{i}}.Append(cellValue{{i}}); + + row{{i}}.Append(cell{{i}}); + """); + } + for (var i = 1; i <= rows; i++) + { + code.AppendLine($$""" sheetData1.Append(row{{i}});"""); + } + code.Append("""" + worksheet1.Append(sheetData1); + worksheetPart1.Worksheet = worksheet1; + } + } + """"); + + roslynCfg.AddSnippet(code.ToString()) + .WithOptions(ParseOptionsHelper.FromCSharp8) + .Verify(); + } } }