Search code examples
c#roslynroslyn-code-analysis

How to find if a parameter to an invoked method is a variable (via "var/string") or an in-line string using Roslyn


I'm currently trying to find invocations of .ExecuteSqlCommand and examine the first value being passed to the sql param.

Here is an example of the differences I've found in our code base.

ExecuteSqlCommand("[sql statement here]");

vs.

var sql = "sql statement";
ExecuteSqlCommand(sql);

So far, I have this:

var invocations = root.DescendantNodes()
    .OfType<InvocationExpressionSyntax>()
    .Select(ie => ModelExtensions.GetSymbolInfo(model, ie).Symbol)
    .Where(symbol => symbol != null && symbol.Name == "ExecuteSqlCommand");

foreach (var invocation in invocations)
{
    var method = (IMethodSymbol)invocation;
    foreach (var param in method.Parameters)
    {
        //I can't quite seem to get information from IParameterSymbol about whether the param is a string literal, or a reference to a string via a variable.
    }
}

If the param is not a string, and instead, a var, then I'll need to get the value of the var (as much as it's defined at runtime).

I'm not too sure if this is a job for the SemanticModel or the SyntaxTree, but my GUESS is that the SemanticModel should have the richer information I need to let me discover what I'm looking for.

My overall goal is to interrogate the sql being passed to the ExecuteSqlCommand method.

Thanks!


Solution

  • The Syntax API could be used to extract the sql statement value but depends on whether the variable declaration (i.e. var sql = "sql statement";) is included as part of code submitted to the syntax tree.

    For example, if it's part of the same method implementation as where ExcuteSqlCommand() is called then you can first get the name of variable (i.e. sql) passed to it and use that to find the matching variable declaration statement within that same method. Finally, the sql statement value (i.e. "sql statement") can be extracted from that.

    The following code first checks if the sql value is passed as a string literal otherwise looks for the variable declaration. The assumption it's all within the same method:

            // whatever c# *method* code contains the sql. 
            // otherwise the root of the tree would need to be changed to filter to a specific single `MethodDeclarationSyntax`.
            string submittedCode = "public void SomeMethodContainingSql(){ ...//rest of code...";
    
            var tree = CSharpSyntaxTree.ParseText(submittedCode);
            var root = (CompilationUnitSyntax) tree.GetRoot();
    
            var arguments = root
                .DescendantNodes()
                .OfType<InvocationExpressionSyntax>()
                .First(node => node.DescendantNodes().OfType<IdentifierNameSyntax>()
                                   .First()
                                   .Identifier.Text == "ExecuteSqlCommand")
                .ArgumentList.DescendantNodes().ToList();
    
            string sqlStatementValue = "";
    
            var literalExpression = arguments.OfType<LiteralExpressionSyntax>().FirstOrDefault();
    
            if (literalExpression != null)
            {
                sqlStatementValue = literalExpression.GetText().ToString();
            }
            else
            {
                var variableName = arguments
                    .First()
                    .ToFullString();
    
                var variableDeclaration = root
                    .DescendantNodes()
                    .OfType<VariableDeclarationSyntax>()
                    .Single(node => node.DescendantNodes().OfType<VariableDeclaratorSyntax>()
                                        .First()
                                        .Identifier.Text == variableName);
    
                sqlStatementValue = variableDeclaration.DescendantNodes()
                    .OfType<LiteralExpressionSyntax>()
                    .First()
                    .DescendantTokens()
                    .First()
                    .Text;
            }
    

    Otherwise, may need to look for the variable declaration in other parts of the submitted code (ex. class fields, properties, other methods, etc.) which is a bit more cumbersome.