使用 Roslyn 确定是否读取私有字段

Determining if a private field is read using Roslyn

我整天都在搜索并阅读了很多帖子,但我无法就此得出结论。我正在尝试创建一个 Roslyn 分析器以在未读私有字段时报告诊断。注册语法动作并查明它是否私有真的很容易。但是现在我一直在尝试找出是否在 class.

中读取了该字段

假设我们有以下示例代码:

public class C {
    private int foo; //private field is declared but never read. Should report diagnostic here
    
    public void DoNothing() {
        //irrelevant
    }
}

有几个示例说明我希望在何处进行标记(初始化与否、注入与否、单行上的多个声明等),但我认为它们可能不是说明问题所必需的。

我目前的情况是这样的:

public override void Initialize(AnalysisContext context) {
    context.EnableConcurrentExecution();
    context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);

    context.RegisterSyntaxNodeAction(AnalyzeField, SyntaxKind.FieldDeclaration);
}

private void AnalyzeField(SyntaxNodeAnalysisContext context) {
    if (!(context.Node is FieldDeclarationSyntax fieldDeclarationSyntax)) {
        return;
    }

    foreach (var variableDeclaration in fieldDeclarationSyntax.Declaration.Variables) {
        if (context.SemanticModel.GetDeclaredSymbol(variableDeclaration) is IFieldSymbol variableDeclarationSymbol &&
            IsFieldPrivate(variableDeclarationSymbol) &&
            !IsFieldRead(context, variableDeclarationSymbol)) {
            
            //report diagnostic here
        }
    }
}

private bool IsFieldPrivate(IFieldSymbol fieldSymbol) {
    return fieldSymbol.DeclaredAccessibility == Accessibility.Private || // the field itself is explicitly private
           fieldSymbol.ContainingType?.DeclaredAccessibility == Accessibility.Private; //the field is not private, but is contained within a private class
}

private bool IsFieldRead(SyntaxNodeAnalysisContext context, IFieldSymbol fieldSymbol) {
    //context.Node.Parent will be the class declaration here since we're analyzing a field declaration
    //but let's be safe about that just in case and make sure we traverse up until we find the class declaration
    var classDeclarationSyntax = context.Node.Parent;
    while (!(classDeclarationSyntax is ClassDeclarationSyntax)) {
        classDeclarationSyntax = classDeclarationSyntax.Parent;
    }
    var methodsInClassContainingPrivateField = classDeclarationSyntax.DescendantNodes().OfType<MethodDeclarationSyntax>().ToImmutableArray();
    foreach (var method in methodsInClassContainingPrivateField) {
        var dataFlowAnalysis = context.SemanticModel.AnalyzeDataFlow(method); //does not work because this is not a StatementSyntax or ExpressionSyntax
        if (dataFlowAnalysis.ReadInside.Contains(fieldSymbol) || dataFlowAnalysis.ReadOutside.Contains(fieldSymbol)) {
            return true;
        }
    }

    return false;
}

我只是不太明白如何让 IsFieldRead() 方法起作用。这真的感觉像是一件应该很容易做的事情,但我就是不能完全理解它。我认为获取方法并分析我的领域的方法以查看它是否被读取将是一个不错的主意,但这不包括该领域是否被另一个私有领域读取,而且我无论如何都无法让它工作。 :)

感谢 一位在 Microsoft 实际从事 Roslyn 工作的人,我设法解决了这个问题。这是我的 IsFieldRead() 方法。关键显然在于 Microsoft.CodeAnalysis.Operations 命名空间。

private bool IsFieldRead(SyntaxNodeAnalysisContext context, IFieldSymbol fieldSymbol) {
    var classDeclarationSyntax = context.Node.Parent;
    while (!(classDeclarationSyntax is ClassDeclarationSyntax)) {
        classDeclarationSyntax = classDeclarationSyntax.Parent;
        if (classDeclarationSyntax == null) {
            throw new InvalidOperationException("You have somehow traversed up and out of the syntax tree when determining if a private member field is being read.");
        }
    }

    //get all methods in the class
    var methodsInClass = classDeclarationSyntax.DescendantNodes().OfType<MethodDeclarationSyntax>().ToImmutableArray();
    foreach (var method in methodsInClass) {
        //get all member references in those methods
        if (context.SemanticModel.GetOperation(method).Descendants().OfType<IMemberReferenceOperation>().ToImmutableArray().Any(x => x.Member.Equals(fieldSymbol))) {
            return true;
        }
    }

    return false;
}

请注意,这仅涵盖方法内的用法。还有其他几个地方,如其他字段、属性和构造函数也需要检查。