public void Serialize_Jump_Using() { var code = @" class C { void Foo() { using (x) { Bar(); } } } "; var dot = CfgSerializer.Serialize("Foo", GetCfgForMethod(code, "Foo")); dot.Should().BeIgnoringLineEndings(@"digraph ""Foo"" { 0 [shape=record label=""{JUMP:UsingStatement|x}""] 0 -> 1 1 [shape=record label=""{USING:UsingStatement|Bar|Bar()}""] 1 -> 2 2 [shape=record label=""{EXIT}""] } "); }
public void Serialize_BinaryBranch_Simple() { var code = @" class C { void Foo() { if (true) { Bar(); } } void Bar() { } } "; var dot = CfgSerializer.Serialize("Foo", GetCfgForMethod(code, "Foo")); dot.Should().BeIgnoringLineEndings(@"digraph ""Foo"" { 0 [shape=record label=""{BINARY:TrueLiteralExpression|true}""] 0 -> 1 [label=""True""] 0 -> 2 [label=""False""] 1 [shape=record label=""{SIMPLE|Bar|Bar()}""] 1 -> 2 2 [shape=record label=""{EXIT}""] } "); }
public void Serialize_Lock_Simple() { var code = @" class C { void Foo() { lock (x) { Bar(); } } } "; var dot = CfgSerializer.Serialize("Foo", GetCfgForMethod(code, "Foo")); dot.Should().BeIgnoringLineEndings(@"digraph ""Foo"" { 0 [shape=record label=""{LOCK:LockStatement|x}""] 0 -> 1 1 [shape=record label=""{SIMPLE|Bar|Bar()}""] 1 -> 2 2 [shape=record label=""{EXIT}""] } "); }
public void Serialize_For_Binary_Simple() { var code = @" class C { void Foo() { for (var i = 0; i < 10; i++) { Bar(); } } } "; var dot = CfgSerializer.Serialize("Foo", GetCfgForMethod(code, "Foo")); dot.Should().BeIgnoringLineEndings(@"digraph ""Foo"" { 0 [shape=record label=""{FOR:ForStatement|0|i = 0}""] 0 -> 1 1 [shape=record label=""{BINARY:ForStatement|i|10|i \< 10}""] 1 -> 2 [label=""True""] 1 -> 3 [label=""False""] 2 [shape=record label=""{SIMPLE|Bar|Bar()}""] 2 -> 4 4 [shape=record label=""{SIMPLE|i|i++}""] 4 -> 1 3 [shape=record label=""{EXIT}""] } "); }
public void Serialize_Foreach_Binary_VarDeclaration() { var code = @" namespace Namespace { public class Test { public void ForEach((string key, string value)[] values) { foreach (var (key, value) in values) { string i = key; string j = value; } } } };"; var dot = CfgSerializer.Serialize("ForEach", GetCfgForMethod(code, "ForEach")); dot.Should().BeIgnoringLineEndings(@"digraph ""ForEach"" { 0 [shape=record label=""{FOREACH:ForEachVariableStatement|values}""] 0 -> 1 1 [shape=record label=""{BINARY:ForEachVariableStatement}""] 1 -> 2 [label=""True""] 1 -> 3 [label=""False""] 2 [shape=record label=""{SIMPLE|key|i = key|value|j = value}""] 2 -> 1 3 [shape=record label=""{EXIT}""] } "); }
public void Serialize_Foreach_Binary_Simple() { var code = @" class C { void Foo() { foreach (var i in items) { Bar(); } } } "; var dot = CfgSerializer.Serialize("Foo", GetCfgForMethod(code, "Foo")); dot.Should().BeIgnoringLineEndings(@"digraph ""Foo"" { 0 [shape=record label=""{FOREACH:ForEachStatement|items}""] 0 -> 1 1 [shape=record label=""{BINARY:ForEachStatement}""] 1 -> 2 [label=""True""] 1 -> 3 [label=""False""] 2 [shape=record label=""{SIMPLE|Bar|Bar()}""] 2 -> 1 3 [shape=record label=""{EXIT}""] } "); }
public void Serialize_Branch_Jump() { var code = @" class C { void Foo(int a) { switch (a) { case 1: c1(); break; case 2: c2(); break; } } } "; var dot = CfgSerializer.Serialize("Foo", GetCfgForMethod(code, "Foo")); dot.Should().BeIgnoringLineEndings(@"digraph ""Foo"" { 0 [shape=record label=""{BRANCH:SwitchStatement|a}""] 0 -> 1 0 -> 2 0 -> 3 2 [shape=record label=""{JUMP:BreakStatement|c2|c2()}""] 2 -> 3 1 [shape=record label=""{JUMP:BreakStatement|c1|c1()}""] 1 -> 3 3 [shape=record label=""{EXIT}""] } "); }
public void Serialize_Empty_Method() { var code = @" class C { void Foo() { } } "; var dot = CfgSerializer.Serialize("Foo", GetCfgForMethod(code, "Foo")); dot.Should().BeIgnoringLineEndings(@"digraph ""Foo"" { 0 [shape=record label=""{EXIT}""] } "); }
public void Serialize_IndexInRange() { var code = @" internal class Test { public void Range() { Range range = ^2..^0; } } "; var dot = CfgSerializer.Serialize("Range", GetCfgForMethod(code, "Range")); dot.Should().BeIgnoringLineEndings(@"digraph ""Range"" { 0 [shape=record label=""{SIMPLE|^2..^0|range = ^2..^0}""] 0 -> 1 1 [shape=record label=""{EXIT}""] } "); }
public void Serialize_Index() { var code = @" internal class Test { public void Index() { Index index = ^1; } } "; var dot = CfgSerializer.Serialize("Index", GetCfgForMethod(code, "Index")); dot.Should().BeIgnoringLineEndings(@"digraph ""Index"" { 0 [shape=record label=""{SIMPLE|^1|index = ^1}""] 0 -> 1 1 [shape=record label=""{EXIT}""] } "); }
public void Serialize_RangeInIndexer() { var code = @" internal class Test { public void Range() { var ints = new[] { 1, 2 }; var lastTwo = ints[^2..^1]; } } "; var dot = CfgSerializer.Serialize("Range", GetCfgForMethod(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]}""] 0 -> 1 1 [shape=record label=""{EXIT}""] } "); }
public void Serialize_Lambda() { var code = @" class C { void Foo() { Bar(x => { return 1 + 1; }); } } "; var dot = CfgSerializer.Serialize("Foo", GetCfgForMethod(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 \})}""] 0 -> 1 1 [shape=record label=""{EXIT}""] } "); }
private void WriteUCFG <TDeclarationSyntax>(SyntaxNodeAnalysisContext context, Func <TDeclarationSyntax, CSharpSyntaxNode> getBody) where TDeclarationSyntax : SyntaxNode { var declaration = (TDeclarationSyntax)context.Node; var symbol = context.SemanticModel.GetDeclaredSymbol(declaration); var methodSymbol = (symbol is IPropertySymbol propertySymbol) ? propertySymbol.GetMethod // We are in PropertyDeclarationSyntax : symbol as IMethodSymbol; // all other are methods if (methodSymbol == null || methodSymbol.IsAbstract || methodSymbol.IsExtern || !CSharpControlFlowGraph.TryGet(getBody(declaration), context.SemanticModel, out var cfg)) { return; } var ucfg = new UniversalControlFlowGraphBuilder() .Build(context.SemanticModel, declaration, methodSymbol, cfg); if (IsValid(ucfg)) { var fileName = $"{projectBuildId}_{Interlocked.Increment(ref protobufFileIndex)}"; WriteProtobuf(ucfg, Path.Combine(protobufDirectory, $"ucfg_{fileName}.pb")); if (ShouldGenerateDot) { WriteDot(Path.Combine(protobufDirectory, $"ucfg_{fileName}.dot"), writer => UcfgSerializer.Serialize(ucfg, writer)); WriteDot(Path.Combine(protobufDirectory, $"cfg_{fileName}.dot"), writer => CfgSerializer.Serialize(ucfg.MethodId, cfg, writer)); } } }
private void WriteUcfg <TDeclarationSyntax>(SyntaxNodeAnalysisContext context, Func <TDeclarationSyntax, CSharpSyntaxNode> getBody) where TDeclarationSyntax : SyntaxNode { var declaration = (TDeclarationSyntax)context.Node; var symbol = context.SemanticModel.GetDeclaredSymbol(declaration); var methodSymbol = (symbol is IPropertySymbol propertySymbol) ? propertySymbol.GetMethod // We are in PropertyDeclarationSyntax : symbol as IMethodSymbol; // all other are methods if (methodSymbol == null || methodSymbol.IsAbstract || methodSymbol.IsExtern || !CSharpControlFlowGraph.TryGet(getBody(declaration), context.SemanticModel, out var cfg)) { return; } try { var ucfg = new UcfgFactory(context.SemanticModel) .Create(declaration, methodSymbol, cfg); if (!IsValid(ucfg)) { return; } var fileName = $"{this.projectBuildId}_{Interlocked.Increment(ref this.protobufFileIndex)}"; WriteProtobuf(ucfg, Path.Combine(this.protobufDirectory, $"ucfg_{fileName}.pb")); if (ShouldGenerateDot) { WriteDot(Path.Combine(this.protobufDirectory, $"ucfg_{fileName}.dot"), writer => UcfgSerializer.Serialize(ucfg, writer)); WriteDot(Path.Combine(this.protobufDirectory, $"cfg_{fileName}.dot"), writer => CfgSerializer.Serialize(ucfg.MethodId, cfg, writer)); } } catch (UcfgException) when(!DebugHelper.IsInternalDebuggingContext()) { // Ignore the exception in production } }
public static string GetCfgGraph(string code, string methodName) { return(CfgSerializer.Serialize(methodName, GetCfgForMethod(code, methodName))); }