From 8eae43587a0c2a21833e2bf48d1dfeddf505ed45 Mon Sep 17 00:00:00 2001 From: Paulo Gomes Date: Wed, 31 May 2017 22:35:54 +0100 Subject: [PATCH] Changed int32, string and void to use their CIL counter parts. Performance improvements. +semver: fix --- .gitignore | 4 + dotnet-ildasm.Sample.exe | Bin 3584 -> 4608 bytes dotnet-ildasm.Sample.res | Bin 0 -> 1476 bytes dotnet-ildasm.sln | 8 +- ...ileOutputWriterVSFileStreamOutputWriter.cs | 48 ++++++++ src/dotnet-ildasm.Benchmarks/Program.cs | 12 ++ .../dotnet-ildasm.Benchmarks.csproj | 17 +++ .../Classes/PublicClass.cs | 31 +++++- .../Classes/StaticClass.cs | 17 +++ .../TypeFormatExtensionsShould.cs | 54 +++++++++ .../InstructionProcessorShould.cs | 104 ++++++++++++++++++ .../SampleTests/InstructionProcessorShould.cs | 38 ------- .../SampleTests/MethodProcessorShould.cs | 18 +-- .../Adapters/ConsoleOutputWriter.cs | 4 + .../Adapters/FileOutputWriter.cs | 15 ++- .../Adapters/FileStreamOutputWriter.cs | 35 ++++++ src/dotnet-ildasm/AssemblySectionProcessor.cs | 22 ++-- src/dotnet-ildasm/IOutputWriter.cs | 4 +- .../TypeReferenceExtensions.cs | 11 +- src/dotnet-ildasm/InstructionProcessor.cs | 10 +- src/dotnet-ildasm/MethodProcessor.cs | 20 ++++ src/dotnet-ildasm/Program.cs | 33 +++--- .../Properties/launchSettings.json | 2 +- src/dotnet-ildasm/TypeProcessor.cs | 1 + src/dotnet-ildasm/dotnet-ildasm.csproj | 4 +- 25 files changed, 422 insertions(+), 90 deletions(-) create mode 100644 dotnet-ildasm.Sample.res create mode 100644 src/dotnet-ildasm.Benchmarks/FileOutputWriterVSFileStreamOutputWriter.cs create mode 100644 src/dotnet-ildasm.Benchmarks/Program.cs create mode 100644 src/dotnet-ildasm.Benchmarks/dotnet-ildasm.Benchmarks.csproj create mode 100644 src/dotnet-ildasm.Sample/Classes/StaticClass.cs create mode 100644 src/dotnet-ildasm.Tests/Infrastructure/TypeFormatExtensionsShould.cs create mode 100644 src/dotnet-ildasm.Tests/InstructionProcessorShould.cs delete mode 100644 src/dotnet-ildasm.Tests/SampleTests/InstructionProcessorShould.cs create mode 100644 src/dotnet-ildasm/Adapters/FileStreamOutputWriter.cs rename src/dotnet-ildasm/{ => Infrastructure}/TypeReferenceExtensions.cs (50%) diff --git a/.gitignore b/.gitignore index 1c8e235..752024d 100644 --- a/.gitignore +++ b/.gitignore @@ -263,4 +263,8 @@ __pycache__/ # Ignore IL files *.il +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# Release note ReleaseNotes.md diff --git a/dotnet-ildasm.Sample.exe b/dotnet-ildasm.Sample.exe index 24951a311fbd692835ad1803bf62d4959d5a2200..bc42a63a8c379d1ff162c6483863f99767c302c4 100644 GIT binary patch literal 4608 zcmeHKTWB2D8UD`fMXOc3wqn`%(%7R&233`}R+Jdhs}@)HER}4ir-wLgPY9UkZUHU`#1_D4~Uz7TS`R68g}4OX+hT?0)CW>_wLb z@(}1ljx^u=-~a#TKj;6?IWx28zH|!-07+gC9|CuIS8YV&hr=4pxr6s|c&q(>_gyh{ zzq`0#2WHLp=6$PbmMz!yLbD`If6+B<*PM8!U{<|~%x~M4?TtfEp8%$Wfw}(AK35;@ z4=_ZB=wNVaj7a;f0p3ksBfJ!tywRrSty=w#=Sku|35hWvC3&xdk3B>%PwPFriPWV)Ha^Lbx7UOAq_2-PY#rqtR-fU#Eo zQRJGcE1Q~|ffS0}z(2EqAm^1P0z=0{-+M&cR`Nh5hpty({I z(LY`2o7S}9-VkQ|#~5vmJ2Ywjg}BCbPBz2#!|3W`HKOiOKw#$)1KT)hj@OCA!R!Mg zn;zh({nYJ0BCv;d(oP_uIy2i3Ih$SD@n&bUey12aMHJG;HEpEW~Lw z=J`1K_SL6>vBH@#p(_}z+tQQyBl)2tLxWGNN~YitPcxC8>%d#Q|60j~(6`TBGPSzopugW=g&Q}#$>TH`n2QvM0<5Z}}I4~-e2G>0^v)mYK^b+IaP zc#)XKYZ`wbvW)Z_@ec3*BBqftlyxsrjTem?)olj?W7RlJoj^O6}y2H6ua+skZDCx(b; zh)40A#4z!Plm&iDdD>KSy^UiDHM`52-Q07A{s!h7>?ql?X76G;k>*TSHTyH9X4fN6 ztZ8;5Vmb=9f^P4oqJDK2^?S)w{AS$m70q6V``wOgG5eloukLv}ah#*Sz0UTth~a%b z`Y&Vl*F=_{@5O9q@&fDjtC$TZFQE;;joIa-V731pvl>}Ddb`1PGjl!pWv=o-%wA5a zHGe8%dS)FswvL_B>^9y=9!CeJWA-D>)SejUrWBYW8aS`z3`@lEi3SF>T+lezg5OQH z;|#qziQHktEV?y*TH~O`r!|ghoYZ(hqlH(+c05Npjjw84A$H+KEi3CjC_IQf_Tv>| z0Y4{pKoWQIq~y_~HJ`x;l!x#);^+BR%VQkdiDzlOgMd%nJGesKAF)b&!%*@&8uy6< zLhWea&7{Rlcj>*qTkZO_`dOh-w|mSyseL2fi(z%Vsf*6Ml6!Hsp;KJ7UR1PBli_n- zWzmtxP*@29b*s8R-WM$LAl4mOn4Ull*>=S3UpA<}Py1k!3YFMuR|BOU{ZO z!)=M)G`Ms2{GzXC*o;&+PspI`+cgzyv+*Qzj;FQRIO)!LepQFHod4-H?X#lgaC7&f z9Xhh9j*5Y2N?&1OEUz1Qj>IM34&{{XO3YZ!qAae|WL$dbpOUU*RK>yVpFg^=MeZL#j7zHhY@^Tcvl>KdWqg{}+_+fKy_s`-Ld ztvNDZahwf1rcPU?lGo$a4fPCJAN8FW$DLgl7t@O+$1d}w$O>-hk&kRDP2XOUm8iqG zV+Fz1{@TR-8##`ag3!0hVbo!Ywe~HCM#f?#B5iBPcs|l{WF@kylG)`*K4;l3xN`%1 zkm>R{07~(n(u7^$}-0yB;sfqp2v+)scGPC2N41+5$=%jAz?)Enf8+enHdmiA|2$ ztHmS0#el<1o@{oCG(~@9+zQJJ;E|tgB!OTZTTXbK!qQ-# zwqmZU@z9*GgPLQloaaJfuRzyU2bXUKfmC!Zu}5I%G`Ed4FU^*xBEMb*+6MXY%D+Up&H!e|J^#eA)JLWols#WYPZ8DU;USnKn>&A^ X`S>beM!!*R4S#$?d=mNpw*vnK_T}TM literal 3584 zcmeHJO^g&p6n@<^v&@b%Y+yAY5qe!oK&9np{UJfJKeNCn3nMcuphmW*yVz~%=^nd# z*clCE2}B`!z(~A!;o`xBcrYdsVmuMTg^PF-6JmlV4@OSL1Ag_oduEnhHG1Jrb=w%e3f9#VRUa?q%e z_T&ci8lGW15;SzM$-HGtdYS@>W8}Js`uM-o$CPbEn=tMxvAk$EO!V-ghJR3MMAij-H|?)b$9LQiM{9^_U{k!w1{5&?ol8cDDU8iTphI7`MKjPX-3Z7)uh{ zD5UdRf*|0ce*CRx7OOpl{T8;;)agQ)s zfkz%~X_Jp+QQt!&*ohwcfpM9#8|Qi-nb7n@?xEF;1FXM8zd^G>DdLx6Grq4}gnUK0 z3B1FYR;4_^xP#GRJOPyRz9<26D|8C0lQU(QDPVJ8Sum6z?Bm$h3r!zgj2ZG)`sh;3 zma*rzbd5*pqh8o#mNG`xK(=d{3u>}j$v$UR(xmM@y2`A=Y;NT(W>d_rQ50t}`eG6R|k1Igg!*J%8dc&&KSonn!LQ#%xV$1@`cK%yy+#q52mh#$#sa;yiYV*(YSD z)?&rqblH07dYi4oF3B3N!0C!mrR^-g$>=f$K$RM)Ub>||O#_UP+>P64HuWs(DSbxh zRB8x#8d!wp1$q^jqr<>F8H_gL+l(#7Q;g>rKVsCWOxsDv4u1jsfPM#_reA>X(oJAM z*MY~_YLW%{bs7hL3Qc-0Il)*Ww?Sb0E{2@^{-_q1p6}RIvb@k0;YQoBjG(5MjauCi zx@d|;4YYm2vl@=rLHnw22{WW}D+oo6N{y;xnvc2q{5Vi<>^%35qKs-fqGZV8hr*|N zB5hs}TbcFRenH1^%K}IC>hcI^!mwS!E(g@479I5MQ0%c?L5FZL$hLYn3B36n@<^i+ zdV^eOLfkpBUAk<3|Lc!q#V=?6`0Os_&QSL2cixy>HFW!2N+Tt$DM~)0sY*VZf%teH zuM3ILWV7-fOM~ato~(4tW|jC8XpOuDD(a&LeWSkDbH|&e;N7px_}*-QcNaaT?;WqS zkL5G**1m){_H&5L_qR<2JE%KGYcFcJT*hc^iw56S3iU5`zu*mv7g<468`DH9aSN9W z^O!L$w63FwZv=&wY{n-7{-OXTT$VN*+Rzrsm9p01-BR*hMRa7$^T(Tl_k}M(A#}@; f-yZnlUPsz6OAidR=-X(Hzlk1L7XMHDfGY4O0*Fes diff --git a/dotnet-ildasm.Sample.res b/dotnet-ildasm.Sample.res new file mode 100644 index 0000000000000000000000000000000000000000..afec77849f3eb2c51e9c7ebfb61134512c7f10c0 GIT binary patch literal 1476 zcmb7D%Wl&^6uq<&LX|49010*@iv^oF33O3aNkas!6jW%`mMqwysXcM6+GFDxNAX{= zV22RD!8fq!9}vz=;>1Ql3S)UPb06oNdv8oc#PH7Nix{gcOkgrlbia zHsqPjx`LO(s^I6O5trb$SYH}HcMRht>=`_gf(=MtZA_cW=YXzpCVEAWkoy{VF&=?z z4Hlvics#nr9>B_=Q^4+7hP0yKEj?c4r&eQuj&O@*^!Y&6Ko!`Vwmu(T1-}c$CN8x# zo7fABRd(TDjQ@w(rU%ehVveCB1N*vv9q4GUUZdeD;>`rKjg`Pl7H zV}c(2x{rG5SbukR-B+SRJA(oGuAo20KQBV{4Bt|H8tWW06QW=Ed>DS`^Cug>%e!ws zy8T%kv8l*Zn8=>faoY|P(o6hM20iD))oJt4VM=r9^C*#`=M+LY{p0<;{k<+%O2pSu z!2lsu&&e}+q`VsubJdJPFH2N1(M>OjkGP86X~$tPm*GSxJ+ATrjIkBi&=*pNx~OS# z+eVkZv-cdXhH{$Rip*hoIPg57(8dXmlvoyJL0TKs Vl+s%@?cLU@2?*%6$_X74{Q(>mH%tHk literal 0 HcmV?d00001 diff --git a/dotnet-ildasm.sln b/dotnet-ildasm.sln index 0f27311..d80a669 100644 --- a/dotnet-ildasm.sln +++ b/dotnet-ildasm.sln @@ -7,16 +7,18 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-ildasm", "src\dotnet EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution items", "Solution items", "{98ACE31A-8E21-4953-A55E-E62C6E10921B}" ProjectSection(SolutionItems) = preProject + .travis.yml = .travis.yml appveyor.yml = appveyor.yml GitVersion.yml = GitVersion.yml README.md = README.md - .travis.yml = .travis.yml EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-ildasm.Tests", "src\dotnet-ildasm.Tests\dotnet-ildasm.Tests.csproj", "{F587A58E-9397-4F75-BCA5-CA92576ECCB6}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-ildasm.Sample", "src\dotnet-ildasm.Sample\dotnet-ildasm.Sample.csproj", "{AAEC7394-13DC-4FD3-96C3-3B2A6E481D2C}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "dotnet-ildasm.Benchmarks", "src\dotnet-ildasm.Benchmarks\dotnet-ildasm.Benchmarks.csproj", "{A05E1F00-989B-47C3-BB4D-0EAF53AF959E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -35,6 +37,10 @@ Global {AAEC7394-13DC-4FD3-96C3-3B2A6E481D2C}.Debug|Any CPU.Build.0 = Debug|Any CPU {AAEC7394-13DC-4FD3-96C3-3B2A6E481D2C}.Release|Any CPU.ActiveCfg = Release|Any CPU {AAEC7394-13DC-4FD3-96C3-3B2A6E481D2C}.Release|Any CPU.Build.0 = Release|Any CPU + {A05E1F00-989B-47C3-BB4D-0EAF53AF959E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A05E1F00-989B-47C3-BB4D-0EAF53AF959E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A05E1F00-989B-47C3-BB4D-0EAF53AF959E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A05E1F00-989B-47C3-BB4D-0EAF53AF959E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/dotnet-ildasm.Benchmarks/FileOutputWriterVSFileStreamOutputWriter.cs b/src/dotnet-ildasm.Benchmarks/FileOutputWriterVSFileStreamOutputWriter.cs new file mode 100644 index 0000000..fae6324 --- /dev/null +++ b/src/dotnet-ildasm.Benchmarks/FileOutputWriterVSFileStreamOutputWriter.cs @@ -0,0 +1,48 @@ +using System; +using System.Linq; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Attributes.Jobs; +using BenchmarkDotNet.Running; +using DotNet.Ildasm; +using DotNet.Ildasm.Adapters; +using Mono.Cecil; + +namespace dotnet_ildasm.Benchmarks +{ + [CoreJob][ClrJob] + public class FileOutputWriterVSFileStreamOutputWriter + { + internal static readonly Lazy SampleAssembly = new Lazy(() => + Mono.Cecil.AssemblyDefinition.ReadAssembly("c:\\git\\dotnet-ildasm\\src\\dotnet-ildasm.Sample\\bin\\Debug\\net45\\dotnet-ildasm.Sample.exe")); + + private static MethodDefinition _methodDefinition; + private static readonly IndentationProvider IndentationProvider = new IndentationProvider(); + private static readonly string TargetILFile = "C:\\git\\dotnet-ildasm\\dotnet-ildasm.Sample.il"; + + static FileOutputWriterVSFileStreamOutputWriter() + { + var type = SampleAssembly.Value.MainModule.Types.FirstOrDefault(x => x.Name == "PublicClass"); + _methodDefinition = type.Methods.FirstOrDefault(x => x.Name == "UsingTryCatch"); + } + + [Benchmark] + public void FileStreamOutputWriter() + { + using (var fileStreamOutputWriter = new FileStreamOutputWriter(IndentationProvider, TargetILFile)) + { + var processor = new MethodProcessor(fileStreamOutputWriter); + processor.WriteBody(_methodDefinition); + } + } + + [Benchmark] + public void FileOutputWriter() + { + using (var fileOutputWriter = new FileOutputWriter(IndentationProvider, TargetILFile)) + { + var processor = new MethodProcessor(fileOutputWriter); + processor.WriteBody(_methodDefinition); + } + } + } +} \ No newline at end of file diff --git a/src/dotnet-ildasm.Benchmarks/Program.cs b/src/dotnet-ildasm.Benchmarks/Program.cs new file mode 100644 index 0000000..281281b --- /dev/null +++ b/src/dotnet-ildasm.Benchmarks/Program.cs @@ -0,0 +1,12 @@ +using BenchmarkDotNet.Running; + +namespace dotnet_ildasm.Benchmarks +{ + class Program + { + static void Main(string[] args) + { + BenchmarkRunner.Run(); + } + } +} \ No newline at end of file diff --git a/src/dotnet-ildasm.Benchmarks/dotnet-ildasm.Benchmarks.csproj b/src/dotnet-ildasm.Benchmarks/dotnet-ildasm.Benchmarks.csproj new file mode 100644 index 0000000..1d58887 --- /dev/null +++ b/src/dotnet-ildasm.Benchmarks/dotnet-ildasm.Benchmarks.csproj @@ -0,0 +1,17 @@ + + + + Exe + netcoreapp1.1;net46 + + + + + + + + + + + + \ No newline at end of file diff --git a/src/dotnet-ildasm.Sample/Classes/PublicClass.cs b/src/dotnet-ildasm.Sample/Classes/PublicClass.cs index 50e9076..29fbafe 100644 --- a/src/dotnet-ildasm.Sample/Classes/PublicClass.cs +++ b/src/dotnet-ildasm.Sample/Classes/PublicClass.cs @@ -1,9 +1,12 @@ -namespace dotnet_ildasm.Sample.Classes +using System; + +namespace dotnet_ildasm.Sample.Classes { public class PublicClass { public void PublicVoidMethod() { + PublicVoidMethodSingleParameter(null); } public void PublicVoidMethodSingleParameter(string parameter1) @@ -13,9 +16,31 @@ public void PublicVoidMethodSingleParameter(string parameter1) public void PublicVoidMethodTwoParameters(string parameter1, int parameter2) { } - - public void PublicVoidMethodParams(params string [] parameters) + + public void PublicVoidMethodParams(params string[] parameters) + { + } + + public void UsingIF(int parameter) + { + if (parameter > 10) + { + } + } + + public void UsingTryCatch(int parameter) { + try + { + Console.WriteLine(parameter); + } + catch (Exception e) + { + Console.WriteLine(e); + throw; + } } + + public string Property1 { get; set; } } } diff --git a/src/dotnet-ildasm.Sample/Classes/StaticClass.cs b/src/dotnet-ildasm.Sample/Classes/StaticClass.cs new file mode 100644 index 0000000..dc8d45c --- /dev/null +++ b/src/dotnet-ildasm.Sample/Classes/StaticClass.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace dotnet_ildasm.Sample.Classes +{ + public static class StaticClass + { + public static void Method1() + { + Method2(); + } + public static void Method2() + { + } + } +} diff --git a/src/dotnet-ildasm.Tests/Infrastructure/TypeFormatExtensionsShould.cs b/src/dotnet-ildasm.Tests/Infrastructure/TypeFormatExtensionsShould.cs new file mode 100644 index 0000000..8480738 --- /dev/null +++ b/src/dotnet-ildasm.Tests/Infrastructure/TypeFormatExtensionsShould.cs @@ -0,0 +1,54 @@ +using System.Linq; +using DotNet.Ildasm.Infrastructure; +using Mono.Cecil; +using Xunit; + +namespace DotNet.Ildasm.Tests.Infrastructure +{ + public class TypeFormatExtensionsShould + { + [Fact] + public void Return_IL_Type_For_Void() + { + var type = DataHelper.SampleAssembly.Value.Modules.First().Types.First(x => x.Name == "Program"); + var methoDefinition = type.Methods.First(x => x.Name == "Main"); + + var actual = methoDefinition.ReturnType.ToILType(); + + Assert.Equal("void", actual); + } + + [Fact] + public void Return_IL_Type_For_String() + { + var type = DataHelper.SampleAssembly.Value.Modules.First().Types.First(x => x.Name == "PublicClass"); + var propertyDefinition = type.Properties.First(x => x.Name == "Property1"); + + var actual = propertyDefinition.GetMethod.ReturnType.ToILType(); + + Assert.Equal("string", actual); + } + + [Fact] + public void Return_IL_Type_For_StringArray() + { + var type = DataHelper.SampleAssembly.Value.Modules.First().Types.First(x => x.Name == "PublicClass"); + var methodDefinition = type.Methods.First(x => x.Name == "PublicVoidMethodParams"); + + var actual = methodDefinition.Parameters.First().ParameterType.ToILType(); + + Assert.Equal("string[]", actual); + } + + [Fact] + public void Return_IL_Type_For_Int32() + { + var type = DataHelper.SampleAssembly.Value.Modules.First().Types.First(x => x.Name == "PublicClass"); + var methodDefinition = type.Methods.First(x => x.Name == "UsingIF"); + + var actual = methodDefinition.Parameters.First().ParameterType.ToILType(); + + Assert.Equal("int", actual); + } + } +} diff --git a/src/dotnet-ildasm.Tests/InstructionProcessorShould.cs b/src/dotnet-ildasm.Tests/InstructionProcessorShould.cs new file mode 100644 index 0000000..d2a60d8 --- /dev/null +++ b/src/dotnet-ildasm.Tests/InstructionProcessorShould.cs @@ -0,0 +1,104 @@ +using System.Linq; +using Mono.Cecil.Cil; +using NSubstitute; +using Xunit; + +namespace DotNet.Ildasm.Tests +{ + public class InstructionProcessorShould + { + private readonly IOutputWriter _outputWriter; + + public InstructionProcessorShould() + { + _outputWriter = Substitute.For(); + } + + [Fact] + public void Be_Able_To_Write_Literal_String() + { + var instruction = Instruction.Create(OpCodes.Ldstr, "some string"); + var instructionProcessor = new InstructionProcessor(_outputWriter); + + instructionProcessor.WriteInstruction(instruction); + + _outputWriter.Received(1).WriteLine("IL_0000: ldstr \"some string\""); + } + + [Fact] + public void Be_Able_To_Write_Literal_Integer32() + { + var instruction = Instruction.Create(OpCodes.Ldc_I4, 356); + var instructionProcessor = new InstructionProcessor(_outputWriter); + + instructionProcessor.WriteInstruction(instruction); + + _outputWriter.Received(1).WriteLine("IL_0000: ldc.i4 356"); + } + + [Fact] + public void Be_Able_To_Write_IF_statement() + { + var instructionTarget = Instruction.Create(OpCodes.Nop); + instructionTarget.Offset = 1; + var instruction = Instruction.Create(OpCodes.Brfalse_S, instructionTarget); + var instructionProcessor = new InstructionProcessor(_outputWriter); + + instructionProcessor.WriteInstruction(instruction); + + _outputWriter.Received(1).WriteLine("IL_0000: brfalse.s IL_0001"); + } + + [Fact] + public void Be_Able_To_Write_Field_Reference() + { + var type = DataHelper.SampleAssembly.Value.Modules.First().Types.First(x => x.Name == "PublicStruct"); + var fieldDefinition = type.Fields.First(x => x.Name == "X"); + var instruction = Instruction.Create(OpCodes.Stfld, fieldDefinition); + var instructionProcessor = new InstructionProcessor(_outputWriter); + + instructionProcessor.WriteInstruction(instruction); + + _outputWriter.Received(1).WriteLine("IL_0000: stfld int dotnet_ildasm.Sample.Structs.PublicStruct::X"); + } + + [Fact] + public void Be_Able_To_Write_Instance_Method_Call() + { + var type = DataHelper.SampleAssembly.Value.Modules.First().Types.First(x => x.Name == "PublicClass"); + var methoDefinition = type.Methods.First(x => x.Name == "PublicVoidMethodSingleParameter"); + var instruction = Instruction.Create(OpCodes.Call, methoDefinition); + var instructionProcessor = new InstructionProcessor(_outputWriter); + + instructionProcessor.WriteInstruction(instruction); + + _outputWriter.Received(1).WriteLine("IL_0000: call instance void dotnet_ildasm.Sample.Classes.PublicClass::PublicVoidMethodSingleParameter(string)"); + } + + [Fact] + public void Be_Able_To_Write_Instance_Property_Reference() + { + var type = DataHelper.SampleAssembly.Value.Modules.First().Types.First(x => x.Name == "PublicClass"); + var propertyDefinition = type.Properties.First(x => x.Name == "Property1"); + var instruction = Instruction.Create(OpCodes.Call, propertyDefinition.GetMethod); + var instructionProcessor = new InstructionProcessor(_outputWriter); + + instructionProcessor.WriteInstruction(instruction); + + _outputWriter.Received(1).WriteLine("IL_0000: call instance string dotnet_ildasm.Sample.Classes.PublicClass::get_Property1()"); + } + + [Fact] + public void Be_Able_To_Write_Static_Method_Call() + { + var type = DataHelper.SampleAssembly.Value.Modules.First().Types.First(x => x.Name == "StaticClass"); + var methoDefinition = type.Methods.First(x => x.Name == "Method2"); + var instruction = Instruction.Create(OpCodes.Call, methoDefinition); + var instructionProcessor = new InstructionProcessor(_outputWriter); + + instructionProcessor.WriteInstruction(instruction); + + _outputWriter.Received(1).WriteLine("IL_0000: call void dotnet_ildasm.Sample.Classes.StaticClass::Method2()"); + } + } +} diff --git a/src/dotnet-ildasm.Tests/SampleTests/InstructionProcessorShould.cs b/src/dotnet-ildasm.Tests/SampleTests/InstructionProcessorShould.cs deleted file mode 100644 index 0aea4c9..0000000 --- a/src/dotnet-ildasm.Tests/SampleTests/InstructionProcessorShould.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Mono.Cecil.Cil; -using NSubstitute; -using Xunit; - -namespace DotNet.Ildasm.Tests.SampleTests -{ - public class InstructionProcessorShould - { - private readonly IOutputWriter _outputWriter; - - public InstructionProcessorShould() - { - _outputWriter = Substitute.For(); - } - - [Fact] - public void Be_Able_To_Write_Literal_String() - { - var instruction = Instruction.Create(OpCodes.Ldstr, "some string"); - var instructionProcessor = new InstructionProcessor(_outputWriter); - - instructionProcessor.WriteInstruction(instruction); - - _outputWriter.Received(1).WriteLine("IL_0000: ldstr \"some string\""); - } - - [Fact] - public void Be_Able_To_Write_Literal_Integer32() - { - var instruction = Instruction.Create(OpCodes.Ldc_I4, 356); - var instructionProcessor = new InstructionProcessor(_outputWriter); - - instructionProcessor.WriteInstruction(instruction); - - _outputWriter.Received(1).WriteLine("IL_0000: ldc.i4 356"); - } - } -} diff --git a/src/dotnet-ildasm.Tests/SampleTests/MethodProcessorShould.cs b/src/dotnet-ildasm.Tests/SampleTests/MethodProcessorShould.cs index 86e6c1e..a8c7751 100644 --- a/src/dotnet-ildasm.Tests/SampleTests/MethodProcessorShould.cs +++ b/src/dotnet-ildasm.Tests/SampleTests/MethodProcessorShould.cs @@ -17,15 +17,15 @@ public MethodProcessorShould() } [Theory] - [InlineData("PublicClass", "PublicVoidMethod", ".method public hidebysig instance [System.Runtime]System.Void PublicVoidMethod() cil managed")] - [InlineData("PublicClass", "PublicVoidMethodSingleParameter", ".method public hidebysig instance [System.Runtime]System.Void PublicVoidMethodSingleParameter([System.Runtime]System.String parameter1) cil managed")] - [InlineData("PublicClass", "PublicVoidMethodTwoParameters", ".method public hidebysig instance [System.Runtime]System.Void PublicVoidMethodTwoParameters([System.Runtime]System.String parameter1, [System.Runtime]System.Int32 parameter2) cil managed")] - [InlineData("PublicClass", "PublicVoidMethodParams", ".method public hidebysig instance [System.Runtime]System.Void PublicVoidMethodParams([System.Runtime]System.String[] parameters) cil managed")] - [InlineData("PublicAbstractClass", "PublicAbstractMethod", ".method public hidebysig newslot abstract virtual instance [System.Runtime]System.Void PublicAbstractMethod() cil managed")] - [InlineData("PublicAbstractClass", "PublicImplementedMethod", ".method public hidebysig instance [System.Runtime]System.Void PublicImplementedMethod() cil managed")] - [InlineData("DerivedPublicClass", "PublicAbstractMethod", ".method public hidebysig virtual instance [System.Runtime]System.Void PublicAbstractMethod() cil managed")] - [InlineData("DerivedPublicClass", "PublicAbstractSealedMethod", ".method public hidebysig virtual final instance [System.Runtime]System.Void PublicAbstractSealedMethod() cil managed")] - [InlineData("DerivedPublicClass", "PublicImplementedMethod", ".method public hidebysig instance [System.Runtime]System.Void PublicImplementedMethod() cil managed")] + [InlineData("PublicClass", "PublicVoidMethod", ".method public hidebysig instance void PublicVoidMethod() cil managed")] + [InlineData("PublicClass", "PublicVoidMethodSingleParameter", ".method public hidebysig instance void PublicVoidMethodSingleParameter(string parameter1) cil managed")] + [InlineData("PublicClass", "PublicVoidMethodTwoParameters", ".method public hidebysig instance void PublicVoidMethodTwoParameters(string parameter1, int parameter2) cil managed")] + [InlineData("PublicClass", "PublicVoidMethodParams", ".method public hidebysig instance void PublicVoidMethodParams(string[] parameters) cil managed")] + [InlineData("PublicAbstractClass", "PublicAbstractMethod", ".method public hidebysig newslot abstract virtual instance void PublicAbstractMethod() cil managed")] + [InlineData("PublicAbstractClass", "PublicImplementedMethod", ".method public hidebysig instance void PublicImplementedMethod() cil managed")] + [InlineData("DerivedPublicClass", "PublicAbstractMethod", ".method public hidebysig virtual instance void PublicAbstractMethod() cil managed")] + [InlineData("DerivedPublicClass", "PublicAbstractSealedMethod", ".method public hidebysig virtual final instance void PublicAbstractSealedMethod() cil managed")] + [InlineData("DerivedPublicClass", "PublicImplementedMethod", ".method public hidebysig instance void PublicImplementedMethod() cil managed")] public void Write_Method_Signature(string className, string methodName, string expectedIL) { var type = _assemblyDefinition.MainModule.Types.FirstOrDefault(x => x.Name == className); diff --git a/src/dotnet-ildasm/Adapters/ConsoleOutputWriter.cs b/src/dotnet-ildasm/Adapters/ConsoleOutputWriter.cs index 1bd2614..ddd12f4 100644 --- a/src/dotnet-ildasm/Adapters/ConsoleOutputWriter.cs +++ b/src/dotnet-ildasm/Adapters/ConsoleOutputWriter.cs @@ -20,5 +20,9 @@ public void WriteLine(string value) { Console.WriteLine(_indentationProvider.Apply(value)); } + + public void Dispose() + { + } } } \ No newline at end of file diff --git a/src/dotnet-ildasm/Adapters/FileOutputWriter.cs b/src/dotnet-ildasm/Adapters/FileOutputWriter.cs index fefe42a..eeb498f 100644 --- a/src/dotnet-ildasm/Adapters/FileOutputWriter.cs +++ b/src/dotnet-ildasm/Adapters/FileOutputWriter.cs @@ -1,27 +1,36 @@ using System; using System.IO; +using System.Text; namespace DotNet.Ildasm.Adapters { public sealed class FileOutputWriter : IOutputWriter { + private readonly StringBuilder _stringBuilder; private readonly IndentationProvider _indentationProvider; private readonly string _filePath; - internal FileOutputWriter(IndentationProvider indentationProvider, string filePath) + public FileOutputWriter(IndentationProvider indentationProvider, string filePath) { + _stringBuilder = new StringBuilder(); _indentationProvider = indentationProvider; this._filePath = filePath; } public void Write(string value) { - File.AppendAllText(_filePath, value); + _stringBuilder.Append(value); } public void WriteLine(string value = "") { - File.AppendAllText(_filePath, $"{_indentationProvider.Apply(value)}{Environment.NewLine}"); + _stringBuilder.AppendLine($"{_indentationProvider.Apply(value)}{Environment.NewLine}"); + } + + public void Dispose() + { + File.WriteAllText(_filePath, _stringBuilder.ToString()); + _stringBuilder.Clear(); } } } \ No newline at end of file diff --git a/src/dotnet-ildasm/Adapters/FileStreamOutputWriter.cs b/src/dotnet-ildasm/Adapters/FileStreamOutputWriter.cs new file mode 100644 index 0000000..1427612 --- /dev/null +++ b/src/dotnet-ildasm/Adapters/FileStreamOutputWriter.cs @@ -0,0 +1,35 @@ +using System; +using System.IO; +using System.Text; + +namespace DotNet.Ildasm.Adapters +{ + public sealed class FileStreamOutputWriter : IOutputWriter, IDisposable + { + FileStream stream; + private readonly IndentationProvider _indentationProvider; + + public FileStreamOutputWriter(IndentationProvider indentationProvider, string filePath) + { + stream = File.Open(filePath, FileMode.OpenOrCreate); + _indentationProvider = indentationProvider; + } + + public void Dispose() + { + stream?.Dispose(); + } + + public void Write(string value) + { + byte[] bytes = Encoding.ASCII.GetBytes(value); + stream.Write(bytes, 0, bytes.Length); + } + + public void WriteLine(string value = "") + { + byte[] bytes = Encoding.ASCII.GetBytes($"{_indentationProvider.Apply(value)}{Environment.NewLine}"); + stream.Write(bytes, 0, bytes.Length); + } + } +} \ No newline at end of file diff --git a/src/dotnet-ildasm/AssemblySectionProcessor.cs b/src/dotnet-ildasm/AssemblySectionProcessor.cs index 7de31b4..a8cd456 100644 --- a/src/dotnet-ildasm/AssemblySectionProcessor.cs +++ b/src/dotnet-ildasm/AssemblySectionProcessor.cs @@ -29,7 +29,7 @@ public void WriteBody(AssemblyDefinition assembly) StringComparison.CurrentCultureIgnoreCase) == 0) continue; - _outputWriter.WriteLine(GetCustomAttribute(customAttribute)); + WriteCustomAttribute(customAttribute); } _outputWriter.WriteLine($".hash algorithm 0x{assembly.Name.HashAlgorithm.ToString("X")}"); @@ -38,27 +38,23 @@ public void WriteBody(AssemblyDefinition assembly) _outputWriter.WriteLine("}"); } - public string GetCustomAttribute(CustomAttribute customAttribute) + public void WriteCustomAttribute(CustomAttribute customAttribute) { - return - $".custom instance void {GetFullTypeName(customAttribute.AttributeType)}::{customAttribute.Constructor.Name}" + - $"{GetConstructorArguments(customAttribute)}"; + _outputWriter.Write( + $".custom instance void {GetFullTypeName(customAttribute.AttributeType)}::{customAttribute.Constructor.Name}"); + WriteConstructorArguments(customAttribute); } - private string GetConstructorArguments(CustomAttribute customAttribute) + private void WriteConstructorArguments(CustomAttribute customAttribute) { - StringBuilder builder = new StringBuilder(); - var argument = customAttribute.ConstructorArguments.FirstOrDefault(); if (!customAttribute.HasConstructorArguments) - builder.Append("()"); + _outputWriter.Write("()"); else - builder.Append($"({argument.Type.MetadataType.ToString().ToLowerInvariant()})"); - - builder.Append($" = ( {BitConverter.ToString(customAttribute.GetBlob()).Replace("-", " ")} )"); + _outputWriter.Write($"({argument.Type.MetadataType.ToString().ToLowerInvariant()})"); - return builder.ToString(); + _outputWriter.WriteLine($"= ( {BitConverter.ToString(customAttribute.GetBlob()).Replace("-", " ")} )"); } public string GetFullTypeName(TypeReference typeReference) diff --git a/src/dotnet-ildasm/IOutputWriter.cs b/src/dotnet-ildasm/IOutputWriter.cs index e64f189..2d614a7 100644 --- a/src/dotnet-ildasm/IOutputWriter.cs +++ b/src/dotnet-ildasm/IOutputWriter.cs @@ -1,6 +1,8 @@ +using System; + namespace DotNet.Ildasm { - public interface IOutputWriter + public interface IOutputWriter : IDisposable { void Write(string value); void WriteLine(string value = ""); diff --git a/src/dotnet-ildasm/TypeReferenceExtensions.cs b/src/dotnet-ildasm/Infrastructure/TypeReferenceExtensions.cs similarity index 50% rename from src/dotnet-ildasm/TypeReferenceExtensions.cs rename to src/dotnet-ildasm/Infrastructure/TypeReferenceExtensions.cs index 0be77dc..823851d 100644 --- a/src/dotnet-ildasm/TypeReferenceExtensions.cs +++ b/src/dotnet-ildasm/Infrastructure/TypeReferenceExtensions.cs @@ -1,12 +1,21 @@ using System; using Mono.Cecil; -namespace DotNet.Ildasm +namespace DotNet.Ildasm.Infrastructure { public static class TypeReferenceExtensions { public static string ToILType(this TypeReference typeReference) { + if (typeReference.MetadataType == MetadataType.Void) + return "void"; + if (typeReference.MetadataType == MetadataType.String) + return "string"; + if (typeReference.MetadataType == MetadataType.Int32) + return "int"; + if (typeReference.MetadataType == MetadataType.Array) + return $"{typeReference.GetElementType().ToILType()}[]"; + if (string.Compare(typeReference.Scope.Name, typeReference.Module.Name, StringComparison.CurrentCultureIgnoreCase) == 0) return $"{typeReference.FullName}"; diff --git a/src/dotnet-ildasm/InstructionProcessor.cs b/src/dotnet-ildasm/InstructionProcessor.cs index 278cba5..7a18252 100644 --- a/src/dotnet-ildasm/InstructionProcessor.cs +++ b/src/dotnet-ildasm/InstructionProcessor.cs @@ -1,6 +1,7 @@ using System; using System.Text; using DotNet.Ildasm; +using DotNet.Ildasm.Infrastructure; using Mono.Cecil; using Mono.Cecil.Cil; @@ -47,8 +48,12 @@ private string GetOperandIL(Instruction instruction) { switch (instruction.Operand) { + case Instruction subInstruction: + builder.Append($"IL_{subInstruction.Offset:x4}"); + break; case MethodReference methodReference: - builder.Append($"{methodReference.ReturnType.ToILType()} {methodReference.DeclaringType.ToILType()}::{methodReference.Name}{GetMethodCallParameters(methodReference)}"); + var instanceString = methodReference.HasThis ? "instance " : string.Empty; + builder.Append($"{instanceString}{methodReference.ReturnType.ToILType()} {methodReference.DeclaringType.ToILType()}::{methodReference.Name}{GetMethodCallParameters(methodReference)}"); break; case FieldDefinition fieldDefinition: builder.Append($"{fieldDefinition.FieldType.ToILType()} {fieldDefinition.DeclaringType.ToILType()}::{fieldDefinition.Name}"); @@ -75,8 +80,7 @@ private static string GetMethodCallParameters(MethodReference method) builder.Append(", "); var parameterDefinition = method.Parameters[i]; - builder.Append($"{parameterDefinition.ParameterType.ToILType()} "); - builder.Append(parameterDefinition.Name); + builder.Append($"{parameterDefinition.ParameterType.ToILType()}"); } } diff --git a/src/dotnet-ildasm/MethodProcessor.cs b/src/dotnet-ildasm/MethodProcessor.cs index 13b02c6..f394342 100644 --- a/src/dotnet-ildasm/MethodProcessor.cs +++ b/src/dotnet-ildasm/MethodProcessor.cs @@ -1,5 +1,7 @@ +using System.Linq; using System.Reflection; using System.Text; +using DotNet.Ildasm.Infrastructure; using Mono.Cecil; namespace DotNet.Ildasm @@ -32,9 +34,12 @@ public void WriteBody(MethodDefinition method) _outputWriter.WriteLine($"// Code size {method.Body.CodeSize}"); _outputWriter.WriteLine($".maxstack {method.Body.MaxStackSize}"); + WriteLocalVariablesIfNeeded(method); + var ilProcessor = method.Body.GetILProcessor(); foreach (var instruction in ilProcessor.Body.Instructions) { + //_outputWriter.WriteLine(instruction.ToString()); _instructionProcessor.WriteInstruction(instruction); } } @@ -42,6 +47,21 @@ public void WriteBody(MethodDefinition method) _outputWriter.WriteLine($"}}// End of method {method.FullName}"); } + private void WriteLocalVariablesIfNeeded(MethodDefinition method) + { + if (method.Body.InitLocals) + { + if (method.Body.Variables.Count == 1) + _outputWriter.WriteLine($".locals init(class {method.Body.Variables.First().VariableType.ToILType()} V_0)"); + else + { + int parameterIndex = 0; + _outputWriter.WriteLine( + $".locals init(class {(string.Join($"V_{parameterIndex}, ", method.Body.Variables.Select(x => x.VariableType.ToILType())))})"); + } + } + } + private string GetMethodSignature(MethodDefinition method) { var builder = new StringBuilder(); diff --git a/src/dotnet-ildasm/Program.cs b/src/dotnet-ildasm/Program.cs index ef94ada..65c2396 100644 --- a/src/dotnet-ildasm/Program.cs +++ b/src/dotnet-ildasm/Program.cs @@ -23,6 +23,8 @@ private static int OnError() private static int PrepareToExecute(CommandOptions options) { + DeleteOutputFileWhenForceOutputOverwriteFlagOn(options); + var indentationProvider = new IndentationProvider(); var outputWriter = GetOutputWriter(options, indentationProvider); @@ -33,28 +35,29 @@ private static int PrepareToExecute(CommandOptions options) return -1; } - DeleteOutputFileWhenForceOutputOverwriteFlagOn(options); - return ExecuteDisassembler(options, outputWriter); } private static int ExecuteDisassembler(CommandOptions options, IOutputWriter outputWriter) { - var assemblyDataProcessor = new AssemblyDataProcessor(options.FilePath, outputWriter); - var assemblyDefinitionResolver = new AssemblyDefinitionResolver(); - var disassembler = new Disassembler(assemblyDataProcessor, assemblyDefinitionResolver); + using (outputWriter) + { + var assemblyDataProcessor = new AssemblyDataProcessor(options.FilePath, outputWriter); + var assemblyDefinitionResolver = new AssemblyDefinitionResolver(); + var disassembler = new Disassembler(assemblyDataProcessor, assemblyDefinitionResolver); - var itemFilter = new ItemFilter(options.ItemFilter); - var result = disassembler.Execute(options, itemFilter); + var itemFilter = new ItemFilter(options.ItemFilter); + var result = disassembler.Execute(options, itemFilter); - if (result.Succeeded || !options.IsTextOutput) - { - Console.WriteLine(result.Message); - return 0; - } + if (result.Succeeded || !options.IsTextOutput) + { + Console.WriteLine(result.Message); + return 0; + } - Console.WriteLine($"Error: {result.Message}"); - return -1; + Console.WriteLine($"Error: {result.Message}"); + return -1; + } } private static void DeleteOutputFileWhenForceOutputOverwriteFlagOn(CommandOptions options) @@ -75,7 +78,7 @@ private static IOutputWriter GetOutputWriter(CommandOptions options, Indentation } else { - outputWriter = new FileOutputWriter(indentationProvider, options.OutputPath); + outputWriter = new FileStreamOutputWriter(indentationProvider, options.OutputPath); } return outputWriter; } diff --git a/src/dotnet-ildasm/Properties/launchSettings.json b/src/dotnet-ildasm/Properties/launchSettings.json index 56ad88d..019d23f 100644 --- a/src/dotnet-ildasm/Properties/launchSettings.json +++ b/src/dotnet-ildasm/Properties/launchSettings.json @@ -2,7 +2,7 @@ "profiles": { "dotnet-ildasm": { "commandName": "Project", - "commandLineArgs": "..\\dotnet-ildasm.Sample\\bin\\Debug\\netstandard1.6\\dotnet-ildasm.Sample.dll -t -i ::.ctor" + "commandLineArgs": "..\\dotnet-ildasm.Sample\\bin\\Debug\\netstandard1.6\\dotnet-ildasm.Sample.dll -t -i ::PublicVoidMethodParams" } } } \ No newline at end of file diff --git a/src/dotnet-ildasm/TypeProcessor.cs b/src/dotnet-ildasm/TypeProcessor.cs index 28d427a..5227d58 100644 --- a/src/dotnet-ildasm/TypeProcessor.cs +++ b/src/dotnet-ildasm/TypeProcessor.cs @@ -2,6 +2,7 @@ using System.IO; using System.Linq; using System.Text; +using DotNet.Ildasm.Infrastructure; using Mono.Cecil; using Mono.Collections.Generic; diff --git a/src/dotnet-ildasm/dotnet-ildasm.csproj b/src/dotnet-ildasm/dotnet-ildasm.csproj index ca93aa2..d6918d8 100644 --- a/src/dotnet-ildasm/dotnet-ildasm.csproj +++ b/src/dotnet-ildasm/dotnet-ildasm.csproj @@ -1,7 +1,7 @@  Exe - netcoreapp1.0;netstandard1.5;net45 + netcoreapp1.0;netcoreapp1.1;netstandard1.5;net45 DotNet.Ildasm $(GitVersion_NuGetVersion) $(GitVersion_AssemblySemVer) @@ -10,7 +10,7 @@ This version adds code indentation and support to structs. https://pjbgf.mit-license.org/ dotnet-ildasm - dotnet-ildasm, ildasm + dotnet-ildasm, ildasm, cil, msil, dotnet-cli pjbgf https://github.com/pjbgf/dotnet-ildasm