简体   繁体   中英

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!

SemanticModel.GetConstantValue is the API we have for handling this situation.

It can accept both a syntax node and an expression . You will still need to track the state of variables back to their declaration sites and determine if they were given a constant expression.

I would use SemanticModel.GetSymbolInfo .Symbol?. DeclaringSyntaxReferences .First() to find the declaration site of a variable and then check to see if its a constant expression.

The Syntax API could be used to extract the sql statement value but depends on whether the variable declaration (ie 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 (ie sql ) passed to it and use that to find the matching variable declaration statement within that same method . Finally, the sql statement value (ie "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.

Unfortunately Roslyn cannot provide a variable's value in a common cases when values is defined at runtime, because Roslyn actually doesn't know all possible values which may pass from outside the program, it doesn't calculate them and so on so forth. But if you can limit the needed cases to a inlining strings or variables which was declared and initialized at strings, Roslyn may help you with this:

  • You need to keep InvocationExpressionSyntax (or directly their first arguments)
var invocations = root.DescendantNodes()
    .OfType<InvocationExpressionSyntax>()
    .Select(ie => (ModelExtensions.GetSymbolInfo(model, ie).Symbol, ie))
    // Would be better to compare not method's name but it FQN
    .Where((symbol, node)) => symbol != null && symbol.Name == "ExecuteSqlCommand" && 
            symbol.Parameters.Length == 1 && 
            symbol.Parameters[0].Type.SpecialType == SpecialType.System_String && 
            node.ArgumentList.Arguments.Count == 1)
    .Select((symbol, node) => node);
  • You need to check not method parameter, but method argument which was passed to it
foreach (var invocation in invocations)
{
    var argument = invocation .ArgumentList.Arguments[0];
    if (argument is LiteralExpressionSyntax literal && literal.IsKind(SyntaxKind.StringLiteralExpression))
    {
        // You find invocation of kind `ExecuteSqlCommand("sql")`
    }
    else
    {
        var argSymbol = ModelExtensions.GetSymbolInfo(model, argument).Symbol;
        if (!(argSymbol is null) && (argSymbol.Kind == SymbolKind.Field || argSymbol.Kind == SymbolKind.Property || argSymbol.Kind == SymbolKind.Local))
        {
            if (argSymbol.DeclaringSyntaxReferences.Length == 1)
            {
                var declarationNode = argSymbol.DeclaringSyntaxReferences[0].GetSyntax();

                // I'm actually don't remember what exactlly `GetSyntax` returns for fields or locals: 
                // VariableDeclaratorSyntax or one of it parent LocalDeclarationStatementSyntax and FieldDeclarationSyntax, but if it returns declarations you also can 
                // get from them VariableDeclaratorSyntax that you need, it's just be a more deep pattern matching

                if (declarationNode is VariableDeclaratorSyntax declaratorSyntax && 
                    declaratorSyntax.EqualsValueClauseSyntax?.Value is LiteralExpressionSyntax literal2 && literal2.IsKind(SyntaxKind.StringLiteralExpression) )
                {
                    // You find invocation of kind `ExecuteSqlCommand(variable)` where variable is local variable or field 
                }
                else if (declarationNode is PropertyDeclarationSyntax property)
                {
                    // You can do the same things for properties initializer or expression body that you was do for fields and locals to check your case,
                    // but it doesn't work for property with get/set accessors in a common cases
                }
            }
        }
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM