Skip to content

Commit

Permalink
Merge pull request #11956 from tmat/AnalyzerLoader
Browse files Browse the repository at this point in the history
Improve implementation of analyzer loader
  • Loading branch information
tmat authored Jun 15, 2016
2 parents 25c8ca3 + 02369b0 commit c966b31
Show file tree
Hide file tree
Showing 34 changed files with 312 additions and 385 deletions.
2 changes: 1 addition & 1 deletion src/Compilers/CSharp/csc/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public static int Main(string[] args)
=> Main(args, SpecializedCollections.EmptyArray<string>());

public static int Main(string[] args, string[] extraArgs)
=> DesktopBuildClient.Run(args, extraArgs, RequestLanguage.CSharpCompile, Csc.Run, new SimpleAnalyzerAssemblyLoader());
=> DesktopBuildClient.Run(args, extraArgs, RequestLanguage.CSharpCompile, Csc.Run, new DesktopAnalyzerAssemblyLoader());

public static int Run(string[] args, string clientDir, string workingDir, string sdkDir, TextWriter textWriter, IAnalyzerAssemblyLoader analyzerLoader)
=> Csc.Run(args, new BuildPaths(clientDir: clientDir, workingDir: workingDir, sdkDir: sdkDir), textWriter, analyzerLoader);
Expand Down
9 changes: 3 additions & 6 deletions src/Compilers/CSharp/csc/csc.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,12 @@
<Compile Include="..\..\Shared\DesktopBuildClient.cs">
<Link>DesktopBuildClient.cs</Link>
</Compile>
<Compile Include="..\..\Shared\AbstractAnalyzerAssemblyLoader.cs">
<Link>AbstractAnalyzerAssemblyLoader.cs</Link>
<Compile Include="..\..\Shared\DesktopAnalyzerAssemblyLoader.cs">
<Link>DesktopAnalyzerAssemblyLoader.cs</Link>
</Compile>
<Compile Include="..\..\Shared\ExitingTraceListener.cs">
<Link>ExitingTraceListener.cs</Link>
</Compile>
<Compile Include="..\..\Shared\SimpleAnalyzerAssemblyLoader.cs">
<Link>SimpleAnalyzerAssemblyLoader.cs</Link>
</Compile>
<Compile Include="..\..\Shared\Csc.cs">
<Link>Csc.cs</Link>
</Compile>
Expand All @@ -71,4 +68,4 @@
<ImportGroup Label="Targets">
<Import Project="..\..\..\..\build\Targets\VSL.Imports.targets" />
</ImportGroup>
</Project>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public Exception LoadAnalyzer(string analyzerPath)

public class AnalyzerFileReferenceTests : TestBase
{
private static readonly SimpleAnalyzerAssemblyLoader s_analyzerLoader = new SimpleAnalyzerAssemblyLoader();
private static readonly DesktopAnalyzerAssemblyLoader s_analyzerLoader = new DesktopAnalyzerAssemblyLoader();

public static AnalyzerFileReference CreateAnalyzerFileReference(string fullPath)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
<Compile Include="PEWriter\PEBuilderTests.cs" />
<Compile Include="PEWriter\UsedNamespaceOrTypeTests.cs" />
<Compile Include="RealParserTests.cs" />
<Compile Include="SimpleAnalyzerAssemblyLoaderTests.cs" />
<Compile Include="DesktopAnalyzerAssemblyLoaderTests.cs" />
<Compile Include="SourceFileResolverTest.cs" />
<Compile Include="SourceGeneratorTests.cs" />
<Compile Include="Text\LargeTextTests.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,34 +1,31 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Roslyn.Test.Utilities;
using Roslyn.Utilities;
using Xunit;

namespace Microsoft.CodeAnalysis.UnitTests
{
public sealed class SimpleAnalyzerAssemblyLoaderTests : TestBase
public sealed class DesktopAnalyzerAssemblyLoaderTests : TestBase
{
[Fact]
public void AddDependencyLocationThrowsOnNull()
{
var loader = new SimpleAnalyzerAssemblyLoader();
var loader = new DesktopAnalyzerAssemblyLoader();

Assert.Throws<ArgumentNullException>("fullPath", () => loader.AddDependencyLocation(null));
Assert.Throws<ArgumentException>("fullPath", () => loader.AddDependencyLocation("a"));
}

[Fact]
public void ThrowsForMissingFile()
{
var path = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName() + ".dll");

var loader = new SimpleAnalyzerAssemblyLoader();
var loader = new DesktopAnalyzerAssemblyLoader();

Assert.ThrowsAny<Exception>(() => loader.LoadFromPath(path));
}
Expand All @@ -40,7 +37,7 @@ public void BasicLoad()

var alphaDll = directory.CreateFile("Alpha.dll").WriteAllBytes(TestResources.AssemblyLoadTests.Alpha);

var loader = new SimpleAnalyzerAssemblyLoader();
var loader = new DesktopAnalyzerAssemblyLoader();

Assembly alpha = loader.LoadFromPath(alphaDll.Path);

Expand All @@ -58,7 +55,7 @@ public void AssemblyLoading()
var gammaDll = Temp.CreateDirectory().CreateFile("Gamma.dll").WriteAllBytes(TestResources.AssemblyLoadTests.Gamma);
var deltaDll = Temp.CreateDirectory().CreateFile("Delta.dll").WriteAllBytes(TestResources.AssemblyLoadTests.Delta);

var loader = new SimpleAnalyzerAssemblyLoader();
var loader = new DesktopAnalyzerAssemblyLoader();
loader.AddDependencyLocation(alphaDll.Path);
loader.AddDependencyLocation(betaDll.Path);
loader.AddDependencyLocation(gammaDll.Path);
Expand Down
1 change: 0 additions & 1 deletion src/Compilers/Core/CommandLine/CommandLine.projitems
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
<Import_RootNamespace>CommandLine</Import_RootNamespace>
</PropertyGroup>
<ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)AssemblyIdentityUtils.cs" />
<Compile Include="$(MSBuildThisFileDirectory)BuildProtocol.cs" />
<Compile Include="$(MSBuildThisFileDirectory)BuildClient.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ConsoleUtil.cs" />
Expand Down
4 changes: 3 additions & 1 deletion src/Compilers/Core/Portable/CodeAnalysis.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@
<Compile Include="CaseInsensitiveComparison.cs" />
<Compile Include="CodeGen\ILEmitStyle.cs" />
<Compile Include="DiagnosticAnalyzer\AnalysisResult.cs" />
<Compile Include="DiagnosticAnalyzer\AnalyzerAssemblyLoader.cs" />
<Compile Include="FileSystem\CompilerPathUtilities.cs" />
<Compile Include="InternalUtilities\AssemblyIdentityUtils.cs" />
<Compile Include="InternalUtilities\EmptyComparer.cs" />
<Compile Include="InternalUtilities\StringOrdinalComparer.cs" />
<Compile Include="MetadataReference\AssemblyIdentityMap.cs" />
Expand Down Expand Up @@ -211,7 +214,6 @@
<Compile Include="Compilation\LoadDirective.cs" />
<Compile Include="Compilation\CommonSyntaxAndDeclarationManager.cs" />
<Compile Include="Compilation\SymbolFilter.cs" />
<Compile Include="CompilerPathUtilities.cs" />
<Compile Include="DiagnosticAnalyzer\AnalysisResultBuilder.cs" />
<Compile Include="DiagnosticAnalyzer\AnalysisScope.cs" />
<Compile Include="DiagnosticAnalyzer\AnalysisState.AnalyzerStateData.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -398,22 +398,34 @@ internal void ResolveAnalyzersAndGeneratorsFromArguments(
}
};

var resolvedReferences = ArrayBuilder<AnalyzerFileReference>.GetInstance();
foreach (var reference in AnalyzerReferences)
{
var resolvedReference = ResolveAnalyzerReference(reference, analyzerLoader);
if (resolvedReference != null)
{
resolvedReference.AnalyzerLoadFailed += errorHandler;
resolvedReference.AddAnalyzers(analyzerBuilder, language);
resolvedReference.AddGenerators(generatorBuilder, language);
resolvedReference.AnalyzerLoadFailed -= errorHandler;
resolvedReferences.Add(resolvedReference);

// register the reference to the analyzer loader:
analyzerLoader.AddDependencyLocation(resolvedReference.FullPath);
}
else
{
diagnostics.Add(new DiagnosticInfo(messageProvider, messageProvider.ERR_MetadataFileNotFound, reference.FilePath));
}
}

// All analyzer references are registered now, we can start loading them:
foreach (var resolvedReference in resolvedReferences)
{
resolvedReference.AnalyzerLoadFailed += errorHandler;
resolvedReference.AddAnalyzers(analyzerBuilder, language);
resolvedReference.AddGenerators(generatorBuilder, language);
resolvedReference.AnalyzerLoadFailed -= errorHandler;
}

resolvedReferences.Free();

analyzers = analyzerBuilder.ToImmutable();
generators = generatorBuilder.ToImmutable();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis
{
internal abstract class AnalyzerAssemblyLoader : IAnalyzerAssemblyLoader
{
private readonly object _guard = new object();

// lock _guard to read/write
private readonly Dictionary<string, Assembly> _loadedAssembliesByPath = new Dictionary<string, Assembly>();
private readonly Dictionary<AssemblyIdentity, Assembly> _loadedAssembliesByIdentity = new Dictionary<AssemblyIdentity, Assembly>();

// maps file name to a full path (lock _guard to read/write):
private readonly Dictionary<string, List<string>> _knownAssemblyPathsBySimpleName = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);

protected abstract Assembly LoadFromPathImpl(string fullPath);

#region Public API

public void AddDependencyLocation(string fullPath)
{
CompilerPathUtilities.RequireAbsolutePath(fullPath, nameof(fullPath));
string simpleName = PathUtilities.GetFileName(fullPath, includeExtension: false);

lock (_guard)
{
List<string> paths;
if (!_knownAssemblyPathsBySimpleName.TryGetValue(simpleName, out paths))
{
_knownAssemblyPathsBySimpleName.Add(simpleName, new List<string>() { fullPath });
}
else if (!paths.Contains(fullPath))
{
paths.Add(fullPath);
}
}
}

public Assembly LoadFromPath(string fullPath)
{
CompilerPathUtilities.RequireAbsolutePath(fullPath, nameof(fullPath));
return LoadFromPathUnchecked(fullPath);
}

#endregion

private Assembly LoadFromPathUnchecked(string fullPath)
{
Debug.Assert(PathUtilities.IsAbsolute(fullPath));

lock (_guard)
{
Assembly existingAssembly;
if (_loadedAssembliesByPath.TryGetValue(fullPath, out existingAssembly))
{
return existingAssembly;
}
}

Assembly assembly = LoadFromPathImpl(fullPath);
return AddToCache(assembly, fullPath);
}

private Assembly AddToCache(Assembly assembly, string fullPath)
{
Debug.Assert(PathUtilities.IsAbsolute(fullPath));
Debug.Assert(assembly != null);

var identity = AssemblyIdentity.FromAssemblyDefinition(assembly);

lock (_guard)
{
// The same assembly may be loaded from two different full paths (e.g. when loaded from GAC, etc.),
// or another thread might have loaded the assembly after we checked above.
Assembly existingAssembly;
if (_loadedAssembliesByIdentity.TryGetValue(identity, out existingAssembly))
{
return existingAssembly;
}

_loadedAssembliesByIdentity.Add(identity, assembly);

// An assembly file might be replaced by another file with a different identity.
// Last one wins.
_loadedAssembliesByPath[fullPath] = assembly;

return assembly;
}
}

public Assembly Load(string displayName)
{
AssemblyIdentity requestedIdentity;
if (!AssemblyIdentity.TryParseDisplayName(displayName, out requestedIdentity))
{
return null;
}

ImmutableArray<string> candidatePaths;
lock (_guard)
{
Assembly existingAssembly;

// First, check if this loader already loaded the requested assembly:
if (_loadedAssembliesByIdentity.TryGetValue(requestedIdentity, out existingAssembly))
{
return existingAssembly;
}

// Second, check if an assembly file of the same simple name was registered with the loader:
List<string> pathList;
if (!_knownAssemblyPathsBySimpleName.TryGetValue(requestedIdentity.Name, out pathList))
{
return null;
}

Debug.Assert(pathList.Count > 0);
candidatePaths = pathList.ToImmutableArray();
}

// Multiple assemblies of the same simple name but different identities might have been registered.
// Load the one that matches the requested identity (if any).
foreach (var candidatePath in candidatePaths)
{
var candidateIdentity = AssemblyIdentityUtils.TryGetAssemblyIdentity(candidatePath);

if (requestedIdentity.Equals(candidateIdentity))
{
return LoadFromPathUnchecked(candidatePath);
}
}

return null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ private IEnumerable<DiagnosticAnalyzer> GetAnalyzersForTypeNames(Assembly analyz
}
catch (Exception e)
{
this.AnalyzerLoadFailed?.Invoke(this, new AnalyzerLoadFailureEventArgs(AnalyzerLoadFailureEventArgs.FailureErrorCode.UnableToCreateAnalyzer, e.Message, e, typeName));
AnalyzerLoadFailed?.Invoke(this, CreateAnalyzerFailedArgs(e, typeName));
reportedError = true;
continue;
}
Expand All @@ -199,7 +199,7 @@ private IEnumerable<DiagnosticAnalyzer> GetAnalyzersForTypeNames(Assembly analyz
}
catch (Exception e)
{
this.AnalyzerLoadFailed?.Invoke(this, new AnalyzerLoadFailureEventArgs(AnalyzerLoadFailureEventArgs.FailureErrorCode.UnableToCreateAnalyzer, e.Message, e, typeName));
AnalyzerLoadFailed?.Invoke(this, CreateAnalyzerFailedArgs(e, typeName));
reportedError = true;
continue;
}
Expand All @@ -213,6 +213,21 @@ private IEnumerable<DiagnosticAnalyzer> GetAnalyzersForTypeNames(Assembly analyz
return analyzers.ToImmutable();
}

private static AnalyzerLoadFailureEventArgs CreateAnalyzerFailedArgs(Exception e, string typeNameOpt = null)
{
// unwrap:
e = (e as TargetInvocationException) ?? e;

// remove all line breaks from the exception message
string message = e.Message.Replace("\r", "").Replace("\n", "");

var errorCode = (typeNameOpt != null) ?
AnalyzerLoadFailureEventArgs.FailureErrorCode.UnableToCreateAnalyzer :
AnalyzerLoadFailureEventArgs.FailureErrorCode.UnableToLoadAnalyzer;

return new AnalyzerLoadFailureEventArgs(errorCode, message, e, typeNameOpt);
}

internal void AddGenerators(ImmutableArray<SourceGenerator>.Builder builder, string language)
{
_sourceGenerators.AddExtensions(builder, language);
Expand Down Expand Up @@ -403,7 +418,7 @@ internal void AddExtensions(ImmutableDictionary<string, ImmutableArray<TExtensio
}
catch (Exception e)
{
_reference.AnalyzerLoadFailed?.Invoke(_reference, new AnalyzerLoadFailureEventArgs(AnalyzerLoadFailureEventArgs.FailureErrorCode.UnableToLoadAnalyzer, e.Message, e));
_reference.AnalyzerLoadFailed?.Invoke(_reference, CreateAnalyzerFailedArgs(e));
return;
}

Expand Down Expand Up @@ -454,7 +469,7 @@ internal void AddExtensions(ImmutableArray<TExtension>.Builder builder, string l
}
catch (Exception e)
{
_reference.AnalyzerLoadFailed?.Invoke(_reference, new AnalyzerLoadFailureEventArgs(AnalyzerLoadFailureEventArgs.FailureErrorCode.UnableToLoadAnalyzer, e.Message));
_reference.AnalyzerLoadFailed?.Invoke(_reference, CreateAnalyzerFailedArgs(e));
return;
}

Expand Down Expand Up @@ -501,7 +516,7 @@ private ImmutableArray<TExtension> GetAnalyzersForTypeNames(Assembly analyzerAss
}
catch (Exception e)
{
_reference.AnalyzerLoadFailed?.Invoke(_reference, new AnalyzerLoadFailureEventArgs(AnalyzerLoadFailureEventArgs.FailureErrorCode.UnableToCreateAnalyzer, e.Message, e, typeName));
_reference.AnalyzerLoadFailed?.Invoke(_reference, CreateAnalyzerFailedArgs(e, typeName));
reportedError = true;
continue;
}
Expand All @@ -515,7 +530,7 @@ private ImmutableArray<TExtension> GetAnalyzersForTypeNames(Assembly analyzerAss
}
catch (Exception e)
{
_reference.AnalyzerLoadFailed?.Invoke(_reference, new AnalyzerLoadFailureEventArgs(AnalyzerLoadFailureEventArgs.FailureErrorCode.UnableToCreateAnalyzer, e.Message, e, typeName));
_reference.AnalyzerLoadFailed?.Invoke(_reference, CreateAnalyzerFailedArgs(e, typeName));
reportedError = true;
continue;
}
Expand Down
Loading

0 comments on commit c966b31

Please sign in to comment.