Browse Source

Merge pull request #2301 from icsharpcode/async-resolve

async IAssemblyResolver
pull/2308/head
Daniel Grunwald 4 years ago committed by GitHub
parent
commit
0c23ab1998
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      ICSharpCode.Decompiler/Metadata/AssemblyReferences.cs
  2. 36
      ICSharpCode.Decompiler/Metadata/UniversalAssemblyResolver.cs
  3. 50
      ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs
  4. 8
      ILSpy.ReadyToRun/ReadyToRunLanguage.cs
  5. 5
      ILSpy/Analyzers/AnalyzerScope.cs
  6. 166
      ILSpy/AssemblyList.cs
  7. 7
      ILSpy/AssemblyListManager.cs
  8. 5
      ILSpy/Languages/CSharpLanguage.cs
  9. 5
      ILSpy/Languages/ILLanguage.cs
  10. 371
      ILSpy/LoadedAssembly.cs
  11. 9
      ILSpy/LoadedAssemblyExtensions.cs
  12. 28
      ILSpy/LoadedPackage.cs
  13. 2
      ILSpy/MainWindow.xaml.cs
  14. 25
      ILSpy/TreeNodes/AssemblyListTreeNode.cs
  15. 7
      ILSpy/TreeNodes/AssemblyTreeNode.cs
  16. 7
      ILSpy/TreeNodes/ModuleReferenceTreeNode.cs
  17. 2
      ILSpy/ViewModels/ManageAssemblyListsViewModel.cs

3
ICSharpCode.Decompiler/Metadata/AssemblyReferences.cs

@ -23,6 +23,7 @@ using System.Reflection;
using System.Reflection.Metadata; using System.Reflection.Metadata;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text; using System.Text;
using System.Threading.Tasks;
namespace ICSharpCode.Decompiler.Metadata namespace ICSharpCode.Decompiler.Metadata
{ {
@ -47,6 +48,8 @@ namespace ICSharpCode.Decompiler.Metadata
#if !VSADDIN #if !VSADDIN
PEFile Resolve(IAssemblyReference reference); PEFile Resolve(IAssemblyReference reference);
PEFile ResolveModule(PEFile mainModule, string moduleName); PEFile ResolveModule(PEFile mainModule, string moduleName);
Task<PEFile> ResolveAsync(IAssemblyReference reference);
Task<PEFile> ResolveModuleAsync(PEFile mainModule, string moduleName);
#endif #endif
} }

36
ICSharpCode.Decompiler/Metadata/UniversalAssemblyResolver.cs

@ -24,6 +24,7 @@ using System.Reflection.Metadata;
using System.Reflection.PortableExecutable; using System.Reflection.PortableExecutable;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace ICSharpCode.Decompiler.Metadata namespace ICSharpCode.Decompiler.Metadata
{ {
@ -67,7 +68,6 @@ namespace ICSharpCode.Decompiler.Metadata
readonly string baseDirectory; readonly string baseDirectory;
readonly List<string> directories = new List<string>(); readonly List<string> directories = new List<string>();
static readonly List<string> gac_paths = GetGacPaths(); static readonly List<string> gac_paths = GetGacPaths();
HashSet<string> targetFrameworkSearchPaths;
static readonly DecompilerRuntime decompilerRuntime; static readonly DecompilerRuntime decompilerRuntime;
public void AddSearchDirectory(string directory) public void AddSearchDirectory(string directory)
@ -87,9 +87,9 @@ namespace ICSharpCode.Decompiler.Metadata
return directories.ToArray(); return directories.ToArray();
} }
string targetFramework; readonly string targetFramework;
TargetFrameworkIdentifier targetFrameworkIdentifier; readonly TargetFrameworkIdentifier targetFrameworkIdentifier;
Version targetFrameworkVersion; readonly Version targetFrameworkVersion;
/// <summary> /// <summary>
/// Creates a new instance of the <see cref="UniversalAssemblyResolver"/>. /// Creates a new instance of the <see cref="UniversalAssemblyResolver"/>.
@ -204,6 +204,16 @@ namespace ICSharpCode.Decompiler.Metadata
} }
return new PEFile(moduleFileName, new FileStream(moduleFileName, FileMode.Open, FileAccess.Read), streamOptions, metadataOptions); return new PEFile(moduleFileName, new FileStream(moduleFileName, FileMode.Open, FileAccess.Read), streamOptions, metadataOptions);
} }
public Task<PEFile> ResolveAsync(IAssemblyReference name)
{
return Task.Run(() => Resolve(name));
}
public Task<PEFile> ResolveModuleAsync(PEFile mainModule, string moduleName)
{
return Task.Run(() => ResolveModule(mainModule, moduleName));
}
#endif #endif
public override bool IsSharedAssembly(IAssemblyReference reference, out string runtimePack) public override bool IsSharedAssembly(IAssemblyReference reference, out string runtimePack)
@ -301,26 +311,20 @@ namespace ICSharpCode.Decompiler.Metadata
return null; return null;
} }
void AddTargetFrameworkSearchPathIfExists(string path)
{
if (targetFrameworkSearchPaths == null)
{
targetFrameworkSearchPaths = new HashSet<string>();
}
if (Directory.Exists(path))
targetFrameworkSearchPaths.Add(path);
}
/// <summary> /// <summary>
/// This only works on Windows /// This only works on Windows
/// </summary> /// </summary>
string ResolveSilverlight(IAssemblyReference name, Version version) string ResolveSilverlight(IAssemblyReference name, Version version)
{ {
AddTargetFrameworkSearchPathIfExists(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "Microsoft Silverlight")); string[] targetFrameworkSearchPaths = {
AddTargetFrameworkSearchPathIfExists(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "Microsoft Silverlight")); Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "Microsoft Silverlight"),
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "Microsoft Silverlight")
};
foreach (var baseDirectory in targetFrameworkSearchPaths) foreach (var baseDirectory in targetFrameworkSearchPaths)
{ {
if (!Directory.Exists(baseDirectory))
continue;
var versionDirectory = Path.Combine(baseDirectory, FindClosestVersionDirectory(baseDirectory, version)); var versionDirectory = Path.Combine(baseDirectory, FindClosestVersionDirectory(baseDirectory, version));
var file = SearchDirectory(name, versionDirectory); var file = SearchDirectory(name, versionDirectory);
if (file != null) if (file != null)

50
ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs

@ -19,6 +19,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using ICSharpCode.Decompiler.Metadata; using ICSharpCode.Decompiler.Metadata;
using ICSharpCode.Decompiler.TypeSystem.Implementation; using ICSharpCode.Decompiler.TypeSystem.Implementation;
@ -181,7 +182,11 @@ namespace ICSharpCode.Decompiler.TypeSystem
// Load referenced assemblies and type-forwarder references. // Load referenced assemblies and type-forwarder references.
// This is necessary to make .NET Core/PCL binaries work better. // This is necessary to make .NET Core/PCL binaries work better.
var referencedAssemblies = new List<PEFile>(); var referencedAssemblies = new List<PEFile>();
var assemblyReferenceQueue = new Queue<(bool IsAssembly, PEFile MainModule, object Reference)>(); var assemblyReferenceQueue = new Queue<(bool IsAssembly, PEFile MainModule, object Reference, Task<PEFile> 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; var mainMetadata = mainModule.Metadata;
foreach (var h in mainMetadata.GetModuleReferences()) foreach (var h in mainMetadata.GetModuleReferences())
{ {
@ -194,7 +199,7 @@ namespace ICSharpCode.Decompiler.TypeSystem
var file = mainMetadata.GetAssemblyFile(fileHandle); var file = mainMetadata.GetAssemblyFile(fileHandle);
if (mainMetadata.StringComparer.Equals(file.Name, moduleName) && file.ContainsMetadata) if (mainMetadata.StringComparer.Equals(file.Name, moduleName) && file.ContainsMetadata)
{ {
assemblyReferenceQueue.Enqueue((false, mainModule, moduleName)); AddToQueue(false, mainModule, moduleName);
break; break;
} }
} }
@ -205,26 +210,12 @@ namespace ICSharpCode.Decompiler.TypeSystem
} }
foreach (var refs in mainModule.AssemblyReferences) 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) while (assemblyReferenceQueue.Count > 0)
{ {
var asmRef = assemblyReferenceQueue.Dequeue(); var asmRef = assemblyReferenceQueue.Dequeue();
if (!processedAssemblyReferences.Add(asmRef)) var asm = asmRef.ResolveTask.GetAwaiter().GetResult();
continue;
PEFile asm;
if (asmRef.IsAssembly)
{
asm = assemblyResolver.Resolve((AssemblyReference)asmRef.Reference);
}
else
{
asm = assemblyResolver.ResolveModule(asmRef.MainModule, (string)asmRef.Reference);
}
if (asm != null) if (asm != null)
{ {
referencedAssemblies.Add(asm); referencedAssemblies.Add(asm);
@ -235,11 +226,11 @@ namespace ICSharpCode.Decompiler.TypeSystem
switch (exportedType.Implementation.Kind) switch (exportedType.Implementation.Kind)
{ {
case SRM.HandleKind.AssemblyReference: 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; break;
case SRM.HandleKind.AssemblyFile: case SRM.HandleKind.AssemblyFile:
var file = metadata.GetAssemblyFile((SRM.AssemblyFileHandle)exportedType.Implementation); var file = metadata.GetAssemblyFile((SRM.AssemblyFileHandle)exportedType.Implementation);
assemblyReferenceQueue.Enqueue((false, asm, metadata.GetString(file.Name))); AddToQueue(false, asm, metadata.GetString(file.Name));
break; break;
} }
} }
@ -261,6 +252,25 @@ namespace ICSharpCode.Decompiler.TypeSystem
} }
this.MainModule = (MetadataModule)base.MainModule; 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<PEFile> 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) bool IsMissing(KnownTypeReference knownType)
{ {
var name = knownType.TypeName; var name = knownType.TypeName;

8
ILSpy.ReadyToRun/ReadyToRunLanguage.cs

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

5
ILSpy/Analyzers/AnalyzerScope.cs

@ -147,10 +147,8 @@ namespace ICSharpCode.ILSpy.Analyzers
continue; continue;
if (checkedFiles.Contains(module)) if (checkedFiles.Contains(module))
continue; continue;
var resolver = assembly.GetAssemblyResolver(); var resolver = assembly.GetAssemblyResolver(loadOnDemand: false);
foreach (var reference in module.AssemblyReferences) foreach (var reference in module.AssemblyReferences)
{
using (LoadedAssembly.DisableAssemblyLoad(AssemblyList))
{ {
if (resolver.Resolve(reference) == curFile) if (resolver.Resolve(reference) == curFile)
{ {
@ -158,7 +156,6 @@ namespace ICSharpCode.ILSpy.Analyzers
break; break;
} }
} }
}
if (found && checkedFiles.Add(module)) if (found && checkedFiles.Add(module))
{ {
if (ModuleReferencesScopeType(module.Metadata, reflectionTypeScopeName, typeScope.Namespace)) if (ModuleReferencesScopeType(module.Metadata, reflectionTypeScopeName, typeScope.Namespace))

166
ILSpy/AssemblyList.cs

@ -17,12 +17,13 @@
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows.Threading; using System.Windows.Threading;
using System.Xml.Linq; using System.Xml.Linq;
@ -39,8 +40,7 @@ namespace ICSharpCode.ILSpy
/// <summary>Dirty flag, used to mark modifications so that the list is saved later</summary> /// <summary>Dirty flag, used to mark modifications so that the list is saved later</summary>
bool dirty; bool dirty;
internal readonly ConcurrentDictionary<(string assemblyName, bool isWinRT, string targetFrameworkIdentifier), LoadedAssembly> assemblyLookupCache = new ConcurrentDictionary<(string assemblyName, bool isWinRT, string targetFrameworkIdentifier), LoadedAssembly>(); readonly object lockObj = new object();
internal readonly ConcurrentDictionary<string, LoadedAssembly> moduleLookupCache = new ConcurrentDictionary<string, LoadedAssembly>();
/// <summary> /// <summary>
/// The assemblies in this list. /// 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 /// Technically read accesses need locking when done on non-GUI threads... but whenever possible, use the
/// thread-safe <see cref="GetAssemblies()"/> method. /// thread-safe <see cref="GetAssemblies()"/> method.
/// </remarks> /// </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) public AssemblyList(string listName)
{ {
@ -81,17 +88,37 @@ namespace ICSharpCode.ILSpy
this.assemblies.AddRange(list.assemblies); 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> /// <summary>
/// Gets the loaded assemblies. This method is thread-safe. /// Gets the loaded assemblies. This method is thread-safe.
/// </summary> /// </summary>
public LoadedAssembly[] GetAssemblies() public LoadedAssembly[] GetAssemblies()
{ {
lock (assemblies) lock (lockObj)
{ {
return assemblies.ToArray(); return assemblies.ToArray();
} }
} }
public int Count {
get {
lock (lockObj)
{
return assemblies.Count;
}
}
}
/// <summary> /// <summary>
/// Saves this assembly list to XML. /// Saves this assembly list to XML.
/// </summary> /// </summary>
@ -111,9 +138,30 @@ namespace ICSharpCode.ILSpy
get { return listName; } 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) void Assemblies_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{ {
ClearCache(); Debug.Assert(Monitor.IsEntered(lockObj));
if (CollectionChangeHasEffectOnSave(e)) if (CollectionChangeHasEffectOnSave(e))
{ {
RefreshSave(); RefreshSave();
@ -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(); file = Path.GetFullPath(file);
moduleLookupCache.Clear(); lock (lockObj)
{
if (byFilename.TryGetValue(file, out var asm))
return asm;
}
return null;
} }
public LoadedAssembly Open(string assemblyUri, bool isAutoLoaded = false) public LoadedAssembly Open(string assemblyUri, bool isAutoLoaded = false)
@ -170,25 +226,18 @@ namespace ICSharpCode.ILSpy
/// Opens an assembly from disk. /// Opens an assembly from disk.
/// Returns the existing assembly node if it is already loaded. /// Returns the existing assembly node if it is already loaded.
/// </summary> /// </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) public LoadedAssembly OpenAssembly(string file, bool isAutoLoaded = false)
{ {
App.Current.Dispatcher.VerifyAccess(); return OpenAssembly(file, () => {
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); var newAsm = new LoadedAssembly(this, file);
newAsm.IsAutoLoaded = isAutoLoaded; newAsm.IsAutoLoaded = isAutoLoaded;
lock (assemblies)
{
this.assemblies.Add(newAsm);
}
return newAsm; return newAsm;
});
} }
/// <summary> /// <summary>
@ -196,21 +245,42 @@ namespace ICSharpCode.ILSpy
/// </summary> /// </summary>
public LoadedAssembly OpenAssembly(string file, Stream stream, bool isAutoLoaded = false) 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)
{ {
if (file.Equals(asm.FileName, StringComparison.OrdinalIgnoreCase)) file = Path.GetFullPath(file);
bool isUIThread = App.Current.Dispatcher.Thread == Thread.CurrentThread;
LoadedAssembly asm;
lock (lockObj)
{
if (byFilename.TryGetValue(file, out asm))
return asm; return asm;
} asm = load();
Debug.Assert(asm.FileName == file);
byFilename.Add(asm.FileName, asm);
var newAsm = new LoadedAssembly(this, file, stream: Task.FromResult(stream)); if (isUIThread)
newAsm.IsAutoLoaded = isAutoLoaded;
lock (assemblies)
{ {
this.assemblies.Add(newAsm); assemblies.Add(asm);
} }
return newAsm; }
if (!isUIThread)
{
App.Current.Dispatcher.BeginInvoke((Action)delegate () {
lock (lockObj)
{
assemblies.Add(asm);
}
}, DispatcherPriority.Normal);
}
return asm;
} }
/// <summary> /// <summary>
@ -221,21 +291,23 @@ namespace ICSharpCode.ILSpy
{ {
App.Current.Dispatcher.VerifyAccess(); App.Current.Dispatcher.VerifyAccess();
file = Path.GetFullPath(file); file = Path.GetFullPath(file);
lock (lockObj)
var target = this.assemblies.FirstOrDefault(asm => file.Equals(asm.FileName, StringComparison.OrdinalIgnoreCase)); {
if (target == null) if (!byFilename.TryGetValue(file, out LoadedAssembly target))
return null;
int index = this.assemblies.IndexOf(target);
if (index < 0)
return null; return null;
var index = this.assemblies.IndexOf(target);
var newAsm = new LoadedAssembly(this, file, stream: Task.FromResult(stream)); var newAsm = new LoadedAssembly(this, file, stream: Task.FromResult(stream));
newAsm.IsAutoLoaded = target.IsAutoLoaded; newAsm.IsAutoLoaded = target.IsAutoLoaded;
lock (assemblies)
{ Debug.Assert(newAsm.FileName == file);
this.assemblies.Remove(target); byFilename[file] = newAsm;
this.assemblies.Insert(index, newAsm); this.assemblies[index] = newAsm;
}
return newAsm; return newAsm;
} }
}
public LoadedAssembly ReloadAssembly(string file) public LoadedAssembly ReloadAssembly(string file)
{ {
@ -251,10 +323,13 @@ namespace ICSharpCode.ILSpy
public LoadedAssembly ReloadAssembly(LoadedAssembly target) public LoadedAssembly ReloadAssembly(LoadedAssembly target)
{ {
App.Current.Dispatcher.VerifyAccess();
var index = this.assemblies.IndexOf(target); var index = this.assemblies.IndexOf(target);
if (index < 0)
return null;
var newAsm = new LoadedAssembly(this, target.FileName, pdbFileName: target.PdbFileName); var newAsm = new LoadedAssembly(this, target.FileName, pdbFileName: target.PdbFileName);
newAsm.IsAutoLoaded = target.IsAutoLoaded; newAsm.IsAutoLoaded = target.IsAutoLoaded;
lock (assemblies) lock (lockObj)
{ {
this.assemblies.Remove(target); this.assemblies.Remove(target);
this.assemblies.Insert(index, newAsm); this.assemblies.Insert(index, newAsm);
@ -265,16 +340,17 @@ namespace ICSharpCode.ILSpy
public void Unload(LoadedAssembly assembly) public void Unload(LoadedAssembly assembly)
{ {
App.Current.Dispatcher.VerifyAccess(); App.Current.Dispatcher.VerifyAccess();
lock (assemblies) lock (lockObj)
{ {
assemblies.Remove(assembly); assemblies.Remove(assembly);
byFilename.Remove(assembly.FileName);
} }
RequestGC(); RequestGC();
} }
static bool gcRequested; static bool gcRequested;
void RequestGC() static void RequestGC()
{ {
if (gcRequested) if (gcRequested)
return; return;
@ -294,7 +370,7 @@ namespace ICSharpCode.ILSpy
public void Sort(int index, int count, IComparer<LoadedAssembly> comparer) public void Sort(int index, int count, IComparer<LoadedAssembly> comparer)
{ {
App.Current.Dispatcher.VerifyAccess(); App.Current.Dispatcher.VerifyAccess();
lock (assemblies) lock (lockObj)
{ {
List<LoadedAssembly> list = new List<LoadedAssembly>(assemblies); List<LoadedAssembly> list = new List<LoadedAssembly>(assemblies);
list.Sort(index, Math.Min(count, list.Count - index), comparer); list.Sort(index, Math.Min(count, list.Count - index), comparer);

7
ILSpy/AssemblyListManager.cs

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

5
ILSpy/Languages/CSharpLanguage.cs

@ -404,9 +404,7 @@ namespace ICSharpCode.ILSpy
base.DecompileAssembly(assembly, output, options); base.DecompileAssembly(assembly, output, options);
// don't automatically load additional assemblies when an assembly node is selected in the tree view // 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);
{
IAssemblyResolver assemblyResolver = assembly.GetAssemblyResolver();
var typeSystem = new DecompilerTypeSystem(module, assemblyResolver, options.DecompilerSettings); var typeSystem = new DecompilerTypeSystem(module, assemblyResolver, options.DecompilerSettings);
var globalType = typeSystem.MainModule.TypeDefinitions.FirstOrDefault(); var globalType = typeSystem.MainModule.TypeDefinitions.FirstOrDefault();
if (globalType != null) if (globalType != null)
@ -483,7 +481,6 @@ namespace ICSharpCode.ILSpy
st = decompiler.DecompileModuleAndAssemblyAttributes(); st = decompiler.DecompileModuleAndAssemblyAttributes();
} }
WriteCode(output, options.DecompilerSettings, st, decompiler.TypeSystem); WriteCode(output, options.DecompilerSettings, st, decompiler.TypeSystem);
}
return null; return null;
} }
} }

5
ILSpy/Languages/ILLanguage.cs

@ -172,9 +172,7 @@ namespace ICSharpCode.ILSpy
} }
// don't automatically load additional assemblies when an assembly node is selected in the tree view // 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.AssemblyResolver = module.GetAssemblyResolver();
dis.DebugInfo = module.GetDebugInfoOrNull(); dis.DebugInfo = module.GetDebugInfoOrNull();
if (options.FullDecompilation) if (options.FullDecompilation)
dis.WriteAssemblyReferences(metadata); dis.WriteAssemblyReferences(metadata);
@ -188,7 +186,6 @@ namespace ICSharpCode.ILSpy
output.WriteLine(); output.WriteLine();
dis.WriteModuleContents(module); dis.WriteModuleContents(module);
} }
}
return null; return null;
} }

371
ILSpy/LoadedAssembly.cs

@ -26,7 +26,6 @@ using System.Reflection.PortableExecutable;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows.Threading;
using ICSharpCode.Decompiler.DebugInfo; using ICSharpCode.Decompiler.DebugInfo;
using ICSharpCode.Decompiler.Metadata; using ICSharpCode.Decompiler.Metadata;
@ -95,7 +94,6 @@ namespace ICSharpCode.ILSpy
this.loadingTask = Task.Run(() => LoadAsync(stream)); // requires that this.fileName is set this.loadingTask = Task.Run(() => LoadAsync(stream)); // requires that this.fileName is set
this.shortName = Path.GetFileNameWithoutExtension(fileName); this.shortName = Path.GetFileNameWithoutExtension(fileName);
this.resolver = new MyAssemblyResolver(this);
} }
public LoadedAssembly(LoadedAssembly bundle, string fileName, Task<Stream> stream, IAssemblyResolver assemblyResolver = null) public LoadedAssembly(LoadedAssembly bundle, string fileName, Task<Stream> stream, IAssemblyResolver assemblyResolver = null)
@ -158,6 +156,24 @@ namespace ICSharpCode.ILSpy
} }
} }
/// <summary>
/// Gets the <see cref="PEFile"/>.
/// Returns null in case of load errors.
/// </summary>
public async Task<PEFile> GetPEFileOrNullAsync()
{
try
{
var loadResult = await loadingTask.ConfigureAwait(false);
return loadResult.PEFile;
}
catch (Exception ex)
{
System.Diagnostics.Trace.TraceError(ex.ToString());
return null;
}
}
ICompilation typeSystem; ICompilation typeSystem;
/// <summary> /// <summary>
@ -368,162 +384,155 @@ namespace ICSharpCode.ILSpy
return debugInfoProvider; return debugInfoProvider;
} }
[ThreadStatic] sealed class MyAssemblyResolver : IAssemblyResolver
static int assemblyLoadDisableCount;
public static IDisposable DisableAssemblyLoad(AssemblyList assemblyList)
{ {
assemblyLoadDisableCount++; readonly LoadedAssembly parent;
return new DecrementAssemblyLoadDisableCount(assemblyList); readonly bool loadOnDemand;
}
public static IDisposable DisableAssemblyLoad() readonly IAssemblyResolver providedAssemblyResolver;
{ readonly AssemblyList assemblyList;
assemblyLoadDisableCount++; readonly LoadedAssembly[] alreadyLoadedAssemblies;
return new DecrementAssemblyLoadDisableCount(MainWindow.Instance.CurrentAssemblyList); readonly Task<string> tfmTask;
} readonly ReferenceLoadInfo referenceLoadInfo;
sealed class DecrementAssemblyLoadDisableCount : IDisposable public MyAssemblyResolver(LoadedAssembly parent, bool loadOnDemand)
{ {
bool disposed; this.parent = parent;
AssemblyList assemblyList; this.loadOnDemand = loadOnDemand;
public DecrementAssemblyLoadDisableCount(AssemblyList assemblyList) this.providedAssemblyResolver = parent.providedAssemblyResolver;
{ this.assemblyList = parent.assemblyList;
this.assemblyList = 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 void Dispose() public PEFile Resolve(IAssemblyReference reference)
{
if (!disposed)
{ {
disposed = true; return ResolveAsync(reference).GetAwaiter().GetResult();
assemblyLoadDisableCount--;
// clear the lookup cache since we might have stored the lookups failed due to DisableAssemblyLoad()
assemblyList.ClearCache();
}
}
} }
sealed class MyAssemblyResolver : IAssemblyResolver Dictionary<string, PEFile> asmLookupByFullName;
{ Dictionary<string, PEFile> asmLookupByShortName;
readonly LoadedAssembly parent;
public MyAssemblyResolver(LoadedAssembly parent) /// <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)
{ {
this.parent = parent; PEFile module;
} // 0) if we're inside a package, look for filename.dll in parent directories
if (providedAssemblyResolver != null)
public PEFile Resolve(IAssemblyReference reference)
{ {
var module = parent.providedAssemblyResolver?.Resolve(reference); module = await providedAssemblyResolver.ResolveAsync(reference).ConfigureAwait(false);
if (module != null) if (module != null)
return module; return module;
return parent.LookupReferencedAssembly(reference)?.GetPEFileOrNull();
} }
public PEFile ResolveModule(PEFile mainModule, string moduleName) string tfm = await tfmTask.ConfigureAwait(false);
{
var module = parent.providedAssemblyResolver?.ResolveModule(mainModule, moduleName);
if (module != null)
return module;
return parent.LookupReferencedModule(mainModule, moduleName)?.GetPEFileOrNull();
}
}
readonly MyAssemblyResolver resolver; bool isWinRT = reference.IsWindowsRuntime;
string key = tfm + ";" + (isWinRT ? reference.Name : reference.FullName);
public IAssemblyResolver GetAssemblyResolver() // 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)
{ {
return resolver; lookup = await CreateLoadedAssemblyLookupAsync(shortNames: isWinRT).ConfigureAwait(false);
lookup = LazyInit.GetOrSet(ref isWinRT ? ref asmLookupByShortName : ref asmLookupByFullName, lookup);
} }
if (lookup.TryGetValue(key, out module))
public AssemblyReferenceClassifier GetAssemblyReferenceClassifier()
{ {
return universalResolver; referenceLoadInfo.AddMessageOnce(reference.FullName, MessageKind.Info, "Success - Found in Assembly List");
return module;
} }
/// <summary> string file = parent.GetUniversalResolver().FindAssemblyFile(reference);
/// 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 (file != null)
{ {
if (reference == null) // Load assembly from disk
throw new ArgumentNullException(nameof(reference)); LoadedAssembly asm;
var tfm = GetTargetFrameworkIdAsync().Result; if (loadOnDemand)
if (reference.IsWindowsRuntime)
{ {
return assemblyList.assemblyLookupCache.GetOrAdd((reference.Name, true, tfm), key => LookupReferencedAssemblyInternal(reference, true, tfm)); asm = assemblyList.OpenAssembly(file, isAutoLoaded: true);
} }
else else
{ {
return assemblyList.assemblyLookupCache.GetOrAdd((reference.FullName, false, tfm), key => LookupReferencedAssemblyInternal(reference, false, tfm)); asm = assemblyList.FindAssembly(file);
}
} }
if (asm != null)
public LoadedAssembly LookupReferencedModule(PEFile mainModule, string moduleName)
{ {
if (mainModule == null) referenceLoadInfo.AddMessage(reference.ToString(), MessageKind.Info, "Success - Loading from: " + file);
throw new ArgumentNullException(nameof(mainModule)); return await asm.GetPEFileOrNullAsync().ConfigureAwait(false);
if (moduleName == null) }
throw new ArgumentNullException(nameof(moduleName)); return null;
return assemblyList.moduleLookupCache.GetOrAdd(mainModule.FileName + ";" + moduleName, _ => LookupReferencedModuleInternal(mainModule, moduleName));
} }
else
{
// Assembly not found; try to find a similar-enough already-loaded assembly
var candidates = new List<(LoadedAssembly assembly, Version version)>();
class MyUniversalResolver : UniversalAssemblyResolver foreach (LoadedAssembly loaded in alreadyLoadedAssemblies)
{ {
public MyUniversalResolver(LoadedAssembly assembly) module = await loaded.GetPEFileOrNullAsync().ConfigureAwait(false);
: base(assembly.FileName, false, assembly.GetTargetFrameworkIdAsync().Result, PEStreamOptions.PrefetchEntireImage, DecompilerSettingsPanel.CurrentDecompilerSettings.ApplyWindowsRuntimeProjections ? MetadataReaderOptions.ApplyWindowsRuntimeProjections : MetadataReaderOptions.None) var reader = module?.Metadata;
if (reader == null || !reader.IsAssembly)
continue;
var asmDef = reader.GetAssemblyDefinition();
var asmDefName = reader.GetString(asmDef.Name);
if (reference.Name.Equals(asmDefName, StringComparison.OrdinalIgnoreCase))
{ {
candidates.Add((loaded, asmDef.Version));
} }
} }
static readonly Dictionary<string, LoadedAssembly> loadingAssemblies = new Dictionary<string, LoadedAssembly>(); if (candidates.Count == 0)
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); referenceLoadInfo.AddMessageOnce(reference.ToString(), MessageKind.Error, "Could not find reference: " + reference);
return null;
}
string file; candidates.SortBy(c => c.version);
LoadedAssembly asm;
lock (loadingAssemblies) 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);
}
}
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)
{ {
try try
{ {
var module = loaded.GetPEFileOrNull(); var module = await loaded.GetPEFileOrNullAsync().ConfigureAwait(false);
var reader = module?.Metadata; var reader = module?.Metadata;
if (reader == null || !reader.IsAssembly) if (reader == null || !reader.IsAssembly)
continue; continue;
var asmDef = reader.GetAssemblyDefinition(); var asmDef = reader.GetAssemblyDefinition();
var asmDefName = loaded.GetTargetFrameworkIdAsync().Result + ";" string tfm = await loaded.GetTargetFrameworkIdAsync();
+ (isWinRT ? reader.GetString(asmDef.Name) : reader.GetFullAssemblyName()); string key = tfm + ";"
if (key.Equals(asmDefName, StringComparison.OrdinalIgnoreCase)) + (shortNames ? reader.GetString(asmDef.Name) : reader.GetFullAssemblyName());
if (!result.ContainsKey(key))
{ {
LoadedAssemblyReferencesInfo.AddMessageOnce(fullName.FullName, MessageKind.Info, "Success - Found in Assembly List"); result.Add(key, module);
return loaded;
} }
} }
catch (BadImageFormatException) catch (BadImageFormatException)
@ -531,145 +540,97 @@ namespace ICSharpCode.ILSpy
continue; continue;
} }
} }
return result;
}
if (universalResolver == null) public PEFile ResolveModule(PEFile mainModule, string moduleName)
{ {
universalResolver = new MyUniversalResolver(this); return ResolveModuleAsync(mainModule, moduleName).GetAwaiter().GetResult();
} }
file = universalResolver.FindAssemblyFile(fullName); public async Task<PEFile> ResolveModuleAsync(PEFile mainModule, string moduleName)
foreach (LoadedAssembly loaded in assemblyList.GetAssemblies())
{ {
if (loaded.FileName.Equals(file, StringComparison.OrdinalIgnoreCase)) if (providedAssemblyResolver != null)
{ {
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)
{
LoadedAssemblyReferencesInfo.AddMessage(fullName.ToString(), MessageKind.Info, "Success - Loading from: " + file);
asm = new LoadedAssembly(assemblyList, file) { IsAutoLoaded = true };
}
else
{
var candidates = new List<(LoadedAssembly assembly, Version version)>();
foreach (LoadedAssembly loaded in assemblyList.GetAssemblies()) string file = Path.Combine(Path.GetDirectoryName(mainModule.FileName), moduleName);
if (File.Exists(file))
{ {
var module = loaded.GetPEFileOrNull(); // Load module from disk
var reader = module?.Metadata; LoadedAssembly asm;
if (reader == null || !reader.IsAssembly) if (loadOnDemand)
continue;
var asmDef = reader.GetAssemblyDefinition();
var asmDefName = reader.GetString(asmDef.Name);
if (fullName.Name.Equals(asmDefName, StringComparison.OrdinalIgnoreCase))
{
candidates.Add((loaded, asmDef.Version));
}
}
if (candidates.Count == 0)
{ {
LoadedAssemblyReferencesInfo.AddMessageOnce(fullName.ToString(), MessageKind.Error, "Could not find reference: " + fullName); asm = assemblyList.OpenAssembly(file, isAutoLoaded: true);
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;
} }
loadingAssemblies.Add(file, asm); else
}
App.Current.Dispatcher.BeginInvoke((Action)delegate () {
lock (assemblyList.assemblies)
{ {
assemblyList.assemblies.Add(asm); asm = assemblyList.FindAssembly(file);
} }
lock (loadingAssemblies) if (asm != null)
{ {
loadingAssemblies.Remove(file); return await asm.GetPEFileOrNullAsync().ConfigureAwait(false);
} }
}, DispatcherPriority.Normal);
return asm;
} }
else
LoadedAssembly LookupReferencedModuleInternal(PEFile mainModule, string moduleName)
{
string file;
LoadedAssembly asm;
lock (loadingAssemblies)
{ {
foreach (LoadedAssembly loaded in assemblyList.GetAssemblies()) // Module does not exist on disk, look for one with a matching name in the assemblylist:
foreach (LoadedAssembly loaded in alreadyLoadedAssemblies)
{ {
var reader = loaded.GetPEFileOrNull()?.Metadata; var module = await loaded.GetPEFileOrNullAsync().ConfigureAwait(false);
var reader = module?.Metadata;
if (reader == null || reader.IsAssembly) if (reader == null || reader.IsAssembly)
continue; continue;
var moduleDef = reader.GetModuleDefinition(); var moduleDef = reader.GetModuleDefinition();
if (moduleName.Equals(reader.GetString(moduleDef.Name), StringComparison.OrdinalIgnoreCase)) if (moduleName.Equals(reader.GetString(moduleDef.Name), StringComparison.OrdinalIgnoreCase))
{ {
LoadedAssemblyReferencesInfo.AddMessageOnce(moduleName, MessageKind.Info, "Success - Found in Assembly List"); referenceLoadInfo.AddMessageOnce(moduleName, MessageKind.Info, "Success - Found in Assembly List");
return loaded; return module;
}
} }
} }
file = Path.Combine(Path.GetDirectoryName(mainModule.FileName), moduleName);
if (!File.Exists(file))
return null; return null;
foreach (LoadedAssembly loaded in assemblyList.GetAssemblies())
{
if (loaded.FileName.Equals(file, StringComparison.OrdinalIgnoreCase))
{
return loaded;
} }
} }
if (file != null && loadingAssemblies.TryGetValue(file, out asm)) public IAssemblyResolver GetAssemblyResolver(bool loadOnDemand = true)
return asm; {
return new MyAssemblyResolver(this, loadOnDemand);
}
if (assemblyLoadDisableCount > 0) private MyUniversalResolver GetUniversalResolver()
return null; {
return LazyInitializer.EnsureInitialized(ref this.universalResolver, () => new MyUniversalResolver(this));
}
if (file != null) public AssemblyReferenceClassifier GetAssemblyReferenceClassifier()
{ {
LoadedAssemblyReferencesInfo.AddMessage(moduleName, MessageKind.Info, "Success - Loading from: " + file); return GetUniversalResolver();
asm = new LoadedAssembly(assemblyList, file) { IsAutoLoaded = true };
} }
else
/// <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()
{ {
LoadedAssemblyReferencesInfo.AddMessageOnce(moduleName, MessageKind.Error, "Could not find reference: " + moduleName); if (GetPEFileOrNull() == null)
return null; return null;
return debugInfoProvider;
} }
loadingAssemblies.Add(file, asm);
} class MyUniversalResolver : UniversalAssemblyResolver
App.Current.Dispatcher.BeginInvoke((Action)delegate () {
lock (assemblyList.assemblies)
{ {
assemblyList.assemblies.Add(asm); public MyUniversalResolver(LoadedAssembly assembly)
} : base(assembly.FileName, false, assembly.GetTargetFrameworkIdAsync().Result, PEStreamOptions.PrefetchEntireImage, DecompilerSettingsPanel.CurrentDecompilerSettings.ApplyWindowsRuntimeProjections ? MetadataReaderOptions.ApplyWindowsRuntimeProjections : MetadataReaderOptions.None)
lock (loadingAssemblies)
{ {
loadingAssemblies.Remove(file);
} }
});
return asm;
} }
[Obsolete("Use GetPEFileAsync() or GetLoadResultAsync() instead")] MyUniversalResolver universalResolver;
public Task ContinueWhenLoaded(Action<Task<PEFile>> onAssemblyLoaded, TaskScheduler taskScheduler)
{
return this.GetPEFileAsync().ContinueWith(onAssemblyLoaded, default(CancellationToken), TaskContinuationOptions.RunContinuationsAsynchronously, taskScheduler);
}
/// <summary> /// <summary>
/// Wait until the assembly is loaded. /// Wait until the assembly is loaded.

9
ILSpy/LoadedAssemblyExtensions.cs

@ -1,10 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using ICSharpCode.Decompiler.DebugInfo; using ICSharpCode.Decompiler.DebugInfo;
using ICSharpCode.Decompiler.Metadata; using ICSharpCode.Decompiler.Metadata;
@ -31,9 +26,9 @@ namespace ICSharpCode.ILSpy
return Mono.Cecil.ModuleDefinition.ReadModule(new UnmanagedMemoryStream(image.Pointer, image.Length)); 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) public static IDebugInfoProvider GetDebugInfoOrNull(this PEFile file)

28
ILSpy/LoadedPackage.cs

@ -240,6 +240,20 @@ namespace ICSharpCode.ILSpy
return parent?.Resolve(reference); return parent?.Resolve(reference);
} }
public Task<PEFile> 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) public PEFile ResolveModule(PEFile mainModule, string moduleName)
{ {
var asm = ResolveFileName(moduleName + ".dll"); var asm = ResolveFileName(moduleName + ".dll");
@ -250,6 +264,20 @@ namespace ICSharpCode.ILSpy
return parent?.ResolveModule(mainModule, moduleName); return parent?.ResolveModule(mainModule, moduleName);
} }
public Task<PEFile> 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<string, LoadedAssembly> assemblies = new Dictionary<string, LoadedAssembly>(StringComparer.OrdinalIgnoreCase); readonly Dictionary<string, LoadedAssembly> assemblies = new Dictionary<string, LoadedAssembly>(StringComparer.OrdinalIgnoreCase);
internal LoadedAssembly ResolveFileName(string name) internal LoadedAssembly ResolveFileName(string name)

2
ILSpy/MainWindow.xaml.cs

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

25
ILSpy/TreeNodes/AssemblyListTreeNode.cs

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

7
ILSpy/TreeNodes/AssemblyTreeNode.cs

@ -20,6 +20,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Documents; using System.Windows.Documents;
@ -460,10 +461,11 @@ namespace ICSharpCode.ILSpy.TreeNodes
return true; return true;
} }
public void Execute(TextViewContext context) public async void Execute(TextViewContext context)
{ {
if (context.SelectedTreeNodes == null) if (context.SelectedTreeNodes == null)
return; return;
var tasks = new List<Task>();
foreach (var node in context.SelectedTreeNodes) foreach (var node in context.SelectedTreeNodes)
{ {
var la = ((AssemblyTreeNode)node).LoadedAssembly; var la = ((AssemblyTreeNode)node).LoadedAssembly;
@ -474,10 +476,11 @@ namespace ICSharpCode.ILSpy.TreeNodes
var metadata = module.Metadata; var metadata = module.Metadata;
foreach (var assyRef in metadata.AssemblyReferences) 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(); MainWindow.Instance.RefreshDecompiledView();
} }
} }

7
ILSpy/TreeNodes/ModuleReferenceTreeNode.cs

@ -71,10 +71,15 @@ namespace ICSharpCode.ILSpy.TreeNodes
var assemblyListNode = parentAssembly.Parent as AssemblyListTreeNode; var assemblyListNode = parentAssembly.Parent as AssemblyListTreeNode;
if (assemblyListNode != null && containsMetadata) if (assemblyListNode != null && containsMetadata)
{ {
assemblyListNode.Select(assemblyListNode.FindAssemblyNode(parentAssembly.LoadedAssembly.LookupReferencedModule(parentAssembly.LoadedAssembly.GetPEFileOrNull(), metadata.GetString(reference.Name)))); 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; e.Handled = true;
} }
} }
}
public override void Decompile(Language language, ITextOutput output, DecompilationOptions options) public override void Decompile(Language language, ITextOutput output, DecompilationOptions options)
{ {

2
ILSpy/ViewModels/ManageAssemblyListsViewModel.cs

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

Loading…
Cancel
Save