使用 Roslyn 语义模型在单个 .cs 文件中查找符号
Using the Roslyn Semantic Model to Find Symbols in a Single .cs File
我正在使用 Roslyn 创建一个分析器,如果特定 class 以不同步的方式公开其字段,它会警告用户,以帮助防止竞争条件。
问题:
我目前有工作代码来检查以确保字段是私有的。我在解决最后一个难题时遇到了麻烦:找出一种方法来确保所有字段仅在锁块内访问,因此它们(表面上)是同步的。
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
}
});
}
}
}
更具体地说,我希望能够确保引用荧光笔突出显示的所有内容(至少微软似乎这么称呼它)都在锁块内,而重载参数则不必如此。
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] 提供了一个 incomplete/missing 缓慢问题的解决方法(link 到 ReferenceResolver
似乎已损坏)。
我也研究了 DataFlowAnalysis
(SemanticModel.AnalyzeDataFlow()
),但我在那里找不到任何特定的方法,显然可以让我保证我正在引用字段 xe
,而不是局部变量 xe
.
问题:
我确实觉得我缺少了一些非常明显的东西。是否有一些我忽略的优雅的方法来实现它?如果答案使用语义模型会更好,因为我希望我必须在其他分析器中使用它来弄清楚 data/references 来自哪里,但我意识到存在局限性,所以没有语义模型的任何答案也不错
备注:
- 貌似在Github[4]也遇到了这个问题,但是貌似还在跟踪,不知道分析员要不要分析到项目层面。它仍然没有得到解决。出于此分析器的目的,我将假设整个 class 包含在单个
.cs
文件中。先小步走好吗?
- 我还搜索了 John Koerner 的网站 [5] 和 Josh Varty 的网站 [6],但找不到与分析器和 DataFlowAnalysis 相关的任何内容。
诀窍是颠倒你问问题的方式。来自:
How do I find all the references to this symbol that I want to ensure is synchronized?
而是
How, upon looking at the use of a symbol, determine if this should be inside of a lock statement?
因为这提供了一个操作过程:您的分析器应该查看方法主体中不在锁定语句中的每个标识符,调用 SemanticModel.GetSymbolInfo()
,获取被引用的符号,然后检查是否该字段是 "synchronized" 通过您的逻辑(私有等)的字段。到那时,由于您正在查看使用情况,您可以标记该特定使用。
这种颠倒是我们期望分析器的编写方式,这并非偶然。原因主要是性能。假设你的分析器是 运行 inside Visual Studio,你删除了一行代码。如果分析器是在 "look at a symbol, now ask for all uses" 的模型中编写的,则意味着所有执行此操作的分析器都可能必须从头开始重新运行。这对您的 CPU 或电池寿命不利。当问题像这样反转时,这意味着我们只需要重新分析该特定文件,因为您没有扩展到 "give me everything".
我正在使用 Roslyn 创建一个分析器,如果特定 class 以不同步的方式公开其字段,它会警告用户,以帮助防止竞争条件。
问题:
我目前有工作代码来检查以确保字段是私有的。我在解决最后一个难题时遇到了麻烦:找出一种方法来确保所有字段仅在锁块内访问,因此它们(表面上)是同步的。
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
}
});
}
}
}
更具体地说,我希望能够确保引用荧光笔突出显示的所有内容(至少微软似乎这么称呼它)都在锁块内,而重载参数则不必如此。
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] 提供了一个 incomplete/missing 缓慢问题的解决方法(link 到 ReferenceResolver
似乎已损坏)。
我也研究了 DataFlowAnalysis
(SemanticModel.AnalyzeDataFlow()
),但我在那里找不到任何特定的方法,显然可以让我保证我正在引用字段 xe
,而不是局部变量 xe
.
问题:
我确实觉得我缺少了一些非常明显的东西。是否有一些我忽略的优雅的方法来实现它?如果答案使用语义模型会更好,因为我希望我必须在其他分析器中使用它来弄清楚 data/references 来自哪里,但我意识到存在局限性,所以没有语义模型的任何答案也不错
备注:
- 貌似在Github[4]也遇到了这个问题,但是貌似还在跟踪,不知道分析员要不要分析到项目层面。它仍然没有得到解决。出于此分析器的目的,我将假设整个 class 包含在单个
.cs
文件中。先小步走好吗? - 我还搜索了 John Koerner 的网站 [5] 和 Josh Varty 的网站 [6],但找不到与分析器和 DataFlowAnalysis 相关的任何内容。
诀窍是颠倒你问问题的方式。来自:
How do I find all the references to this symbol that I want to ensure is synchronized?
而是
How, upon looking at the use of a symbol, determine if this should be inside of a lock statement?
因为这提供了一个操作过程:您的分析器应该查看方法主体中不在锁定语句中的每个标识符,调用 SemanticModel.GetSymbolInfo()
,获取被引用的符号,然后检查是否该字段是 "synchronized" 通过您的逻辑(私有等)的字段。到那时,由于您正在查看使用情况,您可以标记该特定使用。
这种颠倒是我们期望分析器的编写方式,这并非偶然。原因主要是性能。假设你的分析器是 运行 inside Visual Studio,你删除了一行代码。如果分析器是在 "look at a symbol, now ask for all uses" 的模型中编写的,则意味着所有执行此操作的分析器都可能必须从头开始重新运行。这对您的 CPU 或电池寿命不利。当问题像这样反转时,这意味着我们只需要重新分析该特定文件,因为您没有扩展到 "give me everything".