From 53a050552bac87f91c8bb35f139b5aa8b1bcd48a Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sat, 23 Sep 2017 15:38:19 +0200 Subject: [PATCH] Add support for syntax sugar when writing out the ILAst. --- .../TestCases/Pretty/LiftedOperators.cs | 3 ++ ICSharpCode.Decompiler/IL/Instructions.cs | 4 +- ICSharpCode.Decompiler/IL/Instructions.tt | 15 ++++-- .../IL/Instructions/Comp.cs | 6 +++ .../IL/Instructions/ILInstruction.cs | 9 ++++ .../IL/Instructions/IfInstruction.cs | 18 +++++++ .../IL/Instructions/MemoryInstructions.cs | 49 +++++++++++++++++++ .../IL/Instructions/PatternMatching.cs | 21 ++++---- ILSpy/Languages/ILAstLanguage.cs | 26 ++++++++-- 9 files changed, 131 insertions(+), 20 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LiftedOperators.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LiftedOperators.cs index 2297eef58..b2a682b26 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LiftedOperators.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LiftedOperators.cs @@ -134,8 +134,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty a |= x(); a ^= x(); + Console.WriteLine(x() & a); + Console.WriteLine(x() | a); Console.WriteLine(x() ^ a); (new bool?[0])[0] ^= x(); + (new bool?[0])[0] ^= a; } public static void BoolValueConst(bool? a) diff --git a/ICSharpCode.Decompiler/IL/Instructions.cs b/ICSharpCode.Decompiler/IL/Instructions.cs index 48e809e7a..4519ba671 100644 --- a/ICSharpCode.Decompiler/IL/Instructions.cs +++ b/ICSharpCode.Decompiler/IL/Instructions.cs @@ -2946,7 +2946,7 @@ namespace ICSharpCode.Decompiler.IL return InstructionFlags.SideEffect | InstructionFlags.MayThrow; } } - public override void WriteTo(ITextOutput output, ILAstWritingOptions options) + void OriginalWriteTo(ITextOutput output, ILAstWritingOptions options) { if (IsVolatile) output.Write("volatile."); @@ -3070,7 +3070,7 @@ namespace ICSharpCode.Decompiler.IL return InstructionFlags.SideEffect | InstructionFlags.MayThrow; } } - public override void WriteTo(ITextOutput output, ILAstWritingOptions options) + void OriginalWriteTo(ITextOutput output, ILAstWritingOptions options) { if (IsVolatile) output.Write("volatile."); diff --git a/ICSharpCode.Decompiler/IL/Instructions.tt b/ICSharpCode.Decompiler/IL/Instructions.tt index 4a809dbbc..1b23c30d7 100644 --- a/ICSharpCode.Decompiler/IL/Instructions.tt +++ b/ICSharpCode.Decompiler/IL/Instructions.tt @@ -172,16 +172,16 @@ CustomClassName("LdFlda"), CustomArguments("target"), MayThrowIfNotDelayed, HasFieldOperand, ResultType("Ref")), new OpCode("ldsflda", "Load static field address", CustomClassName("LdsFlda"), NoArguments, ResultType("Ref"), HasFieldOperand), - + new OpCode("castclass", "Casts an object to a class.", CustomClassName("CastClass"), Unary, HasTypeOperand, MayThrow, ResultType("type.GetStackType()")), new OpCode("isinst", "Test if object is instance of class or interface.", CustomClassName("IsInst"), Unary, HasTypeOperand, ResultType("O")), new OpCode("ldobj", "Indirect load (ref/pointer dereference).", - CustomClassName("LdObj"), CustomArguments("target"), HasTypeOperand, MemoryAccess, + CustomClassName("LdObj"), CustomArguments("target"), HasTypeOperand, MemoryAccess, CustomWriteToButKeepOriginal, SupportsVolatilePrefix, SupportsUnalignedPrefix, MayThrow, ResultType("type.GetStackType()")), new OpCode("stobj", "Indirect store (store to ref/pointer).", - CustomClassName("StObj"), CustomArguments("target", "value"), HasTypeOperand, MemoryAccess, + CustomClassName("StObj"), CustomArguments("target", "value"), HasTypeOperand, MemoryAccess, CustomWriteToButKeepOriginal, SupportsVolatilePrefix, SupportsUnalignedPrefix, MayThrow, ResultType("type.GetStackType()")), new OpCode("box", "Boxes a value.", @@ -277,7 +277,11 @@ namespace <#=opCode.Namespace#> } <# } #> <# if (opCode.GenerateWriteTo) { #> +<# if (opCode.CustomWriteToButKeepOriginal) { #> + void OriginalWriteTo(ITextOutput output, ILAstWritingOptions options) +<# } else { #> public override void WriteTo(ITextOutput output, ILAstWritingOptions options) +<# } #> {<#=Body(opCode.WriteToBody)#>} <# } #> <# if (opCode.GenerateAcceptVisitor) { #> @@ -462,6 +466,7 @@ namespace ICSharpCode.Decompiler.IL public bool GenerateWriteTo = false; + public bool CustomWriteToButKeepOriginal = false; public List WriteOpCodePrefix = new List(); public List WriteOpCodeSuffix = new List(); public List WriteOperand = new List(); @@ -512,6 +517,10 @@ namespace ICSharpCode.Decompiler.IL static Action CustomWriteTo = opCode => { opCode.GenerateWriteTo = false; }; + + static Action CustomWriteToButKeepOriginal = opCode => { + opCode.CustomWriteToButKeepOriginal = true; + }; static Action CustomComputeFlags = opCode => { opCode.GenerateComputeFlags = false; diff --git a/ICSharpCode.Decompiler/IL/Instructions/Comp.cs b/ICSharpCode.Decompiler/IL/Instructions/Comp.cs index c268934d0..371cc4723 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/Comp.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/Comp.cs @@ -175,6 +175,12 @@ namespace ICSharpCode.Decompiler.IL public override void WriteTo(ITextOutput output, ILAstWritingOptions options) { + if (options.UseLogicOperationSugar && MatchLogicNot(out var arg)) { + output.Write("logic.not("); + arg.WriteTo(output, options); + output.Write(')'); + return; + } output.Write(OpCode); switch (Sign) { case Sign.Signed: diff --git a/ICSharpCode.Decompiler/IL/Instructions/ILInstruction.cs b/ICSharpCode.Decompiler/IL/Instructions/ILInstruction.cs index 6d8e5d64a..a4c8f66ca 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/ILInstruction.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/ILInstruction.cs @@ -743,5 +743,14 @@ namespace ICSharpCode.Decompiler.IL public class ILAstWritingOptions { + /// + /// Sugar for logic.not/and/or. + /// + public bool UseLogicOperationSugar { get; set; } + + /// + /// Sugar for ldfld/stfld. + /// + public bool UseFieldSugar { get; set; } } } diff --git a/ICSharpCode.Decompiler/IL/Instructions/IfInstruction.cs b/ICSharpCode.Decompiler/IL/Instructions/IfInstruction.cs index 77a419349..09ae5a87c 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/IfInstruction.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/IfInstruction.cs @@ -78,6 +78,24 @@ namespace ICSharpCode.Decompiler.IL public override void WriteTo(ITextOutput output, ILAstWritingOptions options) { + if (options.UseLogicOperationSugar) { + if (MatchLogicAnd(out var lhs, out var rhs)) { + output.Write("logic.and("); + lhs.WriteTo(output, options); + output.Write(", "); + rhs.WriteTo(output, options); + output.Write(')'); + return; + } + if (MatchLogicOr(out lhs, out rhs)) { + output.Write("logic.or("); + lhs.WriteTo(output, options); + output.Write(", "); + rhs.WriteTo(output, options); + output.Write(')'); + return; + } + } output.Write(OpCode); output.Write(" ("); condition.WriteTo(output, options); diff --git a/ICSharpCode.Decompiler/IL/Instructions/MemoryInstructions.cs b/ICSharpCode.Decompiler/IL/Instructions/MemoryInstructions.cs index 07204374b..de52425b3 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/MemoryInstructions.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/MemoryInstructions.cs @@ -34,4 +34,53 @@ namespace ICSharpCode.Decompiler.IL /// bool IsVolatile { get; set; } } + + partial class LdObj + { + public override void WriteTo(ITextOutput output, ILAstWritingOptions options) + { + if (options.UseFieldSugar) { + if (this.MatchLdFld(out var target, out var field)) { + output.Write("ldfld "); + Disassembler.DisassemblerHelpers.WriteOperand(output, field); + output.Write('('); + this.target.WriteTo(output, options); + output.Write(')'); + return; + } else if (this.MatchLdsFld(out field)) { + output.Write("ldsfld "); + Disassembler.DisassemblerHelpers.WriteOperand(output, field); + return; + } + } + OriginalWriteTo(output, options); + } + } + + partial class StObj + { + public override void WriteTo(ITextOutput output, ILAstWritingOptions options) + { + if (options.UseFieldSugar) { + if (this.MatchStFld(out var target, out var field, out var value)) { + output.Write("stfld "); + Disassembler.DisassemblerHelpers.WriteOperand(output, field); + output.Write('('); + this.target.WriteTo(output, options); + output.Write(", "); + this.value.WriteTo(output, options); + output.Write(')'); + return; + } else if (this.MatchStsFld(out field, out value)) { + output.Write("stsfld "); + Disassembler.DisassemblerHelpers.WriteOperand(output, field); + output.Write('('); + this.value.WriteTo(output, options); + output.Write(')'); + return; + } + } + OriginalWriteTo(output, options); + } + } } diff --git a/ICSharpCode.Decompiler/IL/Instructions/PatternMatching.cs b/ICSharpCode.Decompiler/IL/Instructions/PatternMatching.cs index 9e9185f7b..1f1b06390 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/PatternMatching.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/PatternMatching.cs @@ -324,17 +324,9 @@ namespace ICSharpCode.Decompiler.IL } } - public bool MatchLdsFld(IField field) - { - if (this is LdObj ldobj && ldobj.Target is LdsFlda ldsflda) { - return field.Equals(ldsflda.Field); - } - return false; - } - public bool MatchLdFld(out ILInstruction target, out IField field) { - if (this is LdObj ldobj && ldobj.Target is LdFlda ldflda) { + if (this is LdObj ldobj && ldobj.Target is LdFlda ldflda && ldobj.UnalignedPrefix == 0 && !ldobj.IsVolatile) { target = ldflda.Target; field = ldflda.Field; return true; @@ -346,7 +338,7 @@ namespace ICSharpCode.Decompiler.IL public bool MatchLdsFld(out IField field) { - if (this is LdObj ldobj && ldobj.Target is LdsFlda ldsflda) { + if (this is LdObj ldobj && ldobj.Target is LdsFlda ldsflda && ldobj.UnalignedPrefix == 0 && !ldobj.IsVolatile) { field = ldsflda.Field; return true; } @@ -354,9 +346,14 @@ namespace ICSharpCode.Decompiler.IL return false; } + public bool MatchLdsFld(IField field) + { + return MatchLdsFld(out var f) && f.Equals(field); + } + public bool MatchStsFld(out IField field, out ILInstruction value) { - if (this is StObj stobj && stobj.Target is LdsFlda ldsflda) { + if (this is StObj stobj && stobj.Target is LdsFlda ldsflda && stobj.UnalignedPrefix == 0 && !stobj.IsVolatile) { field = ldsflda.Field; value = stobj.Value; return true; @@ -368,7 +365,7 @@ namespace ICSharpCode.Decompiler.IL public bool MatchStFld(out ILInstruction target, out IField field, out ILInstruction value) { - if (this is StObj stobj && stobj.Target is LdFlda ldflda) { + if (this is StObj stobj && stobj.Target is LdFlda ldflda && stobj.UnalignedPrefix == 0 && !stobj.IsVolatile) { target = ldflda.Target; field = ldflda.Field; value = stobj.Value; diff --git a/ILSpy/Languages/ILAstLanguage.cs b/ILSpy/Languages/ILAstLanguage.cs index 2ab5ed0dc..7f6324118 100644 --- a/ILSpy/Languages/ILAstLanguage.cs +++ b/ILSpy/Languages/ILAstLanguage.cs @@ -141,12 +141,16 @@ namespace ICSharpCode.ILSpy class BlockIL : ILAstLanguage { readonly IReadOnlyList transforms; - + readonly ILAstWritingOptions writingOptions = new ILAstWritingOptions { + UseFieldSugar = true, + UseLogicOperationSugar = true + }; + public BlockIL(IReadOnlyList transforms) : base("ILAst") { this.transforms = transforms; } - + public override void DecompileMethod(MethodDefinition method, ITextOutput output, DecompilationOptions options) { base.DecompileMethod(method, output, options); @@ -173,11 +177,27 @@ namespace ICSharpCode.ILSpy OnStepperUpdated(new EventArgs()); } } + (output as ISmartTextOutput)?.AddUIElement(OptionsCheckBox(nameof(writingOptions.UseFieldSugar))); + output.WriteLine(); + (output as ISmartTextOutput)?.AddUIElement(OptionsCheckBox(nameof(writingOptions.UseLogicOperationSugar))); + output.WriteLine(); (output as ISmartTextOutput)?.AddButton(Images.ViewCode, "Show Steps", delegate { DebugSteps.Show(); }); output.WriteLine(); - il.WriteTo(output); + il.WriteTo(output, writingOptions); + } + + Func OptionsCheckBox(string propertyName) + { + return () => { + var checkBox = new System.Windows.Controls.CheckBox(); + checkBox.Content = propertyName; + checkBox.Cursor = System.Windows.Input.Cursors.Arrow; + checkBox.SetBinding(System.Windows.Controls.CheckBox.IsCheckedProperty, + new System.Windows.Data.Binding(propertyName) { Source = writingOptions }); + return checkBox; + }; } } }