diff --git a/ILSpy/Analyzers/AnalyzerScope.cs b/ILSpy/Analyzers/AnalyzerScope.cs index 2dbbc91d0..f9df75a0e 100644 --- a/ILSpy/Analyzers/AnalyzerScope.cs +++ b/ILSpy/Analyzers/AnalyzerScope.cs @@ -32,6 +32,7 @@ namespace ICSharpCode.ILSpy.Analyzers public class AnalyzerScope { readonly ITypeDefinition typeScope; + readonly AssemblyListSnapshot assemblyListSnapshot; /// /// Returns whether this scope is local, i.e., AnalyzedSymbol is only reachable @@ -40,6 +41,7 @@ namespace ICSharpCode.ILSpy.Analyzers public bool IsLocal { get; } public AssemblyList AssemblyList { get; } + public ISymbol AnalyzedSymbol { get; } public ITypeDefinition TypeScope => typeScope; @@ -49,6 +51,7 @@ namespace ICSharpCode.ILSpy.Analyzers public AnalyzerScope(AssemblyList assemblyList, IEntity entity) { AssemblyList = assemblyList; + assemblyListSnapshot = assemblyList.GetSnapshot(); AnalyzedSymbol = entity; if (entity is ITypeDefinition type) { @@ -76,7 +79,7 @@ namespace ICSharpCode.ILSpy.Analyzers public IEnumerable GetAllModules() { - return AssemblyList.GetAllAssemblies().GetAwaiter().GetResult() + return assemblyListSnapshot.GetAllAssembliesAsync().GetAwaiter().GetResult() .Select(asm => asm.GetPEFileOrNull()) .Where(x => x != null); } @@ -131,7 +134,7 @@ namespace ICSharpCode.ILSpy.Analyzers toWalkFiles.Push(self); checkedFiles.Add(self); - IList assemblies = AssemblyList.GetAllAssemblies().GetAwaiter().GetResult(); + IList assemblies = assemblyListSnapshot.GetAllAssembliesAsync().GetAwaiter().GetResult(); do { @@ -145,7 +148,7 @@ namespace ICSharpCode.ILSpy.Analyzers continue; if (checkedFiles.Contains(module)) continue; - var resolver = assembly.GetAssemblyResolver(loadOnDemand: false); + var resolver = assembly.GetAssemblyResolver(assemblyListSnapshot, loadOnDemand: false); foreach (var reference in module.AssemblyReferences) { if (resolver.Resolve(reference) == curFile) @@ -184,7 +187,7 @@ namespace ICSharpCode.ILSpy.Analyzers if (friendAssemblies.Count > 0) { - IEnumerable assemblies = AssemblyList.GetAllAssemblies() + IEnumerable assemblies = assemblyListSnapshot.GetAllAssembliesAsync() .GetAwaiter().GetResult(); foreach (var assembly in assemblies) @@ -208,7 +211,8 @@ namespace ICSharpCode.ILSpy.Analyzers foreach (var h in metadata.TypeReferences) { var typeRef = metadata.GetTypeReference(h); - if (metadata.StringComparer.Equals(typeRef.Name, typeScopeName) && metadata.StringComparer.Equals(typeRef.Namespace, typeScopeNamespace)) + if (metadata.StringComparer.Equals(typeRef.Name, typeScopeName) + && metadata.StringComparer.Equals(typeRef.Namespace, typeScopeNamespace)) { hasRef = true; break; @@ -223,7 +227,9 @@ namespace ICSharpCode.ILSpy.Analyzers foreach (var h in metadata.ExportedTypes) { var exportedType = metadata.GetExportedType(h); - if (exportedType.IsForwarder && metadata.StringComparer.Equals(exportedType.Name, typeScopeName) && metadata.StringComparer.Equals(exportedType.Namespace, typeScopeNamespace)) + if (exportedType.IsForwarder + && metadata.StringComparer.Equals(exportedType.Name, typeScopeName) + && metadata.StringComparer.Equals(exportedType.Namespace, typeScopeNamespace)) { hasForward = true; break; diff --git a/ILSpy/AssemblyList.cs b/ILSpy/AssemblyList.cs index e89d2101f..c361b11df 100644 --- a/ILSpy/AssemblyList.cs +++ b/ILSpy/AssemblyList.cs @@ -19,6 +19,7 @@ #nullable enable using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Diagnostics; @@ -118,55 +119,20 @@ namespace ICSharpCode.ILSpy } } - /// - /// Gets all loaded assemblies recursively, including assemblies found in bundles or packages. - /// - public async Task> GetAllAssemblies() + internal AssemblyListSnapshot GetSnapshot() { - var assemblies = GetAssemblies(); - var results = new List(assemblies.Length); - - foreach (var asm in assemblies) - { - LoadedAssembly.LoadResult result; - try - { - result = await asm.GetLoadResultAsync(); - } - catch - { - results.Add(asm); - continue; - } - if (result.Package != null) - { - AddDescendants(result.Package.RootFolder); - } - else if (result.PEFile != null) - { - results.Add(asm); - } - } - - void AddDescendants(PackageFolder folder) + lock (lockObj) { - foreach (var subFolder in folder.Folders) - { - AddDescendants(subFolder); - } - - foreach (var entry in folder.Entries) - { - if (!entry.Name.EndsWith(".dll", StringComparison.OrdinalIgnoreCase)) - continue; - var asm = folder.ResolveFileName(entry.Name); - if (asm == null) - continue; - results.Add(asm); - } + return new AssemblyListSnapshot(assemblies.ToImmutableArray()); } + } - return results; + /// + /// Gets all loaded assemblies recursively, including assemblies found in bundles or packages. + /// + public Task> GetAllAssemblies() + { + return GetSnapshot().GetAllAssembliesAsync(); } public int Count { diff --git a/ILSpy/AssemblyListSnapshot.cs b/ILSpy/AssemblyListSnapshot.cs new file mode 100644 index 000000000..0f0bb9a84 --- /dev/null +++ b/ILSpy/AssemblyListSnapshot.cs @@ -0,0 +1,138 @@ +// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Threading.Tasks; + +using ICSharpCode.Decompiler.Metadata; +using ICSharpCode.Decompiler.Util; + +namespace ICSharpCode.ILSpy +{ + class AssemblyListSnapshot + { + readonly ImmutableArray assemblies; + Dictionary? asmLookupByFullName; + Dictionary? asmLookupByShortName; + + public ImmutableArray Assemblies => assemblies; + + public AssemblyListSnapshot(ImmutableArray assemblies) + { + this.assemblies = assemblies; + } + + public async Task TryGetModuleAsync(IAssemblyReference reference, string tfm) + { + bool isWinRT = reference.IsWindowsRuntime; + string key = tfm + ";" + (isWinRT ? reference.Name : reference.FullName); + var lookup = LazyInit.VolatileRead(ref isWinRT ? ref asmLookupByShortName : ref asmLookupByFullName); + if (lookup == null) + { + lookup = await CreateLoadedAssemblyLookupAsync(shortNames: isWinRT).ConfigureAwait(false); + lookup = LazyInit.GetOrSet(ref isWinRT ? ref asmLookupByShortName : ref asmLookupByFullName, lookup); + } + if (lookup.TryGetValue(key, out PEFile module)) + return module; + return null; + } + + private async Task> CreateLoadedAssemblyLookupAsync(bool shortNames) + { + var result = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (LoadedAssembly loaded in assemblies) + { + try + { + var module = await loaded.GetPEFileOrNullAsync().ConfigureAwait(false); + if (module == null) + continue; + var reader = module.Metadata; + if (reader == null || !reader.IsAssembly) + continue; + string tfm = await loaded.GetTargetFrameworkIdAsync().ConfigureAwait(false); + string key = tfm + ";" + + (shortNames ? module.Name : module.FullName); + if (!result.ContainsKey(key)) + { + result.Add(key, module); + } + } + catch (BadImageFormatException) + { + continue; + } + } + return result; + } + + /// + /// Gets all loaded assemblies recursively, including assemblies found in bundles or packages. + /// + public async Task> GetAllAssembliesAsync() + { + var results = new List(assemblies.Length); + + foreach (var asm in assemblies) + { + LoadedAssembly.LoadResult result; + try + { + result = await asm.GetLoadResultAsync().ConfigureAwait(false); + } + catch + { + results.Add(asm); + continue; + } + if (result.Package != null) + { + AddDescendants(result.Package.RootFolder); + } + else if (result.PEFile != null) + { + results.Add(asm); + } + } + + void AddDescendants(PackageFolder folder) + { + foreach (var subFolder in folder.Folders) + { + AddDescendants(subFolder); + } + + foreach (var entry in folder.Entries) + { + if (!entry.Name.EndsWith(".dll", StringComparison.OrdinalIgnoreCase)) + continue; + var asm = folder.ResolveFileName(entry.Name); + if (asm == null) + continue; + results.Add(asm); + } + } + + return results; + } + } +} diff --git a/ILSpy/LoadedAssembly.cs b/ILSpy/LoadedAssembly.cs index cad1fb69f..624daf8fe 100644 --- a/ILSpy/LoadedAssembly.cs +++ b/ILSpy/LoadedAssembly.cs @@ -470,11 +470,11 @@ namespace ICSharpCode.ILSpy readonly IAssemblyResolver? providedAssemblyResolver; readonly AssemblyList assemblyList; - readonly LoadedAssembly[] alreadyLoadedAssemblies; + readonly AssemblyListSnapshot alreadyLoadedAssemblies; readonly Task tfmTask; readonly ReferenceLoadInfo referenceLoadInfo; - public MyAssemblyResolver(LoadedAssembly parent, bool loadOnDemand) + public MyAssemblyResolver(LoadedAssembly parent, AssemblyListSnapshot assemblyListSnapshot, bool loadOnDemand) { this.parent = parent; this.loadOnDemand = loadOnDemand; @@ -484,7 +484,7 @@ namespace ICSharpCode.ILSpy // Note: we cache a copy of the assembly list in the constructor, so that the // resolve calls only search-by-asm-name in the assemblies that were already loaded // at the time of the GetResolver() call. - this.alreadyLoadedAssemblies = assemblyList.GetAssemblies(); + this.alreadyLoadedAssemblies = assemblyListSnapshot; // If we didn't do this, we'd also search in the assemblies that we just started to load // in previous Resolve() calls; but we don't want to wait for those to be loaded. this.tfmTask = parent.GetTargetFrameworkIdAsync(); @@ -496,9 +496,6 @@ namespace ICSharpCode.ILSpy return ResolveAsync(reference).GetAwaiter().GetResult(); } - Dictionary? asmLookupByFullName; - Dictionary? asmLookupByShortName; - /// /// 0) if we're inside a package, look for filename.dll in parent directories /// 1) try to find exact match by tfm + full asm name in loaded assemblies @@ -524,17 +521,9 @@ namespace ICSharpCode.ILSpy string tfm = await tfmTask.ConfigureAwait(false); - bool isWinRT = reference.IsWindowsRuntime; - string key = tfm + ";" + (isWinRT ? reference.Name : reference.FullName); - // 1) try to find exact match by tfm + full asm name in loaded assemblies - var lookup = LazyInit.VolatileRead(ref isWinRT ? ref asmLookupByShortName : ref asmLookupByFullName); - if (lookup == null) - { - lookup = await CreateLoadedAssemblyLookupAsync(shortNames: isWinRT).ConfigureAwait(false); - lookup = LazyInit.GetOrSet(ref isWinRT ? ref asmLookupByShortName : ref asmLookupByFullName, lookup); - } - if (lookup.TryGetValue(key, out module)) + module = await alreadyLoadedAssemblies.TryGetModuleAsync(reference, tfm); + if (module != null) { referenceLoadInfo.AddMessageOnce(reference.FullName, MessageKind.Info, "Success - Found in Assembly List"); return module; @@ -566,7 +555,7 @@ namespace ICSharpCode.ILSpy // Assembly not found; try to find a similar-enough already-loaded assembly var candidates = new List<(LoadedAssembly assembly, Version version)>(); - foreach (LoadedAssembly loaded in alreadyLoadedAssemblies) + foreach (LoadedAssembly loaded in alreadyLoadedAssemblies.Assemblies) { module = await loaded.GetPEFileOrNullAsync().ConfigureAwait(false); var reader = module?.Metadata; @@ -594,35 +583,6 @@ namespace ICSharpCode.ILSpy } } - private async Task> CreateLoadedAssemblyLookupAsync(bool shortNames) - { - var result = new Dictionary(StringComparer.OrdinalIgnoreCase); - foreach (LoadedAssembly loaded in alreadyLoadedAssemblies) - { - try - { - var module = await loaded.GetPEFileOrNullAsync().ConfigureAwait(false); - if (module == null) - continue; - var reader = module.Metadata; - if (reader == null || !reader.IsAssembly) - continue; - string tfm = await loaded.GetTargetFrameworkIdAsync().ConfigureAwait(false); - string key = tfm + ";" - + (shortNames ? module.Name : module.FullName); - if (!result.ContainsKey(key)) - { - result.Add(key, module); - } - } - catch (BadImageFormatException) - { - continue; - } - } - return result; - } - public PEFile? ResolveModule(PEFile mainModule, string moduleName) { return ResolveModuleAsync(mainModule, moduleName).GetAwaiter().GetResult(); @@ -659,7 +619,7 @@ namespace ICSharpCode.ILSpy else { // Module does not exist on disk, look for one with a matching name in the assemblylist: - foreach (LoadedAssembly loaded in alreadyLoadedAssemblies) + foreach (LoadedAssembly loaded in alreadyLoadedAssemblies.Assemblies) { var module = await loaded.GetPEFileOrNullAsync().ConfigureAwait(false); var reader = module?.Metadata; @@ -679,7 +639,12 @@ namespace ICSharpCode.ILSpy public IAssemblyResolver GetAssemblyResolver(bool loadOnDemand = true) { - return new MyAssemblyResolver(this, loadOnDemand); + return new MyAssemblyResolver(this, AssemblyList.GetSnapshot(), loadOnDemand); + } + + internal IAssemblyResolver GetAssemblyResolver(AssemblyListSnapshot snapshot, bool loadOnDemand = true) + { + return new MyAssemblyResolver(this, snapshot, loadOnDemand); } private UniversalAssemblyResolver GetUniversalResolver()