Skip to content

Commit c15c50e

Browse files
committed
Update generated code detection to avoid ConditionalWeakTable
1 parent b4ede52 commit c15c50e

3 files changed

Lines changed: 135 additions & 99 deletions

File tree

StyleCop.Analyzers/StyleCop.Analyzers/AnalyzerExtensions.cs

Lines changed: 38 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ namespace StyleCop.Analyzers
55
{
66
using System;
77
using System.Collections.Concurrent;
8-
using System.Runtime.CompilerServices;
98
using System.Threading;
109
using Microsoft.CodeAnalysis;
1110
using Microsoft.CodeAnalysis.Diagnostics;
@@ -22,8 +21,8 @@ internal static class AnalyzerExtensions
2221
/// This allows many analyzers that run on every token in the file to avoid checking
2322
/// the same state in the document repeatedly.
2423
/// </remarks>
25-
private static readonly ConditionalWeakTable<Compilation, ConcurrentDictionary<SyntaxTree, bool>> GeneratedHeaderCache
26-
= new ConditionalWeakTable<Compilation, ConcurrentDictionary<SyntaxTree, bool>>();
24+
private static Tuple<WeakReference<Compilation>, ConcurrentDictionary<SyntaxTree, bool>> generatedHeaderCache
25+
= Tuple.Create(new WeakReference<Compilation>(null), default(ConcurrentDictionary<SyntaxTree, bool>));
2726

2827
/// <summary>
2928
/// Register an action to be executed at completion of parsing of a code document. A syntax tree action reports
@@ -35,7 +34,7 @@ private static readonly ConditionalWeakTable<Compilation, ConcurrentDictionary<S
3534
public static void RegisterSyntaxTreeActionHonorExclusions(this CompilationStartAnalysisContext context, Action<SyntaxTreeAnalysisContext> action)
3635
{
3736
Compilation compilation = context.Compilation;
38-
ConcurrentDictionary<SyntaxTree, bool> cache = GeneratedHeaderCache.GetOrCreateValue(compilation);
37+
ConcurrentDictionary<SyntaxTree, bool> cache = GetOrCreateGeneratedDocumentCache(compilation);
3938

4039
context.RegisterSyntaxTreeAction(
4140
c =>
@@ -53,6 +52,40 @@ public static void RegisterSyntaxTreeActionHonorExclusions(this CompilationStart
5352
});
5453
}
5554

55+
/// <summary>
56+
/// Gets or creates a cache which can be used with <see cref="GeneratedCodeAnalysisExtensions"/> methods to
57+
/// efficiently determine whether or not a source file is considered generated.
58+
/// </summary>
59+
/// <param name="compilation">The compilation which the cache applies to.</param>
60+
/// <returns>A cache which tracks the syntax trees in a compilation which are considered generated.</returns>
61+
public static ConcurrentDictionary<SyntaxTree, bool> GetOrCreateGeneratedDocumentCache(this Compilation compilation)
62+
{
63+
var headerCache = generatedHeaderCache;
64+
65+
Compilation cachedCompilation;
66+
if (!headerCache.Item1.TryGetTarget(out cachedCompilation) || cachedCompilation != compilation)
67+
{
68+
var replacementCache = Tuple.Create(new WeakReference<Compilation>(compilation), new ConcurrentDictionary<SyntaxTree, bool>());
69+
while (true)
70+
{
71+
var prior = Interlocked.CompareExchange(ref generatedHeaderCache, replacementCache, headerCache);
72+
if (prior == headerCache)
73+
{
74+
headerCache = replacementCache;
75+
break;
76+
}
77+
78+
headerCache = prior;
79+
if (headerCache.Item1.TryGetTarget(out cachedCompilation) && cachedCompilation == compilation)
80+
{
81+
break;
82+
}
83+
}
84+
}
85+
86+
return headerCache.Item2;
87+
}
88+
5689
/// <summary>
5790
/// Register an action to be executed at completion of semantic analysis of a <see cref="SyntaxNode"/> with an
5891
/// appropriate kind. A syntax node action can report diagnostics about a <see cref="SyntaxNode"/>, and can also
@@ -70,7 +103,7 @@ public static void RegisterSyntaxNodeActionHonorExclusions<TLanguageKindEnum>(th
70103
where TLanguageKindEnum : struct
71104
{
72105
Compilation compilation = context.Compilation;
73-
ConcurrentDictionary<SyntaxTree, bool> cache = GeneratedHeaderCache.GetOrCreateValue(compilation);
106+
ConcurrentDictionary<SyntaxTree, bool> cache = GetOrCreateGeneratedDocumentCache(compilation);
74107

75108
context.RegisterSyntaxNodeAction(
76109
c =>
@@ -88,36 +121,5 @@ public static void RegisterSyntaxNodeActionHonorExclusions<TLanguageKindEnum>(th
88121
},
89122
syntaxKinds);
90123
}
91-
92-
/// <summary>
93-
/// Checks whether the given document is auto generated by a tool (based on filename or comment header).
94-
/// </summary>
95-
/// <remarks>
96-
/// <para>The exact conditions used to identify generated code are subject to change in future releases. The
97-
/// current algorithm uses the following checks.</para>
98-
/// <para>Code is considered generated if it meets any of the following conditions.</para>
99-
/// <list type="bullet">
100-
/// <item>The code is contained in a file which starts with a comment containing the text
101-
/// <c>&lt;auto-generated</c>.</item>
102-
/// <item>The code is contained in a file with a name matching certain patterns (case-insensitive):
103-
/// <list type="bullet">
104-
/// <item>*.designer.cs</item>
105-
/// </list>
106-
/// </item>
107-
/// </list>
108-
/// </remarks>
109-
/// <param name="tree">The syntax tree to examine.</param>
110-
/// <param name="compilation">The <see cref="Compilation"/> containing the specified <paramref name="tree"/>.</param>
111-
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that the task will observe.</param>
112-
/// <returns>
113-
/// <para><see langword="true"/> if <paramref name="tree"/> is located in generated code; otherwise,
114-
/// <see langword="false"/>. If <paramref name="tree"/> is <see langword="null"/>, this method returns
115-
/// <see langword="false"/>.</para>
116-
/// </returns>
117-
public static bool IsGeneratedDocument(this SyntaxTree tree, Compilation compilation, CancellationToken cancellationToken)
118-
{
119-
ConcurrentDictionary<SyntaxTree, bool> cache = GeneratedHeaderCache.GetOrCreateValue(compilation);
120-
return tree.IsGeneratedDocument(cache, cancellationToken);
121-
}
122124
}
123125
}

StyleCop.Analyzers/StyleCop.Analyzers/MaintainabilityRules/SA1401FieldsMustBePrivate.cs

Lines changed: 50 additions & 33 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 Microsoft.CodeAnalysis;
89
using Microsoft.CodeAnalysis.Diagnostics;
@@ -41,54 +42,70 @@ internal class SA1401FieldsMustBePrivate : DiagnosticAnalyzer
4142
/// <inheritdoc/>
4243
public override void Initialize(AnalysisContext context)
4344
{
44-
context.RegisterSymbolAction(AnalyzeField, SymbolKind.Field);
45+
context.RegisterCompilationStartAction(HandleCompilationStart);
4546
}
4647

47-
private static void AnalyzeField(SymbolAnalysisContext symbolAnalysisContext)
48+
private static void HandleCompilationStart(CompilationStartAnalysisContext context)
4849
{
49-
var fieldDeclarationSyntax = (IFieldSymbol)symbolAnalysisContext.Symbol;
50-
if (!IsFieldPrivate(fieldDeclarationSyntax) &&
51-
!IsStaticReadonly(fieldDeclarationSyntax) &&
52-
IsParentAClass(fieldDeclarationSyntax) &&
53-
!fieldDeclarationSyntax.IsConst)
50+
Analyzer analyzer = new Analyzer(context.Compilation.GetOrCreateGeneratedDocumentCache());
51+
context.RegisterSymbolAction(analyzer.AnalyzeField, SymbolKind.Field);
52+
}
53+
54+
private sealed class Analyzer
55+
{
56+
private readonly ConcurrentDictionary<SyntaxTree, bool> generatedHeaderCache;
57+
58+
public Analyzer(ConcurrentDictionary<SyntaxTree, bool> generatedHeaderCache)
59+
{
60+
this.generatedHeaderCache = generatedHeaderCache;
61+
}
62+
63+
public void AnalyzeField(SymbolAnalysisContext symbolAnalysisContext)
5464
{
55-
foreach (var location in symbolAnalysisContext.Symbol.Locations)
65+
var fieldDeclarationSyntax = (IFieldSymbol)symbolAnalysisContext.Symbol;
66+
if (!IsFieldPrivate(fieldDeclarationSyntax) &&
67+
!IsStaticReadonly(fieldDeclarationSyntax) &&
68+
IsParentAClass(fieldDeclarationSyntax) &&
69+
!fieldDeclarationSyntax.IsConst)
5670
{
57-
if (!location.IsInSource)
71+
foreach (var location in symbolAnalysisContext.Symbol.Locations)
5872
{
59-
// assume symbols not defined in a source document are "out of reach"
60-
return;
61-
}
73+
if (!location.IsInSource)
74+
{
75+
// assume symbols not defined in a source document are "out of reach"
76+
return;
77+
}
6278

63-
if (location.SourceTree.IsGeneratedDocument(symbolAnalysisContext.Compilation, symbolAnalysisContext.CancellationToken))
64-
{
65-
return;
79+
if (location.SourceTree.IsGeneratedDocument(this.generatedHeaderCache, symbolAnalysisContext.CancellationToken))
80+
{
81+
return;
82+
}
6683
}
67-
}
6884

69-
symbolAnalysisContext.ReportDiagnostic(Diagnostic.Create(Descriptor, fieldDeclarationSyntax.Locations[0]));
85+
symbolAnalysisContext.ReportDiagnostic(Diagnostic.Create(Descriptor, fieldDeclarationSyntax.Locations[0]));
86+
}
7087
}
71-
}
7288

73-
private static bool IsFieldPrivate(IFieldSymbol fieldDeclarationSyntax)
74-
{
75-
return fieldDeclarationSyntax.DeclaredAccessibility == Accessibility.Private;
76-
}
77-
78-
private static bool IsStaticReadonly(IFieldSymbol fieldDeclarationSyntax)
79-
{
80-
return fieldDeclarationSyntax.IsStatic && fieldDeclarationSyntax.IsReadOnly;
81-
}
89+
private static bool IsFieldPrivate(IFieldSymbol fieldDeclarationSyntax)
90+
{
91+
return fieldDeclarationSyntax.DeclaredAccessibility == Accessibility.Private;
92+
}
8293

83-
private static bool IsParentAClass(IFieldSymbol fieldDeclarationSyntax)
84-
{
85-
if (fieldDeclarationSyntax.ContainingSymbol != null &&
86-
fieldDeclarationSyntax.ContainingSymbol.Kind == SymbolKind.NamedType)
94+
private static bool IsStaticReadonly(IFieldSymbol fieldDeclarationSyntax)
8795
{
88-
return ((ITypeSymbol)fieldDeclarationSyntax.ContainingSymbol).TypeKind == TypeKind.Class;
96+
return fieldDeclarationSyntax.IsStatic && fieldDeclarationSyntax.IsReadOnly;
8997
}
9098

91-
return false;
99+
private static bool IsParentAClass(IFieldSymbol fieldDeclarationSyntax)
100+
{
101+
if (fieldDeclarationSyntax.ContainingSymbol != null &&
102+
fieldDeclarationSyntax.ContainingSymbol.Kind == SymbolKind.NamedType)
103+
{
104+
return ((ITypeSymbol)fieldDeclarationSyntax.ContainingSymbol).TypeKind == TypeKind.Class;
105+
}
106+
107+
return false;
108+
}
92109
}
93110
}
94111
}

StyleCop.Analyzers/StyleCop.Analyzers/NamingRules/SA1303ConstFieldNamesMustBeginWithUpperCaseLetter.cs

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

44
namespace StyleCop.Analyzers.NamingRules
55
{
6+
using System.Collections.Concurrent;
67
using System.Collections.Immutable;
78
using System.Linq;
89
using Microsoft.CodeAnalysis;
@@ -45,50 +46,66 @@ internal class SA1303ConstFieldNamesMustBeginWithUpperCaseLetter : DiagnosticAna
4546
/// <inheritdoc/>
4647
public override void Initialize(AnalysisContext context)
4748
{
48-
context.RegisterSymbolAction(this.HandleFieldDeclaration, SymbolKind.Field);
49+
context.RegisterCompilationStartAction(HandleCompilationStart);
4950
}
5051

51-
private void HandleFieldDeclaration(SymbolAnalysisContext context)
52+
private static void HandleCompilationStart(CompilationStartAnalysisContext context)
5253
{
53-
var symbol = context.Symbol as IFieldSymbol;
54+
Analyzer analyzer = new Analyzer(context.Compilation.GetOrCreateGeneratedDocumentCache());
55+
context.RegisterSymbolAction(analyzer.HandleFieldDeclaration, SymbolKind.Field);
56+
}
5457

55-
if (symbol == null || !symbol.IsConst)
56-
{
57-
return;
58-
}
58+
private sealed class Analyzer
59+
{
60+
private readonly ConcurrentDictionary<SyntaxTree, bool> generatedHeaderCache;
5961

60-
if (NamedTypeHelpers.IsContainedInNativeMethodsClass(symbol.ContainingType))
62+
public Analyzer(ConcurrentDictionary<SyntaxTree, bool> generatedHeaderCache)
6163
{
62-
return;
64+
this.generatedHeaderCache = generatedHeaderCache;
6365
}
6466

65-
/* This code uses char.IsLower(...) instead of !char.IsUpper(...) for all of the following reasons:
66-
* 1. Fields starting with `_` should be reported as SA1309 instead of this diagnostic
67-
* 2. Foreign languages may not have upper case variants for certain characters
68-
* 3. This diagnostic appears targeted for "English" identifiers.
69-
*
70-
* See DotNetAnalyzers/StyleCopAnalyzers#369 for additional information:
71-
* https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/369
72-
*/
73-
if (!string.IsNullOrEmpty(symbol.Name) &&
74-
char.IsLower(symbol.Name[0]) &&
75-
symbol.Locations.Any())
67+
public void HandleFieldDeclaration(SymbolAnalysisContext context)
7668
{
77-
foreach (var location in context.Symbol.Locations)
69+
var symbol = context.Symbol as IFieldSymbol;
70+
71+
if (symbol == null || !symbol.IsConst)
7872
{
79-
if (!location.IsInSource)
80-
{
81-
// assume symbols not defined in a source document are "out of reach"
82-
return;
83-
}
73+
return;
74+
}
8475

85-
if (location.SourceTree.IsGeneratedDocument(context.Compilation, context.CancellationToken))
76+
if (NamedTypeHelpers.IsContainedInNativeMethodsClass(symbol.ContainingType))
77+
{
78+
return;
79+
}
80+
81+
/* This code uses char.IsLower(...) instead of !char.IsUpper(...) for all of the following reasons:
82+
* 1. Fields starting with `_` should be reported as SA1309 instead of this diagnostic
83+
* 2. Foreign languages may not have upper case variants for certain characters
84+
* 3. This diagnostic appears targeted for "English" identifiers.
85+
*
86+
* See DotNetAnalyzers/StyleCopAnalyzers#369 for additional information:
87+
* https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/369
88+
*/
89+
if (!string.IsNullOrEmpty(symbol.Name) &&
90+
char.IsLower(symbol.Name[0]) &&
91+
symbol.Locations.Any())
92+
{
93+
foreach (var location in context.Symbol.Locations)
8694
{
87-
return;
95+
if (!location.IsInSource)
96+
{
97+
// assume symbols not defined in a source document are "out of reach"
98+
return;
99+
}
100+
101+
if (location.SourceTree.IsGeneratedDocument(this.generatedHeaderCache, context.CancellationToken))
102+
{
103+
return;
104+
}
88105
}
89-
}
90106

91-
context.ReportDiagnostic(Diagnostic.Create(Descriptor, symbol.Locations[0]));
107+
context.ReportDiagnostic(Diagnostic.Create(Descriptor, symbol.Locations[0]));
108+
}
92109
}
93110
}
94111
}

0 commit comments

Comments
 (0)