提交 def35ce5 编写于 作者: M manishv

Fix for bug 1022125: diagnostic analyzer service shouldn't load diagnostic...

Fix for bug 1022125: diagnostic analyzer service shouldn't load diagnostic analyzer before it is actually used

This change refactors the IDE diagnostic service to track per-language workspace analyzers (VSIX based analyzers). Each analyzer assembly reference created for every analyzer asset is now queried for analyzers in context of a language, hence avoiding instantiating analyzers targeting a different language. Additionally, now that we use the MetadataReader instead of Reflection based Assembly load to find diagnostic analyzer types within an analyzer assembly, we will delay loading of language specific analyzer assemblies until it is queried for analyzers for that language. (changeset 1332021)
上级 8b080f51
......@@ -80,7 +80,7 @@ public void TestMetadataParse()
{
AnalyzerFileReference reference = new AnalyzerFileReference(Assembly.GetExecutingAssembly().Location);
var analyzerTypeNameMap = reference.GetAnalyzerTypeNameMap();
Assert.Equal(3, analyzerTypeNameMap.Keys.Count);
Assert.Equal(3, analyzerTypeNameMap.Keys.Count());
Assert.Equal(6, analyzerTypeNameMap[string.Empty].Count);
Assert.Contains("Microsoft.CodeAnalysis.UnitTests.AnalyzerFileReferenceTests+TestAnalyzer", analyzerTypeNameMap[string.Empty]);
Assert.Contains("Microsoft.CodeAnalysis.UnitTests.TestAnalyzerCSAll", analyzerTypeNameMap[string.Empty]);
......@@ -90,11 +90,9 @@ public void TestMetadataParse()
Assert.Contains("Microsoft.CodeAnalysis.UnitTests.OpenGenericAnalyzer`1", analyzerTypeNameMap[string.Empty]);
Assert.DoesNotContain("Microsoft.CodeAnalysis.UnitTests.Test.NotAnAnalyzer", analyzerTypeNameMap[string.Empty]);
Assert.Equal(4, analyzerTypeNameMap[LanguageNames.CSharp].Count);
Assert.Equal(2, analyzerTypeNameMap[LanguageNames.CSharp].Count);
Assert.Contains("Microsoft.CodeAnalysis.UnitTests.AnalyzerFileReferenceTests+TestAnalyzerCS", analyzerTypeNameMap[LanguageNames.CSharp]);
Assert.Contains("Microsoft.CodeAnalysis.UnitTests.TestAnalyzerCSVB", analyzerTypeNameMap[LanguageNames.CSharp]);
Assert.Contains("Microsoft.CodeAnalysis.UnitTests.TestAnalyzerCSAll", analyzerTypeNameMap[string.Empty]);
Assert.Contains("Microsoft.CodeAnalysis.UnitTests.TestAnalyzerAllCS", analyzerTypeNameMap[string.Empty]);
Assert.Equal(2, analyzerTypeNameMap[LanguageNames.VisualBasic].Count);
Assert.Contains("Microsoft.CodeAnalysis.UnitTests.AnalyzerFileReferenceTests+TestAnalyzerVB", analyzerTypeNameMap[LanguageNames.VisualBasic]);
......@@ -123,9 +121,7 @@ public void TestGetAnalyzers()
public void TestGetAnalyzersPerLanguage()
{
AnalyzerFileReference reference = new AnalyzerFileReference(Assembly.GetExecutingAssembly().Location);
var builder = ImmutableArray.CreateBuilder<IDiagnosticAnalyzer>();
reference.AddAnalyzers(builder, LanguageNames.CSharp);
var analyzers = builder.ToImmutable();
var analyzers = reference.GetAnalyzers(LanguageNames.CSharp);
Assert.Equal(6, analyzers.Length);
var analyzerNames = analyzers.Select(a => a.GetType().Name);
Assert.Contains("TestAnalyzer", analyzerNames);
......@@ -135,13 +131,15 @@ public void TestGetAnalyzersPerLanguage()
Assert.Contains("TestAnalyzerAllCS", analyzerNames);
Assert.Contains("NestedAnalyzer", analyzerNames);
builder = ImmutableArray.CreateBuilder<IDiagnosticAnalyzer>();
reference.AddAnalyzers(builder, LanguageNames.VisualBasic);
analyzers = builder.ToImmutable();
analyzers = reference.GetAnalyzers(LanguageNames.VisualBasic);
analyzerNames = analyzers.Select(a => a.GetType().Name);
Assert.Equal(2, analyzers.Length);
Assert.Equal(6, analyzers.Length);
Assert.Contains("TestAnalyzerVB", analyzerNames);
Assert.Contains("TestAnalyzerCSVB", analyzerNames);
Assert.Contains("TestAnalyzer", analyzerNames);
Assert.Contains("TestAnalyzerCSAll", analyzerNames);
Assert.Contains("TestAnalyzerAllCS", analyzerNames);
Assert.Contains("NestedAnalyzer", analyzerNames);
}
[Fact]
......
......@@ -9,6 +9,8 @@
using System.Reflection.Metadata;
using System.Security;
using Roslyn.Utilities;
using System.Collections.Concurrent;
using System.Threading;
namespace Microsoft.CodeAnalysis.Diagnostics
{
......@@ -26,11 +28,14 @@ public sealed partial class AnalyzerFileReference : AnalyzerReference
{
private readonly string fullPath;
private readonly Func<string, Assembly> getAssembly;
private string displayName;
private ImmutableArray<IDiagnosticAnalyzer>? lazyAnalyzers;
private Assembly assembly;
private Dictionary<string, HashSet<string>> analyzerTypeNameMap;
private string lazyDisplayName;
private ImmutableArray<IDiagnosticAnalyzer> lazyAllAnalyzers;
private ImmutableArray<IDiagnosticAnalyzer> lazyLanguageAgnosticAnalyzers;
private ConcurrentDictionary<string, ImmutableArray<IDiagnosticAnalyzer>> lazyAnalyzersPerLanguage;
private Assembly lazyAssembly;
private ImmutableDictionary<string, HashSet<string>> lazyAnalyzerTypeNameMap;
public event EventHandler<AnalyzerLoadFailureEventArgs> AnalyzerLoadFailed;
......@@ -72,33 +77,47 @@ public AnalyzerFileReference(string fullPath, Func<string, Assembly> getAssembly
throw new ArgumentException(e.Message, "fullPath");
}
lazyAnalyzers = null;
this.lazyAllAnalyzers = default(ImmutableArray<IDiagnosticAnalyzer>);
this.lazyLanguageAgnosticAnalyzers = default(ImmutableArray<IDiagnosticAnalyzer>);
this.lazyAnalyzersPerLanguage = null;
this.getAssembly = getAssembly;
}
public override ImmutableArray<IDiagnosticAnalyzer> GetAnalyzers()
public override ImmutableArray<IDiagnosticAnalyzer> GetAnalyzersForAllLanguages()
{
if (!lazyAnalyzers.HasValue)
if (lazyAllAnalyzers.IsDefault)
{
lazyAnalyzers = MetadataCache.GetOrCreateAnalyzersFromFile(this);
var allAnalyzers = MetadataCache.GetOrCreateAnalyzersFromFile(this);
ImmutableInterlocked.InterlockedCompareExchange(ref this.lazyAllAnalyzers, allAnalyzers, default(ImmutableArray<IDiagnosticAnalyzer>));
}
return lazyAnalyzers.Value;
return lazyAllAnalyzers;
}
public override ImmutableArray<IDiagnosticAnalyzer> GetAnalyzersForLanguage(string language)
public override ImmutableArray<IDiagnosticAnalyzer> GetAnalyzers(string language)
{
if (string.IsNullOrEmpty(language))
{
throw new ArgumentException("language");
}
if (!lazyAnalyzers.HasValue)
ImmutableArray<IDiagnosticAnalyzer> analyzers;
if (this.lazyAnalyzersPerLanguage == null)
{
Interlocked.CompareExchange(ref this.lazyAnalyzersPerLanguage, new ConcurrentDictionary<string, ImmutableArray<IDiagnosticAnalyzer>>(), null);
}
else if (this.lazyAnalyzersPerLanguage.TryGetValue(language, out analyzers))
{
lazyAnalyzers = MetadataCache.GetOrCreateAnalyzersFromFile(this, language);
return analyzers;
}
return lazyAnalyzers.Value;
analyzers = MetadataCache.GetOrCreateAnalyzersFromFile(this, language);
if (!this.lazyAnalyzersPerLanguage.TryAdd(language, analyzers))
{
return this.lazyAnalyzersPerLanguage[language];
}
return analyzers;
}
public override string FullPath
......@@ -113,13 +132,13 @@ public override string Display
{
get
{
if (displayName == null)
if (lazyDisplayName == null)
{
try
{
var assemblyName = AssemblyName.GetAssemblyName(this.FullPath);
displayName = assemblyName.Name;
return displayName;
lazyDisplayName = assemblyName.Name;
return lazyDisplayName;
}
catch (ArgumentException)
{ }
......@@ -132,54 +151,58 @@ public override string Display
catch (FileNotFoundException)
{ }
displayName = base.Display;
lazyDisplayName = base.Display;
}
return displayName;
return lazyDisplayName;
}
}
private ImmutableArray<IDiagnosticAnalyzer> GetLanguageAgnosticAnalyzers(ImmutableDictionary<string, HashSet<string>> analyzerTypeNameMap, Assembly analyzerAssembly)
{
if (this.lazyLanguageAgnosticAnalyzers.IsDefault)
{
HashSet<string> analyzerTypeNames;
ImmutableArray<IDiagnosticAnalyzer> analyzers;
if (!analyzerTypeNameMap.TryGetValue(string.Empty, out analyzerTypeNames))
{
analyzers = ImmutableArray<IDiagnosticAnalyzer>.Empty;
}
else
{
analyzers = GetAnalyzersForTypeNames(analyzerAssembly, analyzerTypeNames).ToImmutableArrayOrEmpty();
}
ImmutableInterlocked.InterlockedCompareExchange(ref this.lazyLanguageAgnosticAnalyzers, analyzers, default(ImmutableArray<IDiagnosticAnalyzer>));
}
return this.lazyLanguageAgnosticAnalyzers;
}
/// <summary>
/// Adds the <see cref="ImmutableArray{T}"/> of <see cref="IDiagnosticAnalyzer"/> defined in this assembly reference.
/// </summary>
internal void AddAnalyzers(ImmutableArray<IDiagnosticAnalyzer>.Builder builder, string languageOpt = null)
{
// We handle loading of analyzer assemblies ourselves. This allows us to avoid locking the assembly
// file on disk.
List<Type> types = new List<Type>();
ImmutableDictionary<string, HashSet<string>> analyzerTypeNameMap;
Assembly analyzerAssembly = null;
HashSet<string> analyzerTypeNames = new HashSet<string>();
try
{
if (analyzerTypeNameMap == null)
{
analyzerTypeNameMap = GetAnalyzerTypeNameMap();
// If there are language agnostic analyzers, then instantiate them but only do it once.
if (analyzerTypeNameMap.ContainsKey(string.Empty))
{
analyzerTypeNames.AddAll(analyzerTypeNameMap[string.Empty]);
}
}
analyzerTypeNameMap = GetAnalyzerTypeNameMap();
// If the user didn't ask for a specific language then return all analyzers.
bool hasAnyAnalyzerTypes;
if (languageOpt == null)
{
// The type names of language agnostic analyzer have already been added. Add all other language analyzers.
analyzerTypeNames.AddAll(analyzerTypeNameMap.SelectMany(kvp => kvp.Key != string.Empty ? kvp.Value : SpecializedCollections.EmptyEnumerable<string>()));
hasAnyAnalyzerTypes = analyzerTypeNameMap.Any();
}
else
{
// Add the analyzers for the specific language.
if (analyzerTypeNameMap.ContainsKey(languageOpt))
{
analyzerTypeNames.AddAll(analyzerTypeNameMap[languageOpt]);
}
hasAnyAnalyzerTypes = analyzerTypeNameMap.ContainsKey(string.Empty) || analyzerTypeNameMap.ContainsKey(languageOpt);
}
// If there are no analyzers, don't load the assembly at all.
if (analyzerTypeNames.IsEmpty())
if (!hasAnyAnalyzerTypes)
{
this.AnalyzerLoadFailed?.Invoke(this, new AnalyzerLoadFailureEventArgs(AnalyzerLoadFailureEventArgs.FailureErrorCode.NoAnalyzers, null, null));
return;
......@@ -194,42 +217,93 @@ internal void AddAnalyzers(ImmutableArray<IDiagnosticAnalyzer>.Builder builder,
return;
}
// Now given the type names, get the actual System.Type and try to create an instance of the type through reflection.
bool hasAnalyzers = false;
var initialCount = builder.Count;
// Add language agnostic analyzers.
var languageAgnosticAnalyzers = this.GetLanguageAgnosticAnalyzers(analyzerTypeNameMap, analyzerAssembly);
builder.AddRange(languageAgnosticAnalyzers);
// Add language specific analyzers.
var languageSpecificAnalyzerTypeNames = GetLanguageSpecificAnalyzerTypeNames(analyzerTypeNameMap, languageOpt);
var languageSpecificAnalyzers = this.GetAnalyzersForTypeNames(analyzerAssembly, languageSpecificAnalyzerTypeNames);
builder.AddRange(languageSpecificAnalyzers);
// If there were types with the attribute but weren't an analyzer, generate a diagnostic.
if (builder.Count == initialCount)
{
this.AnalyzerLoadFailed?.Invoke(this, new AnalyzerLoadFailureEventArgs(AnalyzerLoadFailureEventArgs.FailureErrorCode.NoAnalyzers, null, null));
}
}
private static IEnumerable<string> GetLanguageSpecificAnalyzerTypeNames(ImmutableDictionary<string, HashSet<string>> analyzerTypeNameMap, string languageOpt)
{
HashSet<string> languageSpecificAnalyzerTypeNames = new HashSet<string>();
if (languageOpt == null)
{
// If the user didn't ask for a specific language then return all language specific analyzers.
languageSpecificAnalyzerTypeNames.AddAll(analyzerTypeNameMap.SelectMany(kvp => kvp.Key != string.Empty ? kvp.Value : SpecializedCollections.EmptyEnumerable<string>()));
}
else
{
// Add the analyzers for the specific language.
if (analyzerTypeNameMap.ContainsKey(languageOpt))
{
languageSpecificAnalyzerTypeNames.AddAll(analyzerTypeNameMap[languageOpt]);
}
}
return languageSpecificAnalyzerTypeNames;
}
private IEnumerable<IDiagnosticAnalyzer> GetAnalyzersForTypeNames(Assembly analyzerAssembly, IEnumerable<string> analyzerTypeNames)
{
// Given the type names, get the actual System.Type and try to create an instance of the type through reflection.
foreach (var typeName in analyzerTypeNames)
{
IDiagnosticAnalyzer analyzer = null;
try
{
var type = analyzerAssembly.GetType(typeName, throwOnError: true);
if (type.GetTypeInfo().ImplementedInterfaces.Contains(typeof(IDiagnosticAnalyzer)))
{
hasAnalyzers = true;
builder.Add((IDiagnosticAnalyzer)Activator.CreateInstance(type));
analyzer = (IDiagnosticAnalyzer)Activator.CreateInstance(type);
}
}
catch (Exception e) if (e is TypeLoadException || e is BadImageFormatException || e is FileNotFoundException || e is FileLoadException ||
e is ArgumentException || e is NotSupportedException || e is TargetInvocationException || e is MemberAccessException)
{
this.AnalyzerLoadFailed?.Invoke(this, new AnalyzerLoadFailureEventArgs(AnalyzerLoadFailureEventArgs.FailureErrorCode.UnableToCreateAnalyzer, e, typeName));
analyzer = null;
}
if (analyzer != null)
{
yield return analyzer;
}
}
}
// If there were types with the attribute but weren't an analyzer.
if (!hasAnalyzers)
internal ImmutableDictionary<string, HashSet<string>> GetAnalyzerTypeNameMap()
{
if (this.lazyAnalyzerTypeNameMap == null)
{
this.AnalyzerLoadFailed?.Invoke(this, new AnalyzerLoadFailureEventArgs(AnalyzerLoadFailureEventArgs.FailureErrorCode.NoAnalyzers, null, null));
var analyzerTypeNameMap = GetAnalyzerTypeNameMap(this.fullPath);
Interlocked.CompareExchange(ref this.lazyAnalyzerTypeNameMap, analyzerTypeNameMap, null);
}
return this.lazyAnalyzerTypeNameMap;
}
/// <summary>
/// Opens the analyzer dll with the metadata reader and builds a map of language -> analyzer type names.
/// </summary>
internal Dictionary<string, HashSet<string>> GetAnalyzerTypeNameMap()
private static ImmutableDictionary<string, HashSet<string>> GetAnalyzerTypeNameMap(string fullPath)
{
var typeNameMap = new Dictionary<string, HashSet<string>>();
var diagnosticNamespaceName = string.Format("{0}.{1}.{2}", nameof(Microsoft), nameof(CodeAnalysis), nameof(Diagnostics));
var supportedLanguageNames = new HashSet<string>();
using (var assembly = MetadataFileFactory.CreateAssembly(this.fullPath))
using (var assembly = MetadataFileFactory.CreateAssembly(fullPath))
{
foreach (var module in assembly.Modules)
{
......@@ -237,15 +311,13 @@ internal void AddAnalyzers(ImmutableArray<IDiagnosticAnalyzer>.Builder builder,
{
var typeDef = module.MetadataReader.GetTypeDefinition(typeDefHandle);
var customAttrs = typeDef.GetCustomAttributes();
foreach (var customAttrHandle in customAttrs)
{
var peModule = module.Module;
Handle ctor;
if (peModule.IsTargetAttribute(customAttrHandle, diagnosticNamespaceName, nameof(DiagnosticAnalyzerAttribute), out ctor))
{
string typeName = GetFullyQualifiedTypeName(typeDef, peModule);
// The DiagnosticAnalyzerAttribute has two constructors:
// 1. Paramterless - means that the analyzer is applicable to any language.
// 2. Single string parameter specifying the language.
......@@ -261,30 +333,41 @@ internal void AddAnalyzers(ImmutableArray<IDiagnosticAnalyzer>.Builder builder,
string languageName;
if (PEModule.CrackStringInAttributeValue(out languageName, ref argsReader))
{
if (!typeNameMap.ContainsKey(languageName))
{
typeNameMap.Add(languageName, new HashSet<string>());
}
typeNameMap[languageName].Add(typeName);
supportedLanguageNames.Add(languageName);
}
}
}
// otherwise the attribute is applicable to all languages.
else
{
if (!typeNameMap.ContainsKey(string.Empty))
{
typeNameMap.Add(string.Empty, new HashSet<string>());
}
typeNameMap[string.Empty].Add(typeName);
supportedLanguageNames.Clear();
supportedLanguageNames.Add(string.Empty);
break;
}
}
}
if (supportedLanguageNames.Any())
{
string typeName = GetFullyQualifiedTypeName(typeDef, module.Module);
foreach (var languageName in supportedLanguageNames)
{
if (!typeNameMap.ContainsKey(languageName))
{
typeNameMap.Add(languageName, new HashSet<string>());
}
typeNameMap[languageName].Add(typeName);
}
supportedLanguageNames.Clear();
}
}
}
}
return typeNameMap;
return typeNameMap.ToImmutableDictionary();
}
private static string GetFullyQualifiedTypeName(TypeDefinition typeDef, PEModule peModule)
......@@ -325,14 +408,15 @@ public override int GetHashCode()
public Assembly GetAssembly()
{
if (assembly == null)
if (lazyAssembly == null)
{
assembly = getAssembly != null ?
var assembly = getAssembly != null ?
getAssembly(fullPath) :
InMemoryAssemblyLoader.Load(fullPath);
Interlocked.CompareExchange(ref this.lazyAssembly, assembly, null);
}
return assembly;
return lazyAssembly;
}
}
}
......@@ -120,12 +120,14 @@ internal struct CachedAnalyzers
// Save a reference to the cached analyzers so that they don't get collected
// if the metadata object gets collected.
public readonly WeakReference Analyzers;
public readonly string Language;
public CachedAnalyzers(ImmutableArray<IDiagnosticAnalyzer> analyzers)
public CachedAnalyzers(ImmutableArray<IDiagnosticAnalyzer> analyzers, string language)
{
Debug.Assert(analyzers != null);
this.Analyzers = new WeakReference(analyzers);
this.Language = language;
}
}
......@@ -714,7 +716,7 @@ internal static ImmutableArray<IDiagnosticAnalyzer> GetOrCreateAnalyzersFromFile
CachedAnalyzers cachedAnalyzers;
if (analyzersFromFiles.TryGetValue(key, out cachedAnalyzers))
{
if (cachedAnalyzers.Analyzers.IsAlive)
if (cachedAnalyzers.Analyzers.IsAlive && cachedAnalyzers.Language == langauge)
{
return (ImmutableArray<IDiagnosticAnalyzer>)cachedAnalyzers.Analyzers.Target;
}
......@@ -735,7 +737,7 @@ internal static ImmutableArray<IDiagnosticAnalyzer> GetOrCreateAnalyzersFromFile
// refresh the timestamp (the file may have changed just before we memory-mapped it):
key = FileKey.Create(fullPath);
analyzersFromFiles[key] = new CachedAnalyzers(analyzers);
analyzersFromFiles[key] = new CachedAnalyzers(analyzers, langauge);
Debug.Assert(!analyzerAssemblyKeys.Contains(key));
analyzerAssemblyKeys.Add(key);
EnableCompactTimer();
......
......@@ -13,7 +13,7 @@ namespace Microsoft.CodeAnalysis.Diagnostics
/// </remarks>
public abstract class AnalyzerReference
{
internal AnalyzerReference()
protected AnalyzerReference()
{
}
......@@ -38,7 +38,18 @@ public virtual bool IsUnresolved
get { return false; }
}
public abstract ImmutableArray<IDiagnosticAnalyzer> GetAnalyzers();
public abstract ImmutableArray<IDiagnosticAnalyzer> GetAnalyzersForLanguage(string language);
/// <summary>
/// Gets all the diagnostic analyzers defined in this assembly reference, irrespective of the language supported by the analyzer.
/// Use this method only if you need all the analyzers defined in the assembly, without a language context.
/// In most instances, either the analyzer reference is associated with a project or is being queried for analyzers in a particular language context.
/// If so, use <see cref="GetAnalyzers(string)"/> method.
/// </summary>
/// <returns></returns>
public abstract ImmutableArray<IDiagnosticAnalyzer> GetAnalyzersForAllLanguages();
/// <summary>
/// Gets all the diagnostic analyzers defined in this assembly reference for the given <paramref name="language"/>.
/// </summary>
public abstract ImmutableArray<IDiagnosticAnalyzer> GetAnalyzers(string language);
}
}
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册