Skip to content

Commit 919d61a

Browse files
committed
Update using alias detection to avoid the use of ConditionalWeakTable
1 parent c15c50e commit 919d61a

3 files changed

Lines changed: 184 additions & 136 deletions

File tree

StyleCop.Analyzers/StyleCop.Analyzers/Helpers/SyntaxTreeHelpers.cs

Lines changed: 43 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33

44
namespace StyleCop.Analyzers.Helpers
55
{
6+
using System;
7+
using System.Collections.Concurrent;
68
using System.Linq;
7-
using System.Runtime.CompilerServices;
9+
using System.Threading;
810
using Microsoft.CodeAnalysis;
911
using Microsoft.CodeAnalysis.CSharp;
1012
using Microsoft.CodeAnalysis.CSharp.Syntax;
@@ -18,27 +20,53 @@ internal static class SyntaxTreeHelpers
1820
/// This allows many analyzers that run on every token in the file to avoid checking
1921
/// the same state in the document repeatedly.
2022
/// </remarks>
21-
private static readonly ConditionalWeakTable<SyntaxTree, StrongBox<bool?>> UsingAliasPresentCheck
22-
= new ConditionalWeakTable<SyntaxTree, StrongBox<bool?>>();
23+
private static Tuple<WeakReference<Compilation>, ConcurrentDictionary<SyntaxTree, bool>> usingAliasCache
24+
= Tuple.Create(new WeakReference<Compilation>(null), default(ConcurrentDictionary<SyntaxTree, bool>));
2325

24-
internal static bool ContainsUsingAlias(this SyntaxTree tree)
26+
public static ConcurrentDictionary<SyntaxTree, bool> GetOrCreateUsingAliasCache(this Compilation compilation)
2527
{
26-
StrongBox<bool?> cachedResult = UsingAliasPresentCheck.GetOrCreateValue(tree);
27-
if (cachedResult.Value.HasValue)
28+
var cache = usingAliasCache;
29+
30+
Compilation cachedCompilation;
31+
if (!cache.Item1.TryGetTarget(out cachedCompilation) || cachedCompilation != compilation)
2832
{
29-
return cachedResult.Value.Value;
33+
var replacementCache = Tuple.Create(new WeakReference<Compilation>(compilation), new ConcurrentDictionary<SyntaxTree, bool>());
34+
while (true)
35+
{
36+
var prior = Interlocked.CompareExchange(ref usingAliasCache, replacementCache, cache);
37+
if (prior == cache)
38+
{
39+
cache = replacementCache;
40+
break;
41+
}
42+
43+
cache = prior;
44+
if (cache.Item1.TryGetTarget(out cachedCompilation) && cachedCompilation == compilation)
45+
{
46+
break;
47+
}
48+
}
3049
}
3150

32-
bool hasUsingAlias = ContainsUsingAliasNoCache(tree);
51+
return cache.Item2;
52+
}
3353

34-
// Update the strongbox's value with our computed result.
35-
// This doesn't change the strongbox reference, and its presence in the
36-
// ConditionalWeakTable is already assured, so we're updating in-place.
37-
// In the event of a race condition with another thread that set the value,
38-
// we'll just be re-setting it to the same value.
39-
cachedResult.Value = hasUsingAlias;
54+
internal static bool ContainsUsingAlias(this SyntaxTree tree, ConcurrentDictionary<SyntaxTree, bool> cache)
55+
{
56+
if (tree == null)
57+
{
58+
return false;
59+
}
60+
61+
bool result;
62+
if (cache.TryGetValue(tree, out result))
63+
{
64+
return result;
65+
}
4066

41-
return hasUsingAlias;
67+
bool generated = ContainsUsingAliasNoCache(tree);
68+
cache.TryAdd(tree, generated);
69+
return generated;
4270
}
4371

4472
private static bool ContainsUsingAliasNoCache(SyntaxTree tree)

StyleCop.Analyzers/StyleCop.Analyzers/MaintainabilityRules/SA1404CodeAnalysisSuppressionMustHaveJustification.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
namespace StyleCop.Analyzers.MaintainabilityRules
55
{
6+
using System.Collections.Concurrent;
67
using System.Collections.Immutable;
78
using System.Diagnostics.CodeAnalysis;
89
using Helpers;
@@ -60,7 +61,7 @@ public override void Initialize(AnalysisContext context)
6061

6162
private static void HandleCompilationStart(CompilationStartAnalysisContext context)
6263
{
63-
AnalyzerInstance instance = new AnalyzerInstance();
64+
AnalyzerInstance instance = new AnalyzerInstance(context.Compilation.GetOrCreateUsingAliasCache());
6465
context.RegisterSyntaxNodeActionHonorExclusions(instance.HandleAttributeNode, SyntaxKind.Attribute);
6566
}
6667

@@ -69,18 +70,25 @@ private static void HandleCompilationStart(CompilationStartAnalysisContext conte
6970
/// </summary>
7071
private sealed class AnalyzerInstance
7172
{
73+
private readonly ConcurrentDictionary<SyntaxTree, bool> usingAliasCache;
74+
7275
/// <summary>
7376
/// A lazily-initialized reference to <see cref="SuppressMessageAttribute"/> within the context of a
7477
/// particular <see cref="Compilation"/>.
7578
/// </summary>
7679
private INamedTypeSymbol suppressMessageAttribute;
7780

81+
public AnalyzerInstance(ConcurrentDictionary<SyntaxTree, bool> usingAliasCache)
82+
{
83+
this.usingAliasCache = usingAliasCache;
84+
}
85+
7886
public void HandleAttributeNode(SyntaxNodeAnalysisContext context)
7987
{
8088
var attribute = (AttributeSyntax)context.Node;
8189

8290
// Return fast if the name doesn't match and the file doesn't contain any using alias directives
83-
if (!attribute.SyntaxTree.ContainsUsingAlias())
91+
if (!attribute.SyntaxTree.ContainsUsingAlias(this.usingAliasCache))
8492
{
8593
SimpleNameSyntax simpleNameSyntax = attribute.Name as SimpleNameSyntax;
8694
if (simpleNameSyntax == null)

0 commit comments

Comments
 (0)