Как определить, является ли параметр вызываемого метода переменной (через «var / string») или встроенной строкой, используя Roslyn - PullRequest
2 голосов
/ 10 июня 2019

В настоящее время я пытаюсь найти вызовы .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.
    }
}

Если параметр не является строкой, а вместо этого, переменная, то яМне нужно будет получить значение переменной (столько, сколько она определена во время выполнения).

Я не слишком уверен, является ли это заданием для SemanticModel или SyntaxTree, но мой GUESS заключается в том, чтоSemanticModel должен иметь более подробную информацию, необходимую для того, чтобы я мог найти то, что я ищу.

Моя общая цель - опросить sql, передаваемый методу ExecuteSqlCommand.

Спасибо!

Ответы [ 3 ]

2 голосов
/ 11 июня 2019

SemanticModel.GetConstantValue - это API, который у нас есть для обработки этой ситуации.

Может принимать синтаксический узел и выражение . Вам все еще нужно будет отслеживать состояние переменных на их сайтах объявлений и определять, было ли им задано постоянное выражение.

Я бы использовал SemanticModel.GetSymbolInfo .Symbol?. DeclaringSyntaxReferences .First (), чтобы найти сайт объявления переменной, а затем проверить, является ли оно константным выражением.

1 голос
/ 10 июня 2019

Синтаксический 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;
        }

В противном случае может потребоваться поиск объявления переменной в других частях представленного кода (например, в полях класса, свойствах, других методах и т. Д.), Что немного более громоздко.

0 голосов
/ 10 июня 2019

К сожалению, 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
                }
            }
        }
    }
}
...