diff --git a/analyzers/src/SonarAnalyzer.CFG/CfgSerializer/CfgSerializer.RoslynCfgWalker.cs b/analyzers/src/SonarAnalyzer.CFG/CfgSerializer/CfgSerializer.RoslynCfgWalker.cs index d808be525f7..1773699cf2b 100644 --- a/analyzers/src/SonarAnalyzer.CFG/CfgSerializer/CfgSerializer.RoslynCfgWalker.cs +++ b/analyzers/src/SonarAnalyzer.CFG/CfgSerializer/CfgSerializer.RoslynCfgWalker.cs @@ -21,161 +21,160 @@ using System.Text; using SonarAnalyzer.CFG.Roslyn; -namespace SonarAnalyzer.CFG +namespace SonarAnalyzer.CFG; + +public static partial class CfgSerializer { - public partial class CfgSerializer + private class RoslynCfgWalker { - private class RoslynCfgWalker + protected readonly DotWriter writer; + private readonly HashSet visited = []; + private readonly RoslynCfgIdProvider cfgIdProvider; + private readonly int cfgId; + + public RoslynCfgWalker(DotWriter writer, RoslynCfgIdProvider cfgIdProvider) { - private readonly DotWriter writer; - private readonly HashSet visited = new(); - private readonly RoslynCfgIdProvider cfgIdProvider; - private readonly int cfgId; + this.writer = writer; + this.cfgIdProvider = cfgIdProvider; + cfgId = cfgIdProvider.Next(); + } - public RoslynCfgWalker(DotWriter writer, RoslynCfgIdProvider cfgIdProvider) + public void Visit(ControlFlowGraph cfg, string title) + { + writer.WriteGraphStart(title); + VisitContent(cfg, title); + writer.WriteGraphEnd(); + } + + protected virtual void WriteEdges(BasicBlock block) + { + foreach (var predecessor in block.Predecessors) { - this.writer = writer; - this.cfgIdProvider = cfgIdProvider; - cfgId = cfgIdProvider.Next(); + var condition = string.Empty; + if (predecessor.Source.ConditionKind != ControlFlowConditionKind.None) + { + condition = predecessor == predecessor.Source.ConditionalSuccessor ? predecessor.Source.ConditionKind.ToString() : "Else"; + } + var semantics = predecessor.Semantics == ControlFlowBranchSemantics.Regular ? null : predecessor.Semantics.ToString(); + writer.WriteEdge(BlockId(predecessor.Source), BlockId(block), $"{semantics} {condition}".Trim()); } - - public void Visit(ControlFlowGraph cfg, string title) + if (block.FallThroughSuccessor is { Destination: null }) { - writer.WriteGraphStart(title); - VisitContent(cfg, title); - writer.WriteGraphEnd(); + writer.WriteEdge(BlockId(block), "NoDestination_" + BlockId(block), block.FallThroughSuccessor.Semantics.ToString()); } + } + + protected string BlockId(BasicBlock block) => + $"cfg{cfgId}_block{block.Ordinal}"; + + private void VisitSubGraph(ControlFlowGraph cfg, string title) + { + writer.WriteSubGraphStart(cfgIdProvider.Next(), title); + VisitContent(cfg, title); + writer.WriteSubGraphEnd(); + } - private void VisitSubGraph(ControlFlowGraph cfg, string title) + private void VisitContent(ControlFlowGraph cfg, string titlePrefix) + { + foreach (var region in cfg.Root.NestedRegions) { - writer.WriteSubGraphStart(cfgIdProvider.Next(), title); - VisitContent(cfg, title); - writer.WriteSubGraphEnd(); + Visit(cfg, region); } - - private void VisitContent(ControlFlowGraph cfg, string titlePrefix) + foreach (var block in cfg.Blocks.Where(x => !visited.Contains(x)).ToArray()) { - foreach (var region in cfg.Root.NestedRegions) - { - Visit(cfg, region); - } - foreach (var block in cfg.Blocks.Where(x => !visited.Contains(x)).ToArray()) - { - Visit(block); - } - foreach (var localFunction in cfg.LocalFunctions) - { - var localFunctionCfg = cfg.GetLocalFunctionControlFlowGraph(localFunction, default); - new RoslynCfgWalker(writer, cfgIdProvider).VisitSubGraph(localFunctionCfg, $"{titlePrefix}.{localFunction.Name}"); - } - foreach (var anonymousFunction in AnonymousFunctions(cfg)) - { - var anonymousFunctionCfg = cfg.GetAnonymousFunctionControlFlowGraph(anonymousFunction, default); - new RoslynCfgWalker(writer, cfgIdProvider).VisitSubGraph(anonymousFunctionCfg, $"{titlePrefix}.anonymous"); - } + Visit(block); } - - private void Visit(ControlFlowGraph cfg, ControlFlowRegion region) + foreach (var localFunction in cfg.LocalFunctions) { - writer.WriteSubGraphStart(cfgIdProvider.Next(), SerializeRegion(region)); - foreach (var nested in region.NestedRegions) - { - Visit(cfg, nested); - } - foreach (var block in cfg.Blocks.Where(x => x.EnclosingRegion == region)) - { - Visit(block); - } - writer.WriteSubGraphEnd(); + var localFunctionCfg = cfg.GetLocalFunctionControlFlowGraph(localFunction, default); + new RoslynCfgWalker(writer, cfgIdProvider).VisitSubGraph(localFunctionCfg, $"{titlePrefix}.{localFunction.Name}"); } - - private void Visit(BasicBlock block) + foreach (var anonymousFunction in AnonymousFunctions(cfg)) { - visited.Add(block); - WriteNode(block); - WriteEdges(block); + var anonymousFunctionCfg = cfg.GetAnonymousFunctionControlFlowGraph(anonymousFunction, default); + new RoslynCfgWalker(writer, cfgIdProvider).VisitSubGraph(anonymousFunctionCfg, $"{titlePrefix}.anonymous"); } + } - private void WriteNode(BasicBlock block) + private void Visit(ControlFlowGraph cfg, ControlFlowRegion region) + { + writer.WriteSubGraphStart(cfgIdProvider.Next(), SerializeRegion(region)); + foreach (var nested in region.NestedRegions) { - var header = block.Kind.ToString().ToUpperInvariant() + " #" + block.Ordinal; - writer.WriteNode(BlockId(block), header, block.Operations.SelectMany(SerializeOperation).Concat(SerializeBranchValue(block.BranchValue)).ToArray()); + Visit(cfg, nested); } + foreach (var block in cfg.Blocks.Where(x => x.EnclosingRegion == region)) + { + Visit(block); + } + writer.WriteSubGraphEnd(); + } - private static IEnumerable SerializeBranchValue(IOperation operation) => - operation == null ? Enumerable.Empty() : new[] { "## BranchValue ##" }.Concat(SerializeOperation(operation)); + private void Visit(BasicBlock block) + { + visited.Add(block); + WriteNode(block); + WriteEdges(block); + } - private static IEnumerable SerializeOperation(IOperation operation) => - SerializeOperation(0, null, operation).Concat(new[] { "##########" }); + private void WriteNode(BasicBlock block) + { + var header = block.Kind.ToString().ToUpperInvariant() + " #" + block.Ordinal; + writer.WriteNode(BlockId(block), header, block.Operations.SelectMany(SerializeOperation).Concat(SerializeBranchValue(block.BranchValue)).ToArray()); + } + + private static IEnumerable SerializeBranchValue(IOperation operation) => + operation is null ? [] : new[] { "## BranchValue ##" }.Concat(SerializeOperation(operation)); - private static IEnumerable SerializeOperation(int level, string prefix, IOperation operation) + private static IEnumerable SerializeOperation(IOperation operation) => + SerializeOperation(0, null, operation).Concat(new[] { "##########" }); + + private static IEnumerable SerializeOperation(int level, string prefix, IOperation operation) + { + var prefixes = operation.GetType().GetProperties() + .GroupBy(x => x.GetValue(operation) as IOperation, x => x.Name) + .Where(x => x.Key is not null) + .ToDictionary(x => x.Key, x => string.Join(", ", x)); + var ret = new List { $"{level}#: {prefix}{operation.Serialize()}" }; + foreach (var child in operation.ToSonar().Children) { - var prefixes = operation.GetType().GetProperties() - .GroupBy(x => x.GetValue(operation) as IOperation, pi => pi.Name) - .Where(x => x.Key is not null) - .ToDictionary(x => x.Key, x => string.Join(", ", x)); - var ret = new List { $"{level}#: {prefix}{operation.Serialize()}" }; - foreach (var child in operation.ToSonar().Children) - { - ret.AddRange(SerializeOperation(level + 1, prefixes.TryGetValue(child, out var childPrefix) ? $"{level}#.{childPrefix}: " : null, child)); - } - return ret; + ret.AddRange(SerializeOperation(level + 1, prefixes.TryGetValue(child, out var childPrefix) ? $"{level}#.{childPrefix}: " : null, child)); } + return ret; + } - private static string SerializeRegion(ControlFlowRegion region) + private static string SerializeRegion(ControlFlowRegion region) + { + var sb = new StringBuilder(); + sb.Append(region.Kind.ToString()).Append(" region"); + if (region.ExceptionType is not null) { - var sb = new StringBuilder(); - sb.Append(region.Kind.ToString()).Append(" region"); - if (region.ExceptionType is not null) - { - sb.Append(": ").Append(region.ExceptionType); - } - if (region.Locals.Any()) - { - sb.Append(", Locals: ").Append(string.Join(", ", region.Locals.Select(x => x.Name ?? "N/A"))); - } - if (region.CaptureIds.Any()) - { - sb.Append(", Captures: ").Append(string.Join(", ", region.CaptureIds.Select(x => x.Serialize()))); // Same as IOperationExtension.SerializeSuffix - } - return sb.ToString(); + sb.Append(": ").Append(region.ExceptionType); } - - private void WriteEdges(BasicBlock block) + if (region.Locals.Any()) { - foreach (var predecessor in block.Predecessors) - { - var condition = string.Empty; - if (predecessor.Source.ConditionKind != ControlFlowConditionKind.None) - { - condition = predecessor == predecessor.Source.ConditionalSuccessor ? predecessor.Source.ConditionKind.ToString() : "Else"; - } - var semantics = predecessor.Semantics == ControlFlowBranchSemantics.Regular ? null : predecessor.Semantics.ToString(); - writer.WriteEdge(BlockId(predecessor.Source), BlockId(block), $"{semantics} {condition}".Trim()); - } - if (block.FallThroughSuccessor is { Destination: null }) - { - writer.WriteEdge(BlockId(block), "NoDestination_" + BlockId(block), block.FallThroughSuccessor.Semantics.ToString()); - } + sb.Append(", Locals: ").Append(string.Join(", ", region.Locals.Select(x => x.Name ?? "N/A"))); } - - private string BlockId(BasicBlock block) => - $"cfg{cfgId}_block{block.Ordinal}"; - - private static IEnumerable AnonymousFunctions(ControlFlowGraph cfg) => - cfg.Blocks - .SelectMany(x => x.Operations) - .Concat(cfg.Blocks.Select(x => x.BranchValue).Where(x => x != null)) - .SelectMany(x => x.DescendantsAndSelf()) - .Where(IFlowAnonymousFunctionOperationWrapper.IsInstance) - .Select(IFlowAnonymousFunctionOperationWrapper.FromOperation); + if (region.CaptureIds.Any()) + { + sb.Append(", Captures: ").Append(string.Join(", ", region.CaptureIds.Select(x => x.Serialize()))); // Same as IOperationExtension.SerializeSuffix + } + return sb.ToString(); } - private class RoslynCfgIdProvider - { - private int value; + private static IEnumerable AnonymousFunctions(ControlFlowGraph cfg) => + cfg.Blocks + .SelectMany(x => x.Operations) + .Concat(cfg.Blocks.Select(x => x.BranchValue).Where(x => x is not null)) + .SelectMany(x => x.DescendantsAndSelf()) + .Where(IFlowAnonymousFunctionOperationWrapper.IsInstance) + .Select(IFlowAnonymousFunctionOperationWrapper.FromOperation); + } - public int Next() => value++; - } + private sealed class RoslynCfgIdProvider + { + private int value; + + public int Next() => value++; } } diff --git a/analyzers/src/SonarAnalyzer.CFG/CfgSerializer/CfgSerializer.RoslynLvaWalker.cs b/analyzers/src/SonarAnalyzer.CFG/CfgSerializer/CfgSerializer.RoslynLvaWalker.cs new file mode 100644 index 00000000000..aaf4765cb41 --- /dev/null +++ b/analyzers/src/SonarAnalyzer.CFG/CfgSerializer/CfgSerializer.RoslynLvaWalker.cs @@ -0,0 +1,46 @@ +/* + * 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. + */ + +using SonarAnalyzer.CFG.LiveVariableAnalysis; +using SonarAnalyzer.CFG.Roslyn; + +namespace SonarAnalyzer.CFG; + +public static partial class CfgSerializer +{ + private sealed class RoslynLvaWalker : RoslynCfgWalker + { + private readonly RoslynLiveVariableAnalysis lva; + + public RoslynLvaWalker(RoslynLiveVariableAnalysis lva, DotWriter writer, RoslynCfgIdProvider cfgIdProvider) : base(writer, cfgIdProvider) + { + this.lva = lva; + } + + protected override void WriteEdges(BasicBlock block) + { + foreach (var predecessor in lva.BlockPredecessors[block.Ordinal].Where(x => !block.Predecessors.Any(y => y.Source == x))) + { + writer.WriteEdge(BlockId(predecessor), BlockId(block), "LVA"); + } + base.WriteEdges(block); + } + } +} diff --git a/analyzers/src/SonarAnalyzer.CFG/CfgSerializer/CfgSerializer.SonarCfgWalker.cs b/analyzers/src/SonarAnalyzer.CFG/CfgSerializer/CfgSerializer.SonarCfgWalker.cs index fcb83fa322b..5a80826718a 100644 --- a/analyzers/src/SonarAnalyzer.CFG/CfgSerializer/CfgSerializer.SonarCfgWalker.cs +++ b/analyzers/src/SonarAnalyzer.CFG/CfgSerializer/CfgSerializer.SonarCfgWalker.cs @@ -21,100 +21,99 @@ using Microsoft.CodeAnalysis.CSharp; using SonarAnalyzer.CFG.Sonar; -namespace SonarAnalyzer.CFG +namespace SonarAnalyzer.CFG; + +public static partial class CfgSerializer { - public partial class CfgSerializer + private sealed class SonarCfgWalker { - private class SonarCfgWalker - { - private readonly BlockIdProvider blockId = new BlockIdProvider(); - private readonly DotWriter writer; + private readonly BlockIdProvider blockId = new(); + private readonly DotWriter writer; - public SonarCfgWalker(DotWriter writer) => - this.writer = writer; + public SonarCfgWalker(DotWriter writer) => + this.writer = writer; - public void Visit(IControlFlowGraph cfg, string title) + public void Visit(IControlFlowGraph cfg, string title) + { + writer.WriteGraphStart(title); + foreach (var block in cfg.Blocks) { - writer.WriteGraphStart(title); - foreach (var block in cfg.Blocks) - { - Visit(block); - } - writer.WriteGraphEnd(); + Visit(block); } + writer.WriteGraphEnd(); + } - private void Visit(Block block) + private void Visit(Block block) + { + if (block is BinaryBranchBlock binaryBranchBlock) { - if (block is BinaryBranchBlock binaryBranchBlock) - { - WriteNode(block, binaryBranchBlock.BranchingNode); - } - else if (block is BranchBlock branchBlock) - { - WriteNode(block, branchBlock.BranchingNode); - } - else if (block is ExitBlock) - { - WriteNode(block); - } - else if (block is ForeachCollectionProducerBlock foreachBlock) - { - WriteNode(foreachBlock, foreachBlock.ForeachNode); - } - else if (block is ForInitializerBlock forBlock) - { - WriteNode(forBlock, forBlock.ForNode); - } - else if (block is JumpBlock jumpBlock) - { - WriteNode(jumpBlock, jumpBlock.JumpNode); - } - else if (block is LockBlock lockBlock) - { - WriteNode(lockBlock, lockBlock.LockNode); - } - else if (block is UsingEndBlock usingBlock) - { - WriteNode(usingBlock, usingBlock.UsingStatement); - } - else - { - WriteNode(block); - } - WriteEdges(block); + WriteNode(block, binaryBranchBlock.BranchingNode); } + else if (block is BranchBlock branchBlock) + { + WriteNode(block, branchBlock.BranchingNode); + } + else if (block is ExitBlock) + { + WriteNode(block); + } + else if (block is ForeachCollectionProducerBlock foreachBlock) + { + WriteNode(foreachBlock, foreachBlock.ForeachNode); + } + else if (block is ForInitializerBlock forBlock) + { + WriteNode(forBlock, forBlock.ForNode); + } + else if (block is JumpBlock jumpBlock) + { + WriteNode(jumpBlock, jumpBlock.JumpNode); + } + else if (block is LockBlock lockBlock) + { + WriteNode(lockBlock, lockBlock.LockNode); + } + else if (block is UsingEndBlock usingBlock) + { + WriteNode(usingBlock, usingBlock.UsingStatement); + } + else + { + WriteNode(block); + } + WriteEdges(block); + } - private void WriteNode(Block block, SyntaxNode terminator = null) + private void WriteNode(Block block, SyntaxNode terminator = null) + { + var header = block.GetType().Name.SplitCamelCaseToWords().First().ToUpperInvariant(); + if (terminator is not null) { - var header = block.GetType().Name.SplitCamelCaseToWords().First().ToUpperInvariant(); - if (terminator != null) - { - header += ":" + terminator.Kind().ToString().Replace("Syntax", string.Empty); - } - writer.WriteNode(blockId.Get(block), header, block.Instructions.Select(i => i.ToString()).ToArray()); + header += ":" + terminator.Kind().ToString().Replace("Syntax", string.Empty); } + writer.WriteNode(blockId.Get(block), header, block.Instructions.Select(x => x.ToString()).ToArray()); + } - private void WriteEdges(Block block) + private void WriteEdges(Block block) + { + foreach (var successor in block.SuccessorBlocks) { - foreach (var successor in block.SuccessorBlocks) - { - writer.WriteEdge(blockId.Get(block), blockId.Get(successor), Label()); + writer.WriteEdge(blockId.Get(block), blockId.Get(successor), Label()); - string Label() + string Label() + { + if (block is BinaryBranchBlock binary) { - if (block is BinaryBranchBlock binary) + if (successor == binary.TrueSuccessorBlock) + { + return bool.TrueString; + } + else if (successor == binary.FalseSuccessorBlock) { - if (successor == binary.TrueSuccessorBlock) - { - return bool.TrueString; - } - else if (successor == binary.FalseSuccessorBlock) - { - return bool.FalseString; - } + return bool.FalseString; } - return string.Empty; } + return string.Empty; } } } diff --git a/analyzers/src/SonarAnalyzer.CFG/CfgSerializer/CfgSerializer.cs b/analyzers/src/SonarAnalyzer.CFG/CfgSerializer/CfgSerializer.cs index 9ecb23e2556..ed2b1524396 100644 --- a/analyzers/src/SonarAnalyzer.CFG/CfgSerializer/CfgSerializer.cs +++ b/analyzers/src/SonarAnalyzer.CFG/CfgSerializer/CfgSerializer.cs @@ -18,25 +18,32 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +using SonarAnalyzer.CFG.LiveVariableAnalysis; using SonarAnalyzer.CFG.Roslyn; using SonarAnalyzer.CFG.Sonar; -namespace SonarAnalyzer.CFG +namespace SonarAnalyzer.CFG; + +public static partial class CfgSerializer { - public partial class CfgSerializer + public static string Serialize(IControlFlowGraph cfg, string title = "SonarCfg") + { + var writer = new DotWriter(); + new SonarCfgWalker(writer).Visit(cfg, title); + return writer.ToString(); + } + + public static string Serialize(ControlFlowGraph cfg, string title = "RoslynCfg") { - public static string Serialize(IControlFlowGraph cfg, string title = "SonarCfg") - { - var writer = new DotWriter(); - new SonarCfgWalker(writer).Visit(cfg, title); - return writer.ToString(); - } + var writer = new DotWriter(); + new RoslynCfgWalker(writer, new RoslynCfgIdProvider()).Visit(cfg, title); + return writer.ToString(); + } - public static string Serialize(ControlFlowGraph cfg, string title = "RoslynCfg") - { - var writer = new DotWriter(); - new RoslynCfgWalker(writer, new RoslynCfgIdProvider()).Visit(cfg, title); - return writer.ToString(); - } + public static string Serialize(RoslynLiveVariableAnalysis lva, string title = "RoslynCfgLva") + { + var writer = new DotWriter(); + new RoslynLvaWalker(lva, writer, new RoslynCfgIdProvider()).Visit(lva.Cfg, title); + return writer.ToString(); } } diff --git a/analyzers/src/SonarAnalyzer.CFG/CfgSerializer/DotWriter.cs b/analyzers/src/SonarAnalyzer.CFG/CfgSerializer/DotWriter.cs index a718bddd199..98201be1582 100644 --- a/analyzers/src/SonarAnalyzer.CFG/CfgSerializer/DotWriter.cs +++ b/analyzers/src/SonarAnalyzer.CFG/CfgSerializer/DotWriter.cs @@ -20,67 +20,70 @@ using System.Text; -namespace SonarAnalyzer.CFG +namespace SonarAnalyzer.CFG; + +public class DotWriter { - public class DotWriter - { - private readonly StringBuilder builder = new StringBuilder(); - private readonly StringBuilder edges = new StringBuilder(); - private bool started; + private readonly StringBuilder builder = new StringBuilder(); + private readonly StringBuilder edges = new StringBuilder(); + private bool started; - public void WriteGraphStart(string graphName) + public void WriteGraphStart(string graphName) + { + if (started) { - if (started) - { - throw new InvalidOperationException("Graph was already started"); - } - started = true; - builder.AppendLine($"digraph \"{Encode(graphName)}\" {{"); + throw new InvalidOperationException("Graph was already started"); } + started = true; + builder.AppendLine($"digraph \"{Encode(graphName)}\" {{"); + } - public void WriteGraphEnd() + public void WriteGraphEnd() + { + if (!started) { - if (!started) - { - throw new InvalidOperationException("Graph was not started"); - } - started = false; - builder.Append(edges).AppendLine("}"); // Edges crossing subgraphs must be listed at the end of the main graph to keep nodes rendered in the correct subgraph - edges.Clear(); + throw new InvalidOperationException("Graph was not started"); } + started = false; + builder.Append(edges).AppendLine("}"); // Edges crossing subgraphs must be listed at the end of the main graph to keep nodes rendered in the correct subgraph + edges.Clear(); + } - public void WriteSubGraphStart(int id, string title) => - builder.AppendLine($"subgraph \"cluster_{id}\" {{\nlabel = \"{Encode(title)}\""); + public void WriteSubGraphStart(int id, string title) => + builder.AppendLine($"subgraph \"cluster_{id}\" {{\nlabel = \"{Encode(title)}\""); - public void WriteSubGraphEnd() => - builder.AppendLine("}"); + public void WriteSubGraphEnd() => + builder.AppendLine("}"); - public void WriteNode(string id, string header, params string[] items) + public void WriteNode(string id, string header, params string[] items) + { + // Curly braces in the label reverse the orientation of the columns/rows + // Columns/rows are created with pipe + // New lines are inserted with \n; \r\n does not work well. + // ID [shape=record label="{
|\n\n...}"] + builder.Append(id).Append(" [shape=record label=\"{").Append(header); + foreach (var item in items) { - // Curly braces in the label reverse the orientation of the columns/rows - // Columns/rows are created with pipe - // New lines are inserted with \n; \r\n does not work well. - // ID [shape=record label="{
|\n\n...}"] - builder.Append(id).Append(" [shape=record label=\"{").Append(header); - foreach (var item in items) - { - builder.Append("|").Append(Encode(item)); - } - builder.AppendLine("}\"]"); + builder.Append("|").Append(Encode(item)); } + builder.AppendLine("}\"]"); + } - public void WriteEdge(string startId, string endId, string label) + public void WriteEdge(string startId, string endId, string label) + { + edges.Append(startId).Append(" -> ").Append(endId); + if (!string.IsNullOrEmpty(label)) { - edges.Append(startId).Append(" -> ").Append(endId); - if (!string.IsNullOrEmpty(label)) - { - edges.Append($" [label=\"{label}\"]"); - } - edges.AppendLine(); + edges.Append($" [label=\"{label}\"]"); } + edges.AppendLine(); + } - private static string Encode(string s) => - s.Replace("\r", string.Empty) + public override string ToString() => + builder.ToString(); + + private static string Encode(string s) => + s.Replace("\r", string.Empty) .Replace("\n", @"\n") .Replace("{", @"\{") .Replace("}", @"\}") @@ -88,8 +91,4 @@ private static string Encode(string s) => .Replace("<", @"\<") .Replace(">", @"\>") .Replace("\"", @"\"""); - - public override string ToString() => - builder.ToString(); - } } diff --git a/analyzers/src/SonarAnalyzer.CFG/LiveVariableAnalysis/RoslynLiveVariableAnalysis.cs b/analyzers/src/SonarAnalyzer.CFG/LiveVariableAnalysis/RoslynLiveVariableAnalysis.cs index 3300ea55669..42e02966445 100644 --- a/analyzers/src/SonarAnalyzer.CFG/LiveVariableAnalysis/RoslynLiveVariableAnalysis.cs +++ b/analyzers/src/SonarAnalyzer.CFG/LiveVariableAnalysis/RoslynLiveVariableAnalysis.cs @@ -28,6 +28,8 @@ public sealed class RoslynLiveVariableAnalysis : LiveVariableAnalysisBase> blockPredecessors = []; private readonly Dictionary> blockSuccessors = []; + internal ImmutableDictionary> BlockPredecessors => blockPredecessors.ToImmutableDictionary(); + protected override BasicBlock ExitBlock => Cfg.ExitBlock; public RoslynLiveVariableAnalysis(ControlFlowGraph cfg, CancellationToken cancel) diff --git a/analyzers/tests/SonarAnalyzer.Test/CFG/Roslyn/RoslynCfgSerializerTest.cs b/analyzers/tests/SonarAnalyzer.Test/CFG/Roslyn/RoslynCfgSerializerTest.cs index 493510bd938..27b9d42903a 100644 --- a/analyzers/tests/SonarAnalyzer.Test/CFG/Roslyn/RoslynCfgSerializerTest.cs +++ b/analyzers/tests/SonarAnalyzer.Test/CFG/Roslyn/RoslynCfgSerializerTest.cs @@ -20,573 +20,588 @@ using SonarAnalyzer.CFG; -namespace SonarAnalyzer.Test.CFG.Sonar -{ - [TestClass] - public class RoslynCfgSerializerTest - { - [TestMethod] - public void Serialize_MethodNameUsedInTitle() - { - var code = @" -class Sample -{ - void Method() { } -}"; - var dot = CfgSerializer.Serialize(TestHelper.CompileCfgCS(code), "GraphTitle"); - dot.Should().BeIgnoringLineEndings( -@"digraph ""GraphTitle"" { -cfg0_block0 [shape=record label=""{ENTRY #0}""] -cfg0_block1 [shape=record label=""{EXIT #1}""] -cfg0_block0 -> cfg0_block1 -} -"); - } - - [TestMethod] - public void Serialize_EmptyMethod() - { - var code = @" -class Sample -{ - void Method() { } -}"; - var dot = CfgSerializer.Serialize(TestHelper.CompileCfgCS(code)); - - dot.Should().BeIgnoringLineEndings( -@"digraph ""RoslynCfg"" { -cfg0_block0 [shape=record label=""{ENTRY #0}""] -cfg0_block1 [shape=record label=""{EXIT #1}""] -cfg0_block0 -> cfg0_block1 -} -"); - } - - [TestMethod] - public void Serialize_OperationSequence() - { - var code = @" -class Sample +namespace SonarAnalyzer.Test.CFG.Sonar; + +[TestClass] +public class RoslynCfgSerializerTest { - void Method() + [TestMethod] + public void Serialize_MethodNameUsedInTitle() { - A(); - B(); - var c = C(); + const string code = """ + class Sample + { + void Method() { } + } + """; + var dot = CfgSerializer.Serialize(TestHelper.CompileCfgCS(code), "GraphTitle"); + dot.Should().BeIgnoringLineEndings( + """ + digraph "GraphTitle" { + cfg0_block0 [shape=record label="{ENTRY #0}"] + cfg0_block1 [shape=record label="{EXIT #1}"] + cfg0_block0 -> cfg0_block1 + } + + """); } - private void A() { } - private void B() { } - private int C() => 42; -}"; - var dot = CfgSerializer.Serialize(TestHelper.CompileCfgCS(code)); - dot.Should().BeIgnoringLineEndings(""" - digraph "RoslynCfg" { - subgraph "cluster_1" { - label = "LocalLifetime region, Locals: c" - cfg0_block1 [shape=record label="{BLOCK #1|0#: ExpressionStatementOperation: A();|1#: 0#.Operation: InvocationOperation: A: A()|2#: 1#.Instance: InstanceReferenceOperation: A|##########|0#: ExpressionStatementOperation: B();|1#: 0#.Operation: InvocationOperation: B: B()|2#: 1#.Instance: InstanceReferenceOperation: B|##########|0#: SimpleAssignmentOperation: c = C()|1#: 0#.Target: LocalReferenceOperation: c = C()|1#: 0#.Value: InvocationOperation: C: C()|2#: 1#.Instance: InstanceReferenceOperation: C|##########}"] - } - cfg0_block0 [shape=record label="{ENTRY #0}"] - cfg0_block2 [shape=record label="{EXIT #2}"] - cfg0_block0 -> cfg0_block1 - cfg0_block1 -> cfg0_block2 - } + [TestMethod] + public void Serialize_EmptyMethod() + { + const string code = """ + class Sample + { + void Method() { } + } + """; + var dot = CfgSerializer.Serialize(TestHelper.CompileCfgCS(code)); - """); - } + dot.Should().BeIgnoringLineEndings( + """ + digraph "RoslynCfg" { + cfg0_block0 [shape=record label="{ENTRY #0}"] + cfg0_block1 [shape=record label="{EXIT #1}"] + cfg0_block0 -> cfg0_block1 + } - [TestMethod] - public void Serialize_Switch() - { - var code = @" -class Sample -{ - void Foo(int a) - { - switch (a) - { - case 1: - c1(); - break; - - case 2: - c2(); - break; - } + """); } - private void c1() { } - private void c2() { } -} -"; - var dot = CfgSerializer.Serialize(TestHelper.CompileCfgCS(code)); - dot.Should().BeIgnoringLineEndings(""" - digraph "RoslynCfg" { - subgraph "cluster_1" { - label = "LocalLifetime region, Captures: #Capture-0" - cfg0_block1 [shape=record label="{BLOCK #1|0#: FlowCaptureOperation: #Capture-0: a|1#: 0#.Value: ParameterReferenceOperation: a|##########|## BranchValue ##|0#: BinaryOperation: 1|1#: 0#.LeftOperand: FlowCaptureReferenceOperation: #Capture-0: a|1#: 0#.RightOperand: LiteralOperation: 1|##########}"] - cfg0_block2 [shape=record label="{BLOCK #2|0#: ExpressionStatementOperation: c1();|1#: 0#.Operation: InvocationOperation: c1: c1()|2#: 1#.Instance: InstanceReferenceOperation: c1|##########}"] - cfg0_block3 [shape=record label="{BLOCK #3|## BranchValue ##|0#: BinaryOperation: 2|1#: 0#.LeftOperand: FlowCaptureReferenceOperation: #Capture-0: a|1#: 0#.RightOperand: LiteralOperation: 2|##########}"] - cfg0_block4 [shape=record label="{BLOCK #4|0#: ExpressionStatementOperation: c2();|1#: 0#.Operation: InvocationOperation: c2: c2()|2#: 1#.Instance: InstanceReferenceOperation: c2|##########}"] - } - cfg0_block0 [shape=record label="{ENTRY #0}"] - cfg0_block5 [shape=record label="{EXIT #5}"] - cfg0_block0 -> cfg0_block1 - cfg0_block1 -> cfg0_block2 [label="Else"] - cfg0_block1 -> cfg0_block3 [label="WhenFalse"] - cfg0_block3 -> cfg0_block4 [label="Else"] - cfg0_block2 -> cfg0_block5 - cfg0_block3 -> cfg0_block5 [label="WhenFalse"] - cfg0_block4 -> cfg0_block5 - } - """); - } - - [TestMethod] - public void Serialize_If() - { - var code = @" -class Sample -{ - void Method() + [TestMethod] + public void Serialize_OperationSequence() { - if (true) - { - Bar(); - } - } - void Bar() { } -} -"; - var dot = CfgSerializer.Serialize(TestHelper.CompileCfgCS(code)); - - dot.Should().BeIgnoringLineEndings(""" - digraph "RoslynCfg" { - cfg0_block0 [shape=record label="{ENTRY #0}"] - cfg0_block1 [shape=record label="{BLOCK #1|## BranchValue ##|0#: LiteralOperation: true|##########}"] - cfg0_block2 [shape=record label="{BLOCK #2|0#: ExpressionStatementOperation: Bar();|1#: 0#.Operation: InvocationOperation: Bar: Bar()|2#: 1#.Instance: InstanceReferenceOperation: Bar|##########}"] - cfg0_block3 [shape=record label="{EXIT #3}"] - cfg0_block0 -> cfg0_block1 - cfg0_block1 -> cfg0_block2 [label="Else"] - cfg0_block1 -> cfg0_block3 [label="WhenFalse"] - cfg0_block2 -> cfg0_block3 - } + const string code = """ + class Sample + { + void Method() + { + A(); + B(); + var c = C(); + } + + private void A() { } + private void B() { } + private int C() => 42; + } + """; + var dot = CfgSerializer.Serialize(TestHelper.CompileCfgCS(code)); + dot.Should().BeIgnoringLineEndings(""" + digraph "RoslynCfg" { + subgraph "cluster_1" { + label = "LocalLifetime region, Locals: c" + cfg0_block1 [shape=record label="{BLOCK #1|0#: ExpressionStatementOperation: A();|1#: 0#.Operation: InvocationOperation: A: A()|2#: 1#.Instance: InstanceReferenceOperation: A|##########|0#: ExpressionStatementOperation: B();|1#: 0#.Operation: InvocationOperation: B: B()|2#: 1#.Instance: InstanceReferenceOperation: B|##########|0#: SimpleAssignmentOperation: c = C()|1#: 0#.Target: LocalReferenceOperation: c = C()|1#: 0#.Value: InvocationOperation: C: C()|2#: 1#.Instance: InstanceReferenceOperation: C|##########}"] + } + cfg0_block0 [shape=record label="{ENTRY #0}"] + cfg0_block2 [shape=record label="{EXIT #2}"] + cfg0_block0 -> cfg0_block1 + cfg0_block1 -> cfg0_block2 + } - """); - } + """); + } - [TestMethod] - public void Serialize_Foreach_Simple() - { - var code = @" -class Sample -{ - void Method(int[] items) + [TestMethod] + public void Serialize_Switch() { - foreach (var i in items) - { - Bar(i); - } - } - private void Bar(int i) { } -}"; - var dot = CfgSerializer.Serialize(TestHelper.CompileCfgCS(code)); - dot.Should().BeIgnoringLineEndings(""" - digraph "RoslynCfg" { - subgraph "cluster_1" { - label = "LocalLifetime region, Captures: #Capture-0" - subgraph "cluster_2" { - label = "TryAndFinally region" - subgraph "cluster_3" { - label = "Try region" - subgraph "cluster_4" { - label = "LocalLifetime region, Locals: i" - cfg0_block3 [shape=record label="{BLOCK #3|0#: SimpleAssignmentOperation: var|1#: 0#.Target: LocalReferenceOperation: var|1#: 0#.Value: ConversionOperation: var|2#: 1#.Operand: PropertyReferenceOperation: var|3#: 2#.Instance: FlowCaptureReferenceOperation: #Capture-0: items|##########|0#: ExpressionStatementOperation: Bar(i);|1#: 0#.Operation: InvocationOperation: Bar: Bar(i)|2#: 1#.Instance: InstanceReferenceOperation: Bar|2#: ArgumentOperation: i|3#: 2#.Value: LocalReferenceOperation: i|##########}"] - } - cfg0_block2 [shape=record label="{BLOCK #2|## BranchValue ##|0#: InvocationOperation: MoveNext: items|1#: 0#.Instance: FlowCaptureReferenceOperation: #Capture-0: items|##########}"] - } - subgraph "cluster_5" { - label = "Finally region, Captures: #Capture-1" - cfg0_block4 [shape=record label="{BLOCK #4|0#: FlowCaptureOperation: #Capture-1: items|1#: 0#.Value: ConversionOperation: items|2#: 1#.Operand: FlowCaptureReferenceOperation: #Capture-0: items|##########|## BranchValue ##|0#: IsNullOperation: items|1#: 0#.Operand: FlowCaptureReferenceOperation: #Capture-1: items|##########}"] - cfg0_block5 [shape=record label="{BLOCK #5|0#: InvocationOperation: Dispose: items|1#: 0#.Instance: FlowCaptureReferenceOperation: #Capture-1: items|##########}"] - cfg0_block6 [shape=record label="{BLOCK #6}"] - } - } - cfg0_block1 [shape=record label="{BLOCK #1|0#: FlowCaptureOperation: #Capture-0: items|1#: 0#.Value: InvocationOperation: GetEnumerator: items|2#: 1#.Instance: ConversionOperation: items|3#: 2#.Operand: ParameterReferenceOperation: items|##########}"] - } - cfg0_block0 [shape=record label="{ENTRY #0}"] - cfg0_block7 [shape=record label="{EXIT #7}"] - cfg0_block2 -> cfg0_block3 [label="Else"] - cfg0_block1 -> cfg0_block2 - cfg0_block3 -> cfg0_block2 - cfg0_block4 -> cfg0_block5 [label="Else"] - cfg0_block4 -> cfg0_block6 [label="WhenTrue"] - cfg0_block5 -> cfg0_block6 - cfg0_block6 -> NoDestination_cfg0_block6 [label="StructuredExceptionHandling"] - cfg0_block0 -> cfg0_block1 - cfg0_block2 -> cfg0_block7 [label="WhenFalse"] - } + const string code = """ + class Sample + { + void Foo(int a) + { + switch (a) + { + case 1: + c1(); + break; + + case 2: + c2(); + break; + } + } + private void c1() { } + private void c2() { } + } + """; + var dot = CfgSerializer.Serialize(TestHelper.CompileCfgCS(code)); + dot.Should().BeIgnoringLineEndings(""" + digraph "RoslynCfg" { + subgraph "cluster_1" { + label = "LocalLifetime region, Captures: #Capture-0" + cfg0_block1 [shape=record label="{BLOCK #1|0#: FlowCaptureOperation: #Capture-0: a|1#: 0#.Value: ParameterReferenceOperation: a|##########|## BranchValue ##|0#: BinaryOperation: 1|1#: 0#.LeftOperand: FlowCaptureReferenceOperation: #Capture-0: a|1#: 0#.RightOperand: LiteralOperation: 1|##########}"] + cfg0_block2 [shape=record label="{BLOCK #2|0#: ExpressionStatementOperation: c1();|1#: 0#.Operation: InvocationOperation: c1: c1()|2#: 1#.Instance: InstanceReferenceOperation: c1|##########}"] + cfg0_block3 [shape=record label="{BLOCK #3|## BranchValue ##|0#: BinaryOperation: 2|1#: 0#.LeftOperand: FlowCaptureReferenceOperation: #Capture-0: a|1#: 0#.RightOperand: LiteralOperation: 2|##########}"] + cfg0_block4 [shape=record label="{BLOCK #4|0#: ExpressionStatementOperation: c2();|1#: 0#.Operation: InvocationOperation: c2: c2()|2#: 1#.Instance: InstanceReferenceOperation: c2|##########}"] + } + cfg0_block0 [shape=record label="{ENTRY #0}"] + cfg0_block5 [shape=record label="{EXIT #5}"] + cfg0_block0 -> cfg0_block1 + cfg0_block1 -> cfg0_block2 [label="Else"] + cfg0_block1 -> cfg0_block3 [label="WhenFalse"] + cfg0_block3 -> cfg0_block4 [label="Else"] + cfg0_block2 -> cfg0_block5 + cfg0_block3 -> cfg0_block5 [label="WhenFalse"] + cfg0_block4 -> cfg0_block5 + } - """); - } + """); + } - [TestMethod] - public void Serialize_Foreach_TupleVarDeclaration() - { - var code = @" -public class Sample -{ - public void Method((string key, string value)[] values) + [TestMethod] + public void Serialize_If() { - foreach (var (key, value) in values) - { - string i = key; - string j = value; - } - } -}"; - var dot = CfgSerializer.Serialize(TestHelper.CompileCfgCS(code)); - dot.Should().BeIgnoringLineEndings(""" - digraph "RoslynCfg" { - subgraph "cluster_1" { - label = "LocalLifetime region, Captures: #Capture-0" - subgraph "cluster_2" { - label = "TryAndFinally region" - subgraph "cluster_3" { - label = "Try region" - subgraph "cluster_4" { - label = "LocalLifetime region, Locals: key, value" - subgraph "cluster_5" { - label = "LocalLifetime region, Locals: i, j" - cfg0_block4 [shape=record label="{BLOCK #4|0#: SimpleAssignmentOperation: i = key|1#: 0#.Target: LocalReferenceOperation: i = key|1#: 0#.Value: LocalReferenceOperation: key|##########|0#: SimpleAssignmentOperation: j = value|1#: 0#.Target: LocalReferenceOperation: j = value|1#: 0#.Value: LocalReferenceOperation: value|##########}"] - } - cfg0_block3 [shape=record label="{BLOCK #3|0#: DeconstructionAssignmentOperation: var (key, value)|1#: 0#.Target: DeclarationExpressionOperation: var (key, value)|2#: 1#.Expression: TupleOperation: (key, value)|3#: LocalReferenceOperation: key|3#: LocalReferenceOperation: value|1#: 0#.Value: ConversionOperation: var (key, value)|2#: 1#.Operand: PropertyReferenceOperation: var (key, value)|3#: 2#.Instance: FlowCaptureReferenceOperation: #Capture-0: values|##########}"] - } - cfg0_block2 [shape=record label="{BLOCK #2|## BranchValue ##|0#: InvocationOperation: MoveNext: values|1#: 0#.Instance: FlowCaptureReferenceOperation: #Capture-0: values|##########}"] - } - subgraph "cluster_6" { - label = "Finally region, Captures: #Capture-1" - cfg0_block5 [shape=record label="{BLOCK #5|0#: FlowCaptureOperation: #Capture-1: values|1#: 0#.Value: ConversionOperation: values|2#: 1#.Operand: FlowCaptureReferenceOperation: #Capture-0: values|##########|## BranchValue ##|0#: IsNullOperation: values|1#: 0#.Operand: FlowCaptureReferenceOperation: #Capture-1: values|##########}"] - cfg0_block6 [shape=record label="{BLOCK #6|0#: InvocationOperation: Dispose: values|1#: 0#.Instance: FlowCaptureReferenceOperation: #Capture-1: values|##########}"] - cfg0_block7 [shape=record label="{BLOCK #7}"] - } - } - cfg0_block1 [shape=record label="{BLOCK #1|0#: FlowCaptureOperation: #Capture-0: values|1#: 0#.Value: InvocationOperation: GetEnumerator: values|2#: 1#.Instance: ConversionOperation: values|3#: 2#.Operand: ParameterReferenceOperation: values|##########}"] - } - cfg0_block0 [shape=record label="{ENTRY #0}"] - cfg0_block8 [shape=record label="{EXIT #8}"] - cfg0_block3 -> cfg0_block4 - cfg0_block2 -> cfg0_block3 [label="Else"] - cfg0_block1 -> cfg0_block2 - cfg0_block4 -> cfg0_block2 - cfg0_block5 -> cfg0_block6 [label="Else"] - cfg0_block5 -> cfg0_block7 [label="WhenTrue"] - cfg0_block6 -> cfg0_block7 - cfg0_block7 -> NoDestination_cfg0_block7 [label="StructuredExceptionHandling"] - cfg0_block0 -> cfg0_block1 - cfg0_block2 -> cfg0_block8 [label="WhenFalse"] - } + const string code = """ + class Sample + { + void Method() + { + if (true) + { + Bar(); + } + } + void Bar() { } + } + """; + var dot = CfgSerializer.Serialize(TestHelper.CompileCfgCS(code)); - """); - } + dot.Should().BeIgnoringLineEndings(""" + digraph "RoslynCfg" { + cfg0_block0 [shape=record label="{ENTRY #0}"] + cfg0_block1 [shape=record label="{BLOCK #1|## BranchValue ##|0#: LiteralOperation: true|##########}"] + cfg0_block2 [shape=record label="{BLOCK #2|0#: ExpressionStatementOperation: Bar();|1#: 0#.Operation: InvocationOperation: Bar: Bar()|2#: 1#.Instance: InstanceReferenceOperation: Bar|##########}"] + cfg0_block3 [shape=record label="{EXIT #3}"] + cfg0_block0 -> cfg0_block1 + cfg0_block1 -> cfg0_block2 [label="Else"] + cfg0_block1 -> cfg0_block3 [label="WhenFalse"] + cfg0_block2 -> cfg0_block3 + } - [TestMethod] - public void Serialize_For() - { - var code = @" -class Sample -{ - void Method() + """); + } + + [TestMethod] + public void Serialize_Foreach_Simple() { - for (var i = 0; i < 10; i++) - { - Bar(i); - } + const string code = """ + class Sample + { + void Method(int[] items) + { + foreach (var i in items) + { + Bar(i); + } + } + private void Bar(int i) { } + } + """; + var dot = CfgSerializer.Serialize(TestHelper.CompileCfgCS(code)); + dot.Should().BeIgnoringLineEndings(""" + digraph "RoslynCfg" { + subgraph "cluster_1" { + label = "LocalLifetime region, Captures: #Capture-0" + subgraph "cluster_2" { + label = "TryAndFinally region" + subgraph "cluster_3" { + label = "Try region" + subgraph "cluster_4" { + label = "LocalLifetime region, Locals: i" + cfg0_block3 [shape=record label="{BLOCK #3|0#: SimpleAssignmentOperation: var|1#: 0#.Target: LocalReferenceOperation: var|1#: 0#.Value: ConversionOperation: var|2#: 1#.Operand: PropertyReferenceOperation: var|3#: 2#.Instance: FlowCaptureReferenceOperation: #Capture-0: items|##########|0#: ExpressionStatementOperation: Bar(i);|1#: 0#.Operation: InvocationOperation: Bar: Bar(i)|2#: 1#.Instance: InstanceReferenceOperation: Bar|2#: ArgumentOperation: i|3#: 2#.Value: LocalReferenceOperation: i|##########}"] + } + cfg0_block2 [shape=record label="{BLOCK #2|## BranchValue ##|0#: InvocationOperation: MoveNext: items|1#: 0#.Instance: FlowCaptureReferenceOperation: #Capture-0: items|##########}"] + } + subgraph "cluster_5" { + label = "Finally region, Captures: #Capture-1" + cfg0_block4 [shape=record label="{BLOCK #4|0#: FlowCaptureOperation: #Capture-1: items|1#: 0#.Value: ConversionOperation: items|2#: 1#.Operand: FlowCaptureReferenceOperation: #Capture-0: items|##########|## BranchValue ##|0#: IsNullOperation: items|1#: 0#.Operand: FlowCaptureReferenceOperation: #Capture-1: items|##########}"] + cfg0_block5 [shape=record label="{BLOCK #5|0#: InvocationOperation: Dispose: items|1#: 0#.Instance: FlowCaptureReferenceOperation: #Capture-1: items|##########}"] + cfg0_block6 [shape=record label="{BLOCK #6}"] + } + } + cfg0_block1 [shape=record label="{BLOCK #1|0#: FlowCaptureOperation: #Capture-0: items|1#: 0#.Value: InvocationOperation: GetEnumerator: items|2#: 1#.Instance: ConversionOperation: items|3#: 2#.Operand: ParameterReferenceOperation: items|##########}"] + } + cfg0_block0 [shape=record label="{ENTRY #0}"] + cfg0_block7 [shape=record label="{EXIT #7}"] + cfg0_block2 -> cfg0_block3 [label="Else"] + cfg0_block1 -> cfg0_block2 + cfg0_block3 -> cfg0_block2 + cfg0_block4 -> cfg0_block5 [label="Else"] + cfg0_block4 -> cfg0_block6 [label="WhenTrue"] + cfg0_block5 -> cfg0_block6 + cfg0_block6 -> NoDestination_cfg0_block6 [label="StructuredExceptionHandling"] + cfg0_block0 -> cfg0_block1 + cfg0_block2 -> cfg0_block7 [label="WhenFalse"] + } + + """); } - private void Bar(int i) { } -} -"; - var dot = CfgSerializer.Serialize(TestHelper.CompileCfgCS(code)); - dot.Should().BeIgnoringLineEndings(""" - digraph "RoslynCfg" { - subgraph "cluster_1" { - label = "LocalLifetime region, Locals: i" - cfg0_block1 [shape=record label="{BLOCK #1|0#: SimpleAssignmentOperation: i = 0|1#: 0#.Target: LocalReferenceOperation: i = 0|1#: 0#.Value: LiteralOperation: 0|##########}"] - cfg0_block2 [shape=record label="{BLOCK #2|## BranchValue ##|0#: BinaryOperation: i \< 10|1#: 0#.LeftOperand: LocalReferenceOperation: i|1#: 0#.RightOperand: LiteralOperation: 10|##########}"] - cfg0_block3 [shape=record label="{BLOCK #3|0#: ExpressionStatementOperation: Bar(i);|1#: 0#.Operation: InvocationOperation: Bar: Bar(i)|2#: 1#.Instance: InstanceReferenceOperation: Bar|2#: ArgumentOperation: i|3#: 2#.Value: LocalReferenceOperation: i|##########|0#: ExpressionStatementOperation: i++|1#: 0#.Operation: IncrementOrDecrementOperation: i++|2#: 1#.Target: LocalReferenceOperation: i|##########}"] - } - cfg0_block0 [shape=record label="{ENTRY #0}"] - cfg0_block4 [shape=record label="{EXIT #4}"] - cfg0_block0 -> cfg0_block1 - cfg0_block1 -> cfg0_block2 - cfg0_block3 -> cfg0_block2 - cfg0_block2 -> cfg0_block3 [label="Else"] - cfg0_block2 -> cfg0_block4 [label="WhenFalse"] + + [TestMethod] + public void Serialize_Foreach_TupleVarDeclaration() + { + const string code = """ + public class Sample + { + public void Method((string key, string value)[] values) + { + foreach (var (key, value) in values) + { + string i = key; + string j = value; + } } + } + """; + var dot = CfgSerializer.Serialize(TestHelper.CompileCfgCS(code)); + dot.Should().BeIgnoringLineEndings(""" + digraph "RoslynCfg" { + subgraph "cluster_1" { + label = "LocalLifetime region, Captures: #Capture-0" + subgraph "cluster_2" { + label = "TryAndFinally region" + subgraph "cluster_3" { + label = "Try region" + subgraph "cluster_4" { + label = "LocalLifetime region, Locals: key, value" + subgraph "cluster_5" { + label = "LocalLifetime region, Locals: i, j" + cfg0_block4 [shape=record label="{BLOCK #4|0#: SimpleAssignmentOperation: i = key|1#: 0#.Target: LocalReferenceOperation: i = key|1#: 0#.Value: LocalReferenceOperation: key|##########|0#: SimpleAssignmentOperation: j = value|1#: 0#.Target: LocalReferenceOperation: j = value|1#: 0#.Value: LocalReferenceOperation: value|##########}"] + } + cfg0_block3 [shape=record label="{BLOCK #3|0#: DeconstructionAssignmentOperation: var (key, value)|1#: 0#.Target: DeclarationExpressionOperation: var (key, value)|2#: 1#.Expression: TupleOperation: (key, value)|3#: LocalReferenceOperation: key|3#: LocalReferenceOperation: value|1#: 0#.Value: ConversionOperation: var (key, value)|2#: 1#.Operand: PropertyReferenceOperation: var (key, value)|3#: 2#.Instance: FlowCaptureReferenceOperation: #Capture-0: values|##########}"] + } + cfg0_block2 [shape=record label="{BLOCK #2|## BranchValue ##|0#: InvocationOperation: MoveNext: values|1#: 0#.Instance: FlowCaptureReferenceOperation: #Capture-0: values|##########}"] + } + subgraph "cluster_6" { + label = "Finally region, Captures: #Capture-1" + cfg0_block5 [shape=record label="{BLOCK #5|0#: FlowCaptureOperation: #Capture-1: values|1#: 0#.Value: ConversionOperation: values|2#: 1#.Operand: FlowCaptureReferenceOperation: #Capture-0: values|##########|## BranchValue ##|0#: IsNullOperation: values|1#: 0#.Operand: FlowCaptureReferenceOperation: #Capture-1: values|##########}"] + cfg0_block6 [shape=record label="{BLOCK #6|0#: InvocationOperation: Dispose: values|1#: 0#.Instance: FlowCaptureReferenceOperation: #Capture-1: values|##########}"] + cfg0_block7 [shape=record label="{BLOCK #7}"] + } + } + cfg0_block1 [shape=record label="{BLOCK #1|0#: FlowCaptureOperation: #Capture-0: values|1#: 0#.Value: InvocationOperation: GetEnumerator: values|2#: 1#.Instance: ConversionOperation: values|3#: 2#.Operand: ParameterReferenceOperation: values|##########}"] + } + cfg0_block0 [shape=record label="{ENTRY #0}"] + cfg0_block8 [shape=record label="{EXIT #8}"] + cfg0_block3 -> cfg0_block4 + cfg0_block2 -> cfg0_block3 [label="Else"] + cfg0_block1 -> cfg0_block2 + cfg0_block4 -> cfg0_block2 + cfg0_block5 -> cfg0_block6 [label="Else"] + cfg0_block5 -> cfg0_block7 [label="WhenTrue"] + cfg0_block6 -> cfg0_block7 + cfg0_block7 -> NoDestination_cfg0_block7 [label="StructuredExceptionHandling"] + cfg0_block0 -> cfg0_block1 + cfg0_block2 -> cfg0_block8 [label="WhenFalse"] + } - """); - } + """); + } - [TestMethod] - public void Serialize_Using() - { - var code = @" -class Sample -{ - void Method() + [TestMethod] + public void Serialize_For() { - using (var x = new System.IO.MemoryStream()) - { - Bar(); - } + const string code = """ + class Sample + { + void Method() + { + for (var i = 0; i < 10; i++) + { + Bar(i); + } + } + private void Bar(int i) { } + } + """; + var dot = CfgSerializer.Serialize(TestHelper.CompileCfgCS(code)); + dot.Should().BeIgnoringLineEndings(""" + digraph "RoslynCfg" { + subgraph "cluster_1" { + label = "LocalLifetime region, Locals: i" + cfg0_block1 [shape=record label="{BLOCK #1|0#: SimpleAssignmentOperation: i = 0|1#: 0#.Target: LocalReferenceOperation: i = 0|1#: 0#.Value: LiteralOperation: 0|##########}"] + cfg0_block2 [shape=record label="{BLOCK #2|## BranchValue ##|0#: BinaryOperation: i \< 10|1#: 0#.LeftOperand: LocalReferenceOperation: i|1#: 0#.RightOperand: LiteralOperation: 10|##########}"] + cfg0_block3 [shape=record label="{BLOCK #3|0#: ExpressionStatementOperation: Bar(i);|1#: 0#.Operation: InvocationOperation: Bar: Bar(i)|2#: 1#.Instance: InstanceReferenceOperation: Bar|2#: ArgumentOperation: i|3#: 2#.Value: LocalReferenceOperation: i|##########|0#: ExpressionStatementOperation: i++|1#: 0#.Operation: IncrementOrDecrementOperation: i++|2#: 1#.Target: LocalReferenceOperation: i|##########}"] + } + cfg0_block0 [shape=record label="{ENTRY #0}"] + cfg0_block4 [shape=record label="{EXIT #4}"] + cfg0_block0 -> cfg0_block1 + cfg0_block1 -> cfg0_block2 + cfg0_block3 -> cfg0_block2 + cfg0_block2 -> cfg0_block3 [label="Else"] + cfg0_block2 -> cfg0_block4 [label="WhenFalse"] + } + + """); } - private void Bar() { } -}"; - var dot = CfgSerializer.Serialize(TestHelper.CompileCfgCS(code)); - dot.Should().BeIgnoringLineEndings(""" - digraph "RoslynCfg" { - subgraph "cluster_1" { - label = "LocalLifetime region, Locals: x" - subgraph "cluster_2" { - label = "TryAndFinally region" - subgraph "cluster_3" { - label = "Try region" - cfg0_block2 [shape=record label="{BLOCK #2|0#: ExpressionStatementOperation: Bar();|1#: 0#.Operation: InvocationOperation: Bar: Bar()|2#: 1#.Instance: InstanceReferenceOperation: Bar|##########}"] - } - subgraph "cluster_4" { - label = "Finally region" - cfg0_block3 [shape=record label="{BLOCK #3|## BranchValue ##|0#: IsNullOperation: x = new System.IO.MemoryStream()|1#: 0#.Operand: LocalReferenceOperation: x = new System.IO.MemoryStream()|##########}"] - cfg0_block4 [shape=record label="{BLOCK #4|0#: InvocationOperation: Dispose: x = new System.IO.MemoryStream()|1#: 0#.Instance: ConversionOperation: x = new System.IO.MemoryStream()|2#: 1#.Operand: LocalReferenceOperation: x = new System.IO.MemoryStream()|##########}"] - cfg0_block5 [shape=record label="{BLOCK #5}"] - } - } - cfg0_block1 [shape=record label="{BLOCK #1|0#: SimpleAssignmentOperation: x = new System.IO.MemoryStream()|1#: 0#.Target: LocalReferenceOperation: x = new System.IO.MemoryStream()|1#: 0#.Value: ObjectCreationOperation: new System.IO.MemoryStream()|##########}"] - } - cfg0_block0 [shape=record label="{ENTRY #0}"] - cfg0_block6 [shape=record label="{EXIT #6}"] - cfg0_block1 -> cfg0_block2 - cfg0_block3 -> cfg0_block4 [label="Else"] - cfg0_block3 -> cfg0_block5 [label="WhenTrue"] - cfg0_block4 -> cfg0_block5 - cfg0_block5 -> NoDestination_cfg0_block5 [label="StructuredExceptionHandling"] - cfg0_block0 -> cfg0_block1 - cfg0_block2 -> cfg0_block6 - } - """); - } + [TestMethod] + public void Serialize_Using() + { + const string code = """ + class Sample + { + void Method() + { + using (var x = new System.IO.MemoryStream()) + { + Bar(); + } + } + private void Bar() { } + } + """; + var dot = CfgSerializer.Serialize(TestHelper.CompileCfgCS(code)); + dot.Should().BeIgnoringLineEndings(""" + digraph "RoslynCfg" { + subgraph "cluster_1" { + label = "LocalLifetime region, Locals: x" + subgraph "cluster_2" { + label = "TryAndFinally region" + subgraph "cluster_3" { + label = "Try region" + cfg0_block2 [shape=record label="{BLOCK #2|0#: ExpressionStatementOperation: Bar();|1#: 0#.Operation: InvocationOperation: Bar: Bar()|2#: 1#.Instance: InstanceReferenceOperation: Bar|##########}"] + } + subgraph "cluster_4" { + label = "Finally region" + cfg0_block3 [shape=record label="{BLOCK #3|## BranchValue ##|0#: IsNullOperation: x = new System.IO.MemoryStream()|1#: 0#.Operand: LocalReferenceOperation: x = new System.IO.MemoryStream()|##########}"] + cfg0_block4 [shape=record label="{BLOCK #4|0#: InvocationOperation: Dispose: x = new System.IO.MemoryStream()|1#: 0#.Instance: ConversionOperation: x = new System.IO.MemoryStream()|2#: 1#.Operand: LocalReferenceOperation: x = new System.IO.MemoryStream()|##########}"] + cfg0_block5 [shape=record label="{BLOCK #5}"] + } + } + cfg0_block1 [shape=record label="{BLOCK #1|0#: SimpleAssignmentOperation: x = new System.IO.MemoryStream()|1#: 0#.Target: LocalReferenceOperation: x = new System.IO.MemoryStream()|1#: 0#.Value: ObjectCreationOperation: new System.IO.MemoryStream()|##########}"] + } + cfg0_block0 [shape=record label="{ENTRY #0}"] + cfg0_block6 [shape=record label="{EXIT #6}"] + cfg0_block1 -> cfg0_block2 + cfg0_block3 -> cfg0_block4 [label="Else"] + cfg0_block3 -> cfg0_block5 [label="WhenTrue"] + cfg0_block4 -> cfg0_block5 + cfg0_block5 -> NoDestination_cfg0_block5 [label="StructuredExceptionHandling"] + cfg0_block0 -> cfg0_block1 + cfg0_block2 -> cfg0_block6 + } - [TestMethod] - public void Serialize_Lock() - { - var code = @" -class Sample -{ - object x; + """); + } - void Method() + [TestMethod] + public void Serialize_Lock() { - lock (x) - { - Bar(); - } - } - private void Bar() { } -}"; - var dot = CfgSerializer.Serialize(TestHelper.CompileCfgCS(code)); - dot.Should().BeIgnoringLineEndings(""" - digraph "RoslynCfg" { - subgraph "cluster_1" { - label = "LocalLifetime region, Locals: N/A, Captures: #Capture-0" - subgraph "cluster_2" { - label = "TryAndFinally region" - subgraph "cluster_3" { - label = "Try region" - cfg0_block2 [shape=record label="{BLOCK #2|0#: InvocationOperation: Enter: x|1#: ArgumentOperation: x|2#: 1#.Value: FlowCaptureReferenceOperation: #Capture-0: x|1#: ArgumentOperation: x|2#: 1#.Value: LocalReferenceOperation: x|##########|0#: ExpressionStatementOperation: Bar();|1#: 0#.Operation: InvocationOperation: Bar: Bar()|2#: 1#.Instance: InstanceReferenceOperation: Bar|##########}"] - } - subgraph "cluster_4" { - label = "Finally region" - cfg0_block3 [shape=record label="{BLOCK #3|## BranchValue ##|0#: LocalReferenceOperation: x|##########}"] - cfg0_block4 [shape=record label="{BLOCK #4|0#: InvocationOperation: Exit: x|1#: ArgumentOperation: x|2#: 1#.Value: FlowCaptureReferenceOperation: #Capture-0: x|##########}"] - cfg0_block5 [shape=record label="{BLOCK #5}"] - } - } - cfg0_block1 [shape=record label="{BLOCK #1|0#: FlowCaptureOperation: #Capture-0: x|1#: 0#.Value: FieldReferenceOperation: x|2#: 1#.Instance: InstanceReferenceOperation: x|##########}"] - } - cfg0_block0 [shape=record label="{ENTRY #0}"] - cfg0_block6 [shape=record label="{EXIT #6}"] - cfg0_block1 -> cfg0_block2 - cfg0_block3 -> cfg0_block4 [label="Else"] - cfg0_block3 -> cfg0_block5 [label="WhenFalse"] - cfg0_block4 -> cfg0_block5 - cfg0_block5 -> NoDestination_cfg0_block5 [label="StructuredExceptionHandling"] - cfg0_block0 -> cfg0_block1 - cfg0_block2 -> cfg0_block6 - } + const string code = """ + class Sample + { + object x; + + void Method() + { + lock (x) + { + Bar(); + } + } + private void Bar() { } + } + """; + var dot = CfgSerializer.Serialize(TestHelper.CompileCfgCS(code)); + dot.Should().BeIgnoringLineEndings(""" + digraph "RoslynCfg" { + subgraph "cluster_1" { + label = "LocalLifetime region, Locals: N/A, Captures: #Capture-0" + subgraph "cluster_2" { + label = "TryAndFinally region" + subgraph "cluster_3" { + label = "Try region" + cfg0_block2 [shape=record label="{BLOCK #2|0#: InvocationOperation: Enter: x|1#: ArgumentOperation: x|2#: 1#.Value: FlowCaptureReferenceOperation: #Capture-0: x|1#: ArgumentOperation: x|2#: 1#.Value: LocalReferenceOperation: x|##########|0#: ExpressionStatementOperation: Bar();|1#: 0#.Operation: InvocationOperation: Bar: Bar()|2#: 1#.Instance: InstanceReferenceOperation: Bar|##########}"] + } + subgraph "cluster_4" { + label = "Finally region" + cfg0_block3 [shape=record label="{BLOCK #3|## BranchValue ##|0#: LocalReferenceOperation: x|##########}"] + cfg0_block4 [shape=record label="{BLOCK #4|0#: InvocationOperation: Exit: x|1#: ArgumentOperation: x|2#: 1#.Value: FlowCaptureReferenceOperation: #Capture-0: x|##########}"] + cfg0_block5 [shape=record label="{BLOCK #5}"] + } + } + cfg0_block1 [shape=record label="{BLOCK #1|0#: FlowCaptureOperation: #Capture-0: x|1#: 0#.Value: FieldReferenceOperation: x|2#: 1#.Instance: InstanceReferenceOperation: x|##########}"] + } + cfg0_block0 [shape=record label="{ENTRY #0}"] + cfg0_block6 [shape=record label="{EXIT #6}"] + cfg0_block1 -> cfg0_block2 + cfg0_block3 -> cfg0_block4 [label="Else"] + cfg0_block3 -> cfg0_block5 [label="WhenFalse"] + cfg0_block4 -> cfg0_block5 + cfg0_block5 -> NoDestination_cfg0_block5 [label="StructuredExceptionHandling"] + cfg0_block0 -> cfg0_block1 + cfg0_block2 -> cfg0_block6 + } - """); - } + """); + } - [TestMethod] - public void Serialize_Regions() - { - var code = @" -class Sample -{ - void Method() + [TestMethod] + public void Serialize_Regions() { - Before(); - try - { - InTry(); - } - finally - { - InFinally(); - } - After(); - } - private void Before() { } - private void InTry() { } - private void InFinally() { } - private void After() { } -}"; - var dot = CfgSerializer.Serialize(TestHelper.CompileCfgCS(code)); - dot.Should().BeIgnoringLineEndings(""" - digraph "RoslynCfg" { - subgraph "cluster_1" { - label = "TryAndFinally region" - subgraph "cluster_2" { - label = "Try region" - cfg0_block2 [shape=record label="{BLOCK #2|0#: ExpressionStatementOperation: InTry();|1#: 0#.Operation: InvocationOperation: InTry: InTry()|2#: 1#.Instance: InstanceReferenceOperation: InTry|##########}"] - } - subgraph "cluster_3" { - label = "Finally region" - cfg0_block3 [shape=record label="{BLOCK #3|0#: ExpressionStatementOperation: InFinally();|1#: 0#.Operation: InvocationOperation: InFinally: InFinally()|2#: 1#.Instance: InstanceReferenceOperation: InFinally|##########}"] - } - } - cfg0_block0 [shape=record label="{ENTRY #0}"] - cfg0_block1 [shape=record label="{BLOCK #1|0#: ExpressionStatementOperation: Before();|1#: 0#.Operation: InvocationOperation: Before: Before()|2#: 1#.Instance: InstanceReferenceOperation: Before|##########}"] - cfg0_block4 [shape=record label="{BLOCK #4|0#: ExpressionStatementOperation: After();|1#: 0#.Operation: InvocationOperation: After: After()|2#: 1#.Instance: InstanceReferenceOperation: After|##########}"] - cfg0_block5 [shape=record label="{EXIT #5}"] - cfg0_block1 -> cfg0_block2 - cfg0_block3 -> NoDestination_cfg0_block3 [label="StructuredExceptionHandling"] - cfg0_block0 -> cfg0_block1 - cfg0_block2 -> cfg0_block4 - cfg0_block4 -> cfg0_block5 - } + const string code = """ + class Sample + { + void Method() + { + Before(); + try + { + InTry(); + } + finally + { + InFinally(); + } + After(); + } + private void Before() { } + private void InTry() { } + private void InFinally() { } + private void After() { } + } + """; + var dot = CfgSerializer.Serialize(TestHelper.CompileCfgCS(code)); + dot.Should().BeIgnoringLineEndings(""" + digraph "RoslynCfg" { + subgraph "cluster_1" { + label = "TryAndFinally region" + subgraph "cluster_2" { + label = "Try region" + cfg0_block2 [shape=record label="{BLOCK #2|0#: ExpressionStatementOperation: InTry();|1#: 0#.Operation: InvocationOperation: InTry: InTry()|2#: 1#.Instance: InstanceReferenceOperation: InTry|##########}"] + } + subgraph "cluster_3" { + label = "Finally region" + cfg0_block3 [shape=record label="{BLOCK #3|0#: ExpressionStatementOperation: InFinally();|1#: 0#.Operation: InvocationOperation: InFinally: InFinally()|2#: 1#.Instance: InstanceReferenceOperation: InFinally|##########}"] + } + } + cfg0_block0 [shape=record label="{ENTRY #0}"] + cfg0_block1 [shape=record label="{BLOCK #1|0#: ExpressionStatementOperation: Before();|1#: 0#.Operation: InvocationOperation: Before: Before()|2#: 1#.Instance: InstanceReferenceOperation: Before|##########}"] + cfg0_block4 [shape=record label="{BLOCK #4|0#: ExpressionStatementOperation: After();|1#: 0#.Operation: InvocationOperation: After: After()|2#: 1#.Instance: InstanceReferenceOperation: After|##########}"] + cfg0_block5 [shape=record label="{EXIT #5}"] + cfg0_block1 -> cfg0_block2 + cfg0_block3 -> NoDestination_cfg0_block3 [label="StructuredExceptionHandling"] + cfg0_block0 -> cfg0_block1 + cfg0_block2 -> cfg0_block4 + cfg0_block4 -> cfg0_block5 + } - """); - } + """); + } - [TestMethod] - public void Serialize_Region_ExceptionType() - { - var code = @" -class Sample -{ - void Method() + [TestMethod] + public void Serialize_Region_ExceptionType() { - try { } - catch(System.InvalidOperationException ex) { } - } -}"; - var dot = CfgSerializer.Serialize(TestHelper.CompileCfgCS(code)); - dot.Should().BeIgnoringLineEndings(""" - digraph "RoslynCfg" { - subgraph "cluster_1" { - label = "TryAndCatch region" - subgraph "cluster_2" { - label = "Try region" - cfg0_block1 [shape=record label="{BLOCK #1}"] - } - subgraph "cluster_3" { - label = "Catch region: System.InvalidOperationException, Locals: ex" - cfg0_block2 [shape=record label="{BLOCK #2|0#: SimpleAssignmentOperation: (System.InvalidOperationException ex)|1#: 0#.Target: LocalReferenceOperation: (System.InvalidOperationException ex)|1#: 0#.Value: CaughtExceptionOperation: (System.InvalidOperationException ex)|##########}"] - } - } - cfg0_block0 [shape=record label="{ENTRY #0}"] - cfg0_block3 [shape=record label="{EXIT #3}"] - cfg0_block0 -> cfg0_block1 - cfg0_block1 -> cfg0_block3 - cfg0_block2 -> cfg0_block3 - } + const string code = """ + class Sample + { + void Method() + { + try { } + catch(System.InvalidOperationException ex) { } + } + } + """; + var dot = CfgSerializer.Serialize(TestHelper.CompileCfgCS(code)); + dot.Should().BeIgnoringLineEndings(""" + digraph "RoslynCfg" { + subgraph "cluster_1" { + label = "TryAndCatch region" + subgraph "cluster_2" { + label = "Try region" + cfg0_block1 [shape=record label="{BLOCK #1}"] + } + subgraph "cluster_3" { + label = "Catch region: System.InvalidOperationException, Locals: ex" + cfg0_block2 [shape=record label="{BLOCK #2|0#: SimpleAssignmentOperation: (System.InvalidOperationException ex)|1#: 0#.Target: LocalReferenceOperation: (System.InvalidOperationException ex)|1#: 0#.Value: CaughtExceptionOperation: (System.InvalidOperationException ex)|##########}"] + } + } + cfg0_block0 [shape=record label="{ENTRY #0}"] + cfg0_block3 [shape=record label="{EXIT #3}"] + cfg0_block0 -> cfg0_block1 + cfg0_block1 -> cfg0_block3 + cfg0_block2 -> cfg0_block3 + } - """); - } + """); + } - [TestMethod] - public void Serialize_LocalFunctions() - { - var code = @" -class Sample -{ - void Method() + [TestMethod] + public void Serialize_LocalFunctions() { - var fourty = 40; - Local(); - - int Local() => fourty + LocalStatic(); + const string code = """ + class Sample + { + void Method() + { + var fourty = 40; + Local(); + + int Local() => fourty + LocalStatic(); + + static int LocalStatic() => LocalStaticArg(1); + static int LocalStaticArg(int one) => one + 1; // Overloaded + } + } + """; + var dot = CfgSerializer.Serialize(TestHelper.CompileCfgCS(code)); + dot.Should().BeIgnoringLineEndings(""" + digraph "RoslynCfg" { + subgraph "cluster_1" { + label = "LocalLifetime region, Locals: fourty" + cfg0_block1 [shape=record label="{BLOCK #1|0#: SimpleAssignmentOperation: fourty = 40|1#: 0#.Target: LocalReferenceOperation: fourty = 40|1#: 0#.Value: LiteralOperation: 40|##########|0#: ExpressionStatementOperation: Local();|1#: 0#.Operation: InvocationOperation: Local: Local()|##########}"] + } + cfg0_block0 [shape=record label="{ENTRY #0}"] + cfg0_block2 [shape=record label="{EXIT #2}"] + subgraph "cluster_3" { + label = "RoslynCfg.Local" + cfg2_block0 [shape=record label="{ENTRY #0}"] + cfg2_block1 [shape=record label="{BLOCK #1|## BranchValue ##|0#: BinaryOperation: fourty + LocalStatic()|1#: 0#.LeftOperand: LocalReferenceOperation: fourty|1#: 0#.RightOperand: InvocationOperation: LocalStatic: LocalStatic()|##########}"] + cfg2_block2 [shape=record label="{EXIT #2}"] + } + subgraph "cluster_5" { + label = "RoslynCfg.LocalStatic" + cfg4_block0 [shape=record label="{ENTRY #0}"] + cfg4_block1 [shape=record label="{BLOCK #1|## BranchValue ##|0#: InvocationOperation: LocalStaticArg: LocalStaticArg(1)|1#: ArgumentOperation: 1|2#: 1#.Value: LiteralOperation: 1|##########}"] + cfg4_block2 [shape=record label="{EXIT #2}"] + } + subgraph "cluster_7" { + label = "RoslynCfg.LocalStaticArg" + cfg6_block0 [shape=record label="{ENTRY #0}"] + cfg6_block1 [shape=record label="{BLOCK #1|## BranchValue ##|0#: BinaryOperation: one + 1|1#: 0#.LeftOperand: ParameterReferenceOperation: one|1#: 0#.RightOperand: LiteralOperation: 1|##########}"] + cfg6_block2 [shape=record label="{EXIT #2}"] + } + cfg0_block0 -> cfg0_block1 + cfg0_block1 -> cfg0_block2 + cfg2_block0 -> cfg2_block1 + cfg2_block1 -> cfg2_block2 [label="Return"] + cfg4_block0 -> cfg4_block1 + cfg4_block1 -> cfg4_block2 [label="Return"] + cfg6_block0 -> cfg6_block1 + cfg6_block1 -> cfg6_block2 [label="Return"] + } - static int LocalStatic() => LocalStaticArg(1); - static int LocalStaticArg(int one) => one + 1; // Overloaded + """); } -}"; - var dot = CfgSerializer.Serialize(TestHelper.CompileCfgCS(code)); - dot.Should().BeIgnoringLineEndings(""" - digraph "RoslynCfg" { - subgraph "cluster_1" { - label = "LocalLifetime region, Locals: fourty" - cfg0_block1 [shape=record label="{BLOCK #1|0#: SimpleAssignmentOperation: fourty = 40|1#: 0#.Target: LocalReferenceOperation: fourty = 40|1#: 0#.Value: LiteralOperation: 40|##########|0#: ExpressionStatementOperation: Local();|1#: 0#.Operation: InvocationOperation: Local: Local()|##########}"] - } - cfg0_block0 [shape=record label="{ENTRY #0}"] - cfg0_block2 [shape=record label="{EXIT #2}"] - subgraph "cluster_3" { - label = "RoslynCfg.Local" - cfg2_block0 [shape=record label="{ENTRY #0}"] - cfg2_block1 [shape=record label="{BLOCK #1|## BranchValue ##|0#: BinaryOperation: fourty + LocalStatic()|1#: 0#.LeftOperand: LocalReferenceOperation: fourty|1#: 0#.RightOperand: InvocationOperation: LocalStatic: LocalStatic()|##########}"] - cfg2_block2 [shape=record label="{EXIT #2}"] - } - subgraph "cluster_5" { - label = "RoslynCfg.LocalStatic" - cfg4_block0 [shape=record label="{ENTRY #0}"] - cfg4_block1 [shape=record label="{BLOCK #1|## BranchValue ##|0#: InvocationOperation: LocalStaticArg: LocalStaticArg(1)|1#: ArgumentOperation: 1|2#: 1#.Value: LiteralOperation: 1|##########}"] - cfg4_block2 [shape=record label="{EXIT #2}"] - } - subgraph "cluster_7" { - label = "RoslynCfg.LocalStaticArg" - cfg6_block0 [shape=record label="{ENTRY #0}"] - cfg6_block1 [shape=record label="{BLOCK #1|## BranchValue ##|0#: BinaryOperation: one + 1|1#: 0#.LeftOperand: ParameterReferenceOperation: one|1#: 0#.RightOperand: LiteralOperation: 1|##########}"] - cfg6_block2 [shape=record label="{EXIT #2}"] - } - cfg0_block0 -> cfg0_block1 - cfg0_block1 -> cfg0_block2 - cfg2_block0 -> cfg2_block1 - cfg2_block1 -> cfg2_block2 [label="Return"] - cfg4_block0 -> cfg4_block1 - cfg4_block1 -> cfg4_block2 [label="Return"] - cfg6_block0 -> cfg6_block1 - cfg6_block1 -> cfg6_block2 [label="Return"] - } - """); - } - - [TestMethod] - public void Serialize_Lambdas() - { - var code = @" -class Sample -{ - void Method(int arg) + [TestMethod] + public void Serialize_Lambdas() { - Bar(x => { return arg + 1; }); - Bar(x => arg - 1); - } - private void Bar(System.Func f) { } -}"; - var dot = CfgSerializer.Serialize(TestHelper.CompileCfgCS(code)); - dot.Should().BeIgnoringLineEndings(""" + const string code = """ + class Sample + { + void Method(int arg) + { + Bar(x => { return arg + 1; }); + Bar(x => arg - 1); + } + private void Bar(System.Func f) { } + } + """; + var dot = CfgSerializer.Serialize(TestHelper.CompileCfgCS(code)); + dot.Should().BeIgnoringLineEndings(""" digraph "RoslynCfg" { cfg0_block0 [shape=record label="{ENTRY #0}"] cfg0_block1 [shape=record label="{BLOCK #1|0#: ExpressionStatementOperation: Bar(x =\> \{ return arg + 1; \});|1#: 0#.Operation: InvocationOperation: Bar: Bar(x =\> \{ return arg + 1; \})|2#: 1#.Instance: InstanceReferenceOperation: Bar|2#: ArgumentOperation: x =\> \{ return arg + 1; \}|3#: 2#.Value: DelegateCreationOperation: x =\> \{ return arg + 1; \}|4#: 3#.Target: FlowAnonymousFunctionOperation: x =\> \{ return arg + 1; \}|##########|0#: ExpressionStatementOperation: Bar(x =\> arg - 1);|1#: 0#.Operation: InvocationOperation: Bar: Bar(x =\> arg - 1)|2#: 1#.Instance: InstanceReferenceOperation: Bar|2#: ArgumentOperation: x =\> arg - 1|3#: 2#.Value: DelegateCreationOperation: x =\> arg - 1|4#: 3#.Target: FlowAnonymousFunctionOperation: x =\> arg - 1|##########}"] @@ -612,131 +627,134 @@ private void Bar(System.Func f) { } } """); - } + } - [TestMethod] - public void Serialize_CaptureId() - { - var code = @" -class Sample -{ - void Method(bool arg) + [TestMethod] + public void Serialize_CaptureId() { - bool b = arg && false; - b = arg || true; - } -}"; - var dot = CfgSerializer.Serialize(TestHelper.CompileCfgCS(code)); - dot.Should().BeIgnoringLineEndings(""" - digraph "RoslynCfg" { - subgraph "cluster_1" { - label = "LocalLifetime region, Locals: b" - subgraph "cluster_2" { - label = "LocalLifetime region, Captures: #Capture-0" - cfg0_block1 [shape=record label="{BLOCK #1|## BranchValue ##|0#: ParameterReferenceOperation: arg|##########}"] - cfg0_block2 [shape=record label="{BLOCK #2|0#: FlowCaptureOperation: #Capture-0: false|1#: 0#.Value: LiteralOperation: false|##########}"] - cfg0_block3 [shape=record label="{BLOCK #3|0#: FlowCaptureOperation: #Capture-0: arg|1#: 0#.Value: LiteralOperation: arg|##########}"] - cfg0_block4 [shape=record label="{BLOCK #4|0#: SimpleAssignmentOperation: b = arg && false|1#: 0#.Target: LocalReferenceOperation: b = arg && false|1#: 0#.Value: FlowCaptureReferenceOperation: #Capture-0: arg && false|##########}"] - } - subgraph "cluster_3" { - label = "LocalLifetime region, Captures: #Capture-1, #Capture-2" - cfg0_block5 [shape=record label="{BLOCK #5|0#: FlowCaptureOperation: #Capture-1: b|1#: 0#.Value: LocalReferenceOperation: b|##########|## BranchValue ##|0#: ParameterReferenceOperation: arg|##########}"] - cfg0_block6 [shape=record label="{BLOCK #6|0#: FlowCaptureOperation: #Capture-2: true|1#: 0#.Value: LiteralOperation: true|##########}"] - cfg0_block7 [shape=record label="{BLOCK #7|0#: FlowCaptureOperation: #Capture-2: arg|1#: 0#.Value: LiteralOperation: arg|##########}"] - cfg0_block8 [shape=record label="{BLOCK #8|0#: ExpressionStatementOperation: b = arg \|\| true;|1#: 0#.Operation: SimpleAssignmentOperation: b = arg \|\| true|2#: 1#.Target: FlowCaptureReferenceOperation: #Capture-1: b|2#: 1#.Value: FlowCaptureReferenceOperation: #Capture-2: arg \|\| true|##########}"] - } - } - cfg0_block0 [shape=record label="{ENTRY #0}"] - cfg0_block9 [shape=record label="{EXIT #9}"] - cfg0_block0 -> cfg0_block1 - cfg0_block1 -> cfg0_block2 [label="Else"] - cfg0_block1 -> cfg0_block3 [label="WhenFalse"] - cfg0_block2 -> cfg0_block4 - cfg0_block3 -> cfg0_block4 - cfg0_block4 -> cfg0_block5 - cfg0_block5 -> cfg0_block6 [label="Else"] - cfg0_block5 -> cfg0_block7 [label="WhenTrue"] - cfg0_block6 -> cfg0_block8 - cfg0_block7 -> cfg0_block8 - cfg0_block8 -> cfg0_block9 + const string code = """ + class Sample + { + void Method(bool arg) + { + bool b = arg && false; + b = arg || true; } + } + """; + var dot = CfgSerializer.Serialize(TestHelper.CompileCfgCS(code)); + dot.Should().BeIgnoringLineEndings(""" + digraph "RoslynCfg" { + subgraph "cluster_1" { + label = "LocalLifetime region, Locals: b" + subgraph "cluster_2" { + label = "LocalLifetime region, Captures: #Capture-0" + cfg0_block1 [shape=record label="{BLOCK #1|## BranchValue ##|0#: ParameterReferenceOperation: arg|##########}"] + cfg0_block2 [shape=record label="{BLOCK #2|0#: FlowCaptureOperation: #Capture-0: false|1#: 0#.Value: LiteralOperation: false|##########}"] + cfg0_block3 [shape=record label="{BLOCK #3|0#: FlowCaptureOperation: #Capture-0: arg|1#: 0#.Value: LiteralOperation: arg|##########}"] + cfg0_block4 [shape=record label="{BLOCK #4|0#: SimpleAssignmentOperation: b = arg && false|1#: 0#.Target: LocalReferenceOperation: b = arg && false|1#: 0#.Value: FlowCaptureReferenceOperation: #Capture-0: arg && false|##########}"] + } + subgraph "cluster_3" { + label = "LocalLifetime region, Captures: #Capture-1, #Capture-2" + cfg0_block5 [shape=record label="{BLOCK #5|0#: FlowCaptureOperation: #Capture-1: b|1#: 0#.Value: LocalReferenceOperation: b|##########|## BranchValue ##|0#: ParameterReferenceOperation: arg|##########}"] + cfg0_block6 [shape=record label="{BLOCK #6|0#: FlowCaptureOperation: #Capture-2: true|1#: 0#.Value: LiteralOperation: true|##########}"] + cfg0_block7 [shape=record label="{BLOCK #7|0#: FlowCaptureOperation: #Capture-2: arg|1#: 0#.Value: LiteralOperation: arg|##########}"] + cfg0_block8 [shape=record label="{BLOCK #8|0#: ExpressionStatementOperation: b = arg \|\| true;|1#: 0#.Operation: SimpleAssignmentOperation: b = arg \|\| true|2#: 1#.Target: FlowCaptureReferenceOperation: #Capture-1: b|2#: 1#.Value: FlowCaptureReferenceOperation: #Capture-2: arg \|\| true|##########}"] + } + } + cfg0_block0 [shape=record label="{ENTRY #0}"] + cfg0_block9 [shape=record label="{EXIT #9}"] + cfg0_block0 -> cfg0_block1 + cfg0_block1 -> cfg0_block2 [label="Else"] + cfg0_block1 -> cfg0_block3 [label="WhenFalse"] + cfg0_block2 -> cfg0_block4 + cfg0_block3 -> cfg0_block4 + cfg0_block4 -> cfg0_block5 + cfg0_block5 -> cfg0_block6 [label="Else"] + cfg0_block5 -> cfg0_block7 [label="WhenTrue"] + cfg0_block6 -> cfg0_block8 + cfg0_block7 -> cfg0_block8 + cfg0_block8 -> cfg0_block9 + } - """); - } + """); + } - [TestMethod] - public void Serialize_InvalidOperation() - { - var code = @" -class Sample -{ - void Method() + [TestMethod] + public void Serialize_InvalidOperation() { - undefined(); - } -}"; - var dot = CfgSerializer.Serialize(TestHelper.CompileCfgCS(code, ignoreErrors: true)); - dot.Should().BeIgnoringLineEndings(""" - digraph "RoslynCfg" { - cfg0_block0 [shape=record label="{ENTRY #0}"] - cfg0_block1 [shape=record label="{BLOCK #1|0#: ExpressionStatementOperation: undefined();|1#: 0#.Operation: INVALID: undefined()|2#: INVALID: undefined|##########}"] - cfg0_block2 [shape=record label="{EXIT #2}"] - cfg0_block0 -> cfg0_block1 - cfg0_block1 -> cfg0_block2 + const string code = """ + class Sample + { + void Method() + { + undefined(); } + } + """; + var dot = CfgSerializer.Serialize(TestHelper.CompileCfgCS(code, ignoreErrors: true)); + dot.Should().BeIgnoringLineEndings(""" + digraph "RoslynCfg" { + cfg0_block0 [shape=record label="{ENTRY #0}"] + cfg0_block1 [shape=record label="{BLOCK #1|0#: ExpressionStatementOperation: undefined();|1#: 0#.Operation: INVALID: undefined()|2#: INVALID: undefined|##########}"] + cfg0_block2 [shape=record label="{EXIT #2}"] + cfg0_block0 -> cfg0_block1 + cfg0_block1 -> cfg0_block2 + } - """); - } + """); + } - [TestMethod] - public void Serialize_TryCatchChain() - { - var code = @" -class Sample -{ - void Method() + [TestMethod] + public void Serialize_TryCatchChain() { - try { } - catch { } + const string code = """ + class Sample + { + void Method() + { + try { } + catch { } - try { } - catch { } - } -}"; - var dot = CfgSerializer.Serialize(TestHelper.CompileCfgCS(code)); - dot.Should().BeIgnoringLineEndings( -@"digraph ""RoslynCfg"" { -subgraph ""cluster_1"" { -label = ""TryAndCatch region"" -subgraph ""cluster_2"" { -label = ""Try region"" -cfg0_block1 [shape=record label=""{BLOCK #1}""] -} -subgraph ""cluster_3"" { -label = ""Catch region: object"" -cfg0_block2 [shape=record label=""{BLOCK #2}""] -} -} -subgraph ""cluster_4"" { -label = ""TryAndCatch region"" -subgraph ""cluster_5"" { -label = ""Try region"" -cfg0_block3 [shape=record label=""{BLOCK #3}""] -} -subgraph ""cluster_6"" { -label = ""Catch region: object"" -cfg0_block4 [shape=record label=""{BLOCK #4}""] -} -} -cfg0_block0 [shape=record label=""{ENTRY #0}""] -cfg0_block5 [shape=record label=""{EXIT #5}""] -cfg0_block0 -> cfg0_block1 -cfg0_block1 -> cfg0_block3 -cfg0_block2 -> cfg0_block3 -cfg0_block3 -> cfg0_block5 -cfg0_block4 -> cfg0_block5 -} -"); - } + try { } + catch { } + } + } + """; + var dot = CfgSerializer.Serialize(TestHelper.CompileCfgCS(code)); + dot.Should().BeIgnoringLineEndings(""" + digraph "RoslynCfg" { + subgraph "cluster_1" { + label = "TryAndCatch region" + subgraph "cluster_2" { + label = "Try region" + cfg0_block1 [shape=record label="{BLOCK #1}"] + } + subgraph "cluster_3" { + label = "Catch region: object" + cfg0_block2 [shape=record label="{BLOCK #2}"] + } + } + subgraph "cluster_4" { + label = "TryAndCatch region" + subgraph "cluster_5" { + label = "Try region" + cfg0_block3 [shape=record label="{BLOCK #3}"] + } + subgraph "cluster_6" { + label = "Catch region: object" + cfg0_block4 [shape=record label="{BLOCK #4}"] + } + } + cfg0_block0 [shape=record label="{ENTRY #0}"] + cfg0_block5 [shape=record label="{EXIT #5}"] + cfg0_block0 -> cfg0_block1 + cfg0_block1 -> cfg0_block3 + cfg0_block2 -> cfg0_block3 + cfg0_block3 -> cfg0_block5 + cfg0_block4 -> cfg0_block5 + } + + """); } } diff --git a/analyzers/tests/SonarAnalyzer.Test/CFG/Roslyn/RoslynLvaSerializerTest.cs b/analyzers/tests/SonarAnalyzer.Test/CFG/Roslyn/RoslynLvaSerializerTest.cs new file mode 100644 index 00000000000..a5b64a93791 --- /dev/null +++ b/analyzers/tests/SonarAnalyzer.Test/CFG/Roslyn/RoslynLvaSerializerTest.cs @@ -0,0 +1,329 @@ +/* + * 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. + */ + +using SonarAnalyzer.CFG; +using SonarAnalyzer.CFG.LiveVariableAnalysis; + +namespace SonarAnalyzer.Test.CFG.Roslyn; + +[TestClass] +public class RoslynLvaSerializerTest +{ + [TestMethod] + public void Serialize_TryCatchFinally() + { + const string code = """ + class Sample + { + void Method() + { + var value = 0; + try + { + Use(0); + value = 42; + } + catch + { + Use(value); + value = 1; + } + finally + { + Use(value); + } + } + + void Use(int v) {} + } + """; + var dot = CfgSerializer.Serialize(CreateLva(code)); + dot.Should().BeIgnoringLineEndings(""" + digraph "RoslynCfgLva" { + subgraph "cluster_1" { + label = "LocalLifetime region, Locals: value" + subgraph "cluster_2" { + label = "TryAndFinally region" + subgraph "cluster_3" { + label = "Try region" + subgraph "cluster_4" { + label = "TryAndCatch region" + subgraph "cluster_5" { + label = "Try region" + cfg0_block2 [shape=record label="{BLOCK #2|0#: ExpressionStatementOperation: Use(0);|1#: 0#.Operation: InvocationOperation: Use: Use(0)|2#: 1#.Instance: InstanceReferenceOperation: Use|2#: ArgumentOperation: 0|3#: 2#.Value: LiteralOperation: 0|##########|0#: ExpressionStatementOperation: value = 42;|1#: 0#.Operation: SimpleAssignmentOperation: value = 42|2#: 1#.Target: LocalReferenceOperation: value|2#: 1#.Value: LiteralOperation: 42|##########}"] + } + subgraph "cluster_6" { + label = "Catch region: object" + cfg0_block3 [shape=record label="{BLOCK #3|0#: ExpressionStatementOperation: Use(value);|1#: 0#.Operation: InvocationOperation: Use: Use(value)|2#: 1#.Instance: InstanceReferenceOperation: Use|2#: ArgumentOperation: value|3#: 2#.Value: LocalReferenceOperation: value|##########|0#: ExpressionStatementOperation: value = 1;|1#: 0#.Operation: SimpleAssignmentOperation: value = 1|2#: 1#.Target: LocalReferenceOperation: value|2#: 1#.Value: LiteralOperation: 1|##########}"] + } + } + } + subgraph "cluster_7" { + label = "Finally region" + cfg0_block4 [shape=record label="{BLOCK #4|0#: ExpressionStatementOperation: Use(value);|1#: 0#.Operation: InvocationOperation: Use: Use(value)|2#: 1#.Instance: InstanceReferenceOperation: Use|2#: ArgumentOperation: value|3#: 2#.Value: LocalReferenceOperation: value|##########}"] + } + } + cfg0_block1 [shape=record label="{BLOCK #1|0#: SimpleAssignmentOperation: value = 0|1#: 0#.Target: LocalReferenceOperation: value = 0|1#: 0#.Value: LiteralOperation: 0|##########}"] + } + cfg0_block0 [shape=record label="{ENTRY #0}"] + cfg0_block5 [shape=record label="{EXIT #5}"] + cfg0_block1 -> cfg0_block2 + cfg0_block2 -> cfg0_block3 [label="LVA"] + cfg0_block1 -> cfg0_block3 [label="LVA"] + cfg0_block2 -> cfg0_block4 [label="LVA"] + cfg0_block3 -> cfg0_block4 [label="LVA"] + cfg0_block4 -> NoDestination_cfg0_block4 [label="StructuredExceptionHandling"] + cfg0_block0 -> cfg0_block1 + cfg0_block4 -> cfg0_block5 [label="LVA"] + cfg0_block4 -> cfg0_block5 [label="LVA"] + cfg0_block2 -> cfg0_block5 + cfg0_block3 -> cfg0_block5 + } + + """); + } + + [TestMethod] + public void Serialize_TryCatchFinallyRethrow() + { + const string code = """ + class Sample + { + void Method() + { + var value = 0; + try + { + Use(0); + value = 42; + } + catch + { + Use(value); + value = 1; + throw; + } + finally + { + Use(value); + } + } + + void Use(int v) {} + } + """; + var dot = CfgSerializer.Serialize(CreateLva(code)); + dot.Should().BeIgnoringLineEndings(""" + digraph "RoslynCfgLva" { + subgraph "cluster_1" { + label = "LocalLifetime region, Locals: value" + subgraph "cluster_2" { + label = "TryAndFinally region" + subgraph "cluster_3" { + label = "Try region" + subgraph "cluster_4" { + label = "TryAndCatch region" + subgraph "cluster_5" { + label = "Try region" + cfg0_block2 [shape=record label="{BLOCK #2|0#: ExpressionStatementOperation: Use(0);|1#: 0#.Operation: InvocationOperation: Use: Use(0)|2#: 1#.Instance: InstanceReferenceOperation: Use|2#: ArgumentOperation: 0|3#: 2#.Value: LiteralOperation: 0|##########|0#: ExpressionStatementOperation: value = 42;|1#: 0#.Operation: SimpleAssignmentOperation: value = 42|2#: 1#.Target: LocalReferenceOperation: value|2#: 1#.Value: LiteralOperation: 42|##########}"] + } + subgraph "cluster_6" { + label = "Catch region: object" + cfg0_block3 [shape=record label="{BLOCK #3|0#: ExpressionStatementOperation: Use(value);|1#: 0#.Operation: InvocationOperation: Use: Use(value)|2#: 1#.Instance: InstanceReferenceOperation: Use|2#: ArgumentOperation: value|3#: 2#.Value: LocalReferenceOperation: value|##########|0#: ExpressionStatementOperation: value = 1;|1#: 0#.Operation: SimpleAssignmentOperation: value = 1|2#: 1#.Target: LocalReferenceOperation: value|2#: 1#.Value: LiteralOperation: 1|##########}"] + } + } + } + subgraph "cluster_7" { + label = "Finally region" + cfg0_block4 [shape=record label="{BLOCK #4|0#: ExpressionStatementOperation: Use(value);|1#: 0#.Operation: InvocationOperation: Use: Use(value)|2#: 1#.Instance: InstanceReferenceOperation: Use|2#: ArgumentOperation: value|3#: 2#.Value: LocalReferenceOperation: value|##########}"] + } + } + cfg0_block1 [shape=record label="{BLOCK #1|0#: SimpleAssignmentOperation: value = 0|1#: 0#.Target: LocalReferenceOperation: value = 0|1#: 0#.Value: LiteralOperation: 0|##########}"] + } + cfg0_block0 [shape=record label="{ENTRY #0}"] + cfg0_block5 [shape=record label="{EXIT #5}"] + cfg0_block1 -> cfg0_block2 + cfg0_block2 -> cfg0_block3 [label="LVA"] + cfg0_block1 -> cfg0_block3 [label="LVA"] + cfg0_block3 -> NoDestination_cfg0_block3 [label="Rethrow"] + cfg0_block2 -> cfg0_block4 [label="LVA"] + cfg0_block4 -> NoDestination_cfg0_block4 [label="StructuredExceptionHandling"] + cfg0_block0 -> cfg0_block1 + cfg0_block4 -> cfg0_block5 [label="LVA"] + cfg0_block2 -> cfg0_block5 + } + + """); + } + + [TestMethod] + public void Serialize_While() + { + const string code = """ + class Sample + { + void Method() + { + var value = 0; + while (value < 10) + { + Use(value); + value++; + } + } + + void Use(int v) {} + } + """; + var dot = CfgSerializer.Serialize(CreateLva(code)); + dot.Should().BeIgnoringLineEndings(""" + digraph "RoslynCfgLva" { + subgraph "cluster_1" { + label = "LocalLifetime region, Locals: value" + cfg0_block1 [shape=record label="{BLOCK #1|0#: SimpleAssignmentOperation: value = 0|1#: 0#.Target: LocalReferenceOperation: value = 0|1#: 0#.Value: LiteralOperation: 0|##########}"] + cfg0_block2 [shape=record label="{BLOCK #2|## BranchValue ##|0#: BinaryOperation: value \< 10|1#: 0#.LeftOperand: LocalReferenceOperation: value|1#: 0#.RightOperand: LiteralOperation: 10|##########}"] + cfg0_block3 [shape=record label="{BLOCK #3|0#: ExpressionStatementOperation: Use(value);|1#: 0#.Operation: InvocationOperation: Use: Use(value)|2#: 1#.Instance: InstanceReferenceOperation: Use|2#: ArgumentOperation: value|3#: 2#.Value: LocalReferenceOperation: value|##########|0#: ExpressionStatementOperation: value++;|1#: 0#.Operation: IncrementOrDecrementOperation: value++|2#: 1#.Target: LocalReferenceOperation: value|##########}"] + } + cfg0_block0 [shape=record label="{ENTRY #0}"] + cfg0_block4 [shape=record label="{EXIT #4}"] + cfg0_block0 -> cfg0_block1 + cfg0_block1 -> cfg0_block2 + cfg0_block3 -> cfg0_block2 + cfg0_block2 -> cfg0_block3 [label="Else"] + cfg0_block2 -> cfg0_block4 [label="WhenFalse"] + } + + """); + } + + [TestMethod] + public void Serialize_Foreach() + { + const string code = """ + class Sample + { + void Method(int[] values) + { + var value = 0; + foreach (var v in values) + { + Use(value); + value = v; + } + } + + void Use(int v) {} + } + """; + var dot = CfgSerializer.Serialize(CreateLva(code)); + dot.Should().BeIgnoringLineEndings(""" + digraph "RoslynCfgLva" { + subgraph "cluster_1" { + label = "LocalLifetime region, Locals: value" + subgraph "cluster_2" { + label = "LocalLifetime region, Captures: #Capture-0" + subgraph "cluster_3" { + label = "TryAndFinally region" + subgraph "cluster_4" { + label = "Try region" + subgraph "cluster_5" { + label = "LocalLifetime region, Locals: v" + cfg0_block4 [shape=record label="{BLOCK #4|0#: SimpleAssignmentOperation: var|1#: 0#.Target: LocalReferenceOperation: var|1#: 0#.Value: ConversionOperation: var|2#: 1#.Operand: PropertyReferenceOperation: var|3#: 2#.Instance: FlowCaptureReferenceOperation: #Capture-0: values|##########|0#: ExpressionStatementOperation: Use(value);|1#: 0#.Operation: InvocationOperation: Use: Use(value)|2#: 1#.Instance: InstanceReferenceOperation: Use|2#: ArgumentOperation: value|3#: 2#.Value: LocalReferenceOperation: value|##########|0#: ExpressionStatementOperation: value = v;|1#: 0#.Operation: SimpleAssignmentOperation: value = v|2#: 1#.Target: LocalReferenceOperation: value|2#: 1#.Value: LocalReferenceOperation: v|##########}"] + } + cfg0_block3 [shape=record label="{BLOCK #3|## BranchValue ##|0#: InvocationOperation: MoveNext: values|1#: 0#.Instance: FlowCaptureReferenceOperation: #Capture-0: values|##########}"] + } + subgraph "cluster_6" { + label = "Finally region, Captures: #Capture-1" + cfg0_block5 [shape=record label="{BLOCK #5|0#: FlowCaptureOperation: #Capture-1: values|1#: 0#.Value: ConversionOperation: values|2#: 1#.Operand: FlowCaptureReferenceOperation: #Capture-0: values|##########|## BranchValue ##|0#: IsNullOperation: values|1#: 0#.Operand: FlowCaptureReferenceOperation: #Capture-1: values|##########}"] + cfg0_block6 [shape=record label="{BLOCK #6|0#: InvocationOperation: Dispose: values|1#: 0#.Instance: FlowCaptureReferenceOperation: #Capture-1: values|##########}"] + cfg0_block7 [shape=record label="{BLOCK #7}"] + } + } + cfg0_block2 [shape=record label="{BLOCK #2|0#: FlowCaptureOperation: #Capture-0: values|1#: 0#.Value: InvocationOperation: GetEnumerator: values|2#: 1#.Instance: ConversionOperation: values|3#: 2#.Operand: ParameterReferenceOperation: values|##########}"] + } + cfg0_block1 [shape=record label="{BLOCK #1|0#: SimpleAssignmentOperation: value = 0|1#: 0#.Target: LocalReferenceOperation: value = 0|1#: 0#.Value: LiteralOperation: 0|##########}"] + } + cfg0_block0 [shape=record label="{ENTRY #0}"] + cfg0_block8 [shape=record label="{EXIT #8}"] + cfg0_block3 -> cfg0_block4 [label="Else"] + cfg0_block2 -> cfg0_block3 + cfg0_block4 -> cfg0_block3 + cfg0_block3 -> cfg0_block5 [label="LVA"] + cfg0_block3 -> cfg0_block5 [label="LVA"] + cfg0_block2 -> cfg0_block5 [label="LVA"] + cfg0_block4 -> cfg0_block5 [label="LVA"] + cfg0_block5 -> cfg0_block6 [label="Else"] + cfg0_block5 -> cfg0_block7 [label="WhenTrue"] + cfg0_block6 -> cfg0_block7 + cfg0_block7 -> NoDestination_cfg0_block7 [label="StructuredExceptionHandling"] + cfg0_block1 -> cfg0_block2 + cfg0_block0 -> cfg0_block1 + cfg0_block7 -> cfg0_block8 [label="LVA"] + cfg0_block3 -> cfg0_block8 [label="WhenFalse"] + } + + """); + } + + [TestMethod] + public void Serialize_IfElse() + { + const string code = """ + class Sample + { + void Method(int value) + { + if (value % 2 == 0) + { + Use(value); + } + else + { + Use(value); + } + } + + void Use(int v) {} + } + """; + var dot = CfgSerializer.Serialize(CreateLva(code)); + dot.Should().BeIgnoringLineEndings(""" + digraph "RoslynCfgLva" { + cfg0_block0 [shape=record label="{ENTRY #0}"] + cfg0_block1 [shape=record label="{BLOCK #1|## BranchValue ##|0#: BinaryOperation: value % 2 == 0|1#: 0#.LeftOperand: BinaryOperation: value % 2|2#: 1#.LeftOperand: ParameterReferenceOperation: value|2#: 1#.RightOperand: LiteralOperation: 2|1#: 0#.RightOperand: LiteralOperation: 0|##########}"] + cfg0_block2 [shape=record label="{BLOCK #2|0#: ExpressionStatementOperation: Use(value);|1#: 0#.Operation: InvocationOperation: Use: Use(value)|2#: 1#.Instance: InstanceReferenceOperation: Use|2#: ArgumentOperation: value|3#: 2#.Value: ParameterReferenceOperation: value|##########}"] + cfg0_block3 [shape=record label="{BLOCK #3|0#: ExpressionStatementOperation: Use(value);|1#: 0#.Operation: InvocationOperation: Use: Use(value)|2#: 1#.Instance: InstanceReferenceOperation: Use|2#: ArgumentOperation: value|3#: 2#.Value: ParameterReferenceOperation: value|##########}"] + cfg0_block4 [shape=record label="{EXIT #4}"] + cfg0_block0 -> cfg0_block1 + cfg0_block1 -> cfg0_block2 [label="Else"] + cfg0_block1 -> cfg0_block3 [label="WhenFalse"] + cfg0_block2 -> cfg0_block4 + cfg0_block3 -> cfg0_block4 + } + + """); + } + + private static RoslynLiveVariableAnalysis CreateLva(string code) + { + var cfg = TestHelper.CompileCfgCS(code); + return new RoslynLiveVariableAnalysis(cfg, CancellationToken.None); + } +} diff --git a/analyzers/tests/SonarAnalyzer.Test/CFG/Sonar/SonarCfgSerializerTest.cs b/analyzers/tests/SonarAnalyzer.Test/CFG/Sonar/SonarCfgSerializerTest.cs index d8df565573c..cbf830259a4 100644 --- a/analyzers/tests/SonarAnalyzer.Test/CFG/Sonar/SonarCfgSerializerTest.cs +++ b/analyzers/tests/SonarAnalyzer.Test/CFG/Sonar/SonarCfgSerializerTest.cs @@ -22,368 +22,394 @@ using SonarAnalyzer.CFG; using SonarAnalyzer.CFG.Sonar; -namespace SonarAnalyzer.Test.CFG.Sonar -{ - [TestClass] - public class SonarCfgSerializerTest - { - [TestMethod] - public void Serialize_Empty_Method() - { - var code = @" -class C +namespace SonarAnalyzer.Test.CFG.Sonar; + +[TestClass] +public class SonarCfgSerializerTest { - void Foo() + [TestMethod] + public void Serialize_Empty_Method() { + const string code = """ + class C + { + void Foo() + { + } + } + """; + var dot = CfgSerializer.Serialize(CreateMethodCfg(code), "Foo"); + + dot.Should().BeIgnoringLineEndings(""" + digraph "Foo" { + 0 [shape=record label="{EXIT}"] + } + + """); } -} -"; - var dot = CfgSerializer.Serialize(CreateMethodCfg(code), "Foo"); - dot.Should().BeIgnoringLineEndings(@"digraph ""Foo"" { -0 [shape=record label=""{EXIT}""] -} -"); - } - - [TestMethod] - public void Serialize_Branch_Jump() - { - var code = @" -class C -{ - void Foo(int a) + [TestMethod] + public void Serialize_Branch_Jump() { - switch (a) - { - case 1: - c1(); - break; - - case 2: - c2(); - break; - } + const string code = """ + class C + { + void Foo(int a) + { + switch (a) + { + case 1: + c1(); + break; + + case 2: + c2(); + break; + } + } + } + """; + var dot = CfgSerializer.Serialize(CreateMethodCfg(code), "Foo"); + + dot.Should().BeIgnoringLineEndings(""" + digraph "Foo" { + 0 [shape=record label="{BRANCH:SwitchStatement|a}"] + 1 [shape=record label="{BINARY:CaseSwitchLabel|a}"] + 2 [shape=record label="{JUMP:BreakStatement|c1|c1()}"] + 3 [shape=record label="{BINARY:CaseSwitchLabel|a}"] + 5 [shape=record label="{JUMP:BreakStatement|c2|c2()}"] + 4 [shape=record label="{EXIT}"] + 0 -> 1 + 1 -> 2 [label="True"] + 1 -> 3 [label="False"] + 2 -> 4 + 3 -> 5 [label="True"] + 3 -> 4 [label="False"] + 5 -> 4 + } + + """); } -} -"; - var dot = CfgSerializer.Serialize(CreateMethodCfg(code), "Foo"); - - dot.Should().BeIgnoringLineEndings(@"digraph ""Foo"" { -0 [shape=record label=""{BRANCH:SwitchStatement|a}""] -1 [shape=record label=""{BINARY:CaseSwitchLabel|a}""] -2 [shape=record label=""{JUMP:BreakStatement|c1|c1()}""] -3 [shape=record label=""{BINARY:CaseSwitchLabel|a}""] -5 [shape=record label=""{JUMP:BreakStatement|c2|c2()}""] -4 [shape=record label=""{EXIT}""] -0 -> 1 -1 -> 2 [label=""True""] -1 -> 3 [label=""False""] -2 -> 4 -3 -> 5 [label=""True""] -3 -> 4 [label=""False""] -5 -> 4 -} -"); - } - - [TestMethod] - public void Serialize_BinaryBranch_Simple() - { - var code = @" -class C -{ - void Foo() + + [TestMethod] + public void Serialize_BinaryBranch_Simple() { - if (true) - { - Bar(); - } + const string code = """ + class C + { + void Foo() + { + if (true) + { + Bar(); + } + } + void Bar() { } + } + """; + var dot = CfgSerializer.Serialize(CreateMethodCfg(code), "Foo"); + + dot.Should().BeIgnoringLineEndings(""" + digraph "Foo" { + 0 [shape=record label="{BINARY:TrueLiteralExpression|true}"] + 1 [shape=record label="{SIMPLE|Bar|Bar()}"] + 2 [shape=record label="{EXIT}"] + 0 -> 1 [label="True"] + 0 -> 2 [label="False"] + 1 -> 2 + } + + """); } - void Bar() { } -} -"; - var dot = CfgSerializer.Serialize(CreateMethodCfg(code), "Foo"); - - dot.Should().BeIgnoringLineEndings(@"digraph ""Foo"" { -0 [shape=record label=""{BINARY:TrueLiteralExpression|true}""] -1 [shape=record label=""{SIMPLE|Bar|Bar()}""] -2 [shape=record label=""{EXIT}""] -0 -> 1 [label=""True""] -0 -> 2 [label=""False""] -1 -> 2 -} -"); - } - - [TestMethod] - public void Serialize_Foreach_Binary_Simple() - { - var code = @" -class C -{ - void Foo() + + [TestMethod] + public void Serialize_Foreach_Binary_Simple() { - foreach (var i in items) - { - Bar(); - } + const string code = """ + class C + { + void Foo() + { + foreach (var i in items) + { + Bar(); + } + } + } + """; + var dot = CfgSerializer.Serialize(CreateMethodCfg(code), "Foo"); + + dot.Should().BeIgnoringLineEndings(""" + digraph "Foo" { + 0 [shape=record label="{FOREACH:ForEachStatement|items}"] + 1 [shape=record label="{BINARY:ForEachStatement}"] + 2 [shape=record label="{SIMPLE|Bar|Bar()}"] + 3 [shape=record label="{EXIT}"] + 0 -> 1 + 1 -> 2 [label="True"] + 1 -> 3 [label="False"] + 2 -> 1 + } + + """); } -} -"; - var dot = CfgSerializer.Serialize(CreateMethodCfg(code), "Foo"); - - dot.Should().BeIgnoringLineEndings(@"digraph ""Foo"" { -0 [shape=record label=""{FOREACH:ForEachStatement|items}""] -1 [shape=record label=""{BINARY:ForEachStatement}""] -2 [shape=record label=""{SIMPLE|Bar|Bar()}""] -3 [shape=record label=""{EXIT}""] -0 -> 1 -1 -> 2 [label=""True""] -1 -> 3 [label=""False""] -2 -> 1 -} -"); - } - - [TestMethod] - public void Serialize_Foreach_Binary_VarDeclaration() - { - var code = @" -namespace Namespace + + [TestMethod] + public void Serialize_Foreach_Binary_VarDeclaration() { - public class Test - { - public void ForEach((string key, string value)[] values) + const string code = """ + namespace Namespace { - foreach (var (key, value) in values) + public class Test { - string i = key; - string j = value; + public void ForEach((string key, string value)[] values) + { + foreach (var (key, value) in values) + { + string i = key; + string j = value; + } + } } + }; + """; + + var dot = CfgSerializer.Serialize(CreateMethodCfg(code), "ForEach"); + + dot.Should().BeIgnoringLineEndings(""" + digraph "ForEach" { + 0 [shape=record label="{FOREACH:ForEachVariableStatement|values}"] + 1 [shape=record label="{BINARY:ForEachVariableStatement}"] + 2 [shape=record label="{SIMPLE|key|i = key|value|j = value}"] + 3 [shape=record label="{EXIT}"] + 0 -> 1 + 1 -> 2 [label="True"] + 1 -> 3 [label="False"] + 2 -> 1 } - } - };"; - - var dot = CfgSerializer.Serialize(CreateMethodCfg(code), "ForEach"); - - dot.Should().BeIgnoringLineEndings(@"digraph ""ForEach"" { -0 [shape=record label=""{FOREACH:ForEachVariableStatement|values}""] -1 [shape=record label=""{BINARY:ForEachVariableStatement}""] -2 [shape=record label=""{SIMPLE|key|i = key|value|j = value}""] -3 [shape=record label=""{EXIT}""] -0 -> 1 -1 -> 2 [label=""True""] -1 -> 3 [label=""False""] -2 -> 1 -} -"); - } - - [TestMethod] - public void Serialize_For_Binary_Simple() - { - var code = @" -class C -{ - void Foo() + + """); + } + + [TestMethod] + public void Serialize_For_Binary_Simple() { - for (var i = 0; i < 10; i++) - { - Bar(); - } + const string code = """ + class C + { + void Foo() + { + for (var i = 0; i < 10; i++) + { + Bar(); + } + } + } + """; + var dot = CfgSerializer.Serialize(CreateMethodCfg(code), "Foo"); + + dot.Should().BeIgnoringLineEndings(""" + digraph "Foo" { + 0 [shape=record label="{FOR:ForStatement|0|i = 0}"] + 1 [shape=record label="{BINARY:ForStatement|i|10|i \< 10}"] + 2 [shape=record label="{SIMPLE|Bar|Bar()}"] + 4 [shape=record label="{SIMPLE|i|i++}"] + 3 [shape=record label="{EXIT}"] + 0 -> 1 + 1 -> 2 [label="True"] + 1 -> 3 [label="False"] + 2 -> 4 + 4 -> 1 + } + + """); } -} -"; - var dot = CfgSerializer.Serialize(CreateMethodCfg(code), "Foo"); - - dot.Should().BeIgnoringLineEndings(@"digraph ""Foo"" { -0 [shape=record label=""{FOR:ForStatement|0|i = 0}""] -1 [shape=record label=""{BINARY:ForStatement|i|10|i \< 10}""] -2 [shape=record label=""{SIMPLE|Bar|Bar()}""] -4 [shape=record label=""{SIMPLE|i|i++}""] -3 [shape=record label=""{EXIT}""] -0 -> 1 -1 -> 2 [label=""True""] -1 -> 3 [label=""False""] -2 -> 4 -4 -> 1 -} -"); - } - - [TestMethod] - public void Serialize_Jump_Using() - { - var code = @" -class C -{ - void Foo() + + [TestMethod] + public void Serialize_Jump_Using() { - using (x) - { - Bar(); - } + const string code = """ + class C + { + void Foo() + { + using (x) + { + Bar(); + } + } + } + """; + var dot = CfgSerializer.Serialize(CreateMethodCfg(code), "Foo"); + + dot.Should().BeIgnoringLineEndings(""" + digraph "Foo" { + 0 [shape=record label="{JUMP:UsingStatement|x}"] + 1 [shape=record label="{USING:UsingStatement|Bar|Bar()}"] + 2 [shape=record label="{EXIT}"] + 0 -> 1 + 1 -> 2 + } + + """); } -} -"; - var dot = CfgSerializer.Serialize(CreateMethodCfg(code), "Foo"); - - dot.Should().BeIgnoringLineEndings(@"digraph ""Foo"" { -0 [shape=record label=""{JUMP:UsingStatement|x}""] -1 [shape=record label=""{USING:UsingStatement|Bar|Bar()}""] -2 [shape=record label=""{EXIT}""] -0 -> 1 -1 -> 2 -} -"); - } - - [TestMethod] - public void Serialize_Lock_Simple() - { - var code = @" -class C -{ - void Foo() + + [TestMethod] + public void Serialize_Lock_Simple() { - lock (x) - { - Bar(); - } + const string code = """ + class C + { + void Foo() + { + lock (x) + { + Bar(); + } + } + } + """; + var dot = CfgSerializer.Serialize(CreateMethodCfg(code), "Foo"); + + dot.Should().BeIgnoringLineEndings(""" + digraph "Foo" { + 0 [shape=record label="{LOCK:LockStatement|x}"] + 1 [shape=record label="{SIMPLE|Bar|Bar()}"] + 2 [shape=record label="{EXIT}"] + 0 -> 1 + 1 -> 2 + } + + """); } -} -"; - var dot = CfgSerializer.Serialize(CreateMethodCfg(code), "Foo"); - - dot.Should().BeIgnoringLineEndings(@"digraph ""Foo"" { -0 [shape=record label=""{LOCK:LockStatement|x}""] -1 [shape=record label=""{SIMPLE|Bar|Bar()}""] -2 [shape=record label=""{EXIT}""] -0 -> 1 -1 -> 2 -} -"); - } - - [TestMethod] - public void Serialize_Lambda() - { - var code = @" -class C -{ - void Foo() + + [TestMethod] + public void Serialize_Lambda() { - Bar(x => - { - return 1 + 1; - }); + const string code = """ + class C + { + void Foo() + { + Bar(x => + { + return 1 + 1; + }); + } + } + """; + var dot = CfgSerializer.Serialize(CreateMethodCfg(code), "Foo"); + + dot.Should().BeIgnoringLineEndings(""" + digraph "Foo" { + 0 [shape=record label="{SIMPLE|Bar|x =\>\n \{\n return 1 + 1;\n \}|Bar(x =\>\n \{\n return 1 + 1;\n \})}"] + 1 [shape=record label="{EXIT}"] + 0 -> 1 + } + + """); } -} -"; - var dot = CfgSerializer.Serialize(CreateMethodCfg(code), "Foo"); - dot.Should().BeIgnoringLineEndings(@"digraph ""Foo"" { -0 [shape=record label=""{SIMPLE|Bar|x =\>\n \{\n return 1 + 1;\n \}|Bar(x =\>\n \{\n return 1 + 1;\n \})}""] -1 [shape=record label=""{EXIT}""] -0 -> 1 -} -"); - } - - [TestMethod] - public void Serialize_Range() - { - var code = @" -internal class Test -{ - public void Range() + [TestMethod] + public void Serialize_Range() { - Range r = 1..4; + const string code = """ + internal class Test + { + public void Range() + { + Range r = 1..4; + } + } + """; + var dot = CfgSerializer.Serialize(CreateMethodCfg(code), "Range"); + + dot.Should().BeIgnoringLineEndings(""" + digraph "Range" { + 0 [shape=record label="{SIMPLE|1..4|r = 1..4}"] + 1 [shape=record label="{EXIT}"] + 0 -> 1 + } + + """); } -} -"; - var dot = CfgSerializer.Serialize(CreateMethodCfg(code), "Range"); - dot.Should().BeIgnoringLineEndings(@"digraph ""Range"" { -0 [shape=record label=""{SIMPLE|1..4|r = 1..4}""] -1 [shape=record label=""{EXIT}""] -0 -> 1 -} -"); - } - - [TestMethod] - public void Serialize_Index() - { - var code = @" -internal class Test -{ - public void Index() + [TestMethod] + public void Serialize_Index() { - Index index = ^1; + const string code = """ + internal class Test + { + public void Index() + { + Index index = ^1; + } + } + """; + var dot = CfgSerializer.Serialize(CreateMethodCfg(code), "Index"); + + dot.Should().BeIgnoringLineEndings(""" + digraph "Index" { + 0 [shape=record label="{SIMPLE|^1|index = ^1}"] + 1 [shape=record label="{EXIT}"] + 0 -> 1 + } + + """); } -} -"; - var dot = CfgSerializer.Serialize(CreateMethodCfg(code), "Index"); - dot.Should().BeIgnoringLineEndings(@"digraph ""Index"" { -0 [shape=record label=""{SIMPLE|^1|index = ^1}""] -1 [shape=record label=""{EXIT}""] -0 -> 1 -} -"); - } - - [TestMethod] - public void Serialize_IndexInRange() - { - var code = @" -internal class Test -{ - public void Range() + [TestMethod] + public void Serialize_IndexInRange() { - Range range = ^2..^0; + const string code = """ + internal class Test + { + public void Range() + { + Range range = ^2..^0; + } + } + """; + var dot = CfgSerializer.Serialize(CreateMethodCfg(code), "Range"); + + dot.Should().BeIgnoringLineEndings(""" + digraph "Range" { + 0 [shape=record label="{SIMPLE|^2..^0|range = ^2..^0}"] + 1 [shape=record label="{EXIT}"] + 0 -> 1 + } + + """); } -} -"; - var dot = CfgSerializer.Serialize(CreateMethodCfg(code), "Range"); - dot.Should().BeIgnoringLineEndings(@"digraph ""Range"" { -0 [shape=record label=""{SIMPLE|^2..^0|range = ^2..^0}""] -1 [shape=record label=""{EXIT}""] -0 -> 1 -} -"); - } - - [TestMethod] - public void Serialize_RangeInIndexer() - { - var code = @" -internal class Test -{ - public void Range() + [TestMethod] + public void Serialize_RangeInIndexer() { - var ints = new[] { 1, 2 }; - var lastTwo = ints[^2..^1]; + const string code = """ + internal class Test + { + public void Range() + { + var ints = new[] { 1, 2 }; + var lastTwo = ints[^2..^1]; + } + } + """; + var dot = CfgSerializer.Serialize(CreateMethodCfg(code), "Range"); + + dot.Should().BeIgnoringLineEndings(""" + digraph "Range" { + 0 [shape=record label="{SIMPLE|new[] \{ 1, 2 \}|1|2|\{ 1, 2 \}|ints = new[] \{ 1, 2 \}|ints|^2..^1|ints[^2..^1]|lastTwo = ints[^2..^1]}"] + 1 [shape=record label="{EXIT}"] + 0 -> 1 + } + + """); } -} -"; - var dot = CfgSerializer.Serialize(CreateMethodCfg(code), "Range"); - dot.Should().BeIgnoringLineEndings(@"digraph ""Range"" { -0 [shape=record label=""{SIMPLE|new[] \{ 1, 2 \}|1|2|\{ 1, 2 \}|ints = new[] \{ 1, 2 \}|ints|^2..^1|ints[^2..^1]|lastTwo = ints[^2..^1]}""] -1 [shape=record label=""{EXIT}""] -0 -> 1 -} -"); - } - - private static IControlFlowGraph CreateMethodCfg(string code) - { - var (tree, model) = TestHelper.CompileIgnoreErrorsCS(code); - return CSharpControlFlowGraph.Create(tree.First().Body, model); - } + private static IControlFlowGraph CreateMethodCfg(string code) + { + var (tree, model) = TestHelper.CompileIgnoreErrorsCS(code); + return CSharpControlFlowGraph.Create(tree.First().Body, model); } } diff --git a/analyzers/tests/SonarAnalyzer.Test/LiveVariableAnalysis/RoslynLiveVariableAnalysisTest.cs b/analyzers/tests/SonarAnalyzer.Test/LiveVariableAnalysis/RoslynLiveVariableAnalysisTest.cs index d0b2b22c4f6..275f44bcc60 100644 --- a/analyzers/tests/SonarAnalyzer.Test/LiveVariableAnalysis/RoslynLiveVariableAnalysisTest.cs +++ b/analyzers/tests/SonarAnalyzer.Test/LiveVariableAnalysis/RoslynLiveVariableAnalysisTest.cs @@ -21,6 +21,7 @@ extern alias csharp; using csharp::SonarAnalyzer.Extensions; using Microsoft.CodeAnalysis.CSharp; +using SonarAnalyzer.CFG; using SonarAnalyzer.CFG.LiveVariableAnalysis; using SonarAnalyzer.CFG.Roslyn; @@ -837,7 +838,7 @@ public void NestedImplicitFinally_Lock_ForEach_LiveIn() var context = CreateContextCS(code, null, "string[] args"); context.Validate("Method(0);", LiveIn("args", null), LiveOut("args", "value", null)); // The null-named symbol is implicit `bool LockTaken` from the lock(args) statement context.Validate("Method(1);", LiveIn("value", null), LiveOut("value", null)); - context.Validate("Method(value);", LiveIn(null, "value"), LiveOut(new string[] { null })); + context.Validate("Method(value);", LiveIn(null, "value"), LiveOut([null])); context.Validate("Method(2);"); context.ValidateExit(); } @@ -1080,6 +1081,10 @@ public Context(string code, AnalyzerLanguage language, string localFunctionName { Cfg = TestHelper.CompileCfg(code, language, code.Contains("// Error CS"), localFunctionName); Lva = new RoslynLiveVariableAnalysis(Cfg, default); + const string Separator = "----------"; + Console.WriteLine(Separator); + Console.WriteLine(CfgSerializer.Serialize(Lva)); + Console.WriteLine(Separator); } public Context(string code, SyntaxKind syntaxKind)