/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Scripting/Core/Hosting/AssemblyLoader/InteractiveAssemblyLoader.cs
486 строк
20 KB
Cyrus Najmabadi
Remove unused usings
18 июн 2025, 22:17
18 июн 2025, 22:17
481c36c
Код
Авторство
О чём код?
// 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; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Reflection.Metadata; using System.Reflection.PortableExecutable; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Scripting.Hosting { /// <summary> /// Implements an assembly loader for interactive compiler and REPL. /// </summary> /// <remarks> /// <para> /// The class is thread-safe. /// </para> /// </remarks> public sealed partial class InteractiveAssemblyLoader : IDisposable { private class LoadedAssembly { /// <summary> /// The original path of the assembly before it was shadow-copied. /// For GAC'd assemblies, this is equal to Assembly.Location no matter what path was used to load them. /// </summary> public string? OriginalPath { get; set; } } private readonly AssemblyLoaderImpl _runtimeAssemblyLoader; private readonly MetadataShadowCopyProvider? _shadowCopyProvider; // Synchronizes assembly reference tracking. // Needs to be thread-safe since assembly loading may be triggered at any time by CLR type loader. private readonly object _referencesLock = new object(); // loaded assembly -> loaded assembly info private readonly Dictionary<Assembly, LoadedAssembly> _assembliesLoadedFromLocation; // { original full path -> assembly } // Note, that there might be multiple entries for a single GAC'd assembly. private readonly Dictionary<string, AssemblyAndLocation> _assembliesLoadedFromLocationByFullPath; // simple name -> loaded assemblies private readonly Dictionary<string, List<LoadedAssemblyInfo>> _loadedAssembliesBySimpleName; // simple name -> identity and location of a known dependency private readonly Dictionary<string, List<AssemblyIdentityAndLocation>> _dependenciesWithLocationBySimpleName; [DebuggerDisplay("{GetDebuggerDisplay(),nq}")] private readonly struct AssemblyIdentityAndLocation { public readonly AssemblyIdentity Identity; public readonly string Location; public AssemblyIdentityAndLocation(AssemblyIdentity identity, string location) { Identity = identity; Location = location; } private string GetDebuggerDisplay() => Identity + " @ " + Location; } [DebuggerDisplay("{GetDebuggerDisplay(),nq}")] private readonly struct LoadedAssemblyInfo(Assembly assembly, AssemblyIdentity identity, string? location) { public readonly Assembly Assembly = assembly; public readonly AssemblyIdentity Identity = identity; public readonly string? Location = location; public bool IsDefault => Assembly == null; private string GetDebuggerDisplay() => IsDefault ? "uninitialized" : Identity.GetDisplayName() + (Location != null ? " @ " + Location : ""); } public InteractiveAssemblyLoader(MetadataShadowCopyProvider? shadowCopyProvider = null) { _shadowCopyProvider = shadowCopyProvider; _assembliesLoadedFromLocationByFullPath = new Dictionary<string, AssemblyAndLocation>(); _assembliesLoadedFromLocation = new Dictionary<Assembly, LoadedAssembly>(); _loadedAssembliesBySimpleName = new Dictionary<string, List<LoadedAssemblyInfo>>(AssemblyIdentityComparer.SimpleNameComparer); _dependenciesWithLocationBySimpleName = new Dictionary<string, List<AssemblyIdentityAndLocation>>(); _runtimeAssemblyLoader = AssemblyLoaderImpl.Create(this); } public void Dispose() { _runtimeAssemblyLoader.Dispose(); } internal Assembly LoadAssemblyFromStream(Stream peStream, Stream pdbStream) { Assembly assembly = _runtimeAssemblyLoader.LoadFromStream(peStream, pdbStream); RegisterDependency(assembly); return assembly; } private AssemblyAndLocation Load(string reference) { MetadataShadowCopy? copy = null; try { if (_shadowCopyProvider != null) { // All modules of the assembly has to be copied at once to keep the metadata consistent. // This API copies the xml doc file and keeps the memory-maps of the metadata. // We don't need that here but presumably the provider is shared with the compilation API that needs both. // Ideally the CLR would expose API to load Assembly given a byte* and not create their own memory-map. copy = _shadowCopyProvider.GetMetadataShadowCopy(reference, MetadataImageKind.Assembly); } var result = _runtimeAssemblyLoader.LoadFromPath((copy != null) ? copy.PrimaryModule.FullPath : reference); if (_shadowCopyProvider != null && result.GlobalAssemblyCache) { _shadowCopyProvider.SuppressShadowCopy(reference); } return result; } catch (FileNotFoundException) { return default; } finally { // copy holds on the file handle, we need to keep the handle // open until the file is locked by the CLR assembly loader: copy?.DisposeFileHandles(); } } /// <summary> /// Notifies the assembly loader about a dependency that might be loaded in future. /// </summary> /// <param name="dependency">Assembly identity.</param> /// <param name="path">Assembly location.</param> /// <remarks> /// Associates a full assembly name with its location. The association is used when an assembly /// is being loaded and its name needs to be resolved to a location. /// </remarks> /// <exception cref="ArgumentNullException"><paramref name="dependency"/> is null.</exception> /// <exception cref="ArgumentException"><paramref name="path"/> is not an existing path.</exception> public void RegisterDependency(AssemblyIdentity dependency, string path) { if (dependency == null) { throw new ArgumentNullException(nameof(dependency)); } if (!PathUtilities.IsAbsolute(path)) { throw new ArgumentException(ScriptingResources.AbsolutePathExpected, nameof(path)); } lock (_referencesLock) { RegisterDependencyNoLock(new AssemblyIdentityAndLocation(dependency, path)); } } /// <summary> /// Notifies the assembly loader about an in-memory dependency that should be available within the resolution context. /// </summary> /// <param name="dependency">Assembly identity.</param> /// <exception cref="ArgumentNullException"><paramref name="dependency"/> is null.</exception> /// <remarks> /// When another in-memory assembly references the <paramref name="dependency"/> the loader /// responds with the specified dependency if the assembly identity matches the requested one. /// </remarks> public void RegisterDependency(Assembly dependency) { if (dependency == null) { throw new ArgumentNullException(nameof(dependency)); } lock (_referencesLock) { RegisterLoadedAssemblySimpleNameNoLock(dependency, location: null); } } private void RegisterLoadedAssemblySimpleNameNoLock(Assembly assembly, string? location) { var identity = AssemblyIdentity.FromAssemblyDefinition(assembly); var info = new LoadedAssemblyInfo(assembly, identity, location); if (_loadedAssembliesBySimpleName.TryGetValue(identity.Name, out var existingInfos)) { existingInfos.Add(info); } else { _loadedAssembliesBySimpleName.Add(identity.Name, new List<LoadedAssemblyInfo> { info }); } } private void RegisterDependencyNoLock(AssemblyIdentityAndLocation dependency) { string simpleName = dependency.Identity.Name; if (_dependenciesWithLocationBySimpleName.TryGetValue(simpleName, out var sameSimpleNameAssemblyIdentities)) { sameSimpleNameAssemblyIdentities.Add(dependency); } else { _dependenciesWithLocationBySimpleName.Add(simpleName, new List<AssemblyIdentityAndLocation> { dependency }); } } internal Assembly? ResolveAssembly(string assemblyDisplayName, Assembly? requestingAssembly) { if (!AssemblyIdentity.TryParseDisplayName(assemblyDisplayName, out var identity)) { return null; } string? loadDirectory; lock (_referencesLock) { if (requestingAssembly != null && _assembliesLoadedFromLocation.TryGetValue(requestingAssembly, out var loadedAssembly)) { loadDirectory = Path.GetDirectoryName(loadedAssembly.OriginalPath); } else { loadDirectory = null; } } return ResolveAssembly(identity, loadDirectory); } internal Assembly? ResolveAssembly(AssemblyIdentity identity, string? loadDirectory) { // if the referring assembly is already loaded by our loader, load from its directory: if (loadDirectory != null) { Assembly? assembly; var conflictingLoadedAssemblyOpt = default(LoadedAssemblyInfo); var loadedAssemblyWithEqualNameAndVersionOpt = default(LoadedAssemblyInfo); lock (_referencesLock) { // Has the file already been loaded? assembly = TryGetAssemblyLoadedFromPath(identity, loadDirectory); if (assembly != null) { return assembly; } // Has an assembly with the same name and version been loaded (possibly from a different directory)? if (_loadedAssembliesBySimpleName.TryGetValue(identity.Name, out var loadedInfos)) { // Desktop FX: A weak-named assembly conflicts with another weak-named assembly of the same simple name, // unless we find an assembly whose identity matches exactly and whose content is exactly the same. // TODO: We shouldn't block this on CoreCLR. https://github.com/dotnet/roslyn/issues/38621 if (!identity.IsStrongName) { conflictingLoadedAssemblyOpt = loadedInfos.FirstOrDefault(info => !info.Identity.IsStrongName); } loadedAssemblyWithEqualNameAndVersionOpt = loadedInfos.FirstOrDefault(info => AssemblyIdentityComparer.SimpleNameComparer.Equals(info.Identity.Name, identity.Name) && info.Identity.Version == identity.Version); } } var assemblyFilePath = FindExistingAssemblyFile(identity.Name, loadDirectory); if (assemblyFilePath != null) { // TODO: Stop using reflection once ModuleVersionId property once is available in Core contract. if (loadedAssemblyWithEqualNameAndVersionOpt.Assembly != null) { if (TryReadMvid(assemblyFilePath, out var mvid) && loadedAssemblyWithEqualNameAndVersionOpt.Assembly.ManifestModule.ModuleVersionId == mvid) { return loadedAssemblyWithEqualNameAndVersionOpt.Assembly; } // error: attempt to load an assembly with the same identity as already loaded assembly but different content throw new InteractiveAssemblyLoaderException( string.Format(null, ScriptingResources.AssemblyAlreadyLoaded, identity.Name, identity.Version, loadedAssemblyWithEqualNameAndVersionOpt.Location, assemblyFilePath) ); } // TODO: Desktop FX only https://github.com/dotnet/roslyn/issues/38621 if (!conflictingLoadedAssemblyOpt.IsDefault) { // error: attempt to load an assembly with the same identity as already loaded assembly but different content throw new InteractiveAssemblyLoaderException( string.Format(null, ScriptingResources.AssemblyAlreadyLoadedNotSigned, identity.Name, conflictingLoadedAssemblyOpt.Location, assemblyFilePath) ); } assembly = ShadowCopyAndLoadDependency(assemblyFilePath).Assembly; if (assembly != null) { return assembly; } } } return GetOrLoadKnownAssembly(identity); } private static string? FindExistingAssemblyFile(string simpleName, string directory) { string pathWithoutExtension = Path.Combine(directory, simpleName); foreach (var extension in RuntimeMetadataReferenceResolver.AssemblyExtensions) { string path = pathWithoutExtension + extension; if (File.Exists(path)) { return path; } } return null; } private Assembly? TryGetAssemblyLoadedFromPath(AssemblyIdentity identity, string directory) { string pathWithoutExtension = Path.Combine(directory, identity.Name); foreach (var extension in RuntimeMetadataReferenceResolver.AssemblyExtensions) { AssemblyAndLocation assemblyAndLocation; if (_assembliesLoadedFromLocationByFullPath.TryGetValue(pathWithoutExtension + extension, out assemblyAndLocation) && identity.Equals(AssemblyIdentity.FromAssemblyDefinition(assemblyAndLocation.Assembly))) { return assemblyAndLocation.Assembly; } } return null; } private static bool TryReadMvid(string filePath, out Guid mvid) { try { using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete)) { using (var peReader = new PEReader(stream)) { var metadataReader = peReader.GetMetadataReader(); mvid = metadataReader.GetGuid(metadataReader.GetModuleDefinition().Mvid); return true; } } } catch { mvid = default; return false; } } private Assembly? GetOrLoadKnownAssembly(AssemblyIdentity identity) { Assembly? assembly = null; string? assemblyFileToLoad = null; // Try to find the assembly among assemblies that we loaded, comparing its identity with the requested one. lock (_referencesLock) { // already loaded assemblies: if (_loadedAssembliesBySimpleName.TryGetValue(identity.Name, out var infos)) { assembly = FindHighestVersionOrFirstMatchingIdentity(identity, infos); if (assembly != null) { return assembly; } } // names: if (_dependenciesWithLocationBySimpleName.TryGetValue(identity.Name, out var sameSimpleNameIdentities)) { var identityAndLocation = FindHighestVersionOrFirstMatchingIdentity(identity, sameSimpleNameIdentities); if (identityAndLocation.Identity != null) { assemblyFileToLoad = identityAndLocation.Location; AssemblyAndLocation assemblyAndLocation; if (_assembliesLoadedFromLocationByFullPath.TryGetValue(assemblyFileToLoad, out assemblyAndLocation)) { return assemblyAndLocation.Assembly; } } } } if (assemblyFileToLoad != null) { assembly = ShadowCopyAndLoadDependency(assemblyFileToLoad).Assembly; } return assembly; } private AssemblyAndLocation ShadowCopyAndLoadDependency(string originalPath) { AssemblyAndLocation assemblyAndLocation = Load(originalPath); if (assemblyAndLocation.IsDefault) { return default; } lock (_referencesLock) { // Always remember the path. The assembly might have been loaded from another path or not loaded yet. _assembliesLoadedFromLocationByFullPath[originalPath] = assemblyAndLocation; if (_assembliesLoadedFromLocation.TryGetValue(assemblyAndLocation.Assembly, out var loadedAssembly)) { return assemblyAndLocation; } _assembliesLoadedFromLocation.Add( assemblyAndLocation.Assembly, new LoadedAssembly { OriginalPath = assemblyAndLocation.GlobalAssemblyCache ? assemblyAndLocation.Location : originalPath }); RegisterLoadedAssemblySimpleNameNoLock(assemblyAndLocation.Assembly, assemblyAndLocation.Location); } return assemblyAndLocation; } private static Assembly? FindHighestVersionOrFirstMatchingIdentity(AssemblyIdentity identity, IEnumerable<LoadedAssemblyInfo> infos) { Assembly? candidate = null; Version? candidateVersion = null; foreach (var info in infos) { if (DesktopAssemblyIdentityComparer.Default.ReferenceMatchesDefinition(identity, info.Identity)) { if (candidate == null || candidateVersion < info.Identity.Version) { candidate = info.Assembly; candidateVersion = info.Identity.Version; } } } return candidate; } private static AssemblyIdentityAndLocation FindHighestVersionOrFirstMatchingIdentity(AssemblyIdentity identity, IEnumerable<AssemblyIdentityAndLocation> assemblies) { var candidate = default(AssemblyIdentityAndLocation); foreach (var assembly in assemblies) { if (DesktopAssemblyIdentityComparer.Default.ReferenceMatchesDefinition(identity, assembly.Identity)) { if (candidate.Identity == null || candidate.Identity.Version < assembly.Identity.Version) { candidate = assembly; } } } return candidate; } } }