【问题标题】:How to find if a parameter to an invoked method is a variable (via "var/string") or an in-line string using Roslyn如何使用 Roslyn 查找被调用方法的参数是变量(通过“var/string”)还是内联字符串
【发布时间】:2019-06-10 17:40:55
【问题描述】:

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

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

ExecuteSqlCommand("[sql statement here]");

对比

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。

谢谢!

【问题讨论】:

    标签: c# roslyn roslyn-code-analysis


    【解决方案1】:

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

    它可以接受syntax nodeexpression。您仍然需要将变量的状态跟踪回它们的声明站点,并确定它们是否被赋予了常量表达式。

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

    【讨论】:

    • Johnathon,我假设 GetConstantValue 只有在变量声明为 const 时才有效?在这种情况下,“sql”变量被声明为“var”,所以我认为 GetConstantValue 对我没有帮助。
    • var 与此问题正交。如果有人编写字符串文字(或任何文字,例如 int),则可以在编译时知道该值。如果你给SemanticModel.GetConstantValueSyntaxNode 表示的表达式在编译时是已知的,它将返回它的值。您不需要执行this answer 建议的所有语法树遍历。 SemanticModel.GetConstantValue 正是为此目的而设计的
    【解决方案2】:

    语法 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;
            }
    

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

    【讨论】:

    • 雷,非常感谢!看起来语法 API 是这里的方法。我假设如果不是 StringLiteralExpression,将使用 IdentifierName 类型的语法调用 ExecuteSqlCommand,但是人们可以做各种疯狂的事情: ExecuteSqlCommand(sql1 + sql2): an AddExpression ExecuteSqlCommand(string.Format(" INSERT INTO {0}", sql)): an InovcationExpression ExecuteSqlCommand($"{sql1}{sql2}"): an InterpolatedStringExpression 所以我认为要真正支持它来处理任何人的事情需要做很多工作可以考虑扔它。
    • 很高兴这提供了一些帮助。同意这可能无法涵盖所有​​可能的情况,但希望能涵盖其中两个更常见的情况。
    【解决方案3】:

    不幸的是,在运行时定义值的常见情况下,Roslyn 无法提供变量的值,因为 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
                    }
                }
            }
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-10-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-12-05
      • 2019-04-08
      • 1970-01-01
      相关资源
      最近更新 更多