Browse Source

Simplify the assembly-resolving implementation.

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

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))

158
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,7 +323,10 @@ 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 (assemblies)
@ -268,6 +343,7 @@ namespace ICSharpCode.ILSpy
lock (assemblies) lock (assemblies)
{ {
assemblies.Remove(assembly); assemblies.Remove(assembly);
byFilename.Remove(assembly.FileName);
} }
RequestGC(); RequestGC();
} }

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;
} }

377
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)
@ -386,199 +384,155 @@ namespace ICSharpCode.ILSpy
return debugInfoProvider; 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 sealed class MyAssemblyResolver : IAssemblyResolver
{ {
readonly LoadedAssembly parent; readonly LoadedAssembly parent;
readonly bool loadOnDemand;
public MyAssemblyResolver(LoadedAssembly parent) readonly IAssemblyResolver providedAssemblyResolver;
readonly AssemblyList assemblyList;
readonly LoadedAssembly[] alreadyLoadedAssemblies;
readonly Task<string> tfmTask;
readonly ReferenceLoadInfo referenceLoadInfo;
public MyAssemblyResolver(LoadedAssembly parent, bool loadOnDemand)
{ {
this.parent = parent; this.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) public PEFile Resolve(IAssemblyReference reference)
{ {
var module = parent.providedAssemblyResolver?.Resolve(reference); return ResolveAsync(reference).GetAwaiter().GetResult();
if (module != null)
return module;
return parent.LookupReferencedAssembly(reference)?.GetPEFileOrNull();
} }
public PEFile ResolveModule(PEFile mainModule, string moduleName) Dictionary<string, PEFile> asmLookupByFullName;
{ Dictionary<string, PEFile> asmLookupByShortName;
var module = parent.providedAssemblyResolver?.ResolveModule(mainModule, moduleName);
if (module != null)
return module;
return parent.LookupReferencedModule(mainModule, moduleName)?.GetPEFileOrNull();
}
/// <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) public async Task<PEFile> ResolveAsync(IAssemblyReference reference)
{ {
if (parent.providedAssemblyResolver != null) PEFile module;
// 0) if we're inside a package, look for filename.dll in parent directories
if (providedAssemblyResolver != null)
{ {
var module = await parent.providedAssemblyResolver.ResolveAsync(reference).ConfigureAwait(false); module = await providedAssemblyResolver.ResolveAsync(reference).ConfigureAwait(false);
if (module != null) if (module != null)
return module; return module;
} }
var asm = parent.LookupReferencedAssembly(reference);
if (asm != null)
{
return await asm.GetPEFileOrNullAsync().ConfigureAwait(false);
}
return null;
}
public async Task<PEFile> ResolveModuleAsync(PEFile mainModule, string moduleName) string tfm = await tfmTask.ConfigureAwait(false);
{
if (parent.providedAssemblyResolver != null) 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); lookup = await CreateLoadedAssemblyLookupAsync(shortNames: isWinRT).ConfigureAwait(false);
if (module != null) lookup = LazyInit.GetOrSet(ref isWinRT ? ref asmLookupByShortName : ref asmLookupByFullName, lookup);
return module;
} }
var asm = parent.LookupReferencedModule(mainModule, moduleName); if (lookup.TryGetValue(key, out module))
if (asm != null)
{ {
return await asm.GetPEFileOrNullAsync().ConfigureAwait(false); referenceLoadInfo.AddMessageOnce(reference.FullName, MessageKind.Info, "Success - Found in Assembly List");
} return module;
return null;
}
} }
readonly MyAssemblyResolver resolver; string file = parent.GetUniversalResolver().FindAssemblyFile(reference);
public IAssemblyResolver GetAssemblyResolver() if (file != null)
{ {
return resolver; // Load assembly from disk
} LoadedAssembly asm;
if (loadOnDemand)
private MyUniversalResolver GetUniversalResolver()
{ {
return LazyInitializer.EnsureInitialized(ref this.universalResolver, () => new MyUniversalResolver(this)); asm = assemblyList.OpenAssembly(file, isAutoLoaded: true);
} }
else
public AssemblyReferenceClassifier GetAssemblyReferenceClassifier()
{ {
return GetUniversalResolver(); asm = assemblyList.FindAssembly(file);
} }
if (asm != null)
/// <summary>
/// Returns the debug info for this assembly. Returns null in case of load errors or no debug info is available.
/// </summary>
public IDebugInfoProvider GetDebugInfoOrNull()
{ {
if (GetPEFileOrNull() == null) referenceLoadInfo.AddMessage(reference.ToString(), MessageKind.Info, "Success - Loading from: " + file);
return await asm.GetPEFileOrNullAsync().ConfigureAwait(false);
}
return null; return null;
return debugInfoProvider;
} }
else
public LoadedAssembly LookupReferencedAssembly(IAssemblyReference reference)
{ {
if (reference == null) // Assembly not found; try to find a similar-enough already-loaded assembly
throw new ArgumentNullException(nameof(reference)); var candidates = new List<(LoadedAssembly assembly, Version version)>();
var tfm = GetTargetFrameworkIdAsync().Result;
if (reference.IsWindowsRuntime) foreach (LoadedAssembly loaded in alreadyLoadedAssemblies)
{ {
return assemblyList.assemblyLookupCache.GetOrAdd((reference.Name, true, tfm), key => LookupReferencedAssemblyInternal(reference, true, tfm)); module = await loaded.GetPEFileOrNullAsync().ConfigureAwait(false);
} var reader = module?.Metadata;
else if (reader == null || !reader.IsAssembly)
continue;
var asmDef = reader.GetAssemblyDefinition();
var asmDefName = reader.GetString(asmDef.Name);
if (reference.Name.Equals(asmDefName, StringComparison.OrdinalIgnoreCase))
{ {
return assemblyList.assemblyLookupCache.GetOrAdd((reference.FullName, false, tfm), key => LookupReferencedAssemblyInternal(reference, false, tfm)); candidates.Add((loaded, asmDef.Version));
} }
} }
public LoadedAssembly LookupReferencedModule(PEFile mainModule, string moduleName) if (candidates.Count == 0)
{ {
if (mainModule == null) referenceLoadInfo.AddMessageOnce(reference.ToString(), MessageKind.Error, "Could not find reference: " + reference);
throw new ArgumentNullException(nameof(mainModule)); return null;
if (moduleName == null)
throw new ArgumentNullException(nameof(moduleName));
return assemblyList.moduleLookupCache.GetOrAdd(mainModule.FileName + ";" + moduleName, _ => LookupReferencedModuleInternal(mainModule, moduleName));
} }
class MyUniversalResolver : UniversalAssemblyResolver candidates.SortBy(c => c.version);
{
public MyUniversalResolver(LoadedAssembly assembly) var bestCandidate = candidates.FirstOrDefault(c => c.version >= reference.Version).assembly ?? candidates.Last().assembly;
: base(assembly.FileName, false, assembly.GetTargetFrameworkIdAsync().Result, PEStreamOptions.PrefetchEntireImage, DecompilerSettingsPanel.CurrentDecompilerSettings.ApplyWindowsRuntimeProjections ? MetadataReaderOptions.ApplyWindowsRuntimeProjections : MetadataReaderOptions.None) referenceLoadInfo.AddMessageOnce(reference.ToString(), MessageKind.Info, "Success - Found in Assembly List with different TFM or version: " + bestCandidate.fileName);
{ return await bestCandidate.GetPEFileOrNullAsync().ConfigureAwait(false);
} }
} }
static readonly Dictionary<string, LoadedAssembly> loadingAssemblies = new Dictionary<string, LoadedAssembly>(); private async Task<Dictionary<string, PEFile>> CreateLoadedAssemblyLookupAsync(bool shortNames)
MyUniversalResolver universalResolver;
/// <summary>
/// 0) if we're inside a package, look for filename.dll in parent directories
/// (this step already happens in MyAssemblyResolver; not in LookupReferencedAssembly)
/// 1) try to find exact match by tfm + full asm name in loaded assemblies
/// 2) try to find match in search paths
/// 3) if a.deps.json is found: search %USERPROFILE%/.nuget/packages/* as well
/// 4) look in /dotnet/shared/{runtime-pack}/{closest-version}
/// 5) if the version is retargetable or all zeros or ones, search C:\Windows\Microsoft.NET\Framework64\v4.0.30319
/// 6) For "mscorlib.dll" we use the exact same assembly with which ILSpy runs
/// 7) Search the GAC
/// 8) search C:\Windows\Microsoft.NET\Framework64\v4.0.30319
/// 9) try to find match by asm name (no tfm/version) in loaded assemblies
/// </summary>
LoadedAssembly LookupReferencedAssemblyInternal(IAssemblyReference fullName, bool isWinRT, string tfm)
{
string key = tfm + ";" + (isWinRT ? fullName.Name : fullName.FullName);
string file;
LoadedAssembly asm;
lock (loadingAssemblies)
{ {
foreach (LoadedAssembly loaded in assemblyList.GetAssemblies()) 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)
@ -586,140 +540,97 @@ namespace ICSharpCode.ILSpy
continue; continue;
} }
} }
return result;
file = GetUniversalResolver().FindAssemblyFile(fullName);
foreach (LoadedAssembly loaded in assemblyList.GetAssemblies())
{
if (loaded.FileName.Equals(file, StringComparison.OrdinalIgnoreCase))
{
return loaded;
}
} }
if (file != null && loadingAssemblies.TryGetValue(file, out asm)) public PEFile ResolveModule(PEFile mainModule, string moduleName)
return asm;
if (assemblyLoadDisableCount > 0)
return null;
if (file != null)
{ {
LoadedAssemblyReferencesInfo.AddMessage(fullName.ToString(), MessageKind.Info, "Success - Loading from: " + file); return ResolveModuleAsync(mainModule, moduleName).GetAwaiter().GetResult();
asm = new LoadedAssembly(assemblyList, file) { IsAutoLoaded = true };
} }
else
{
var candidates = new List<(LoadedAssembly assembly, Version version)>();
foreach (LoadedAssembly loaded in assemblyList.GetAssemblies()) public async Task<PEFile> ResolveModuleAsync(PEFile mainModule, string moduleName)
{
var module = loaded.GetPEFileOrNull();
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))
{ {
candidates.Add((loaded, asmDef.Version)); if (providedAssemblyResolver != null)
}
}
if (candidates.Count == 0)
{ {
LoadedAssemblyReferencesInfo.AddMessageOnce(fullName.ToString(), MessageKind.Error, "Could not find reference: " + fullName); var module = await providedAssemblyResolver.ResolveModuleAsync(mainModule, moduleName).ConfigureAwait(false);
return null; if (module != null)
return module;
} }
candidates.SortBy(c => c.version);
var bestCandidate = candidates.FirstOrDefault(c => c.version >= fullName.Version).assembly ?? candidates.Last().assembly; string file = Path.Combine(Path.GetDirectoryName(mainModule.FileName), moduleName);
LoadedAssemblyReferencesInfo.AddMessageOnce(fullName.ToString(), MessageKind.Info, "Success - Found in Assembly List with different TFM or version: " + bestCandidate.fileName); if (File.Exists(file))
return bestCandidate; {
} // Load module from disk
loadingAssemblies.Add(file, asm); LoadedAssembly asm;
if (loadOnDemand)
{
asm = assemblyList.OpenAssembly(file, isAutoLoaded: true);
} }
App.Current.Dispatcher.BeginInvoke((Action)delegate () { else
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)

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/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