如何使用Roslyn查找被调用方法的参数是变量(通过“ var / string”)还是嵌入式字符串

时间:2019-06-10 17:40:55

标签: roslyn roslyn-code-analysis

我目前正在尝试查找.ExecuteSqlCommand的调用,并检查传递给sql参数的第一个值。

这是我在我们的代码库中发现的差异的一个示例。

ExecuteSqlCommand("[sql statement here]");

vs。

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

到目前为止,我有这个:

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.
    }
}

如果参数不是字符串,而是var,那么我将需要获取var的值(与运行时定义的一样多)。

我不太确定这是SemanticModel还是SyntaxTree的工作,但是我的想法是SemanticModel应该具有更丰富的信息,我需要让我发现自己想要的东西。

我的总体目标是询问传递给ExecuteSqlCommand方法的sql。

谢谢!

3 个答案:

答案 0 :(得分:2)

SemanticModel.GetConstantValue是我们用于处理这种情况的API。

它既可以接受syntax node也可以接受expression。您仍然需要跟踪变量的状态回到它们的声明位置,并确定它们是否被赋予了常量表达式。

我将使用SemanticModel.GetSymbolInfo。Symbol?。DeclaringSyntaxReferences。First()查找变量的声明位置,然后检查其是否为常量表达式。

答案 1 :(得分:1)

语法API可用于提取sql语句值,但取决于是否将变量声明(即var sql = "sql statement";)作为提交给语法树的代码的一部分而包括在内。

例如,如果它与调用ExcuteSqlCommand()的方法实现相同,则可以首先获取传递给它的变量名称(即sql),然后使用该名称来查找匹配项在同一方法中的变量声明语句 。最后,可以从中提取sql语句值(即"sql statement")。

以下代码首先检查sql值是否作为字符串文字传递,否则将查找变量声明。假设全部在同一方法内:

        // 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;
        }

否则,可能需要在提交的代码的其他部分(例如,类字段,属性,其他方法等)中查找变量声明,这会比较麻烦。

答案 2 :(得分:0)

不幸的是,在通常情况下,在运行时定义值的情况下,Roslyn无法提供变量的值,因为Roslyn实际上不知道可能从程序外部传递的所有可能值,因此不计算它们,依此类推。但是,如果您可以将所需的情况限制为在字符串中声明并初始化的内联字符串或变量,罗斯林可能会为您提供帮助:

  • 您需要保留InvocationExpressionSyntax(或直接保留其第一个参数)
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);
  • 您不需要检查方法参数,而是检查传递给它的方法参数
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
                }
            }
        }
    }
}