diff --git a/Roslyn.sln b/Roslyn.sln index 96428e7c36670..016b90b55746c 100644 --- a/Roslyn.sln +++ b/Roslyn.sln @@ -1,4 +1,5 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 + +Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.0.31319.15 MinimumVisualStudioVersion = 10.0.40219.1 @@ -497,6 +498,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.Remo EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.CSharp.Emit2.UnitTests", "src\Compilers\CSharp\Test\Emit2\Microsoft.CodeAnalysis.CSharp.Emit2.UnitTests.csproj", "{2B7DC612-1B37-41F7-BE31-4D600930EAC9}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.Remote.ServiceHub.UnitTests", "src\Workspaces\Remote\ServiceHubTest\Microsoft.CodeAnalysis.Remote.ServiceHub.UnitTests.csproj", "{8D830CBB-CA6E-47D8-9FB8-9230AAD272F3}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution src\Analyzers\VisualBasic\CodeFixes\VisualBasicCodeFixes.projitems*{0141285d-8f6c-42c7-baf3-3c0ccd61c716}*SharedItemsImports = 5 @@ -1296,6 +1299,10 @@ Global {2B7DC612-1B37-41F7-BE31-4D600930EAC9}.Debug|Any CPU.Build.0 = Debug|Any CPU {2B7DC612-1B37-41F7-BE31-4D600930EAC9}.Release|Any CPU.ActiveCfg = Release|Any CPU {2B7DC612-1B37-41F7-BE31-4D600930EAC9}.Release|Any CPU.Build.0 = Release|Any CPU + {8D830CBB-CA6E-47D8-9FB8-9230AAD272F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8D830CBB-CA6E-47D8-9FB8-9230AAD272F3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8D830CBB-CA6E-47D8-9FB8-9230AAD272F3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8D830CBB-CA6E-47D8-9FB8-9230AAD272F3}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1523,6 +1530,7 @@ Global {3829F774-33F2-41E9-B568-AE555004FC62} = {8977A560-45C2-4EC2-A849-97335B382C74} {8FCD1B85-BE63-4A2F-8E19-37244F19BE0F} = {55A62CFA-1155-46F1-ADF3-BEEE51B58AB5} {2B7DC612-1B37-41F7-BE31-4D600930EAC9} = {32A48625-F0AD-419D-828B-A50BDABA38EA} + {8D830CBB-CA6E-47D8-9FB8-9230AAD272F3} = {55A62CFA-1155-46F1-ADF3-BEEE51B58AB5} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {604E6B91-7BC0-4126-AE07-D4D2FEFC3D29} diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/DefaultAnalyzerAssemblyLoader.Core.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/DefaultAnalyzerAssemblyLoader.Core.cs index 0b3a04e6b1c40..422cbbe7067e1 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/DefaultAnalyzerAssemblyLoader.Core.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/DefaultAnalyzerAssemblyLoader.Core.cs @@ -71,6 +71,8 @@ internal class DefaultAnalyzerAssemblyLoader : AnalyzerAssemblyLoader "System.Xml.XDocument", "System.Xml.XPath.XDocument"); + internal virtual ImmutableHashSet AssemblySimpleNamesToBeLoadedInCompilerContext => CompilerAssemblySimpleNames; + // This is the context where compiler (and some of its dependencies) are being loaded into, which might be different from AssemblyLoadContext.Default. private static readonly AssemblyLoadContext s_compilerLoadContext = AssemblyLoadContext.GetLoadContext(typeof(DefaultAnalyzerAssemblyLoader).GetTypeInfo().Assembly)!; @@ -119,7 +121,7 @@ public DirectoryLoadContext(string directory, DefaultAnalyzerAssemblyLoader load protected override Assembly? Load(AssemblyName assemblyName) { var simpleName = assemblyName.Name!; - if (CompilerAssemblySimpleNames.Contains(simpleName)) + if (_loader.AssemblySimpleNamesToBeLoadedInCompilerContext.Contains(simpleName)) { // Delegate to the compiler's load context to load the compiler or anything // referenced by the compiler diff --git a/src/Workspaces/Core/Portable/Microsoft.CodeAnalysis.Workspaces.csproj b/src/Workspaces/Core/Portable/Microsoft.CodeAnalysis.Workspaces.csproj index 885b3161498c5..84d5ad632ea87 100644 --- a/src/Workspaces/Core/Portable/Microsoft.CodeAnalysis.Workspaces.csproj +++ b/src/Workspaces/Core/Portable/Microsoft.CodeAnalysis.Workspaces.csproj @@ -76,6 +76,7 @@ + diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteAnalyzerAssemblyLoader.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteAnalyzerAssemblyLoader.cs new file mode 100644 index 0000000000000..9df2a92ed1292 --- /dev/null +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteAnalyzerAssemblyLoader.cs @@ -0,0 +1,50 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.IO; + +namespace Microsoft.CodeAnalysis.Remote.Diagnostics +{ + /// + /// For analyzers shipped in Roslyn, different set of assemblies might be used when running + /// in-proc and OOP e.g. in-proc (VS) running on desktop clr and OOP running on ServiceHub .Net6 + /// host. We need to make sure to use the ones from the same location as the remote. + /// + internal sealed class RemoteAnalyzerAssemblyLoader : DefaultAnalyzerAssemblyLoader + { + private readonly string _baseDirectory; + + public RemoteAnalyzerAssemblyLoader(string baseDirectory) + { + _baseDirectory = baseDirectory; + } + + protected override string GetPathToLoad(string fullPath) + { + var fixedPath = Path.GetFullPath(Path.Combine(_baseDirectory, Path.GetFileName(fullPath))); + return File.Exists(fixedPath) ? fixedPath : fullPath; + } + +#if NETCOREAPP + + // The following are special assemblies since they contain IDE analyzers and/or their dependencies, + // but in the meantime, they also contain the host of compiler in remote process. Therefore on coreclr, + // we must ensure they are only loaded once and in the same ALC compiler asemblies are loaded into. + // Otherwise these analyzers will fail to interoperate with the host due to mismatch in assembly identity. + private static readonly ImmutableHashSet s_ideAssemblySimpleNames = + CompilerAssemblySimpleNames.Union(new[] + { + "Microsoft.CodeAnalysis.Features", + "Microsoft.CodeAnalysis.CSharp.Features", + "Microsoft.CodeAnalysis.VisualBasic.Features", + "Microsoft.CodeAnalysis.Workspaces", + "Microsoft.CodeAnalysis.CSharp.Workspaces", + "Microsoft.CodeAnalysis.VisualBasic.Workspaces", + }); + + internal override ImmutableHashSet AssemblySimpleNamesToBeLoadedInCompilerContext => s_ideAssemblySimpleNames; +#endif + } +} diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteAnalyzerAssemblyLoaderService.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteAnalyzerAssemblyLoaderService.cs index 2a6fec1c4927d..f2a8f467f6b9c 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteAnalyzerAssemblyLoaderService.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteAnalyzerAssemblyLoaderService.cs @@ -34,24 +34,5 @@ public RemoteAnalyzerAssemblyLoaderService() public IAnalyzerAssemblyLoader GetLoader(in AnalyzerAssemblyLoaderOptions options) => options.ShadowCopy ? _shadowCopyLoader : _loader; - - // For analyzers shipped in Roslyn, different set of assemblies might be used when running - // in-proc and OOP e.g. in-proc (VS) running on desktop clr and OOP running on ServiceHub .Net6 - // host. We need to make sure to use the ones from the same location as the remote. - private sealed class RemoteAnalyzerAssemblyLoader : DefaultAnalyzerAssemblyLoader - { - private readonly string _baseDirectory; - - public RemoteAnalyzerAssemblyLoader(string baseDirectory) - { - _baseDirectory = baseDirectory; - } - - protected override string GetPathToLoad(string fullPath) - { - var fixedPath = Path.GetFullPath(Path.Combine(_baseDirectory, Path.GetFileName(fullPath))); - return File.Exists(fixedPath) ? fixedPath : fullPath; - } - } } } diff --git a/src/Workspaces/Remote/ServiceHub/Microsoft.CodeAnalysis.Remote.ServiceHub.csproj b/src/Workspaces/Remote/ServiceHub/Microsoft.CodeAnalysis.Remote.ServiceHub.csproj index ad741ebc0aaf1..f330551a2603f 100644 --- a/src/Workspaces/Remote/ServiceHub/Microsoft.CodeAnalysis.Remote.ServiceHub.csproj +++ b/src/Workspaces/Remote/ServiceHub/Microsoft.CodeAnalysis.Remote.ServiceHub.csproj @@ -59,6 +59,7 @@ + diff --git a/src/Workspaces/Remote/ServiceHubTest/Microsoft.CodeAnalysis.Remote.ServiceHub.UnitTests.csproj b/src/Workspaces/Remote/ServiceHubTest/Microsoft.CodeAnalysis.Remote.ServiceHub.UnitTests.csproj new file mode 100644 index 0000000000000..af9d35f033920 --- /dev/null +++ b/src/Workspaces/Remote/ServiceHubTest/Microsoft.CodeAnalysis.Remote.ServiceHub.UnitTests.csproj @@ -0,0 +1,16 @@ + + + + + Library + net6.0-windows + Microsoft.CodeAnalysis.UnitTests + + + + + + + + + \ No newline at end of file diff --git a/src/Workspaces/Remote/ServiceHubTest/RemoteAnalyzerAssemblyLoaderTests.cs b/src/Workspaces/Remote/ServiceHubTest/RemoteAnalyzerAssemblyLoaderTests.cs new file mode 100644 index 0000000000000..c0cfdeeafa5c0 --- /dev/null +++ b/src/Workspaces/Remote/ServiceHubTest/RemoteAnalyzerAssemblyLoaderTests.cs @@ -0,0 +1,70 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.IO; +using System.Reflection; +using System.Runtime.Loader; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Remote.Diagnostics; +using Xunit; + +namespace Microsoft.CodeAnalysis.Remote.UnitTests +{ + public class RemoteAnalyzerAssemblyLoaderTests + { + [Fact] + public void NonIdeAnalyzerAssemblyShouldBeLoadedInSeparateALC() + { + var remoteAssemblyInCurrentAlc = typeof(RemoteAnalyzerAssemblyLoader).GetTypeInfo().Assembly; + var remoteAssemblyLocation = remoteAssemblyInCurrentAlc.Location; + + var loader = new RemoteAnalyzerAssemblyLoader(Path.GetDirectoryName(remoteAssemblyLocation)!); + + // Try to load MS.CA.Remote.ServiceHub.dll as an analyzer assembly via RemoteAnalyzerAssemblyLoader + // since it's not one of the special assemblies listed in RemoteAnalyzerAssemblyLoader, + // RemoteAnalyzerAssemblyLoader should loaded in a spearate DirectoryLoadContext. + loader.AddDependencyLocation(remoteAssemblyLocation); + var remoteAssemblyLoadedViaRemoteLoader = loader.LoadFromPath(remoteAssemblyLocation); + + var alc1 = AssemblyLoadContext.GetLoadContext(remoteAssemblyInCurrentAlc); + var alc2 = AssemblyLoadContext.GetLoadContext(remoteAssemblyLoadedViaRemoteLoader); + Assert.NotEqual(alc1, alc2); + } + + [Fact] + public void IdeAnalyzerAssemblyShouldBeLoadedInLoaderALC() + { + var featuresAssemblyInCurrentAlc = typeof(Microsoft.CodeAnalysis.Completion.CompletionProvider).GetTypeInfo().Assembly; + var featuresAssemblyLocation = featuresAssemblyInCurrentAlc.Location; + + // Try to load MS.CA.Features.dll as an analyzer assembly via RemoteAnalyzerAssemblyLoader + // since it's listed as one of the special assemblies in RemoteAnalyzerAssemblyLoader, + // RemoteAnalyzerAssemblyLoader should loaded in its own ALC. + var loader = new RemoteAnalyzerAssemblyLoader(Path.GetDirectoryName(featuresAssemblyLocation)!); + loader.AddDependencyLocation(featuresAssemblyLocation); + + var featuresAssemblyLoadedViaRemoteLoader = loader.LoadFromPath(featuresAssemblyLocation); + + var alc1 = AssemblyLoadContext.GetLoadContext(featuresAssemblyInCurrentAlc); + var alc2 = AssemblyLoadContext.GetLoadContext(featuresAssemblyLoadedViaRemoteLoader); + Assert.Equal(alc1, alc2); + } + + [Fact] + public void CompilerAssemblyShouldBeLoadedInLoaderALC() + { + var compilerAssemblyInCurrentAlc = typeof(SyntaxNode).GetTypeInfo().Assembly; + var compilerAssemblyLocation = compilerAssemblyInCurrentAlc.Location; + + var loader = new RemoteAnalyzerAssemblyLoader(Path.GetDirectoryName(compilerAssemblyLocation)!); + loader.AddDependencyLocation(compilerAssemblyLocation); + + var compilerAssemblyLoadedViaRemoteLoader = loader.LoadFromPath(compilerAssemblyLocation); + + var alc1 = AssemblyLoadContext.GetLoadContext(compilerAssemblyInCurrentAlc); + var alc2 = AssemblyLoadContext.GetLoadContext(compilerAssemblyLoadedViaRemoteLoader); + Assert.Equal(alc1, alc2); + } + } +}