Skip to content

Commit 683c138

Browse files
committed
Use reflection to access Roslyn internals rather than the file system APIs
1 parent c2d711e commit 683c138

1 file changed

Lines changed: 61 additions & 30 deletions

File tree

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

Lines changed: 61 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@
44
namespace StyleCop.Analyzers
55
{
66
using System;
7+
using System.Collections.Concurrent;
78
using System.Collections.Immutable;
89
using System.IO;
10+
using System.Linq;
911
using System.Reflection;
1012
using System.Threading;
1113
using Microsoft.CodeAnalysis;
1214
using Microsoft.CodeAnalysis.Diagnostics;
15+
using Microsoft.CodeAnalysis.Text;
1316
using Newtonsoft.Json;
1417
using StyleCop.Analyzers.Settings.ObjectModel;
1518

@@ -20,8 +23,11 @@ internal static class SettingsHelper
2023
{
2124
private const string SettingsFileName = "stylecop.json";
2225

23-
private static Func<string, bool> fileExists;
24-
private static Func<string, string> fileReadAllText;
26+
private static readonly ConcurrentDictionary<Type, ConcurrentDictionary<string, FieldInfo>> FieldInfos =
27+
new ConcurrentDictionary<Type, ConcurrentDictionary<string, FieldInfo>>();
28+
29+
private static readonly ConcurrentDictionary<Type, ConcurrentDictionary<string, PropertyInfo>> PropertyInfos =
30+
new ConcurrentDictionary<Type, ConcurrentDictionary<string, PropertyInfo>>();
2531

2632
/// <summary>
2733
/// Gets the StyleCop settings.
@@ -53,8 +59,8 @@ private static StyleCopSettings GetStyleCopSettings(ImmutableArray<AdditionalTex
5359
{
5460
if (Path.GetFileName(additionalFile.Path).ToLowerInvariant() == SettingsFileName)
5561
{
56-
string additionalTextContent = ReadAdditionalText(additionalFile, cancellationToken);
57-
var root = JsonConvert.DeserializeObject<SettingsFile>(additionalTextContent);
62+
SourceText additionalTextContent = GetText(additionalFile, cancellationToken);
63+
var root = JsonConvert.DeserializeObject<SettingsFile>(additionalTextContent.ToString());
5864
return root.Settings;
5965
}
6066
}
@@ -68,49 +74,74 @@ private static StyleCopSettings GetStyleCopSettings(ImmutableArray<AdditionalTex
6874
}
6975

7076
/// <summary>
71-
/// This code works around dotnet/roslyn#6596 by using the file system APIs instead of the Roslyn APIs to read
72-
/// the additional text. If the file system APIs are not available, the code falls back to the previous
73-
/// behavior.
77+
/// This code works around dotnet/roslyn#6596 by using reflection APIs to bypass the problematic method while
78+
/// reading the content of an <see cref="AdditionalText"/> file. If the reflection approach fails, the code
79+
/// falls back to the previous behavior.
7480
/// </summary>
7581
/// <param name="additionalText">The additional text to read.</param>
7682
/// <param name="cancellationToken">The cancellation token that the operation will observe.</param>
77-
/// <returns>The content of the additional text as a string.</returns>
78-
private static string ReadAdditionalText(AdditionalText additionalText, CancellationToken cancellationToken)
83+
/// <returns>The content of the additional text file.</returns>
84+
private static SourceText GetText(AdditionalText additionalText, CancellationToken cancellationToken)
7985
{
80-
if (fileExists == null)
86+
object document = GetField(additionalText, "_document");
87+
if (document != null)
8188
{
82-
Type fileClass = typeof(string).GetTypeInfo().Assembly.GetType("System.IO.File");
83-
if (fileClass != null)
89+
object textSource = GetField(document, "textSource");
90+
if (textSource != null)
8491
{
85-
MethodInfo readAllText = fileClass.GetRuntimeMethod("ReadAllText", new[] { typeof(string) });
86-
MethodInfo exists = fileClass.GetRuntimeMethod("Exists", new[] { typeof(string) });
87-
if (readAllText != null && exists != null)
92+
object textAndVersion = CallMethod(textSource, "GetValue", new[] { typeof(CancellationToken) }, cancellationToken);
93+
if (textAndVersion != null)
8894
{
89-
Interlocked.CompareExchange(ref fileReadAllText, (Func<string, string>)readAllText.CreateDelegate(typeof(Func<string, string>)), null);
90-
Interlocked.CompareExchange(ref fileExists, (Func<string, bool>)exists.CreateDelegate(typeof(Func<string, bool>)), null);
95+
SourceText text = GetProperty(textAndVersion, "Text") as SourceText;
96+
if (text != null)
97+
{
98+
return text;
99+
}
91100
}
92101
}
102+
}
93103

94-
if (fileExists == null)
95-
{
96-
// this special case allows for a clean fall back to AdditionalText.GetText()
97-
fileExists = _ => false;
98-
}
104+
return additionalText.GetText(cancellationToken);
105+
}
106+
107+
private static object GetField(object obj, string name)
108+
{
109+
if (obj == null)
110+
{
111+
return null;
99112
}
100113

101-
try
114+
ConcurrentDictionary<string, FieldInfo> fieldsForType = FieldInfos.GetOrAdd(obj.GetType(), _ => new ConcurrentDictionary<string, FieldInfo>());
115+
FieldInfo fieldInfo;
116+
if (!fieldsForType.TryGetValue(name, out fieldInfo))
102117
{
103-
if (fileExists(additionalText.Path))
104-
{
105-
return fileReadAllText(additionalText.Path);
106-
}
118+
fieldInfo = fieldsForType.GetOrAdd(name, _ => obj.GetType().GetRuntimeFields().FirstOrDefault(i => i.Name == name));
107119
}
108-
catch (IOException)
120+
121+
return fieldInfo?.GetValue(obj);
122+
}
123+
124+
private static object CallMethod(object obj, string name, Type[] parameters, params object[] arguments)
125+
{
126+
MethodInfo methodInfo = obj?.GetType().GetRuntimeMethod(name, parameters);
127+
return methodInfo?.Invoke(obj, arguments);
128+
}
129+
130+
private static object GetProperty(object obj, string name)
131+
{
132+
if (obj == null)
133+
{
134+
return null;
135+
}
136+
137+
ConcurrentDictionary<string, PropertyInfo> propertiesForType = PropertyInfos.GetOrAdd(obj.GetType(), _ => new ConcurrentDictionary<string, PropertyInfo>());
138+
PropertyInfo propertyInfo;
139+
if (!propertiesForType.TryGetValue(name, out propertyInfo))
109140
{
110-
// fall back to AdditionalFile.GetText()
141+
propertyInfo = propertiesForType.GetOrAdd(name, _ => obj.GetType().GetRuntimeProperty(name));
111142
}
112143

113-
return additionalText.GetText(cancellationToken).ToString();
144+
return propertyInfo?.GetValue(obj);
114145
}
115146
}
116147
}

0 commit comments

Comments
 (0)