44namespace 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