From 496bae96f27d916e77cb2068f510a04e8dfbc51a Mon Sep 17 00:00:00 2001 From: tom-englert Date: Sun, 13 Nov 2022 16:12:08 +0100 Subject: [PATCH] Fix #388: Normalize IL to ensure consistent outcome. --- readme.md | 128 +- .../Tests.BackwardCompatibility.verified.txt | 203 ++ src/Tests/Tests.MethodNameUsage.verified.txt | 1 - .../Tests.TypeDefinitionUsage.verified.txt | 106 +- src/Tests/Tests.TypeNameUsage.verified.txt | 106 +- src/Tests/Tests.cs | 11 +- .../Import/Adapters.cs | 220 ++ .../Import/CustomAttributeDecoder.cs | 238 ++ .../Import/Filter.cs | 40 + .../Import/ReadMe.txt | 1 + .../Import/ReflectionDisassemblerImport.cs | 2083 +++++++++++++++++ .../Import/TextOutputWithRollback.cs | 106 + .../VerifyICSharpCodeDecompiler.cs | 62 +- 13 files changed, 3114 insertions(+), 191 deletions(-) create mode 100644 src/Tests/Tests.BackwardCompatibility.verified.txt create mode 100644 src/Verify.ICSharpCode.Decompiler/Import/Adapters.cs create mode 100644 src/Verify.ICSharpCode.Decompiler/Import/CustomAttributeDecoder.cs create mode 100644 src/Verify.ICSharpCode.Decompiler/Import/Filter.cs create mode 100644 src/Verify.ICSharpCode.Decompiler/Import/ReadMe.txt create mode 100644 src/Verify.ICSharpCode.Decompiler/Import/ReflectionDisassemblerImport.cs create mode 100644 src/Verify.ICSharpCode.Decompiler/Import/TextOutputWithRollback.cs diff --git a/readme.md b/readme.md index 5d0c122..42e9aef 100644 --- a/readme.md +++ b/readme.md @@ -97,6 +97,7 @@ Result: 01 00 00 00 00 ) // Fields + .field private string 'property' .field private class [System.ObjectModel]System.ComponentModel.PropertyChangedEventHandler PropertyChanged .custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 @@ -104,39 +105,20 @@ Result: .custom instance void [System.Runtime]System.Diagnostics.DebuggerBrowsableAttribute::.ctor(valuetype [System.Runtime]System.Diagnostics.DebuggerBrowsableState) = ( 01 00 00 00 00 00 00 00 ) - .field private string 'property' // Methods - .method private hidebysig - instance void OnPropertyChanged ( - [opt] string propertyName - ) cil managed + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed { - .param [1] = nullref - .custom instance void [System.Runtime]System.Runtime.CompilerServices.CallerMemberNameAttribute::.ctor() = ( - 01 00 00 00 - ) - // Method begins at RVA 0x20b8 // Header size: 1 - // Code size: 26 (0x1a) + // Code size: 8 (0x8) .maxstack 8 IL_0000: ldarg.0 - IL_0001: ldfld class [System.ObjectModel]System.ComponentModel.PropertyChangedEventHandler Target::PropertyChanged - IL_0006: dup - IL_0007: brtrue.s IL_000c - - IL_0009: pop - IL_000a: br.s IL_0019 - - IL_000c: ldarg.0 - IL_000d: ldarg.1 - IL_000e: newobj instance void [System.ObjectModel]System.ComponentModel.PropertyChangedEventArgs::.ctor(string) - IL_0013: callvirt instance void [System.ObjectModel]System.ComponentModel.PropertyChangedEventHandler::Invoke(object, class [System.ObjectModel]System.ComponentModel.PropertyChangedEventArgs) - IL_0018: nop - - IL_0019: ret - } // end of method Target::OnPropertyChanged + IL_0001: call instance void [System.Runtime]System.Object::.ctor() + IL_0006: nop + IL_0007: ret + } // end of method Target::.ctor .method public final hidebysig specialname newslot virtual instance void add_PropertyChanged ( @@ -146,7 +128,6 @@ Result: .custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) - // Method begins at RVA 0x20d4 // Header size: 12 // Code size: 41 (0x29) .maxstack 3 @@ -180,6 +161,48 @@ Result: IL_0028: ret } // end of method Target::add_PropertyChanged + .method public hidebysig specialname + instance string get_Property () cil managed + { + // Header size: 1 + // Code size: 7 (0x7) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: ldfld string Target::'property' + IL_0006: ret + } // end of method Target::get_Property + + .method private hidebysig + instance void OnPropertyChanged ( + [opt] string propertyName + ) cil managed + { + .param [1] = nullref + .custom instance void [System.Runtime]System.Runtime.CompilerServices.CallerMemberNameAttribute::.ctor() = ( + 01 00 00 00 + ) + // Header size: 1 + // Code size: 26 (0x1a) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: ldfld class [System.ObjectModel]System.ComponentModel.PropertyChangedEventHandler Target::PropertyChanged + IL_0006: dup + IL_0007: brtrue.s IL_000c + + IL_0009: pop + IL_000a: br.s IL_0019 + + IL_000c: ldarg.0 + IL_000d: ldarg.1 + IL_000e: newobj instance void [System.ObjectModel]System.ComponentModel.PropertyChangedEventArgs::.ctor(string) + IL_0013: callvirt instance void [System.ObjectModel]System.ComponentModel.PropertyChangedEventHandler::Invoke(object, class [System.ObjectModel]System.ComponentModel.PropertyChangedEventArgs) + IL_0018: nop + + IL_0019: ret + } // end of method Target::OnPropertyChanged + .method public final hidebysig specialname newslot virtual instance void remove_PropertyChanged ( class [System.ObjectModel]System.ComponentModel.PropertyChangedEventHandler 'value' @@ -188,7 +211,6 @@ Result: .custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) - // Method begins at RVA 0x210c // Header size: 12 // Code size: 41 (0x29) .maxstack 3 @@ -222,25 +244,11 @@ Result: IL_0028: ret } // end of method Target::remove_PropertyChanged - .method public hidebysig specialname - instance string get_Property () cil managed - { - // Method begins at RVA 0x2141 - // Header size: 1 - // Code size: 7 (0x7) - .maxstack 8 - - IL_0000: ldarg.0 - IL_0001: ldfld string Target::'property' - IL_0006: ret - } // end of method Target::get_Property - .method public hidebysig specialname instance void set_Property ( string 'value' ) cil managed { - // Method begins at RVA 0x2149 // Header size: 1 // Code size: 21 (0x15) .maxstack 8 @@ -256,20 +264,6 @@ Result: IL_0014: ret } // end of method Target::set_Property - .method public hidebysig specialname rtspecialname - instance void .ctor () cil managed - { - // Method begins at RVA 0x215f - // Header size: 1 - // Code size: 8 (0x8) - .maxstack 8 - - IL_0000: ldarg.0 - IL_0001: call instance void [System.Runtime]System.Object::.ctor() - IL_0006: nop - IL_0007: ret - } // end of method Target::.ctor - // Events .event [System.ObjectModel]System.ComponentModel.PropertyChangedEventHandler PropertyChanged { @@ -287,7 +281,7 @@ Result: } // end of class Target ``` -snippet source | anchor +snippet source | anchor A string for the type name can also be used: @@ -339,7 +333,6 @@ Result: .custom instance void [System.Runtime]System.Runtime.CompilerServices.CallerMemberNameAttribute::.ctor() = ( 01 00 00 00 ) - // Method begins at RVA 0x20b8 // Header size: 1 // Code size: 26 (0x1a) .maxstack 8 @@ -361,10 +354,27 @@ Result: IL_0019: ret } // end of method Target::OnPropertyChanged ``` -snippet source | anchor +snippet source | anchor +### Settings +Starting with version 4.0 the generated IL is normalized by default, i.e. types and members are sorted by name, and RVA adress comments are stripped, to avoid failed tests only because the binary layout has changed. + +To get backward compatible behavior, use the `DontNormalizeIL` setting: + + + +```cs +[Test] +public Task BackwardCompatibility() +{ + using var file = new PEFile(assemblyPath); + return Verify(new TypeToDisassemble(file, "Target")).DontNormalizeIL(); +} +``` +snippet source | anchor + ## Icon diff --git a/src/Tests/Tests.BackwardCompatibility.verified.txt b/src/Tests/Tests.BackwardCompatibility.verified.txt new file mode 100644 index 0000000..afc28b9 --- /dev/null +++ b/src/Tests/Tests.BackwardCompatibility.verified.txt @@ -0,0 +1,203 @@ +.class public auto ansi beforefieldinit Target + extends [System.Runtime]System.Object + implements .custom instance void System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8) = ( + 01 00 00 00 00 + ) + [System.ObjectModel]System.ComponentModel.INotifyPropertyChanged +{ + .custom instance void System.Runtime.CompilerServices.NullableContextAttribute::.ctor(uint8) = ( + 01 00 02 00 00 + ) + .custom instance void System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8) = ( + 01 00 00 00 00 + ) + // Fields + .field private class [System.ObjectModel]System.ComponentModel.PropertyChangedEventHandler PropertyChanged + .custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( + 01 00 00 00 + ) + .custom instance void [System.Runtime]System.Diagnostics.DebuggerBrowsableAttribute::.ctor(valuetype [System.Runtime]System.Diagnostics.DebuggerBrowsableState) = ( + 01 00 00 00 00 00 00 00 + ) + .field private string 'property' + + // Methods + .method private hidebysig + instance void OnPropertyChanged ( + [opt] string propertyName + ) cil managed + { + .param [1] = nullref + .custom instance void [System.Runtime]System.Runtime.CompilerServices.CallerMemberNameAttribute::.ctor() = ( + 01 00 00 00 + ) + // Method begins at RVA 0x20a8 + // Header size: 1 + // Code size: 26 (0x1a) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: ldfld class [System.ObjectModel]System.ComponentModel.PropertyChangedEventHandler Target::PropertyChanged + IL_0006: dup + IL_0007: brtrue.s IL_000c + + IL_0009: pop + IL_000a: br.s IL_0019 + + IL_000c: ldarg.0 + IL_000d: ldarg.1 + IL_000e: newobj instance void [System.ObjectModel]System.ComponentModel.PropertyChangedEventArgs::.ctor(string) + IL_0013: callvirt instance void [System.ObjectModel]System.ComponentModel.PropertyChangedEventHandler::Invoke(object, class [System.ObjectModel]System.ComponentModel.PropertyChangedEventArgs) + IL_0018: nop + + IL_0019: ret + } // end of method Target::OnPropertyChanged + + .method public final hidebysig specialname newslot virtual + instance void add_PropertyChanged ( + class [System.ObjectModel]System.ComponentModel.PropertyChangedEventHandler 'value' + ) cil managed + { + .custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( + 01 00 00 00 + ) + // Method begins at RVA 0x20c4 + // Header size: 12 + // Code size: 41 (0x29) + .maxstack 3 + .locals init ( + [0] class [System.ObjectModel]System.ComponentModel.PropertyChangedEventHandler, + [1] class [System.ObjectModel]System.ComponentModel.PropertyChangedEventHandler, + [2] class [System.ObjectModel]System.ComponentModel.PropertyChangedEventHandler + ) + + IL_0000: ldarg.0 + IL_0001: ldfld class [System.ObjectModel]System.ComponentModel.PropertyChangedEventHandler Target::PropertyChanged + IL_0006: stloc.0 + // loop start (head: IL_0007) + IL_0007: ldloc.0 + IL_0008: stloc.1 + IL_0009: ldloc.1 + IL_000a: ldarg.1 + IL_000b: call class [System.Runtime]System.Delegate [System.Runtime]System.Delegate::Combine(class [System.Runtime]System.Delegate, class [System.Runtime]System.Delegate) + IL_0010: castclass [System.ObjectModel]System.ComponentModel.PropertyChangedEventHandler + IL_0015: stloc.2 + IL_0016: ldarg.0 + IL_0017: ldflda class [System.ObjectModel]System.ComponentModel.PropertyChangedEventHandler Target::PropertyChanged + IL_001c: ldloc.2 + IL_001d: ldloc.1 + IL_001e: call !!0 [System.Threading]System.Threading.Interlocked::CompareExchange(!!0&, !!0, !!0) + IL_0023: stloc.0 + IL_0024: ldloc.0 + IL_0025: ldloc.1 + IL_0026: bne.un.s IL_0007 + // end loop + IL_0028: ret + } // end of method Target::add_PropertyChanged + + .method public final hidebysig specialname newslot virtual + instance void remove_PropertyChanged ( + class [System.ObjectModel]System.ComponentModel.PropertyChangedEventHandler 'value' + ) cil managed + { + .custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( + 01 00 00 00 + ) + // Method begins at RVA 0x20fc + // Header size: 12 + // Code size: 41 (0x29) + .maxstack 3 + .locals init ( + [0] class [System.ObjectModel]System.ComponentModel.PropertyChangedEventHandler, + [1] class [System.ObjectModel]System.ComponentModel.PropertyChangedEventHandler, + [2] class [System.ObjectModel]System.ComponentModel.PropertyChangedEventHandler + ) + + IL_0000: ldarg.0 + IL_0001: ldfld class [System.ObjectModel]System.ComponentModel.PropertyChangedEventHandler Target::PropertyChanged + IL_0006: stloc.0 + // loop start (head: IL_0007) + IL_0007: ldloc.0 + IL_0008: stloc.1 + IL_0009: ldloc.1 + IL_000a: ldarg.1 + IL_000b: call class [System.Runtime]System.Delegate [System.Runtime]System.Delegate::Remove(class [System.Runtime]System.Delegate, class [System.Runtime]System.Delegate) + IL_0010: castclass [System.ObjectModel]System.ComponentModel.PropertyChangedEventHandler + IL_0015: stloc.2 + IL_0016: ldarg.0 + IL_0017: ldflda class [System.ObjectModel]System.ComponentModel.PropertyChangedEventHandler Target::PropertyChanged + IL_001c: ldloc.2 + IL_001d: ldloc.1 + IL_001e: call !!0 [System.Threading]System.Threading.Interlocked::CompareExchange(!!0&, !!0, !!0) + IL_0023: stloc.0 + IL_0024: ldloc.0 + IL_0025: ldloc.1 + IL_0026: bne.un.s IL_0007 + // end loop + IL_0028: ret + } // end of method Target::remove_PropertyChanged + + .method public hidebysig specialname + instance string get_Property () cil managed + { + // Method begins at RVA 0x2131 + // Header size: 1 + // Code size: 7 (0x7) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: ldfld string Target::'property' + IL_0006: ret + } // end of method Target::get_Property + + .method public hidebysig specialname + instance void set_Property ( + string 'value' + ) cil managed + { + // Method begins at RVA 0x2139 + // Header size: 1 + // Code size: 21 (0x15) + .maxstack 8 + + IL_0000: nop + IL_0001: ldarg.0 + IL_0002: ldarg.1 + IL_0003: stfld string Target::'property' + IL_0008: ldarg.0 + IL_0009: ldstr "Property" + IL_000e: call instance void Target::OnPropertyChanged(string) + IL_0013: nop + IL_0014: ret + } // end of method Target::set_Property + + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + // Method begins at RVA 0x214f + // Header size: 1 + // Code size: 8 (0x8) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: call instance void [System.Runtime]System.Object::.ctor() + IL_0006: nop + IL_0007: ret + } // end of method Target::.ctor + + // Events + .event [System.ObjectModel]System.ComponentModel.PropertyChangedEventHandler PropertyChanged + { + .addon instance void Target::add_PropertyChanged(class [System.ObjectModel]System.ComponentModel.PropertyChangedEventHandler) + .removeon instance void Target::remove_PropertyChanged(class [System.ObjectModel]System.ComponentModel.PropertyChangedEventHandler) + } + + + // Properties + .property instance string Property() + { + .get instance string Target::get_Property() + .set instance void Target::set_Property(string) + } + +} // end of class Target diff --git a/src/Tests/Tests.MethodNameUsage.verified.txt b/src/Tests/Tests.MethodNameUsage.verified.txt index 1169818..75f850e 100644 --- a/src/Tests/Tests.MethodNameUsage.verified.txt +++ b/src/Tests/Tests.MethodNameUsage.verified.txt @@ -7,7 +7,6 @@ .custom instance void [System.Runtime]System.Runtime.CompilerServices.CallerMemberNameAttribute::.ctor() = ( 01 00 00 00 ) - // Method begins at RVA 0x20b8 // Header size: 1 // Code size: 26 (0x1a) .maxstack 8 diff --git a/src/Tests/Tests.TypeDefinitionUsage.verified.txt b/src/Tests/Tests.TypeDefinitionUsage.verified.txt index b43de62..be071ea 100644 --- a/src/Tests/Tests.TypeDefinitionUsage.verified.txt +++ b/src/Tests/Tests.TypeDefinitionUsage.verified.txt @@ -12,6 +12,7 @@ 01 00 00 00 00 ) // Fields + .field private string 'property' .field private class [System.ObjectModel]System.ComponentModel.PropertyChangedEventHandler PropertyChanged .custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 @@ -19,39 +20,20 @@ .custom instance void [System.Runtime]System.Diagnostics.DebuggerBrowsableAttribute::.ctor(valuetype [System.Runtime]System.Diagnostics.DebuggerBrowsableState) = ( 01 00 00 00 00 00 00 00 ) - .field private string 'property' // Methods - .method private hidebysig - instance void OnPropertyChanged ( - [opt] string propertyName - ) cil managed + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed { - .param [1] = nullref - .custom instance void [System.Runtime]System.Runtime.CompilerServices.CallerMemberNameAttribute::.ctor() = ( - 01 00 00 00 - ) - // Method begins at RVA 0x20b8 // Header size: 1 - // Code size: 26 (0x1a) + // Code size: 8 (0x8) .maxstack 8 IL_0000: ldarg.0 - IL_0001: ldfld class [System.ObjectModel]System.ComponentModel.PropertyChangedEventHandler Target::PropertyChanged - IL_0006: dup - IL_0007: brtrue.s IL_000c - - IL_0009: pop - IL_000a: br.s IL_0019 - - IL_000c: ldarg.0 - IL_000d: ldarg.1 - IL_000e: newobj instance void [System.ObjectModel]System.ComponentModel.PropertyChangedEventArgs::.ctor(string) - IL_0013: callvirt instance void [System.ObjectModel]System.ComponentModel.PropertyChangedEventHandler::Invoke(object, class [System.ObjectModel]System.ComponentModel.PropertyChangedEventArgs) - IL_0018: nop - - IL_0019: ret - } // end of method Target::OnPropertyChanged + IL_0001: call instance void [System.Runtime]System.Object::.ctor() + IL_0006: nop + IL_0007: ret + } // end of method Target::.ctor .method public final hidebysig specialname newslot virtual instance void add_PropertyChanged ( @@ -61,7 +43,6 @@ .custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) - // Method begins at RVA 0x20d4 // Header size: 12 // Code size: 41 (0x29) .maxstack 3 @@ -95,6 +76,48 @@ IL_0028: ret } // end of method Target::add_PropertyChanged + .method public hidebysig specialname + instance string get_Property () cil managed + { + // Header size: 1 + // Code size: 7 (0x7) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: ldfld string Target::'property' + IL_0006: ret + } // end of method Target::get_Property + + .method private hidebysig + instance void OnPropertyChanged ( + [opt] string propertyName + ) cil managed + { + .param [1] = nullref + .custom instance void [System.Runtime]System.Runtime.CompilerServices.CallerMemberNameAttribute::.ctor() = ( + 01 00 00 00 + ) + // Header size: 1 + // Code size: 26 (0x1a) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: ldfld class [System.ObjectModel]System.ComponentModel.PropertyChangedEventHandler Target::PropertyChanged + IL_0006: dup + IL_0007: brtrue.s IL_000c + + IL_0009: pop + IL_000a: br.s IL_0019 + + IL_000c: ldarg.0 + IL_000d: ldarg.1 + IL_000e: newobj instance void [System.ObjectModel]System.ComponentModel.PropertyChangedEventArgs::.ctor(string) + IL_0013: callvirt instance void [System.ObjectModel]System.ComponentModel.PropertyChangedEventHandler::Invoke(object, class [System.ObjectModel]System.ComponentModel.PropertyChangedEventArgs) + IL_0018: nop + + IL_0019: ret + } // end of method Target::OnPropertyChanged + .method public final hidebysig specialname newslot virtual instance void remove_PropertyChanged ( class [System.ObjectModel]System.ComponentModel.PropertyChangedEventHandler 'value' @@ -103,7 +126,6 @@ .custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) - // Method begins at RVA 0x210c // Header size: 12 // Code size: 41 (0x29) .maxstack 3 @@ -137,25 +159,11 @@ IL_0028: ret } // end of method Target::remove_PropertyChanged - .method public hidebysig specialname - instance string get_Property () cil managed - { - // Method begins at RVA 0x2141 - // Header size: 1 - // Code size: 7 (0x7) - .maxstack 8 - - IL_0000: ldarg.0 - IL_0001: ldfld string Target::'property' - IL_0006: ret - } // end of method Target::get_Property - .method public hidebysig specialname instance void set_Property ( string 'value' ) cil managed { - // Method begins at RVA 0x2149 // Header size: 1 // Code size: 21 (0x15) .maxstack 8 @@ -171,20 +179,6 @@ IL_0014: ret } // end of method Target::set_Property - .method public hidebysig specialname rtspecialname - instance void .ctor () cil managed - { - // Method begins at RVA 0x215f - // Header size: 1 - // Code size: 8 (0x8) - .maxstack 8 - - IL_0000: ldarg.0 - IL_0001: call instance void [System.Runtime]System.Object::.ctor() - IL_0006: nop - IL_0007: ret - } // end of method Target::.ctor - // Events .event [System.ObjectModel]System.ComponentModel.PropertyChangedEventHandler PropertyChanged { diff --git a/src/Tests/Tests.TypeNameUsage.verified.txt b/src/Tests/Tests.TypeNameUsage.verified.txt index b43de62..be071ea 100644 --- a/src/Tests/Tests.TypeNameUsage.verified.txt +++ b/src/Tests/Tests.TypeNameUsage.verified.txt @@ -12,6 +12,7 @@ 01 00 00 00 00 ) // Fields + .field private string 'property' .field private class [System.ObjectModel]System.ComponentModel.PropertyChangedEventHandler PropertyChanged .custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 @@ -19,39 +20,20 @@ .custom instance void [System.Runtime]System.Diagnostics.DebuggerBrowsableAttribute::.ctor(valuetype [System.Runtime]System.Diagnostics.DebuggerBrowsableState) = ( 01 00 00 00 00 00 00 00 ) - .field private string 'property' // Methods - .method private hidebysig - instance void OnPropertyChanged ( - [opt] string propertyName - ) cil managed + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed { - .param [1] = nullref - .custom instance void [System.Runtime]System.Runtime.CompilerServices.CallerMemberNameAttribute::.ctor() = ( - 01 00 00 00 - ) - // Method begins at RVA 0x20b8 // Header size: 1 - // Code size: 26 (0x1a) + // Code size: 8 (0x8) .maxstack 8 IL_0000: ldarg.0 - IL_0001: ldfld class [System.ObjectModel]System.ComponentModel.PropertyChangedEventHandler Target::PropertyChanged - IL_0006: dup - IL_0007: brtrue.s IL_000c - - IL_0009: pop - IL_000a: br.s IL_0019 - - IL_000c: ldarg.0 - IL_000d: ldarg.1 - IL_000e: newobj instance void [System.ObjectModel]System.ComponentModel.PropertyChangedEventArgs::.ctor(string) - IL_0013: callvirt instance void [System.ObjectModel]System.ComponentModel.PropertyChangedEventHandler::Invoke(object, class [System.ObjectModel]System.ComponentModel.PropertyChangedEventArgs) - IL_0018: nop - - IL_0019: ret - } // end of method Target::OnPropertyChanged + IL_0001: call instance void [System.Runtime]System.Object::.ctor() + IL_0006: nop + IL_0007: ret + } // end of method Target::.ctor .method public final hidebysig specialname newslot virtual instance void add_PropertyChanged ( @@ -61,7 +43,6 @@ .custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) - // Method begins at RVA 0x20d4 // Header size: 12 // Code size: 41 (0x29) .maxstack 3 @@ -95,6 +76,48 @@ IL_0028: ret } // end of method Target::add_PropertyChanged + .method public hidebysig specialname + instance string get_Property () cil managed + { + // Header size: 1 + // Code size: 7 (0x7) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: ldfld string Target::'property' + IL_0006: ret + } // end of method Target::get_Property + + .method private hidebysig + instance void OnPropertyChanged ( + [opt] string propertyName + ) cil managed + { + .param [1] = nullref + .custom instance void [System.Runtime]System.Runtime.CompilerServices.CallerMemberNameAttribute::.ctor() = ( + 01 00 00 00 + ) + // Header size: 1 + // Code size: 26 (0x1a) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: ldfld class [System.ObjectModel]System.ComponentModel.PropertyChangedEventHandler Target::PropertyChanged + IL_0006: dup + IL_0007: brtrue.s IL_000c + + IL_0009: pop + IL_000a: br.s IL_0019 + + IL_000c: ldarg.0 + IL_000d: ldarg.1 + IL_000e: newobj instance void [System.ObjectModel]System.ComponentModel.PropertyChangedEventArgs::.ctor(string) + IL_0013: callvirt instance void [System.ObjectModel]System.ComponentModel.PropertyChangedEventHandler::Invoke(object, class [System.ObjectModel]System.ComponentModel.PropertyChangedEventArgs) + IL_0018: nop + + IL_0019: ret + } // end of method Target::OnPropertyChanged + .method public final hidebysig specialname newslot virtual instance void remove_PropertyChanged ( class [System.ObjectModel]System.ComponentModel.PropertyChangedEventHandler 'value' @@ -103,7 +126,6 @@ .custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) - // Method begins at RVA 0x210c // Header size: 12 // Code size: 41 (0x29) .maxstack 3 @@ -137,25 +159,11 @@ IL_0028: ret } // end of method Target::remove_PropertyChanged - .method public hidebysig specialname - instance string get_Property () cil managed - { - // Method begins at RVA 0x2141 - // Header size: 1 - // Code size: 7 (0x7) - .maxstack 8 - - IL_0000: ldarg.0 - IL_0001: ldfld string Target::'property' - IL_0006: ret - } // end of method Target::get_Property - .method public hidebysig specialname instance void set_Property ( string 'value' ) cil managed { - // Method begins at RVA 0x2149 // Header size: 1 // Code size: 21 (0x15) .maxstack 8 @@ -171,20 +179,6 @@ IL_0014: ret } // end of method Target::set_Property - .method public hidebysig specialname rtspecialname - instance void .ctor () cil managed - { - // Method begins at RVA 0x215f - // Header size: 1 - // Code size: 8 (0x8) - .maxstack 8 - - IL_0000: ldarg.0 - IL_0001: call instance void [System.Runtime]System.Object::.ctor() - IL_0006: nop - IL_0007: ret - } // end of method Target::.ctor - // Events .event [System.ObjectModel]System.ComponentModel.PropertyChangedEventHandler PropertyChanged { diff --git a/src/Tests/Tests.cs b/src/Tests/Tests.cs index 35d1000..c791c3e 100644 --- a/src/Tests/Tests.cs +++ b/src/Tests/Tests.cs @@ -1,4 +1,4 @@ -using ICSharpCode.Decompiler; +using ICSharpCode.Decompiler; using ICSharpCode.Decompiler.Metadata; using VerifyTests.ICSharpCode.Decompiler; @@ -92,4 +92,13 @@ public async Task TypeNameMisMatch() })!; await Verify(exception); } + + #region BackwardCompatibility + [Test] + public Task BackwardCompatibility() + { + using var file = new PEFile(assemblyPath); + return Verify(new TypeToDisassemble(file, "Target")).DontNormalizeIL(); + } + #endregion } \ No newline at end of file diff --git a/src/Verify.ICSharpCode.Decompiler/Import/Adapters.cs b/src/Verify.ICSharpCode.Decompiler/Import/Adapters.cs new file mode 100644 index 0000000..3bf279f --- /dev/null +++ b/src/Verify.ICSharpCode.Decompiler/Import/Adapters.cs @@ -0,0 +1,220 @@ +// ReSharper disable All + +// Copyright (c) 2022 tom-englert.de 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; + +using ICSharpCode.Decompiler.Metadata; + +using System.Reflection.Metadata; +using System.Collections.Generic; +using System.Linq; + +namespace ICSharpCode.Decompiler.Disassembler +{ + public abstract class Adapter + { + private readonly Lazy displayName; + + protected Adapter(PEFile module, TypeDefinitionAdapter? containingType) + { + Module = module; + ContainingType = containingType; + displayName = new Lazy(GetDisplayName); + } + + public PEFile Module { get; } + + public string DisplayName => displayName.Value; + + public TypeDefinitionAdapter? ContainingType { get; } + + protected string GetString(StringHandle handle) + { + return Module.Metadata.GetString(handle); + } + + protected abstract string GetDisplayName(); + } + + public abstract class Adapter : Adapter where T : struct + { + protected Adapter(PEFile module, T handle, TypeDefinitionAdapter? containingType) + : base(module, containingType) + { + Handle = handle; + } + + public T Handle { get; } + } + + public class TypeDefinitionAdapter : Adapter + { + public TypeDefinitionAdapter(PEFile module, TypeDefinitionHandle handle, TypeDefinitionAdapter? containingType = null) + : base(module, handle, containingType) + { + Definition = module.Metadata.GetTypeDefinition(handle); + GenericContext = new GenericContext(handle, module); + } + + public TypeDefinition Definition { get; } + + public GenericContext GenericContext { get; } + + protected override string GetDisplayName() => Definition.GetFullTypeName(Module.Metadata).ToILNameString(); + } + + public class MethodDefinitionAdapter : Adapter + { + public MethodDefinitionAdapter(PEFile module, MethodDefinitionHandle handle, TypeDefinitionAdapter? containingType = null) + : base(module, handle, containingType) + { + Definition = module.Metadata.GetMethodDefinition(handle); + GenericContext = new GenericContext(handle, module); + } + + public MethodDefinition Definition { get; } + + public GenericContext GenericContext { get; } + + protected override string GetDisplayName() + { + var output = new PlainTextOutput(); + + output.Write(GetString(Definition.Name)); + var genericParameterCount = Definition.GetGenericParameters().Count; + if (genericParameterCount > 0) + { + output.Write($"`{genericParameterCount}"); + } + + output.Write("("); + + var signatureProvider = new DisassemblerSignatureTypeProvider(Module, output); + var signature = Definition.DecodeSignature(signatureProvider, GenericContext); + var first = true; + + foreach (var action in signature.ParameterTypes) + { + if (!first) + { + output.Write(", "); + } + first = false; + action(ILNameSyntax.ShortTypeName); + } + + output.Write(")"); + + return output.ToString(); + } + } + + public class InterfaceImplementationAdapter : Adapter + { + public InterfaceImplementationAdapter(PEFile module, InterfaceImplementationHandle handle, TypeDefinitionAdapter? containingType = null) + : base(module, handle, containingType) + { + Implementation = module.Metadata.GetInterfaceImplementation(handle); + } + + public InterfaceImplementation Implementation { get; } + + protected override string GetDisplayName() => Implementation.Interface.GetFullTypeName(Module.Metadata).ToILNameString(); + } + + public class FieldDefinitionAdapter : Adapter + { + public FieldDefinitionAdapter(PEFile module, FieldDefinitionHandle handle, TypeDefinitionAdapter? containingType = null) + : base(module, handle, containingType) + { + Definition = module.Metadata.GetFieldDefinition(handle); + } + + public FieldDefinition Definition { get; } + + protected override string GetDisplayName() => GetString(Definition.Name); + } + + public class PropertyDefinitionAdapter : Adapter + { + public PropertyDefinitionAdapter(PEFile module, PropertyDefinitionHandle handle, TypeDefinitionAdapter? containingType = null) + : base(module, handle, containingType) + { + Definition = module.Metadata.GetPropertyDefinition(handle); + } + + public PropertyDefinition Definition { get; } + + protected override string GetDisplayName() => GetString(Definition.Name); + } + + public class EventDefinitionAdapter : Adapter + { + public EventDefinitionAdapter(PEFile module, EventDefinitionHandle handle, TypeDefinitionAdapter? containingType = null) + : base(module, handle, containingType) + { + Definition = module.Metadata.GetEventDefinition(handle); + } + + public EventDefinition Definition { get; } + + protected override string GetDisplayName() => GetString(Definition.Name); + } + + public static class AdapterExtensionMethods + { + public static ICollection GetTopLevelTypeDefinitions(this PEFile module) + { + return module.Metadata.GetTopLevelTypeDefinitions().Select(h => new TypeDefinitionAdapter(module, h)).ToArray(); + } + + public static ICollection GetNestedTypes(this TypeDefinitionAdapter type) + { + return type.Definition.GetNestedTypes().Select(item => new TypeDefinitionAdapter(type.Module, item, type)).ToArray(); + } + + public static ICollection GetMethods(this TypeDefinitionAdapter type) + { + return type.Definition.GetMethods().Select(item => new MethodDefinitionAdapter(type.Module, item, type)).ToArray(); + } + + public static ICollection GetInterfaceImplementations(this TypeDefinitionAdapter type) + { + return type.Definition.GetInterfaceImplementations().Select(item => new InterfaceImplementationAdapter(type.Module, item, type)).ToArray(); + } + + public static ICollection GetFields(this TypeDefinitionAdapter type) + { + return type.Definition.GetFields().Select(item => new FieldDefinitionAdapter(type.Module, item, type)).ToArray(); + } + + public static ICollection GetEvents(this TypeDefinitionAdapter type) + { + return type.Definition.GetEvents().Select(item => new EventDefinitionAdapter(type.Module, item, type)).ToArray(); + } + + public static ICollection GetProperties(this TypeDefinitionAdapter type) + { + return type.Definition.GetProperties().Select(item => new PropertyDefinitionAdapter(type.Module, item, type)).ToArray(); + } + } +} diff --git a/src/Verify.ICSharpCode.Decompiler/Import/CustomAttributeDecoder.cs b/src/Verify.ICSharpCode.Decompiler/Import/CustomAttributeDecoder.cs new file mode 100644 index 0000000..eb1c2a3 --- /dev/null +++ b/src/Verify.ICSharpCode.Decompiler/Import/CustomAttributeDecoder.cs @@ -0,0 +1,238 @@ +// ReSharper disable All +#nullable disable + +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Reflection.Metadata; + +namespace ICSharpCode.Decompiler.Metadata +{ + /// + /// Decodes custom attribute blobs. + /// + internal readonly struct CustomAttributeDecoder + { + // This is a stripped-down copy of SRM's internal CustomAttributeDecoder. + // We need it to decode security declarations. + + private readonly ICustomAttributeTypeProvider _provider; + private readonly MetadataReader _reader; + private readonly bool _provideBoxingTypeInfo; + + public CustomAttributeDecoder(ICustomAttributeTypeProvider provider, MetadataReader reader, bool provideBoxingTypeInfo = false) + { + _reader = reader; + _provider = provider; + _provideBoxingTypeInfo = provideBoxingTypeInfo; + } + + public ImmutableArray> DecodeNamedArguments(ref BlobReader valueReader, int count) + { + var arguments = ImmutableArray.CreateBuilder>(count); + for (int i = 0; i < count; i++) + { + CustomAttributeNamedArgumentKind kind = (CustomAttributeNamedArgumentKind)valueReader.ReadSerializationTypeCode(); + if (kind != CustomAttributeNamedArgumentKind.Field && kind != CustomAttributeNamedArgumentKind.Property) + { + throw new BadImageFormatException(); + } + + ArgumentTypeInfo info = DecodeNamedArgumentType(ref valueReader); + string name = valueReader.ReadSerializedString(); + CustomAttributeTypedArgument argument = DecodeArgument(ref valueReader, info); + arguments.Add(new CustomAttributeNamedArgument(name, kind, argument.Type, argument.Value)); + } + + return arguments.MoveToImmutable(); + } + + private struct ArgumentTypeInfo + { + public TType Type; + public TType ElementType; + public SerializationTypeCode TypeCode; + public SerializationTypeCode ElementTypeCode; + } + + private ArgumentTypeInfo DecodeNamedArgumentType(ref BlobReader valueReader, bool isElementType = false) + { + var info = new ArgumentTypeInfo { + TypeCode = valueReader.ReadSerializationTypeCode(), + }; + + switch (info.TypeCode) + { + case SerializationTypeCode.Boolean: + case SerializationTypeCode.Byte: + case SerializationTypeCode.Char: + case SerializationTypeCode.Double: + case SerializationTypeCode.Int16: + case SerializationTypeCode.Int32: + case SerializationTypeCode.Int64: + case SerializationTypeCode.SByte: + case SerializationTypeCode.Single: + case SerializationTypeCode.String: + case SerializationTypeCode.UInt16: + case SerializationTypeCode.UInt32: + case SerializationTypeCode.UInt64: + info.Type = _provider.GetPrimitiveType((PrimitiveTypeCode)info.TypeCode); + break; + + case SerializationTypeCode.Type: + info.Type = _provider.GetSystemType(); + break; + + case SerializationTypeCode.TaggedObject: + info.Type = _provider.GetPrimitiveType(PrimitiveTypeCode.Object); + break; + + case SerializationTypeCode.SZArray: + if (isElementType) + { + // jagged arrays are not allowed. + throw new BadImageFormatException(); + } + + var elementInfo = DecodeNamedArgumentType(ref valueReader, isElementType: true); + info.ElementType = elementInfo.Type; + info.ElementTypeCode = elementInfo.TypeCode; + info.Type = _provider.GetSZArrayType(info.ElementType); + break; + + case SerializationTypeCode.Enum: + string typeName = valueReader.ReadSerializedString(); + info.Type = _provider.GetTypeFromSerializedName(typeName); + info.TypeCode = (SerializationTypeCode)_provider.GetUnderlyingEnumType(info.Type); + break; + + default: + throw new BadImageFormatException(); + } + + return info; + } + + private CustomAttributeTypedArgument DecodeArgument(ref BlobReader valueReader, ArgumentTypeInfo info) + { + var outer = info; + if (info.TypeCode == SerializationTypeCode.TaggedObject) + { + info = DecodeNamedArgumentType(ref valueReader); + } + + // PERF_TODO: https://github.com/dotnet/corefx/issues/6533 + // Cache /reuse common arguments to avoid boxing (small integers, true, false). + object value; + switch (info.TypeCode) + { + case SerializationTypeCode.Boolean: + value = valueReader.ReadBoolean(); + break; + + case SerializationTypeCode.Byte: + value = valueReader.ReadByte(); + break; + + case SerializationTypeCode.Char: + value = valueReader.ReadChar(); + break; + + case SerializationTypeCode.Double: + value = valueReader.ReadDouble(); + break; + + case SerializationTypeCode.Int16: + value = valueReader.ReadInt16(); + break; + + case SerializationTypeCode.Int32: + value = valueReader.ReadInt32(); + break; + + case SerializationTypeCode.Int64: + value = valueReader.ReadInt64(); + break; + + case SerializationTypeCode.SByte: + value = valueReader.ReadSByte(); + break; + + case SerializationTypeCode.Single: + value = valueReader.ReadSingle(); + break; + + case SerializationTypeCode.UInt16: + value = valueReader.ReadUInt16(); + break; + + case SerializationTypeCode.UInt32: + value = valueReader.ReadUInt32(); + break; + + case SerializationTypeCode.UInt64: + value = valueReader.ReadUInt64(); + break; + + case SerializationTypeCode.String: + value = valueReader.ReadSerializedString(); + break; + + case SerializationTypeCode.Type: + string typeName = valueReader.ReadSerializedString(); + value = _provider.GetTypeFromSerializedName(typeName); + break; + + case SerializationTypeCode.SZArray: + value = DecodeArrayArgument(ref valueReader, info); + break; + + default: + 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); + } + + private ImmutableArray>? DecodeArrayArgument(ref BlobReader blobReader, ArgumentTypeInfo info) + { + int count = blobReader.ReadInt32(); + if (count == -1) + { + return null; + } + + if (count == 0) + { + return ImmutableArray>.Empty; + } + + if (count < 0) + { + throw new BadImageFormatException(); + } + + var elementInfo = new ArgumentTypeInfo { + Type = info.ElementType, + TypeCode = info.ElementTypeCode, + }; + + var array = ImmutableArray.CreateBuilder>(count); + + for (int i = 0; i < count; i++) + { + array.Add(DecodeArgument(ref blobReader, elementInfo)); + } + + return array.MoveToImmutable(); + } + } +} \ No newline at end of file diff --git a/src/Verify.ICSharpCode.Decompiler/Import/Filter.cs b/src/Verify.ICSharpCode.Decompiler/Import/Filter.cs new file mode 100644 index 0000000..51534f1 --- /dev/null +++ b/src/Verify.ICSharpCode.Decompiler/Import/Filter.cs @@ -0,0 +1,40 @@ +// ReSharper disable All + +// Copyright (c) 2022 tom-englert.de 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.Collections.Generic; +using System.Linq; + +namespace ICSharpCode.Decompiler.Disassembler +{ + public interface IFilter + { + ICollection Filter(ICollection items) where T : Adapter; + } + + public class SortByNameFilter : IFilter + { + public ICollection Filter(ICollection items) where T : Adapter + { + return items.OrderBy(item => item.DisplayName).ToArray(); + } + } +} \ No newline at end of file diff --git a/src/Verify.ICSharpCode.Decompiler/Import/ReadMe.txt b/src/Verify.ICSharpCode.Decompiler/Import/ReadMe.txt new file mode 100644 index 0000000..bbd9336 --- /dev/null +++ b/src/Verify.ICSharpCode.Decompiler/Import/ReadMe.txt @@ -0,0 +1 @@ +TODO: remove the imported source in this folder as soon as thie filtering feature is released in the ICSharpCode.Decompiler \ No newline at end of file diff --git a/src/Verify.ICSharpCode.Decompiler/Import/ReflectionDisassemblerImport.cs b/src/Verify.ICSharpCode.Decompiler/Import/ReflectionDisassemblerImport.cs new file mode 100644 index 0000000..d25b2ec --- /dev/null +++ b/src/Verify.ICSharpCode.Decompiler/Import/ReflectionDisassemblerImport.cs @@ -0,0 +1,2083 @@ +// ReSharper disable All +#nullable disable + +// 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. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Reflection; +using System.Reflection.Metadata; +using System.Reflection.Metadata.Ecma335; +using System.Threading; + +using ICSharpCode.Decompiler.DebugInfo; +using ICSharpCode.Decompiler.IL; +using ICSharpCode.Decompiler.Metadata; + +namespace ICSharpCode.Decompiler.Disassembler +{ + /// + /// Disassembles type and member definitions. + /// + public sealed class ReflectionDisassemblerImport + { + readonly ITextOutput output; + CancellationToken cancellationToken; + bool isInType; // whether we are currently disassembling a whole type (-> defaultCollapsed for foldings) + MethodBodyDisassembler methodBodyDisassembler; + + public bool DetectControlStructure { + get => methodBodyDisassembler.DetectControlStructure; + set => methodBodyDisassembler.DetectControlStructure = value; + } + + public bool ShowSequencePoints { + get => methodBodyDisassembler.ShowSequencePoints; + set => methodBodyDisassembler.ShowSequencePoints = value; + } + + public bool ShowMetadataTokens { + get => methodBodyDisassembler.ShowMetadataTokens; + set => methodBodyDisassembler.ShowMetadataTokens = value; + } + + public bool ShowMetadataTokensInBase10 { + get => methodBodyDisassembler.ShowMetadataTokensInBase10; + set => methodBodyDisassembler.ShowMetadataTokensInBase10 = value; + } + + public bool ShowRawRVAOffsetAndBytes { + get => methodBodyDisassembler.ShowRawRVAOffsetAndBytes; + set => methodBodyDisassembler.ShowRawRVAOffsetAndBytes = value; + } + + public IDebugInfoProvider DebugInfo { + get => methodBodyDisassembler.DebugInfo; + set => methodBodyDisassembler.DebugInfo = value; + } + + public bool ExpandMemberDefinitions { get; set; } + + public IAssemblyResolver AssemblyResolver { get; set; } + + public IFilter Filter { get; set; } + + public ReflectionDisassemblerImport(ITextOutput output, CancellationToken cancellationToken) + : this(output, new MethodBodyDisassembler(output, cancellationToken), cancellationToken) + { + } + + public ReflectionDisassemblerImport(ITextOutput output, MethodBodyDisassembler methodBodyDisassembler, CancellationToken cancellationToken) + { + if (output == null) + throw new ArgumentNullException(nameof(output)); + this.output = output; + this.cancellationToken = cancellationToken; + this.methodBodyDisassembler = methodBodyDisassembler; + } + + EnumNameCollection methodAttributeFlags = new EnumNameCollection() { + { MethodAttributes.Final, "final" }, + { MethodAttributes.HideBySig, "hidebysig" }, + { MethodAttributes.SpecialName, "specialname" }, + { MethodAttributes.PinvokeImpl, null }, // handled separately + { MethodAttributes.UnmanagedExport, "export" }, + { MethodAttributes.RTSpecialName, "rtspecialname" }, + { MethodAttributes.RequireSecObject, "reqsecobj" }, + { MethodAttributes.NewSlot, "newslot" }, + { MethodAttributes.CheckAccessOnOverride, "strict" }, + { MethodAttributes.Abstract, "abstract" }, + { MethodAttributes.Virtual, "virtual" }, + { MethodAttributes.Static, "static" }, + { MethodAttributes.HasSecurity, null }, // ?? also invisible in ILDasm + }; + + EnumNameCollection methodVisibility = new EnumNameCollection() { + { MethodAttributes.Private, "private" }, + { MethodAttributes.FamANDAssem, "famandassem" }, + { MethodAttributes.Assembly, "assembly" }, + { MethodAttributes.Family, "family" }, + { MethodAttributes.FamORAssem, "famorassem" }, + { MethodAttributes.Public, "public" }, + }; + + EnumNameCollection callingConvention = new EnumNameCollection() { + { SignatureCallingConvention.CDecl, "unmanaged cdecl" }, + { SignatureCallingConvention.StdCall, "unmanaged stdcall" }, + { SignatureCallingConvention.ThisCall, "unmanaged thiscall" }, + { SignatureCallingConvention.FastCall, "unmanaged fastcall" }, + { SignatureCallingConvention.VarArgs, "vararg" }, + { SignatureCallingConvention.Default, null }, + }; + + EnumNameCollection methodCodeType = new EnumNameCollection() { + { MethodImplAttributes.IL, "cil" }, + { MethodImplAttributes.Native, "native" }, + { MethodImplAttributes.OPTIL, "optil" }, + { MethodImplAttributes.Runtime, "runtime" }, + }; + + EnumNameCollection methodImpl = new EnumNameCollection() { + { MethodImplAttributes.Synchronized, "synchronized" }, + { MethodImplAttributes.NoInlining, "noinlining" }, + { MethodImplAttributes.NoOptimization, "nooptimization" }, + { MethodImplAttributes.PreserveSig, "preservesig" }, + { MethodImplAttributes.InternalCall, "internalcall" }, + { MethodImplAttributes.ForwardRef, "forwardref" }, + { MethodImplAttributes.AggressiveInlining, "aggressiveinlining" }, + }; + + public void DisassembleMethod(PEFile module, MethodDefinitionHandle handle) + { + DisassembleMethod(new MethodDefinitionAdapter(module, handle)); + } + + private void DisassembleMethod(MethodDefinitionAdapter method) + { + var module = method.Module; + var handle = method.Handle; + + // write method header + output.WriteReference(module, handle, ".method", isDefinition: true); + output.Write(" "); + DisassembleMethodHeaderInternal(method); + DisassembleMethodBlock(method); + } + + public void DisassembleMethodHeader(PEFile module, MethodDefinitionHandle handle) + { + // write method header + output.WriteReference(module, handle, ".method", isDefinition: true); + output.Write(" "); + DisassembleMethodHeaderInternal(new MethodDefinitionAdapter(module, handle)); + } + + void DisassembleMethodHeaderInternal(MethodDefinitionAdapter adapter) + { + var module = adapter.Module; + var handle = adapter.Handle; + var genericContext = adapter.GenericContext; + + var metadata = module.Metadata; + + WriteMetadataToken(output, module, handle, MetadataTokens.GetToken(handle), + spaceAfter: true, spaceBefore: false, ShowMetadataTokens, ShowMetadataTokensInBase10); + var methodDefinition = adapter.Definition; + // .method public hidebysig specialname + // instance default class [mscorlib]System.IO.TextWriter get_BaseWriter () cil managed + // + //emit flags + WriteEnum(methodDefinition.Attributes & MethodAttributes.MemberAccessMask, methodVisibility); + WriteFlags(methodDefinition.Attributes & ~MethodAttributes.MemberAccessMask, methodAttributeFlags); + bool isCompilerControlled = (methodDefinition.Attributes & MethodAttributes.MemberAccessMask) == MethodAttributes.PrivateScope; + if (isCompilerControlled) + output.Write("privatescope "); + + if ((methodDefinition.Attributes & MethodAttributes.PinvokeImpl) == MethodAttributes.PinvokeImpl) + { + output.Write("pinvokeimpl"); + var info = methodDefinition.GetImport(); + if (!info.Module.IsNil) + { + var moduleRef = metadata.GetModuleReference(info.Module); + output.Write("(\"" + DisassemblerHelpers.EscapeString(metadata.GetString(moduleRef.Name)) + "\""); + + if (!info.Name.IsNil && metadata.GetString(info.Name) != metadata.GetString(methodDefinition.Name)) + output.Write(" as \"" + DisassemblerHelpers.EscapeString(metadata.GetString(info.Name)) + "\""); + + if ((info.Attributes & MethodImportAttributes.ExactSpelling) == MethodImportAttributes.ExactSpelling) + output.Write(" nomangle"); + + switch (info.Attributes & MethodImportAttributes.CharSetMask) + { + case MethodImportAttributes.CharSetAnsi: + output.Write(" ansi"); + break; + case MethodImportAttributes.CharSetAuto: + output.Write(" autochar"); + break; + case MethodImportAttributes.CharSetUnicode: + output.Write(" unicode"); + break; + } + + if ((info.Attributes & MethodImportAttributes.SetLastError) == MethodImportAttributes.SetLastError) + output.Write(" lasterr"); + + switch (info.Attributes & MethodImportAttributes.CallingConventionMask) + { + case MethodImportAttributes.CallingConventionCDecl: + output.Write(" cdecl"); + break; + case MethodImportAttributes.CallingConventionFastCall: + output.Write(" fastcall"); + break; + case MethodImportAttributes.CallingConventionStdCall: + output.Write(" stdcall"); + break; + case MethodImportAttributes.CallingConventionThisCall: + output.Write(" thiscall"); + break; + case MethodImportAttributes.CallingConventionWinApi: + output.Write(" winapi"); + break; + } + + output.Write(')'); + } + output.Write(' '); + } + + output.WriteLine(); + output.Indent(); + MethodSignature>? signature; + try + { + var signatureProvider = new DisassemblerSignatureTypeProvider(module, output); + signature = methodDefinition.DecodeSignature(signatureProvider, genericContext); + if (signature.Value.Header.HasExplicitThis) + { + output.Write("instance explicit "); + } + else if (signature.Value.Header.IsInstance) + { + output.Write("instance "); + } + + //call convention + WriteEnum(signature.Value.Header.CallingConvention, callingConvention); + + //return type + signature.Value.ReturnType(ILNameSyntax.Signature); + } + catch (BadImageFormatException) + { + signature = null; + output.Write(""); + } + output.Write(' '); + + var parameters = methodDefinition.GetParameters(); + if (parameters.Count > 0) + { + var firstParam = metadata.GetParameter(parameters.First()); + if (firstParam.SequenceNumber == 0) + { + var marshallingDesc = firstParam.GetMarshallingDescriptor(); + if (!marshallingDesc.IsNil) + { + WriteMarshalInfo(metadata.GetBlobReader(marshallingDesc)); + } + } + } + + if (isCompilerControlled) + { + output.Write(DisassemblerHelpers.Escape(metadata.GetString(methodDefinition.Name) + "$PST" + MetadataTokens.GetToken(handle).ToString("X8"))); + } + else + { + output.Write(DisassemblerHelpers.Escape(metadata.GetString(methodDefinition.Name))); + } + + WriteTypeParameters(output, module, genericContext, methodDefinition.GetGenericParameters()); + + //( params ) + output.Write(" ("); + if (signature?.ParameterTypes.Length > 0) + { + output.WriteLine(); + output.Indent(); + WriteParameters(metadata, parameters, signature.Value); + output.Unindent(); + } + output.Write(") "); + //cil managed + WriteEnum(methodDefinition.ImplAttributes & MethodImplAttributes.CodeTypeMask, methodCodeType); + if ((methodDefinition.ImplAttributes & MethodImplAttributes.ManagedMask) == MethodImplAttributes.Managed) + output.Write("managed "); + else + output.Write("unmanaged "); + WriteFlags(methodDefinition.ImplAttributes & ~(MethodImplAttributes.CodeTypeMask | MethodImplAttributes.ManagedMask), methodImpl); + + output.Unindent(); + } + + internal static void WriteMetadataToken(ITextOutput output, PEFile module, Handle? handle, + int metadataToken, bool spaceAfter, bool spaceBefore, bool showMetadataTokens, bool base10) + { + // handle can be null in case of errors, if that's the case, we always want to print a comment, + // with the metadataToken. + if (showMetadataTokens || handle == null) + { + if (spaceBefore) + { + output.Write(' '); + } + output.Write("/* "); + string format = base10 ? null : "X8"; + if (handle == null || !handle.Value.IsEntityHandle()) + { + output.Write(metadataToken.ToString(format)); + } + else + { + output.WriteReference(module, handle.GetValueOrDefault(), + metadataToken.ToString(format), "metadata"); + } + output.Write(" */"); + if (spaceAfter) + { + output.Write(' '); + } + } + else if (spaceBefore && spaceAfter) + { + output.Write(' '); + } + } + + void DisassembleMethodBlock(MethodDefinitionAdapter adapter) + { + var module = adapter.Module; + var handle = adapter.Handle; + var genericContext = adapter.GenericContext; + + var metadata = module.Metadata; + var methodDefinition = metadata.GetMethodDefinition(handle); + + OpenBlock(defaultCollapsed: isInType); + WriteAttributes(module, methodDefinition.GetCustomAttributes()); + foreach (var h in handle.GetMethodImplementations(metadata)) + { + var impl = metadata.GetMethodImplementation(h); + output.Write(".override method "); + impl.MethodDeclaration.WriteTo(module, output, genericContext); + output.WriteLine(); + } + + foreach (var p in methodDefinition.GetGenericParameters()) + { + WriteGenericParameterAttributes(module, genericContext, p); + } + foreach (var p in methodDefinition.GetParameters()) + { + WriteParameterAttributes(module, p); + } + WriteSecurityDeclarations(module, methodDefinition.GetDeclarativeSecurityAttributes()); + + if (methodDefinition.HasBody()) + { + methodBodyDisassembler.Disassemble(module, handle); + } + var declaringType = metadata.GetTypeDefinition(methodDefinition.GetDeclaringType()); + CloseBlock("end of method " + DisassemblerHelpers.Escape(metadata.GetString(declaringType.Name)) + + "::" + DisassemblerHelpers.Escape(metadata.GetString(methodDefinition.Name))); + } + + void WriteSecurityDeclarations(PEFile module, DeclarativeSecurityAttributeHandleCollection secDeclProvider) + { + if (secDeclProvider.Count == 0) + return; + foreach (var h in secDeclProvider) + { + output.Write(".permissionset "); + var secdecl = module.Metadata.GetDeclarativeSecurityAttribute(h); + switch ((ushort)secdecl.Action) + { + case 1: // DeclarativeSecurityAction.Request + output.Write("request"); + break; + case 2: // DeclarativeSecurityAction.Demand + output.Write("demand"); + break; + case 3: // DeclarativeSecurityAction.Assert + output.Write("assert"); + break; + case 4: // DeclarativeSecurityAction.Deny + output.Write("deny"); + break; + case 5: // DeclarativeSecurityAction.PermitOnly + output.Write("permitonly"); + break; + case 6: // DeclarativeSecurityAction.LinkDemand + output.Write("linkcheck"); + break; + case 7: // DeclarativeSecurityAction.InheritDemand + output.Write("inheritcheck"); + break; + case 8: // DeclarativeSecurityAction.RequestMinimum + output.Write("reqmin"); + break; + case 9: // DeclarativeSecurityAction.RequestOptional + output.Write("reqopt"); + break; + case 10: // DeclarativeSecurityAction.RequestRefuse + output.Write("reqrefuse"); + break; + case 11: // DeclarativeSecurityAction.PreJitGrant + output.Write("prejitgrant"); + break; + case 12: // DeclarativeSecurityAction.PreJitDeny + output.Write("prejitdeny"); + break; + case 13: // DeclarativeSecurityAction.NonCasDemand + output.Write("noncasdemand"); + break; + case 14: // DeclarativeSecurityAction.NonCasLinkDemand + output.Write("noncaslinkdemand"); + break; + case 15: // DeclarativeSecurityAction.NonCasInheritance + output.Write("noncasinheritance"); + break; + default: + output.Write(secdecl.Action.ToString()); + break; + } + var blob = module.Metadata.GetBlobReader(secdecl.PermissionSet); + if (AssemblyResolver == null) + { + output.Write(" = "); + WriteBlob(blob); + output.WriteLine(); + } + else if ((char)blob.ReadByte() != '.') + { + blob.Reset(); + output.WriteLine(); + output.Indent(); + output.Write("bytearray"); + WriteBlob(blob); + output.WriteLine(); + output.Unindent(); + } + else + { + var outputWithRollback = new TextOutputWithRollback(output); + try + { + TryDecodeSecurityDeclaration(outputWithRollback, blob, module); + outputWithRollback.Commit(); + } + catch (Exception ex) when (ex is BadImageFormatException || ex is EnumUnderlyingTypeResolveException) + { + blob.Reset(); + output.Write(" = "); + WriteBlob(blob); + output.WriteLine(); + } + } + } + } + + 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(" = {"); + output.Indent(); + + string currentAssemblyName = null; + string currentFullAssemblyName = null; + if (module.Metadata.IsAssembly) + { + try + { + currentAssemblyName = module.Metadata.GetString(module.Metadata.GetAssemblyDefinition().Name); + } + catch (BadImageFormatException) + { + currentAssemblyName = ""; + } + if (!module.Metadata.TryGetFullAssemblyName(out currentFullAssemblyName)) + { + currentFullAssemblyName = ""; + } + } + int count = blob.ReadCompressedInteger(); + for (int i = 0; i < count; i++) + { + var fullTypeName = blob.ReadSerializedString(); + string[] nameParts = fullTypeName.Split(new[] { ", " }, StringSplitOptions.None); + if (nameParts.Length < 2 || nameParts[1] == currentAssemblyName) + { + output.Write("class "); + output.Write(DisassemblerHelpers.Escape(fullTypeName)); + } + else + { + output.Write('['); + output.Write(nameParts[1]); + output.Write(']'); + output.Write(nameParts[0]); + } + output.Write(" = {"); + 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(); + } + + 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("}"); + } + + void WriteValue(ITextOutput output, (PrimitiveTypeCode Code, string Name) type, object value) + { + if (value is CustomAttributeTypedArgument<(PrimitiveTypeCode, string)> boxedValue) + { + output.Write("object("); + 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 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(")"); + } + else + { + string typeName = type.Name != null && !type.Name.StartsWith("enum ", StringComparison.Ordinal) + ? type.Name : PrimitiveTypeCodeToString(type.Code); + + output.Write(typeName); + output.Write("("); + WriteSimpleValue(output, value, typeName); + output.Write(")"); + } + } + + private static void WriteSimpleValue(ITextOutput output, object value, string typeName) + { + 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 + { + output.Write(info.Name); + } + break; + default: + DisassemblerHelpers.WriteOperand(output, value); + break; + } + } + + static string PrimitiveTypeCodeToString(PrimitiveTypeCode typeCode) + { + 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: + return "unknown"; + } + } + + void WriteMarshalInfo(BlobReader marshalInfo) + { + output.Write("marshal("); + WriteNativeType(ref marshalInfo); + output.Write(") "); + } + + void WriteNativeType(ref BlobReader blob) + { + byte type; + switch (type = blob.ReadByte()) + { + case 0x66: // None + case 0x50: // Max + break; + case 0x02: // NATIVE_TYPE_BOOLEAN + output.Write("bool"); + break; + case 0x03: // NATIVE_TYPE_I1 + output.Write("int8"); + break; + case 0x04: // NATIVE_TYPE_U1 + output.Write("unsigned int8"); + break; + case 0x05: // NATIVE_TYPE_I2 + output.Write("int16"); + break; + case 0x06: // NATIVE_TYPE_U2 + output.Write("unsigned int16"); + break; + case 0x07: // NATIVE_TYPE_I4 + output.Write("int32"); + break; + case 0x08: // NATIVE_TYPE_U4 + output.Write("unsigned int32"); + break; + case 0x09: // NATIVE_TYPE_I8 + output.Write("int64"); + break; + case 0x0a: // NATIVE_TYPE_U8 + output.Write("unsigned int64"); + break; + case 0x0b: // NATIVE_TYPE_R4 + output.Write("float32"); + break; + case 0x0c: // NATIVE_TYPE_R8 + output.Write("float64"); + break; + case 0x14: // NATIVE_TYPE_LPSTR + output.Write("lpstr"); + break; + case 0x1f: // NATIVE_TYPE_INT + output.Write("int"); + break; + case 0x20: // NATIVE_TYPE_UINT + output.Write("unsigned int"); + break; + case 0x26: // NATIVE_TYPE_FUNC + output.Write("Func"); + break; + case 0x2a: // NATIVE_TYPE_ARRAY + if (blob.RemainingBytes > 0) + WriteNativeType(ref blob); + output.Write('['); + int sizeParameterIndex = blob.TryReadCompressedInteger(out int value) ? value : -1; + int size = blob.TryReadCompressedInteger(out value) ? value : -1; + int sizeParameterMultiplier = blob.TryReadCompressedInteger(out value) ? value : -1; + if (size >= 0) + { + output.Write(size.ToString()); + } + if (sizeParameterIndex >= 0 && sizeParameterMultiplier != 0) + { + output.Write(" + "); + output.Write(sizeParameterIndex.ToString()); + } + output.Write(']'); + break; + case 0x0f: // Currency + output.Write("currency"); + break; + case 0x13: // BStr + output.Write("bstr"); + break; + case 0x15: // LPWStr + output.Write("lpwstr"); + break; + case 0x16: // LPTStr + output.Write("lptstr"); + break; + case 0x17: // FixedSysString + output.Write("fixed sysstring[{0}]", blob.ReadCompressedInteger()); + break; + case 0x19: // IUnknown + output.Write("iunknown"); + break; + case 0x1a: // IDispatch + output.Write("idispatch"); + break; + case 0x1b: // Struct + output.Write("struct"); + break; + case 0x1c: // IntF + output.Write("interface"); + break; + case 0x1d: // SafeArray + output.Write("safearray "); + if (blob.RemainingBytes > 0) + { + byte elementType = blob.ReadByte(); + switch (elementType) + { + case 0: // None + break; + case 2: // I2 + output.Write("int16"); + break; + case 3: // I4 + output.Write("int32"); + break; + case 4: // R4 + output.Write("float32"); + break; + case 5: // R8 + output.Write("float64"); + break; + case 6: // Currency + output.Write("currency"); + break; + case 7: // Date + output.Write("date"); + break; + case 8: // BStr + output.Write("bstr"); + break; + case 9: // Dispatch + output.Write("idispatch"); + break; + case 10: // Error + output.Write("error"); + break; + case 11: // Bool + output.Write("bool"); + break; + case 12: // Variant + output.Write("variant"); + break; + case 13: // Unknown + output.Write("iunknown"); + break; + case 14: // Decimal + output.Write("decimal"); + break; + case 16: // I1 + output.Write("int8"); + break; + case 17: // UI1 + output.Write("unsigned int8"); + break; + case 18: // UI2 + output.Write("unsigned int16"); + break; + case 19: // UI4 + output.Write("unsigned int32"); + break; + case 22: // Int + output.Write("int"); + break; + case 23: // UInt + output.Write("unsigned int"); + break; + default: + output.Write(elementType.ToString()); + break; + } + } + break; + case 0x1e: // FixedArray + output.Write("fixed array"); + output.Write("[{0}]", blob.TryReadCompressedInteger(out value) ? value : 0); + if (blob.RemainingBytes > 0) + { + output.Write(' '); + WriteNativeType(ref blob); + } + break; + case 0x22: // ByValStr + output.Write("byvalstr"); + break; + case 0x23: // ANSIBStr + output.Write("ansi bstr"); + break; + case 0x24: // TBStr + output.Write("tbstr"); + break; + case 0x25: // VariantBool + output.Write("variant bool"); + break; + case 0x28: // ASAny + output.Write("as any"); + break; + case 0x2b: // LPStruct + output.Write("lpstruct"); + break; + case 0x2c: // CustomMarshaler + string guidValue = blob.ReadSerializedString(); + string unmanagedType = blob.ReadSerializedString(); + string managedType = blob.ReadSerializedString(); + string cookie = blob.ReadSerializedString(); + + var guid = !string.IsNullOrEmpty(guidValue) ? new Guid(guidValue) : Guid.Empty; + + output.Write("custom(\"{0}\", \"{1}\"", + DisassemblerHelpers.EscapeString(managedType), + DisassemblerHelpers.EscapeString(cookie)); + if (guid != Guid.Empty || !string.IsNullOrEmpty(unmanagedType)) + { + output.Write(", \"{0}\", \"{1}\"", guid.ToString(), DisassemblerHelpers.EscapeString(unmanagedType)); + } + output.Write(')'); + break; + case 0x2d: // Error + output.Write("error"); + break; + default: + output.Write(type.ToString()); + break; + } + } + + void WriteParameters(MetadataReader metadata, IEnumerable parameters, MethodSignature> signature) + { + int i = 0; + int offset = signature.Header.IsInstance ? 1 : 0; + + foreach (var h in parameters) + { + var p = metadata.GetParameter(h); + // skip return type parameter handle + if (p.SequenceNumber == 0) + continue; + + // fill gaps in parameter list + while (i < p.SequenceNumber - 1) + { + if (i > 0) + { + output.Write(','); + output.WriteLine(); + } + signature.ParameterTypes[i](ILNameSyntax.Signature); + output.Write(' '); + output.WriteLocalReference("''", "param_" + (i + offset), isDefinition: true); + i++; + } + + // separator + if (i > 0) + { + output.Write(','); + output.WriteLine(); + } + + // print parameter + if ((p.Attributes & ParameterAttributes.In) == ParameterAttributes.In) + output.Write("[in] "); + if ((p.Attributes & ParameterAttributes.Out) == ParameterAttributes.Out) + output.Write("[out] "); + if ((p.Attributes & ParameterAttributes.Optional) == ParameterAttributes.Optional) + output.Write("[opt] "); + signature.ParameterTypes[i](ILNameSyntax.Signature); + output.Write(' '); + var md = p.GetMarshallingDescriptor(); + if (!md.IsNil) + { + WriteMarshalInfo(metadata.GetBlobReader(md)); + } + output.WriteLocalReference(DisassemblerHelpers.Escape(metadata.GetString(p.Name)), "param_" + (i + offset), isDefinition: true); + i++; + } + + // add remaining parameter types as unnamed parameters + while (i < signature.RequiredParameterCount) + { + if (i > 0) + { + output.Write(','); + output.WriteLine(); + } + signature.ParameterTypes[i](ILNameSyntax.Signature); + output.Write(' '); + output.WriteLocalReference("''", "param_" + (i + offset), isDefinition: true); + i++; + } + + output.WriteLine(); + } + + void WriteGenericParameterAttributes(PEFile module, GenericContext context, GenericParameterHandle handle) + { + var metadata = module.Metadata; + var p = metadata.GetGenericParameter(handle); + if (p.GetCustomAttributes().Count > 0) + { + output.Write(".param type {0}", metadata.GetString(p.Name)); + output.WriteLine(); + output.Indent(); + WriteAttributes(module, p.GetCustomAttributes()); + output.Unindent(); + } + foreach (var constraintHandle in p.GetConstraints()) + { + var constraint = metadata.GetGenericParameterConstraint(constraintHandle); + if (constraint.GetCustomAttributes().Count > 0) + { + output.Write(".param constraint {0}, ", metadata.GetString(p.Name)); + constraint.Type.WriteTo(module, output, context, ILNameSyntax.TypeName); + output.WriteLine(); + output.Indent(); + WriteAttributes(module, constraint.GetCustomAttributes()); + output.Unindent(); + } + } + } + + void WriteParameterAttributes(PEFile module, ParameterHandle handle) + { + var metadata = module.Metadata; + var p = metadata.GetParameter(handle); + if (p.GetDefaultValue().IsNil && p.GetCustomAttributes().Count == 0) + return; + output.Write(".param [{0}]", p.SequenceNumber); + if (!p.GetDefaultValue().IsNil) + { + output.Write(" = "); + WriteConstant(metadata, metadata.GetConstant(p.GetDefaultValue())); + } + output.WriteLine(); + output.Indent(); + WriteAttributes(module, p.GetCustomAttributes()); + output.Unindent(); + } + + void WriteConstant(MetadataReader metadata, Constant constant) + { + switch (constant.TypeCode) + { + case ConstantTypeCode.NullReference: + output.Write("nullref"); + break; + default: + var blob = metadata.GetBlobReader(constant.Value); + object value; + try + { + value = blob.ReadConstant(constant.TypeCode); + } + catch (ArgumentOutOfRangeException) + { + output.Write($"/* Constant with invalid typecode: {constant.TypeCode} */"); + return; + } + if (value is string) + { + DisassemblerHelpers.WriteOperand(output, value); + } + else + { + string typeName = DisassemblerHelpers.PrimitiveTypeName(value.GetType().FullName); + output.Write(typeName); + output.Write('('); + float? cf = value as float?; + double? cd = value as double?; + if (cf.HasValue && (float.IsNaN(cf.Value) || float.IsInfinity(cf.Value))) + { + output.Write("0x{0:x8}", BitConverter.ToInt32(BitConverter.GetBytes(cf.Value), 0)); + } + else if (cd.HasValue && (double.IsNaN(cd.Value) || double.IsInfinity(cd.Value))) + { + output.Write("0x{0:x16}", BitConverter.DoubleToInt64Bits(cd.Value)); + } + else + { + DisassemblerHelpers.WriteOperand(output, value); + } + output.Write(')'); + } + break; + } + } + + EnumNameCollection fieldVisibility = new EnumNameCollection() { + { FieldAttributes.Private, "private" }, + { FieldAttributes.FamANDAssem, "famandassem" }, + { FieldAttributes.Assembly, "assembly" }, + { FieldAttributes.Family, "family" }, + { FieldAttributes.FamORAssem, "famorassem" }, + { FieldAttributes.Public, "public" }, + }; + + EnumNameCollection fieldAttributes = new EnumNameCollection() { + { FieldAttributes.Static, "static" }, + { FieldAttributes.Literal, "literal" }, + { FieldAttributes.InitOnly, "initonly" }, + { FieldAttributes.SpecialName, "specialname" }, + { FieldAttributes.RTSpecialName, "rtspecialname" }, + { FieldAttributes.NotSerialized, "notserialized" }, + }; + + public void DisassembleField(PEFile module, FieldDefinitionHandle handle) + { + DisassembleField(new FieldDefinitionAdapter(module, handle)); + } + + private void DisassembleField(FieldDefinitionAdapter field) + { + var module = field.Module; + var metadata = module.Metadata; + var fieldDefinition = field.Definition; + char sectionPrefix = DisassembleFieldHeaderInternal(module, field.Handle, metadata, fieldDefinition); + output.WriteLine(); + var attributes = fieldDefinition.GetCustomAttributes(); + if (attributes.Count > 0) + { + output.MarkFoldStart(); + WriteAttributes(module, fieldDefinition.GetCustomAttributes()); + output.MarkFoldEnd(); + } + if (fieldDefinition.HasFlag(FieldAttributes.HasFieldRVA)) + { + // Field data as specified in II.16.3.1 of ECMA-335 6th edition + int rva = fieldDefinition.GetRelativeVirtualAddress(); + int sectionIndex = module.Reader.PEHeaders.GetContainingSectionIndex(rva); + if (sectionIndex < 0) + { + output.WriteLine($"// RVA {rva:X8} invalid (not in any section)"); + } + else + { + BlobReader initVal; + try + { + initVal = fieldDefinition.GetInitialValue(module.Reader, null); + } + catch (BadImageFormatException ex) + { + initVal = default; + output.WriteLine("// .data {2}_{0:X8} = {1}", fieldDefinition.GetRelativeVirtualAddress(), ex.Message, sectionPrefix); + } + if (initVal.Length > 0) + { + var sectionHeader = module.Reader.PEHeaders.SectionHeaders[sectionIndex]; + output.Write(".data "); + if (sectionHeader.Name == ".text") + { + output.Write("cil "); + } + else if (sectionHeader.Name == ".tls") + { + output.Write("tls "); + } + else if (sectionHeader.Name != ".data") + { + output.Write($"/* {sectionHeader.Name} */ "); + } + output.Write($"{sectionPrefix}_{rva:X8} = bytearray "); + WriteBlob(initVal); + output.WriteLine(); + } + } + } + } + + public void DisassembleFieldHeader(PEFile module, FieldDefinitionHandle handle) + { + var metadata = module.Metadata; + var fieldDefinition = metadata.GetFieldDefinition(handle); + DisassembleFieldHeaderInternal(module, handle, metadata, fieldDefinition); + } + + private char DisassembleFieldHeaderInternal(PEFile module, FieldDefinitionHandle handle, MetadataReader metadata, FieldDefinition fieldDefinition) + { + output.WriteReference(module, handle, ".field", isDefinition: true); + WriteMetadataToken(output, module, handle, MetadataTokens.GetToken(handle), + spaceAfter: true, spaceBefore: true, ShowMetadataTokens, ShowMetadataTokensInBase10); + int offset = fieldDefinition.GetOffset(); + if (offset > -1) + { + output.Write("[" + offset + "] "); + } + WriteEnum(fieldDefinition.Attributes & FieldAttributes.FieldAccessMask, fieldVisibility); + const FieldAttributes hasXAttributes = FieldAttributes.HasDefault | FieldAttributes.HasFieldMarshal | FieldAttributes.HasFieldRVA; + WriteFlags(fieldDefinition.Attributes & ~(FieldAttributes.FieldAccessMask | hasXAttributes), fieldAttributes); + + var signature = fieldDefinition.DecodeSignature(new DisassemblerSignatureTypeProvider(module, output), new GenericContext(fieldDefinition.GetDeclaringType(), module)); + + var marshallingDescriptor = fieldDefinition.GetMarshallingDescriptor(); + if (!marshallingDescriptor.IsNil) + { + WriteMarshalInfo(metadata.GetBlobReader(marshallingDescriptor)); + } + + signature(ILNameSyntax.Signature); + output.Write(' '); + var fieldName = metadata.GetString(fieldDefinition.Name); + output.Write(DisassemblerHelpers.Escape(fieldName)); + char sectionPrefix = 'D'; + if (fieldDefinition.HasFlag(FieldAttributes.HasFieldRVA)) + { + int rva = fieldDefinition.GetRelativeVirtualAddress(); + sectionPrefix = GetRVASectionPrefix(module.Reader.PEHeaders, rva); + output.Write(" at {1}_{0:X8}", rva, sectionPrefix); + } + + var defaultValue = fieldDefinition.GetDefaultValue(); + if (!defaultValue.IsNil) + { + output.Write(" = "); + WriteConstant(metadata, metadata.GetConstant(defaultValue)); + } + + return sectionPrefix; + } + + char GetRVASectionPrefix(System.Reflection.PortableExecutable.PEHeaders headers, int rva) + { + int sectionIndex = headers.GetContainingSectionIndex(rva); + if (sectionIndex < 0) + return 'D'; + var sectionHeader = headers.SectionHeaders[sectionIndex]; + switch (sectionHeader.Name) + { + case ".tls": + return 'T'; + case ".text": + return 'I'; + default: + return 'D'; + } + } + + EnumNameCollection propertyAttributes = new EnumNameCollection() { + { PropertyAttributes.SpecialName, "specialname" }, + { PropertyAttributes.RTSpecialName, "rtspecialname" }, + { PropertyAttributes.HasDefault, "hasdefault" }, + }; + + public void DisassembleProperty(PEFile module, PropertyDefinitionHandle property) + { + DisassembleProperty(new PropertyDefinitionAdapter(module, property)); + } + + private void DisassembleProperty(PropertyDefinitionAdapter adapter) + { + var module = adapter.Module; + var metadata = module.Metadata; + var propertyDefinition = adapter.Definition; + var property = adapter.Handle; + + PropertyAccessors accessors = DisassemblePropertyHeaderInternal(module, property, metadata, propertyDefinition); + + OpenBlock(false); + WriteAttributes(module, propertyDefinition.GetCustomAttributes()); + WriteNestedMethod(".get", module, accessors.Getter); + WriteNestedMethod(".set", module, accessors.Setter); + foreach (var method in accessors.Others) + { + WriteNestedMethod(".other", module, method); + } + CloseBlock(); + } + + public void DisassemblePropertyHeader(PEFile module, PropertyDefinitionHandle property) + { + var metadata = module.Metadata; + var propertyDefinition = metadata.GetPropertyDefinition(property); + DisassemblePropertyHeaderInternal(module, property, metadata, propertyDefinition); + } + + private PropertyAccessors DisassemblePropertyHeaderInternal(PEFile module, PropertyDefinitionHandle handle, MetadataReader metadata, PropertyDefinition propertyDefinition) + { + output.WriteReference(module, handle, ".property", isDefinition: true); + WriteMetadataToken(output, module, handle, MetadataTokens.GetToken(handle), + spaceAfter: true, spaceBefore: true, ShowMetadataTokens, ShowMetadataTokensInBase10); + WriteFlags(propertyDefinition.Attributes, propertyAttributes); + var accessors = propertyDefinition.GetAccessors(); + var declaringType = metadata.GetMethodDefinition(accessors.GetAny()).GetDeclaringType(); + var signature = propertyDefinition.DecodeSignature(new DisassemblerSignatureTypeProvider(module, output), new GenericContext(declaringType, module)); + + if (signature.Header.IsInstance) + output.Write("instance "); + signature.ReturnType(ILNameSyntax.Signature); + output.Write(' '); + output.Write(DisassemblerHelpers.Escape(metadata.GetString(propertyDefinition.Name))); + + output.Write('('); + if (signature.ParameterTypes.Length > 0) + { + var parameters = metadata.GetMethodDefinition(accessors.GetAny()).GetParameters(); + int parametersCount = accessors.Getter.IsNil ? parameters.Count - 1 : parameters.Count; + + output.WriteLine(); + output.Indent(); + WriteParameters(metadata, parameters.Take(parametersCount), signature); + output.Unindent(); + } + output.Write(')'); + return accessors; + } + + void WriteNestedMethod(string keyword, PEFile module, MethodDefinitionHandle method) + { + if (method.IsNil) + return; + + output.Write(keyword); + output.Write(' '); + ((EntityHandle)method).WriteTo(module, output, GenericContext.Empty); + output.WriteLine(); + } + + EnumNameCollection eventAttributes = new EnumNameCollection() { + { EventAttributes.SpecialName, "specialname" }, + { EventAttributes.RTSpecialName, "rtspecialname" }, + }; + + public void DisassembleEvent(PEFile module, EventDefinitionHandle handle) + { + DisassembleEvent(new EventDefinitionAdapter(module, handle)); + } + + private void DisassembleEvent(EventDefinitionAdapter adapter) + { + var module = adapter.Module; + var handle = adapter.Handle; + + var eventDefinition = module.Metadata.GetEventDefinition(handle); + var accessors = eventDefinition.GetAccessors(); + DisassembleEventHeaderInternal(module, handle, eventDefinition, accessors); + OpenBlock(false); + WriteAttributes(module, eventDefinition.GetCustomAttributes()); + WriteNestedMethod(".addon", module, accessors.Adder); + WriteNestedMethod(".removeon", module, accessors.Remover); + WriteNestedMethod(".fire", module, accessors.Raiser); + foreach (var method in accessors.Others) + { + WriteNestedMethod(".other", module, method); + } + CloseBlock(); + } + + public void DisassembleEventHeader(PEFile module, EventDefinitionHandle handle) + { + var eventDefinition = module.Metadata.GetEventDefinition(handle); + var accessors = eventDefinition.GetAccessors(); + DisassembleEventHeaderInternal(module, handle, eventDefinition, accessors); + } + + private void DisassembleEventHeaderInternal(PEFile module, EventDefinitionHandle handle, EventDefinition eventDefinition, EventAccessors accessors) + { + TypeDefinitionHandle declaringType; + if (!accessors.Adder.IsNil) + { + declaringType = module.Metadata.GetMethodDefinition(accessors.Adder).GetDeclaringType(); + } + else if (!accessors.Remover.IsNil) + { + declaringType = module.Metadata.GetMethodDefinition(accessors.Remover).GetDeclaringType(); + } + else + { + declaringType = module.Metadata.GetMethodDefinition(accessors.Raiser).GetDeclaringType(); + } + output.WriteReference(module, handle, ".event", isDefinition: true); + WriteMetadataToken(output, module, handle, MetadataTokens.GetToken(handle), + spaceAfter: true, spaceBefore: true, ShowMetadataTokens, ShowMetadataTokensInBase10); + WriteFlags(eventDefinition.Attributes, eventAttributes); + var provider = new DisassemblerSignatureTypeProvider(module, output); + Action signature; + switch (eventDefinition.Type.Kind) + { + case HandleKind.TypeDefinition: + signature = provider.GetTypeFromDefinition(module.Metadata, (TypeDefinitionHandle)eventDefinition.Type, 0); + break; + case HandleKind.TypeReference: + signature = provider.GetTypeFromReference(module.Metadata, (TypeReferenceHandle)eventDefinition.Type, 0); + break; + case HandleKind.TypeSpecification: + signature = provider.GetTypeFromSpecification(module.Metadata, new GenericContext(declaringType, module), + (TypeSpecificationHandle)eventDefinition.Type, 0); + break; + default: + throw new BadImageFormatException("Expected a TypeDef, TypeRef or TypeSpec handle!"); + } + signature(ILNameSyntax.TypeName); + output.Write(' '); + output.Write(DisassemblerHelpers.Escape(module.Metadata.GetString(eventDefinition.Name))); + } + + EnumNameCollection typeVisibility = new EnumNameCollection() { + { TypeAttributes.Public, "public" }, + { TypeAttributes.NotPublic, "private" }, + { TypeAttributes.NestedPublic, "nested public" }, + { TypeAttributes.NestedPrivate, "nested private" }, + { TypeAttributes.NestedAssembly, "nested assembly" }, + { TypeAttributes.NestedFamily, "nested family" }, + { TypeAttributes.NestedFamANDAssem, "nested famandassem" }, + { TypeAttributes.NestedFamORAssem, "nested famorassem" }, + }; + + EnumNameCollection typeLayout = new EnumNameCollection() { + { TypeAttributes.AutoLayout, "auto" }, + { TypeAttributes.SequentialLayout, "sequential" }, + { TypeAttributes.ExplicitLayout, "explicit" }, + }; + + EnumNameCollection typeStringFormat = new EnumNameCollection() { + { TypeAttributes.AutoClass, "auto" }, + { TypeAttributes.AnsiClass, "ansi" }, + { TypeAttributes.UnicodeClass, "unicode" }, + }; + + EnumNameCollection typeAttributes = new EnumNameCollection() { + { TypeAttributes.Abstract, "abstract" }, + { TypeAttributes.Sealed, "sealed" }, + { TypeAttributes.SpecialName, "specialname" }, + { TypeAttributes.Import, "import" }, + { TypeAttributes.Serializable, "serializable" }, + { TypeAttributes.WindowsRuntime, "windowsruntime" }, + { TypeAttributes.BeforeFieldInit, "beforefieldinit" }, + { TypeAttributes.HasSecurity, null }, + }; + + public void DisassembleType(PEFile module, TypeDefinitionHandle type) + { + DisassembleType(new TypeDefinitionAdapter(module, type)); + } + + private void DisassembleType(TypeDefinitionAdapter type) + { + var module = type.Module; + var typeDefinition = type.Definition; + var genericContext = type.GenericContext; + + DisassembleTypeHeaderInternal(type); + + var interfaces = OnFilter(type.GetInterfaceImplementations()); + if (interfaces.Count > 0) + { + output.Indent(); + bool first = true; + foreach (var i in interfaces) + { + if (!first) + output.WriteLine(","); + if (first) + output.Write("implements "); + else + output.Write(" "); + first = false; + var iface = i.Implementation; + WriteAttributes(module, iface.GetCustomAttributes()); + iface.Interface.WriteTo(module, output, genericContext, ILNameSyntax.TypeName); + } + output.WriteLine(); + output.Unindent(); + } + + output.WriteLine("{"); + output.Indent(); + bool oldIsInType = isInType; + isInType = true; + WriteAttributes(module, typeDefinition.GetCustomAttributes()); + WriteSecurityDeclarations(module, typeDefinition.GetDeclarativeSecurityAttributes()); + foreach (var tp in typeDefinition.GetGenericParameters()) + { + WriteGenericParameterAttributes(module, genericContext, tp); + } + var layout = typeDefinition.GetLayout(); + if (!layout.IsDefault) + { + output.WriteLine(".pack {0}", layout.PackingSize); + output.WriteLine(".size {0}", layout.Size); + output.WriteLine(); + } + var nestedTypes = OnFilter(type.GetNestedTypes()); + if (nestedTypes.Any()) + { + output.WriteLine("// Nested Types"); + foreach (var nestedType in nestedTypes) + { + cancellationToken.ThrowIfCancellationRequested(); + DisassembleType(nestedType); + output.WriteLine(); + } + output.WriteLine(); + } + var fields = OnFilter(type.GetFields()); + if (fields.Any()) + { + output.WriteLine("// Fields"); + foreach (var field in fields) + { + cancellationToken.ThrowIfCancellationRequested(); + DisassembleField(field); + } + output.WriteLine(); + } + var methods = OnFilter(type.GetMethods()); + if (methods.Any()) + { + output.WriteLine("// Methods"); + foreach (var m in methods) + { + cancellationToken.ThrowIfCancellationRequested(); + DisassembleMethod(m); + output.WriteLine(); + } + } + var events = OnFilter(type.GetEvents()); + if (events.Any()) + { + output.WriteLine("// Events"); + foreach (var ev in events) + { + cancellationToken.ThrowIfCancellationRequested(); + DisassembleEvent(ev); + output.WriteLine(); + } + output.WriteLine(); + } + var properties = OnFilter(type.GetProperties()); + if (properties.Any()) + { + output.WriteLine("// Properties"); + foreach (var prop in properties) + { + cancellationToken.ThrowIfCancellationRequested(); + DisassembleProperty(prop); + } + output.WriteLine(); + } + CloseBlock("end of class " + (!typeDefinition.GetDeclaringType().IsNil ? module.Metadata.GetString(typeDefinition.Name) : typeDefinition.GetFullTypeName(module.Metadata).ToString())); + isInType = oldIsInType; + } + + public void DisassembleTypeHeader(PEFile module, TypeDefinitionHandle type) + { + DisassembleTypeHeaderInternal(new TypeDefinitionAdapter(module, type)); + } + + private void DisassembleTypeHeaderInternal(TypeDefinitionAdapter type) + { + var module = type.Module; + var handle = type.Handle; + var typeDefinition = type.Definition; + var genericContext = type.GenericContext; + + output.WriteReference(module, handle, ".class", isDefinition: true); + WriteMetadataToken(output, module, handle, MetadataTokens.GetToken(handle), + spaceAfter: true, spaceBefore: true, ShowMetadataTokens, ShowMetadataTokensInBase10); + if ((typeDefinition.Attributes & TypeAttributes.ClassSemanticsMask) == TypeAttributes.Interface) + output.Write("interface "); + WriteEnum(typeDefinition.Attributes & TypeAttributes.VisibilityMask, typeVisibility); + WriteEnum(typeDefinition.Attributes & TypeAttributes.LayoutMask, typeLayout); + WriteEnum(typeDefinition.Attributes & TypeAttributes.StringFormatMask, typeStringFormat); + const TypeAttributes masks = TypeAttributes.ClassSemanticsMask | TypeAttributes.VisibilityMask | TypeAttributes.LayoutMask | TypeAttributes.StringFormatMask; + WriteFlags(typeDefinition.Attributes & ~masks, typeAttributes); + + output.Write(typeDefinition.GetDeclaringType().IsNil ? typeDefinition.GetFullTypeName(module.Metadata).ToILNameString() : DisassemblerHelpers.Escape(module.Metadata.GetString(typeDefinition.Name))); + WriteTypeParameters(output, module, genericContext, typeDefinition.GetGenericParameters()); + output.MarkFoldStart(defaultCollapsed: !ExpandMemberDefinitions && isInType); + output.WriteLine(); + + EntityHandle baseType = typeDefinition.GetBaseTypeOrNil(); + if (!baseType.IsNil) + { + output.Indent(); + output.Write("extends "); + baseType.WriteTo(module, output, genericContext, ILNameSyntax.TypeName); + output.WriteLine(); + output.Unindent(); + } + } + + void WriteTypeParameters(ITextOutput output, PEFile module, GenericContext context, GenericParameterHandleCollection p) + { + if (p.Count > 0) + { + output.Write('<'); + var metadata = module.Metadata; + for (int i = 0; i < p.Count; i++) + { + if (i > 0) + output.Write(", "); + var gp = metadata.GetGenericParameter(p[i]); + if ((gp.Attributes & GenericParameterAttributes.ReferenceTypeConstraint) == GenericParameterAttributes.ReferenceTypeConstraint) + { + output.Write("class "); + } + else if ((gp.Attributes & GenericParameterAttributes.NotNullableValueTypeConstraint) == GenericParameterAttributes.NotNullableValueTypeConstraint) + { + output.Write("valuetype "); + } + if ((gp.Attributes & GenericParameterAttributes.DefaultConstructorConstraint) == GenericParameterAttributes.DefaultConstructorConstraint) + { + output.Write(".ctor "); + } + var constraints = gp.GetConstraints(); + if (constraints.Count > 0) + { + output.Write('('); + for (int j = 0; j < constraints.Count; j++) + { + if (j > 0) + output.Write(", "); + var constraint = metadata.GetGenericParameterConstraint(constraints[j]); + constraint.Type.WriteTo(module, output, context, ILNameSyntax.TypeName); + } + output.Write(") "); + } + if ((gp.Attributes & GenericParameterAttributes.Contravariant) == GenericParameterAttributes.Contravariant) + { + output.Write('-'); + } + else if ((gp.Attributes & GenericParameterAttributes.Covariant) == GenericParameterAttributes.Covariant) + { + output.Write('+'); + } + output.Write(DisassemblerHelpers.Escape(metadata.GetString(gp.Name))); + } + output.Write('>'); + } + } + + private ICollection OnFilter(ICollection items) where T : Adapter + { + return Filter?.Filter(items) ?? items; + } + + void WriteAttributes(PEFile module, CustomAttributeHandleCollection attributes) + { + var metadata = module.Metadata; + foreach (CustomAttributeHandle a in attributes) + { + output.Write(".custom "); + var attr = metadata.GetCustomAttribute(a); + attr.Constructor.WriteTo(module, output, GenericContext.Empty); + if (!attr.Value.IsNil) + { + output.Write(" = "); + WriteBlob(attr.Value, metadata); + } + output.WriteLine(); + } + } + + void WriteBlob(BlobHandle blob, MetadataReader metadata) + { + var reader = metadata.GetBlobReader(blob); + WriteBlob(reader); + } + + void WriteBlob(BlobReader reader) + { + output.Write("("); + output.Indent(); + + for (int i = 0; i < reader.Length; i++) + { + if (i % 16 == 0 && i < reader.Length - 1) + { + output.WriteLine(); + } + else + { + output.Write(' '); + } + output.Write(reader.ReadByte().ToString("x2")); + } + + output.WriteLine(); + output.Unindent(); + output.Write(")"); + } + + void OpenBlock(bool defaultCollapsed) + { + output.MarkFoldStart(defaultCollapsed: !ExpandMemberDefinitions && defaultCollapsed); + output.WriteLine(); + output.WriteLine("{"); + output.Indent(); + } + + void CloseBlock(string comment = null) + { + output.Unindent(); + output.Write("}"); + if (comment != null) + output.Write(" // " + comment); + output.MarkFoldEnd(); + output.WriteLine(); + } + + void WriteFlags(T flags, EnumNameCollection flagNames) where T : struct + { + long val = Convert.ToInt64(flags); + long tested = 0; + foreach (var pair in flagNames) + { + tested |= pair.Key; + if ((val & pair.Key) != 0 && pair.Value != null) + { + output.Write(pair.Value); + output.Write(' '); + } + } + if ((val & ~tested) != 0) + output.Write("flag({0:x4}) ", val & ~tested); + } + + void WriteEnum(T enumValue, EnumNameCollection enumNames) where T : struct + { + long val = Convert.ToInt64(enumValue); + foreach (var pair in enumNames) + { + if (pair.Key == val) + { + if (pair.Value != null) + { + output.Write(pair.Value); + output.Write(' '); + } + return; + } + } + if (val != 0) + { + output.Write("flag({0:x4})", val); + output.Write(' '); + } + + } + + sealed class EnumNameCollection : IEnumerable> where T : struct + { + List> names = new List>(); + + public void Add(T flag, string name) + { + this.names.Add(new KeyValuePair(Convert.ToInt64(flag), name)); + } + + public IEnumerator> GetEnumerator() + { + return names.GetEnumerator(); + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return names.GetEnumerator(); + } + } + + public void DisassembleNamespace(string nameSpace, PEFile module, IEnumerable types) + { + if (!string.IsNullOrEmpty(nameSpace)) + { + output.Write(".namespace " + DisassemblerHelpers.Escape(nameSpace)); + OpenBlock(false); + } + bool oldIsInType = isInType; + isInType = true; + foreach (var td in types) + { + cancellationToken.ThrowIfCancellationRequested(); + DisassembleType(module, td); + output.WriteLine(); + } + if (!string.IsNullOrEmpty(nameSpace)) + { + CloseBlock(); + isInType = oldIsInType; + } + } + + public void WriteAssemblyHeader(PEFile module) + { + var metadata = module.Metadata; + if (!metadata.IsAssembly) + return; + output.Write(".assembly "); + var asm = metadata.GetAssemblyDefinition(); + if ((asm.Flags & AssemblyFlags.WindowsRuntime) == AssemblyFlags.WindowsRuntime) + output.Write("windowsruntime "); + output.Write(DisassemblerHelpers.Escape(metadata.GetString(asm.Name))); + OpenBlock(false); + WriteAttributes(module, asm.GetCustomAttributes()); + WriteSecurityDeclarations(module, asm.GetDeclarativeSecurityAttributes()); + if (!asm.PublicKey.IsNil) + { + output.Write(".publickey = "); + WriteBlob(asm.PublicKey, metadata); + output.WriteLine(); + } + if (asm.HashAlgorithm != AssemblyHashAlgorithm.None) + { + output.Write(".hash algorithm 0x{0:x8}", (int)asm.HashAlgorithm); + if (asm.HashAlgorithm == AssemblyHashAlgorithm.Sha1) + output.Write(" // SHA1"); + output.WriteLine(); + } + Version v = asm.Version; + if (v != null) + { + output.WriteLine(".ver {0}:{1}:{2}:{3}", v.Major, v.Minor, v.Build, v.Revision); + } + CloseBlock(); + } + + public void WriteAssemblyReferences(MetadataReader metadata) + { + foreach (var m in metadata.GetModuleReferences()) + { + var mref = metadata.GetModuleReference(m); + output.WriteLine(".module extern {0}", DisassemblerHelpers.Escape(metadata.GetString(mref.Name))); + } + foreach (var a in metadata.AssemblyReferences) + { + var aref = metadata.GetAssemblyReference(a); + output.Write(".assembly extern "); + if ((aref.Flags & AssemblyFlags.WindowsRuntime) == AssemblyFlags.WindowsRuntime) + output.Write("windowsruntime "); + output.Write(DisassemblerHelpers.Escape(metadata.GetString(aref.Name))); + OpenBlock(false); + if (!aref.PublicKeyOrToken.IsNil) + { + output.Write(".publickeytoken = "); + WriteBlob(aref.PublicKeyOrToken, metadata); + output.WriteLine(); + } + if (aref.Version != null) + { + output.WriteLine(".ver {0}:{1}:{2}:{3}", aref.Version.Major, aref.Version.Minor, aref.Version.Build, aref.Version.Revision); + } + CloseBlock(); + } + } + + public void WriteModuleHeader(PEFile module, bool skipMVID = false) + { + var metadata = module.Metadata; + + void WriteExportedType(ExportedType exportedType) + { + if (!exportedType.Namespace.IsNil) + { + output.Write(DisassemblerHelpers.Escape(metadata.GetString(exportedType.Namespace))); + output.Write('.'); + } + output.Write(DisassemblerHelpers.Escape(metadata.GetString(exportedType.Name))); + } + + foreach (var et in metadata.ExportedTypes) + { + var exportedType = metadata.GetExportedType(et); + output.Write(".class extern "); + if (exportedType.IsForwarder) + output.Write("forwarder "); + WriteExportedType(exportedType); + OpenBlock(false); + switch (exportedType.Implementation.Kind) + { + case HandleKind.AssemblyFile: + var file = metadata.GetAssemblyFile((AssemblyFileHandle)exportedType.Implementation); + output.WriteLine(".file {0}", metadata.GetString(file.Name)); + int typeDefId = exportedType.GetTypeDefinitionId(); + if (typeDefId != 0) + output.WriteLine(".class 0x{0:x8}", typeDefId); + break; + case HandleKind.ExportedType: + output.Write(".class extern "); + var declaringType = metadata.GetExportedType((ExportedTypeHandle)exportedType.Implementation); + while (true) + { + WriteExportedType(declaringType); + if (declaringType.Implementation.Kind == HandleKind.ExportedType) + { + declaringType = metadata.GetExportedType((ExportedTypeHandle)declaringType.Implementation); + } + else + { + break; + } + } + output.WriteLine(); + break; + case HandleKind.AssemblyReference: + output.Write(".assembly extern "); + var reference = metadata.GetAssemblyReference((AssemblyReferenceHandle)exportedType.Implementation); + output.Write(DisassemblerHelpers.Escape(metadata.GetString(reference.Name))); + output.WriteLine(); + break; + default: + throw new BadImageFormatException("Implementation must either be an index into the File, ExportedType or AssemblyRef table."); + } + CloseBlock(); + } + var moduleDefinition = metadata.GetModuleDefinition(); + + output.WriteLine(".module {0}", metadata.GetString(moduleDefinition.Name)); + if (!skipMVID) + { + output.WriteLine("// MVID: {0}", metadata.GetGuid(moduleDefinition.Mvid).ToString("B").ToUpperInvariant()); + } + + var headers = module.Reader.PEHeaders; + output.WriteLine(".imagebase 0x{0:x8}", headers.PEHeader.ImageBase); + output.WriteLine(".file alignment 0x{0:x8}", headers.PEHeader.FileAlignment); + output.WriteLine(".stackreserve 0x{0:x8}", headers.PEHeader.SizeOfStackReserve); + output.WriteLine(".subsystem 0x{0:x} // {1}", headers.PEHeader.Subsystem, headers.PEHeader.Subsystem.ToString()); + output.WriteLine(".corflags 0x{0:x} // {1}", headers.CorHeader.Flags, headers.CorHeader.Flags.ToString()); + + WriteAttributes(module, metadata.GetCustomAttributes(EntityHandle.ModuleDefinition)); + } + + public void WriteModuleContents(PEFile module) + { + foreach (var handle in OnFilter(module.GetTopLevelTypeDefinitions())) + { + DisassembleType(handle); + output.WriteLine(); + } + } + } +} \ No newline at end of file diff --git a/src/Verify.ICSharpCode.Decompiler/Import/TextOutputWithRollback.cs b/src/Verify.ICSharpCode.Decompiler/Import/TextOutputWithRollback.cs new file mode 100644 index 0000000..baebd21 --- /dev/null +++ b/src/Verify.ICSharpCode.Decompiler/Import/TextOutputWithRollback.cs @@ -0,0 +1,106 @@ +// ReSharper disable All +#nullable disable + +using System; +using System.Collections.Generic; +using System.Reflection.Metadata; +using System.Text; +using ICSharpCode.Decompiler.Disassembler; +using ICSharpCode.Decompiler.Metadata; +using ICSharpCode.Decompiler.TypeSystem; +using ICSharpCode.Decompiler; + +namespace ICSharpCode.Decompiler.Disassembler; + +internal class TextOutputWithRollback : ITextOutput +{ + List> actions; + ITextOutput target; + + public TextOutputWithRollback(ITextOutput target) + { + this.target = target; + this.actions = new List>(); + } + + string ITextOutput.IndentationString + { + get + { + return target.IndentationString; + } + set + { + target.IndentationString = value; + } + } + + public void Commit() + { + foreach (var action in actions) + { + action(target); + } + } + + public void Indent() + { + actions.Add(target => target.Indent()); + } + + public void MarkFoldEnd() + { + actions.Add(target => target.MarkFoldEnd()); + } + + public void MarkFoldStart(string collapsedText = "...", bool defaultCollapsed = false) + { + actions.Add(target => target.MarkFoldStart(collapsedText, defaultCollapsed)); + } + + public void Unindent() + { + actions.Add(target => target.Unindent()); + } + + public void Write(char ch) + { + actions.Add(target => target.Write(ch)); + } + + public void Write(string text) + { + actions.Add(target => target.Write(text)); + } + + public void WriteLine() + { + actions.Add(target => target.WriteLine()); + } + + public void WriteLocalReference(string text, object reference, bool isDefinition = false) + { + actions.Add(target => target.WriteLocalReference(text, reference, isDefinition)); + } + + public void WriteReference(OpCodeInfo opCode, bool omitSuffix = false) + { + actions.Add(target => target.WriteReference(opCode)); + } + + public void WriteReference(PEFile module, Handle handle, string text, string protocol = "decompile", bool isDefinition = false) + { + actions.Add(target => target.WriteReference(module, handle, text, protocol, isDefinition)); + } + + public void WriteReference(IType type, string text, bool isDefinition = false) + { + actions.Add(target => target.WriteReference(type, text, isDefinition)); + } + + public void WriteReference(IMember member, string text, bool isDefinition = false) + { + actions.Add(target => target.WriteReference(member, text, isDefinition)); + } +} + diff --git a/src/Verify.ICSharpCode.Decompiler/VerifyICSharpCodeDecompiler.cs b/src/Verify.ICSharpCode.Decompiler/VerifyICSharpCodeDecompiler.cs index 50c83bd..15d5f78 100644 --- a/src/Verify.ICSharpCode.Decompiler/VerifyICSharpCodeDecompiler.cs +++ b/src/Verify.ICSharpCode.Decompiler/VerifyICSharpCodeDecompiler.cs @@ -1,4 +1,5 @@ -using ICSharpCode.Decompiler; +using System.Text.RegularExpressions; +using ICSharpCode.Decompiler; using ICSharpCode.Decompiler.Disassembler; using VerifyTests.ICSharpCode.Decompiler; @@ -6,6 +7,8 @@ namespace VerifyTests; public static class VerifyICSharpCodeDecompiler { + static readonly Regex RvaScrubber = new(@"[ \t]+// Method begins at RVA 0x\w{4}\r?\n"); + public static void Enable() { VerifierSettings.RegisterFileConverter(ConvertTypeDefinitionHandle); @@ -13,30 +16,53 @@ public static void Enable() VerifierSettings.RegisterFileConverter(ConvertPropertyDefinitionHandle); } - static ConversionResult ConvertTypeDefinitionHandle(TypeToDisassemble type, IReadOnlyDictionary _) + public static VerifySettings DontNormalizeIL(this VerifySettings settings) { - PlainTextOutput output = new(); - ReflectionDisassembler disassembler = new(output, default); - disassembler.DisassembleType(type.file, type.type); - return ConversionResult(output); + settings.Context["VerifyICSharpCodeDecompiler.Normalize"] = false; + return settings; } - static ConversionResult ConvertPropertyDefinitionHandle(PropertyToDisassemble property, IReadOnlyDictionary _) + public static SettingsTask DontNormalizeIL(this SettingsTask settings) { - PlainTextOutput output = new(); - ReflectionDisassembler disassembler = new(output, default); - disassembler.DisassembleProperty(property.file, property.Property); - return ConversionResult(output); + settings.CurrentSettings.DontNormalizeIL(); + return settings; } - static ConversionResult ConvertMethodDefinitionHandle(MethodToDisassemble method, IReadOnlyDictionary _) + static bool GetNormalizeIL(this IReadOnlyDictionary context) { - PlainTextOutput output = new(); - ReflectionDisassembler disassembler = new(output, default); - disassembler.DisassembleMethod(method.file, method.method); - return ConversionResult(output); + if (context.TryGetValue("VerifyICSharpCodeDecompiler.Normalize", out var value) && value is bool result) + { + return result; + } + + return true; } - static ConversionResult ConversionResult(PlainTextOutput output) => - new(null,"txt", output.ToString()); + static ConversionResult ConvertTypeDefinitionHandle(TypeToDisassemble type, IReadOnlyDictionary context) => + Convert(context, disassembler => disassembler.DisassembleType(type.file, type.type)); + + static ConversionResult ConvertPropertyDefinitionHandle(PropertyToDisassemble property, IReadOnlyDictionary context) => + Convert(context, disassembler => disassembler.DisassembleProperty(property.file, property.Property)); + + static ConversionResult ConvertMethodDefinitionHandle(MethodToDisassemble method, IReadOnlyDictionary context) => + Convert(context, disassembler => disassembler.DisassembleMethod(method.file, method.method)); + + static ConversionResult Convert(IReadOnlyDictionary context, Action action) + { + PlainTextOutput output = new(); + ReflectionDisassemblerImport disassembler = new(output, default); + var normalizeIl = context.GetNormalizeIL(); + + if (normalizeIl) + disassembler.Filter = new SortByNameFilter(); + + action(disassembler); + + var data = output.ToString(); + + if (normalizeIl) + data = RvaScrubber.Replace(data, string.Empty); + + return new(null, "txt", data); + } } \ No newline at end of file