From b492a20442c1d0efd03f77a92849e67aaffcca73 Mon Sep 17 00:00:00 2001 From: dymanoid <9433345+dymanoid@users.noreply.github.com> Date: Sat, 6 Jun 2020 21:59:58 +0200 Subject: [PATCH] Move assembly resolution logic to better place WholeProjectDecompiler shall not care about checking whether an assembly is in GAC. --- .../IlspyCmdProgram.cs | 4 +- .../GetDecompiledProjectCmdlet.cs | 4 +- .../ICSharpCode.Decompiler.Tests.csproj | 1 + .../RoundtripAssembly.cs | 28 +++----- .../TestAssemblyResolver.cs | 28 ++++++++ .../WholeProjectDecompiler.cs | 70 +++++++++++-------- .../Metadata/AssemblyReferences.cs | 1 + .../Metadata/UniversalAssemblyResolver.cs | 5 ++ ILSpy/Languages/CSharpLanguage.cs | 4 +- ILSpy/LoadedAssembly.cs | 5 ++ 10 files changed, 96 insertions(+), 54 deletions(-) create mode 100644 ICSharpCode.Decompiler.Tests/TestAssemblyResolver.cs diff --git a/ICSharpCode.Decompiler.Console/IlspyCmdProgram.cs b/ICSharpCode.Decompiler.Console/IlspyCmdProgram.cs index 8f409a956..684933a25 100644 --- a/ICSharpCode.Decompiler.Console/IlspyCmdProgram.cs +++ b/ICSharpCode.Decompiler.Console/IlspyCmdProgram.cs @@ -176,14 +176,12 @@ Remarks: int DecompileAsProject(string assemblyFileName, string outputDirectory) { - var decompiler = new WholeProjectDecompiler() { Settings = GetSettings() }; var module = new PEFile(assemblyFileName); var resolver = new UniversalAssemblyResolver(assemblyFileName, false, module.Reader.DetectTargetFrameworkId()); foreach (var path in ReferencePaths) { resolver.AddSearchDirectory(path); } - decompiler.AssemblyResolver = resolver; - decompiler.DebugInfoProvider = TryLoadPDB(module); + var decompiler = new WholeProjectDecompiler(GetSettings(), resolver, TryLoadPDB(module)); decompiler.DecompileProject(module, outputDirectory); return 0; } diff --git a/ICSharpCode.Decompiler.PowerShell/GetDecompiledProjectCmdlet.cs b/ICSharpCode.Decompiler.PowerShell/GetDecompiledProjectCmdlet.cs index 70ecdc439..736f59933 100644 --- a/ICSharpCode.Decompiler.PowerShell/GetDecompiledProjectCmdlet.cs +++ b/ICSharpCode.Decompiler.PowerShell/GetDecompiledProjectCmdlet.cs @@ -78,9 +78,9 @@ namespace ICSharpCode.Decompiler.PowerShell private void DoDecompile(string path) { - WholeProjectDecompiler decompiler = new WholeProjectDecompiler(); PEFile module = Decompiler.TypeSystem.MainModule.PEFile; - decompiler.AssemblyResolver = new UniversalAssemblyResolver(module.FileName, false, module.Reader.DetectTargetFrameworkId()); + var assemblyResolver = new UniversalAssemblyResolver(module.FileName, false, module.Reader.DetectTargetFrameworkId()); + WholeProjectDecompiler decompiler = new WholeProjectDecompiler(assemblyResolver); decompiler.ProgressIndicator = this; fileName = module.FileName; completed = 0; diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj index b409376e0..4aaec9f79 100644 --- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj +++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj @@ -88,6 +88,7 @@ + diff --git a/ICSharpCode.Decompiler.Tests/RoundtripAssembly.cs b/ICSharpCode.Decompiler.Tests/RoundtripAssembly.cs index 92e7a16c9..2139269c9 100644 --- a/ICSharpCode.Decompiler.Tests/RoundtripAssembly.cs +++ b/ICSharpCode.Decompiler.Tests/RoundtripAssembly.cs @@ -148,16 +148,19 @@ namespace ICSharpCode.Decompiler.Tests Stopwatch w = Stopwatch.StartNew(); using (var fileStream = new FileStream(file, FileMode.Open, FileAccess.Read)) { PEFile module = new PEFile(file, fileStream, PEStreamOptions.PrefetchEntireImage); - var resolver = new UniversalAssemblyResolver(file, false, module.Reader.DetectTargetFrameworkId(), PEStreamOptions.PrefetchMetadata); + var resolver = new TestAssemblyResolver(file, inputDir, module.Reader.DetectTargetFrameworkId()); resolver.AddSearchDirectory(inputDir); resolver.RemoveSearchDirectory("."); - var decompiler = new TestProjectDecompiler(inputDir); - decompiler.AssemblyResolver = resolver; + + // use a fixed GUID so that we can diff the output between different ILSpy runs without spurious changes + var projectGuid = Guid.Parse("{127C83E4-4587-4CF9-ADCA-799875F3DFE6}"); + // Let's limit the roundtrip tests to C# 7.3 for now; because 8.0 is still in preview // and the generated project doesn't build as-is. - decompiler.Settings = new DecompilerSettings(LanguageVersion.CSharp7_3); - // use a fixed GUID so that we can diff the output between different ILSpy runs without spurious changes - decompiler.ProjectGuid = Guid.Parse("{127C83E4-4587-4CF9-ADCA-799875F3DFE6}"); + var settings = new DecompilerSettings(LanguageVersion.CSharp7_3); + + var decompiler = new TestProjectDecompiler(projectGuid, resolver, settings); + if (snkFilePath != null) { decompiler.StrongNameKeyFile = Path.Combine(inputDir, snkFilePath); } @@ -260,18 +263,9 @@ namespace ICSharpCode.Decompiler.Tests class TestProjectDecompiler : WholeProjectDecompiler { - readonly string[] localAssemblies; - - public TestProjectDecompiler(string baseDir) - { - localAssemblies = new DirectoryInfo(baseDir).EnumerateFiles("*.dll").Select(f => f.FullName).ToArray(); - } - - protected override bool IsGacAssembly(IAssemblyReference r, PEFile asm) + public TestProjectDecompiler(Guid projecGuid, IAssemblyResolver resolver, DecompilerSettings settings) + : base(settings, projecGuid, resolver, debugInfoProvider: null) { - if (asm == null) - return false; - return !localAssemblies.Contains(asm.FileName); } } diff --git a/ICSharpCode.Decompiler.Tests/TestAssemblyResolver.cs b/ICSharpCode.Decompiler.Tests/TestAssemblyResolver.cs new file mode 100644 index 000000000..d97c6d526 --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestAssemblyResolver.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection.Metadata; +using System.Reflection.PortableExecutable; +using ICSharpCode.Decompiler.Metadata; + +namespace ICSharpCode.Decompiler.Tests +{ + sealed class TestAssemblyResolver : UniversalAssemblyResolver + { + readonly HashSet localAssemblies = new HashSet(); + + public TestAssemblyResolver(string mainAssemblyFileName, string baseDir, string targetFramework) + : base(mainAssemblyFileName, false, targetFramework, PEStreamOptions.PrefetchMetadata, MetadataReaderOptions.ApplyWindowsRuntimeProjections) + { + var assemblyNames = new DirectoryInfo(baseDir).EnumerateFiles("*.dll").Select(f => Path.GetFileNameWithoutExtension(f.Name)); + foreach (var name in assemblyNames) { + localAssemblies.Add(name); + } + } + + public override bool IsGacAssembly(IAssemblyReference reference) + { + return reference != null && !localAssemblies.Contains(reference.Name); + } + } +} diff --git a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs index 1dfa9020b..5e024b790 100644 --- a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs @@ -45,18 +45,10 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler public class WholeProjectDecompiler { #region Settings - DecompilerSettings settings = new DecompilerSettings(); - - public DecompilerSettings Settings { - get { - return settings; - } - set { - if (value == null) - throw new ArgumentNullException(); - settings = value; - } - } + /// + /// Gets the setting this instance uses for decompiling. + /// + public DecompilerSettings Settings { get; } LanguageVersion? languageVersion; @@ -71,15 +63,14 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler } } - public IAssemblyResolver AssemblyResolver { get; set; } + public IAssemblyResolver AssemblyResolver { get; } - public IDebugInfoProvider DebugInfoProvider { get; set; } + public IDebugInfoProvider DebugInfoProvider { get; } /// /// The MSBuild ProjectGuid to use for the new project. - /// null to automatically generate a new GUID. /// - public Guid? ProjectGuid { get; set; } + public Guid ProjectGuid { get; } /// /// Path to the snk file to use for signing. @@ -92,6 +83,31 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler public IProgress ProgressIndicator { get; set; } #endregion + public WholeProjectDecompiler(IAssemblyResolver assemblyResolver) + : this(new DecompilerSettings(), assemblyResolver, debugInfoProvider: null) + { + } + + public WholeProjectDecompiler( + DecompilerSettings settings, + IAssemblyResolver assemblyResolver, + IDebugInfoProvider debugInfoProvider) + : this(settings, Guid.NewGuid(), assemblyResolver, debugInfoProvider) + { + } + + protected WholeProjectDecompiler( + DecompilerSettings settings, + Guid projectGuid, + IAssemblyResolver assemblyResolver, + IDebugInfoProvider debugInfoProvider) + { + Settings = settings ?? throw new ArgumentNullException(nameof(settings)); + ProjectGuid = projectGuid; + AssemblyResolver = assemblyResolver ?? throw new ArgumentNullException(nameof(assemblyResolver)); + DebugInfoProvider = debugInfoProvider; + } + // per-run members HashSet directories = new HashSet(Platform.FileNameComparer); @@ -132,7 +148,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler { const string ns = "http://schemas.microsoft.com/developer/msbuild/2003"; string platformName = GetPlatformName(module); - Guid guid = this.ProjectGuid ?? Guid.NewGuid(); + Guid guid = this.ProjectGuid; var targetFramework = DetectTargetFramework(module); List typeGuids = new List(); @@ -228,7 +244,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler w.WriteStartElement("Reference"); w.WriteAttributeString("Include", r.Name); var asm = AssemblyResolver.Resolve(r); - if (!IsGacAssembly(r, asm)) { + if (!AssemblyResolver.IsGacAssembly(r)) { if (asm != null) { w.WriteElementString("HintPath", asm.FileName); } @@ -313,18 +329,14 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler return result; } - protected virtual bool IsGacAssembly(Metadata.IAssemblyReference r, Metadata.PEFile asm) - { - return false; - } #endregion #region WriteCodeFilesInProject - protected virtual bool IncludeTypeWhenDecompilingProject(Metadata.PEFile module, TypeDefinitionHandle type) + protected virtual bool IncludeTypeWhenDecompilingProject(PEFile module, TypeDefinitionHandle type) { var metadata = module.Metadata; var typeDef = metadata.GetTypeDefinition(type); - if (metadata.GetString(typeDef.Name) == "" || CSharpDecompiler.MemberIsHidden(module, type, settings)) + if (metadata.GetString(typeDef.Name) == "" || CSharpDecompiler.MemberIsHidden(module, type, Settings)) return false; if (metadata.GetString(typeDef.Namespace) == "XamlGeneratedNamespace" && metadata.GetString(typeDef.Name) == "GeneratedInternalTypeHelper") return false; @@ -333,7 +345,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler CSharpDecompiler CreateDecompiler(DecompilerTypeSystem ts) { - var decompiler = new CSharpDecompiler(ts, settings); + var decompiler = new CSharpDecompiler(ts, Settings); decompiler.DebugInfoProvider = DebugInfoProvider; decompiler.AstTransforms.Add(new EscapeInvalidIdentifiers()); decompiler.AstTransforms.Add(new RemoveCLSCompliantAttribute()); @@ -352,7 +364,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler Directory.CreateDirectory(Path.Combine(targetDirectory, prop)); string assemblyInfo = Path.Combine(prop, "AssemblyInfo.cs"); using (StreamWriter w = new StreamWriter(Path.Combine(targetDirectory, assemblyInfo))) { - syntaxTree.AcceptVisitor(new CSharpOutputVisitor(w, settings.CSharpFormattingOptions)); + syntaxTree.AcceptVisitor(new CSharpOutputVisitor(w, Settings.CSharpFormattingOptions)); } return new[] { ("Compile", assemblyInfo) }; } @@ -374,8 +386,8 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler } }, StringComparer.OrdinalIgnoreCase).ToList(); int total = files.Count; - var progress = this.ProgressIndicator; - DecompilerTypeSystem ts = new DecompilerTypeSystem(module, AssemblyResolver, settings); + var progress = ProgressIndicator; + DecompilerTypeSystem ts = new DecompilerTypeSystem(module, AssemblyResolver, Settings); Parallel.ForEach( files, new ParallelOptions { @@ -388,7 +400,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler CSharpDecompiler decompiler = CreateDecompiler(ts); decompiler.CancellationToken = cancellationToken; var syntaxTree = decompiler.DecompileTypes(file.ToArray()); - syntaxTree.AcceptVisitor(new CSharpOutputVisitor(w, settings.CSharpFormattingOptions)); + syntaxTree.AcceptVisitor(new CSharpOutputVisitor(w, Settings.CSharpFormattingOptions)); } catch (Exception innerException) when (!(innerException is OperationCanceledException || innerException is DecompilerException)) { throw new DecompilerException(module, $"Error decompiling for '{file.Key}'", innerException); } diff --git a/ICSharpCode.Decompiler/Metadata/AssemblyReferences.cs b/ICSharpCode.Decompiler/Metadata/AssemblyReferences.cs index 22d154960..d066778af 100644 --- a/ICSharpCode.Decompiler/Metadata/AssemblyReferences.cs +++ b/ICSharpCode.Decompiler/Metadata/AssemblyReferences.cs @@ -46,6 +46,7 @@ namespace ICSharpCode.Decompiler.Metadata { PEFile Resolve(IAssemblyReference reference); PEFile ResolveModule(PEFile mainModule, string moduleName); + bool IsGacAssembly(IAssemblyReference reference); } public interface IAssemblyReference diff --git a/ICSharpCode.Decompiler/Metadata/UniversalAssemblyResolver.cs b/ICSharpCode.Decompiler/Metadata/UniversalAssemblyResolver.cs index 22fc24105..3186fa3d3 100644 --- a/ICSharpCode.Decompiler/Metadata/UniversalAssemblyResolver.cs +++ b/ICSharpCode.Decompiler/Metadata/UniversalAssemblyResolver.cs @@ -173,6 +173,11 @@ namespace ICSharpCode.Decompiler.Metadata return new PEFile(moduleFileName, new FileStream(moduleFileName, FileMode.Open, FileAccess.Read), streamOptions, metadataOptions); } + public virtual bool IsGacAssembly(IAssemblyReference reference) + { + return GetAssemblyInGac(reference) != null; + } + public string FindAssemblyFile(IAssemblyReference name) { if (name.IsWindowsRuntime) { diff --git a/ILSpy/Languages/CSharpLanguage.cs b/ILSpy/Languages/CSharpLanguage.cs index 7fd6fe6cb..52c7e4644 100644 --- a/ILSpy/Languages/CSharpLanguage.cs +++ b/ILSpy/Languages/CSharpLanguage.cs @@ -438,12 +438,10 @@ namespace ICSharpCode.ILSpy readonly DecompilationOptions options; public ILSpyWholeProjectDecompiler(LoadedAssembly assembly, DecompilationOptions options) + : base(options.DecompilerSettings, assembly.GetAssemblyResolver(), assembly.GetDebugInfoOrNull()) { this.assembly = assembly; this.options = options; - base.Settings = options.DecompilerSettings; - base.AssemblyResolver = assembly.GetAssemblyResolver(); - base.DebugInfoProvider = assembly.GetDebugInfoOrNull(); } protected override IEnumerable<(string itemType, string fileName)> WriteResourceToFile(string fileName, string resourceName, Stream entryStream) diff --git a/ILSpy/LoadedAssembly.cs b/ILSpy/LoadedAssembly.cs index 4ce66d2b5..fc0e1bcdc 100644 --- a/ILSpy/LoadedAssembly.cs +++ b/ILSpy/LoadedAssembly.cs @@ -253,6 +253,11 @@ namespace ICSharpCode.ILSpy this.parent = parent; } + public bool IsGacAssembly(IAssemblyReference reference) + { + return parent.universalResolver?.IsGacAssembly(reference) == true; + } + public PEFile Resolve(Decompiler.Metadata.IAssemblyReference reference) { return parent.LookupReferencedAssembly(reference)?.GetPEFileOrNull();