From 696cbc2136c1c8a743497a297793b30cacc31100 Mon Sep 17 00:00:00 2001 From: "Andrew Crawley (US - DIAGNOSTICS)" Date: Tue, 26 Apr 2022 18:52:49 -0700 Subject: [PATCH 01/19] Allow user to provide ID when generating a PDB This commit adds a new parameter to PortablePdbWriter.WritePdb that allows the caller to specify the exact Guid and timestamp that should be used in the generated PDB. This will be useful for several scenarios that are interesting for the Visual Studio debugger's integration: 1. Generating a PDB for an assembly that was built without debug info. The PDB writer currently fails in this case, since the input assembly has no debug directory from which to extract the relevant info. The debugger can provide values that will allow us to load the generated PDB. 2. Generating a PDB for an assembly that has multiple debug directories. The PDB writer currently uses the first debug directory it finds, but this isn't necessarily the correct one. The debugger can provide the correct values. --- .../PdbGenerationTestRunner.cs | 47 ++++++++++++++++--- .../DebugInfo/PortablePdbWriter.cs | 14 ++++-- 2 files changed, 50 insertions(+), 11 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/PdbGenerationTestRunner.cs b/ICSharpCode.Decompiler.Tests/PdbGenerationTestRunner.cs index 35f1eede4f..f55098e8e5 100644 --- a/ICSharpCode.Decompiler.Tests/PdbGenerationTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/PdbGenerationTestRunner.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Reflection.Metadata; using System.Reflection.PortableExecutable; using System.Runtime.CompilerServices; using System.Text; @@ -46,18 +47,38 @@ public void LambdaCapturing() TestGeneratePdb(); } + [Test] + public void CustomPdbId() + { + // Generate a PDB for an assembly using a randomly-generated ID, then validate that the PDB uses the specified ID + (string peFileName, string pdbFileName) = CompileTestCase(nameof(HelloWorld)); + + var moduleDefinition = new PEFile(peFileName); + var resolver = new UniversalAssemblyResolver(peFileName, false, moduleDefinition.Metadata.DetectTargetFrameworkId(), null, PEStreamOptions.PrefetchEntireImage); + var decompiler = new CSharpDecompiler(moduleDefinition, resolver, new DecompilerSettings()); + var expectedPdbId = new BlobContentId(Guid.NewGuid(), (uint)Random.Shared.Next()); + + using (FileStream pdbStream = File.Open(Path.Combine(TestCasePath, nameof(CustomPdbId) + ".pdb"), FileMode.OpenOrCreate, FileAccess.ReadWrite)) + { + pdbStream.SetLength(0); + PortablePdbWriter.WritePdb(moduleDefinition, decompiler, new DecompilerSettings(), pdbStream, noLogo: true, pdbId: expectedPdbId); + + pdbStream.Position = 0; + var metadataReader = MetadataReaderProvider.FromPortablePdbStream(pdbStream).GetMetadataReader(); + var generatedPdbId = new BlobContentId(metadataReader.DebugMetadataHeader.Id); + + Assert.AreEqual(expectedPdbId.Guid, generatedPdbId.Guid); + Assert.AreEqual(expectedPdbId.Stamp, generatedPdbId.Stamp); + } + } + private void TestGeneratePdb([CallerMemberName] string testName = null) { const PdbToXmlOptions options = PdbToXmlOptions.IncludeEmbeddedSources | PdbToXmlOptions.ThrowOnError | PdbToXmlOptions.IncludeTokens | PdbToXmlOptions.ResolveTokens | PdbToXmlOptions.IncludeMethodSpans; string xmlFile = Path.Combine(TestCasePath, testName + ".xml"); - string xmlContent = File.ReadAllText(xmlFile); - XDocument document = XDocument.Parse(xmlContent); - var files = document.Descendants("file").ToDictionary(f => f.Attribute("name").Value, f => f.Value); - Tester.CompileCSharpWithPdb(Path.Combine(TestCasePath, testName + ".expected"), files); + (string peFileName, string pdbFileName) = CompileTestCase(testName); - string peFileName = Path.Combine(TestCasePath, testName + ".expected.dll"); - string pdbFileName = Path.Combine(TestCasePath, testName + ".expected.pdb"); var moduleDefinition = new PEFile(peFileName); var resolver = new UniversalAssemblyResolver(peFileName, false, moduleDefinition.Metadata.DetectTargetFrameworkId(), null, PEStreamOptions.PrefetchEntireImage); var decompiler = new CSharpDecompiler(moduleDefinition, resolver, new DecompilerSettings()); @@ -87,6 +108,20 @@ private void TestGeneratePdb([CallerMemberName] string testName = null) Assert.AreEqual(Normalize(expectedFileName), Normalize(generatedFileName)); } + private (string peFileName, string pdbFileName) CompileTestCase(string testName) + { + string xmlFile = Path.Combine(TestCasePath, testName + ".xml"); + string xmlContent = File.ReadAllText(xmlFile); + XDocument document = XDocument.Parse(xmlContent); + var files = document.Descendants("file").ToDictionary(f => f.Attribute("name").Value, f => f.Value); + Tester.CompileCSharpWithPdb(Path.Combine(TestCasePath, testName + ".expected"), files); + + string peFileName = Path.Combine(TestCasePath, testName + ".expected.dll"); + string pdbFileName = Path.Combine(TestCasePath, testName + ".expected.pdb"); + + return (peFileName, pdbFileName); + } + private void ProcessXmlFile(string fileName) { var document = XDocument.Load(fileName); diff --git a/ICSharpCode.Decompiler/DebugInfo/PortablePdbWriter.cs b/ICSharpCode.Decompiler/DebugInfo/PortablePdbWriter.cs index ba010a45d6..cc8e342bde 100644 --- a/ICSharpCode.Decompiler/DebugInfo/PortablePdbWriter.cs +++ b/ICSharpCode.Decompiler/DebugInfo/PortablePdbWriter.cs @@ -49,7 +49,7 @@ public static bool HasCodeViewDebugDirectoryEntry(PEFile file) return file.Reader.ReadDebugDirectory().Any(entry => entry.Type == DebugDirectoryEntryType.CodeView); } - public static void WritePdb(PEFile file, CSharpDecompiler decompiler, DecompilerSettings settings, Stream targetStream, bool noLogo = false) + public static void WritePdb(PEFile file, CSharpDecompiler decompiler, DecompilerSettings settings, Stream targetStream, bool noLogo = false, BlobContentId? pdbId = null) { MetadataBuilder metadata = new MetadataBuilder(); MetadataReader reader = file.Metadata; @@ -195,10 +195,14 @@ string BuildFileNameFromTypeName(TypeDefinitionHandle handle) metadata.AddCustomDebugInformation(row.Parent, row.Guid, row.Blob); } - var debugDir = file.Reader.ReadDebugDirectory().FirstOrDefault(dir => dir.Type == DebugDirectoryEntryType.CodeView); - var portable = file.Reader.ReadCodeViewDebugDirectoryData(debugDir); - var contentId = new BlobContentId(portable.Guid, debugDir.Stamp); - PortablePdbBuilder serializer = new PortablePdbBuilder(metadata, GetRowCounts(reader), entrypointHandle, blobs => contentId); + if (pdbId == null) + { + var debugDir = file.Reader.ReadDebugDirectory().FirstOrDefault(dir => dir.Type == DebugDirectoryEntryType.CodeView); + var portable = file.Reader.ReadCodeViewDebugDirectoryData(debugDir); + pdbId = new BlobContentId(portable.Guid, debugDir.Stamp); + } + + PortablePdbBuilder serializer = new PortablePdbBuilder(metadata, GetRowCounts(reader), entrypointHandle, blobs => pdbId.Value); BlobBuilder blobBuilder = new BlobBuilder(); serializer.Serialize(blobBuilder); blobBuilder.WriteContentTo(targetStream); From 3693c0c9e9ed8ad3f80bb435b72f507cea4e16a7 Mon Sep 17 00:00:00 2001 From: James May Date: Fri, 29 Apr 2022 01:11:40 +1000 Subject: [PATCH 02/19] Add Debug Directory raw data and sub tree nodes, with special handling for: * CodeView * EmbeddedPortablePdb * PdbChecksum --- .../DebugDirectory/CodeViewTreeNode.cs | 75 +++++++++++++++++ .../DebugDirectoryEntryTreeNode.cs | 62 ++++++++++++++ .../DebugDirectory/PdbChecksumTreeNode.cs | 83 +++++++++++++++++++ ILSpy/Metadata/DebugDirectoryTreeNode.cs | 45 +++++++++- ILSpy/Metadata/DebugMetadataTreeNode.cs | 4 +- ILSpy/TreeNodes/AssemblyTreeNode.cs | 2 +- 6 files changed, 263 insertions(+), 8 deletions(-) create mode 100644 ILSpy/Metadata/DebugDirectory/CodeViewTreeNode.cs create mode 100644 ILSpy/Metadata/DebugDirectory/DebugDirectoryEntryTreeNode.cs create mode 100644 ILSpy/Metadata/DebugDirectory/PdbChecksumTreeNode.cs diff --git a/ILSpy/Metadata/DebugDirectory/CodeViewTreeNode.cs b/ILSpy/Metadata/DebugDirectory/CodeViewTreeNode.cs new file mode 100644 index 0000000000..5fdf81a906 --- /dev/null +++ b/ILSpy/Metadata/DebugDirectory/CodeViewTreeNode.cs @@ -0,0 +1,75 @@ +// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team +// +// 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. + +#nullable enable + +using System.Reflection.PortableExecutable; + +using ICSharpCode.Decompiler; +using ICSharpCode.Decompiler.Metadata; +using ICSharpCode.ILSpy.TreeNodes; +using ICSharpCode.ILSpy.ViewModels; + +namespace ICSharpCode.ILSpy.Metadata +{ + sealed class CodeViewTreeNode : ILSpyTreeNode + { + readonly CodeViewDebugDirectoryData entry; + public CodeViewTreeNode(CodeViewDebugDirectoryData entry) + { + this.entry = entry; + } + + override public object Text => nameof(DebugDirectoryEntryType.CodeView); + + public override object ToolTip => "Associated PDB file description."; + + public override object Icon => Images.Literal; + + public override bool View(TabPageModel tabPage) + { + tabPage.Title = Text.ToString(); + tabPage.SupportsLanguageSwitching = false; + + var dataGrid = Helpers.PrepareDataGrid(tabPage, this); + + dataGrid.ItemsSource = new[] { entry }; + + tabPage.Content = dataGrid; + + return true; + } + + sealed class PdbChecksumDebugDirectoryDataEntry + { + readonly CodeViewDebugDirectoryData entry; + public PdbChecksumDebugDirectoryDataEntry(CodeViewDebugDirectoryData entry) + { + this.entry = entry; + } + } + + public override void Decompile(Language language, ITextOutput output, DecompilationOptions options) + { + language.WriteCommentLine(output, Text.ToString()); + language.WriteCommentLine(output, $"GUID: {entry.Guid}"); + language.WriteCommentLine(output, $"Age: {entry.Age}"); + language.WriteCommentLine(output, $"Path: {entry.Path}"); + } + } +} diff --git a/ILSpy/Metadata/DebugDirectory/DebugDirectoryEntryTreeNode.cs b/ILSpy/Metadata/DebugDirectory/DebugDirectoryEntryTreeNode.cs new file mode 100644 index 0000000000..5eb4da06fd --- /dev/null +++ b/ILSpy/Metadata/DebugDirectory/DebugDirectoryEntryTreeNode.cs @@ -0,0 +1,62 @@ +// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team +// +// 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. + +#nullable enable + +using System.Reflection.PortableExecutable; + +using ICSharpCode.Decompiler; +using ICSharpCode.Decompiler.Metadata; +using ICSharpCode.ILSpy.TreeNodes; +using ICSharpCode.ILSpy.ViewModels; + +namespace ICSharpCode.ILSpy.Metadata +{ + sealed class DebugDirectoryEntryTreeNode : ILSpyTreeNode + { + readonly PEFile module; + readonly PEReader reader; + readonly DebugDirectoryEntry entry; + public DebugDirectoryEntryTreeNode(PEFile module, DebugDirectoryEntry entry) + { + this.module = module; + this.reader = module.Reader; + this.entry = entry; + } + + override public object Text => $"{entry.Type}"; + + public override object Icon => Images.Literal; + + public override void Decompile(Language language, ITextOutput output, DecompilationOptions options) + { + language.WriteCommentLine(output, Text.ToString()); + if (entry.DataSize > 0) + { + language.WriteCommentLine(output, $"Raw Data ({entry.DataSize}):"); + int dataOffset = module.Reader.IsLoadedImage ? entry.DataRelativeVirtualAddress : entry.DataPointer; + var data = module.Reader.GetEntireImage().GetContent(dataOffset, entry.DataSize); + language.WriteCommentLine(output, data.ToHexString(data.Length)); + } + else + { + language.WriteCommentLine(output, $"(no data)"); + } + } + } +} diff --git a/ILSpy/Metadata/DebugDirectory/PdbChecksumTreeNode.cs b/ILSpy/Metadata/DebugDirectory/PdbChecksumTreeNode.cs new file mode 100644 index 0000000000..13444518d9 --- /dev/null +++ b/ILSpy/Metadata/DebugDirectory/PdbChecksumTreeNode.cs @@ -0,0 +1,83 @@ +// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team +// +// 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. + +#nullable enable + +using System.Reflection.PortableExecutable; + +using ICSharpCode.Decompiler; +using ICSharpCode.Decompiler.Metadata; +using ICSharpCode.ILSpy.TreeNodes; +using ICSharpCode.ILSpy.ViewModels; + +namespace ICSharpCode.ILSpy.Metadata +{ + sealed class PdbChecksumTreeNode : ILSpyTreeNode + { + readonly PdbChecksumDebugDirectoryData entry; + public PdbChecksumTreeNode(PdbChecksumDebugDirectoryData entry) + { + this.entry = entry; + } + + override public object Text => nameof(DebugDirectoryEntryType.PdbChecksum); + + public override object ToolTip + => "The entry stores a crypto hash of the content of the symbol file the PE/COFF\n" + + "file was built with. The hash can be used to validate that a given PDB file was\n" + + "built with the PE/COFF file and not altered in any way. More than one entry can\n" + + "be present if multiple PDBs were produced during the build of the PE/COFF file\n" + + "(for example, private and public symbols)."; + + public override object Icon => Images.Literal; + + public override bool View(TabPageModel tabPage) + { + tabPage.Title = Text.ToString(); + tabPage.SupportsLanguageSwitching = false; + + var dataGrid = Helpers.PrepareDataGrid(tabPage, this); + + dataGrid.ItemsSource = new[] { new PdbChecksumDebugDirectoryDataEntry(entry) }; + + tabPage.Content = dataGrid; + + return true; + } + + sealed class PdbChecksumDebugDirectoryDataEntry + { + readonly PdbChecksumDebugDirectoryData entry; + public PdbChecksumDebugDirectoryDataEntry(PdbChecksumDebugDirectoryData entry) + { + this.entry = entry; + } + + public string AlgorithmName => entry.AlgorithmName; + + public string Checksum => entry.Checksum.ToHexString(entry.Checksum.Length); + } + + public override void Decompile(Language language, ITextOutput output, DecompilationOptions options) + { + language.WriteCommentLine(output, Text.ToString()); + language.WriteCommentLine(output, $"AlgorithmName: {entry.AlgorithmName}"); + language.WriteCommentLine(output, $"Checksum: {entry.Checksum.ToHexString(entry.Checksum.Length)}"); + } + } +} diff --git a/ILSpy/Metadata/DebugDirectoryTreeNode.cs b/ILSpy/Metadata/DebugDirectoryTreeNode.cs index 9db16a628e..78e3704c17 100644 --- a/ILSpy/Metadata/DebugDirectoryTreeNode.cs +++ b/ILSpy/Metadata/DebugDirectoryTreeNode.cs @@ -16,6 +16,8 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +#nullable enable + using System; using System.Collections.Generic; using System.Reflection.PortableExecutable; @@ -36,6 +38,7 @@ class DebugDirectoryTreeNode : ILSpyTreeNode public DebugDirectoryTreeNode(PEFile module) { this.module = module; + this.LazyLoading = true; } public override object Text => "Debug Directory"; @@ -51,7 +54,10 @@ public override bool View(ViewModels.TabPageModel tabPage) var entries = new List(); foreach (var entry in module.Reader.ReadDebugDirectory()) { - entries.Add(new DebugDirectoryEntryView(entry)); + int dataOffset = module.Reader.IsLoadedImage ? entry.DataRelativeVirtualAddress : entry.DataPointer; + var data = module.Reader.GetEntireImage().GetContent(dataOffset, entry.DataSize); + + entries.Add(new DebugDirectoryEntryView(entry, data.ToHexString(data.Length))); } dataGrid.ItemsSource = entries.ToArray(); @@ -60,6 +66,37 @@ public override bool View(ViewModels.TabPageModel tabPage) return true; } + protected override void LoadChildren() + { + foreach (var entry in module.Reader.ReadDebugDirectory()) + { + switch (entry.Type) + { + case DebugDirectoryEntryType.CodeView: + var codeViewData = module.Reader.ReadCodeViewDebugDirectoryData(entry); + this.Children.Add(new CodeViewTreeNode(codeViewData)); + break; + + case DebugDirectoryEntryType.EmbeddedPortablePdb: + var embeddedPortablePdbReader = module.Reader.ReadEmbeddedPortablePdbDebugDirectoryData(entry).GetMetadataReader(); + this.Children.Add(new DebugMetadataTreeNode(module, isEmbedded: true, provider: embeddedPortablePdbReader)); + break; + + case DebugDirectoryEntryType.PdbChecksum: + var pdbChecksumData = module.Reader.ReadPdbChecksumDebugDirectoryData(entry); + this.Children.Add(new PdbChecksumTreeNode(pdbChecksumData)); + break; + + case DebugDirectoryEntryType.Unknown: + case DebugDirectoryEntryType.Coff: + case DebugDirectoryEntryType.Reproducible: + default: + this.Children.Add(new DebugDirectoryEntryTreeNode(module, entry)); + break; + } + } + } + public override void Decompile(Language language, ITextOutput output, DecompilationOptions options) { language.WriteCommentLine(output, "Data Directories"); @@ -67,7 +104,6 @@ public override void Decompile(Language language, ITextOutput output, Decompilat class DebugDirectoryEntryView { - public int Characteristics { get; set; } public uint Timestamp { get; set; } public ushort MajorVersion { get; set; } public ushort MinorVersion { get; set; } @@ -75,10 +111,11 @@ class DebugDirectoryEntryView public int SizeOfRawData { get; set; } public int AddressOfRawData { get; set; } public int PointerToRawData { get; set; } + public string RawData { get; set; } - public DebugDirectoryEntryView(DebugDirectoryEntry entry) + public DebugDirectoryEntryView(DebugDirectoryEntry entry, string data) { - this.Characteristics = 0; + this.RawData = data; this.Timestamp = entry.Stamp; this.MajorVersion = entry.MajorVersion; this.MinorVersion = entry.MinorVersion; diff --git a/ILSpy/Metadata/DebugMetadataTreeNode.cs b/ILSpy/Metadata/DebugMetadataTreeNode.cs index c0edc0d026..cd140aa573 100644 --- a/ILSpy/Metadata/DebugMetadataTreeNode.cs +++ b/ILSpy/Metadata/DebugMetadataTreeNode.cs @@ -32,14 +32,12 @@ class DebugMetadataTreeNode : ILSpyTreeNode { private PEFile module; private MetadataReader provider; - private AssemblyTreeNode assemblyTreeNode; private bool isEmbedded; - public DebugMetadataTreeNode(PEFile module, bool isEmbedded, MetadataReader provider, AssemblyTreeNode assemblyTreeNode) + public DebugMetadataTreeNode(PEFile module, bool isEmbedded, MetadataReader provider) { this.module = module; this.provider = provider; - this.assemblyTreeNode = assemblyTreeNode; this.isEmbedded = isEmbedded; this.Text = "Debug Metadata (" + (isEmbedded ? "Embedded" : "From portable PDB") + ")"; this.LazyLoading = true; diff --git a/ILSpy/TreeNodes/AssemblyTreeNode.cs b/ILSpy/TreeNodes/AssemblyTreeNode.cs index 38be04a722..19b6b633a3 100644 --- a/ILSpy/TreeNodes/AssemblyTreeNode.cs +++ b/ILSpy/TreeNodes/AssemblyTreeNode.cs @@ -210,7 +210,7 @@ void LoadChildrenForPEFile(PEFile module) if (debugInfo is PortableDebugInfoProvider ppdb && ppdb.GetMetadataReader() is System.Reflection.Metadata.MetadataReader reader) { - this.Children.Add(new Metadata.DebugMetadataTreeNode(module, ppdb.IsEmbedded, reader, this)); + this.Children.Add(new Metadata.DebugMetadataTreeNode(module, ppdb.IsEmbedded, reader)); } this.Children.Add(new ReferenceFolderTreeNode(module, this)); if (module.Resources.Any()) From 1532d2fc295ffac09dd852e38f9bc006efaf3924 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Fri, 29 Apr 2022 14:21:00 +0200 Subject: [PATCH 03/19] Refactor DoDecompile(ITypeDefinition): Extract handling of members and types into local function. Order of output no longer depends on the order of decompilation, which will become relevant in the next commit. --- .../CSharp/CSharpDecompiler.cs | 127 +++++++++--------- 1 file changed, 65 insertions(+), 62 deletions(-) diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index c8c9d60ffd..24265ac60f 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -1244,9 +1244,11 @@ EntityDeclaration DoDecompile(ITypeDefinition typeDef, DecompileRun decompileRun { Debug.Assert(decompilationContext.CurrentTypeDefinition == typeDef); var watch = System.Diagnostics.Stopwatch.StartNew(); + MultiDictionary entityMap = new(); + TypeSystemAstBuilder typeSystemAstBuilder; try { - var typeSystemAstBuilder = CreateAstBuilder(decompileRun.Settings); + typeSystemAstBuilder = CreateAstBuilder(decompileRun.Settings); var entityDecl = typeSystemAstBuilder.ConvertEntity(typeDef); var typeDecl = entityDecl as TypeDeclaration; if (typeDecl == null) @@ -1289,16 +1291,6 @@ EntityDeclaration DoDecompile(ITypeDefinition typeDef, DecompileRun decompileRun } } - foreach (var type in typeDef.NestedTypes) - { - if (!type.MetadataToken.IsNil && !MemberIsHidden(module.PEFile, type.MetadataToken, settings)) - { - var nestedType = DoDecompile(type, decompileRun, decompilationContext.WithCurrentTypeDefinition(type)); - SetNewModifier(nestedType); - typeDecl.Members.Add(nestedType); - } - } - decompileRun.EnumValueDisplayMode = typeDef.Kind == TypeKind.Enum ? DetectBestEnumValueDisplayMode(typeDef, module.PEFile) : null; @@ -1309,60 +1301,20 @@ EntityDeclaration DoDecompile(ITypeDefinition typeDef, DecompileRun decompileRun // For COM interop scenarios, the relative order of virtual functions/properties matters: IEnumerable allOrderedMembers = RequiresNativeOrdering(typeDef) ? GetMembersWithNativeOrdering(typeDef) : - fieldsAndProperties.Concat(typeDef.Events).Concat(typeDef.Methods); + fieldsAndProperties.Concat(typeDef.Events).Concat(typeDef.Methods); + + var allOrderedEntities = typeDef.NestedTypes.Concat(allOrderedMembers); - foreach (var member in allOrderedMembers) + foreach (var member in allOrderedEntities) { - if (member is IField || member is IProperty) - { - var fieldOrProperty = member; - if (fieldOrProperty.MetadataToken.IsNil || MemberIsHidden(module.PEFile, fieldOrProperty.MetadataToken, settings)) - { - continue; - } - if (fieldOrProperty is IField field) - { - if (typeDef.Kind == TypeKind.Enum && !field.IsConst) - continue; - var memberDecl = DoDecompile(field, decompileRun, decompilationContext.WithCurrentMember(field)); - typeDecl.Members.Add(memberDecl); - } - else if (fieldOrProperty is IProperty property) - { - if (recordDecompiler?.PropertyIsGenerated(property) == true) - { - continue; - } - var propDecl = DoDecompile(property, decompileRun, decompilationContext.WithCurrentMember(property)); - typeDecl.Members.Add(propDecl); - } - } - else if (member is IMethod method) - { - if (recordDecompiler?.MethodIsGenerated(method) == true) - { - continue; - } - if (!method.MetadataToken.IsNil && !MemberIsHidden(module.PEFile, method.MetadataToken, settings)) - { - var memberDecl = DoDecompile(method, decompileRun, decompilationContext.WithCurrentMember(method)); - typeDecl.Members.Add(memberDecl); - typeDecl.Members.AddRange(AddInterfaceImplHelpers(memberDecl, method, typeSystemAstBuilder)); - } - } - else if (member is IEvent @event) - { - if (!@event.MetadataToken.IsNil && !MemberIsHidden(module.PEFile, @event.MetadataToken, settings)) - { - var eventDecl = DoDecompile(@event, decompileRun, decompilationContext.WithCurrentMember(@event)); - typeDecl.Members.Add(eventDecl); - } - } - else - { - throw new ArgumentOutOfRangeException("Unexpected member type"); - } + DoDecompileMember(member, recordDecompiler); } + + foreach (var member in allOrderedEntities) + { + typeDecl.Members.AddRange(entityMap[member]); + } + if (typeDecl.Members.OfType().Any(idx => idx.PrivateImplementationType.IsNull)) { // Remove the [DefaultMember] attribute if the class contains indexers @@ -1421,6 +1373,57 @@ EntityDeclaration DoDecompile(ITypeDefinition typeDef, DecompileRun decompileRun watch.Stop(); Instrumentation.DecompilerEventSource.Log.DoDecompileTypeDefinition(typeDef.FullName, watch.ElapsedMilliseconds); } + + void DoDecompileMember(IEntity entity, RecordDecompiler recordDecompiler) + { + EntityDeclaration entityDecl; + if (entity.MetadataToken.IsNil || MemberIsHidden(module.PEFile, entity.MetadataToken, settings)) + { + return; + } + switch (entity) + { + case IField field: + if (typeDef.Kind == TypeKind.Enum && !field.IsConst) + { + return; + } + entityDecl = DoDecompile(field, decompileRun, decompilationContext.WithCurrentMember(field)); + entityMap.Add(field, entityDecl); + break; + case IProperty property: + if (recordDecompiler?.PropertyIsGenerated(property) == true) + { + return; + } + entityDecl = DoDecompile(property, decompileRun, decompilationContext.WithCurrentMember(property)); + entityMap.Add(property, entityDecl); + break; + case IMethod method: + if (recordDecompiler?.MethodIsGenerated(method) == true) + { + return; + } + entityDecl = DoDecompile(method, decompileRun, decompilationContext.WithCurrentMember(method)); + entityMap.Add(method, entityDecl); + foreach (var helper in AddInterfaceImplHelpers(entityDecl, method, typeSystemAstBuilder)) + { + entityMap.Add(method, helper); + } + break; + case IEvent @event: + entityDecl = DoDecompile(@event, decompileRun, decompilationContext.WithCurrentMember(@event)); + entityMap.Add(@event, entityDecl); + break; + case ITypeDefinition type: + var nestedType = DoDecompile(type, decompileRun, decompilationContext.WithCurrentTypeDefinition(type)); + SetNewModifier(nestedType); + entityMap.Add(type, nestedType); + break; + default: + throw new ArgumentOutOfRangeException("Unexpected member type"); + } + } } EnumValueDisplayMode DetectBestEnumValueDisplayMode(ITypeDefinition typeDef, PEFile module) From b7edf2eb5985d23783ec15ca854ae2828d5bc899 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Fri, 29 Apr 2022 15:45:12 +0200 Subject: [PATCH 04/19] Detect compiler-generated code that is still needed after decompilation. --- .../CSharp/CSharpDecompiler.cs | 48 +++++++++++++++---- .../Util/MultiDictionary.cs | 2 +- 2 files changed, 39 insertions(+), 11 deletions(-) diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index 24265ac60f..8460dd4e33 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -1244,7 +1244,8 @@ EntityDeclaration DoDecompile(ITypeDefinition typeDef, DecompileRun decompileRun { Debug.Assert(decompilationContext.CurrentTypeDefinition == typeDef); var watch = System.Diagnostics.Stopwatch.StartNew(); - MultiDictionary entityMap = new(); + var entityMap = new MultiDictionary(); + var workList = new Queue(); TypeSystemAstBuilder typeSystemAstBuilder; try { @@ -1305,11 +1306,29 @@ EntityDeclaration DoDecompile(ITypeDefinition typeDef, DecompileRun decompileRun var allOrderedEntities = typeDef.NestedTypes.Concat(allOrderedMembers); - foreach (var member in allOrderedEntities) + // Decompile members that are not compiler-generated. + foreach (var entity in allOrderedEntities) + { + if (entity.MetadataToken.IsNil || MemberIsHidden(module.PEFile, entity.MetadataToken, settings)) + { + continue; + } + DoDecompileMember(entity, recordDecompiler); + } + + // Decompile compiler-generated members that are still needed. + while (workList.Count > 0) { - DoDecompileMember(member, recordDecompiler); + var entity = workList.Dequeue(); + if (entityMap.Contains(entity) || entity.MetadataToken.IsNil) + { + // Member is already decompiled. + continue; + } + DoDecompileMember(entity, recordDecompiler); } + // Add all decompiled members to syntax tree in the correct order. foreach (var member in allOrderedEntities) { typeDecl.Members.AddRange(entityMap[member]); @@ -1377,10 +1396,6 @@ EntityDeclaration DoDecompile(ITypeDefinition typeDef, DecompileRun decompileRun void DoDecompileMember(IEntity entity, RecordDecompiler recordDecompiler) { EntityDeclaration entityDecl; - if (entity.MetadataToken.IsNil || MemberIsHidden(module.PEFile, entity.MetadataToken, settings)) - { - return; - } switch (entity) { case IField field: @@ -1416,13 +1431,26 @@ void DoDecompileMember(IEntity entity, RecordDecompiler recordDecompiler) entityMap.Add(@event, entityDecl); break; case ITypeDefinition type: - var nestedType = DoDecompile(type, decompileRun, decompilationContext.WithCurrentTypeDefinition(type)); - SetNewModifier(nestedType); - entityMap.Add(type, nestedType); + entityDecl = DoDecompile(type, decompileRun, decompilationContext.WithCurrentTypeDefinition(type)); + SetNewModifier(entityDecl); + entityMap.Add(type, entityDecl); break; default: throw new ArgumentOutOfRangeException("Unexpected member type"); } + + foreach (var node in entityDecl.Descendants) + { + var rr = node.GetResolveResult(); + if (rr is MemberResolveResult mrr && mrr.Member.DeclaringTypeDefinition == typeDef) + { + workList.Enqueue(mrr.Member); + } + else if (rr is TypeResolveResult trr && trr.Type.GetDefinition()?.DeclaringTypeDefinition == typeDef) + { + workList.Enqueue(trr.Type.GetDefinition()); + } + } } } diff --git a/ICSharpCode.Decompiler/Util/MultiDictionary.cs b/ICSharpCode.Decompiler/Util/MultiDictionary.cs index 52a451f5a3..9b113870e1 100644 --- a/ICSharpCode.Decompiler/Util/MultiDictionary.cs +++ b/ICSharpCode.Decompiler/Util/MultiDictionary.cs @@ -105,7 +105,7 @@ public void Clear() get { return this[key]; } } - bool ILookup.Contains(TKey key) + public bool Contains(TKey key) { return dict.ContainsKey(key); } From 40ffc1e90eeea8cbfbb75cd9837c6c8050a7b2bf Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Fri, 29 Apr 2022 16:28:32 +0200 Subject: [PATCH 05/19] Remove backing fields of auto properties and events. The previous commit started decompiling backing fields because they are still needed prior to the C# transforms. --- .../Transforms/PatternStatementTransform.cs | 46 +++++++++---------- .../Implementation/KnownAttributes.cs | 2 + 2 files changed, 23 insertions(+), 25 deletions(-) diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs b/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs index 56af467974..ca35ea9464 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs @@ -640,17 +640,19 @@ PropertyDeclaration TransformAutomaticProperty(PropertyDeclaration propertyDecla propertyDeclaration.Modifiers &= ~Modifiers.Readonly; propertyDeclaration.Getter.Modifiers &= ~Modifiers.Readonly; - // Add C# 7.3 attributes on backing field: - var attributes = field.GetAttributes() - .Where(a => !attributeTypesToRemoveFromAutoProperties.Contains(a.AttributeType.FullName)) - .Select(context.TypeSystemAstBuilder.ConvertAttribute).ToArray(); - if (attributes.Length > 0) + var fieldDecl = propertyDeclaration.Parent?.Children.OfType() + .FirstOrDefault(fd => field.Equals(fd.GetSymbol())); + if (fieldDecl != null) { - var section = new AttributeSection { - AttributeTarget = "field" - }; - section.Attributes.AddRange(attributes); - propertyDeclaration.Attributes.Add(section); + fieldDecl.Remove(); + // Add C# 7.3 attributes on backing field: + CSharpDecompiler.RemoveAttribute(fieldDecl, KnownAttribute.CompilerGenerated); + CSharpDecompiler.RemoveAttribute(fieldDecl, KnownAttribute.DebuggerBrowsable); + foreach (var section in fieldDecl.Attributes) + { + section.AttributeTarget = "field"; + propertyDeclaration.Attributes.Add(section.Detach()); + } } } // Since the property instance is not changed, we can continue in the visitor as usual, so return null @@ -1010,23 +1012,17 @@ EventDeclaration TransformAutomaticEvents(CustomEventDeclaration ev) ed.Variables.Add(new VariableInitializer(ev.Name)); ed.CopyAnnotationsFrom(ev); - if (ev.GetSymbol() is IEvent eventDef) + var fieldDecl = ev.Parent?.Children.OfType() + .FirstOrDefault(fd => fd.Variables.Single().Name == ev.Name); + if (fieldDecl != null) { - IField field = eventDef.DeclaringType.GetFields(f => f.Name == ev.Name, GetMemberOptions.IgnoreInheritedMembers).SingleOrDefault(); - if (field != null) + fieldDecl.Remove(); + CSharpDecompiler.RemoveAttribute(fieldDecl, KnownAttribute.CompilerGenerated); + CSharpDecompiler.RemoveAttribute(fieldDecl, KnownAttribute.DebuggerBrowsable); + foreach (var section in fieldDecl.Attributes) { - ed.AddAnnotation(field); - var attributes = field.GetAttributes() - .Where(a => !attributeTypesToRemoveFromAutoEvents.Contains(a.AttributeType.FullName)) - .Select(context.TypeSystemAstBuilder.ConvertAttribute).ToArray(); - if (attributes.Length > 0) - { - var section = new AttributeSection { - AttributeTarget = "field" - }; - section.Attributes.AddRange(attributes); - ed.Attributes.Add(section); - } + section.AttributeTarget = "field"; + ed.Attributes.Add(section.Detach()); } } diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs index 2ce7b2b62f..e54b32e0b2 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs @@ -48,6 +48,7 @@ public enum KnownAttribute SpecialName, DebuggerHidden, DebuggerStepThrough, + DebuggerBrowsable, // Assembly attributes: AssemblyVersion, @@ -124,6 +125,7 @@ static class KnownAttributes new TopLevelTypeName("System.Runtime.CompilerServices", nameof(SpecialNameAttribute)), new TopLevelTypeName("System.Diagnostics", nameof(DebuggerHiddenAttribute)), new TopLevelTypeName("System.Diagnostics", nameof(DebuggerStepThroughAttribute)), + new TopLevelTypeName("System.Diagnostics", nameof(DebuggerBrowsableAttribute)), // Assembly attributes: new TopLevelTypeName("System.Reflection", nameof(AssemblyVersionAttribute)), new TopLevelTypeName("System.Runtime.CompilerServices", nameof(InternalsVisibleToAttribute)), From e043f43925fb066471e4b5881c708aa0ed4f78fe Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Fri, 29 Apr 2022 20:16:50 +0200 Subject: [PATCH 06/19] Ignore local functions when collecting used members. This is necessary because LocalFunctionMethod.DeclaringTypeDefinition points to the current type. --- ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index 8460dd4e33..b63b073964 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -1442,11 +1442,14 @@ void DoDecompileMember(IEntity entity, RecordDecompiler recordDecompiler) foreach (var node in entityDecl.Descendants) { var rr = node.GetResolveResult(); - if (rr is MemberResolveResult mrr && mrr.Member.DeclaringTypeDefinition == typeDef) + if (rr is MemberResolveResult mrr + && mrr.Member.DeclaringTypeDefinition == typeDef + && !(mrr.Member is IMethod { IsLocalFunction: true })) { workList.Enqueue(mrr.Member); } - else if (rr is TypeResolveResult trr && trr.Type.GetDefinition()?.DeclaringTypeDefinition == typeDef) + else if (rr is TypeResolveResult trr + && trr.Type.GetDefinition()?.DeclaringTypeDefinition == typeDef) { workList.Enqueue(trr.Type.GetDefinition()); } From 54b5c222832334b7d0cbee75c998ae254d904ac7 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sat, 30 Apr 2022 19:13:40 +0200 Subject: [PATCH 07/19] Fix formatting. --- ILSpy/Metadata/DebugDirectory/DebugDirectoryEntryTreeNode.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ILSpy/Metadata/DebugDirectory/DebugDirectoryEntryTreeNode.cs b/ILSpy/Metadata/DebugDirectory/DebugDirectoryEntryTreeNode.cs index 5eb4da06fd..995cc8a173 100644 --- a/ILSpy/Metadata/DebugDirectory/DebugDirectoryEntryTreeNode.cs +++ b/ILSpy/Metadata/DebugDirectory/DebugDirectoryEntryTreeNode.cs @@ -56,7 +56,7 @@ public override void Decompile(Language language, ITextOutput output, Decompilat else { language.WriteCommentLine(output, $"(no data)"); - } + } } } } From 071ca335614ba8c9e548d999a216d9e399e67c68 Mon Sep 17 00:00:00 2001 From: "Andrew Crawley (US - DIAGNOSTICS)" Date: Wed, 27 Apr 2022 17:54:02 -0700 Subject: [PATCH 08/19] Fix file locking issue in tests --- .../ICSharpCode.Decompiler.Tests.csproj | 1 + .../PdbGenerationTestRunner.cs | 2 +- .../TestCases/PdbGen/CustomPdbId.xml | 19 +++++++++++++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 ICSharpCode.Decompiler.Tests/TestCases/PdbGen/CustomPdbId.xml diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj index 8d1dfa60b0..622b84aa73 100644 --- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj +++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj @@ -307,6 +307,7 @@ + diff --git a/ICSharpCode.Decompiler.Tests/PdbGenerationTestRunner.cs b/ICSharpCode.Decompiler.Tests/PdbGenerationTestRunner.cs index f55098e8e5..174711c8b5 100644 --- a/ICSharpCode.Decompiler.Tests/PdbGenerationTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/PdbGenerationTestRunner.cs @@ -51,7 +51,7 @@ public void LambdaCapturing() public void CustomPdbId() { // Generate a PDB for an assembly using a randomly-generated ID, then validate that the PDB uses the specified ID - (string peFileName, string pdbFileName) = CompileTestCase(nameof(HelloWorld)); + (string peFileName, string pdbFileName) = CompileTestCase(nameof(CustomPdbId)); var moduleDefinition = new PEFile(peFileName); var resolver = new UniversalAssemblyResolver(peFileName, false, moduleDefinition.Metadata.DetectTargetFrameworkId(), null, PEStreamOptions.PrefetchEntireImage); diff --git a/ICSharpCode.Decompiler.Tests/TestCases/PdbGen/CustomPdbId.xml b/ICSharpCode.Decompiler.Tests/TestCases/PdbGen/CustomPdbId.xml new file mode 100644 index 0000000000..e0708da65a --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/PdbGen/CustomPdbId.xml @@ -0,0 +1,19 @@ + + + + + + \ No newline at end of file From d6d0392d2f4f6d50bee568462da59fc20f723046 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sat, 30 Apr 2022 20:26:55 +0200 Subject: [PATCH 09/19] Fix crash when IL byte code unexpectedly ends in the middle of an operand. --- .../CSharp/RequiredNamespaceCollector.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/ICSharpCode.Decompiler/CSharp/RequiredNamespaceCollector.cs b/ICSharpCode.Decompiler/CSharp/RequiredNamespaceCollector.cs index 6c4aa4acd8..459a71a142 100644 --- a/ICSharpCode.Decompiler/CSharp/RequiredNamespaceCollector.cs +++ b/ICSharpCode.Decompiler/CSharp/RequiredNamespaceCollector.cs @@ -302,7 +302,15 @@ void CollectNamespacesFromMethodBody(MethodBodyBlock method, MetadataModule modu case OperandType.Sig: case OperandType.Tok: case OperandType.Type: - var handle = MetadataTokenHelpers.EntityHandleOrNil(instructions.ReadInt32()); + EntityHandle handle; + try + { + handle = MetadataTokenHelpers.EntityHandleOrNil(instructions.ReadInt32()); + } + catch (BadImageFormatException) + { + return; + } if (handle.IsNil) break; switch (handle.Kind) From 54a3bba8207788a134c7185ab9f512519ad55174 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sat, 7 May 2022 11:21:10 +0200 Subject: [PATCH 10/19] #2685: Do not add interface impl helper for extern methods. --- ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index b63b073964..ebf80585a5 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -1107,6 +1107,10 @@ public string DecompileAsString(IEnumerable definitions) { yield break; // cannot create forwarder for static interface impl } + if (memberDecl.HasModifier(Modifiers.Extern)) + { + yield break; // cannot create forwarder for extern method + } var genericContext = new Decompiler.TypeSystem.GenericContext(method); var methodHandle = (MethodDefinitionHandle)method.MetadataToken; foreach (var h in methodHandle.GetMethodImplementations(metadata)) From 146fd72e4f38e9f43db99dfaf87f77a567d4a263 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Tue, 10 May 2022 19:29:45 +0200 Subject: [PATCH 11/19] Do not add NETCORE preproc symbol if CompilerOptions.TargetNet40 is specified. --- ICSharpCode.Decompiler.Tests/Helpers/Tester.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs b/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs index 8d6b68b675..e71a1cb711 100644 --- a/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs +++ b/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs @@ -318,7 +318,10 @@ public static List GetPreprocessorSymbols(CompilerOptions flags) } if ((flags & CompilerOptions.UseRoslynMask) != 0) { - preprocessorSymbols.Add("NETCORE"); + if (!flags.HasFlag(CompilerOptions.TargetNet40)) + { + preprocessorSymbols.Add("NETCORE"); + } preprocessorSymbols.Add("ROSLYN"); preprocessorSymbols.Add("CS60"); preprocessorSymbols.Add("VB11"); From e61f46c4749a35a9c0a65ecc9e0abf0d690da0eb Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Tue, 10 May 2022 19:34:28 +0200 Subject: [PATCH 12/19] Add MethodCodeType argument to MethodImplAttribute. --- .../PrettyTestRunner.cs | 6 ++ .../TestCases/Pretty/MetadataAttributes.cs | 92 +++++++++++++++++++ .../Implementation/MetadataMethod.cs | 14 ++- 3 files changed, 107 insertions(+), 5 deletions(-) create mode 100644 ICSharpCode.Decompiler.Tests/TestCases/Pretty/MetadataAttributes.cs diff --git a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs index db7b7ff301..57cea8ebad 100644 --- a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs @@ -714,6 +714,12 @@ public async Task StaticAbstractInterfaceMembers([ValueSource(nameof(roslynLates await RunForLibrary(cscOptions: cscOptions | CompilerOptions.Preview); } + [Test] + public async Task MetadataAttributes([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + async Task RunForLibrary([CallerMemberName] string testName = null, AssemblerOptions asmOptions = AssemblerOptions.None, CompilerOptions cscOptions = CompilerOptions.None, DecompilerSettings decompilerSettings = null) { await Run(testName, asmOptions | AssemblerOptions.Library, cscOptions | CompilerOptions.Library, decompilerSettings); diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/MetadataAttributes.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/MetadataAttributes.cs new file mode 100644 index 0000000000..a1a8e258cf --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/MetadataAttributes.cs @@ -0,0 +1,92 @@ +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty +{ + internal class MetadataAttributes + { + private class MethodImplAttr + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public extern void A(); +#if NETCORE + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + public extern void B(); +#endif + [MethodImpl(MethodImplOptions.ForwardRef)] + public extern void D(); + [MethodImpl(MethodImplOptions.InternalCall)] + public extern void E(); + [MethodImpl(MethodImplOptions.NoInlining)] + public extern void F(); + [MethodImpl(MethodImplOptions.NoOptimization)] + public extern void G(); + [PreserveSig] + public extern void H(); + [MethodImpl(MethodImplOptions.Synchronized)] + public extern void I(); + [MethodImpl(MethodImplOptions.Unmanaged)] + public extern void J(); + [MethodImpl(MethodImplOptions.AggressiveInlining, MethodCodeType = MethodCodeType.Native)] + public extern void A1(); +#if NETCORE + [MethodImpl(MethodImplOptions.AggressiveOptimization, MethodCodeType = MethodCodeType.Native)] + public extern void B1(); +#endif + [MethodImpl(MethodImplOptions.ForwardRef, MethodCodeType = MethodCodeType.Native)] + public extern void D1(); + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Native)] + public extern void E1(); + [MethodImpl(MethodImplOptions.NoInlining, MethodCodeType = MethodCodeType.Native)] + public extern void F1(); + [MethodImpl(MethodImplOptions.NoOptimization, MethodCodeType = MethodCodeType.Native)] + public extern void G1(); + [MethodImpl(MethodImplOptions.PreserveSig, MethodCodeType = MethodCodeType.Native)] + public extern void H1(); + [MethodImpl(MethodImplOptions.Synchronized, MethodCodeType = MethodCodeType.Native)] + public extern void I1(); + [MethodImpl(MethodImplOptions.Unmanaged, MethodCodeType = MethodCodeType.Native)] + public extern void J1(); + [MethodImpl(MethodImplOptions.AggressiveInlining, MethodCodeType = MethodCodeType.OPTIL)] + public extern void A2(); +#if NETCORE + [MethodImpl(MethodImplOptions.AggressiveOptimization, MethodCodeType = MethodCodeType.OPTIL)] + public extern void B2(); +#endif + [MethodImpl(MethodImplOptions.ForwardRef, MethodCodeType = MethodCodeType.OPTIL)] + public extern void D2(); + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.OPTIL)] + public extern void E2(); + [MethodImpl(MethodImplOptions.NoInlining, MethodCodeType = MethodCodeType.OPTIL)] + public extern void F2(); + [MethodImpl(MethodImplOptions.NoOptimization, MethodCodeType = MethodCodeType.OPTIL)] + public extern void G2(); + [MethodImpl(MethodImplOptions.PreserveSig, MethodCodeType = MethodCodeType.OPTIL)] + public extern void H2(); + [MethodImpl(MethodImplOptions.Synchronized, MethodCodeType = MethodCodeType.OPTIL)] + public extern void I2(); + [MethodImpl(MethodImplOptions.Unmanaged, MethodCodeType = MethodCodeType.OPTIL)] + public extern void J2(); + [MethodImpl(MethodImplOptions.AggressiveInlining, MethodCodeType = MethodCodeType.OPTIL)] + public extern void A3(); +#if NETCORE + [MethodImpl(MethodImplOptions.AggressiveOptimization, MethodCodeType = MethodCodeType.Runtime)] + public extern void B3(); +#endif + [MethodImpl(MethodImplOptions.ForwardRef, MethodCodeType = MethodCodeType.Runtime)] + public extern void D3(); + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + public extern void E3(); + [MethodImpl(MethodImplOptions.NoInlining, MethodCodeType = MethodCodeType.Runtime)] + public extern void F3(); + [MethodImpl(MethodImplOptions.NoOptimization, MethodCodeType = MethodCodeType.Runtime)] + public extern void G3(); + [MethodImpl(MethodImplOptions.PreserveSig, MethodCodeType = MethodCodeType.Runtime)] + public extern void H3(); + [MethodImpl(MethodImplOptions.Synchronized, MethodCodeType = MethodCodeType.Runtime)] + public extern void I3(); + [MethodImpl(MethodImplOptions.Unmanaged, MethodCodeType = MethodCodeType.Runtime)] + public extern void J3(); + } + } +} diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataMethod.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataMethod.cs index e9a1d7ac98..282740f92a 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataMethod.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataMethod.cs @@ -333,6 +333,7 @@ public IEnumerable GetAttributes() var metadata = module.metadata; var def = metadata.GetMethodDefinition(handle); MethodImplAttributes implAttributes = def.ImplAttributes & ~MethodImplAttributes.CodeTypeMask; + int methodCodeType = (int)(def.ImplAttributes & MethodImplAttributes.CodeTypeMask); #region DllImportAttribute var info = def.GetImport(); @@ -430,7 +431,7 @@ public IEnumerable GetAttributes() #endregion #region PreserveSigAttribute - if (implAttributes == MethodImplAttributes.PreserveSig) + if (implAttributes == MethodImplAttributes.PreserveSig && methodCodeType == 0) { b.Add(KnownAttribute.PreserveSig); implAttributes = 0; @@ -440,10 +441,13 @@ public IEnumerable GetAttributes() #region MethodImplAttribute if (implAttributes != 0) { - b.Add(KnownAttribute.MethodImpl, - new TopLevelTypeName("System.Runtime.CompilerServices", nameof(MethodImplOptions)), - (int)implAttributes - ); + var methodImpl = new AttributeBuilder(module, KnownAttribute.MethodImpl); + methodImpl.AddFixedArg(new TopLevelTypeName("System.Runtime.CompilerServices", nameof(MethodImplOptions)), (int)implAttributes); + if (methodCodeType != 0) + { + methodImpl.AddNamedArg("MethodCodeType", new TopLevelTypeName("System.Runtime.CompilerServices", nameof(MethodCodeType)), methodCodeType); + } + b.Add(methodImpl.Build()); } #endregion From 1aa36a23f228f16afd13b9b7aebf20fba119a1a6 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Tue, 10 May 2022 13:35:05 +0200 Subject: [PATCH 13/19] Use test-summary/action@v1 for test reports. --- .github/workflows/build-ilspy.yml | 14 +++++--------- .../ICSharpCode.Decompiler.Tests.csproj | 3 +++ .../ILSpy.BamlDecompiler.Tests.csproj | 2 ++ ILSpy.Tests/ILSpy.Tests.csproj | 2 ++ packages.props | 1 + 5 files changed, 13 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build-ilspy.yml b/.github/workflows/build-ilspy.yml index b15519581f..3a2e278c83 100644 --- a/.github/workflows/build-ilspy.yml +++ b/.github/workflows/build-ilspy.yml @@ -47,7 +47,7 @@ jobs: run: msbuild ILSpy.sln /p:Configuration=${{ matrix.configuration }} /p:Platform=$env:BuildPlatform /m - name: Execute unit tests - run: dotnet test --logger "trx;LogFileName=${{ matrix.configuration }}-test-results.trx" $env:Tests1 $env:Tests2 $env:Tests3 + run: dotnet test --logger "junit;LogFileName=${{ matrix.configuration }}.xml" --results-directory test-results $env:Tests1 $env:Tests2 $env:Tests3 env: Tests1: ICSharpCode.Decompiler.Tests\bin\${{ matrix.configuration }}\net6.0-windows\win-x64\ICSharpCode.Decompiler.Tests.dll Tests2: ILSpy.Tests\bin\${{ matrix.configuration }}\net6.0-windows\ILSpy.Tests.dll @@ -58,17 +58,13 @@ jobs: if: success() || failure() with: name: test-results-${{ matrix.configuration }} - path: '**/*.trx' + path: 'test-results/${{ matrix.configuration }}.xml' - name: Create Test Report - uses: phoenix-actions/test-reporting@v6 - if: github.event_name != 'pull_request' && (success() || failure()) + uses: test-summary/action@v1 + if: always() with: - name: Unit Test Results (${{ matrix.configuration }}) - path: '**/*.trx' - reporter: dotnet-trx - list-suites: 'all' - list-tests: 'failed' + paths: "test-results/${{ matrix.configuration }}.xml" - name: Format check run: python BuildTools\tidy.py diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj index 622b84aa73..bd7cb7915a 100644 --- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj +++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj @@ -54,6 +54,8 @@ + + @@ -106,6 +108,7 @@ + diff --git a/ILSpy.BamlDecompiler.Tests/ILSpy.BamlDecompiler.Tests.csproj b/ILSpy.BamlDecompiler.Tests/ILSpy.BamlDecompiler.Tests.csproj index 9c8ccfa47e..8910e3084e 100644 --- a/ILSpy.BamlDecompiler.Tests/ILSpy.BamlDecompiler.Tests.csproj +++ b/ILSpy.BamlDecompiler.Tests/ILSpy.BamlDecompiler.Tests.csproj @@ -33,6 +33,8 @@ + + diff --git a/ILSpy.Tests/ILSpy.Tests.csproj b/ILSpy.Tests/ILSpy.Tests.csproj index 345189f44c..5c6bc058e6 100644 --- a/ILSpy.Tests/ILSpy.Tests.csproj +++ b/ILSpy.Tests/ILSpy.Tests.csproj @@ -52,6 +52,8 @@ + + diff --git a/packages.props b/packages.props index f26b44c9f1..fe4e907483 100644 --- a/packages.props +++ b/packages.props @@ -20,6 +20,7 @@ 2.7.4 3.13.3 4.2.1 + 3.0.110 3.1.2 17.0.0 4.16.1 From 3e05a8d763eb62df7667b97192acdd74d5956229 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Tue, 10 May 2022 22:21:48 +0200 Subject: [PATCH 14/19] #2685: Emit 'override' without 'newslot' as 'virtual' if there is no (known) method to override. --- ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index ebf80585a5..049042ea21 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -1567,6 +1567,14 @@ EntityDeclaration DoDecompile(IMethod method, DecompileRun decompileRun, ITypeRe { SetNewModifier(methodDecl); } + else if (!method.IsVirtual && method.IsOverride && InheritanceHelper.GetBaseMember(method) == null) + { + methodDecl.Modifiers &= ~Modifiers.Override; + if (!method.DeclaringTypeDefinition.IsSealed) + { + methodDecl.Modifiers |= Modifiers.Virtual; + } + } if (IsCovariantReturnOverride(method)) { RemoveAttribute(methodDecl, KnownAttribute.PreserveBaseOverrides); From c66eb7bbef640611dc71a18f25a3ebf2d45b4831 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Tue, 10 May 2022 23:10:43 +0200 Subject: [PATCH 15/19] #2685: Hide ctors from ComImport classes. #2685: Remove logic added in previous commit, as it breaks our ILPretty tests. --- .../CSharp/CSharpDecompiler.cs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index 049042ea21..3af5d305d5 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -289,6 +289,9 @@ public static bool MemberIsHidden(Metadata.PEFile module, EntityHandle member, D var methodSemantics = module.MethodSemanticsLookup.GetSemantics(methodHandle).Item2; if (methodSemantics != 0 && methodSemantics != System.Reflection.MethodSemanticsAttributes.Other) return true; + name = metadata.GetString(method.Name); + if (name == ".ctor" && method.RelativeVirtualAddress == 0 && metadata.GetTypeDefinition(method.GetDeclaringType()).Attributes.HasFlag(System.Reflection.TypeAttributes.Import)) + return true; if (settings.LocalFunctions && LocalFunctionDecompiler.IsLocalFunctionMethod(module, methodHandle)) return true; if (settings.AnonymousMethods && methodHandle.HasGeneratedName(metadata) && methodHandle.IsCompilerGenerated(metadata)) @@ -1567,14 +1570,14 @@ EntityDeclaration DoDecompile(IMethod method, DecompileRun decompileRun, ITypeRe { SetNewModifier(methodDecl); } - else if (!method.IsVirtual && method.IsOverride && InheritanceHelper.GetBaseMember(method) == null) - { - methodDecl.Modifiers &= ~Modifiers.Override; - if (!method.DeclaringTypeDefinition.IsSealed) - { - methodDecl.Modifiers |= Modifiers.Virtual; - } - } + //else if (!method.IsVirtual && method.IsOverride && InheritanceHelper.GetBaseMember(method) == null) + //{ + // methodDecl.Modifiers &= ~Modifiers.Override; + // if (!method.DeclaringTypeDefinition.IsSealed) + // { + // methodDecl.Modifiers |= Modifiers.Virtual; + // } + //} if (IsCovariantReturnOverride(method)) { RemoveAttribute(methodDecl, KnownAttribute.PreserveBaseOverrides); From 7e08c348b5665256c7d1379cc94ff62cc2eaf606 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Wed, 11 May 2022 23:16:01 +0200 Subject: [PATCH 16/19] #2685: Emit 'override' without 'newslot' as 'virtual' if there is no (known) method to override. --- .../TestCases/ILPretty/FSharpLoops_Debug.cs | 4 +-- .../TestCases/ILPretty/FSharpLoops_Release.cs | 4 +-- .../TestCases/ILPretty/SequenceOfNestedIfs.cs | 4 +-- .../TestCases/ILPretty/UnknownTypes.cs | 2 +- .../CSharp/CSharpDecompiler.cs | 33 ++++++++++++++----- ICSharpCode.Decompiler/DecompileRun.cs | 2 ++ 6 files changed, 34 insertions(+), 15 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpLoops_Debug.cs b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpLoops_Debug.cs index c2a148c2a2..584b75b181 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpLoops_Debug.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpLoops_Debug.cs @@ -92,7 +92,7 @@ public override void Close() pc = 2; } - public override bool get_CheckClose() + public bool get_CheckClose() { switch (pc) { @@ -106,7 +106,7 @@ public override bool get_CheckClose() [DebuggerNonUserCode] [CompilerGenerated] - public override int get_LastGenerated() + public int get_LastGenerated() { return current; } diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpLoops_Release.cs b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpLoops_Release.cs index eb885b0713..7d6c0e14b0 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpLoops_Release.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpLoops_Release.cs @@ -93,7 +93,7 @@ public override void Close() pc = 2; } - public override bool get_CheckClose() + public bool get_CheckClose() { switch (pc) { @@ -107,7 +107,7 @@ public override bool get_CheckClose() [DebuggerNonUserCode] [CompilerGenerated] - public override int get_LastGenerated() + public int get_LastGenerated() { return current; } diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/SequenceOfNestedIfs.cs b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/SequenceOfNestedIfs.cs index 3f14f46386..9603be34dc 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/SequenceOfNestedIfs.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/SequenceOfNestedIfs.cs @@ -12,11 +12,11 @@ public class SequenceOfNestedIfs { public bool _clear; public Material _material; - public override bool CheckShader() + public virtual bool CheckShader() { return false; } - public override void CreateMaterials() + public virtual void CreateMaterials() { if (!_clear) { diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/UnknownTypes.cs b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/UnknownTypes.cs index c77ae5e148..b834dae1bb 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/UnknownTypes.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/UnknownTypes.cs @@ -2,7 +2,7 @@ internal class UnknownTypes { private readonly IInterface memberField; - public override bool CanExecute(CallbackQuery message) + public virtual bool CanExecute(CallbackQuery message) { return ((IInterface)(object)memberField).Execute(new SomeClass { ChatId = StaticClass.GetChatId(message), diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index 3af5d305d5..52078eb828 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -1570,14 +1570,16 @@ EntityDeclaration DoDecompile(IMethod method, DecompileRun decompileRun, ITypeRe { SetNewModifier(methodDecl); } - //else if (!method.IsVirtual && method.IsOverride && InheritanceHelper.GetBaseMember(method) == null) - //{ - // methodDecl.Modifiers &= ~Modifiers.Override; - // if (!method.DeclaringTypeDefinition.IsSealed) - // { - // methodDecl.Modifiers |= Modifiers.Virtual; - // } - //} + else if (!method.IsStatic && !method.IsExplicitInterfaceImplementation + && !method.IsVirtual && method.IsOverride + && InheritanceHelper.GetBaseMember(method) == null && IsTypeHierarchyKnown(method.DeclaringType)) + { + methodDecl.Modifiers &= ~Modifiers.Override; + if (!method.DeclaringTypeDefinition.IsSealed) + { + methodDecl.Modifiers |= Modifiers.Virtual; + } + } if (IsCovariantReturnOverride(method)) { RemoveAttribute(methodDecl, KnownAttribute.PreserveBaseOverrides); @@ -1585,6 +1587,21 @@ EntityDeclaration DoDecompile(IMethod method, DecompileRun decompileRun, ITypeRe methodDecl.Modifiers |= Modifiers.Override; } return methodDecl; + + bool IsTypeHierarchyKnown(IType type) + { + var definition = type.GetDefinition(); + if (definition == null) + { + return false; + } + + if (decompileRun.TypeHierarchyIsKnown.TryGetValue(definition, out var value)) + return value; + value = method.DeclaringType.GetNonInterfaceBaseTypes().All(t => t.Kind != TypeKind.Unknown); + decompileRun.TypeHierarchyIsKnown.Add(definition, value); + return value; + } } finally { diff --git a/ICSharpCode.Decompiler/DecompileRun.cs b/ICSharpCode.Decompiler/DecompileRun.cs index 4d278ab7fd..2692ddf1af 100644 --- a/ICSharpCode.Decompiler/DecompileRun.cs +++ b/ICSharpCode.Decompiler/DecompileRun.cs @@ -19,6 +19,8 @@ internal class DecompileRun public IDocumentationProvider DocumentationProvider { get; set; } public Dictionary RecordDecompilers { get; } = new Dictionary(); + public Dictionary TypeHierarchyIsKnown { get; } = new(); + Lazy usingScope => new Lazy(() => CreateUsingScope(Namespaces)); From 44aa41d933be3d5f2e50d8996486368876ea71f8 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sat, 14 May 2022 10:39:48 +0200 Subject: [PATCH 17/19] Fix #2686: Introduce ToolPaneModel.AssociatedCommand to allow overriding Window-menu action. --- ILSpy/Analyzers/AnalyzeCommand.cs | 17 ++++++++++++----- ILSpy/MainWindow.xaml.cs | 4 ++-- ILSpy/ViewModels/AnalyzerPaneModel.cs | 1 + ILSpy/ViewModels/ToolPaneModel.cs | 2 ++ 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/ILSpy/Analyzers/AnalyzeCommand.cs b/ILSpy/Analyzers/AnalyzeCommand.cs index 71a0415391..e221235e83 100644 --- a/ILSpy/Analyzers/AnalyzeCommand.cs +++ b/ILSpy/Analyzers/AnalyzeCommand.cs @@ -58,24 +58,30 @@ bool IsValidReference(object reference) public void Execute(TextViewContext context) { + AnalyzerTreeView analyzerTreeView = MainWindow.Instance.AnalyzerTreeView; + if (analyzerTreeView == null) + { + return; + } if (context.SelectedTreeNodes != null) { foreach (IMemberTreeNode node in context.SelectedTreeNodes) { - MainWindow.Instance.AnalyzerTreeView.Analyze(node.Member); + analyzerTreeView.Analyze(node.Member); } } else if (context.Reference != null && context.Reference.Reference is IEntity entity) { - MainWindow.Instance.AnalyzerTreeView.Analyze(entity); + analyzerTreeView.Analyze(entity); } } public override bool CanExecute(object parameter) { - if (MainWindow.Instance.AnalyzerTreeView.IsKeyboardFocusWithin) + AnalyzerTreeView analyzerTreeView = MainWindow.Instance.AnalyzerTreeView; + if (analyzerTreeView != null && analyzerTreeView.IsKeyboardFocusWithin) { - return MainWindow.Instance.AnalyzerTreeView.SelectedItems.OfType().All(n => n is IMemberTreeNode); + return analyzerTreeView.SelectedItems.OfType().All(n => n is IMemberTreeNode); } else { @@ -85,7 +91,8 @@ public override bool CanExecute(object parameter) public override void Execute(object parameter) { - if (MainWindow.Instance.AnalyzerTreeView.IsKeyboardFocusWithin) + AnalyzerTreeView analyzerTreeView = MainWindow.Instance.AnalyzerTreeView; + if (analyzerTreeView != null && analyzerTreeView.IsKeyboardFocusWithin) { foreach (IMemberTreeNode node in MainWindow.Instance.AnalyzerTreeView.SelectedItems.OfType().ToArray()) { diff --git a/ILSpy/MainWindow.xaml.cs b/ILSpy/MainWindow.xaml.cs index c6331c4ec1..69920b9603 100644 --- a/ILSpy/MainWindow.xaml.cs +++ b/ILSpy/MainWindow.xaml.cs @@ -89,7 +89,7 @@ partial class MainWindow : Window public AnalyzerTreeView AnalyzerTreeView { get { - return FindResource("AnalyzerTreeView") as AnalyzerTreeView; + return !IsLoaded ? null : FindResource("AnalyzerTreeView") as AnalyzerTreeView; } } @@ -428,7 +428,7 @@ void ToolsChanged(object sender, NotifyCollectionChangedEventArgs e) MenuItem CreateMenuItem(ToolPaneModel pane) { MenuItem menuItem = new MenuItem(); - menuItem.Command = new ToolPaneCommand(pane.ContentId); + menuItem.Command = pane.AssociatedCommand ?? new ToolPaneCommand(pane.ContentId); menuItem.Header = pane.Title; menuItem.Tag = pane; var shortcutKey = pane.ShortcutKey; diff --git a/ILSpy/ViewModels/AnalyzerPaneModel.cs b/ILSpy/ViewModels/AnalyzerPaneModel.cs index d86558ee65..a91f27e3fc 100644 --- a/ILSpy/ViewModels/AnalyzerPaneModel.cs +++ b/ILSpy/ViewModels/AnalyzerPaneModel.cs @@ -31,6 +31,7 @@ private AnalyzerPaneModel() ContentId = PaneContentId; Title = Properties.Resources.Analyze; ShortcutKey = new KeyGesture(Key.R, ModifierKeys.Control); + AssociatedCommand = ILSpyCommands.Analyze; } public override DataTemplate Template => (DataTemplate)MainWindow.Instance.FindResource("AnalyzerPaneTemplate"); diff --git a/ILSpy/ViewModels/ToolPaneModel.cs b/ILSpy/ViewModels/ToolPaneModel.cs index 547385b1b5..15a79250ba 100644 --- a/ILSpy/ViewModels/ToolPaneModel.cs +++ b/ILSpy/ViewModels/ToolPaneModel.cs @@ -34,5 +34,7 @@ public virtual void Show() public KeyGesture ShortcutKey { get; protected set; } public string Icon { get; protected set; } + + public ICommand AssociatedCommand { get; set; } } } From a79161207748a8e2f732fc50dfe155f4368483cd Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 15 May 2022 12:44:58 +0200 Subject: [PATCH 18/19] Fix broken HexFilterControl --- ILSpy/Metadata/Helpers.cs | 3 +++ ILSpy/Metadata/HexFilterControl.xaml.cs | 2 ++ 2 files changed, 5 insertions(+) diff --git a/ILSpy/Metadata/Helpers.cs b/ILSpy/Metadata/Helpers.cs index add2fbd15d..cd6558a178 100644 --- a/ILSpy/Metadata/Helpers.cs +++ b/ILSpy/Metadata/Helpers.cs @@ -136,6 +136,7 @@ DataGridColumn GetColumn() { return new DataGridCheckBoxColumn() { Header = e.PropertyName, + SortMemberPath = e.PropertyName, Binding = binding }; } @@ -146,12 +147,14 @@ DataGridColumn GetColumn() { return new DataGridTemplateColumn() { Header = e.PropertyName, + SortMemberPath = e.PropertyName, CellTemplate = GetOrCreateLinkCellTemplate(e.PropertyName, descriptor, binding) }; } return new DataGridTextColumn() { Header = e.PropertyName, + SortMemberPath = e.PropertyName, Binding = binding }; } diff --git a/ILSpy/Metadata/HexFilterControl.xaml.cs b/ILSpy/Metadata/HexFilterControl.xaml.cs index 9a50e0bed2..26f4d5ea7a 100644 --- a/ILSpy/Metadata/HexFilterControl.xaml.cs +++ b/ILSpy/Metadata/HexFilterControl.xaml.cs @@ -73,6 +73,8 @@ public ContentFilter(string filter) public bool IsMatch(object value) { + if (string.IsNullOrWhiteSpace(filter)) + return true; if (value == null) return false; From c0f01353730feeee66cc6fb84f882e1e8cc3f937 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Tue, 17 May 2022 10:09:13 +0200 Subject: [PATCH 19/19] #2691: Do not use AssemblyDefintion.GetAssemblyName(). This fails in culture-invariant mode (ilspycmd) when trying to work with satellite assemblies, because System.Reflection.AssemblyName tries to retrieve CultureInfo of the assembly culture. --- .../Metadata/DotNetCorePathFinderExtensions.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ICSharpCode.Decompiler/Metadata/DotNetCorePathFinderExtensions.cs b/ICSharpCode.Decompiler/Metadata/DotNetCorePathFinderExtensions.cs index e391ab1e24..9e31cf9c90 100644 --- a/ICSharpCode.Decompiler/Metadata/DotNetCorePathFinderExtensions.cs +++ b/ICSharpCode.Decompiler/Metadata/DotNetCorePathFinderExtensions.cs @@ -73,13 +73,13 @@ public static string DetectTargetFrameworkId(this MetadataReader metadata, strin if (metadata.IsAssembly) { - var thisAssemblyName = metadata.GetAssemblyDefinition().GetAssemblyName(); - switch (thisAssemblyName.Name) + AssemblyDefinition assemblyDefinition = metadata.GetAssemblyDefinition(); + switch (metadata.GetString(assemblyDefinition.Name)) { case "mscorlib": - return $".NETFramework,Version=v{thisAssemblyName.Version.ToString(2)}"; + return $".NETFramework,Version=v{assemblyDefinition.Version.ToString(2)}"; case "netstandard": - return $".NETStandard,Version=v{thisAssemblyName.Version.ToString(2)}"; + return $".NETStandard,Version=v{assemblyDefinition.Version.ToString(2)}"; } }