Browse Source

#2594: Introduce AssemblyListSnapshot to avoid expensive and repetitive LoadedAssembly.CreateLoadedAssemblyLookupAsync calls

pull/2643/head
Siegfried Pammer 4 years ago
parent
commit
11fbb2610d
  1. 18
      ILSpy/Analyzers/AnalyzerScope.cs
  2. 56
      ILSpy/AssemblyList.cs
  3. 138
      ILSpy/AssemblyListSnapshot.cs
  4. 61
      ILSpy/LoadedAssembly.cs

18
ILSpy/Analyzers/AnalyzerScope.cs

@ -32,6 +32,7 @@ namespace ICSharpCode.ILSpy.Analyzers
public class AnalyzerScope public class AnalyzerScope
{ {
readonly ITypeDefinition typeScope; readonly ITypeDefinition typeScope;
readonly AssemblyListSnapshot assemblyListSnapshot;
/// <summary> /// <summary>
/// Returns whether this scope is local, i.e., AnalyzedSymbol is only reachable /// 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 bool IsLocal { get; }
public AssemblyList AssemblyList { get; } public AssemblyList AssemblyList { get; }
public ISymbol AnalyzedSymbol { get; } public ISymbol AnalyzedSymbol { get; }
public ITypeDefinition TypeScope => typeScope; public ITypeDefinition TypeScope => typeScope;
@ -49,6 +51,7 @@ namespace ICSharpCode.ILSpy.Analyzers
public AnalyzerScope(AssemblyList assemblyList, IEntity entity) public AnalyzerScope(AssemblyList assemblyList, IEntity entity)
{ {
AssemblyList = assemblyList; AssemblyList = assemblyList;
assemblyListSnapshot = assemblyList.GetSnapshot();
AnalyzedSymbol = entity; AnalyzedSymbol = entity;
if (entity is ITypeDefinition type) if (entity is ITypeDefinition type)
{ {
@ -76,7 +79,7 @@ namespace ICSharpCode.ILSpy.Analyzers
public IEnumerable<PEFile> GetAllModules() public IEnumerable<PEFile> GetAllModules()
{ {
return AssemblyList.GetAllAssemblies().GetAwaiter().GetResult() return assemblyListSnapshot.GetAllAssembliesAsync().GetAwaiter().GetResult()
.Select(asm => asm.GetPEFileOrNull()) .Select(asm => asm.GetPEFileOrNull())
.Where(x => x != null); .Where(x => x != null);
} }
@ -131,7 +134,7 @@ namespace ICSharpCode.ILSpy.Analyzers
toWalkFiles.Push(self); toWalkFiles.Push(self);
checkedFiles.Add(self); checkedFiles.Add(self);
IList<LoadedAssembly> assemblies = AssemblyList.GetAllAssemblies().GetAwaiter().GetResult(); IList<LoadedAssembly> assemblies = assemblyListSnapshot.GetAllAssembliesAsync().GetAwaiter().GetResult();
do do
{ {
@ -145,7 +148,7 @@ namespace ICSharpCode.ILSpy.Analyzers
continue; continue;
if (checkedFiles.Contains(module)) if (checkedFiles.Contains(module))
continue; continue;
var resolver = assembly.GetAssemblyResolver(loadOnDemand: false); var resolver = assembly.GetAssemblyResolver(assemblyListSnapshot, loadOnDemand: false);
foreach (var reference in module.AssemblyReferences) foreach (var reference in module.AssemblyReferences)
{ {
if (resolver.Resolve(reference) == curFile) if (resolver.Resolve(reference) == curFile)
@ -184,7 +187,7 @@ namespace ICSharpCode.ILSpy.Analyzers
if (friendAssemblies.Count > 0) if (friendAssemblies.Count > 0)
{ {
IEnumerable<LoadedAssembly> assemblies = AssemblyList.GetAllAssemblies() IEnumerable<LoadedAssembly> assemblies = assemblyListSnapshot.GetAllAssembliesAsync()
.GetAwaiter().GetResult(); .GetAwaiter().GetResult();
foreach (var assembly in assemblies) foreach (var assembly in assemblies)
@ -208,7 +211,8 @@ namespace ICSharpCode.ILSpy.Analyzers
foreach (var h in metadata.TypeReferences) foreach (var h in metadata.TypeReferences)
{ {
var typeRef = metadata.GetTypeReference(h); 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; hasRef = true;
break; break;
@ -223,7 +227,9 @@ namespace ICSharpCode.ILSpy.Analyzers
foreach (var h in metadata.ExportedTypes) foreach (var h in metadata.ExportedTypes)
{ {
var exportedType = metadata.GetExportedType(h); 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; hasForward = true;
break; break;

56
ILSpy/AssemblyList.cs

@ -19,6 +19,7 @@
#nullable enable #nullable enable
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Diagnostics; using System.Diagnostics;
@ -118,55 +119,20 @@ namespace ICSharpCode.ILSpy
} }
} }
/// <summary> internal AssemblyListSnapshot GetSnapshot()
/// Gets all loaded assemblies recursively, including assemblies found in bundles or packages.
/// </summary>
public async Task<IList<LoadedAssembly>> GetAllAssemblies()
{ {
var assemblies = GetAssemblies(); lock (lockObj)
var results = new List<LoadedAssembly>(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)
{ {
foreach (var subFolder in folder.Folders) return new AssemblyListSnapshot(assemblies.ToImmutableArray());
{
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; /// <summary>
/// Gets all loaded assemblies recursively, including assemblies found in bundles or packages.
/// </summary>
public Task<IList<LoadedAssembly>> GetAllAssemblies()
{
return GetSnapshot().GetAllAssembliesAsync();
} }
public int Count { public int Count {

138
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<LoadedAssembly> assemblies;
Dictionary<string, PEFile>? asmLookupByFullName;
Dictionary<string, PEFile>? asmLookupByShortName;
public ImmutableArray<LoadedAssembly> Assemblies => assemblies;
public AssemblyListSnapshot(ImmutableArray<LoadedAssembly> assemblies)
{
this.assemblies = assemblies;
}
public async Task<PEFile?> 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<Dictionary<string, PEFile>> CreateLoadedAssemblyLookupAsync(bool shortNames)
{
var result = new Dictionary<string, PEFile>(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;
}
/// <summary>
/// Gets all loaded assemblies recursively, including assemblies found in bundles or packages.
/// </summary>
public async Task<IList<LoadedAssembly>> GetAllAssembliesAsync()
{
var results = new List<LoadedAssembly>(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;
}
}
}

61
ILSpy/LoadedAssembly.cs

@ -470,11 +470,11 @@ namespace ICSharpCode.ILSpy
readonly IAssemblyResolver? providedAssemblyResolver; readonly IAssemblyResolver? providedAssemblyResolver;
readonly AssemblyList assemblyList; readonly AssemblyList assemblyList;
readonly LoadedAssembly[] alreadyLoadedAssemblies; readonly AssemblyListSnapshot alreadyLoadedAssemblies;
readonly Task<string> tfmTask; readonly Task<string> tfmTask;
readonly ReferenceLoadInfo referenceLoadInfo; readonly ReferenceLoadInfo referenceLoadInfo;
public MyAssemblyResolver(LoadedAssembly parent, bool loadOnDemand) public MyAssemblyResolver(LoadedAssembly parent, AssemblyListSnapshot assemblyListSnapshot, bool loadOnDemand)
{ {
this.parent = parent; this.parent = parent;
this.loadOnDemand = loadOnDemand; 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 // 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 // resolve calls only search-by-asm-name in the assemblies that were already loaded
// at the time of the GetResolver() call. // 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 // 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. // in previous Resolve() calls; but we don't want to wait for those to be loaded.
this.tfmTask = parent.GetTargetFrameworkIdAsync(); this.tfmTask = parent.GetTargetFrameworkIdAsync();
@ -496,9 +496,6 @@ namespace ICSharpCode.ILSpy
return ResolveAsync(reference).GetAwaiter().GetResult(); return ResolveAsync(reference).GetAwaiter().GetResult();
} }
Dictionary<string, PEFile>? asmLookupByFullName;
Dictionary<string, PEFile>? asmLookupByShortName;
/// <summary> /// <summary>
/// 0) if we're inside a package, look for filename.dll in parent directories /// 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 /// 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); 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 // 1) try to find exact match by tfm + full asm name in loaded assemblies
var lookup = LazyInit.VolatileRead(ref isWinRT ? ref asmLookupByShortName : ref asmLookupByFullName); module = await alreadyLoadedAssemblies.TryGetModuleAsync(reference, tfm);
if (lookup == null) if (module != null)
{
lookup = await CreateLoadedAssemblyLookupAsync(shortNames: isWinRT).ConfigureAwait(false);
lookup = LazyInit.GetOrSet(ref isWinRT ? ref asmLookupByShortName : ref asmLookupByFullName, lookup);
}
if (lookup.TryGetValue(key, out module))
{ {
referenceLoadInfo.AddMessageOnce(reference.FullName, MessageKind.Info, "Success - Found in Assembly List"); referenceLoadInfo.AddMessageOnce(reference.FullName, MessageKind.Info, "Success - Found in Assembly List");
return module; return module;
@ -566,7 +555,7 @@ namespace ICSharpCode.ILSpy
// Assembly not found; try to find a similar-enough already-loaded assembly // Assembly not found; try to find a similar-enough already-loaded assembly
var candidates = new List<(LoadedAssembly assembly, Version version)>(); 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); module = await loaded.GetPEFileOrNullAsync().ConfigureAwait(false);
var reader = module?.Metadata; var reader = module?.Metadata;
@ -594,35 +583,6 @@ namespace ICSharpCode.ILSpy
} }
} }
private async Task<Dictionary<string, PEFile>> CreateLoadedAssemblyLookupAsync(bool shortNames)
{
var result = new Dictionary<string, PEFile>(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) public PEFile? ResolveModule(PEFile mainModule, string moduleName)
{ {
return ResolveModuleAsync(mainModule, moduleName).GetAwaiter().GetResult(); return ResolveModuleAsync(mainModule, moduleName).GetAwaiter().GetResult();
@ -659,7 +619,7 @@ namespace ICSharpCode.ILSpy
else else
{ {
// Module does not exist on disk, look for one with a matching name in the assemblylist: // 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 module = await loaded.GetPEFileOrNullAsync().ConfigureAwait(false);
var reader = module?.Metadata; var reader = module?.Metadata;
@ -679,7 +639,12 @@ namespace ICSharpCode.ILSpy
public IAssemblyResolver GetAssemblyResolver(bool loadOnDemand = true) 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() private UniversalAssemblyResolver GetUniversalResolver()

Loading…
Cancel
Save