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/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"); diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj index 8013ab9e65..0f100b8736 100644 --- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj +++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj @@ -54,6 +54,8 @@ + + @@ -108,6 +110,7 @@ + @@ -309,6 +312,7 @@ + diff --git a/ICSharpCode.Decompiler.Tests/PdbGenerationTestRunner.cs b/ICSharpCode.Decompiler.Tests/PdbGenerationTestRunner.cs index 35f1eede4f..174711c8b5 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(CustomPdbId)); + + 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.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/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.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 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/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index c8c9d60ffd..52078eb828 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)) @@ -1107,6 +1110,10 @@ IEnumerable AddInterfaceImplHelpers( { 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)) @@ -1244,9 +1251,12 @@ EntityDeclaration DoDecompile(ITypeDefinition typeDef, DecompileRun decompileRun { Debug.Assert(decompilationContext.CurrentTypeDefinition == typeDef); var watch = System.Diagnostics.Stopwatch.StartNew(); + var entityMap = new MultiDictionary(); + var workList = new Queue(); + 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 +1299,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 +1309,38 @@ 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); - foreach (var member in allOrderedMembers) + var allOrderedEntities = typeDef.NestedTypes.Concat(allOrderedMembers); + + // Decompile members that are not compiler-generated. + foreach (var entity in allOrderedEntities) { - if (member is IField || member is IProperty) + if (entity.MetadataToken.IsNil || MemberIsHidden(module.PEFile, entity.MetadataToken, settings)) { - 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); - } + continue; } - else + DoDecompileMember(entity, recordDecompiler); + } + + // Decompile compiler-generated members that are still needed. + while (workList.Count > 0) + { + var entity = workList.Dequeue(); + if (entityMap.Contains(entity) || entity.MetadataToken.IsNil) { - throw new ArgumentOutOfRangeException("Unexpected member type"); + // 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]); } + if (typeDecl.Members.OfType().Any(idx => idx.PrivateImplementationType.IsNull)) { // Remove the [DefaultMember] attribute if the class contains indexers @@ -1421,6 +1399,69 @@ EntityDeclaration DoDecompile(ITypeDefinition typeDef, DecompileRun decompileRun watch.Stop(); Instrumentation.DecompilerEventSource.Log.DoDecompileTypeDefinition(typeDef.FullName, watch.ElapsedMilliseconds); } + + void DoDecompileMember(IEntity entity, RecordDecompiler recordDecompiler) + { + EntityDeclaration entityDecl; + 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: + 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 + && !(mrr.Member is IMethod { IsLocalFunction: true })) + { + workList.Enqueue(mrr.Member); + } + else if (rr is TypeResolveResult trr + && trr.Type.GetDefinition()?.DeclaringTypeDefinition == typeDef) + { + workList.Enqueue(trr.Type.GetDefinition()); + } + } + } } EnumValueDisplayMode DetectBestEnumValueDisplayMode(ITypeDefinition typeDef, PEFile module) @@ -1529,6 +1570,16 @@ EntityDeclaration DoDecompile(IMethod method, DecompileRun decompileRun, ITypeRe { SetNewModifier(methodDecl); } + 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); @@ -1536,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/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) 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/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); 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)); 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)}"; } } 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)), 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 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 @@ IEnumerable ILookup.this[TKey key] { get { return this[key]; } } - bool ILookup.Contains(TKey key) + public bool Contains(TKey key) { return dict.ContainsKey(key); } 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/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 @@ public SharpTreeView AssemblyTreeView { 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/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..995cc8a173 --- /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/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; 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()) 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; } } } 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