From ce855885b13c3fe88efb52ef571c568055c22f8b Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Fri, 13 Jul 2018 09:07:38 +0200 Subject: [PATCH] Implement new declsec parser in ReflectionDisassembler + added unit tests. --- .../DisassemblerPrettyTestRunner.cs | 68 ++ .../Helpers/Tester.cs | 1 + .../ICSharpCode.Decompiler.Tests.csproj | 2 + .../Pretty/SecurityDeclarations.il | 381 +++++++++++ .../Disassembler/ReflectionDisassembler.cs | 614 +++++++----------- .../Metadata/CustomAttributeDecoder.cs | 9 +- .../Metadata/UniversalAssemblyResolver.cs | 4 +- 7 files changed, 702 insertions(+), 377 deletions(-) create mode 100644 ICSharpCode.Decompiler.Tests/DisassemblerPrettyTestRunner.cs create mode 100644 ICSharpCode.Decompiler.Tests/TestCases/Disassembler/Pretty/SecurityDeclarations.il diff --git a/ICSharpCode.Decompiler.Tests/DisassemblerPrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/DisassemblerPrettyTestRunner.cs new file mode 100644 index 000000000..ce002b835 --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/DisassemblerPrettyTestRunner.cs @@ -0,0 +1,68 @@ +// Copyright (c) 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. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; +using ICSharpCode.Decompiler.Tests.Helpers; +using NUnit.Framework; + +namespace ICSharpCode.Decompiler.Tests +{ + [TestFixture, Parallelizable(ParallelScope.All)] + public class DisassemblerPrettyTestRunner + { + static readonly string TestCasePath = Tester.TestCasePath + "/Disassembler/Pretty"; + + [Test] + public void AllFilesHaveTests() + { + var testNames = typeof(DisassemblerPrettyTestRunner).GetMethods() + .Where(m => m.GetCustomAttributes(typeof(TestAttribute), false).Any()) + .Select(m => m.Name) + .ToArray(); + foreach (var file in new DirectoryInfo(TestCasePath).EnumerateFiles()) { + if (file.Extension.Equals(".il", StringComparison.OrdinalIgnoreCase)) { + var testName = file.Name.Split('.')[0]; + Assert.Contains(testName, testNames); + } + } + } + + [Test] + public void SecurityDeclarations() + { + Run(); + } + + void Run([CallerMemberName] string testName = null) + { + var ilExpectedFile = Path.Combine(TestCasePath, testName + ".il"); + var ilResultFile = Path.Combine(TestCasePath, testName + ".result.il"); + + var executable = Tester.AssembleIL(ilExpectedFile, AssemblerOptions.Library); + var disassembled = Tester.Disassemble(executable, ilResultFile, AssemblerOptions.UseOwnDisassembler); + + CodeAssert.FilesAreEqual(ilExpectedFile, ilResultFile); + } + } +} diff --git a/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs b/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs index 72d329a5b..82f459196 100644 --- a/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs +++ b/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs @@ -121,6 +121,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers var metadata = peFile.Metadata; var output = new PlainTextOutput(writer); ReflectionDisassembler rd = new ReflectionDisassembler(output, CancellationToken.None); + rd.AssemblyResolver = new UniversalAssemblyResolver(sourceFileName, true, null); rd.DetectControlStructure = false; rd.WriteAssemblyReferences(metadata); if (metadata.IsAssembly) diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj index 3c9d548cc..74df8dbef 100644 --- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj +++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj @@ -62,11 +62,13 @@ + + diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Disassembler/Pretty/SecurityDeclarations.il b/ICSharpCode.Decompiler.Tests/TestCases/Disassembler/Pretty/SecurityDeclarations.il new file mode 100644 index 000000000..056544a9d --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/Disassembler/Pretty/SecurityDeclarations.il @@ -0,0 +1,381 @@ +.assembly extern mscorlib +{ + .publickeytoken = ( + b7 7a 5c 56 19 34 e0 89 + ) + .ver 4:0:0:0 +} +.assembly SecurityDeclarations +{ + .custom instance void [mscorlib]System.Reflection.AssemblyFileVersionAttribute::.ctor(string) = ( + 01 00 07 31 2e 30 2e 30 2e 30 00 00 + ) + .hash algorithm 0x00008004 // SHA1 + .ver 1:0:0:0 +} + +.module SecurityDeclarations.dll +// MVID: {761F919A-2373-48EB-9282-9DAB26913D43} +.imagebase 0x10000000 +.file alignment 0x00000200 +.stackreserve 0x00100000 +.subsystem 0x0003 // WindowsCui +.corflags 0x00000001 // ILOnly + + +.class private auto ansi '' +{ +} // end of class + +.class private sequential ansi sealed beforefieldinit SecurityDeclarations.TestStruct + extends [mscorlib]System.ValueType +{ + .pack 0 + .size 1 + +} // end of class SecurityDeclarations.TestStruct + +.class private auto ansi beforefieldinit SecurityDeclarations.SimpleType + extends [mscorlib]System.Object +{ + // Methods + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + // Method begins at RVA 0x2050 + // Code size 8 (0x8) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: call instance void [mscorlib]System.Object::.ctor() + IL_0006: nop + IL_0007: ret + } // end of method SimpleType::.ctor + +} // end of class SecurityDeclarations.SimpleType + +.class private auto ansi sealed SecurityDeclarations.TestEnum + extends [mscorlib]System.Enum +{ + // Fields + .field public specialname rtspecialname int32 value__ + .field public static literal valuetype SecurityDeclarations.TestEnum A = int32(0) + .field public static literal valuetype SecurityDeclarations.TestEnum B = int32(1) + .field public static literal valuetype SecurityDeclarations.TestEnum C = int32(2) + +} // end of class SecurityDeclarations.TestEnum + +.class private auto ansi beforefieldinit SecurityDeclarations.SecurityAttrTest + extends [mscorlib]System.Security.Permissions.SecurityAttribute +{ + // Fields + .field private string[] _testStringArray + .field private int32[] _testInt32Array + .field private valuetype SecurityDeclarations.TestEnum[] _testEnumArray + .field private class [mscorlib]System.Type[] _testTypeArray + .field public int32 TestInt32 + .field public class [mscorlib]System.Type TestType + .field public valuetype SecurityDeclarations.TestEnum TestEnumType + .field public object TestBoxed + .field public object TestBoxed2 + .field public string TestString + .field public object TestBoxedString + .field public object TestBoxedArray + .field public object TestBoxedType + + // Methods + .method public hidebysig specialname rtspecialname + instance void .ctor ( + valuetype [mscorlib]System.Security.Permissions.SecurityAction action + ) cil managed + { + // Method begins at RVA 0x2059 + // Code size 10 (0xa) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: call instance void [mscorlib]System.Security.Permissions.SecurityAttribute::.ctor(valuetype [mscorlib]System.Security.Permissions.SecurityAction) + IL_0007: nop + IL_0008: nop + IL_0009: ret + } // end of method SecurityAttrTest::.ctor + + .method public hidebysig virtual + instance class [mscorlib]System.Security.IPermission CreatePermission () cil managed + { + // Method begins at RVA 0x2064 + // Code size 7 (0x7) + .maxstack 8 + + IL_0000: nop + IL_0001: newobj instance void [mscorlib]System.NotImplementedException::.ctor() + IL_0006: throw + } // end of method SecurityAttrTest::CreatePermission + + .method public hidebysig specialname + instance string[] get_TestStringArray () cil managed + { + // Method begins at RVA 0x206c + // Code size 7 (0x7) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: ldfld string[] SecurityDeclarations.SecurityAttrTest::_testStringArray + IL_0006: ret + } // end of method SecurityAttrTest::get_TestStringArray + + .method public hidebysig specialname + instance void set_TestStringArray ( + string[] 'value' + ) cil managed + { + // Method begins at RVA 0x2074 + // Code size 8 (0x8) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: stfld string[] SecurityDeclarations.SecurityAttrTest::_testStringArray + IL_0007: ret + } // end of method SecurityAttrTest::set_TestStringArray + + .method public hidebysig specialname + instance int32[] get_TestInt32Array () cil managed + { + // Method begins at RVA 0x207d + // Code size 7 (0x7) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: ldfld int32[] SecurityDeclarations.SecurityAttrTest::_testInt32Array + IL_0006: ret + } // end of method SecurityAttrTest::get_TestInt32Array + + .method public hidebysig specialname + instance void set_TestInt32Array ( + int32[] 'value' + ) cil managed + { + // Method begins at RVA 0x2085 + // Code size 8 (0x8) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: stfld int32[] SecurityDeclarations.SecurityAttrTest::_testInt32Array + IL_0007: ret + } // end of method SecurityAttrTest::set_TestInt32Array + + .method public hidebysig specialname + instance valuetype SecurityDeclarations.TestEnum[] get_TestEnumArray () cil managed + { + // Method begins at RVA 0x208e + // Code size 7 (0x7) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: ldfld valuetype SecurityDeclarations.TestEnum[] SecurityDeclarations.SecurityAttrTest::_testEnumArray + IL_0006: ret + } // end of method SecurityAttrTest::get_TestEnumArray + + .method public hidebysig specialname + instance void set_TestEnumArray ( + valuetype SecurityDeclarations.TestEnum[] 'value' + ) cil managed + { + // Method begins at RVA 0x2096 + // Code size 8 (0x8) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: stfld valuetype SecurityDeclarations.TestEnum[] SecurityDeclarations.SecurityAttrTest::_testEnumArray + IL_0007: ret + } // end of method SecurityAttrTest::set_TestEnumArray + + .method public hidebysig specialname + instance class [mscorlib]System.Type[] get_TestTypeArray () cil managed + { + // Method begins at RVA 0x209f + // Code size 7 (0x7) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: ldfld class [mscorlib]System.Type[] SecurityDeclarations.SecurityAttrTest::_testTypeArray + IL_0006: ret + } // end of method SecurityAttrTest::get_TestTypeArray + + .method public hidebysig specialname + instance void set_TestTypeArray ( + class [mscorlib]System.Type[] 'value' + ) cil managed + { + // Method begins at RVA 0x20a7 + // Code size 8 (0x8) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: stfld class [mscorlib]System.Type[] SecurityDeclarations.SecurityAttrTest::_testTypeArray + IL_0007: ret + } // end of method SecurityAttrTest::set_TestTypeArray + + // Properties + .property instance string[] TestStringArray() + { + .get instance string[] SecurityDeclarations.SecurityAttrTest::get_TestStringArray() + .set instance void SecurityDeclarations.SecurityAttrTest::set_TestStringArray(string[]) + } + .property instance int32[] TestInt32Array() + { + .get instance int32[] SecurityDeclarations.SecurityAttrTest::get_TestInt32Array() + .set instance void SecurityDeclarations.SecurityAttrTest::set_TestInt32Array(int32[]) + } + .property instance valuetype SecurityDeclarations.TestEnum[] TestEnumArray() + { + .get instance valuetype SecurityDeclarations.TestEnum[] SecurityDeclarations.SecurityAttrTest::get_TestEnumArray() + .set instance void SecurityDeclarations.SecurityAttrTest::set_TestEnumArray(valuetype SecurityDeclarations.TestEnum[]) + } + .property instance class [mscorlib]System.Type[] TestTypeArray() + { + .get instance class [mscorlib]System.Type[] SecurityDeclarations.SecurityAttrTest::get_TestTypeArray() + .set instance void SecurityDeclarations.SecurityAttrTest::set_TestTypeArray(class [mscorlib]System.Type[]) + } + +} // end of class SecurityDeclarations.SecurityAttrTest + +.class private auto ansi beforefieldinit SecurityDeclarations.TestStringTypes + extends [mscorlib]System.Object +{ + .permissionset assert = { + class 'SecurityDeclarations.SecurityAttrTest, SecurityDeclarations, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' = { + field string TestString = string('Hello World!') + field object TestBoxedString = object(string('Boxed String')) + property string[] TestStringArray = string[2]('a' 'b') + field object TestBoxedArray = object(string[2]('c' 'd')) + } + } + // Methods + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + // Method begins at RVA 0x2050 + // Code size 8 (0x8) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: call instance void [mscorlib]System.Object::.ctor() + IL_0006: nop + IL_0007: ret + } // end of method TestStringTypes::.ctor + +} // end of class SecurityDeclarations.TestStringTypes + +.class private auto ansi beforefieldinit SecurityDeclarations.TestTypeTypes + extends [mscorlib]System.Object +{ + .permissionset demand = { + class 'SecurityDeclarations.SecurityAttrTest, SecurityDeclarations, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' = { + field type TestType = type(SecurityDeclarations.SimpleType) + field object TestBoxed = object(type(SecurityDeclarations.TestEnum)) + property type[] TestTypeArray = type[2](SecurityDeclarations.TestStruct SecurityDeclarations.SimpleType) + field object TestBoxedArray = object(type[2](SecurityDeclarations.TestStringTypes SecurityDeclarations.TestTypeTypes)) + } + } + // Methods + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + // Method begins at RVA 0x2050 + // Code size 8 (0x8) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: call instance void [mscorlib]System.Object::.ctor() + IL_0006: nop + IL_0007: ret + } // end of method TestTypeTypes::.ctor + +} // end of class SecurityDeclarations.TestTypeTypes + +.class private auto ansi beforefieldinit SecurityDeclarations.TestEnumTypes + extends [mscorlib]System.Object +{ + .permissionset inheritcheck = { + class 'SecurityDeclarations.SecurityAttrTest, SecurityDeclarations, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' = { + field enum SecurityDeclarations.TestEnum TestEnumType = int32(0) + field object TestBoxed = object(int32(1)) + property enum SecurityDeclarations.TestEnum[] TestEnumArray = int32[3](0 1 2) + field object TestBoxed2 = object(object[4](int32(0) int32(1) int32(2) object[1](int32(3)))) + field object TestBoxedArray = object(int32[3](0 1 2)) + } + } + // Methods + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + // Method begins at RVA 0x2050 + // Code size 8 (0x8) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: call instance void [mscorlib]System.Object::.ctor() + IL_0006: nop + IL_0007: ret + } // end of method TestEnumTypes::.ctor + +} // end of class SecurityDeclarations.TestEnumTypes + +.class private auto ansi beforefieldinit SecurityDeclarations.TestInt32Types + extends [mscorlib]System.Object +{ + .permissionset permitonly = { + class 'SecurityDeclarations.SecurityAttrTest, SecurityDeclarations, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' = { + field int32 TestInt32 = int32(5) + field object TestBoxed = object(int32(10)) + property int32[] TestInt32Array = int32[3](1 2 3) + field object TestBoxedArray = object(int32[3](4 5 6)) + field object TestBoxed2 = object(object[3](int32(7) int32(8) int32(9))) + } + } + // Methods + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + // Method begins at RVA 0x2050 + // Code size 8 (0x8) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: call instance void [mscorlib]System.Object::.ctor() + IL_0006: nop + IL_0007: ret + } // end of method TestInt32Types::.ctor + +} // end of class SecurityDeclarations.TestInt32Types + +.class private auto ansi beforefieldinit SecurityDeclarations.NestedArrays + extends [mscorlib]System.Object +{ + .permissionset assert = { + class 'SecurityDeclarations.SecurityAttrTest, SecurityDeclarations, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' = { + field object TestBoxed2 = object(object[4](int32(1) int32(2) int32(3) object[3](int32(4) int32(5) int32(6)))) + } + } + // Methods + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + // Method begins at RVA 0x2050 + // Code size 8 (0x8) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: call instance void [mscorlib]System.Object::.ctor() + IL_0006: nop + IL_0007: ret + } // end of method NestedArrays::.ctor + +} // end of class SecurityDeclarations.NestedArrays + diff --git a/ICSharpCode.Decompiler/Disassembler/ReflectionDisassembler.cs b/ICSharpCode.Decompiler/Disassembler/ReflectionDisassembler.cs index d78184394..9dffc605e 100644 --- a/ICSharpCode.Decompiler/Disassembler/ReflectionDisassembler.cs +++ b/ICSharpCode.Decompiler/Disassembler/ReflectionDisassembler.cs @@ -133,7 +133,6 @@ namespace ICSharpCode.Decompiler.Disassembler public void DisassembleMethod(PEFile module, MethodDefinitionHandle handle) { - mscorlib = null; var genericContext = new GenericContext(handle, module); // write method header output.WriteReference(module, handle, ".method", isDefinition: true); @@ -144,7 +143,6 @@ namespace ICSharpCode.Decompiler.Disassembler public void DisassembleMethodHeader(PEFile module, MethodDefinitionHandle handle) { - mscorlib = null; var genericContext = new GenericContext(handle, module); // write method header output.WriteReference(module, handle, ".method", isDefinition: true); @@ -399,6 +397,153 @@ namespace ICSharpCode.Decompiler.Disassembler } } + class SecurityDeclarationDecoder : ICustomAttributeTypeProvider<(PrimitiveTypeCode, string)> + { + readonly ITextOutput output; + readonly IAssemblyResolver resolver; + readonly PEFile module; + + public SecurityDeclarationDecoder(ITextOutput output, IAssemblyResolver resolver, PEFile module) + { + this.output = output; + this.resolver = resolver; + this.module = module; + } + + public (PrimitiveTypeCode, string) GetPrimitiveType(PrimitiveTypeCode typeCode) + { + return (typeCode, null); + } + + public (PrimitiveTypeCode, string) GetSystemType() + { + return (0, "type"); + } + + public (PrimitiveTypeCode, string) GetSZArrayType((PrimitiveTypeCode, string) elementType) + { + return (elementType.Item1, (elementType.Item2 ?? PrimitiveTypeCodeToString(elementType.Item1)) + "[]"); + } + + public (PrimitiveTypeCode, string) GetTypeFromDefinition(MetadataReader reader, TypeDefinitionHandle handle, byte rawTypeKind) + { + throw new NotImplementedException(); + } + + public (PrimitiveTypeCode, string) GetTypeFromReference(MetadataReader reader, TypeReferenceHandle handle, byte rawTypeKind) + { + throw new NotImplementedException(); + } + + public (PrimitiveTypeCode, string) GetTypeFromSerializedName(string name) + { + if (resolver == null) + throw new EnumUnderlyingTypeResolveException(); + var (containingModule, typeDefHandle) = ResolveType(name, module); + if (typeDefHandle.IsNil) + throw new EnumUnderlyingTypeResolveException(); + if (typeDefHandle.IsEnum(containingModule.Metadata, out var typeCode)) + return (typeCode, "enum " + name); + return (0, name); + } + + public PrimitiveTypeCode GetUnderlyingEnumType((PrimitiveTypeCode, string) type) + { + return type.Item1; + } + + public bool IsSystemType((PrimitiveTypeCode, string) type) + { + return "type" == type.Item2; + } + + (PEFile, TypeDefinitionHandle) ResolveType(string typeName, PEFile module) + { + string[] nameParts = typeName.Split(new[] { ", " }, 2, StringSplitOptions.None); + string[] typeNameParts = nameParts[0].Split('.'); + PEFile containingModule = null; + TypeDefinitionHandle typeDefHandle = default; + // if we deal with an assembly-qualified name, resolve the assembly + if (nameParts.Length == 2) + containingModule = resolver.Resolve(AssemblyNameReference.Parse(nameParts[1])); + if (containingModule != null) { + // try to find the type in the assembly + typeDefHandle = FindType(containingModule, typeNameParts); + } else { + // just fully-qualified name, try current assembly + typeDefHandle = FindType(module, typeNameParts); + containingModule = module; + if (typeDefHandle.IsNil && TryResolveMscorlib(out var mscorlib)) { + // otherwise try mscorlib + typeDefHandle = FindType(mscorlib, typeNameParts); + containingModule = mscorlib; + } + } + + return (containingModule, typeDefHandle); + + TypeDefinitionHandle FindType(PEFile currentModule, string[] name) + { + var metadata = currentModule.Metadata; + var currentNamespace = metadata.GetNamespaceDefinitionRoot(); + ImmutableArray typeDefinitions = default; + + for (int i = 0; i < name.Length; i++) { + string identifier = name[i]; + if (!typeDefinitions.IsDefault) { + restart: + foreach (var type in typeDefinitions) { + var typeDef = metadata.GetTypeDefinition(type); + var currentTypeName = metadata.GetString(typeDef.Name); + if (identifier == currentTypeName) { + if (i + 1 == name.Length) + return type; + typeDefinitions = typeDef.GetNestedTypes(); + goto restart; + } + } + } else { + var next = currentNamespace.NamespaceDefinitions.FirstOrDefault(ns => metadata.StringComparer.Equals(metadata.GetNamespaceDefinition(ns).Name, identifier)); + if (!next.IsNil) { + currentNamespace = metadata.GetNamespaceDefinition(next); + } else { + typeDefinitions = currentNamespace.TypeDefinitions; + i--; + } + } + } + return default; + } + } + + PrimitiveTypeCode ResolveEnumUnderlyingType(string typeName, PEFile module) + { + if (typeName.StartsWith("enum ", StringComparison.Ordinal)) + typeName = typeName.Substring(5); + var (containingModule, typeDefHandle) = ResolveType(typeName, module); + + if (typeDefHandle.IsNil || !typeDefHandle.IsEnum(containingModule.Metadata, out var typeCode)) + throw new EnumUnderlyingTypeResolveException(); + return typeCode; + } + + PEFile mscorlib; + + bool TryResolveMscorlib(out PEFile mscorlib) + { + mscorlib = null; + if (this.mscorlib != null) { + mscorlib = this.mscorlib; + return true; + } + if (resolver == null) { + return false; + } + this.mscorlib = mscorlib = resolver.Resolve(AssemblyNameReference.Parse("mscorlib")); + return this.mscorlib != null; + } + } + void TryDecodeSecurityDeclaration(TextOutputWithRollback output, BlobReader blob, PEFile module) { output.WriteLine(" = {"); @@ -410,7 +555,7 @@ namespace ICSharpCode.Decompiler.Disassembler currentAssemblyName = module.Metadata.GetString(module.Metadata.GetAssemblyDefinition().Name); currentFullAssemblyName = module.Metadata.GetFullAssemblyName(); } - int count = blob.ReadCompressedInteger(); + int count = blob.ReadCompressedInteger(); for (int i = 0; i < count; i++) { var fullTypeName = blob.ReadSerializedString(); string[] nameParts = fullTypeName.Split(new[] { ", " }, StringSplitOptions.None); @@ -427,413 +572,139 @@ namespace ICSharpCode.Decompiler.Disassembler blob.ReadCompressedInteger(); // ? // The specification seems to be incorrect here, so I'm using the logic from Cecil instead. int argCount = blob.ReadCompressedInteger(); + + var decoder = new CustomAttributeDecoder<(PrimitiveTypeCode Code, string Name)>(new SecurityDeclarationDecoder(output, AssemblyResolver, module), module.Metadata, provideBoxingTypeInfo: true); + var arguments = decoder.DecodeNamedArguments(ref blob, argCount); + if (argCount > 0) { output.WriteLine(); output.Indent(); + } - for (int j = 0; j < argCount; j++) { - WriteSecurityDeclarationArgument(output, module, ref blob); - output.WriteLine(); + foreach (var argument in arguments) { + switch (argument.Kind) { + case CustomAttributeNamedArgumentKind.Field: + output.Write("field "); + break; + case CustomAttributeNamedArgumentKind.Property: + output.Write("property "); + break; } + output.Write(argument.Type.Name ?? PrimitiveTypeCodeToString(argument.Type.Code)); + output.Write(" " + argument.Name + " = "); + + WriteValue(output, argument.Type, argument.Value); + output.WriteLine(); + } + + if (argCount > 0) { output.Unindent(); } + output.Write('}'); if (i + 1 < count) output.Write(','); output.WriteLine(); } + output.Unindent(); output.WriteLine("}"); } - enum TypeKind - { - Primitive, - Type, - Enum - } - - struct TypeInfo - { - public PrimitiveSerializationTypeCode TypeCode; - public TypeKind Kind; - public bool IsArray; - public bool IsBoxed; - public string TypeName; - - public TypeInfo(PrimitiveSerializationTypeCode typeCode, TypeKind kind, bool isArray, bool isBoxed, string typeName) - { - TypeCode = typeCode; - Kind = kind; - IsArray = isArray; - IsBoxed = isBoxed; - TypeName = typeName; - } - } - - static TypeInfo ReadArgumentType(ref BlobReader blob) - { - var b = blob.ReadByte(); - if (2 <= b && b <= 14) { - return new TypeInfo((PrimitiveSerializationTypeCode)b, TypeKind.Primitive, false, false, null); - } - switch (b) { - case 0x1d: - var result = ReadArgumentType(ref blob); - return new TypeInfo(result.TypeCode, result.Kind, true, false, result.TypeName); - case 0x50: - return new TypeInfo(0, TypeKind.Type, false, false, null); - case 0x51: // boxed primitive type - return new TypeInfo(0, TypeKind.Primitive, false, true, null); - case 0x55: // enum - return new TypeInfo(0, TypeKind.Enum, false, false, blob.ReadSerializedString()); - default: - throw new BadImageFormatException($"Unexpected custom attribute type 0x{b:x}."); - } - } - - object ReadSimpleArgumentValue(ref BlobReader blob, PEFile module, ref TypeInfo typeInfo) - { - switch (typeInfo.Kind) { - case TypeKind.Enum: - if (typeInfo.TypeCode == 0 && typeInfo.TypeName != null) { - typeInfo.TypeCode = ResolveEnumUnderlyingType(typeInfo.TypeName, module, out _); - } - goto case TypeKind.Primitive; - case TypeKind.Primitive: - switch (typeInfo.TypeCode) { - case PrimitiveSerializationTypeCode.Boolean: return blob.ReadBoolean(); - case PrimitiveSerializationTypeCode.Byte: return blob.ReadByte(); - case PrimitiveSerializationTypeCode.SByte: return blob.ReadSByte(); - case PrimitiveSerializationTypeCode.Char: return blob.ReadChar(); - case PrimitiveSerializationTypeCode.Int16: return blob.ReadInt16(); - case PrimitiveSerializationTypeCode.UInt16: return blob.ReadUInt16(); - case PrimitiveSerializationTypeCode.Int32: return blob.ReadInt32(); - case PrimitiveSerializationTypeCode.UInt32: return blob.ReadUInt32(); - case PrimitiveSerializationTypeCode.Int64: return blob.ReadInt64(); - case PrimitiveSerializationTypeCode.UInt64: return blob.ReadUInt64(); - case PrimitiveSerializationTypeCode.Single: return blob.ReadSingle(); - case PrimitiveSerializationTypeCode.Double: return blob.ReadDouble(); - case PrimitiveSerializationTypeCode.String: return blob.ReadSerializedString(); - default: throw new ArgumentOutOfRangeException(nameof(typeInfo.TypeCode)); - } - case TypeKind.Type: - return blob.ReadSerializedString(); - default: - throw new ArgumentOutOfRangeException(nameof(typeInfo.Kind)); - } - } - - PrimitiveSerializationTypeCode ResolveEnumUnderlyingType(string typeName, PEFile module, out (PEFile Module, EntityHandle Handle) typeDefinition) - { - typeDefinition = default; - - TypeDefinitionHandle FindType(PEFile currentModule, string[] name) - { - var metadata = currentModule.Metadata; - var currentNamespace = metadata.GetNamespaceDefinitionRoot(); - ImmutableArray typeDefinitions = default; - - for (int i = 0; i < name.Length; i++) { - string identifier = name[i]; - if (!typeDefinitions.IsDefault) { - restart: - foreach (var type in typeDefinitions) { - var typeDef = metadata.GetTypeDefinition(type); - var currentTypeName = metadata.GetString(typeDef.Name); - if (identifier == currentTypeName) { - if (i + 1 == name.Length) - return type; - typeDefinitions = typeDef.GetNestedTypes(); - goto restart; - } - } - } else { - var next = currentNamespace.NamespaceDefinitions.FirstOrDefault(ns => metadata.StringComparer.Equals(metadata.GetNamespaceDefinition(ns).Name, identifier)); - if (!next.IsNil) { - currentNamespace = metadata.GetNamespaceDefinition(next); - } else { - typeDefinitions = currentNamespace.TypeDefinitions; - i--; - } - } - } - return default; - } - string[] nameParts = typeName.Split(new[] { ", " }, 2, StringSplitOptions.None); - string[] typeNameParts = nameParts[0].Split('.'); - PEFile containingModule = null; - TypeDefinitionHandle typeDefHandle = default; - // if we deal with an assembly-qualified name, resolve the assembly - if (nameParts.Length == 2) - containingModule = AssemblyResolver.Resolve(AssemblyNameReference.Parse(nameParts[1])); - if (containingModule != null) { - // try to find the type in the assembly - typeDefHandle = FindType(containingModule, typeNameParts); - } else { - // just fully-qualified name, try current assembly - typeDefHandle = FindType(module, typeNameParts); - containingModule = module; - if (typeDefHandle.IsNil && TryResolveMscorlib(out var mscorlib)) { - // otherwise try mscorlib - typeDefHandle = FindType(mscorlib, typeNameParts); - containingModule = mscorlib; - } - } - if (typeDefHandle.IsNil || !typeDefHandle.IsEnum(containingModule.Metadata, out var typeCode)) - throw new EnumUnderlyingTypeResolveException(); - typeDefinition = (containingModule, typeDefHandle); - return (PrimitiveSerializationTypeCode)typeCode; - } - - PEFile mscorlib; - - bool TryResolveMscorlib(out PEFile mscorlib) - { - mscorlib = null; - if (this.mscorlib != null) { - mscorlib = this.mscorlib; - return true; - } - if (AssemblyResolver == null) { - return false; - } - this.mscorlib = mscorlib = AssemblyResolver.Resolve(AssemblyNameReference.Parse("mscorlib")); - return this.mscorlib != null; - } - - object ReadArgumentValue(ref BlobReader blob, PEFile module, ref TypeInfo typeInfo) + void WriteValue(ITextOutput output, (PrimitiveTypeCode Code, string Name) type, object value) { - if (typeInfo.IsArray) { - uint elementCount = blob.ReadUInt32(); - if (elementCount == 0xFFFF_FFFF) { - return null; - } else { - var array = new object[elementCount]; - for (int i = 0; i < elementCount; i++) { - array[i] = ReadSimpleArgumentValue(ref blob, module, ref typeInfo); - } - return array; - } - } else if (typeInfo.IsBoxed) { - typeInfo = ReadArgumentType(ref blob); - typeInfo.IsBoxed = true; - if (typeInfo.IsArray) { - return ReadArgumentValue(ref blob, module, ref typeInfo); - } else { - return ReadSimpleArgumentValue(ref blob, module, ref typeInfo); - } - } else { - return ReadSimpleArgumentValue(ref blob, module, ref typeInfo); - } - } - - static void WritePrimitiveTypeCode(ITextOutput output, TypeCode typeCode) - { - switch (typeCode) { - case TypeCode.Boolean: - output.Write("bool"); - break; - case TypeCode.Char: - output.Write("char"); - break; - case TypeCode.SByte: - output.Write("int8"); - break; - case TypeCode.Byte: - output.Write("uint8"); - break; - case TypeCode.Int16: - output.Write("int16"); - break; - case TypeCode.UInt16: - output.Write("uint16"); - break; - case TypeCode.Int32: - output.Write("int32"); - break; - case TypeCode.UInt32: - output.Write("uint32"); - break; - case TypeCode.Int64: - output.Write("int64"); - break; - case TypeCode.UInt64: - output.Write("uint64"); - break; - case TypeCode.Single: - output.Write("float32"); - break; - case TypeCode.Double: - output.Write("float64"); - break; - } - } - - static void WritePrimitiveTypeCode(ITextOutput output, PrimitiveSerializationTypeCode typeCode) - { - switch (typeCode) { - case PrimitiveSerializationTypeCode.Boolean: - output.Write("bool"); - break; - case PrimitiveSerializationTypeCode.Byte: - output.Write("uint8"); - break; - case PrimitiveSerializationTypeCode.SByte: - output.Write("int8"); - break; - case PrimitiveSerializationTypeCode.Char: - output.Write("char"); - break; - case PrimitiveSerializationTypeCode.Int16: - output.Write("int16"); - break; - case PrimitiveSerializationTypeCode.UInt16: - output.Write("uint16"); - break; - case PrimitiveSerializationTypeCode.Int32: - output.Write("int32"); - break; - case PrimitiveSerializationTypeCode.UInt32: - output.Write("uint32"); - break; - case PrimitiveSerializationTypeCode.Int64: - output.Write("int64"); - break; - case PrimitiveSerializationTypeCode.UInt64: - output.Write("uint64"); - break; - case PrimitiveSerializationTypeCode.Single: - output.Write("float32"); - break; - case PrimitiveSerializationTypeCode.Double: - output.Write("float64"); - break; - case PrimitiveSerializationTypeCode.String: - output.Write("string"); - break; - } - } - - void WriteSecurityDeclarationArgument(ITextOutput output, PEFile module, ref BlobReader blob) - { - switch (blob.ReadByte()) { - case 0x53: - output.Write("field "); - break; - case 0x54: - output.Write("property "); - break; - } - var typeInfo = ReadArgumentType(ref blob); - var typeDefinition = default((PEFile Module, EntityHandle Handle)); - if (typeInfo.Kind == TypeKind.Enum) { - typeInfo.TypeCode = ResolveEnumUnderlyingType(typeInfo.TypeName, module, out typeDefinition); - } - var name = blob.ReadSerializedString(); - object value = ReadArgumentValue(ref blob, module, ref typeInfo); - - WriteTypeInfo(output, module, typeInfo, typeDefinition.Module, typeDefinition.Handle); - - output.Write(' '); - output.Write(DisassemblerHelpers.Escape(name)); - output.Write(" = "); - - if (typeInfo.IsBoxed) { + if (value is CustomAttributeTypedArgument<(PrimitiveTypeCode, string)> boxedValue) { output.Write("object("); - } - - WriteTypeInfo(output, module, typeInfo, typeDefinition.Module, typeDefinition.Handle, isValue: true); - - if (typeInfo.IsArray && ((object[])value).Length >= 0) { - output.Write(((object[])value).Length.ToString()); - output.Write(']'); - output.Write('('); + WriteValue(output, boxedValue.Type, boxedValue.Value); + output.Write(")"); + } else if (value is ImmutableArray> arrayValue) { + string elementType = type.Name != null && !type.Name.StartsWith("enum ", StringComparison.Ordinal) + ? type.Name.Remove(type.Name.Length - 2) : PrimitiveTypeCodeToString(type.Code); + + output.Write(elementType); + output.Write("["); + output.Write(arrayValue.Length.ToString()); + output.Write("]("); bool first = true; - foreach (var item in (object[])value) { - if (!first) output.Write(' '); - WriteValue(output, item, typeInfo); + foreach (var item in arrayValue) { + if (!first) output.Write(" "); + if (item.Value is CustomAttributeTypedArgument<(PrimitiveTypeCode, string)> boxedItem) { + WriteValue(output, boxedItem.Type, boxedItem.Value); + } else { + WriteSimpleValue(output, item.Value, elementType); + } first = false; } - output.Write(')'); + output.Write(")"); } else { - output.Write('('); - WriteValue(output, value, typeInfo); - output.Write(')'); - } + string typeName = type.Name != null && !type.Name.StartsWith("enum ", StringComparison.Ordinal) + ? type.Name : PrimitiveTypeCodeToString(type.Code); - if (typeInfo.IsBoxed) { - output.Write(')'); + output.Write(typeName); + output.Write("("); + WriteSimpleValue(output, value, typeName); + output.Write(")"); } } - static void WriteValue(ITextOutput output, object value, TypeInfo typeInfo) + private static void WriteSimpleValue(ITextOutput output, object value, string typeName) { - switch (typeInfo.Kind) { - case TypeKind.Primitive: - if (value is string) { - output.Write("'" + DisassemblerHelpers.EscapeString((string)value).Replace("'", "\'") + "'"); + switch (typeName) { + case "string": + output.Write("'" + DisassemblerHelpers.EscapeString(value.ToString()).Replace("'", "\'") + "'"); + break; + case "type": + var info = ((PrimitiveTypeCode Code, string Name))value; + if (info.Name.StartsWith("enum ", StringComparison.Ordinal)) { + output.Write(info.Name.Substring(5)); } else { - DisassemblerHelpers.WriteOperand(output, value); + output.Write(info.Name); } break; - case TypeKind.Type: + default: output.Write(value.ToString()); break; - case TypeKind.Enum: - DisassemblerHelpers.WriteOperand(output, value); - break; - default: - throw new ArgumentOutOfRangeException(nameof(typeInfo.Kind)); } } - static void WriteTypeInfo(ITextOutput output, PEFile currentModule, TypeInfo typeInfo, PEFile referencedModule, EntityHandle type, bool isValue = false) + static string PrimitiveTypeCodeToString(PrimitiveTypeCode typeCode) { - if (typeInfo.IsBoxed && !isValue) { - output.Write("object"); - return; - } - switch (typeInfo.Kind) { - case TypeKind.Primitive: - WritePrimitiveTypeCode(output, typeInfo.TypeCode); - break; - case TypeKind.Type: - output.Write("type"); - break; - case TypeKind.Enum: - if (isValue) { - WritePrimitiveTypeCode(output, typeInfo.TypeCode); - } else { - output.Write("enum "); - if (type.IsNil) { - output.Write(DisassemblerHelpers.Escape(typeInfo.TypeName)); - break; - } - if (referencedModule != currentModule) { - output.Write('['); - output.Write(referencedModule.Name); - output.Write(']'); - output.WriteReference(referencedModule, type, type.GetFullTypeName(referencedModule.Metadata).ToString()); - } else { - output.Write(DisassemblerHelpers.Escape(typeInfo.TypeName)); - } - } - break; + switch (typeCode) { + case PrimitiveTypeCode.Boolean: + return "bool"; + case PrimitiveTypeCode.Byte: + return "uint8"; + case PrimitiveTypeCode.SByte: + return "int8"; + case PrimitiveTypeCode.Char: + return "char"; + case PrimitiveTypeCode.Int16: + return "int16"; + case PrimitiveTypeCode.UInt16: + return "uint16"; + case PrimitiveTypeCode.Int32: + return "int32"; + case PrimitiveTypeCode.UInt32: + return "uint32"; + case PrimitiveTypeCode.Int64: + return "int64"; + case PrimitiveTypeCode.UInt64: + return "uint64"; + case PrimitiveTypeCode.Single: + return "float32"; + case PrimitiveTypeCode.Double: + return "float64"; + case PrimitiveTypeCode.String: + return "string"; + case PrimitiveTypeCode.Object: + return "object"; default: - break; - } - - if (typeInfo.IsArray) { - if (isValue) { - output.Write('['); - } else { - output.Write("[]"); - } + return "unknown"; } } + #endregion #region WriteMarshalInfo @@ -1168,7 +1039,6 @@ namespace ICSharpCode.Decompiler.Disassembler public void DisassembleField(PEFile module, FieldDefinitionHandle field) { - mscorlib = null; var metadata = module.Metadata; var fieldDefinition = metadata.GetFieldDefinition(field); output.WriteReference(module, field, ".field ", isDefinition: true); @@ -1219,7 +1089,6 @@ namespace ICSharpCode.Decompiler.Disassembler public void DisassembleProperty(PEFile module, PropertyDefinitionHandle property) { - mscorlib = null; var metadata = module.Metadata; var propertyDefinition = metadata.GetPropertyDefinition(property); output.WriteReference(module, property, ".property", true); @@ -1277,7 +1146,6 @@ namespace ICSharpCode.Decompiler.Disassembler public void DisassembleEvent(PEFile module, EventDefinitionHandle handle) { - mscorlib = null; var eventDefinition = module.Metadata.GetEventDefinition(handle); var accessors = eventDefinition.GetAccessors(); TypeDefinitionHandle declaringType; @@ -1359,7 +1227,6 @@ namespace ICSharpCode.Decompiler.Disassembler public void DisassembleType(PEFile module, TypeDefinitionHandle type) { - mscorlib = null; var typeDefinition = module.Metadata.GetTypeDefinition(type); output.WriteReference(module, type, ".class", true); output.Write(" "); @@ -1623,7 +1490,6 @@ namespace ICSharpCode.Decompiler.Disassembler public void DisassembleNamespace(string nameSpace, PEFile module, IEnumerable types) { - mscorlib = null; if (!string.IsNullOrEmpty(nameSpace)) { output.Write(".namespace " + DisassemblerHelpers.Escape(nameSpace)); OpenBlock(false); diff --git a/ICSharpCode.Decompiler/Metadata/CustomAttributeDecoder.cs b/ICSharpCode.Decompiler/Metadata/CustomAttributeDecoder.cs index 2e7e7fe58..254132f98 100644 --- a/ICSharpCode.Decompiler/Metadata/CustomAttributeDecoder.cs +++ b/ICSharpCode.Decompiler/Metadata/CustomAttributeDecoder.cs @@ -18,11 +18,13 @@ namespace ICSharpCode.Decompiler.Metadata private readonly ICustomAttributeTypeProvider _provider; private readonly MetadataReader _reader; + private readonly bool _provideBoxingTypeInfo; - public CustomAttributeDecoder(ICustomAttributeTypeProvider provider, MetadataReader reader) + public CustomAttributeDecoder(ICustomAttributeTypeProvider provider, MetadataReader reader, bool provideBoxingTypeInfo = false) { _reader = reader; _provider = provider; + _provideBoxingTypeInfo = provideBoxingTypeInfo; } public ImmutableArray> DecodeNamedArguments(ref BlobReader valueReader, int count) @@ -109,6 +111,7 @@ namespace ICSharpCode.Decompiler.Metadata private CustomAttributeTypedArgument DecodeArgument(ref BlobReader valueReader, ArgumentTypeInfo info) { + var outer = info; if (info.TypeCode == SerializationTypeCode.TaggedObject) { info = DecodeNamedArgumentType(ref valueReader); } @@ -182,6 +185,10 @@ namespace ICSharpCode.Decompiler.Metadata throw new BadImageFormatException(); } + if (_provideBoxingTypeInfo && outer.TypeCode == SerializationTypeCode.TaggedObject) { + return new CustomAttributeTypedArgument(outer.Type, new CustomAttributeTypedArgument(info.Type, value)); + } + return new CustomAttributeTypedArgument(info.Type, value); } diff --git a/ICSharpCode.Decompiler/Metadata/UniversalAssemblyResolver.cs b/ICSharpCode.Decompiler/Metadata/UniversalAssemblyResolver.cs index 40b6f7b5b..058f1c894 100644 --- a/ICSharpCode.Decompiler/Metadata/UniversalAssemblyResolver.cs +++ b/ICSharpCode.Decompiler/Metadata/UniversalAssemblyResolver.cs @@ -73,7 +73,7 @@ namespace ICSharpCode.Decompiler.Metadata PEStreamOptions options = PEStreamOptions.Default) { this.options = options; - this.TargetFramework = targetFramework; + this.TargetFramework = targetFramework ?? string.Empty; this.mainAssemblyFileName = mainAssemblyFileName; this.baseDirectory = Path.GetDirectoryName(mainAssemblyFileName); this.throwOnError = throwOnError; @@ -245,7 +245,7 @@ namespace ICSharpCode.Decompiler.Metadata static bool IsZero(Version version) { - return version.Major == 0 && version.Minor == 0 && version.Build == 0 && version.Revision == 0; + return version == null || (version.Major == 0 && version.Minor == 0 && version.Build == 0 && version.Revision == 0); } internal static Version ZeroVersion = new Version(0,0,0,0);