From 7df1b2908d606188732dd1a7dea08e05a43391d2 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Tue, 5 Sep 2017 18:39:56 +0200 Subject: [PATCH] Basic implementation of DotNetCorePathFinder --- .../DotNetCore/DotNetCorePathFinder.cs | 116 ++++++++++++++++++ .../DotNetCorePathFinderExtensions.cs | 33 +++++ .../ICSharpCode.Decompiler.csproj | 2 + ILSpy/LoadedAssembly.cs | 43 +++++-- 4 files changed, 187 insertions(+), 7 deletions(-) create mode 100644 ICSharpCode.Decompiler/DotNetCore/DotNetCorePathFinder.cs create mode 100644 ICSharpCode.Decompiler/DotNetCore/DotNetCorePathFinderExtensions.cs diff --git a/ICSharpCode.Decompiler/DotNetCore/DotNetCorePathFinder.cs b/ICSharpCode.Decompiler/DotNetCore/DotNetCorePathFinder.cs new file mode 100644 index 000000000..f776a7053 --- /dev/null +++ b/ICSharpCode.Decompiler/DotNetCore/DotNetCorePathFinder.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using ICSharpCode.Decompiler.TypeSystem.Implementation; +using Mono.Cecil; +using Newtonsoft.Json.Linq; + +namespace ICSharpCode.Decompiler +{ + public class DotNetCorePathFinder + { + class DotNetCorePackageInfo + { + public readonly string Name; + public readonly string Version; + public readonly string Type; + public readonly string Path; + public readonly string[] RuntimeComponents; + + public DotNetCorePackageInfo(string fullName, string type, string path, string[] runtimeComponents) + { + var parts = fullName.Split('/'); + this.Name = parts[0]; + this.Version = parts[1]; + this.Type = type; + this.Path = path; + this.RuntimeComponents = runtimeComponents ?? new string[0]; + } + } + + static readonly string[] LookupPaths = new string[] { + Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".nuget\\packages") + }; + + readonly Dictionary packages; + ISet packageBasePaths = new HashSet(StringComparer.Ordinal); + readonly string assemblyName; + readonly string basePath; + readonly string targetFrameworkId; + readonly string version; + + public DotNetCorePathFinder(string parentAssemblyFileName, string targetFrameworkId, string version) + { + this.assemblyName = Path.GetFileNameWithoutExtension(parentAssemblyFileName); + this.basePath = Path.GetDirectoryName(parentAssemblyFileName); + this.targetFrameworkId = targetFrameworkId; + this.version = version; + + DecompilerEventSource.Log.Info($"Looking for {assemblyName}.deps.json in {basePath}"); + + var depsJsonFileName = Path.Combine(basePath, $"{assemblyName}.deps.json"); + if (!File.Exists(depsJsonFileName)) { + DecompilerEventSource.Log.Info($"{assemblyName}.deps.json not found."); + return; + } + + packages = LoadPackageInfos(depsJsonFileName, targetFrameworkId).ToDictionary(i => i.Name); + + foreach (var path in LookupPaths) { + foreach (var pk in packages) { + foreach (var item in pk.Value.RuntimeComponents) { + var itemPath = Path.GetDirectoryName(item); + var fullPath = Path.Combine(path, pk.Value.Name, pk.Value.Version, itemPath); + if (Directory.Exists(fullPath)) + packageBasePaths.Add(fullPath); + } + } + } + } + + public string TryResolveDotNetCore(AssemblyNameReference name) + { + DecompilerEventSource.Log.Info($"TryResolveDotNetCore: {assemblyName}, version: {version}, reference: {name}"); + + foreach (var basePath in packageBasePaths) { + DecompilerEventSource.Log.Info($"TryResolveDotNetCore: scanning {basePath}..."); + + 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"))) { + return Path.Combine(basePath, name.Name + ".exe"); + } + } + + return FallbackToDotNetSharedDirectory(name, version); + } + + static IEnumerable LoadPackageInfos(string depsJsonFileName, string targetFramework) + { + var dependencies = JObject.Parse(File.ReadAllText(depsJsonFileName)); + var runtimeInfos = dependencies["targets"][targetFramework].Children().OfType().ToArray(); + var libraries = dependencies["libraries"].Children().OfType().ToArray(); + + foreach (var library in libraries) { + var type = library.First()["type"].ToString(); + var path = library.First()["path"]?.ToString(); + var runtimeInfo = runtimeInfos.FirstOrDefault(r => r.Name == library.Name)?.First()["runtime"]?.Children().OfType().Select(i => i.Name).ToArray(); + + yield return new DotNetCorePackageInfo(library.Name, type, path, runtimeInfo); + } + } + + static string FallbackToDotNetSharedDirectory(AssemblyNameReference name, string version) + { + var basePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "dotnet\\shared\\Microsoft.NETCore.App", version); + DecompilerEventSource.Log.Info($"Fallback: use {basePath}"); + 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"))) { + return Path.Combine(basePath, name.Name + ".exe"); + } + return null; + } + } +} diff --git a/ICSharpCode.Decompiler/DotNetCore/DotNetCorePathFinderExtensions.cs b/ICSharpCode.Decompiler/DotNetCore/DotNetCorePathFinderExtensions.cs new file mode 100644 index 000000000..e054348d3 --- /dev/null +++ b/ICSharpCode.Decompiler/DotNetCore/DotNetCorePathFinderExtensions.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using ICSharpCode.Decompiler.TypeSystem.Implementation; +using Mono.Cecil; +using Newtonsoft.Json.Linq; + +namespace ICSharpCode.Decompiler +{ + + public static class DotNetCorePathFinderExtensions + { + public static string DetectTargetFrameworkId(this AssemblyDefinition assembly) + { + if (assembly == null) + throw new ArgumentNullException(nameof(assembly)); + + const string TargetFrameworkAttributeName = "System.Runtime.Versioning.TargetFrameworkAttribute"; + + foreach (var attribute in assembly.CustomAttributes) { + if (attribute.AttributeType.FullName != TargetFrameworkAttributeName) + continue; + var blobReader = new BlobReader(attribute.GetBlob(), null); + if (blobReader.ReadUInt16() == 0x0001) { + return blobReader.ReadSerString(); + } + } + + return string.Empty; + } + } +} diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index b7f4ee584..6d239ffa6 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -45,6 +45,7 @@ + @@ -259,6 +260,7 @@ + diff --git a/ILSpy/LoadedAssembly.cs b/ILSpy/LoadedAssembly.cs index 9871bacab..c2b376eb4 100644 --- a/ILSpy/LoadedAssembly.cs +++ b/ILSpy/LoadedAssembly.cs @@ -20,6 +20,7 @@ using System; using System.IO; using System.Threading.Tasks; using System.Windows.Threading; +using ICSharpCode.Decompiler; using ICSharpCode.ILSpy.Options; using Mono.Cecil; @@ -34,7 +35,8 @@ namespace ICSharpCode.ILSpy readonly AssemblyList assemblyList; readonly string fileName; readonly string shortName; - + readonly Lazy targetFrameworkId; + public LoadedAssembly(AssemblyList assemblyList, string fileName, Stream stream = null) { if (assemblyList == null) @@ -46,8 +48,11 @@ namespace ICSharpCode.ILSpy this.assemblyTask = Task.Factory.StartNew(LoadAssembly, stream); // requires that this.fileName is set this.shortName = Path.GetFileNameWithoutExtension(fileName); + this.targetFrameworkId = new Lazy(AssemblyDefinition.DetectTargetFrameworkId, false); } - + + public string TargetFrameworkId => targetFrameworkId.Value; + /// /// Gets the Cecil ModuleDefinition. /// Can be null when there was a load error. @@ -234,23 +239,47 @@ namespace ICSharpCode.ILSpy { return assemblyList.assemblyLookupCache.GetOrAdd(fullName, LookupReferencedAssemblyInternal); } + + DotNetCorePathFinder dotNetCorePathFinder; LoadedAssembly LookupReferencedAssemblyInternal(string fullName) { + DecompilerEventSource.Log.Info($"Looking for {fullName}..."); + foreach (LoadedAssembly asm in assemblyList.GetAssemblies()) { - if (asm.AssemblyDefinition != null && fullName.Equals(asm.AssemblyDefinition.FullName, StringComparison.OrdinalIgnoreCase)) + if (asm.AssemblyDefinition != null && fullName.Equals(asm.AssemblyDefinition.FullName, StringComparison.OrdinalIgnoreCase)) { + DecompilerEventSource.Log.Info($"Found in list of loaded assemblies."); return asm; + } } if (assemblyLoadDisableCount > 0) return null; if (!App.Current.Dispatcher.CheckAccess()) { + DecompilerEventSource.Log.Info($"Retry on UI thread..."); // Call this method on the GUI thread. return (LoadedAssembly)App.Current.Dispatcher.Invoke(DispatcherPriority.Normal, new Func(LookupReferencedAssembly), fullName); } - + + DecompilerEventSource.Log.Info($"Detected target framework: {TargetFrameworkId}."); + + var targetFramework = TargetFrameworkId.Split(new[] { ",Version=v" }, StringSplitOptions.None); var name = AssemblyNameReference.Parse(fullName); - string file = GacInterop.FindAssemblyInNetGac(name); + string file = null; + switch (targetFramework[0]) { + case ".NETCoreApp": + case ".NETStandard": + if (targetFramework.Length != 2) break; + if (dotNetCorePathFinder == null) { + var version = targetFramework[1].Length == 3 ? targetFramework[1] + ".0" : targetFramework[1]; + dotNetCorePathFinder = new DotNetCorePathFinder(fileName, TargetFrameworkId, version); + } + file = dotNetCorePathFinder.TryResolveDotNetCore(name); + break; + default: + file = GacInterop.FindAssemblyInNetGac(name); + break; + } if (file == null) { string dir = Path.GetDirectoryName(this.fileName); if (File.Exists(Path.Combine(dir, name.Name + ".dll"))) @@ -259,8 +288,8 @@ namespace ICSharpCode.ILSpy file = Path.Combine(dir, name.Name + ".exe"); } if (file != null) { - var loaded = assemblyList.OpenAssembly(file, true); - return loaded; + DecompilerEventSource.Log.Info($"Success - Loading {file}..."); + return assemblyList.OpenAssembly(file, true); } else { return null; }