使用 Roslyn 语义模型在单个 .cs 文件中查找符号 [英] Using the Roslyn Semantic Model to Find Symbols in a Single .cs File

查看:37
本文介绍了使用 Roslyn 语义模型在单个 .cs 文件中查找符号的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用 Roslyn 创建一个分析器,如果特定类以不同步的方式公开其字段,它会警告用户,以帮助防止竞争条件.

I am using Roslyn to create an analyzer that warns users if a particular class exposes its fields in an unsynchronized manner, to help prevent race conditions.

我目前有工作代码来检查以确保字段是私有的.我在解决最后一个难题时遇到了麻烦:想办法确保所有字段只能在锁块内访问,因此它们(表面上)是同步的.

I currently have working code that checks to make sure a field is private. I’m having trouble with the last piece of the puzzle: figuring out a way to make sure that all fields are only accessed inside a lock block, so they’re (ostensibly) synchronized.

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.FindSymbols;

namespace RaceConditions
{
    [DiagnosticAnalyzer(LanguageNames.CSharp)]
    public class UnsynchronizedMemberAccess : DiagnosticAnalyzer
    {
        public const string DiagnosticId = "UnsynchronizedMemberAccess";
        internal static readonly LocalizableString Title = "UnsynchronizedMemberAccess Title";
        private static readonly LocalizableString MessageFormat = "Unsychronized fields are not thread-safe";
        private static readonly LocalizableString Description = "Accessing fields without a get/set methods synchronized with each other and the constructor may lead to race conditions";
        internal const string Category = "Race Conditions";

        private static DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, isEnabledByDefault: true, description: Description);
        public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get { return ImmutableArray.Create(Rule); } }

        //meant to stop other classes and itself from accessing members in an unsychronized fashion.
        public override void Initialize(AnalysisContext analysisContext)
        {
            analysisContext.RegisterSemanticModelAction((context) =>
            {
                var model = context.SemanticModel;
                var root = model.SyntaxTree.GetRoot();
                var nodes = model.SyntaxTree.GetRoot().DescendantNodes();

                var fields = nodes.OfType<VariableDeclaratorSyntax>()
                        .Where(v => v.Ancestors().OfType<FieldDeclarationSyntax>().Any());
                //since (it appears) that you can't read/write to a an initialized field,
                //I think it means you can only read/write inside a block
                foreach (BlockSyntax b in nodes.OfType<BlockSyntax>())
                {
                    //where I plan to put code to check references to the fields
                }
            });
        }
    }
}

更具体地说,我希望能够确保引用突出显示的所有内容(至少微软似乎是这么称呼它的)都在一个锁块内,而重载的参数则不必.

More specifically, I’d like to be able to ensure everything highlighted by the reference highlighter (at least that’s what Microsoft seems to call it) is inside a lock block, while overloaded parameters do not have to.

using System;
using System.Linq;
using System.Activities;
using System.Activities.Statements;
using System.Data.SqlClient;

namespace Sandbox
{

    partial class Program
    {
        private int xe = 0, y = 0;

        public Program(int xe)
        {
            this.xe = xe;
        }

        void bleh()
        {
            if (xe == 0)
            {
                xe = xe + 1;
            }
        }

        static void Main(string[] args)
        {
            Program p0 = new Program(5),
                p1 = new Program(p0),
                p2 = new Program(p0.xe);

            Console.WriteLine(p1.xe);
            Console.Read();
        }
    }

    partial class Program
    {
        public Program(Program p) : this(p.xe) { }
    }
}

研究:

在这里,Josh Varty [1] 建议我使用 SymbolFinder.FindReferencesAsync,它需要一个 Solution 对象.Jason Malinowski [2] 说我不应该在分析器中使用 do this,因为制作 MSBuildWorkspace 来获取 Solution 对象太慢了,而这个人 [3] 为缓慢问题提供了不完整/缺失的解决方法(ReferenceResolver 的链接似乎已损坏).

The Research:

Here, Josh Varty [1] suggests I use SymbolFinder.FindReferencesAsync, which requires a Solution object. Jason Malinowski [2] says that I shouldn’t use do this in an analyzer, since making a MSBuildWorkspace to get a Solution object is too slow, while this person [3] offers an incomplete/missing workaround to the slowness issue (the link to ReferenceResolver seems to be broken).

我还研究了 DataFlowAnalysis(SemanticModel.AnalyzeDataFlow()),但我在那里找不到任何特定的方法,这显然让我保证我引用字段 xe,而不是局部变量 xe.

I have also looked into DataFlowAnalysis (SemanticModel.AnalyzeDataFlow()), but I can’t find any particular methods there that obviously let me guarantee that I’m referencing the field xe, and not the local variable xe.

我确实觉得我遗漏了一些非常明显的东西.是否有一些优雅的方法来实现我忽略的这一点?如果答案使用语义模型会更好,因为我希望我必须在其他分析器中使用它来找出数据/引用的来源,但我意识到存在局限性,因此任何没有语义模型的答案也是很好.

I do feel like there is something monumentally obvious I’m missing. Is there some elegant way to implement this that I’ve overlooked? It’d be preferable if the answer uses the semantic model, since I expect I have to use it in other analyzers to figure out where data/references come from, but I realize there are limitations, so any answers without the semantic model are also fine.

  • 显然,Github [4] 也遇到了这个问题,但显然它仍在那里跟踪,他们不知道分析器是否应该在项目级别进行分析.它仍然没有得到解决.出于此分析器的目的,我将假设整个类包含在单个 .cs 文件中.首先是小步骤,嗯?
  • 我还搜索了 John Koerner 的网站 [5] 和 Josh Varty 的网站 [6],但找不到与分析器和 DataFlowAnalysis 相关的任何内容.
  • Apparently, this issue was also encountered at Github [4], but apparently it’s still being tracked there, and they don’t know if the analyzer should analyze on the project level or not. It still hasn’t been resolved. For the purposes of this analyzer, I will assume that the entire class contained in a single .cs file. Small steps first, eh?
  • I also searched through John Koerner's website [5] and Josh Varty's website [6], and couldn’t find anything relevant to both analyzers and DataFlowAnalysis.

推荐答案

诀窍是颠倒你提出问题的方式.从:

The trick is to invert how you're asking the question. Go from:

如何找到我想要确保同步的对该符号的所有引用?

How do I find all the references to this symbol that I want to ensure is synchronized?

而是

如何通过查看符号的使用来确定它是否应该在 lock 语句中?

How, upon looking at the use of a symbol, determine if this should be inside of a lock statement?

因为这提供了一个过程:您的分析器应该查看不在 lock 语句中的方法体中的每个标识符,调用 SemanticModel.GetSymbolInfo(),获取被引用的符号,然后检查该字段是否是通过您的逻辑(私有等)同步"的字段.到那时,由于您正在查看用途,因此您可以标记该特定用途.

Because this offers a course of action: your analyzer should instead look at each identifier in a method body that's not in a lock statement, call SemanticModel.GetSymbolInfo(), get the symbol that's being referenced, and then check if that field is one that's "synchronized" via your logic (private, etc.). At that point, then since you're looking at the use, you can flag that particular use.

这种倒置是我们期望编写分析器的方式,这并非偶然.原因主要是性能.假设您的分析器在 Visual Studio 中运行,您删除了一行代码.如果分析器是按照看一个符号,现在要求所有用途"的模型编写的,这意味着任何和所有这样做的分析器都可能必须从头开始重新运行.这对您的 CPU 或电池寿命不利.当问题像这样倒过来时,这意味着我们只需要重新分析那个特定的文件,因为你没有扩展到给我一切".

This inversion is how we expect analyzers to be written, and it's not an accident. The reason is largely performance. Imagine your analyzer is running inside Visual Studio, and you delete a line of code. If analyzers were written in the model of "look at a symbol, now ask for all uses", it means any and all analyzers that did that potentially have to rerun from scratch. That's not great for your CPU or battery life. When the question is inverted like this, it means we only have to reanalyze that specific file, since you're not expanding to "give me everything".

这篇关于使用 Roslyn 语义模型在单个 .cs 文件中查找符号的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆