From 899714d6031de0c9e7f4429c431b3b0777671827 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sat, 13 Feb 2021 19:59:20 +0100 Subject: [PATCH 1/5] Remove unnecessary mutable state from UniversalAssemblyResolver. --- .../Metadata/UniversalAssemblyResolver.cs | 25 +++++++------------ 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/ICSharpCode.Decompiler/Metadata/UniversalAssemblyResolver.cs b/ICSharpCode.Decompiler/Metadata/UniversalAssemblyResolver.cs index b3570a6e5..f468bb2cb 100644 --- a/ICSharpCode.Decompiler/Metadata/UniversalAssemblyResolver.cs +++ b/ICSharpCode.Decompiler/Metadata/UniversalAssemblyResolver.cs @@ -67,7 +67,6 @@ namespace ICSharpCode.Decompiler.Metadata readonly string baseDirectory; readonly List directories = new List(); static readonly List gac_paths = GetGacPaths(); - HashSet targetFrameworkSearchPaths; static readonly DecompilerRuntime decompilerRuntime; public void AddSearchDirectory(string directory) @@ -87,9 +86,9 @@ namespace ICSharpCode.Decompiler.Metadata return directories.ToArray(); } - string targetFramework; - TargetFrameworkIdentifier targetFrameworkIdentifier; - Version targetFrameworkVersion; + readonly string targetFramework; + readonly TargetFrameworkIdentifier targetFrameworkIdentifier; + readonly Version targetFrameworkVersion; /// /// Creates a new instance of the . @@ -301,26 +300,20 @@ namespace ICSharpCode.Decompiler.Metadata return null; } - void AddTargetFrameworkSearchPathIfExists(string path) - { - if (targetFrameworkSearchPaths == null) - { - targetFrameworkSearchPaths = new HashSet(); - } - if (Directory.Exists(path)) - targetFrameworkSearchPaths.Add(path); - } - /// /// This only works on Windows /// string ResolveSilverlight(IAssemblyReference name, Version version) { - AddTargetFrameworkSearchPathIfExists(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "Microsoft Silverlight")); - AddTargetFrameworkSearchPathIfExists(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "Microsoft Silverlight")); + string[] targetFrameworkSearchPaths = { + Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "Microsoft Silverlight"), + Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "Microsoft Silverlight") + }; foreach (var baseDirectory in targetFrameworkSearchPaths) { + if (!Directory.Exists(baseDirectory)) + continue; var versionDirectory = Path.Combine(baseDirectory, FindClosestVersionDirectory(baseDirectory, version)); var file = SearchDirectory(name, versionDirectory); if (file != null) From d70bfe80d5a91f4b54b44d3e8f57abbcded349a2 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sat, 13 Feb 2021 20:17:12 +0100 Subject: [PATCH 2/5] Add ResolveAsync() method to IAssemblyResolver interface. DecompilerTypeSystem uses this to resolve/load multiple assemblies in parallel. Unfortunately this doesn't gain us any performance yet in ILSpy because there we have a global assembly-loader-lock :( --- .../Metadata/AssemblyReferences.cs | 3 ++ .../Metadata/UniversalAssemblyResolver.cs | 11 ++++ .../TypeSystem/DecompilerTypeSystem.cs | 50 +++++++++++-------- ILSpy/LoadedAssembly.cs | 50 +++++++++++++++++++ ILSpy/LoadedPackage.cs | 28 +++++++++++ 5 files changed, 122 insertions(+), 20 deletions(-) diff --git a/ICSharpCode.Decompiler/Metadata/AssemblyReferences.cs b/ICSharpCode.Decompiler/Metadata/AssemblyReferences.cs index 5fd63155d..241f0bdf4 100644 --- a/ICSharpCode.Decompiler/Metadata/AssemblyReferences.cs +++ b/ICSharpCode.Decompiler/Metadata/AssemblyReferences.cs @@ -23,6 +23,7 @@ using System.Reflection; using System.Reflection.Metadata; using System.Security.Cryptography; using System.Text; +using System.Threading.Tasks; namespace ICSharpCode.Decompiler.Metadata { @@ -47,6 +48,8 @@ namespace ICSharpCode.Decompiler.Metadata #if !VSADDIN PEFile Resolve(IAssemblyReference reference); PEFile ResolveModule(PEFile mainModule, string moduleName); + Task ResolveAsync(IAssemblyReference reference); + Task ResolveModuleAsync(PEFile mainModule, string moduleName); #endif } diff --git a/ICSharpCode.Decompiler/Metadata/UniversalAssemblyResolver.cs b/ICSharpCode.Decompiler/Metadata/UniversalAssemblyResolver.cs index f468bb2cb..38e65bb03 100644 --- a/ICSharpCode.Decompiler/Metadata/UniversalAssemblyResolver.cs +++ b/ICSharpCode.Decompiler/Metadata/UniversalAssemblyResolver.cs @@ -24,6 +24,7 @@ using System.Reflection.Metadata; using System.Reflection.PortableExecutable; using System.Text; using System.Text.RegularExpressions; +using System.Threading.Tasks; namespace ICSharpCode.Decompiler.Metadata { @@ -203,6 +204,16 @@ namespace ICSharpCode.Decompiler.Metadata } return new PEFile(moduleFileName, new FileStream(moduleFileName, FileMode.Open, FileAccess.Read), streamOptions, metadataOptions); } + + public Task ResolveAsync(IAssemblyReference name) + { + return Task.Run(() => Resolve(name)); + } + + public Task ResolveModuleAsync(PEFile mainModule, string moduleName) + { + return Task.Run(() => ResolveModule(mainModule, moduleName)); + } #endif public override bool IsSharedAssembly(IAssemblyReference reference, out string runtimePack) diff --git a/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs b/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs index 81584e90c..9c044c72c 100644 --- a/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs +++ b/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs @@ -19,6 +19,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using ICSharpCode.Decompiler.Metadata; using ICSharpCode.Decompiler.TypeSystem.Implementation; @@ -181,7 +182,11 @@ namespace ICSharpCode.Decompiler.TypeSystem // Load referenced assemblies and type-forwarder references. // This is necessary to make .NET Core/PCL binaries work better. var referencedAssemblies = new List(); - var assemblyReferenceQueue = new Queue<(bool IsAssembly, PEFile MainModule, object Reference)>(); + var assemblyReferenceQueue = new Queue<(bool IsAssembly, PEFile MainModule, object Reference, Task ResolveTask)>(); + var comparer = KeyComparer.Create(((bool IsAssembly, PEFile MainModule, object Reference) reference) => + reference.IsAssembly ? "A:" + ((AssemblyReference)reference.Reference).FullName : + "M:" + reference.Reference); + var assemblyReferencesInQueue = new HashSet<(bool IsAssembly, PEFile Parent, object Reference)>(comparer); var mainMetadata = mainModule.Metadata; foreach (var h in mainMetadata.GetModuleReferences()) { @@ -194,7 +199,7 @@ namespace ICSharpCode.Decompiler.TypeSystem var file = mainMetadata.GetAssemblyFile(fileHandle); if (mainMetadata.StringComparer.Equals(file.Name, moduleName) && file.ContainsMetadata) { - assemblyReferenceQueue.Enqueue((false, mainModule, moduleName)); + AddToQueue(false, mainModule, moduleName); break; } } @@ -205,26 +210,12 @@ namespace ICSharpCode.Decompiler.TypeSystem } foreach (var refs in mainModule.AssemblyReferences) { - assemblyReferenceQueue.Enqueue((true, mainModule, refs)); + AddToQueue(true, mainModule, refs); } - var comparer = KeyComparer.Create(((bool IsAssembly, PEFile MainModule, object Reference) reference) => - reference.IsAssembly ? "A:" + ((AssemblyReference)reference.Reference).FullName : - "M:" + reference.Reference); - var processedAssemblyReferences = new HashSet<(bool IsAssembly, PEFile Parent, object Reference)>(comparer); while (assemblyReferenceQueue.Count > 0) { var asmRef = assemblyReferenceQueue.Dequeue(); - if (!processedAssemblyReferences.Add(asmRef)) - continue; - PEFile asm; - if (asmRef.IsAssembly) - { - asm = assemblyResolver.Resolve((AssemblyReference)asmRef.Reference); - } - else - { - asm = assemblyResolver.ResolveModule(asmRef.MainModule, (string)asmRef.Reference); - } + var asm = asmRef.ResolveTask.GetAwaiter().GetResult(); if (asm != null) { referencedAssemblies.Add(asm); @@ -235,11 +226,11 @@ namespace ICSharpCode.Decompiler.TypeSystem switch (exportedType.Implementation.Kind) { case SRM.HandleKind.AssemblyReference: - assemblyReferenceQueue.Enqueue((true, asm, new AssemblyReference(asm, (SRM.AssemblyReferenceHandle)exportedType.Implementation))); + AddToQueue(true, asm, new AssemblyReference(asm, (SRM.AssemblyReferenceHandle)exportedType.Implementation)); break; case SRM.HandleKind.AssemblyFile: var file = metadata.GetAssemblyFile((SRM.AssemblyFileHandle)exportedType.Implementation); - assemblyReferenceQueue.Enqueue((false, asm, metadata.GetString(file.Name))); + AddToQueue(false, asm, metadata.GetString(file.Name)); break; } } @@ -261,6 +252,25 @@ namespace ICSharpCode.Decompiler.TypeSystem } this.MainModule = (MetadataModule)base.MainModule; + void AddToQueue(bool isAssembly, PEFile mainModule, object reference) + { + if (assemblyReferencesInQueue.Add((isAssembly, mainModule, reference))) + { + // Immediately start loading the referenced module as we add the entry to the queue. + // This allows loading multiple modules in parallel. + Task asm; + if (isAssembly) + { + asm = assemblyResolver.ResolveAsync((AssemblyReference)reference); + } + else + { + asm = assemblyResolver.ResolveModuleAsync(mainModule, (string)reference); + } + assemblyReferenceQueue.Enqueue((isAssembly, mainModule, reference, asm)); + } + } + bool IsMissing(KnownTypeReference knownType) { var name = knownType.TypeName; diff --git a/ILSpy/LoadedAssembly.cs b/ILSpy/LoadedAssembly.cs index 7c4ddfee8..a506c0924 100644 --- a/ILSpy/LoadedAssembly.cs +++ b/ILSpy/LoadedAssembly.cs @@ -158,6 +158,24 @@ namespace ICSharpCode.ILSpy } } + /// + /// Gets the . + /// Returns null in case of load errors. + /// + public async Task GetPEFileOrNullAsync() + { + try + { + var loadResult = await loadingTask.ConfigureAwait(false); + return loadResult.PEFile; + } + catch (Exception ex) + { + System.Diagnostics.Trace.TraceError(ex.ToString()); + return null; + } + } + ICompilation typeSystem; /// @@ -429,6 +447,38 @@ namespace ICSharpCode.ILSpy return module; return parent.LookupReferencedModule(mainModule, moduleName)?.GetPEFileOrNull(); } + + public async Task ResolveAsync(IAssemblyReference reference) + { + if (parent.providedAssemblyResolver != null) + { + var module = await parent.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 ResolveModuleAsync(PEFile mainModule, string moduleName) + { + if (parent.providedAssemblyResolver != null) + { + var module = await parent.providedAssemblyResolver.ResolveModuleAsync(mainModule, moduleName).ConfigureAwait(false); + if (module != null) + return module; + } + var asm = parent.LookupReferencedModule(mainModule, moduleName); + if (asm != null) + { + return await asm.GetPEFileOrNullAsync().ConfigureAwait(false); + } + return null; + } } readonly MyAssemblyResolver resolver; diff --git a/ILSpy/LoadedPackage.cs b/ILSpy/LoadedPackage.cs index d6ed2be4b..4e5c1b750 100644 --- a/ILSpy/LoadedPackage.cs +++ b/ILSpy/LoadedPackage.cs @@ -240,6 +240,20 @@ namespace ICSharpCode.ILSpy return parent?.Resolve(reference); } + public Task ResolveAsync(IAssemblyReference reference) + { + var asm = ResolveFileName(reference.Name + ".dll"); + if (asm != null) + { + return asm.GetPEFileOrNullAsync(); + } + if (parent != null) + { + return parent.ResolveAsync(reference); + } + return null; + } + public PEFile ResolveModule(PEFile mainModule, string moduleName) { var asm = ResolveFileName(moduleName + ".dll"); @@ -250,6 +264,20 @@ namespace ICSharpCode.ILSpy return parent?.ResolveModule(mainModule, moduleName); } + public Task ResolveModuleAsync(PEFile mainModule, string moduleName) + { + var asm = ResolveFileName(moduleName + ".dll"); + if (asm != null) + { + return asm.GetPEFileOrNullAsync(); + } + if (parent != null) + { + return parent.ResolveModuleAsync(mainModule, moduleName); + } + return null; + } + readonly Dictionary assemblies = new Dictionary(StringComparer.OrdinalIgnoreCase); internal LoadedAssembly ResolveFileName(string name) From 3664e36e3e187c8bb77ab1e19aa51118eacfd2a3 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sat, 13 Feb 2021 20:36:46 +0100 Subject: [PATCH 3/5] Fix GetAssemblyReferenceClassifier() not initializing the universalResolver. --- ILSpy/LoadedAssembly.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/ILSpy/LoadedAssembly.cs b/ILSpy/LoadedAssembly.cs index a506c0924..aef112b81 100644 --- a/ILSpy/LoadedAssembly.cs +++ b/ILSpy/LoadedAssembly.cs @@ -488,9 +488,14 @@ namespace ICSharpCode.ILSpy return resolver; } + private MyUniversalResolver GetUniversalResolver() + { + return LazyInitializer.EnsureInitialized(ref this.universalResolver, () => new MyUniversalResolver(this)); + } + public AssemblyReferenceClassifier GetAssemblyReferenceClassifier() { - return universalResolver; + return GetUniversalResolver(); } /// @@ -582,12 +587,7 @@ namespace ICSharpCode.ILSpy } } - if (universalResolver == null) - { - universalResolver = new MyUniversalResolver(this); - } - - file = universalResolver.FindAssemblyFile(fullName); + file = GetUniversalResolver().FindAssemblyFile(fullName); foreach (LoadedAssembly loaded in assemblyList.GetAssemblies()) { From 263d9b9e07803de044bc768f93226ed509021c3c Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sat, 13 Feb 2021 22:24:42 +0100 Subject: [PATCH 4/5] 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. --- ILSpy.ReadyToRun/ReadyToRunLanguage.cs | 8 +- ILSpy/Analyzers/AnalyzerScope.cs | 11 +- ILSpy/AssemblyList.cs | 170 +++++-- ILSpy/AssemblyListManager.cs | 7 +- ILSpy/Languages/CSharpLanguage.cs | 141 +++--- ILSpy/Languages/ILLanguage.cs | 25 +- ILSpy/LoadedAssembly.cs | 423 +++++++----------- ILSpy/LoadedAssemblyExtensions.cs | 9 +- ILSpy/MainWindow.xaml.cs | 2 +- ILSpy/TreeNodes/AssemblyListTreeNode.cs | 41 +- ILSpy/TreeNodes/ModuleReferenceTreeNode.cs | 9 +- .../ManageAssemblyListsViewModel.cs | 2 +- 12 files changed, 405 insertions(+), 443 deletions(-) diff --git a/ILSpy.ReadyToRun/ReadyToRunLanguage.cs b/ILSpy.ReadyToRun/ReadyToRunLanguage.cs index 2563eb604..beff6dc95 100644 --- a/ILSpy.ReadyToRun/ReadyToRunLanguage.cs +++ b/ILSpy.ReadyToRun/ReadyToRunLanguage.cs @@ -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); } diff --git a/ILSpy/Analyzers/AnalyzerScope.cs b/ILSpy/Analyzers/AnalyzerScope.cs index 3aba1ca36..375380d74 100644 --- a/ILSpy/Analyzers/AnalyzerScope.cs +++ b/ILSpy/Analyzers/AnalyzerScope.cs @@ -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)) diff --git a/ILSpy/AssemblyList.cs b/ILSpy/AssemblyList.cs index 35420146c..b7a4c9177 100644 --- a/ILSpy/AssemblyList.cs +++ b/ILSpy/AssemblyList.cs @@ -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 /// Dirty flag, used to mark modifications so that the list is saved later 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 moduleLookupCache = new ConcurrentDictionary(); + readonly object lockObj = new object(); /// /// The assemblies in this list. @@ -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 method. /// - internal readonly ObservableCollection assemblies = new ObservableCollection(); + readonly ObservableCollection assemblies = new ObservableCollection(); + + /// + /// 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. + /// + readonly Dictionary byFilename = new Dictionary(StringComparer.OrdinalIgnoreCase); public AssemblyList(string listName) { @@ -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; + } + } + /// /// Gets the loaded assemblies. This method is thread-safe. /// public LoadedAssembly[] GetAssemblies() { - lock (assemblies) + lock (lockObj) { return assemblies.ToArray(); } } + public int Count { + get { + lock (lockObj) + { + return assemblies.Count; + } + } + } + /// /// Saves this assembly list to XML. /// @@ -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 } } - internal void ClearCache() + /// + /// Find an assembly that was previously opened. + /// + 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 /// Opens an assembly from disk. /// Returns the existing assembly node if it is already loaded. /// + /// + /// 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. + /// 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; + }); } /// @@ -196,21 +245,42 @@ namespace ICSharpCode.ILSpy /// 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 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; } /// @@ -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 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 lock (assemblies) { assemblies.Remove(assembly); + byFilename.Remove(assembly.FileName); } RequestGC(); } diff --git a/ILSpy/AssemblyListManager.cs b/ILSpy/AssemblyListManager.cs index 3a57f4ab4..8000f3b51 100644 --- a/ILSpy/AssemblyListManager.cs +++ b/ILSpy/AssemblyListManager.cs @@ -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 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 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 if (!AssemblyLists.Contains(ManageAssemblyListsViewModel.ASPDotNetMVC3List)) { AssemblyList mvc = ManageAssemblyListsViewModel.CreateDefaultList(ManageAssemblyListsViewModel.ASPDotNetMVC3List); - if (mvc.assemblies.Count > 0) + if (mvc.Count > 0) { CreateList(mvc); } diff --git a/ILSpy/Languages/CSharpLanguage.cs b/ILSpy/Languages/CSharpLanguage.cs index aafbf0b4d..6d4beb8f7 100644 --- a/ILSpy/Languages/CSharpLanguage.cs +++ b/ILSpy/Languages/CSharpLanguage.cs @@ -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; } } diff --git a/ILSpy/Languages/ILLanguage.cs b/ILSpy/Languages/ILLanguage.cs index 2ce956ba8..e5666461d 100644 --- a/ILSpy/Languages/ILLanguage.cs +++ b/ILSpy/Languages/ILLanguage.cs @@ -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; } diff --git a/ILSpy/LoadedAssembly.cs b/ILSpy/LoadedAssembly.cs index aef112b81..352beb065 100644 --- a/ILSpy/LoadedAssembly.cs +++ b/ILSpy/LoadedAssembly.cs @@ -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 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, IAssemblyResolver assemblyResolver = null) @@ -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 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 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 + /// 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 + /// public async Task 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 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(); - } - - /// - /// Returns the debug info for this assembly. Returns null in case of load errors or no debug info is available. - /// - 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 loadingAssemblies = new Dictionary(); - MyUniversalResolver universalResolver; - - /// - /// 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 - /// - 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 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> CreateLoadedAssemblyLookupAsync(bool shortNames) { - foreach (LoadedAssembly loaded in assemblyList.GetAssemblies()) + var result = new Dictionary(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 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> 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(); + } + + /// + /// Returns the debug info for this assembly. Returns null in case of load errors or no debug info is available. + /// + 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; + /// /// Wait until the assembly is loaded. /// Throws an AggregateException when loading the assembly fails. diff --git a/ILSpy/LoadedAssemblyExtensions.cs b/ILSpy/LoadedAssemblyExtensions.cs index 0e11707c7..0b8e3060f 100644 --- a/ILSpy/LoadedAssemblyExtensions.cs +++ b/ILSpy/LoadedAssemblyExtensions.cs @@ -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 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) diff --git a/ILSpy/MainWindow.xaml.cs b/ILSpy/MainWindow.xaml.cs index 96951570a..5622f0f50 100644 --- a/ILSpy/MainWindow.xaml.cs +++ b/ILSpy/MainWindow.xaml.cs @@ -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(); diff --git a/ILSpy/TreeNodes/AssemblyListTreeNode.cs b/ILSpy/TreeNodes/AssemblyListTreeNode.cs index 4fdc24fa0..13a0af699 100644 --- a/ILSpy/TreeNodes/AssemblyListTreeNode.cs +++ b/ILSpy/TreeNodes/AssemblyListTreeNode.cs @@ -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 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 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 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 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); } } diff --git a/ILSpy/TreeNodes/ModuleReferenceTreeNode.cs b/ILSpy/TreeNodes/ModuleReferenceTreeNode.cs index 6c53ad1cb..6216a3704 100644 --- a/ILSpy/TreeNodes/ModuleReferenceTreeNode.cs +++ b/ILSpy/TreeNodes/ModuleReferenceTreeNode.cs @@ -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; + } } } diff --git a/ILSpy/ViewModels/ManageAssemblyListsViewModel.cs b/ILSpy/ViewModels/ManageAssemblyListsViewModel.cs index 871b10541..f6ff717a5 100644 --- a/ILSpy/ViewModels/ManageAssemblyListsViewModel.cs +++ b/ILSpy/ViewModels/ManageAssemblyListsViewModel.cs @@ -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); } From fff2d0a787daa3419d311b94db5b78d35d8a24c8 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Mon, 15 Feb 2021 17:53:36 +0100 Subject: [PATCH 5/5] Fix assertions on AssemblyList.Sort and AssemblyList.Unload. Fix hang on LoadDependencies.Execute. --- ILSpy/AssemblyList.cs | 8 ++++---- ILSpy/TreeNodes/AssemblyTreeNode.cs | 7 +++++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/ILSpy/AssemblyList.cs b/ILSpy/AssemblyList.cs index b7a4c9177..f2aa07c5b 100644 --- a/ILSpy/AssemblyList.cs +++ b/ILSpy/AssemblyList.cs @@ -329,7 +329,7 @@ namespace ICSharpCode.ILSpy return null; var newAsm = new LoadedAssembly(this, target.FileName, pdbFileName: target.PdbFileName); newAsm.IsAutoLoaded = target.IsAutoLoaded; - lock (assemblies) + lock (lockObj) { this.assemblies.Remove(target); this.assemblies.Insert(index, newAsm); @@ -340,7 +340,7 @@ namespace ICSharpCode.ILSpy public void Unload(LoadedAssembly assembly) { App.Current.Dispatcher.VerifyAccess(); - lock (assemblies) + lock (lockObj) { assemblies.Remove(assembly); byFilename.Remove(assembly.FileName); @@ -350,7 +350,7 @@ namespace ICSharpCode.ILSpy static bool gcRequested; - void RequestGC() + static void RequestGC() { if (gcRequested) return; @@ -370,7 +370,7 @@ namespace ICSharpCode.ILSpy public void Sort(int index, int count, IComparer comparer) { App.Current.Dispatcher.VerifyAccess(); - lock (assemblies) + lock (lockObj) { List list = new List(assemblies); list.Sort(index, Math.Min(count, list.Count - index), comparer); diff --git a/ILSpy/TreeNodes/AssemblyTreeNode.cs b/ILSpy/TreeNodes/AssemblyTreeNode.cs index 8dba58e8a..036879ca5 100644 --- a/ILSpy/TreeNodes/AssemblyTreeNode.cs +++ b/ILSpy/TreeNodes/AssemblyTreeNode.cs @@ -20,6 +20,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; @@ -460,10 +461,11 @@ namespace ICSharpCode.ILSpy.TreeNodes return true; } - public void Execute(TextViewContext context) + public async void Execute(TextViewContext context) { if (context.SelectedTreeNodes == null) return; + var tasks = new List(); foreach (var node in context.SelectedTreeNodes) { var la = ((AssemblyTreeNode)node).LoadedAssembly; @@ -474,10 +476,11 @@ namespace ICSharpCode.ILSpy.TreeNodes var metadata = module.Metadata; foreach (var assyRef in metadata.AssemblyReferences) { - resolver.Resolve(new AssemblyReference(module, assyRef)); + tasks.Add(resolver.ResolveAsync(new AssemblyReference(module, assyRef))); } } } + await Task.WhenAll(tasks); MainWindow.Instance.RefreshDecompiledView(); } }