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); } }
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.