Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to determine the potential value of a variable using Roslyn?

Tags:

.net

roslyn

I am using the Roslyn CTP and I am trying to determine if the value of a variable in a class has a value. Lets say I am trying to detect when someone is using a BinaryExpressionSyntax to determine if a string is equal to nothing "".

For example:

private void StringLiteral(string a)
        {
            if (a == "")   //flagged because we do not see a explicit set of 'a'
            {
                Console.WriteLine("Empty String");
            }
            a="42";
            if (a == "")  //not flagged because 'a' has been set
            {
                Console.WriteLine("Empty String");
            }
}

I can get the BinaryExpressionSyntax and examine both the left and right sides using Semantic and Syntax but I don't see anything in the debugger that tracks the possible value. I know this could get sketchy e.g.:

private void BooleanTest(string a, bool b)
        {

            if (b)   
            {
                a="";
            }
            if (!b)  
            {
                a="42";
            }
             if (a == "")  // Maybe 'a' is set maybe it isn't so we will probably not flag this one
            {
                Console.WriteLine("What Do I Do?");
            }
}

Is it possible with the Roslyn CTP to determine if a potential value has been set on a variable? I would think that this would come into play a lot in the StyleCOp/FxCop rules.

like image 868
Jay Avatar asked Oct 08 '22 03:10

Jay


1 Answers

You could try to use SemanticModel.AnalyzeRegionDataFlow() for this. You give it a text span and it tells you information about the dataflow in that piece of text, including what variables are sure to be assigned in the AlwaysAssigned property.

The whole code (assuming you have a compilation unit, not just a method) could look like this:

var tree = SyntaxTree.ParseCompilationUnit(code);

var compilation = Compilation.Create("foo")
    .AddSyntaxTrees(tree);

var semanticModel = compilation.GetSemanticModel(tree);

var methods = tree.Root.DescendentNodes().OfType<MethodDeclarationSyntax>();

foreach (var method in methods)
{
    Console.WriteLine(method.Identifier.ValueText);

    var binaryExpressions = method.DescendentNodes()
        .OfType<BinaryExpressionSyntax>()
        .Where(e => e.Kind == SyntaxKind.EqualsExpression);

    foreach (var binaryExpression in binaryExpressions)
    {
        Console.WriteLine(binaryExpression);

        // get TextSpan that starts at the beginning of the method body
        // and ends at the beginning of the binary expression
        var textBefore = TextSpan.FromBounds(
            method.BodyOpt.Span.Start, binaryExpression.Span.Start);

        //Console.WriteLine(tree.Root.GetFullTextAsIText().GetText(textBefore));

        var alwaysAssigned = semanticModel.AnalyzeRegionDataFlow(textBefore)
            .AlwaysAssigned;

        var isAAlwaysAssigned = alwaysAssigned.Any(s => s.Name == "a");

        Console.WriteLine(isAAlwaysAssigned);
    }

    Console.WriteLine();
}

For your first method, it correctly detects that a wasn't assigned before the first if, but is certainly assigned before the second if.

For your second method, Roslyn seems to think that a doesn't have to be assigned. But that is in line with how the C# compiler behaves. For example, the following method won't compile:

private void BooleanTest(bool b)
{
    string a;
    if (b)
        a = "";
    if (!b)
        a = "42";
    if (a == "")
        Console.WriteLine("What Do I Do?");
}

But if you replace the second if with else, it will compile. And similarly, Roslyn will detect that the variable is always assigned.

like image 125
svick Avatar answered Oct 12 '22 10:10

svick