Skip to content

Commit 1bd6ffb

Browse files
committed
Merge pull request #1742 from sharwell/fix-1736
Work around perf problems caused by AdditionalText.GetText
2 parents 564fcc5 + cab4f7b commit 1bd6ffb

9 files changed

Lines changed: 134 additions & 31 deletions

File tree

StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/DocumentationRules/FileHeaderCodeFixProvider.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ public override Task RegisterCodeFixesAsync(CodeFixContext context)
6666
private static async Task<Document> GetTransformedDocumentAsync(Document document, CancellationToken cancellationToken)
6767
{
6868
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
69-
var settings = document.Project.AnalyzerOptions.GetStyleCopSettings();
69+
var settings = document.Project.AnalyzerOptions.GetStyleCopSettings(cancellationToken);
7070

7171
var fileHeader = FileHeaderHelpers.ParseFileHeader(root);
7272
SyntaxNode newSyntaxRoot;

StyleCop.Analyzers/StyleCop.Analyzers.Test/Settings/SettingsUnitTests.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public class SettingsUnitTests
2121
[Fact]
2222
public void VerifySettingsDefaults()
2323
{
24-
var styleCopSettings = SettingsHelper.GetStyleCopSettings(default(SyntaxTreeAnalysisContext));
24+
var styleCopSettings = SettingsHelper.GetStyleCopSettings(default(SyntaxTreeAnalysisContext), CancellationToken.None);
2525

2626
Assert.Equal("PlaceholderCompany", styleCopSettings.DocumentationRules.CompanyName);
2727
Assert.Equal("Copyright (c) PlaceholderCompany. All rights reserved.", styleCopSettings.DocumentationRules.CopyrightText);
@@ -57,7 +57,7 @@ public async Task VerifySettingsAreReadCorrectlyAsync()
5757
";
5858
var context = await CreateAnalysisContextAsync(settings).ConfigureAwait(false);
5959

60-
var styleCopSettings = context.GetStyleCopSettings();
60+
var styleCopSettings = context.GetStyleCopSettings(CancellationToken.None);
6161

6262
Assert.Equal("TestCompany", styleCopSettings.DocumentationRules.CompanyName);
6363
Assert.Equal("Custom copyright text.", styleCopSettings.DocumentationRules.CopyrightText);
@@ -83,7 +83,7 @@ public async Task VerifySettingsWillUseCompanyNameInDefaultCopyrightTextAsync()
8383
";
8484
var context = await CreateAnalysisContextAsync(settings).ConfigureAwait(false);
8585

86-
var styleCopSettings = context.GetStyleCopSettings();
86+
var styleCopSettings = context.GetStyleCopSettings(CancellationToken.None);
8787

8888
Assert.Equal("TestCompany", styleCopSettings.DocumentationRules.CompanyName);
8989
Assert.Equal("Copyright (c) TestCompany. All rights reserved.", styleCopSettings.DocumentationRules.CopyrightText);
@@ -103,7 +103,7 @@ public async Task VerifyCircularReferenceBehaviorAsync()
103103
";
104104
var context = await CreateAnalysisContextAsync(settings).ConfigureAwait(false);
105105

106-
var styleCopSettings = context.GetStyleCopSettings();
106+
var styleCopSettings = context.GetStyleCopSettings(CancellationToken.None);
107107

108108
Assert.Equal("[CircularReference]", styleCopSettings.DocumentationRules.CopyrightText);
109109
}
@@ -122,7 +122,7 @@ public async Task VerifyInvalidReferenceBehaviorAsync()
122122
";
123123
var context = await CreateAnalysisContextAsync(settings).ConfigureAwait(false);
124124

125-
var styleCopSettings = context.GetStyleCopSettings();
125+
var styleCopSettings = context.GetStyleCopSettings(CancellationToken.None);
126126

127127
Assert.Equal("[InvalidReference]", styleCopSettings.DocumentationRules.CopyrightText);
128128
}
@@ -133,7 +133,7 @@ public async Task VerifyInvalidJsonBehaviorAsync()
133133
var settings = @"This is not a JSON file.";
134134
var context = await CreateAnalysisContextAsync(settings).ConfigureAwait(false);
135135

136-
var styleCopSettings = context.GetStyleCopSettings();
136+
var styleCopSettings = context.GetStyleCopSettings(CancellationToken.None);
137137

138138
// The result is the same as the default settings.
139139
Assert.Equal("PlaceholderCompany", styleCopSettings.DocumentationRules.CompanyName);

StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/FileHeaderAnalyzers.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ namespace StyleCop.Analyzers.DocumentationRules
66
using System;
77
using System.Collections.Immutable;
88
using System.IO;
9+
using System.Threading;
910
using System.Xml.Linq;
1011
using Microsoft.CodeAnalysis;
1112
using Microsoft.CodeAnalysis.Diagnostics;
@@ -181,7 +182,7 @@ private static void HandleCompilationStart(CompilationStartAnalysisContext conte
181182
// Disabling SA1633 will disable all other header related diagnostics.
182183
if (!compilation.IsAnalyzerSuppressed(SA1633Identifier))
183184
{
184-
Analyzer analyzer = new Analyzer(context.Options);
185+
Analyzer analyzer = new Analyzer(context.Options, context.CancellationToken);
185186
context.RegisterSyntaxTreeActionHonorExclusions(ctx => analyzer.HandleSyntaxTreeAction(ctx, compilation));
186187
}
187188
}
@@ -190,9 +191,9 @@ private sealed class Analyzer
190191
{
191192
private readonly DocumentationSettings documentationSettings;
192193

193-
public Analyzer(AnalyzerOptions options)
194+
public Analyzer(AnalyzerOptions options, CancellationToken cancellationToken)
194195
{
195-
StyleCopSettings settings = options.GetStyleCopSettings();
196+
StyleCopSettings settings = options.GetStyleCopSettings(cancellationToken);
196197
this.documentationSettings = settings.DocumentationRules;
197198
}
198199

StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/SA1600ElementsMustBeDocumented.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ namespace StyleCop.Analyzers.DocumentationRules
66
using System;
77
using System.Collections.Immutable;
88
using System.Linq;
9+
using System.Threading;
910
using Helpers;
1011
using Microsoft.CodeAnalysis;
1112
using Microsoft.CodeAnalysis.CSharp;
@@ -58,7 +59,7 @@ public override void Initialize(AnalysisContext context)
5859

5960
private static void HandleCompilationStart(CompilationStartAnalysisContext context)
6061
{
61-
Analyzer analyzer = new Analyzer(context.Options);
62+
Analyzer analyzer = new Analyzer(context.Options, context.CancellationToken);
6263
context.RegisterSyntaxNodeActionHonorExclusions(analyzer.HandleBaseTypeDeclaration, BaseTypeDeclarationKinds);
6364
context.RegisterSyntaxNodeActionHonorExclusions(analyzer.HandleMethodDeclaration, SyntaxKind.MethodDeclaration);
6465
context.RegisterSyntaxNodeActionHonorExclusions(analyzer.HandleConstructorDeclaration, SyntaxKind.ConstructorDeclaration);
@@ -75,9 +76,9 @@ private class Analyzer
7576
{
7677
private readonly DocumentationSettings documentationSettings;
7778

78-
public Analyzer(AnalyzerOptions options)
79+
public Analyzer(AnalyzerOptions options, CancellationToken cancellationToken)
7980
{
80-
StyleCopSettings settings = options.GetStyleCopSettings();
81+
StyleCopSettings settings = options.GetStyleCopSettings(cancellationToken);
8182
this.documentationSettings = settings.DocumentationRules;
8283
}
8384

StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/SA1601PartialElementsMustBeDocumented.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ namespace StyleCop.Analyzers.DocumentationRules
66
using System;
77
using System.Collections.Immutable;
88
using System.Linq;
9+
using System.Threading;
910
using Microsoft.CodeAnalysis;
1011
using Microsoft.CodeAnalysis.CSharp;
1112
using Microsoft.CodeAnalysis.CSharp.Syntax;
@@ -99,7 +100,7 @@ public override void Initialize(AnalysisContext context)
99100

100101
private static void HandleCompilationStart(CompilationStartAnalysisContext context)
101102
{
102-
Analyzer analyzer = new Analyzer(context.Options);
103+
Analyzer analyzer = new Analyzer(context.Options, context.CancellationToken);
103104
context.RegisterSyntaxNodeActionHonorExclusions(analyzer.HandleBaseTypeDeclaration, BaseTypeDeclarationKinds);
104105
context.RegisterSyntaxNodeActionHonorExclusions(analyzer.HandleMethodDeclaration, SyntaxKind.MethodDeclaration);
105106
}
@@ -108,9 +109,9 @@ private class Analyzer
108109
{
109110
private readonly DocumentationSettings documentationSettings;
110111

111-
public Analyzer(AnalyzerOptions options)
112+
public Analyzer(AnalyzerOptions options, CancellationToken cancellationToken)
112113
{
113-
StyleCopSettings settings = options.GetStyleCopSettings();
114+
StyleCopSettings settings = options.GetStyleCopSettings(cancellationToken);
114115
this.documentationSettings = settings.DocumentationRules;
115116
}
116117

StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/SA1602EnumerationItemsMustBeDocumented.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ namespace StyleCop.Analyzers.DocumentationRules
55
{
66
using System;
77
using System.Collections.Immutable;
8+
using System.Threading;
89
using Microsoft.CodeAnalysis;
910
using Microsoft.CodeAnalysis.CSharp;
1011
using Microsoft.CodeAnalysis.CSharp.Syntax;
@@ -66,17 +67,17 @@ public override void Initialize(AnalysisContext context)
6667

6768
private static void HandleCompilationStart(CompilationStartAnalysisContext context)
6869
{
69-
Analyzer analyzer = new Analyzer(context.Options);
70+
Analyzer analyzer = new Analyzer(context.Options, context.CancellationToken);
7071
context.RegisterSyntaxNodeActionHonorExclusions(analyzer.HandleEnumMember, SyntaxKind.EnumMemberDeclaration);
7172
}
7273

7374
private class Analyzer
7475
{
7576
private readonly DocumentationSettings documentationSettings;
7677

77-
public Analyzer(AnalyzerOptions options)
78+
public Analyzer(AnalyzerOptions options, CancellationToken cancellationToken)
7879
{
79-
StyleCopSettings settings = options.GetStyleCopSettings();
80+
StyleCopSettings settings = options.GetStyleCopSettings(cancellationToken);
8081
this.documentationSettings = settings.DocumentationRules;
8182
}
8283

StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/SA1649FileNameMustMatchTypeName.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ namespace StyleCop.Analyzers.DocumentationRules
77
using System.Collections.Immutable;
88
using System.IO;
99
using System.Linq;
10+
using System.Threading;
1011
using Microsoft.CodeAnalysis;
1112
using Microsoft.CodeAnalysis.CSharp;
1213
using Microsoft.CodeAnalysis.CSharp.Syntax;
@@ -54,17 +55,17 @@ public override void Initialize(AnalysisContext context)
5455

5556
private static void HandleCompilationStart(CompilationStartAnalysisContext context)
5657
{
57-
var analyzer = new Analyzer(context.Options);
58+
var analyzer = new Analyzer(context.Options, context.CancellationToken);
5859
context.RegisterSyntaxTreeActionHonorExclusions(analyzer.HandleSyntaxTreeAction);
5960
}
6061

6162
private class Analyzer
6263
{
6364
private readonly FileNamingConvention fileNamingConvention;
6465

65-
public Analyzer(AnalyzerOptions options)
66+
public Analyzer(AnalyzerOptions options, CancellationToken cancellationToken)
6667
{
67-
StyleCopSettings settings = options.GetStyleCopSettings();
68+
StyleCopSettings settings = options.GetStyleCopSettings(cancellationToken);
6869
this.fileNamingConvention = settings.DocumentationRules.FileNamingConvention;
6970
}
7071

StyleCop.Analyzers/StyleCop.Analyzers/NamingRules/SA1305FieldNamesMustNotUseHungarianNotation.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ namespace StyleCop.Analyzers.NamingRules
66
using System;
77
using System.Collections.Immutable;
88
using System.Text.RegularExpressions;
9+
using System.Threading;
910
using Helpers;
1011
using Microsoft.CodeAnalysis;
1112
using Microsoft.CodeAnalysis.CSharp;
@@ -80,17 +81,17 @@ public override void Initialize(AnalysisContext context)
8081

8182
private static void HandleCompilationStart(CompilationStartAnalysisContext context)
8283
{
83-
Analyzer analyzer = new Analyzer(context.Options);
84+
Analyzer analyzer = new Analyzer(context.Options, context.CancellationToken);
8485
context.RegisterSyntaxNodeActionHonorExclusions(analyzer.HandleVariableDeclarationSyntax, SyntaxKind.VariableDeclaration);
8586
}
8687

8788
private sealed class Analyzer
8889
{
8990
private readonly NamingSettings namingSettings;
9091

91-
public Analyzer(AnalyzerOptions options)
92+
public Analyzer(AnalyzerOptions options, CancellationToken cancellationToken)
9293
{
93-
StyleCopSettings settings = options.GetStyleCopSettings();
94+
StyleCopSettings settings = options.GetStyleCopSettings(cancellationToken);
9495
this.namingSettings = settings.NamingRules;
9596
}
9697

StyleCop.Analyzers/StyleCop.Analyzers/Settings/SettingsHelper.cs

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

44
namespace StyleCop.Analyzers
55
{
6+
using System;
7+
using System.Collections.Concurrent;
68
using System.Collections.Immutable;
79
using System.IO;
10+
using System.Linq;
11+
using System.Reflection;
12+
using System.Threading;
813
using Microsoft.CodeAnalysis;
914
using Microsoft.CodeAnalysis.Diagnostics;
15+
using Microsoft.CodeAnalysis.Text;
1016
using Newtonsoft.Json;
1117
using StyleCop.Analyzers.Settings.ObjectModel;
1218

@@ -17,35 +23,52 @@ internal static class SettingsHelper
1723
{
1824
private const string SettingsFileName = "stylecop.json";
1925

26+
private static readonly bool AvoidAdditionalTextGetText;
27+
28+
private static readonly ConcurrentDictionary<Type, ConcurrentDictionary<string, FieldInfo>> FieldInfos =
29+
new ConcurrentDictionary<Type, ConcurrentDictionary<string, FieldInfo>>();
30+
31+
private static readonly ConcurrentDictionary<Type, ConcurrentDictionary<string, PropertyInfo>> PropertyInfos =
32+
new ConcurrentDictionary<Type, ConcurrentDictionary<string, PropertyInfo>>();
33+
34+
static SettingsHelper()
35+
{
36+
// dotnet/roslyn#6596 was fixed for Roslyn 1.2
37+
AvoidAdditionalTextGetText = typeof(AdditionalText).GetTypeInfo().Assembly.GetName().Version < new Version(1, 2, 0, 0);
38+
}
39+
2040
/// <summary>
2141
/// Gets the StyleCop settings.
2242
/// </summary>
2343
/// <param name="context">The context that will be used to determine the StyleCop settings.</param>
44+
/// <param name="cancellationToken">The cancellation token that the operation will observe.</param>
2445
/// <returns>A <see cref="StyleCopSettings"/> instance that represents the StyleCop settings for the given context.</returns>
25-
internal static StyleCopSettings GetStyleCopSettings(this SyntaxTreeAnalysisContext context)
46+
internal static StyleCopSettings GetStyleCopSettings(this SyntaxTreeAnalysisContext context, CancellationToken cancellationToken)
2647
{
27-
return context.Options.GetStyleCopSettings();
48+
return context.Options.GetStyleCopSettings(cancellationToken);
2849
}
2950

3051
/// <summary>
3152
/// Gets the StyleCop settings.
3253
/// </summary>
3354
/// <param name="options">The analyzer options that will be used to determine the StyleCop settings.</param>
55+
/// <param name="cancellationToken">The cancellation token that the operation will observe.</param>
3456
/// <returns>A <see cref="StyleCopSettings"/> instance that represents the StyleCop settings for the given context.</returns>
35-
internal static StyleCopSettings GetStyleCopSettings(this AnalyzerOptions options)
57+
internal static StyleCopSettings GetStyleCopSettings(this AnalyzerOptions options, CancellationToken cancellationToken)
3658
{
37-
return GetStyleCopSettings(options != null ? options.AdditionalFiles : ImmutableArray.Create<AdditionalText>());
59+
return GetStyleCopSettings(options != null ? options.AdditionalFiles : ImmutableArray.Create<AdditionalText>(), cancellationToken);
3860
}
3961

40-
private static StyleCopSettings GetStyleCopSettings(ImmutableArray<AdditionalText> additionalFiles)
62+
private static StyleCopSettings GetStyleCopSettings(ImmutableArray<AdditionalText> additionalFiles, CancellationToken cancellationToken)
4163
{
4264
try
4365
{
4466
foreach (var additionalFile in additionalFiles)
4567
{
4668
if (Path.GetFileName(additionalFile.Path).ToLowerInvariant() == SettingsFileName)
4769
{
48-
var root = JsonConvert.DeserializeObject<SettingsFile>(additionalFile.GetText().ToString());
70+
SourceText additionalTextContent = GetText(additionalFile, cancellationToken);
71+
var root = JsonConvert.DeserializeObject<SettingsFile>(additionalTextContent.ToString());
4972
return root.Settings;
5073
}
5174
}
@@ -57,5 +80,79 @@ private static StyleCopSettings GetStyleCopSettings(ImmutableArray<AdditionalTex
5780

5881
return new StyleCopSettings();
5982
}
83+
84+
/// <summary>
85+
/// This code works around dotnet/roslyn#6596 by using reflection APIs to bypass the problematic method while
86+
/// reading the content of an <see cref="AdditionalText"/> file. If the reflection approach fails, the code
87+
/// falls back to the previous behavior.
88+
/// </summary>
89+
/// <param name="additionalText">The additional text to read.</param>
90+
/// <param name="cancellationToken">The cancellation token that the operation will observe.</param>
91+
/// <returns>The content of the additional text file.</returns>
92+
private static SourceText GetText(AdditionalText additionalText, CancellationToken cancellationToken)
93+
{
94+
if (AvoidAdditionalTextGetText)
95+
{
96+
object document = GetField(additionalText, "_document");
97+
if (document != null)
98+
{
99+
object textSource = GetField(document, "textSource");
100+
if (textSource != null)
101+
{
102+
object textAndVersion = CallMethod(textSource, "GetValue", new[] { typeof(CancellationToken) }, cancellationToken);
103+
if (textAndVersion != null)
104+
{
105+
SourceText text = GetProperty(textAndVersion, "Text") as SourceText;
106+
if (text != null)
107+
{
108+
return text;
109+
}
110+
}
111+
}
112+
}
113+
}
114+
115+
return additionalText.GetText(cancellationToken);
116+
}
117+
118+
private static object GetField(object obj, string name)
119+
{
120+
if (obj == null)
121+
{
122+
return null;
123+
}
124+
125+
ConcurrentDictionary<string, FieldInfo> fieldsForType = FieldInfos.GetOrAdd(obj.GetType(), _ => new ConcurrentDictionary<string, FieldInfo>());
126+
FieldInfo fieldInfo;
127+
if (!fieldsForType.TryGetValue(name, out fieldInfo))
128+
{
129+
fieldInfo = fieldsForType.GetOrAdd(name, _ => obj.GetType().GetRuntimeFields().FirstOrDefault(i => i.Name == name));
130+
}
131+
132+
return fieldInfo?.GetValue(obj);
133+
}
134+
135+
private static object CallMethod(object obj, string name, Type[] parameters, params object[] arguments)
136+
{
137+
MethodInfo methodInfo = obj?.GetType().GetRuntimeMethod(name, parameters);
138+
return methodInfo?.Invoke(obj, arguments);
139+
}
140+
141+
private static object GetProperty(object obj, string name)
142+
{
143+
if (obj == null)
144+
{
145+
return null;
146+
}
147+
148+
ConcurrentDictionary<string, PropertyInfo> propertiesForType = PropertyInfos.GetOrAdd(obj.GetType(), _ => new ConcurrentDictionary<string, PropertyInfo>());
149+
PropertyInfo propertyInfo;
150+
if (!propertiesForType.TryGetValue(name, out propertyInfo))
151+
{
152+
propertyInfo = propertiesForType.GetOrAdd(name, _ => obj.GetType().GetRuntimeProperty(name));
153+
}
154+
155+
return propertyInfo?.GetValue(obj);
156+
}
60157
}
61158
}

0 commit comments

Comments
 (0)