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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
Can you tell me why this end point is unreacheable ?
int TestMethod (int i)
{
if (i > 0)
return 1;
else
return 0;
}
Doesn’t make sense to me. I’ve opened an issue on Roslyn’s GitHub: https://github.com/dotnet/roslyn/issues/4630
Neal Gafter provided a response.
From: https://msdn.microsoft.com/en-us/library/aa664759(v=vs.71)
> Because a return statement unconditionally transfers control elsewhere, the end point of a return statement is never reachable.
Maybe some clarification is required in the documentation.