Learn Roslyn Now: Part 9 Control Flow Analysis

Control flow analysis is used to understand the various entry and exit points within a block of code and to answer questions about reachability. If we’re analyzing a method, we might be interested in all the points at which we can return out of the method. If we’re analyzing a for-loop, we  might be interested in all the places we break or continue.

We trigger control flow analysis via an extension method on the SemanticModel. This returns an instance of ControlFlowAnalysis to us that exposes the following properties:

  • EntryPoints – The set of statements inside the region that are the destination of branches outside the region.
  • ExitPoints – The set of statements inside a region that jump to locations outside the region.
  • EndPointIsReachable – Indicates whether a region completes normally. Returns true if and only if the end of the last statement is reachable or the entire region contains no statements.
  • StartPointIsReachable – Indicates whether a region can begin normally.
  • ReturnStatements – The set of returns statements within a region.
  • Succeeded – Returns true if and only if analysis was successful. Analysis can fail if the region does not properly span a single expression, a single statement, or a contiguous series of statements within the enclosing block.

Basic usage of the API:


var tree = CSharpSyntaxTree.ParseText(@"
class C
{
void M()
{
for (int i = 0; i < 10; i++)
{
if (i == 3)
continue;
if (i == 8)
break;
}
}
}
");
var Mscorlib = PortableExecutableReference.CreateFromAssembly(typeof(object).Assembly);
var compilation = CSharpCompilation.Create("MyCompilation",
syntaxTrees: new[] { tree }, references: new[] { Mscorlib });
var model = compilation.GetSemanticModel(tree);
var firstFor = tree.GetRoot().DescendantNodes().OfType<ForStatementSyntax>().Single();
ControlFlowAnalysis result = model.AnalyzeControlFlow(firstFor.Statement);
Console.WriteLine(result.Succeeded); //True
Console.WriteLine(result.ExitPoints.Count()); //2 – continue, and break

Alternatively, we can specify two statements and analyze the statements between the two. The following example demonstrates this and the usage of EntryPoints:


var tree = CSharpSyntaxTree.ParseText(@"
class C
{
void M(int x)
{
L1: ; // 1
if (x == 0) goto L1; //firstIf
if (x == 1) goto L2;
if (x == 3) goto L3;
L3: ; //label3
L2: ; // 2
if(x == 4) goto L3;
}
}
");
var Mscorlib = PortableExecutableReference.CreateFromAssembly(typeof(object).Assembly);
var compilation = CSharpCompilation.Create("MyCompilation",
syntaxTrees: new[] { tree }, references: new[] { Mscorlib });
var model = compilation.GetSemanticModel(tree);
//Choose first and last statements
var firstIf = tree.GetRoot().DescendantNodes().OfType<IfStatementSyntax>().First();
var label3 = tree.GetRoot().DescendantNodes().OfType<LabeledStatementSyntax>().Skip(1).Take(1).Single();
ControlFlowAnalysis result = model.AnalyzeControlFlow(firstIf, label3);
Console.WriteLine(result.EntryPoints); //1 – Label 3 is a candidate entry point within these statements
Console.WriteLine(result.ExitPoints); //2 – goto L1 and goto L2 and candidate exit points

In the above example, we see an example of a possible entry point label L3. To the best of my knowledge, labels are the only possible entry points.

Finally, we’ll take a look at answering questions about reachability. In the following, neither the start point or the end point is reachable:


var tree = CSharpSyntaxTree.ParseText(@"
class C
{
void M(int x)
{
return;
if(x == 0) //-+ Start is unreachable
System.Console.WriteLine(""Hello""); // |
L1: //-+ End is unreachable
}
}
");
var Mscorlib = PortableExecutableReference.CreateFromAssembly(typeof(object).Assembly);
var compilation = CSharpCompilation.Create("MyCompilation",
syntaxTrees: new[] { tree }, references: new[] { Mscorlib });
var model = compilation.GetSemanticModel(tree);
//Choose first and last statements
var firstIf = tree.GetRoot().DescendantNodes().OfType<IfStatementSyntax>().Single();
var label1 = tree.GetRoot().DescendantNodes().OfType<LabeledStatementSyntax>().Single();
ControlFlowAnalysis result = model.AnalyzeControlFlow(firstIf, label1);
Console.WriteLine(result.StartPointIsReachable); //False
Console.WriteLine(result.EndPointIsReachable); //False

Overall, the Control Flow API seems a lot more intuitive than the Data Flow Analysis API. It requires less knowledge of the C# specification and is straightforward to work with. At Code Connect, we’ve been using it when rewriting and logging methods. Although it looks like no one has experimented much with this API, I’m really interested to see what uses others will come up with.

3 thoughts on “Learn Roslyn Now: Part 9 Control Flow Analysis

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s