From 5734da4294d10076ffd7ed5639ee1d73299aa89a Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Mon, 1 Mar 2021 20:45:08 +0100 Subject: [PATCH] Fix #2314: ILSpy incorrectly resolves a runtime dependency when dll is present in both WindowsDesktop.App and NETCore.App --- .../CSharp/CSharpDecompiler.cs | 2 +- .../ICSharpCode.Decompiler.csproj | 1 + .../Metadata/DotNetCorePathFinder.cs | 20 ++++- .../DotNetCorePathFinderExtensions.cs | 70 +++++---------- .../Metadata/ReferenceLoadInfo.cs | 87 +++++++++++++++++++ .../Metadata/UniversalAssemblyResolver.cs | 13 ++- ILSpy/LoadedAssembly.cs | 9 +- ILSpy/TreeNodes/ReferenceFolderTreeNode.cs | 4 +- 8 files changed, 146 insertions(+), 60 deletions(-) create mode 100644 ICSharpCode.Decompiler/Metadata/ReferenceLoadInfo.cs diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index e1d135139..6a6ecb687 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -430,7 +430,7 @@ namespace ICSharpCode.Decompiler.CSharp settings.LoadInMemory = true; var file = LoadPEFile(fileName, settings); var resolver = new UniversalAssemblyResolver(fileName, settings.ThrowOnAssemblyResolveErrors, - file.DetectTargetFrameworkId(), + file.DetectTargetFrameworkId(), file.DetectRuntimePack(), settings.LoadInMemory ? PEStreamOptions.PrefetchMetadata : PEStreamOptions.Default, settings.ApplyWindowsRuntimeProjections ? MetadataReaderOptions.ApplyWindowsRuntimeProjections : MetadataReaderOptions.None); return new DecompilerTypeSystem(file, resolver); diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index 0e7fcdcac..80c9977ca 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -91,6 +91,7 @@ + diff --git a/ICSharpCode.Decompiler/Metadata/DotNetCorePathFinder.cs b/ICSharpCode.Decompiler/Metadata/DotNetCorePathFinder.cs index e2038e16b..82f154364 100644 --- a/ICSharpCode.Decompiler/Metadata/DotNetCorePathFinder.cs +++ b/ICSharpCode.Decompiler/Metadata/DotNetCorePathFinder.cs @@ -75,10 +75,13 @@ namespace ICSharpCode.Decompiler.Metadata readonly List packageBasePaths = new List(); readonly Version targetFrameworkVersion; readonly string dotnetBasePath = FindDotNetExeDirectory(); + readonly string preferredRuntimePack; - public DotNetCorePathFinder(TargetFrameworkIdentifier targetFramework, Version targetFrameworkVersion) + public DotNetCorePathFinder(TargetFrameworkIdentifier targetFramework, Version targetFrameworkVersion, + string preferredRuntimePack) { this.targetFrameworkVersion = targetFrameworkVersion; + this.preferredRuntimePack = preferredRuntimePack; if (targetFramework == TargetFrameworkIdentifier.NETStandard) { @@ -90,8 +93,9 @@ namespace ICSharpCode.Decompiler.Metadata } } - public DotNetCorePathFinder(string parentAssemblyFileName, string targetFrameworkIdString, TargetFrameworkIdentifier targetFramework, Version targetFrameworkVersion, ReferenceLoadInfo loadInfo = null) - : this(targetFramework, targetFrameworkVersion) + public DotNetCorePathFinder(string parentAssemblyFileName, string targetFrameworkIdString, string preferredRuntimePack, + TargetFrameworkIdentifier targetFramework, Version targetFrameworkVersion, ReferenceLoadInfo loadInfo = null) + : this(targetFramework, targetFrameworkVersion, preferredRuntimePack) { string assemblyName = Path.GetFileNameWithoutExtension(parentAssemblyFileName); string basePath = Path.GetDirectoryName(parentAssemblyFileName); @@ -203,7 +207,15 @@ namespace ICSharpCode.Decompiler.Metadata runtimePack = null; return null; } - foreach (string pack in RuntimePacks) + + IEnumerable runtimePacks = RuntimePacks; + + if (preferredRuntimePack != null) + { + runtimePacks = new[] { preferredRuntimePack }.Concat(runtimePacks); + } + + foreach (string pack in runtimePacks) { runtimePack = pack; string basePath = Path.Combine(dotnetBasePath, "shared", pack); diff --git a/ICSharpCode.Decompiler/Metadata/DotNetCorePathFinderExtensions.cs b/ICSharpCode.Decompiler/Metadata/DotNetCorePathFinderExtensions.cs index c9bf3d969..03387f61e 100644 --- a/ICSharpCode.Decompiler/Metadata/DotNetCorePathFinderExtensions.cs +++ b/ICSharpCode.Decompiler/Metadata/DotNetCorePathFinderExtensions.cs @@ -17,8 +17,6 @@ // DEALINGS IN THE SOFTWARE. using System; -using System.Collections.Generic; -using System.Linq; using System.Reflection.Metadata; using System.Reflection.PortableExecutable; using System.Text.RegularExpressions; @@ -187,68 +185,42 @@ namespace ICSharpCode.Decompiler.Metadata var refPathMatch = Regex.Match(assemblyPath, RefPathPattern, RegexOptions.IgnoreCase | RegexOptions.Compiled); return refPathMatch.Success; } - } - - public class ReferenceLoadInfo - { - readonly Dictionary loadedAssemblyReferences = new Dictionary(); - public void AddMessage(string fullName, MessageKind kind, string message) + public static string DetectRuntimePack(this PEFile assembly) { - lock (loadedAssemblyReferences) + if (assembly is null) { - if (!loadedAssemblyReferences.TryGetValue(fullName, out var referenceInfo)) - { - referenceInfo = new UnresolvedAssemblyNameReference(fullName); - loadedAssemblyReferences.Add(fullName, referenceInfo); - } - referenceInfo.Messages.Add((kind, message)); + throw new ArgumentNullException(nameof(assembly)); } - } - public void AddMessageOnce(string fullName, MessageKind kind, string message) - { - lock (loadedAssemblyReferences) + var metadata = assembly.Metadata; + + foreach (var r in metadata.AssemblyReferences) { - if (!loadedAssemblyReferences.TryGetValue(fullName, out var referenceInfo)) - { - referenceInfo = new UnresolvedAssemblyNameReference(fullName); - loadedAssemblyReferences.Add(fullName, referenceInfo); - referenceInfo.Messages.Add((kind, message)); - } - else + var reference = metadata.GetAssemblyReference(r); + + if (reference.PublicKeyOrToken.IsNil) + continue; + + if (metadata.StringComparer.Equals(reference.Name, "WindowsBase")) { - var lastMsg = referenceInfo.Messages.LastOrDefault(); - if (kind != lastMsg.Item1 && message != lastMsg.Item2) - referenceInfo.Messages.Add((kind, message)); + return "Microsoft.WindowsDesktop.App"; } - } - } - - public bool TryGetInfo(string fullName, out UnresolvedAssemblyNameReference info) - { - lock (loadedAssemblyReferences) - { - return loadedAssemblyReferences.TryGetValue(fullName, out info); - } - } - public IReadOnlyList Entries { - get { - lock (loadedAssemblyReferences) + if (metadata.StringComparer.Equals(reference.Name, "PresentationFramework")) { - return loadedAssemblyReferences.Values.ToList(); + return "Microsoft.WindowsDesktop.App"; } - } - } - public bool HasErrors { - get { - lock (loadedAssemblyReferences) + if (metadata.StringComparer.Equals(reference.Name, "PresentationCore")) { - return loadedAssemblyReferences.Any(i => i.Value.HasErrors); + return "Microsoft.WindowsDesktop.App"; } + + // TODO add support for ASP.NET Core } + + return "Microsoft.NETCore.App"; } } } diff --git a/ICSharpCode.Decompiler/Metadata/ReferenceLoadInfo.cs b/ICSharpCode.Decompiler/Metadata/ReferenceLoadInfo.cs new file mode 100644 index 000000000..53ef9ea6f --- /dev/null +++ b/ICSharpCode.Decompiler/Metadata/ReferenceLoadInfo.cs @@ -0,0 +1,87 @@ +// Copyright (c) 2018 Siegfried Pammer +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace ICSharpCode.Decompiler.Metadata +{ + public class ReferenceLoadInfo + { + readonly Dictionary loadedAssemblyReferences = new Dictionary(); + + public void AddMessage(string fullName, MessageKind kind, string message) + { + lock (loadedAssemblyReferences) + { + if (!loadedAssemblyReferences.TryGetValue(fullName, out var referenceInfo)) + { + referenceInfo = new UnresolvedAssemblyNameReference(fullName); + loadedAssemblyReferences.Add(fullName, referenceInfo); + } + referenceInfo.Messages.Add((kind, message)); + } + } + + public void AddMessageOnce(string fullName, MessageKind kind, string message) + { + lock (loadedAssemblyReferences) + { + if (!loadedAssemblyReferences.TryGetValue(fullName, out var referenceInfo)) + { + referenceInfo = new UnresolvedAssemblyNameReference(fullName); + loadedAssemblyReferences.Add(fullName, referenceInfo); + referenceInfo.Messages.Add((kind, message)); + } + else + { + var lastMsg = referenceInfo.Messages.LastOrDefault(); + if (kind != lastMsg.Item1 && message != lastMsg.Item2) + referenceInfo.Messages.Add((kind, message)); + } + } + } + + public bool TryGetInfo(string fullName, out UnresolvedAssemblyNameReference info) + { + lock (loadedAssemblyReferences) + { + return loadedAssemblyReferences.TryGetValue(fullName, out info); + } + } + + public IReadOnlyList Entries { + get { + lock (loadedAssemblyReferences) + { + return loadedAssemblyReferences.Values.ToList(); + } + } + } + + public bool HasErrors { + get { + lock (loadedAssemblyReferences) + { + return loadedAssemblyReferences.Any(i => i.Value.HasErrors); + } + } + } + } +} diff --git a/ICSharpCode.Decompiler/Metadata/UniversalAssemblyResolver.cs b/ICSharpCode.Decompiler/Metadata/UniversalAssemblyResolver.cs index 38e65bb03..49d803e12 100644 --- a/ICSharpCode.Decompiler/Metadata/UniversalAssemblyResolver.cs +++ b/ICSharpCode.Decompiler/Metadata/UniversalAssemblyResolver.cs @@ -88,6 +88,7 @@ namespace ICSharpCode.Decompiler.Metadata } readonly string targetFramework; + readonly string runtimePack; readonly TargetFrameworkIdentifier targetFrameworkIdentifier; readonly Version targetFrameworkVersion; @@ -109,16 +110,20 @@ namespace ICSharpCode.Decompiler.Metadata /// "Silverlight", if the string doesn't match any of these, the resolver falls back to ".NET Framework", /// which is "classic" .NET <= 4.8. /// + /// + /// Identifier of the runtime pack this assembly was compiled for. + /// If omitted, falling back to "Microsoft.NETCore.App" and this is ignored in case of classic .NET /// Options used for the . /// Options used for the . public UniversalAssemblyResolver(string mainAssemblyFileName, bool throwOnError, string targetFramework, - PEStreamOptions streamOptions = PEStreamOptions.Default, MetadataReaderOptions metadataOptions = MetadataReaderOptions.Default) + string runtimePack = null, PEStreamOptions streamOptions = PEStreamOptions.Default, MetadataReaderOptions metadataOptions = MetadataReaderOptions.Default) { this.mainAssemblyFileName = mainAssemblyFileName; this.throwOnError = throwOnError; this.streamOptions = streamOptions; this.metadataOptions = metadataOptions; this.targetFramework = targetFramework ?? string.Empty; + this.runtimePack = runtimePack ?? "Microsoft.NETCore.App"; (targetFrameworkIdentifier, targetFrameworkVersion) = ParseTargetFramework(this.targetFramework); if (mainAssemblyFileName != null) @@ -228,7 +233,7 @@ namespace ICSharpCode.Decompiler.Metadata return FindWindowsMetadataFile(name); } - string file = null; + string file; switch (targetFrameworkIdentifier) { case TargetFrameworkIdentifier.NETCoreApp: @@ -238,9 +243,9 @@ namespace ICSharpCode.Decompiler.Metadata if (dotNetCorePathFinder == null) { if (mainAssemblyFileName == null) - dotNetCorePathFinder = new DotNetCorePathFinder(targetFrameworkIdentifier, targetFrameworkVersion); + dotNetCorePathFinder = new DotNetCorePathFinder(targetFrameworkIdentifier, targetFrameworkVersion, runtimePack); else - dotNetCorePathFinder = new DotNetCorePathFinder(mainAssemblyFileName, targetFramework, targetFrameworkIdentifier, targetFrameworkVersion); + dotNetCorePathFinder = new DotNetCorePathFinder(mainAssemblyFileName, targetFramework, runtimePack, targetFrameworkIdentifier, targetFrameworkVersion); foreach (var directory in directories) { dotNetCorePathFinder.AddSearchDirectory(directory); diff --git a/ILSpy/LoadedAssembly.cs b/ILSpy/LoadedAssembly.cs index 50060e3b2..0276fd9ae 100644 --- a/ILSpy/LoadedAssembly.cs +++ b/ILSpy/LoadedAssembly.cs @@ -116,6 +116,12 @@ namespace ICSharpCode.ILSpy return assembly.DetectTargetFrameworkId() ?? string.Empty; } + public async Task GetRuntimePackAsync() + { + var assembly = await GetPEFileAsync().ConfigureAwait(false); + return assembly.DetectRuntimePack() ?? string.Empty; + } + public ReferenceLoadInfo LoadedAssemblyReferencesInfo { get; } = new ReferenceLoadInfo(); IDebugInfoProvider? debugInfoProvider; @@ -614,6 +620,7 @@ namespace ICSharpCode.ILSpy { return LazyInitializer.EnsureInitialized(ref this.universalResolver, () => { var targetFramework = this.GetTargetFrameworkIdAsync().Result; + var runtimePack = this.GetRuntimePackAsync().Result; var readerOptions = DecompilerSettingsPanel.CurrentDecompilerSettings.ApplyWindowsRuntimeProjections ? MetadataReaderOptions.ApplyWindowsRuntimeProjections @@ -622,7 +629,7 @@ namespace ICSharpCode.ILSpy var rootedPath = Path.IsPathRooted(this.FileName) ? this.FileName : null; return new UniversalAssemblyResolver(rootedPath, throwOnError: false, targetFramework, - PEStreamOptions.PrefetchEntireImage, readerOptions); + runtimePack, PEStreamOptions.PrefetchEntireImage, readerOptions); })!; } diff --git a/ILSpy/TreeNodes/ReferenceFolderTreeNode.cs b/ILSpy/TreeNodes/ReferenceFolderTreeNode.cs index f42b0e765..987741cd1 100644 --- a/ILSpy/TreeNodes/ReferenceFolderTreeNode.cs +++ b/ILSpy/TreeNodes/ReferenceFolderTreeNode.cs @@ -56,7 +56,9 @@ namespace ICSharpCode.ILSpy.TreeNodes public override void Decompile(Language language, ITextOutput output, DecompilationOptions options) { - language.WriteCommentLine(output, $"Detected Target-Framework-Id: {parentAssembly.LoadedAssembly.GetTargetFrameworkIdAsync().Result}"); + language.WriteCommentLine(output, $"Detected TargetFramework-Id: {parentAssembly.LoadedAssembly.GetTargetFrameworkIdAsync().Result}"); + language.WriteCommentLine(output, $"Detected RuntimePack: {parentAssembly.LoadedAssembly.GetRuntimePackAsync().Result}"); + App.Current.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(EnsureLazyChildren)); output.WriteLine(); language.WriteCommentLine(output, "Referenced assemblies (in metadata order):");