Browse Source

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

pull/2643/head
Siegfried Pammer 3 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 @@ -32,6 +32,7 @@ namespace ICSharpCode.ILSpy.Analyzers
public class AnalyzerScope
{
readonly ITypeDefinition typeScope;
readonly AssemblyListSnapshot assemblyListSnapshot;
/// <summary>
/// Returns whether this scope is local, i.e., AnalyzedSymbol is only reachable
@ -40,6 +41,7 @@ namespace ICSharpCode.ILSpy.Analyzers @@ -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 @@ -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 @@ -76,7 +79,7 @@ namespace ICSharpCode.ILSpy.Analyzers
public IEnumerable<PEFile> 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 @@ -131,7 +134,7 @@ namespace ICSharpCode.ILSpy.Analyzers
toWalkFiles.Push(self);
checkedFiles.Add(self);
IList<LoadedAssembly> assemblies = AssemblyList.GetAllAssemblies().GetAwaiter().GetResult();
IList<LoadedAssembly> assemblies = assemblyListSnapshot.GetAllAssembliesAsync().GetAwaiter().GetResult();
do
{
@ -145,7 +148,7 @@ namespace ICSharpCode.ILSpy.Analyzers @@ -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 @@ -184,7 +187,7 @@ namespace ICSharpCode.ILSpy.Analyzers
if (friendAssemblies.Count > 0)
{
IEnumerable<LoadedAssembly> assemblies = AssemblyList.GetAllAssemblies()
IEnumerable<LoadedAssembly> assemblies = assemblyListSnapshot.GetAllAssembliesAsync()
.GetAwaiter().GetResult();
foreach (var assembly in assemblies)
@ -208,7 +211,8 @@ namespace ICSharpCode.ILSpy.Analyzers @@ -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 @@ -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;

56
ILSpy/AssemblyList.cs

@ -19,6 +19,7 @@ @@ -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 @@ -118,55 +119,20 @@ namespace ICSharpCode.ILSpy
}
}
/// <summary>
/// Gets all loaded assemblies recursively, including assemblies found in bundles or packages.
/// </summary>
public async Task<IList<LoadedAssembly>> GetAllAssemblies()
internal AssemblyListSnapshot GetSnapshot()
{
var assemblies = GetAssemblies();
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)
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;
/// <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 {

138
ILSpy/AssemblyListSnapshot.cs

@ -0,0 +1,138 @@ @@ -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 @@ -470,11 +470,11 @@ namespace ICSharpCode.ILSpy
readonly IAssemblyResolver? providedAssemblyResolver;
readonly AssemblyList assemblyList;
readonly LoadedAssembly[] alreadyLoadedAssemblies;
readonly AssemblyListSnapshot alreadyLoadedAssemblies;
readonly Task<string> 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 @@ -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 @@ -496,9 +496,6 @@ namespace ICSharpCode.ILSpy
return ResolveAsync(reference).GetAwaiter().GetResult();
}
Dictionary<string, PEFile>? asmLookupByFullName;
Dictionary<string, PEFile>? asmLookupByShortName;
/// <summary>
/// 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 @@ -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 @@ -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 @@ -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)
{
return ResolveModuleAsync(mainModule, moduleName).GetAwaiter().GetResult();
@ -659,7 +619,7 @@ namespace ICSharpCode.ILSpy @@ -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 @@ -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()

Loading…
Cancel
Save