diff --git a/src/Compilers/CSharp/csc/Program.cs b/src/Compilers/CSharp/csc/Program.cs index 586645bb7c457..f0391150baef9 100644 --- a/src/Compilers/CSharp/csc/Program.cs +++ b/src/Compilers/CSharp/csc/Program.cs @@ -12,7 +12,7 @@ public static int Main(string[] args) => Main(args, SpecializedCollections.EmptyArray()); 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); diff --git a/src/Compilers/CSharp/csc/csc.csproj b/src/Compilers/CSharp/csc/csc.csproj index dc9d45919c3bc..8454bbaa208d3 100644 --- a/src/Compilers/CSharp/csc/csc.csproj +++ b/src/Compilers/CSharp/csc/csc.csproj @@ -41,15 +41,12 @@ DesktopBuildClient.cs - - AbstractAnalyzerAssemblyLoader.cs + + DesktopAnalyzerAssemblyLoader.cs ExitingTraceListener.cs - - SimpleAnalyzerAssemblyLoader.cs - Csc.cs @@ -71,4 +68,4 @@ - + \ No newline at end of file diff --git a/src/Compilers/Core/CodeAnalysisTest/AnalyzerFileReferenceTests.cs b/src/Compilers/Core/CodeAnalysisTest/AnalyzerFileReferenceTests.cs index c6f815254b4a1..9bdcc18805b6b 100644 --- a/src/Compilers/Core/CodeAnalysisTest/AnalyzerFileReferenceTests.cs +++ b/src/Compilers/Core/CodeAnalysisTest/AnalyzerFileReferenceTests.cs @@ -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) { diff --git a/src/Compilers/Core/CodeAnalysisTest/CodeAnalysisTest.csproj b/src/Compilers/Core/CodeAnalysisTest/CodeAnalysisTest.csproj index dff583bc78812..0231b2c97a4fc 100644 --- a/src/Compilers/Core/CodeAnalysisTest/CodeAnalysisTest.csproj +++ b/src/Compilers/Core/CodeAnalysisTest/CodeAnalysisTest.csproj @@ -54,7 +54,7 @@ - + diff --git a/src/Compilers/Core/CodeAnalysisTest/SimpleAnalyzerAssemblyLoaderTests.cs b/src/Compilers/Core/CodeAnalysisTest/DesktopAnalyzerAssemblyLoaderTests.cs similarity index 86% rename from src/Compilers/Core/CodeAnalysisTest/SimpleAnalyzerAssemblyLoaderTests.cs rename to src/Compilers/Core/CodeAnalysisTest/DesktopAnalyzerAssemblyLoaderTests.cs index e48cc9239c1e5..a8352a28e1482 100644 --- a/src/Compilers/Core/CodeAnalysisTest/SimpleAnalyzerAssemblyLoaderTests.cs +++ b/src/Compilers/Core/CodeAnalysisTest/DesktopAnalyzerAssemblyLoaderTests.cs @@ -1,26 +1,23 @@ // 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("fullPath", () => loader.AddDependencyLocation(null)); + Assert.Throws("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(() => 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); diff --git a/src/Compilers/Core/CommandLine/CommandLine.projitems b/src/Compilers/Core/CommandLine/CommandLine.projitems index f26f34374443c..961631a34fc17 100644 --- a/src/Compilers/Core/CommandLine/CommandLine.projitems +++ b/src/Compilers/Core/CommandLine/CommandLine.projitems @@ -9,7 +9,6 @@ CommandLine - diff --git a/src/Compilers/Core/Portable/CodeAnalysis.csproj b/src/Compilers/Core/Portable/CodeAnalysis.csproj index 10ef62d2aff50..86422b85f50b7 100644 --- a/src/Compilers/Core/Portable/CodeAnalysis.csproj +++ b/src/Compilers/Core/Portable/CodeAnalysis.csproj @@ -94,6 +94,9 @@ + + + @@ -211,7 +214,6 @@ - diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerAssemblyLoader.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerAssemblyLoader.cs new file mode 100644 index 0000000000000..eed8a6d358c02 --- /dev/null +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerAssemblyLoader.cs @@ -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 _loadedAssembliesByPath = new Dictionary(); + private readonly Dictionary _loadedAssembliesByIdentity = new Dictionary(); + + // maps file name to a full path (lock _guard to read/write): + private readonly Dictionary> _knownAssemblyPathsBySimpleName = new Dictionary>(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 paths; + if (!_knownAssemblyPathsBySimpleName.TryGetValue(simpleName, out paths)) + { + _knownAssemblyPathsBySimpleName.Add(simpleName, new List() { 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 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 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; + } + } +} diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/IAnalyzerAssemblyLoader.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/IAnalyzerAssemblyLoader.cs index d098fb2d589f0..225d523034915 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/IAnalyzerAssemblyLoader.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/IAnalyzerAssemblyLoader.cs @@ -1,5 +1,6 @@ // 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 /// instance. /// + /// is null. + /// is not a full path. Assembly LoadFromPath(string fullPath); /// /// Adds a file to consider when loading an analyzer or its dependencies. /// + /// is null. + /// is not a full path. void AddDependencyLocation(string fullPath); } } diff --git a/src/Compilers/Core/Portable/CompilerPathUtilities.cs b/src/Compilers/Core/Portable/FileSystem/CompilerPathUtilities.cs similarity index 100% rename from src/Compilers/Core/Portable/CompilerPathUtilities.cs rename to src/Compilers/Core/Portable/FileSystem/CompilerPathUtilities.cs diff --git a/src/Compilers/Core/CommandLine/AssemblyIdentityUtils.cs b/src/Compilers/Core/Portable/InternalUtilities/AssemblyIdentityUtils.cs similarity index 87% rename from src/Compilers/Core/CommandLine/AssemblyIdentityUtils.cs rename to src/Compilers/Core/Portable/InternalUtilities/AssemblyIdentityUtils.cs index 576c067e9b8fc..e44a1f7209295 100644 --- a/src/Compilers/Core/CommandLine/AssemblyIdentityUtils.cs +++ b/src/Compilers/Core/Portable/InternalUtilities/AssemblyIdentityUtils.cs @@ -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(); diff --git a/src/Compilers/Server/VBCSCompiler/VBCSCompiler.csproj b/src/Compilers/Server/VBCSCompiler/VBCSCompiler.csproj index 13cec24f06d87..fae50bcb9f1c0 100644 --- a/src/Compilers/Server/VBCSCompiler/VBCSCompiler.csproj +++ b/src/Compilers/Server/VBCSCompiler/VBCSCompiler.csproj @@ -48,8 +48,8 @@ - - AbstractAnalyzerAssemblyLoader.cs + + DesktopAnalyzerAssemblyLoader.cs DesktopBuildClient.cs diff --git a/src/Compilers/Shared/AbstractAnalyzerAssemblyLoader.cs b/src/Compilers/Shared/AbstractAnalyzerAssemblyLoader.cs deleted file mode 100644 index 095b6735208fc..0000000000000 --- a/src/Compilers/Shared/AbstractAnalyzerAssemblyLoader.cs +++ /dev/null @@ -1,154 +0,0 @@ -// 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 _pathsToAssemblies = new Dictionary(StringComparer.OrdinalIgnoreCase); - private readonly Dictionary _namesToAssemblies = new Dictionary(); - private readonly List _dependencyPaths = new List(); - private readonly object _guard = new object(); - - private bool _hookedAssemblyResolve; - - /// - /// 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. - /// - 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; - } - - /// - /// Handler for . Delegates to - /// and prevents exceptions from leaking out. - /// - 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); - } - } -} diff --git a/src/Compilers/Shared/CoreClrAnalyzerAssemblyLoader.cs b/src/Compilers/Shared/CoreClrAnalyzerAssemblyLoader.cs index 702ba026e75aa..c7a7b6221e444 100644 --- a/src/Compilers/Shared/CoreClrAnalyzerAssemblyLoader.cs +++ b/src/Compilers/Shared/CoreClrAnalyzerAssemblyLoader.cs @@ -1,156 +1,26 @@ // 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 _pathsToAssemblies = new Dictionary(StringComparer.OrdinalIgnoreCase); - private readonly Dictionary _namesToAssemblies = new Dictionary(); - private readonly List _dependencyPaths = new List(); - 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" }; - - /// - /// Searches and loads from the base directory of the current - /// app context - /// - 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); + }; } - /// - /// Assumes we have a lock on _guard - /// - 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); } } diff --git a/src/Compilers/Shared/DesktopAnalyzerAssemblyLoader.cs b/src/Compilers/Shared/DesktopAnalyzerAssemblyLoader.cs new file mode 100644 index 0000000000000..88373622595a4 --- /dev/null +++ b/src/Compilers/Shared/DesktopAnalyzerAssemblyLoader.cs @@ -0,0 +1,48 @@ +// 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 +{ + /// + /// 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. + /// + /// + /// 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. + /// + 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; + } + } + } +} diff --git a/src/Compilers/Shared/ShadowCopyAnalyzerAssemblyLoader.cs b/src/Compilers/Shared/ShadowCopyAnalyzerAssemblyLoader.cs index aad5a157d0381..68371e18ac4e0 100644 --- a/src/Compilers/Shared/ShadowCopyAnalyzerAssemblyLoader.cs +++ b/src/Compilers/Shared/ShadowCopyAnalyzerAssemblyLoader.cs @@ -8,7 +8,7 @@ namespace Microsoft.CodeAnalysis { - internal sealed class ShadowCopyAnalyzerAssemblyLoader : AbstractAnalyzerAssemblyLoader + internal sealed class ShadowCopyAnalyzerAssemblyLoader : DesktopAnalyzerAssemblyLoader { /// /// 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) diff --git a/src/Compilers/Test/Utilities/CSharp.Desktop/MockCSharpCompiler.cs b/src/Compilers/Test/Utilities/CSharp.Desktop/MockCSharpCompiler.cs index df7d71e9a603c..de68a32dcee8e 100644 --- a/src/Compilers/Test/Utilities/CSharp.Desktop/MockCSharpCompiler.cs +++ b/src/Compilers/Test/Utilities/CSharp.Desktop/MockCSharpCompiler.cs @@ -21,7 +21,7 @@ public MockCSharpCompiler(string responseFile, string baseDirectory, string[] ar } public MockCSharpCompiler(string responseFile, string baseDirectory, string[] args, ImmutableArray 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; } diff --git a/src/Compilers/Test/Utilities/VisualBasic/MockVbi.vb b/src/Compilers/Test/Utilities/VisualBasic/MockVbi.vb index e06ad47a7589e..d4068bd75ea83 100644 --- a/src/Compilers/Test/Utilities/VisualBasic/MockVbi.vb +++ b/src/Compilers/Test/Utilities/VisualBasic/MockVbi.vb @@ -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) diff --git a/src/Compilers/Test/Utilities/VisualBasic/MockVisualBasicCompiler.vb b/src/Compilers/Test/Utilities/VisualBasic/MockVisualBasicCompiler.vb index 76a586cfcb8c3..f88cc5cf106ea 100644 --- a/src/Compilers/Test/Utilities/VisualBasic/MockVisualBasicCompiler.vb +++ b/src/Compilers/Test/Utilities/VisualBasic/MockVisualBasicCompiler.vb @@ -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 diff --git a/src/Compilers/VisualBasic/vbc/Program.cs b/src/Compilers/VisualBasic/vbc/Program.cs index 097f6037fd768..154ac4cf4ddd9 100644 --- a/src/Compilers/VisualBasic/vbc/Program.cs +++ b/src/Compilers/VisualBasic/vbc/Program.cs @@ -12,7 +12,7 @@ public static int Main(string[] args) => Main(args, SpecializedCollections.EmptyArray()); 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); diff --git a/src/Compilers/VisualBasic/vbc/vbc.csproj b/src/Compilers/VisualBasic/vbc/vbc.csproj index 88fb9e9033091..9c1df8894a179 100644 --- a/src/Compilers/VisualBasic/vbc/vbc.csproj +++ b/src/Compilers/VisualBasic/vbc/vbc.csproj @@ -41,14 +41,11 @@ DesktopBuildClient.cs - - AbstractAnalyzerAssemblyLoader.cs - ExitingTraceListener.cs - - SimpleAnalyzerAssemblyLoader.cs + + DesktopAnalyzerAssemblyLoader.cs Vbc.cs @@ -71,4 +68,4 @@ - + \ No newline at end of file diff --git a/src/Test/Utilities/Desktop/TestUtilities.Desktop.csproj b/src/Test/Utilities/Desktop/TestUtilities.Desktop.csproj index 57e741966ebe5..8831b50c56a5d 100644 --- a/src/Test/Utilities/Desktop/TestUtilities.Desktop.csproj +++ b/src/Test/Utilities/Desktop/TestUtilities.Desktop.csproj @@ -91,14 +91,8 @@ - - AssemblyIdentityUtils.cs - - - AbstractAnalyzerAssemblyLoader.cs - - - SimpleAnalyzerAssemblyLoader.cs + + DesktopAnalyzerAssemblyLoader.cs diff --git a/src/VisualStudio/Core/Def/Implementation/Diagnostics/VisualStudioWorkspaceDiagnosticAnalyzerProviderService.AnalyzerAssemblyLoader.cs b/src/VisualStudio/Core/Def/Implementation/Diagnostics/VisualStudioWorkspaceDiagnosticAnalyzerProviderService.AnalyzerAssemblyLoader.cs index 8f3e1b8f37e85..3b0b8392cd3ba 100644 --- a/src/VisualStudio/Core/Def/Implementation/Diagnostics/VisualStudioWorkspaceDiagnosticAnalyzerProviderService.AnalyzerAssemblyLoader.cs +++ b/src/VisualStudio/Core/Def/Implementation/Diagnostics/VisualStudioWorkspaceDiagnosticAnalyzerProviderService.AnalyzerAssemblyLoader.cs @@ -15,7 +15,7 @@ private sealed class AnalyzerAssemblyLoader : IAnalyzerAssemblyLoader public AnalyzerAssemblyLoader() { - _fallbackLoader = new SimpleAnalyzerAssemblyLoader(); + _fallbackLoader = new DesktopAnalyzerAssemblyLoader(); } public void AddDependencyLocation(string fullPath) diff --git a/src/VisualStudio/Core/Def/Implementation/Library/ObjectBrowser/AbstractListItemFactory.cs b/src/VisualStudio/Core/Def/Implementation/Library/ObjectBrowser/AbstractListItemFactory.cs index 64bf3e1924e5e..659f12f0edc4f 100644 --- a/src/VisualStudio/Core/Def/Implementation/Library/ObjectBrowser/AbstractListItemFactory.cs +++ b/src/VisualStudio/Core/Def/Implementation/Library/ObjectBrowser/AbstractListItemFactory.cs @@ -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; diff --git a/src/VisualStudio/Core/Def/Implementation/Library/ObjectBrowser/ObjectList.cs b/src/VisualStudio/Core/Def/Implementation/Library/ObjectBrowser/ObjectList.cs index 08008680d3646..a2dd4272efa60 100644 --- a/src/VisualStudio/Core/Def/Implementation/Library/ObjectBrowser/ObjectList.cs +++ b/src/VisualStudio/Core/Def/Implementation/Library/ObjectBrowser/ObjectList.cs @@ -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; diff --git a/src/Workspaces/Core/Desktop/InternalUtilities/FilePathUtilities.cs b/src/Workspaces/Core/Desktop/InternalUtilities/FilePathUtilities.cs index 07e71e936511b..896c6194144a4 100644 --- a/src/Workspaces/Core/Desktop/InternalUtilities/FilePathUtilities.cs +++ b/src/Workspaces/Core/Desktop/InternalUtilities/FilePathUtilities.cs @@ -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; diff --git a/src/Workspaces/Core/Desktop/Workspace/FileTextLoader.cs b/src/Workspaces/Core/Desktop/Workspace/FileTextLoader.cs index 6b056a0000b03..9e27e13a503d4 100644 --- a/src/Workspaces/Core/Desktop/Workspace/FileTextLoader.cs +++ b/src/Workspaces/Core/Desktop/Workspace/FileTextLoader.cs @@ -29,7 +29,7 @@ public class FileTextLoader : TextLoader /// is not an absolute path. public FileTextLoader(string path, Encoding defaultEncoding) { - FilePathUtilities.RequireAbsolutePath(path, "path"); + CompilerPathUtilities.RequireAbsolutePath(path, "path"); _path = path; _defaultEncoding = defaultEncoding; diff --git a/src/Workspaces/Core/Desktop/Workspace/Host/SimpleAnalyzerAssemblyLoaderService.cs b/src/Workspaces/Core/Desktop/Workspace/Host/SimpleAnalyzerAssemblyLoaderService.cs index 6d46cc8138dfb..5004ac87ceb42 100644 --- a/src/Workspaces/Core/Desktop/Workspace/Host/SimpleAnalyzerAssemblyLoaderService.cs +++ b/src/Workspaces/Core/Desktop/Workspace/Host/SimpleAnalyzerAssemblyLoaderService.cs @@ -1,9 +1,6 @@ // 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() { diff --git a/src/Workspaces/Core/Desktop/Workspaces.Desktop.csproj b/src/Workspaces/Core/Desktop/Workspaces.Desktop.csproj index b271b6dfae950..e11bd2eb36939 100644 --- a/src/Workspaces/Core/Desktop/Workspaces.Desktop.csproj +++ b/src/Workspaces/Core/Desktop/Workspaces.Desktop.csproj @@ -56,18 +56,12 @@ InternalUtilities\FileKey.cs - - InternalUtilities\AbstractAnalyzerAssemblyLoader.cs - - - AssemblyIdentityUtils.cs + + InternalUtilities\DesktopAnalyzerAssemblyLoader.cs InternalUtilities\GlobalAssemblyCache.cs - - InternalUtilities\SimpleAnalyzerAssemblyLoader.cs - diff --git a/src/Workspaces/Core/Portable/Utilities/CompilerUtilities/CompilerPathUtilities.cs b/src/Workspaces/Core/Portable/Utilities/CompilerUtilities/CompilerPathUtilities.cs new file mode 100644 index 0000000000000..64104e4b63801 --- /dev/null +++ b/src/Workspaces/Core/Portable/Utilities/CompilerUtilities/CompilerPathUtilities.cs @@ -0,0 +1,23 @@ +// 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); + } + } + } +} diff --git a/src/Workspaces/Core/Portable/Workspaces.csproj b/src/Workspaces/Core/Portable/Workspaces.csproj index 04fa5c71108b9..850118da5630d 100644 --- a/src/Workspaces/Core/Portable/Workspaces.csproj +++ b/src/Workspaces/Core/Portable/Workspaces.csproj @@ -51,9 +51,15 @@ InternalUtilities\CorLightup.cs + + InternalUtilities\AnalyzerAssemblyLoader.cs + InternalUtilities\RelativePathResolver.cs + + InternalUtilities\AssemblyIdentityUtils.cs + InternalUtilities\BitArithmeticUtilities.cs @@ -491,6 +497,7 @@ +