From 4f8c588c7b6b932b4341f699fb42f1f0783f6e02 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sat, 25 Jul 2020 14:32:43 +0200 Subject: [PATCH] Fix #2068: ILSpy can't find referenced library even though it's open --- .../Metadata/DotNetCorePathFinder.cs | 45 ++++++++++--------- .../Metadata/UniversalAssemblyResolver.cs | 10 +++-- ILSpy/LoadedAssembly.cs | 38 +++++++++++++++- 3 files changed, 67 insertions(+), 26 deletions(-) diff --git a/ICSharpCode.Decompiler/Metadata/DotNetCorePathFinder.cs b/ICSharpCode.Decompiler/Metadata/DotNetCorePathFinder.cs index 740ecc0d4..e118bd05b 100644 --- a/ICSharpCode.Decompiler/Metadata/DotNetCorePathFinder.cs +++ b/ICSharpCode.Decompiler/Metadata/DotNetCorePathFinder.cs @@ -61,29 +61,30 @@ namespace ICSharpCode.Decompiler.Metadata }; readonly DotNetCorePackageInfo[] packages; - ISet packageBasePaths = new HashSet(StringComparer.Ordinal); - readonly Version version; + readonly List searchPaths = new List(); + readonly List packageBasePaths = new List(); + readonly Version targetFrameworkVersion; readonly string dotnetBasePath = FindDotNetExeDirectory(); - public DotNetCorePathFinder(Version version) + public DotNetCorePathFinder(Version targetFrameworkVersion) { - this.version = version; + this.targetFrameworkVersion = targetFrameworkVersion; } - public DotNetCorePathFinder(string parentAssemblyFileName, string targetFrameworkIdString, TargetFrameworkIdentifier targetFramework, Version version, ReferenceLoadInfo loadInfo = null) + public DotNetCorePathFinder(string parentAssemblyFileName, string targetFrameworkIdString, TargetFrameworkIdentifier targetFramework, Version targetFrameworkVersion, ReferenceLoadInfo loadInfo = null) { string assemblyName = Path.GetFileNameWithoutExtension(parentAssemblyFileName); string basePath = Path.GetDirectoryName(parentAssemblyFileName); - this.version = version; + this.targetFrameworkVersion = targetFrameworkVersion; if (targetFramework == TargetFrameworkIdentifier.NETStandard) { // .NET Standard 2.1 is implemented by .NET Core 3.0 or higher - if (version.Major == 2 && version.Minor == 1) { - this.version = new Version(3, 0, 0); + if (targetFrameworkVersion.Major == 2 && targetFrameworkVersion.Minor == 1) { + this.targetFrameworkVersion = new Version(3, 0, 0); } } - packageBasePaths.Add(basePath); + searchPaths.Add(basePath); var depsJsonFileName = Path.Combine(basePath, $"{assemblyName}.deps.json"); if (File.Exists(depsJsonFileName)) { @@ -106,17 +107,17 @@ namespace ICSharpCode.Decompiler.Metadata public void AddSearchDirectory(string path) { - this.packageBasePaths.Add(path); + this.searchPaths.Add(path); } public void RemoveSearchDirectory(string path) { - this.packageBasePaths.Remove(path); + this.searchPaths.Remove(path); } public string TryResolveDotNetCore(IAssemblyReference name) { - foreach (var basePath in packageBasePaths) { + foreach (var basePath in searchPaths.Concat(packageBasePaths)) { if (File.Exists(Path.Combine(basePath, name.Name + ".dll"))) { return Path.Combine(basePath, name.Name + ".dll"); } else if (File.Exists(Path.Combine(basePath, name.Name + ".exe"))) { @@ -124,7 +125,7 @@ namespace ICSharpCode.Decompiler.Metadata } } - return FallbackToDotNetSharedDirectory(name, version); + return FallbackToDotNetSharedDirectory(name); } internal string GetReferenceAssemblyPath(string targetFramework) @@ -169,7 +170,7 @@ namespace ICSharpCode.Decompiler.Metadata } } - string FallbackToDotNetSharedDirectory(IAssemblyReference name, Version version) + string FallbackToDotNetSharedDirectory(IAssemblyReference name) { if (dotnetBasePath == null) return null; @@ -177,7 +178,7 @@ namespace ICSharpCode.Decompiler.Metadata foreach (var basePath in basePaths) { if (!Directory.Exists(basePath)) continue; - var closestVersion = GetClosestVersionFolder(basePath, version); + var closestVersion = GetClosestVersionFolder(basePath, targetFrameworkVersion); if (File.Exists(Path.Combine(basePath, closestVersion, name.Name + ".dll"))) { return Path.Combine(basePath, closestVersion, name.Name + ".dll"); } else if (File.Exists(Path.Combine(basePath, closestVersion, name.Name + ".exe"))) { @@ -189,15 +190,17 @@ namespace ICSharpCode.Decompiler.Metadata static string GetClosestVersionFolder(string basePath, Version version) { - string result = null; - foreach (var folder in new DirectoryInfo(basePath).GetDirectories().Select(d => ConvertToVersion(d.Name)).Where(v => v.Item1 != null).OrderByDescending(v => v.Item1)) { - if (folder.Item1 >= version) - result = folder.Item2; + var foundVersions = new DirectoryInfo(basePath).GetDirectories() + .Select(d => ConvertToVersion(d.Name)) + .Where(v => v.version != null); + foreach (var folder in foundVersions.OrderBy(v => v.Item1)) { + if (folder.version >= version) + return folder.directoryName; } - return result ?? version.ToString(); + return version.ToString(); } - internal static (Version, string) ConvertToVersion(string name) + internal static (Version version, string directoryName) ConvertToVersion(string name) { string RemoveTrailingVersionInfo() { diff --git a/ICSharpCode.Decompiler/Metadata/UniversalAssemblyResolver.cs b/ICSharpCode.Decompiler/Metadata/UniversalAssemblyResolver.cs index 3186fa3d3..1f4108302 100644 --- a/ICSharpCode.Decompiler/Metadata/UniversalAssemblyResolver.cs +++ b/ICSharpCode.Decompiler/Metadata/UniversalAssemblyResolver.cs @@ -327,9 +327,13 @@ namespace ICSharpCode.Decompiler.Metadata if (assembly != null) return assembly; - assembly = SearchDirectory(name, framework_dirs); - if (assembly != null) - return assembly; + // when decompiling assemblies that target frameworks prior to 4.0, we can fall back to the 4.0 assemblies in case the target framework is not installed. + // but when looking for Microsoft.Build.Framework, Version=15.0.0.0 we should not use the version 4.0 assembly here so that the LoadedAssembly logic can instead fall back to version 15.1.0.0 + if (name.Version <= new Version(4, 0, 0, 0)) { + assembly = SearchDirectory(name, framework_dirs); + if (assembly != null) + return assembly; + } if (throwOnError) throw new AssemblyResolutionException(name); diff --git a/ILSpy/LoadedAssembly.cs b/ILSpy/LoadedAssembly.cs index fc0e1bcdc..8f48a19e2 100644 --- a/ILSpy/LoadedAssembly.cs +++ b/ILSpy/LoadedAssembly.cs @@ -20,6 +20,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Linq; using System.Reflection.Metadata; using System.Reflection.PortableExecutable; using System.Runtime.CompilerServices; @@ -31,6 +32,7 @@ using ICSharpCode.Decompiler.Metadata; using ICSharpCode.Decompiler.PdbProvider; using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.TypeSystem.Implementation; +using ICSharpCode.Decompiler.Util; using ICSharpCode.ILSpy.Options; namespace ICSharpCode.ILSpy @@ -318,6 +320,17 @@ namespace ICSharpCode.ILSpy static readonly Dictionary loadingAssemblies = new Dictionary(); MyUniversalResolver universalResolver; + /// + /// 1) try to find exact match by tfm + full asm name in loaded assemblies + /// 2) try to find match in search paths + /// 3) if a.deps.json is found: search %USERPROFILE%/.nuget/packages/* as well + /// 4) look in /dotnet/shared/{runtime-pack}/{closest-version} + /// 5) if the version is retargetable or all zeros or ones, search C:\Windows\Microsoft.NET\Framework64\v4.0.30319 + /// 6) For "mscorlib.dll" we use the exact same assembly with which ILSpy runs + /// 7) Search the GAC + /// 8) search C:\Windows\Microsoft.NET\Framework64\v4.0.30319 + /// 9) try to find match by asm name (no tfm/version) in loaded assemblies + /// LoadedAssembly LookupReferencedAssemblyInternal(IAssemblyReference fullName, bool isWinRT, string tfm) { string key = tfm + ";" + (isWinRT ? fullName.Name : fullName.FullName); @@ -359,8 +372,29 @@ namespace ICSharpCode.ILSpy LoadedAssemblyReferencesInfo.AddMessage(fullName.ToString(), MessageKind.Info, "Success - Loading from: " + file); asm = new LoadedAssembly(assemblyList, file) { IsAutoLoaded = true }; } else { - LoadedAssemblyReferencesInfo.AddMessageOnce(fullName.ToString(), MessageKind.Error, "Could not find reference: " + fullName); - return null; + var candidates = new List<(LoadedAssembly assembly, Version version)>(); + + foreach (LoadedAssembly loaded in assemblyList.GetAssemblies()) { + 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 (candidates.Count == 0) { + LoadedAssemblyReferencesInfo.AddMessageOnce(fullName.ToString(), MessageKind.Error, "Could not find reference: " + fullName); + 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); }