Using the Roslyn semantic model to search for characters in a single .cs file

I am using Roslyn to create an analyzer that alerts users if a particular class exposes its fields in an unsynchronized way to prevent race conditions.

Problem:

I currently have a working code that checks to see if a field is private. I am having problems with the last part of the puzzle: figuring out a way to make sure that all fields are only available inside the lock block, so theyre (supposedly) 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 } }); } } } 

In particular, Id, in order to be able to guarantee that everything highlighted with a reference marker (at least what Microsoft seems to be calling) is inside the blocking block, while overloaded parameters are not necessary.

 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) { } } } 

Study:

Here, Josh Warty [1] proposes using SymbolFinder.FindReferencesAsync , which requires a Solution object. Jason Malinowski [2] says that I should not use this in the analyzer, since creating MSBuildWorkspace to get the Solution object is too slow, while this person [3] offers an incomplete / missing workaround for the slowness problem (the link to the ReferenceResolver seems broken )

I also looked at DataFlowAnalysis ( SemanticModel.AnalyzeDataFlow() ), but I cannot find any specific methods there that obviously allow me to guarantee that Im refers to the xe field and not to the local xe variable.

Question:

I feel that there is something monumental obvious. Is there any elegant way to implement this that I forgot? Itd is preferable if the answer uses a semantic model, since I expect that I should use it in other analyzers to find out where the data / links come from, but I understand that there are limitations, so any answers without a semantic model are also fine.

Notes:

  • Apparently, this problem also occurs in Github [4], but apparently it is still being tracked there and they don’t know whether the analyzer analyzes at the project level or not. He was still not allowed. For the purposes of this analyzer, I assume that the entire class is contained in a single .cs file. First, small steps are performed, and ??
  • I also looked at the John Kerner website [5] and the Josh Warty website [6] and could not find anything that would be important for either the analyzers or the DataFlowAnalysis.
+5
source share
1 answer

The trick is to invert how you ask the question. Transfer from:

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

but instead

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

Since this offers a course of action: instead, your analyzer should look at every identifier in the body of the method that is not in the lock statement, call SemanticModel.GetSymbolInfo() , get the referenced character, and then check if this field is one of which is "synchronized" through your logic (private, etc.). At this point, since you are looking at usage, you can mark this specific usage.

This inversion is how we expect the analyzers to be recorded, and this is not an accident. The reason is largely related to performance. Imagine that your analyzer works inside Visual Studio and you delete a line of code. If the analyzers were written in the model “look at the symbol, now ask for all the uses”, this means that all and all analyzers that could be reprogrammed from scratch. This is not very convenient for your processor or battery. When a question is inverted in this way, it means that we only need to re-analyze this particular file, since you are not expanding to “give me everything”.

+4
source

All Articles