提交 c966b31c 编写于 作者: T Tomáš Matoušek 提交者: GitHub

Merge pull request #11956 from tmat/AnalyzerLoader

Improve implementation of analyzer loader
......@@ -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);
......
......@@ -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>
......@@ -71,4 +68,4 @@
<ImportGroup Label="Targets">
<Import Project="..\..\..\..\build\Targets\VSL.Imports.targets" />
</ImportGroup>
</Project>
</Project>
\ No newline at end of file
......@@ -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)
{
......
......@@ -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" />
......
// 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]
......@@ -28,7 +25,7 @@ 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));
}
......@@ -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);
......@@ -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);
......
......@@ -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" />
......
......@@ -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" />
......@@ -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" />
......
......@@ -398,15 +398,16 @@ public IEnumerable<AnalyzerReference> ResolveAnalyzerReferences(IAnalyzerAssembl
}
};
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
{
......@@ -414,6 +415,17 @@ public IEnumerable<AnalyzerReference> ResolveAnalyzerReferences(IAnalyzerAssembl
}
}
// 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();
}
......
// 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;
}
}
}
......@@ -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;
}
......@@ -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;
}
......@@ -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);
......@@ -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;
}
......@@ -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;
}
......@@ -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;
}
......@@ -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;
}
......
// 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.Reflection;
namespace Microsoft.CodeAnalysis
......@@ -29,11 +30,15 @@ public interface IAnalyzerAssemblyLoader
/// Multiple calls with the same path should return the same
/// <see cref="Assembly"/> instance.
/// </remarks>
/// <exception cref="ArgumentNullException"><paramref name="fullPath" /> is null.</exception>
/// <exception cref="ArgumentException"><paramref name="fullPath" /> is not a full path.</exception>
Assembly LoadFromPath(string fullPath);
/// <summary>
/// Adds a file to consider when loading an analyzer or its dependencies.
/// </summary>
/// <exception cref="ArgumentNullException"><paramref name="fullPath" /> is null.</exception>
/// <exception cref="ArgumentException"><paramref name="fullPath" /> is not a full path.</exception>
void AddDependencyLocation(string fullPath);
}
}
......@@ -2,12 +2,12 @@
using System;
using System.Collections.Immutable;
using System.IO;
using System.Reflection;
using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CommandLine
namespace Microsoft.CodeAnalysis
{
internal static class AssemblyIdentityUtils
{
......@@ -15,7 +15,7 @@ public static AssemblyIdentity TryGetAssemblyIdentity(string filePath)
{
try
{
using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete))
using (var stream = PortableShim.FileStream.Create(filePath, PortableShim.FileMode.Open, PortableShim.FileAccess.Read, PortableShim.FileShare.ReadWriteBitwiseOrDelete))
using (var peReader = new PEReader(stream))
{
var metadataReader = peReader.GetMetadataReader();
......
......@@ -72,12 +72,14 @@ private static bool CheckCore(string baseDirectory, IEnumerable<CommandLineAnaly
}
}
// Second, load all of the assemblies upfront.
// Register analyzers and their dependencies upfront,
// so that assembly references can be resolved:
foreach (var resolvedPath in resolvedPaths)
{
loader.AddDependencyLocation(resolvedPath);
}
// Load all analyzer assemblies:
var loadedAssemblies = new List<Assembly>();
foreach (var resolvedPath in resolvedPaths)
{
......
......@@ -48,8 +48,8 @@
<Reference Include="System.Core" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\..\Shared\AbstractAnalyzerAssemblyLoader.cs">
<Link>AbstractAnalyzerAssemblyLoader.cs</Link>
<Compile Include="..\..\Shared\DesktopAnalyzerAssemblyLoader.cs">
<Link>DesktopAnalyzerAssemblyLoader.cs</Link>
</Compile>
<Compile Include="..\..\Shared\DesktopBuildClient.cs">
<Link>DesktopBuildClient.cs</Link>
......
// 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.Linq;
using System.Reflection;
using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;
using Roslyn.Utilities;
using static Microsoft.CodeAnalysis.CommandLine.AssemblyIdentityUtils;
namespace Microsoft.CodeAnalysis
{
internal abstract class AbstractAnalyzerAssemblyLoader : IAnalyzerAssemblyLoader
{
private readonly Dictionary<string, Assembly> _pathsToAssemblies = new Dictionary<string, Assembly>(StringComparer.OrdinalIgnoreCase);
private readonly Dictionary<string, Assembly> _namesToAssemblies = new Dictionary<string, Assembly>();
private readonly List<string> _dependencyPaths = new List<string>();
private readonly object _guard = new object();
private bool _hookedAssemblyResolve;
/// <summary>
/// Implemented by derived types to handle the actual loading of an assembly from
/// a file on disk, and any bookkeeping specific to the derived type.
/// </summary>
protected abstract Assembly LoadCore(string fullPath);
public void AddDependencyLocation(string fullPath)
{
if (fullPath == null)
{
throw new ArgumentNullException(nameof(fullPath));
}
lock (_guard)
{
if (!_dependencyPaths.Contains(fullPath, StringComparer.OrdinalIgnoreCase))
{
_dependencyPaths.Add(fullPath);
}
}
}
public Assembly LoadFromPath(string fullPath)
{
if (fullPath == null)
{
throw new ArgumentNullException(nameof(fullPath));
}
Debug.Assert(PathUtilities.IsAbsolute(fullPath));
lock (_guard)
{
Assembly assembly;
if (_pathsToAssemblies.TryGetValue(fullPath, out assembly))
{
return assembly;
}
assembly = LoadInternal(fullPath);
if (!_hookedAssemblyResolve)
{
_hookedAssemblyResolve = true;
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
}
return assembly;
}
}
private Assembly LoadInternal(string fullPath)
{
Assembly assembly = LoadCore(fullPath);
string assemblyName = assembly.FullName;
_pathsToAssemblies[fullPath] = assembly;
_namesToAssemblies[assemblyName] = assembly;
return assembly;
}
/// <summary>
/// Handler for <see cref="AppDomain.AssemblyResolve"/>. Delegates to <see cref="AssemblyResolveInternal(ResolveEventArgs)"/>
/// and prevents exceptions from leaking out.
/// </summary>
private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
try
{
return AssemblyResolveInternal(args);
}
catch
{
return null;
}
}
private Assembly AssemblyResolveInternal(ResolveEventArgs args)
{
string requestedNameWithPolicyApplied = AppDomain.CurrentDomain.ApplyPolicy(args.Name);
lock (_guard)
{
Assembly assembly;
if (_namesToAssemblies.TryGetValue(requestedNameWithPolicyApplied, out assembly))
{
return assembly;
}
AssemblyIdentity requestedAssemblyIdentity;
if (!AssemblyIdentity.TryParseDisplayName(requestedNameWithPolicyApplied, out requestedAssemblyIdentity))
{
return null;
}
foreach (string candidatePath in _dependencyPaths)
{
if (AssemblyAlreadyLoaded(candidatePath) ||
!FileMatchesAssemblyName(candidatePath, requestedAssemblyIdentity.Name))
{
continue;
}
AssemblyIdentity candidateIdentity = TryGetAssemblyIdentity(candidatePath);
if (requestedAssemblyIdentity.Equals(candidateIdentity))
{
return LoadInternal(candidatePath);
}
}
return null;
}
}
private bool AssemblyAlreadyLoaded(string path)
{
return _pathsToAssemblies.ContainsKey(path);
}
private bool FileMatchesAssemblyName(string path, string assemblySimpleName)
{
return Path.GetFileNameWithoutExtension(path).Equals(assemblySimpleName, StringComparison.OrdinalIgnoreCase);
}
}
}
// 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.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using Roslyn.Utilities;
using static Microsoft.CodeAnalysis.CommandLine.AssemblyIdentityUtils;
namespace Microsoft.CodeAnalysis
{
/// Core CLR compatible wrapper for loading analyzers.
internal sealed class CoreClrAnalyzerAssemblyLoader : IAnalyzerAssemblyLoader
internal sealed class CoreClrAnalyzerAssemblyLoader : AnalyzerAssemblyLoader
{
private readonly Dictionary<string, Assembly> _pathsToAssemblies = new Dictionary<string, Assembly>(StringComparer.OrdinalIgnoreCase);
private readonly Dictionary<string, Assembly> _namesToAssemblies = new Dictionary<string, Assembly>();
private readonly List<string> _dependencyPaths = new List<string>();
private readonly object _guard = new object();
private readonly AssemblyLoadContext _loadContext = AssemblyLoadContext.GetLoadContext(typeof(CoreClrAnalyzerAssemblyLoader).GetTypeInfo().Assembly);
private readonly AssemblyLoadContext _loadContext;
public CoreClrAnalyzerAssemblyLoader()
{
_loadContext.Resolving += this.Load;
}
public void AddDependencyLocation(string fullPath)
{
if (fullPath == null)
{
throw new ArgumentNullException(nameof(fullPath));
}
lock (_guard)
{
_dependencyPaths.Add(fullPath);
}
}
public Assembly LoadFromPath(string fullPath)
{
if (fullPath == null)
{
throw new ArgumentNullException(nameof(fullPath));
}
Debug.Assert(PathUtilities.IsAbsolute(fullPath));
lock (_guard)
{
Assembly assembly;
if (_pathsToAssemblies.TryGetValue(fullPath, out assembly))
{
return assembly;
}
return LoadAndCache(fullPath);
}
}
private static readonly string[] s_extensions = new string[] { ".dll", ".exe" };
/// <summary>
/// Searches and loads from the base directory of the current
/// app context
/// </summary>
private Assembly AppContextLoad(AssemblyName assemblyName)
{
var baseDir = AppContext.BaseDirectory;
foreach (var extension in s_extensions)
{
var path = Path.Combine(baseDir, assemblyName.Name + extension);
_loadContext = AssemblyLoadContext.GetLoadContext(typeof(CoreClrAnalyzerAssemblyLoader).GetTypeInfo().Assembly);
if (File.Exists(path))
{
lock (_guard)
{
return LoadAndCache(path);
}
}
}
return null;
}
private Assembly Load(AssemblyLoadContext loadContext, AssemblyName assemblyName)
{
lock (_guard)
_loadContext.Resolving += (context, name) =>
{
// Try and grab assembly using standard load
Assembly assembly = AppContextLoad(assemblyName);
if (assembly != null)
{
return assembly;
}
string fullName = assemblyName.FullName;
if (_namesToAssemblies.TryGetValue(fullName, out assembly))
{
return assembly;
}
AssemblyIdentity requestedIdentity;
if (!AssemblyIdentity.TryParseDisplayName(fullName, out requestedIdentity))
{
return null;
}
foreach (var candidatePath in _dependencyPaths)
{
if (IsAssemblyAlreadyLoaded(candidatePath) ||
!FileMatchesAssemblyName(candidatePath, requestedIdentity.Name))
{
continue;
}
var candidateIdentity = TryGetAssemblyIdentity(candidatePath);
if (requestedIdentity.Equals(candidateIdentity))
{
return LoadAndCache(candidatePath);
}
}
return null;
}
Debug.Assert(ReferenceEquals(context, _loadContext));
return Load(name.FullName);
};
}
/// <remarks>
/// Assumes we have a lock on _guard
/// </remarks>
private Assembly LoadAndCache(string fullPath)
{
var assembly = _loadContext.LoadFromAssemblyPath(fullPath);
var name = assembly.FullName;
_pathsToAssemblies[fullPath] = assembly;
_namesToAssemblies[name] = assembly;
return assembly;
}
private bool IsAssemblyAlreadyLoaded(string path)
{
return _pathsToAssemblies.ContainsKey(path);
}
private bool FileMatchesAssemblyName(string path, string assemblySimpleName)
{
return Path.GetFileNameWithoutExtension(path).Equals(assemblySimpleName, StringComparison.OrdinalIgnoreCase);
}
protected override Assembly LoadFromPathImpl(string fullPath) => _loadContext.LoadFromAssemblyPath(fullPath);
}
}
// 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.Reflection;
using System.Threading;
namespace Microsoft.CodeAnalysis
{
/// <summary>
/// Loads analyzer assemblies from their original locations in the file system.
/// Assemblies will only be loaded from the locations specified when the loader
/// is instantiated.
/// </summary>
/// <remarks>
/// This type is meant to be used in scenarios where it is OK for the analyzer
/// assemblies to be locked on disk for the lifetime of the host; for example,
/// csc.exe and vbc.exe. In scenarios where support for updating or deleting
/// the analyzer on disk is required a different loader should be used.
/// </remarks>
internal class DesktopAnalyzerAssemblyLoader : AnalyzerAssemblyLoader
{
private int _hookedAssemblyResolve;
protected override Assembly LoadFromPathImpl(string fullPath)
{
if (Interlocked.CompareExchange(ref _hookedAssemblyResolve, 0, 1) == 0)
{
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
}
return LoadImpl(fullPath);
}
protected virtual Assembly LoadImpl(string fullPath) => Assembly.LoadFrom(fullPath);
private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
try
{
return Load(AppDomain.CurrentDomain.ApplyPolicy(args.Name));
}
catch
{
return null;
}
}
}
}
......@@ -8,7 +8,7 @@
namespace Microsoft.CodeAnalysis
{
internal sealed class ShadowCopyAnalyzerAssemblyLoader : AbstractAnalyzerAssemblyLoader
internal sealed class ShadowCopyAnalyzerAssemblyLoader : DesktopAnalyzerAssemblyLoader
{
/// <summary>
/// The base directory for shadow copies. Each instance of
......@@ -75,13 +75,7 @@ private void DeleteLeftoverDirectories()
}
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods",
MessageId = "System.Reflection.Assembly.LoadFrom",
Justification = @"We need to call Assembly.LoadFrom in order to load analyzer assemblies.
We can't use Assembly.Load(AssemblyName) because we need to be able to load assemblies outside of the csc/vbc/vbcscompiler/VS binding paths.
We can't use Assembly.Load(byte[]) because VS won't load resource assemblies for those due to an assembly binding optimization.
That leaves Assembly.LoadFrom(string) as the only option that works everywhere.")]
protected override Assembly LoadCore(string fullPath)
protected override Assembly LoadImpl(string fullPath)
{
if (_shadowCopyDirectory == null)
{
......@@ -91,7 +85,7 @@ protected override Assembly LoadCore(string fullPath)
string assemblyDirectory = CreateUniqueDirectoryForAssembly();
string shadowCopyPath = CopyFileAndResources(fullPath, assemblyDirectory);
return Assembly.LoadFrom(shadowCopyPath);
return base.LoadImpl(shadowCopyPath);
}
private string CopyFileAndResources(string fullPath, string assemblyDirectory)
......
......@@ -21,7 +21,7 @@ public MockCSharpCompiler(string responseFile, string baseDirectory, string[] ar
}
public MockCSharpCompiler(string responseFile, string baseDirectory, string[] args, ImmutableArray<DiagnosticAnalyzer> analyzers)
: base(CSharpCommandLineParser.Default, responseFile, args, Path.GetDirectoryName(typeof(CSharpCompiler).Assembly.Location), baseDirectory, RuntimeEnvironment.GetRuntimeDirectory(), Environment.GetEnvironmentVariable("LIB"), new SimpleAnalyzerAssemblyLoader())
: base(CSharpCommandLineParser.Default, responseFile, args, Path.GetDirectoryName(typeof(CSharpCompiler).Assembly.Location), baseDirectory, RuntimeEnvironment.GetRuntimeDirectory(), Environment.GetEnvironmentVariable("LIB"), new DesktopAnalyzerAssemblyLoader())
{
_analyzers = analyzers;
}
......
......@@ -10,7 +10,7 @@ Friend Class MockVbi
Inherits VisualBasicCompiler
Public Sub New(responseFile As String, baseDirectory As String, args As String())
MyBase.New(VisualBasicCommandLineParser.ScriptRunner, responseFile, args, Path.GetDirectoryName(GetType(VisualBasicCompiler).Assembly.Location), baseDirectory, RuntimeEnvironment.GetRuntimeDirectory(), Nothing, New SimpleAnalyzerAssemblyLoader())
MyBase.New(VisualBasicCommandLineParser.ScriptRunner, responseFile, args, Path.GetDirectoryName(GetType(VisualBasicCompiler).Assembly.Location), baseDirectory, RuntimeEnvironment.GetRuntimeDirectory(), Nothing, New DesktopAnalyzerAssemblyLoader())
End Sub
Protected Overrides Sub CompilerSpecificSqm(sqm As IVsSqmMulti, sqmSession As UInteger)
......
......@@ -21,7 +21,7 @@ Friend Class MockVisualBasicCompiler
End Sub
Public Sub New(responseFile As String, baseDirectory As String, args As String(), analyzers As ImmutableArray(Of DiagnosticAnalyzer))
MyBase.New(VisualBasicCommandLineParser.Default, responseFile, args, Path.GetDirectoryName(GetType(VisualBasicCompiler).Assembly.Location), baseDirectory, RuntimeEnvironment.GetRuntimeDirectory(), Environment.GetEnvironmentVariable("LIB"), New SimpleAnalyzerAssemblyLoader())
MyBase.New(VisualBasicCommandLineParser.Default, responseFile, args, Path.GetDirectoryName(GetType(VisualBasicCompiler).Assembly.Location), baseDirectory, RuntimeEnvironment.GetRuntimeDirectory(), Environment.GetEnvironmentVariable("LIB"), New DesktopAnalyzerAssemblyLoader())
_analyzers = analyzers
End Sub
......
......@@ -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.VisualBasicCompile, Vbc.Run, new SimpleAnalyzerAssemblyLoader());
=> DesktopBuildClient.Run(args, extraArgs, RequestLanguage.VisualBasicCompile, Vbc.Run, new DesktopAnalyzerAssemblyLoader());
public static int Run(string[] args, string clientDir, string workingDir, string sdkDir, TextWriter textWriter, IAnalyzerAssemblyLoader analyzerLoader)
=> Vbc.Run(args, new BuildPaths(clientDir: clientDir, workingDir: workingDir, sdkDir: sdkDir), textWriter, analyzerLoader);
......
......@@ -41,14 +41,11 @@
<Compile Include="..\..\Shared\DesktopBuildClient.cs">
<Link>DesktopBuildClient.cs</Link>
</Compile>
<Compile Include="..\..\Shared\AbstractAnalyzerAssemblyLoader.cs">
<Link>AbstractAnalyzerAssemblyLoader.cs</Link>
</Compile>
<Compile Include="..\..\Shared\ExitingTraceListener.cs">
<Link>ExitingTraceListener.cs</Link>
</Compile>
<Compile Include="..\..\Shared\SimpleAnalyzerAssemblyLoader.cs">
<Link>SimpleAnalyzerAssemblyLoader.cs</Link>
<Compile Include="..\..\Shared\DesktopAnalyzerAssemblyLoader.cs">
<Link>DesktopAnalyzerAssemblyLoader.cs</Link>
</Compile>
<Compile Include="..\..\Shared\Vbc.cs">
<Link>Vbc.cs</Link>
......@@ -71,4 +68,4 @@
<ImportGroup Label="Targets">
<Import Project="..\..\..\..\build\Targets\VSL.Imports.targets" />
</ImportGroup>
</Project>
</Project>
\ No newline at end of file
......@@ -91,14 +91,8 @@
<Reference Include="System.Xml.Linq" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\..\..\Compilers\Core\CommandLine\AssemblyIdentityUtils.cs">
<Link>AssemblyIdentityUtils.cs</Link>
</Compile>
<Compile Include="..\..\..\Compilers\Shared\AbstractAnalyzerAssemblyLoader.cs">
<Link>AbstractAnalyzerAssemblyLoader.cs</Link>
</Compile>
<Compile Include="..\..\..\Compilers\Shared\SimpleAnalyzerAssemblyLoader.cs">
<Link>SimpleAnalyzerAssemblyLoader.cs</Link>
<Compile Include="..\..\..\Compilers\Shared\DesktopAnalyzerAssemblyLoader.cs">
<Link>DesktopAnalyzerAssemblyLoader.cs</Link>
</Compile>
<Compile Include="AppDomainUtils.cs" />
<Compile Include="CLRHelpers.cs" />
......
......@@ -15,7 +15,7 @@ private sealed class AnalyzerAssemblyLoader : IAnalyzerAssemblyLoader
public AnalyzerAssemblyLoader()
{
_fallbackLoader = new SimpleAnalyzerAssemblyLoader();
_fallbackLoader = new DesktopAnalyzerAssemblyLoader();
}
public void AddDependencyLocation(string fullPath)
......
......@@ -10,7 +10,6 @@
using Microsoft.CodeAnalysis.LanguageServices;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Utilities;
using Microsoft.CodeAnalysis.CommandLine;
using Microsoft.VisualStudio.LanguageServices.Implementation.Library.ObjectBrowser.Lists;
using Roslyn.Utilities;
......
......@@ -5,7 +5,6 @@
using System.Diagnostics;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CommandLine;
using Microsoft.VisualStudio.LanguageServices.Implementation.F1Help;
using Microsoft.VisualStudio.LanguageServices.Implementation.Library.ObjectBrowser.Lists;
using Microsoft.VisualStudio.LanguageServices.Implementation.Library.VsNavInfo;
......
......@@ -2,7 +2,6 @@
using System;
using System.IO;
using Microsoft.CodeAnalysis;
namespace Roslyn.Utilities
{
......@@ -89,19 +88,6 @@ public static string GetRelativePath(string baseDirectory, string fullPath)
return relativePath;
}
internal static void RequireAbsolutePath(string path, string argumentName)
{
if (path == null)
{
throw new ArgumentNullException(argumentName);
}
if (!PathUtilities.IsAbsolute(path))
{
throw new ArgumentException(WorkspacesResources.AbsolutePathExpected, argumentName);
}
}
public static bool PathsEqual(string path1, string path2)
{
return string.Compare(path1, path2, StringComparison.OrdinalIgnoreCase) == 0;
......
......@@ -29,7 +29,7 @@ public class FileTextLoader : TextLoader
/// <exception cref="ArgumentException"><paramref name="path"/> is not an absolute path.</exception>
public FileTextLoader(string path, Encoding defaultEncoding)
{
FilePathUtilities.RequireAbsolutePath(path, "path");
CompilerPathUtilities.RequireAbsolutePath(path, "path");
_path = path;
_defaultEncoding = defaultEncoding;
......
// 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.Composition;
using System.Reflection;
using Microsoft.CodeAnalysis.Host.Mef;
namespace Microsoft.CodeAnalysis.Host
......@@ -11,7 +8,7 @@ namespace Microsoft.CodeAnalysis.Host
[ExportWorkspaceService(typeof(IAnalyzerService), ServiceLayer.Default), Shared]
internal sealed class SimpleAnalyzerAssemblyLoaderService : IAnalyzerService
{
private readonly SimpleAnalyzerAssemblyLoader _loader = new SimpleAnalyzerAssemblyLoader();
private readonly DesktopAnalyzerAssemblyLoader _loader = new DesktopAnalyzerAssemblyLoader();
public IAnalyzerAssemblyLoader GetLoader()
{
......
......@@ -56,18 +56,12 @@
<Compile Include="..\..\..\Compilers\Core\Portable\FileKey.cs">
<Link>InternalUtilities\FileKey.cs</Link>
</Compile>
<Compile Include="..\..\..\Compilers\Shared\AbstractAnalyzerAssemblyLoader.cs">
<Link>InternalUtilities\AbstractAnalyzerAssemblyLoader.cs</Link>
</Compile>
<Compile Include="..\..\..\Compilers\Core\CommandLine\AssemblyIdentityUtils.cs">
<Link>AssemblyIdentityUtils.cs</Link>
<Compile Include="..\..\..\Compilers\Shared\DesktopAnalyzerAssemblyLoader.cs">
<Link>InternalUtilities\DesktopAnalyzerAssemblyLoader.cs</Link>
</Compile>
<Compile Include="..\..\..\Compilers\Shared\GlobalAssemblyCacheHelpers\GlobalAssemblyCacheLocation.cs">
<Link>InternalUtilities\GlobalAssemblyCache.cs</Link>
</Compile>
<Compile Include="..\..\..\Compilers\Shared\SimpleAnalyzerAssemblyLoader.cs">
<Link>InternalUtilities\SimpleAnalyzerAssemblyLoader.cs</Link>
</Compile>
<Compile Include="Options\ExportOptionAttribute.cs" />
<Compile Include="InternalUtilities\FilePathUtilities.cs" />
<Compile Include="Log\EtwLogger.cs" />
......
// 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 Microsoft.CodeAnalysis;
namespace Roslyn.Utilities
{
internal static class CompilerPathUtilities
{
internal static void RequireAbsolutePath(string path, string argumentName)
{
if (path == null)
{
throw new ArgumentNullException(argumentName);
}
if (!PathUtilities.IsAbsolute(path))
{
throw new ArgumentException(WorkspacesResources.AbsolutePathExpected, argumentName);
}
}
}
}
......@@ -51,9 +51,15 @@
<Compile Include="..\..\..\Compilers\Core\Portable\CorLightup.cs">
<Link>InternalUtilities\CorLightup.cs</Link>
</Compile>
<Compile Include="..\..\..\Compilers\Core\Portable\DiagnosticAnalyzer\AnalyzerAssemblyLoader.cs">
<Link>InternalUtilities\AnalyzerAssemblyLoader.cs</Link>
</Compile>
<Compile Include="..\..\..\Compilers\Core\Portable\FileSystem\RelativePathResolver.cs">
<Link>InternalUtilities\RelativePathResolver.cs</Link>
</Compile>
<Compile Include="..\..\..\Compilers\Core\Portable\InternalUtilities\AssemblyIdentityUtils.cs">
<Link>InternalUtilities\AssemblyIdentityUtils.cs</Link>
</Compile>
<Compile Include="..\..\..\Compilers\Core\Portable\InternalUtilities\BitArithmeticUtilities.cs">
<Link>InternalUtilities\BitArithmeticUtilities.cs</Link>
</Compile>
......@@ -491,6 +497,7 @@
<Compile Include="Utilities\BKTree.Edge.cs" />
<Compile Include="Utilities\BKTree.Node.cs" />
<Compile Include="Utilities\BKTree.Serialization.cs" />
<Compile Include="Utilities\CompilerUtilities\CompilerPathUtilities.cs" />
<Compile Include="Utilities\ForegroundThreadDataKind.cs" />
<Compile Include="Utilities\IReadOnlyDictionaryExtensions.cs" />
<Compile Include="Utilities\IReadOnlyListExtensions.cs" />
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册