Browse Source

Simplify the assembly-resolving implementation.

* We no longer maintain the weird `loadingAssemblies` global state.
   * AssemblyList now internally handles multiple concurrent load requests for the same filename.
   * AssemblyList.assemblies and its lock is now private to the AssemblyList.
 * Removed a questionable caching layer (cache was per-AssemblyList, but was caching the result of a per-LoadedAssembly lookup function.
 * Replaced static DisableAssemblyLoad() with bool-parameter on GetAssemblyResolver() call.
pull/2301/head
Daniel Grunwald 4 years ago
parent
commit
263d9b9e07
  1. 8
      ILSpy.ReadyToRun/ReadyToRunLanguage.cs
  2. 11
      ILSpy/Analyzers/AnalyzerScope.cs
  3. 170
      ILSpy/AssemblyList.cs
  4. 7
      ILSpy/AssemblyListManager.cs
  5. 141
      ILSpy/Languages/CSharpLanguage.cs
  6. 25
      ILSpy/Languages/ILLanguage.cs
  7. 423
      ILSpy/LoadedAssembly.cs
  8. 9
      ILSpy/LoadedAssemblyExtensions.cs
  9. 2
      ILSpy/MainWindow.xaml.cs
  10. 41
      ILSpy/TreeNodes/AssemblyListTreeNode.cs
  11. 9
      ILSpy/TreeNodes/ModuleReferenceTreeNode.cs
  12. 2
      ILSpy/ViewModels/ManageAssemblyListsViewModel.cs

8
ILSpy.ReadyToRun/ReadyToRunLanguage.cs

@ -212,17 +212,17 @@ namespace ICSharpCode.ILSpy.ReadyToRun @@ -212,17 +212,17 @@ namespace ICSharpCode.ILSpy.ReadyToRun
private class ReadyToRunAssemblyResolver : ILCompiler.Reflection.ReadyToRun.IAssemblyResolver
{
private LoadedAssembly loadedAssembly;
private Decompiler.Metadata.IAssemblyResolver assemblyResolver;
public ReadyToRunAssemblyResolver(LoadedAssembly loadedAssembly)
{
this.loadedAssembly = loadedAssembly;
assemblyResolver = loadedAssembly.GetAssemblyResolver();
}
public IAssemblyMetadata FindAssembly(MetadataReader metadataReader, AssemblyReferenceHandle assemblyReferenceHandle, string parentFile)
{
LoadedAssembly loadedAssembly = this.loadedAssembly.LookupReferencedAssembly(new Decompiler.Metadata.AssemblyReference(metadataReader, assemblyReferenceHandle));
PEReader reader = loadedAssembly?.GetPEFileOrNull()?.Reader;
PEFile module = assemblyResolver.Resolve(new Decompiler.Metadata.AssemblyReference(metadataReader, assemblyReferenceHandle));
PEReader reader = module?.Reader;
return reader == null ? null : new StandaloneAssemblyMetadata(reader);
}

11
ILSpy/Analyzers/AnalyzerScope.cs

@ -147,16 +147,13 @@ namespace ICSharpCode.ILSpy.Analyzers @@ -147,16 +147,13 @@ namespace ICSharpCode.ILSpy.Analyzers
continue;
if (checkedFiles.Contains(module))
continue;
var resolver = assembly.GetAssemblyResolver();
var resolver = assembly.GetAssemblyResolver(loadOnDemand: false);
foreach (var reference in module.AssemblyReferences)
{
using (LoadedAssembly.DisableAssemblyLoad(AssemblyList))
if (resolver.Resolve(reference) == curFile)
{
if (resolver.Resolve(reference) == curFile)
{
found = true;
break;
}
found = true;
break;
}
}
if (found && checkedFiles.Add(module))

170
ILSpy/AssemblyList.cs

@ -17,12 +17,13 @@ @@ -17,12 +17,13 @@
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Threading;
using System.Xml.Linq;
@ -39,8 +40,7 @@ namespace ICSharpCode.ILSpy @@ -39,8 +40,7 @@ namespace ICSharpCode.ILSpy
/// <summary>Dirty flag, used to mark modifications so that the list is saved later</summary>
bool dirty;
internal readonly ConcurrentDictionary<(string assemblyName, bool isWinRT, string targetFrameworkIdentifier), LoadedAssembly> assemblyLookupCache = new ConcurrentDictionary<(string assemblyName, bool isWinRT, string targetFrameworkIdentifier), LoadedAssembly>();
internal readonly ConcurrentDictionary<string, LoadedAssembly> moduleLookupCache = new ConcurrentDictionary<string, LoadedAssembly>();
readonly object lockObj = new object();
/// <summary>
/// The assemblies in this list.
@ -51,7 +51,14 @@ namespace ICSharpCode.ILSpy @@ -51,7 +51,14 @@ namespace ICSharpCode.ILSpy
/// Technically read accesses need locking when done on non-GUI threads... but whenever possible, use the
/// thread-safe <see cref="GetAssemblies()"/> method.
/// </remarks>
internal readonly ObservableCollection<LoadedAssembly> assemblies = new ObservableCollection<LoadedAssembly>();
readonly ObservableCollection<LoadedAssembly> assemblies = new ObservableCollection<LoadedAssembly>();
/// <summary>
/// Assembly lookup by filename.
/// Usually byFilename.Values == assemblies; but when an assembly is loaded by a background thread,
/// that assembly is added to byFilename immediately, and to assemblies only later on the main thread.
/// </summary>
readonly Dictionary<string, LoadedAssembly> byFilename = new Dictionary<string, LoadedAssembly>(StringComparer.OrdinalIgnoreCase);
public AssemblyList(string listName)
{
@ -81,17 +88,37 @@ namespace ICSharpCode.ILSpy @@ -81,17 +88,37 @@ namespace ICSharpCode.ILSpy
this.assemblies.AddRange(list.assemblies);
}
public event NotifyCollectionChangedEventHandler CollectionChanged {
add {
App.Current.Dispatcher.VerifyAccess();
this.assemblies.CollectionChanged += value;
}
remove {
App.Current.Dispatcher.VerifyAccess();
this.assemblies.CollectionChanged -= value;
}
}
/// <summary>
/// Gets the loaded assemblies. This method is thread-safe.
/// </summary>
public LoadedAssembly[] GetAssemblies()
{
lock (assemblies)
lock (lockObj)
{
return assemblies.ToArray();
}
}
public int Count {
get {
lock (lockObj)
{
return assemblies.Count;
}
}
}
/// <summary>
/// Saves this assembly list to XML.
/// </summary>
@ -111,9 +138,30 @@ namespace ICSharpCode.ILSpy @@ -111,9 +138,30 @@ namespace ICSharpCode.ILSpy
get { return listName; }
}
internal void Move(LoadedAssembly[] assembliesToMove, int index)
{
App.Current.Dispatcher.VerifyAccess();
lock (lockObj)
{
foreach (LoadedAssembly asm in assembliesToMove)
{
int nodeIndex = assemblies.IndexOf(asm);
Debug.Assert(nodeIndex >= 0);
if (nodeIndex < index)
index--;
assemblies.RemoveAt(nodeIndex);
}
Array.Reverse(assembliesToMove);
foreach (LoadedAssembly asm in assembliesToMove)
{
assemblies.Insert(index, asm);
}
}
}
void Assemblies_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
ClearCache();
Debug.Assert(Monitor.IsEntered(lockObj));
if (CollectionChangeHasEffectOnSave(e))
{
RefreshSave();
@ -155,10 +203,18 @@ namespace ICSharpCode.ILSpy @@ -155,10 +203,18 @@ namespace ICSharpCode.ILSpy
}
}
internal void ClearCache()
/// <summary>
/// Find an assembly that was previously opened.
/// </summary>
public LoadedAssembly FindAssembly(string file)
{
assemblyLookupCache.Clear();
moduleLookupCache.Clear();
file = Path.GetFullPath(file);
lock (lockObj)
{
if (byFilename.TryGetValue(file, out var asm))
return asm;
}
return null;
}
public LoadedAssembly Open(string assemblyUri, bool isAutoLoaded = false)
@ -170,25 +226,18 @@ namespace ICSharpCode.ILSpy @@ -170,25 +226,18 @@ namespace ICSharpCode.ILSpy
/// Opens an assembly from disk.
/// Returns the existing assembly node if it is already loaded.
/// </summary>
/// <remarks>
/// If called on the UI thread, the newly opened assembly is added to the list synchronously.
/// If called on another thread, the newly opened assembly won't be returned by GetAssemblies()
/// until the UI thread gets around to adding the assembly.
/// </remarks>
public LoadedAssembly OpenAssembly(string file, bool isAutoLoaded = false)
{
App.Current.Dispatcher.VerifyAccess();
file = Path.GetFullPath(file);
foreach (LoadedAssembly asm in this.assemblies)
{
if (file.Equals(asm.FileName, StringComparison.OrdinalIgnoreCase))
return asm;
}
var newAsm = new LoadedAssembly(this, file);
newAsm.IsAutoLoaded = isAutoLoaded;
lock (assemblies)
{
this.assemblies.Add(newAsm);
}
return newAsm;
return OpenAssembly(file, () => {
var newAsm = new LoadedAssembly(this, file);
newAsm.IsAutoLoaded = isAutoLoaded;
return newAsm;
});
}
/// <summary>
@ -196,21 +245,42 @@ namespace ICSharpCode.ILSpy @@ -196,21 +245,42 @@ namespace ICSharpCode.ILSpy
/// </summary>
public LoadedAssembly OpenAssembly(string file, Stream stream, bool isAutoLoaded = false)
{
App.Current.Dispatcher.VerifyAccess();
return OpenAssembly(file, () => {
var newAsm = new LoadedAssembly(this, file, stream: Task.FromResult(stream));
newAsm.IsAutoLoaded = isAutoLoaded;
return newAsm;
});
}
foreach (LoadedAssembly asm in this.assemblies)
LoadedAssembly OpenAssembly(string file, Func<LoadedAssembly> load)
{
file = Path.GetFullPath(file);
bool isUIThread = App.Current.Dispatcher.Thread == Thread.CurrentThread;
LoadedAssembly asm;
lock (lockObj)
{
if (file.Equals(asm.FileName, StringComparison.OrdinalIgnoreCase))
if (byFilename.TryGetValue(file, out asm))
return asm;
asm = load();
Debug.Assert(asm.FileName == file);
byFilename.Add(asm.FileName, asm);
if (isUIThread)
{
assemblies.Add(asm);
}
}
var newAsm = new LoadedAssembly(this, file, stream: Task.FromResult(stream));
newAsm.IsAutoLoaded = isAutoLoaded;
lock (assemblies)
if (!isUIThread)
{
this.assemblies.Add(newAsm);
App.Current.Dispatcher.BeginInvoke((Action)delegate () {
lock (lockObj)
{
assemblies.Add(asm);
}
}, DispatcherPriority.Normal);
}
return newAsm;
return asm;
}
/// <summary>
@ -221,20 +291,22 @@ namespace ICSharpCode.ILSpy @@ -221,20 +291,22 @@ namespace ICSharpCode.ILSpy
{
App.Current.Dispatcher.VerifyAccess();
file = Path.GetFullPath(file);
var target = this.assemblies.FirstOrDefault(asm => file.Equals(asm.FileName, StringComparison.OrdinalIgnoreCase));
if (target == null)
return null;
var index = this.assemblies.IndexOf(target);
var newAsm = new LoadedAssembly(this, file, stream: Task.FromResult(stream));
newAsm.IsAutoLoaded = target.IsAutoLoaded;
lock (assemblies)
lock (lockObj)
{
this.assemblies.Remove(target);
this.assemblies.Insert(index, newAsm);
if (!byFilename.TryGetValue(file, out LoadedAssembly target))
return null;
int index = this.assemblies.IndexOf(target);
if (index < 0)
return null;
var newAsm = new LoadedAssembly(this, file, stream: Task.FromResult(stream));
newAsm.IsAutoLoaded = target.IsAutoLoaded;
Debug.Assert(newAsm.FileName == file);
byFilename[file] = newAsm;
this.assemblies[index] = newAsm;
return newAsm;
}
return newAsm;
}
public LoadedAssembly ReloadAssembly(string file)
@ -251,7 +323,10 @@ namespace ICSharpCode.ILSpy @@ -251,7 +323,10 @@ namespace ICSharpCode.ILSpy
public LoadedAssembly ReloadAssembly(LoadedAssembly target)
{
App.Current.Dispatcher.VerifyAccess();
var index = this.assemblies.IndexOf(target);
if (index < 0)
return null;
var newAsm = new LoadedAssembly(this, target.FileName, pdbFileName: target.PdbFileName);
newAsm.IsAutoLoaded = target.IsAutoLoaded;
lock (assemblies)
@ -268,6 +343,7 @@ namespace ICSharpCode.ILSpy @@ -268,6 +343,7 @@ namespace ICSharpCode.ILSpy
lock (assemblies)
{
assemblies.Remove(assembly);
byFilename.Remove(assembly.FileName);
}
RequestGC();
}

7
ILSpy/AssemblyListManager.cs

@ -16,7 +16,6 @@ @@ -16,7 +16,6 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Xml.Linq;
@ -165,7 +164,7 @@ namespace ICSharpCode.ILSpy @@ -165,7 +164,7 @@ namespace ICSharpCode.ILSpy
if (!AssemblyLists.Contains(ManageAssemblyListsViewModel.DotNet4List))
{
AssemblyList dotnet4 = ManageAssemblyListsViewModel.CreateDefaultList(ManageAssemblyListsViewModel.DotNet4List);
if (dotnet4.assemblies.Count > 0)
if (dotnet4.Count > 0)
{
CreateList(dotnet4);
}
@ -174,7 +173,7 @@ namespace ICSharpCode.ILSpy @@ -174,7 +173,7 @@ namespace ICSharpCode.ILSpy
if (!AssemblyLists.Contains(ManageAssemblyListsViewModel.DotNet35List))
{
AssemblyList dotnet35 = ManageAssemblyListsViewModel.CreateDefaultList(ManageAssemblyListsViewModel.DotNet35List);
if (dotnet35.assemblies.Count > 0)
if (dotnet35.Count > 0)
{
CreateList(dotnet35);
}
@ -183,7 +182,7 @@ namespace ICSharpCode.ILSpy @@ -183,7 +182,7 @@ namespace ICSharpCode.ILSpy
if (!AssemblyLists.Contains(ManageAssemblyListsViewModel.ASPDotNetMVC3List))
{
AssemblyList mvc = ManageAssemblyListsViewModel.CreateDefaultList(ManageAssemblyListsViewModel.ASPDotNetMVC3List);
if (mvc.assemblies.Count > 0)
if (mvc.Count > 0)
{
CreateList(mvc);
}

141
ILSpy/Languages/CSharpLanguage.cs

@ -404,86 +404,83 @@ namespace ICSharpCode.ILSpy @@ -404,86 +404,83 @@ namespace ICSharpCode.ILSpy
base.DecompileAssembly(assembly, output, options);
// don't automatically load additional assemblies when an assembly node is selected in the tree view
using (options.FullDecompilation ? null : LoadedAssembly.DisableAssemblyLoad(assembly.AssemblyList))
IAssemblyResolver assemblyResolver = assembly.GetAssemblyResolver(loadOnDemand: options.FullDecompilation);
var typeSystem = new DecompilerTypeSystem(module, assemblyResolver, options.DecompilerSettings);
var globalType = typeSystem.MainModule.TypeDefinitions.FirstOrDefault();
if (globalType != null)
{
IAssemblyResolver assemblyResolver = assembly.GetAssemblyResolver();
var typeSystem = new DecompilerTypeSystem(module, assemblyResolver, options.DecompilerSettings);
var globalType = typeSystem.MainModule.TypeDefinitions.FirstOrDefault();
if (globalType != null)
output.Write("// Global type: ");
output.WriteReference(globalType, globalType.FullName);
output.WriteLine();
}
var metadata = module.Metadata;
var corHeader = module.Reader.PEHeaders.CorHeader;
var entrypointHandle = MetadataTokenHelpers.EntityHandleOrNil(corHeader.EntryPointTokenOrRelativeVirtualAddress);
if (!entrypointHandle.IsNil && entrypointHandle.Kind == HandleKind.MethodDefinition)
{
var entrypoint = typeSystem.MainModule.ResolveMethod(entrypointHandle, new Decompiler.TypeSystem.GenericContext());
if (entrypoint != null)
{
output.Write("// Global type: ");
output.WriteReference(globalType, globalType.FullName);
output.Write("// Entry point: ");
output.WriteReference(entrypoint, entrypoint.DeclaringType.FullName + "." + entrypoint.Name);
output.WriteLine();
}
var metadata = module.Metadata;
var corHeader = module.Reader.PEHeaders.CorHeader;
var entrypointHandle = MetadataTokenHelpers.EntityHandleOrNil(corHeader.EntryPointTokenOrRelativeVirtualAddress);
if (!entrypointHandle.IsNil && entrypointHandle.Kind == HandleKind.MethodDefinition)
{
var entrypoint = typeSystem.MainModule.ResolveMethod(entrypointHandle, new Decompiler.TypeSystem.GenericContext());
if (entrypoint != null)
{
output.Write("// Entry point: ");
output.WriteReference(entrypoint, entrypoint.DeclaringType.FullName + "." + entrypoint.Name);
output.WriteLine();
}
}
output.WriteLine("// Architecture: " + GetPlatformDisplayName(module));
if ((corHeader.Flags & System.Reflection.PortableExecutable.CorFlags.ILOnly) == 0)
{
output.WriteLine("// This assembly contains unmanaged code.");
}
string runtimeName = GetRuntimeDisplayName(module);
if (runtimeName != null)
{
output.WriteLine("// Runtime: " + runtimeName);
}
if ((corHeader.Flags & System.Reflection.PortableExecutable.CorFlags.StrongNameSigned) != 0)
{
output.WriteLine("// This assembly is signed with a strong name key.");
}
if (module.Reader.ReadDebugDirectory().Any(d => d.Type == DebugDirectoryEntryType.Reproducible))
{
output.WriteLine("// This assembly was compiled using the /deterministic option.");
}
if (metadata.IsAssembly)
{
var asm = metadata.GetAssemblyDefinition();
if (asm.HashAlgorithm != AssemblyHashAlgorithm.None)
output.WriteLine("// Hash algorithm: " + asm.HashAlgorithm.ToString().ToUpper());
if (!asm.PublicKey.IsNil)
{
output.Write("// Public key: ");
var reader = metadata.GetBlobReader(asm.PublicKey);
while (reader.RemainingBytes > 0)
output.Write(reader.ReadByte().ToString("x2"));
output.WriteLine();
}
}
var debugInfo = assembly.GetDebugInfoOrNull();
if (debugInfo != null)
}
output.WriteLine("// Architecture: " + GetPlatformDisplayName(module));
if ((corHeader.Flags & System.Reflection.PortableExecutable.CorFlags.ILOnly) == 0)
{
output.WriteLine("// This assembly contains unmanaged code.");
}
string runtimeName = GetRuntimeDisplayName(module);
if (runtimeName != null)
{
output.WriteLine("// Runtime: " + runtimeName);
}
if ((corHeader.Flags & System.Reflection.PortableExecutable.CorFlags.StrongNameSigned) != 0)
{
output.WriteLine("// This assembly is signed with a strong name key.");
}
if (module.Reader.ReadDebugDirectory().Any(d => d.Type == DebugDirectoryEntryType.Reproducible))
{
output.WriteLine("// This assembly was compiled using the /deterministic option.");
}
if (metadata.IsAssembly)
{
var asm = metadata.GetAssemblyDefinition();
if (asm.HashAlgorithm != AssemblyHashAlgorithm.None)
output.WriteLine("// Hash algorithm: " + asm.HashAlgorithm.ToString().ToUpper());
if (!asm.PublicKey.IsNil)
{
output.WriteLine("// Debug info: " + debugInfo.Description);
output.Write("// Public key: ");
var reader = metadata.GetBlobReader(asm.PublicKey);
while (reader.RemainingBytes > 0)
output.Write(reader.ReadByte().ToString("x2"));
output.WriteLine();
}
output.WriteLine();
}
var debugInfo = assembly.GetDebugInfoOrNull();
if (debugInfo != null)
{
output.WriteLine("// Debug info: " + debugInfo.Description);
}
output.WriteLine();
CSharpDecompiler decompiler = new CSharpDecompiler(typeSystem, options.DecompilerSettings);
decompiler.CancellationToken = options.CancellationToken;
if (options.EscapeInvalidIdentifiers)
{
decompiler.AstTransforms.Add(new EscapeInvalidIdentifiers());
}
SyntaxTree st;
if (options.FullDecompilation)
{
st = decompiler.DecompileWholeModuleAsSingleFile();
}
else
{
st = decompiler.DecompileModuleAndAssemblyAttributes();
}
WriteCode(output, options.DecompilerSettings, st, decompiler.TypeSystem);
CSharpDecompiler decompiler = new CSharpDecompiler(typeSystem, options.DecompilerSettings);
decompiler.CancellationToken = options.CancellationToken;
if (options.EscapeInvalidIdentifiers)
{
decompiler.AstTransforms.Add(new EscapeInvalidIdentifiers());
}
SyntaxTree st;
if (options.FullDecompilation)
{
st = decompiler.DecompileWholeModuleAsSingleFile();
}
else
{
st = decompiler.DecompileModuleAndAssemblyAttributes();
}
WriteCode(output, options.DecompilerSettings, st, decompiler.TypeSystem);
return null;
}
}

25
ILSpy/Languages/ILLanguage.cs

@ -172,22 +172,19 @@ namespace ICSharpCode.ILSpy @@ -172,22 +172,19 @@ namespace ICSharpCode.ILSpy
}
// don't automatically load additional assemblies when an assembly node is selected in the tree view
using (options.FullDecompilation ? null : LoadedAssembly.DisableAssemblyLoad(assembly.AssemblyList))
dis.AssemblyResolver = module.GetAssemblyResolver(loadOnDemand: options.FullDecompilation);
dis.DebugInfo = module.GetDebugInfoOrNull();
if (options.FullDecompilation)
dis.WriteAssemblyReferences(metadata);
if (metadata.IsAssembly)
dis.WriteAssemblyHeader(module);
output.WriteLine();
dis.WriteModuleHeader(module);
if (options.FullDecompilation)
{
dis.AssemblyResolver = module.GetAssemblyResolver();
dis.DebugInfo = module.GetDebugInfoOrNull();
if (options.FullDecompilation)
dis.WriteAssemblyReferences(metadata);
if (metadata.IsAssembly)
dis.WriteAssemblyHeader(module);
output.WriteLine();
dis.WriteModuleHeader(module);
if (options.FullDecompilation)
{
output.WriteLine();
output.WriteLine();
dis.WriteModuleContents(module);
}
output.WriteLine();
dis.WriteModuleContents(module);
}
return null;
}

423
ILSpy/LoadedAssembly.cs

@ -26,7 +26,6 @@ using System.Reflection.PortableExecutable; @@ -26,7 +26,6 @@ using System.Reflection.PortableExecutable;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Threading;
using ICSharpCode.Decompiler.DebugInfo;
using ICSharpCode.Decompiler.Metadata;
@ -95,7 +94,6 @@ namespace ICSharpCode.ILSpy @@ -95,7 +94,6 @@ namespace ICSharpCode.ILSpy
this.loadingTask = Task.Run(() => LoadAsync(stream)); // requires that this.fileName is set
this.shortName = Path.GetFileNameWithoutExtension(fileName);
this.resolver = new MyAssemblyResolver(this);
}
public LoadedAssembly(LoadedAssembly bundle, string fileName, Task<Stream> stream, IAssemblyResolver assemblyResolver = null)
@ -386,241 +384,118 @@ namespace ICSharpCode.ILSpy @@ -386,241 +384,118 @@ namespace ICSharpCode.ILSpy
return debugInfoProvider;
}
[ThreadStatic]
static int assemblyLoadDisableCount;
public static IDisposable DisableAssemblyLoad(AssemblyList assemblyList)
{
assemblyLoadDisableCount++;
return new DecrementAssemblyLoadDisableCount(assemblyList);
}
public static IDisposable DisableAssemblyLoad()
{
assemblyLoadDisableCount++;
return new DecrementAssemblyLoadDisableCount(MainWindow.Instance.CurrentAssemblyList);
}
sealed class DecrementAssemblyLoadDisableCount : IDisposable
{
bool disposed;
AssemblyList assemblyList;
public DecrementAssemblyLoadDisableCount(AssemblyList assemblyList)
{
this.assemblyList = assemblyList;
}
public void Dispose()
{
if (!disposed)
{
disposed = true;
assemblyLoadDisableCount--;
// clear the lookup cache since we might have stored the lookups failed due to DisableAssemblyLoad()
assemblyList.ClearCache();
}
}
}
sealed class MyAssemblyResolver : IAssemblyResolver
{
readonly LoadedAssembly parent;
readonly bool loadOnDemand;
public MyAssemblyResolver(LoadedAssembly parent)
readonly IAssemblyResolver providedAssemblyResolver;
readonly AssemblyList assemblyList;
readonly LoadedAssembly[] alreadyLoadedAssemblies;
readonly Task<string> tfmTask;
readonly ReferenceLoadInfo referenceLoadInfo;
public MyAssemblyResolver(LoadedAssembly parent, bool loadOnDemand)
{
this.parent = parent;
this.loadOnDemand = loadOnDemand;
this.providedAssemblyResolver = parent.providedAssemblyResolver;
this.assemblyList = parent.assemblyList;
// 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();
// 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();
this.referenceLoadInfo = parent.LoadedAssemblyReferencesInfo;
}
public PEFile Resolve(IAssemblyReference reference)
{
var module = parent.providedAssemblyResolver?.Resolve(reference);
if (module != null)
return module;
return parent.LookupReferencedAssembly(reference)?.GetPEFileOrNull();
}
public PEFile ResolveModule(PEFile mainModule, string moduleName)
{
var module = parent.providedAssemblyResolver?.ResolveModule(mainModule, moduleName);
if (module != null)
return module;
return parent.LookupReferencedModule(mainModule, moduleName)?.GetPEFileOrNull();
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
/// 2) try to find match in search paths
/// 3) if a.deps.json is found: search %USERPROFILE%/.nuget/packages/* as well
/// 4) look in /dotnet/shared/{runtime-pack}/{closest-version}
/// 5) if the version is retargetable or all zeros or ones, search C:\Windows\Microsoft.NET\Framework64\v4.0.30319
/// 6) For "mscorlib.dll" we use the exact same assembly with which ILSpy runs
/// 7) Search the GAC
/// 8) search C:\Windows\Microsoft.NET\Framework64\v4.0.30319
/// 9) try to find match by asm name (no tfm/version) in loaded assemblies
/// </summary>
public async Task<PEFile> ResolveAsync(IAssemblyReference reference)
{
if (parent.providedAssemblyResolver != null)
PEFile module;
// 0) if we're inside a package, look for filename.dll in parent directories
if (providedAssemblyResolver != null)
{
var module = await parent.providedAssemblyResolver.ResolveAsync(reference).ConfigureAwait(false);
module = await providedAssemblyResolver.ResolveAsync(reference).ConfigureAwait(false);
if (module != null)
return module;
}
var asm = parent.LookupReferencedAssembly(reference);
if (asm != null)
{
return await asm.GetPEFileOrNullAsync().ConfigureAwait(false);
}
return null;
}
public async Task<PEFile> ResolveModuleAsync(PEFile mainModule, string moduleName)
{
if (parent.providedAssemblyResolver != null)
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)
{
var module = await parent.providedAssemblyResolver.ResolveModuleAsync(mainModule, moduleName).ConfigureAwait(false);
if (module != null)
return module;
lookup = await CreateLoadedAssemblyLookupAsync(shortNames: isWinRT).ConfigureAwait(false);
lookup = LazyInit.GetOrSet(ref isWinRT ? ref asmLookupByShortName : ref asmLookupByFullName, lookup);
}
var asm = parent.LookupReferencedModule(mainModule, moduleName);
if (asm != null)
if (lookup.TryGetValue(key, out module))
{
return await asm.GetPEFileOrNullAsync().ConfigureAwait(false);
referenceLoadInfo.AddMessageOnce(reference.FullName, MessageKind.Info, "Success - Found in Assembly List");
return module;
}
return null;
}
}
readonly MyAssemblyResolver resolver;
public IAssemblyResolver GetAssemblyResolver()
{
return resolver;
}
private MyUniversalResolver GetUniversalResolver()
{
return LazyInitializer.EnsureInitialized(ref this.universalResolver, () => new MyUniversalResolver(this));
}
string file = parent.GetUniversalResolver().FindAssemblyFile(reference);
public AssemblyReferenceClassifier GetAssemblyReferenceClassifier()
{
return GetUniversalResolver();
}
/// <summary>
/// Returns the debug info for this assembly. Returns null in case of load errors or no debug info is available.
/// </summary>
public IDebugInfoProvider GetDebugInfoOrNull()
{
if (GetPEFileOrNull() == null)
return null;
return debugInfoProvider;
}
public LoadedAssembly LookupReferencedAssembly(IAssemblyReference reference)
{
if (reference == null)
throw new ArgumentNullException(nameof(reference));
var tfm = GetTargetFrameworkIdAsync().Result;
if (reference.IsWindowsRuntime)
{
return assemblyList.assemblyLookupCache.GetOrAdd((reference.Name, true, tfm), key => LookupReferencedAssemblyInternal(reference, true, tfm));
}
else
{
return assemblyList.assemblyLookupCache.GetOrAdd((reference.FullName, false, tfm), key => LookupReferencedAssemblyInternal(reference, false, tfm));
}
}
public LoadedAssembly LookupReferencedModule(PEFile mainModule, string moduleName)
{
if (mainModule == null)
throw new ArgumentNullException(nameof(mainModule));
if (moduleName == null)
throw new ArgumentNullException(nameof(moduleName));
return assemblyList.moduleLookupCache.GetOrAdd(mainModule.FileName + ";" + moduleName, _ => LookupReferencedModuleInternal(mainModule, moduleName));
}
class MyUniversalResolver : UniversalAssemblyResolver
{
public MyUniversalResolver(LoadedAssembly assembly)
: base(assembly.FileName, false, assembly.GetTargetFrameworkIdAsync().Result, PEStreamOptions.PrefetchEntireImage, DecompilerSettingsPanel.CurrentDecompilerSettings.ApplyWindowsRuntimeProjections ? MetadataReaderOptions.ApplyWindowsRuntimeProjections : MetadataReaderOptions.None)
{
}
}
static readonly Dictionary<string, LoadedAssembly> loadingAssemblies = new Dictionary<string, LoadedAssembly>();
MyUniversalResolver universalResolver;
/// <summary>
/// 0) if we're inside a package, look for filename.dll in parent directories
/// (this step already happens in MyAssemblyResolver; not in LookupReferencedAssembly)
/// 1) try to find exact match by tfm + full asm name in loaded assemblies
/// 2) try to find match in search paths
/// 3) if a.deps.json is found: search %USERPROFILE%/.nuget/packages/* as well
/// 4) look in /dotnet/shared/{runtime-pack}/{closest-version}
/// 5) if the version is retargetable or all zeros or ones, search C:\Windows\Microsoft.NET\Framework64\v4.0.30319
/// 6) For "mscorlib.dll" we use the exact same assembly with which ILSpy runs
/// 7) Search the GAC
/// 8) search C:\Windows\Microsoft.NET\Framework64\v4.0.30319
/// 9) try to find match by asm name (no tfm/version) in loaded assemblies
/// </summary>
LoadedAssembly LookupReferencedAssemblyInternal(IAssemblyReference fullName, bool isWinRT, string tfm)
{
string key = tfm + ";" + (isWinRT ? fullName.Name : fullName.FullName);
string file;
LoadedAssembly asm;
lock (loadingAssemblies)
{
foreach (LoadedAssembly loaded in assemblyList.GetAssemblies())
if (file != null)
{
try
// Load assembly from disk
LoadedAssembly asm;
if (loadOnDemand)
{
var module = loaded.GetPEFileOrNull();
var reader = module?.Metadata;
if (reader == null || !reader.IsAssembly)
continue;
var asmDef = reader.GetAssemblyDefinition();
var asmDefName = loaded.GetTargetFrameworkIdAsync().Result + ";"
+ (isWinRT ? reader.GetString(asmDef.Name) : reader.GetFullAssemblyName());
if (key.Equals(asmDefName, StringComparison.OrdinalIgnoreCase))
{
LoadedAssemblyReferencesInfo.AddMessageOnce(fullName.FullName, MessageKind.Info, "Success - Found in Assembly List");
return loaded;
}
asm = assemblyList.OpenAssembly(file, isAutoLoaded: true);
}
catch (BadImageFormatException)
else
{
continue;
asm = assemblyList.FindAssembly(file);
}
}
file = GetUniversalResolver().FindAssemblyFile(fullName);
foreach (LoadedAssembly loaded in assemblyList.GetAssemblies())
{
if (loaded.FileName.Equals(file, StringComparison.OrdinalIgnoreCase))
if (asm != null)
{
return loaded;
referenceLoadInfo.AddMessage(reference.ToString(), MessageKind.Info, "Success - Loading from: " + file);
return await asm.GetPEFileOrNullAsync().ConfigureAwait(false);
}
}
if (file != null && loadingAssemblies.TryGetValue(file, out asm))
return asm;
if (assemblyLoadDisableCount > 0)
return null;
if (file != null)
{
LoadedAssemblyReferencesInfo.AddMessage(fullName.ToString(), MessageKind.Info, "Success - Loading from: " + file);
asm = new LoadedAssembly(assemblyList, file) { IsAutoLoaded = true };
}
else
{
// Assembly not found; try to find a similar-enough already-loaded assembly
var candidates = new List<(LoadedAssembly assembly, Version version)>();
foreach (LoadedAssembly loaded in assemblyList.GetAssemblies())
foreach (LoadedAssembly loaded in alreadyLoadedAssemblies)
{
var module = loaded.GetPEFileOrNull();
module = await loaded.GetPEFileOrNullAsync().ConfigureAwait(false);
var reader = module?.Metadata;
if (reader == null || !reader.IsAssembly)
continue;
var asmDef = reader.GetAssemblyDefinition();
var asmDefName = reader.GetString(asmDef.Name);
if (fullName.Name.Equals(asmDefName, StringComparison.OrdinalIgnoreCase))
if (reference.Name.Equals(asmDefName, StringComparison.OrdinalIgnoreCase))
{
candidates.Add((loaded, asmDef.Version));
}
@ -628,99 +503,135 @@ namespace ICSharpCode.ILSpy @@ -628,99 +503,135 @@ namespace ICSharpCode.ILSpy
if (candidates.Count == 0)
{
LoadedAssemblyReferencesInfo.AddMessageOnce(fullName.ToString(), MessageKind.Error, "Could not find reference: " + fullName);
referenceLoadInfo.AddMessageOnce(reference.ToString(), MessageKind.Error, "Could not find reference: " + reference);
return null;
}
candidates.SortBy(c => c.version);
var bestCandidate = candidates.FirstOrDefault(c => c.version >= fullName.Version).assembly ?? candidates.Last().assembly;
LoadedAssemblyReferencesInfo.AddMessageOnce(fullName.ToString(), MessageKind.Info, "Success - Found in Assembly List with different TFM or version: " + bestCandidate.fileName);
return bestCandidate;
var bestCandidate = candidates.FirstOrDefault(c => c.version >= reference.Version).assembly ?? candidates.Last().assembly;
referenceLoadInfo.AddMessageOnce(reference.ToString(), MessageKind.Info, "Success - Found in Assembly List with different TFM or version: " + bestCandidate.fileName);
return await bestCandidate.GetPEFileOrNullAsync().ConfigureAwait(false);
}
loadingAssemblies.Add(file, asm);
}
App.Current.Dispatcher.BeginInvoke((Action)delegate () {
lock (assemblyList.assemblies)
{
assemblyList.assemblies.Add(asm);
}
lock (loadingAssemblies)
{
loadingAssemblies.Remove(file);
}
}, DispatcherPriority.Normal);
return asm;
}
LoadedAssembly LookupReferencedModuleInternal(PEFile mainModule, string moduleName)
{
string file;
LoadedAssembly asm;
lock (loadingAssemblies)
private async Task<Dictionary<string, PEFile>> CreateLoadedAssemblyLookupAsync(bool shortNames)
{
foreach (LoadedAssembly loaded in assemblyList.GetAssemblies())
var result = new Dictionary<string, PEFile>(StringComparer.OrdinalIgnoreCase);
foreach (LoadedAssembly loaded in alreadyLoadedAssemblies)
{
var reader = loaded.GetPEFileOrNull()?.Metadata;
if (reader == null || reader.IsAssembly)
continue;
var moduleDef = reader.GetModuleDefinition();
if (moduleName.Equals(reader.GetString(moduleDef.Name), StringComparison.OrdinalIgnoreCase))
try
{
LoadedAssemblyReferencesInfo.AddMessageOnce(moduleName, MessageKind.Info, "Success - Found in Assembly List");
return loaded;
var module = await loaded.GetPEFileOrNullAsync().ConfigureAwait(false);
var reader = module?.Metadata;
if (reader == null || !reader.IsAssembly)
continue;
var asmDef = reader.GetAssemblyDefinition();
string tfm = await loaded.GetTargetFrameworkIdAsync();
string key = tfm + ";"
+ (shortNames ? reader.GetString(asmDef.Name) : reader.GetFullAssemblyName());
if (!result.ContainsKey(key))
{
result.Add(key, module);
}
}
catch (BadImageFormatException)
{
continue;
}
}
return result;
}
file = Path.Combine(Path.GetDirectoryName(mainModule.FileName), moduleName);
if (!File.Exists(file))
return null;
public PEFile ResolveModule(PEFile mainModule, string moduleName)
{
return ResolveModuleAsync(mainModule, moduleName).GetAwaiter().GetResult();
}
foreach (LoadedAssembly loaded in assemblyList.GetAssemblies())
public async Task<PEFile> ResolveModuleAsync(PEFile mainModule, string moduleName)
{
if (providedAssemblyResolver != null)
{
if (loaded.FileName.Equals(file, StringComparison.OrdinalIgnoreCase))
{
return loaded;
}
var module = await providedAssemblyResolver.ResolveModuleAsync(mainModule, moduleName).ConfigureAwait(false);
if (module != null)
return module;
}
if (file != null && loadingAssemblies.TryGetValue(file, out asm))
return asm;
if (assemblyLoadDisableCount > 0)
return null;
if (file != null)
string file = Path.Combine(Path.GetDirectoryName(mainModule.FileName), moduleName);
if (File.Exists(file))
{
LoadedAssemblyReferencesInfo.AddMessage(moduleName, MessageKind.Info, "Success - Loading from: " + file);
asm = new LoadedAssembly(assemblyList, file) { IsAutoLoaded = true };
// Load module from disk
LoadedAssembly asm;
if (loadOnDemand)
{
asm = assemblyList.OpenAssembly(file, isAutoLoaded: true);
}
else
{
asm = assemblyList.FindAssembly(file);
}
if (asm != null)
{
return await asm.GetPEFileOrNullAsync().ConfigureAwait(false);
}
}
else
{
LoadedAssemblyReferencesInfo.AddMessageOnce(moduleName, MessageKind.Error, "Could not find reference: " + moduleName);
return null;
// Module does not exist on disk, look for one with a matching name in the assemblylist:
foreach (LoadedAssembly loaded in alreadyLoadedAssemblies)
{
var module = await loaded.GetPEFileOrNullAsync().ConfigureAwait(false);
var reader = module?.Metadata;
if (reader == null || reader.IsAssembly)
continue;
var moduleDef = reader.GetModuleDefinition();
if (moduleName.Equals(reader.GetString(moduleDef.Name), StringComparison.OrdinalIgnoreCase))
{
referenceLoadInfo.AddMessageOnce(moduleName, MessageKind.Info, "Success - Found in Assembly List");
return module;
}
}
}
loadingAssemblies.Add(file, asm);
return null;
}
App.Current.Dispatcher.BeginInvoke((Action)delegate () {
lock (assemblyList.assemblies)
{
assemblyList.assemblies.Add(asm);
}
lock (loadingAssemblies)
{
loadingAssemblies.Remove(file);
}
});
return asm;
}
[Obsolete("Use GetPEFileAsync() or GetLoadResultAsync() instead")]
public Task ContinueWhenLoaded(Action<Task<PEFile>> onAssemblyLoaded, TaskScheduler taskScheduler)
public IAssemblyResolver GetAssemblyResolver(bool loadOnDemand = true)
{
return new MyAssemblyResolver(this, loadOnDemand);
}
private MyUniversalResolver GetUniversalResolver()
{
return this.GetPEFileAsync().ContinueWith(onAssemblyLoaded, default(CancellationToken), TaskContinuationOptions.RunContinuationsAsynchronously, taskScheduler);
return LazyInitializer.EnsureInitialized(ref this.universalResolver, () => new MyUniversalResolver(this));
}
public AssemblyReferenceClassifier GetAssemblyReferenceClassifier()
{
return GetUniversalResolver();
}
/// <summary>
/// Returns the debug info for this assembly. Returns null in case of load errors or no debug info is available.
/// </summary>
public IDebugInfoProvider GetDebugInfoOrNull()
{
if (GetPEFileOrNull() == null)
return null;
return debugInfoProvider;
}
class MyUniversalResolver : UniversalAssemblyResolver
{
public MyUniversalResolver(LoadedAssembly assembly)
: base(assembly.FileName, false, assembly.GetTargetFrameworkIdAsync().Result, PEStreamOptions.PrefetchEntireImage, DecompilerSettingsPanel.CurrentDecompilerSettings.ApplyWindowsRuntimeProjections ? MetadataReaderOptions.ApplyWindowsRuntimeProjections : MetadataReaderOptions.None)
{
}
}
MyUniversalResolver universalResolver;
/// <summary>
/// Wait until the assembly is loaded.
/// Throws an AggregateException when loading the assembly fails.

9
ILSpy/LoadedAssemblyExtensions.cs

@ -1,10 +1,5 @@ @@ -1,10 +1,5 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using ICSharpCode.Decompiler.DebugInfo;
using ICSharpCode.Decompiler.Metadata;
@ -31,9 +26,9 @@ namespace ICSharpCode.ILSpy @@ -31,9 +26,9 @@ namespace ICSharpCode.ILSpy
return Mono.Cecil.ModuleDefinition.ReadModule(new UnmanagedMemoryStream(image.Pointer, image.Length));
}
public static IAssemblyResolver GetAssemblyResolver(this PEFile file)
public static IAssemblyResolver GetAssemblyResolver(this PEFile file, bool loadOnDemand = true)
{
return GetLoadedAssembly(file).GetAssemblyResolver();
return GetLoadedAssembly(file).GetAssemblyResolver(loadOnDemand);
}
public static IDebugInfoProvider GetDebugInfoOrNull(this PEFile file)

2
ILSpy/MainWindow.xaml.cs

@ -749,7 +749,7 @@ namespace ICSharpCode.ILSpy @@ -749,7 +749,7 @@ namespace ICSharpCode.ILSpy
history.Clear();
this.assemblyList = assemblyList;
assemblyList.assemblies.CollectionChanged += assemblyList_Assemblies_CollectionChanged;
assemblyList.CollectionChanged += assemblyList_Assemblies_CollectionChanged;
assemblyListTreeNode = new AssemblyListTreeNode(assemblyList);
assemblyListTreeNode.FilterSettings = sessionSettings.FilterSettings.Clone();

41
ILSpy/TreeNodes/AssemblyListTreeNode.cs

@ -17,7 +17,6 @@ @@ -17,7 +17,6 @@
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using System.Windows;
@ -45,17 +44,17 @@ namespace ICSharpCode.ILSpy.TreeNodes @@ -45,17 +44,17 @@ namespace ICSharpCode.ILSpy.TreeNodes
public AssemblyListTreeNode(AssemblyList assemblyList)
{
this.assemblyList = assemblyList ?? throw new ArgumentNullException(nameof(assemblyList));
BindToObservableCollection(assemblyList.assemblies);
BindToObservableCollection(assemblyList);
}
public override object Text {
get { return assemblyList.ListName; }
}
void BindToObservableCollection(ObservableCollection<LoadedAssembly> collection)
void BindToObservableCollection(AssemblyList collection)
{
this.Children.Clear();
this.Children.AddRange(collection.Select(a => new AssemblyTreeNode(a)));
this.Children.AddRange(collection.GetAssemblies().Select(a => new AssemblyTreeNode(a)));
collection.CollectionChanged += delegate (object sender, NotifyCollectionChangedEventArgs e) {
switch (e.Action)
{
@ -70,7 +69,7 @@ namespace ICSharpCode.ILSpy.TreeNodes @@ -70,7 +69,7 @@ namespace ICSharpCode.ILSpy.TreeNodes
throw new NotImplementedException();
case NotifyCollectionChangedAction.Reset:
this.Children.Clear();
this.Children.AddRange(collection.Select(a => new AssemblyTreeNode(a)));
this.Children.AddRange(collection.GetAssemblies().Select(a => new AssemblyTreeNode(a)));
break;
default:
throw new NotSupportedException("Invalid value for NotifyCollectionChangedAction");
@ -99,29 +98,15 @@ namespace ICSharpCode.ILSpy.TreeNodes @@ -99,29 +98,15 @@ namespace ICSharpCode.ILSpy.TreeNodes
files = e.Data.GetData(DataFormats.FileDrop) as string[];
if (files != null)
{
lock (assemblyList.assemblies)
{
var assemblies = files
.Where(file => file != null)
.Select(file => assemblyList.OpenAssembly(file))
.Where(asm => asm != null)
.Distinct()
.ToArray();
foreach (LoadedAssembly asm in assemblies)
{
int nodeIndex = assemblyList.assemblies.IndexOf(asm);
if (nodeIndex < index)
index--;
assemblyList.assemblies.RemoveAt(nodeIndex);
}
Array.Reverse(assemblies);
foreach (LoadedAssembly asm in assemblies)
{
assemblyList.assemblies.Insert(index, asm);
}
var nodes = assemblies.SelectArray(MainWindow.Instance.FindTreeNode);
MainWindow.Instance.SelectNodes(nodes);
}
var assemblies = files
.Where(file => file != null)
.Select(file => assemblyList.OpenAssembly(file))
.Where(asm => asm != null)
.Distinct()
.ToArray();
assemblyList.Move(assemblies, index);
var nodes = assemblies.SelectArray(MainWindow.Instance.FindTreeNode);
MainWindow.Instance.SelectNodes(nodes);
}
}

9
ILSpy/TreeNodes/ModuleReferenceTreeNode.cs

@ -71,8 +71,13 @@ namespace ICSharpCode.ILSpy.TreeNodes @@ -71,8 +71,13 @@ namespace ICSharpCode.ILSpy.TreeNodes
var assemblyListNode = parentAssembly.Parent as AssemblyListTreeNode;
if (assemblyListNode != null && containsMetadata)
{
assemblyListNode.Select(assemblyListNode.FindAssemblyNode(parentAssembly.LoadedAssembly.LookupReferencedModule(parentAssembly.LoadedAssembly.GetPEFileOrNull(), metadata.GetString(reference.Name))));
e.Handled = true;
var resolver = parentAssembly.LoadedAssembly.GetAssemblyResolver();
var mainModule = parentAssembly.LoadedAssembly.GetPEFileOrNull();
if (mainModule != null)
{
assemblyListNode.Select(assemblyListNode.FindAssemblyNode(resolver.ResolveModule(mainModule, metadata.GetString(reference.Name))));
e.Handled = true;
}
}
}

2
ILSpy/ViewModels/ManageAssemblyListsViewModel.cs

@ -358,7 +358,7 @@ namespace ICSharpCode.ILSpy.ViewModels @@ -358,7 +358,7 @@ namespace ICSharpCode.ILSpy.ViewModels
if (dlg.ShowDialog() == true)
{
var list = CreateDefaultList(config.Name, config.Path, dlg.ListName);
if (list.assemblies.Count > 0)
if (list.Count > 0)
{
manager.CreateList(list);
}

Loading…
Cancel
Save