From 5007c660caa57a766d3a724910ba9e364396f169 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Tue, 22 Aug 2017 20:39:17 +0200 Subject: [PATCH] Add NullCoalescingInstruction --- .../CSharp/CSharpDecompiler.cs | 1 + .../CSharp/ExpressionBuilder.cs | 23 +++- .../ICSharpCode.Decompiler.csproj | 2 + ICSharpCode.Decompiler/IL/Instructions.cs | 104 +++++++++++++++++- ICSharpCode.Decompiler/IL/Instructions.tt | 6 + .../Instructions/NullCoalescingInstruction.cs | 69 ++++++++++++ .../IL/Transforms/NullCoalescingTransform.cs | 64 +++++++++++ 7 files changed, 267 insertions(+), 2 deletions(-) create mode 100644 ICSharpCode.Decompiler/IL/Instructions/NullCoalescingInstruction.cs create mode 100644 ICSharpCode.Decompiler/IL/Transforms/NullCoalescingTransform.cs diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index 49a2317ff..506f3278a 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -106,6 +106,7 @@ namespace ICSharpCode.Decompiler.CSharp // Pretty much all transforms that open up new expression inlining // opportunities belong in this category. new ExpressionTransforms(), + new NullCoalescingTransform(), new TransformArrayInitializers(), new TransformCollectionAndObjectInitializers(), new ILInlining() diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index 779c8a263..3d81ef64c 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -1592,7 +1592,28 @@ namespace ICSharpCode.Decompiler.CSharp .WithILInstruction(block) .WithRR(resolver.WithCheckForOverflow(inst.CheckForOverflow).ResolveUnaryOperator(op, target.ResolveResult)); } - + + protected internal override TranslatedExpression VisitNullCoalescingInstruction(NullCoalescingInstruction inst, TranslationContext context) + { + var value = Translate(inst.ValueInst); + var fallback = Translate(inst.FallbackInst); + var rr = resolver.ResolveBinaryOperator(BinaryOperatorType.NullCoalescing, value.ResolveResult, fallback.ResolveResult); + if (rr.IsError) { + IType targetType; + if (!value.Type.Equals(SpecialType.NullType) && !fallback.Type.Equals(SpecialType.NullType) && !value.Type.Equals(fallback.Type)) { + targetType = compilation.FindType(inst.ResultType.ToKnownTypeCode()); + } else { + targetType = value.Type.Equals(SpecialType.NullType) ? fallback.Type : value.Type; + } + value = value.ConvertTo(targetType, this); + fallback = fallback.ConvertTo(targetType, this); + rr = new ResolveResult(targetType); + } + return new BinaryOperatorExpression(value, BinaryOperatorType.NullCoalescing, fallback) + .WithILInstruction(inst) + .WithRR(rr); + } + protected internal override TranslatedExpression VisitIfInstruction(IfInstruction inst, TranslationContext context) { var condition = TranslateCondition(inst.Condition); diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index ada11e794..5d2520d31 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -283,8 +283,10 @@ + + diff --git a/ICSharpCode.Decompiler/IL/Instructions.cs b/ICSharpCode.Decompiler/IL/Instructions.cs index cb5569716..6a426461b 100644 --- a/ICSharpCode.Decompiler/IL/Instructions.cs +++ b/ICSharpCode.Decompiler/IL/Instructions.cs @@ -59,6 +59,8 @@ namespace ICSharpCode.Decompiler.IL Leave, /// If statement / conditional expression. if (condition) trueExpr else falseExpr IfInstruction, + /// Null coalescing operator expression. if.notnull(valueInst, fallbackInst) + NullCoalescingInstruction, /// Switch statement SwitchInstruction, /// Switch section within a switch statement @@ -836,7 +838,6 @@ namespace ICSharpCode.Decompiler.IL { } public override StackType ResultType { get { return StackType.I4; } } - public override void AcceptVisitor(ILVisitor visitor) { visitor.VisitLogicNot(this); @@ -1185,6 +1186,94 @@ namespace ICSharpCode.Decompiler.IL } } namespace ICSharpCode.Decompiler.IL +{ + /// Null coalescing operator expression. if.notnull(valueInst, fallbackInst) + public sealed partial class NullCoalescingInstruction : ILInstruction + { + public static readonly SlotInfo ValueInstSlot = new SlotInfo("ValueInst"); + ILInstruction valueInst; + public ILInstruction ValueInst { + get { return this.valueInst; } + set { + ValidateChild(value); + SetChildInstruction(ref this.valueInst, value, 0); + } + } + public static readonly SlotInfo FallbackInstSlot = new SlotInfo("FallbackInst"); + ILInstruction fallbackInst; + public ILInstruction FallbackInst { + get { return this.fallbackInst; } + set { + ValidateChild(value); + SetChildInstruction(ref this.fallbackInst, value, 1); + } + } + protected sealed override int GetChildCount() + { + return 2; + } + protected sealed override ILInstruction GetChild(int index) + { + switch (index) { + case 0: + return this.valueInst; + case 1: + return this.fallbackInst; + default: + throw new IndexOutOfRangeException(); + } + } + protected sealed override void SetChild(int index, ILInstruction value) + { + switch (index) { + case 0: + this.ValueInst = value; + break; + case 1: + this.FallbackInst = value; + break; + default: + throw new IndexOutOfRangeException(); + } + } + protected sealed override SlotInfo GetChildSlot(int index) + { + switch (index) { + case 0: + return ValueInstSlot; + case 1: + return FallbackInstSlot; + default: + throw new IndexOutOfRangeException(); + } + } + public sealed override ILInstruction Clone() + { + var clone = (NullCoalescingInstruction)ShallowClone(); + clone.ValueInst = this.valueInst.Clone(); + clone.FallbackInst = this.fallbackInst.Clone(); + return clone; + } + public override void AcceptVisitor(ILVisitor visitor) + { + visitor.VisitNullCoalescingInstruction(this); + } + public override T AcceptVisitor(ILVisitor visitor) + { + return visitor.VisitNullCoalescingInstruction(this); + } + public override T AcceptVisitor(ILVisitor visitor, C context) + { + return visitor.VisitNullCoalescingInstruction(this, context); + } + protected internal override bool PerformMatch(ILInstruction other, ref Patterns.Match match) + { + var o = other as NullCoalescingInstruction; + return o != null && this.valueInst.PerformMatch(o.valueInst, ref match) && this.fallbackInst.PerformMatch(o.fallbackInst, ref match); + } + } +} +namespace ICSharpCode.Decompiler.IL { /// Switch statement public sealed partial class SwitchInstruction : ILInstruction @@ -4020,6 +4109,10 @@ namespace ICSharpCode.Decompiler.IL { Default(inst); } + protected internal virtual void VisitNullCoalescingInstruction(NullCoalescingInstruction inst) + { + Default(inst); + } protected internal virtual void VisitSwitchInstruction(SwitchInstruction inst) { Default(inst); @@ -4290,6 +4383,10 @@ namespace ICSharpCode.Decompiler.IL { return Default(inst); } + protected internal virtual T VisitNullCoalescingInstruction(NullCoalescingInstruction inst) + { + return Default(inst); + } protected internal virtual T VisitSwitchInstruction(SwitchInstruction inst) { return Default(inst); @@ -4560,6 +4657,10 @@ namespace ICSharpCode.Decompiler.IL { return Default(inst, context); } + protected internal virtual T VisitNullCoalescingInstruction(NullCoalescingInstruction inst, C context) + { + return Default(inst, context); + } protected internal virtual T VisitSwitchInstruction(SwitchInstruction inst, C context) { return Default(inst, context); @@ -4780,6 +4881,7 @@ namespace ICSharpCode.Decompiler.IL "br", "leave", "if", + "if.notnull", "switch", "switch.section", "try.catch", diff --git a/ICSharpCode.Decompiler/IL/Instructions.tt b/ICSharpCode.Decompiler/IL/Instructions.tt index a735d806e..be5cbf167 100644 --- a/ICSharpCode.Decompiler/IL/Instructions.tt +++ b/ICSharpCode.Decompiler/IL/Instructions.tt @@ -87,6 +87,12 @@ new ChildInfo("trueInst"), new ChildInfo("falseInst"), }), CustomConstructor, CustomComputeFlags, CustomWriteTo), + new OpCode("if.notnull", "Null coalescing operator expression. if.notnull(valueInst, fallbackInst)", + CustomClassName("NullCoalescingInstruction"), + CustomChildren(new []{ + new ChildInfo("valueInst"), + new ChildInfo("fallbackInst"), + }), CustomConstructor, CustomComputeFlags, CustomWriteTo), new OpCode("switch", "Switch statement", CustomClassName("SwitchInstruction"), CustomConstructor, CustomComputeFlags, CustomWriteTo, ResultType("Void"), MatchCondition("Value.PerformMatch(o.Value, ref match) && DefaultBody.PerformMatch(o.DefaultBody, ref match) && Patterns.ListMatch.DoMatch(this.Sections, o.Sections, ref match)")), diff --git a/ICSharpCode.Decompiler/IL/Instructions/NullCoalescingInstruction.cs b/ICSharpCode.Decompiler/IL/Instructions/NullCoalescingInstruction.cs new file mode 100644 index 000000000..415c8c7dd --- /dev/null +++ b/ICSharpCode.Decompiler/IL/Instructions/NullCoalescingInstruction.cs @@ -0,0 +1,69 @@ +// Copyright (c) 2017 Siegfried Pammer +// +// 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.Threading; +using ICSharpCode.Decompiler.IL.Transforms; +using Mono.Cecil; +using ICSharpCode.Decompiler.Disassembler; +using System.Linq; +using ICSharpCode.Decompiler.TypeSystem; +using ICSharpCode.Decompiler.Util; +using System.Diagnostics; + +namespace ICSharpCode.Decompiler.IL +{ + /// Null coalescing operator expression. if.notnull(valueInst, fallbackInst) + partial class NullCoalescingInstruction + { + public NullCoalescingInstruction(ILInstruction valueInst, ILInstruction fallbackInst) : base(OpCode.NullCoalescingInstruction) + { + this.ValueInst = valueInst; + this.FallbackInst = fallbackInst; + } + + public override StackType ResultType { + get { + return CommonResultType(valueInst.ResultType, fallbackInst.ResultType); + } + } + + public override InstructionFlags DirectFlags { + get { + return InstructionFlags.ControlFlow; + } + } + + protected override InstructionFlags ComputeFlags() + { + return InstructionFlags.ControlFlow | SemanticHelper.CombineBranches(valueInst.Flags, fallbackInst.Flags); + } + + public override void WriteTo(ITextOutput output) + { + output.Write(OpCode); + output.Write(" ("); + valueInst.WriteTo(output); + output.Write(", "); + fallbackInst.WriteTo(output); + output.Write(")"); + + } + } +} diff --git a/ICSharpCode.Decompiler/IL/Transforms/NullCoalescingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/NullCoalescingTransform.cs new file mode 100644 index 000000000..097945580 --- /dev/null +++ b/ICSharpCode.Decompiler/IL/Transforms/NullCoalescingTransform.cs @@ -0,0 +1,64 @@ +// Copyright (c) 2017 Siegfried Pammer +// +// 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.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ICSharpCode.Decompiler.IL.Transforms +{ + class NullCoalescingTransform : IBlockTransform + { + BlockTransformContext context; + + void IBlockTransform.Run(Block block, BlockTransformContext context) + { + this.context = context; + for (int i = block.Instructions.Count - 1; i >= 0; i--) { + if (TransformNullCoalescing(block, i)) { + block.Instructions.RemoveAt(i); + continue; + } + } + } + + /// + /// stloc s(valueInst) + /// if (comp(ldloc s == ldnull)) { + /// stloc s(fallbackInst) + /// } + /// => + /// stloc s(if.notnull(valueInst, fallbackInst) + /// + bool TransformNullCoalescing(Block block, int i) + { + if (i == 0) return false; + if (!(block.Instructions[i] is IfInstruction ifInstruction) || !(block.Instructions[i - 1] is StLoc stloc) || stloc.Variable.Kind != VariableKind.StackSlot) + return false; + if (!ifInstruction.Condition.MatchCompEquals(out var left, out var right) || !left.MatchLdLoc(stloc.Variable) || !right.MatchLdNull()) + return false; + if (!ifInstruction.FalseInst.MatchNop() || !(ifInstruction.TrueInst is Block b) || b.Instructions.Count != 1 || !(b.Instructions[0] is StLoc fallbackStore) || fallbackStore.Variable != stloc.Variable) + return false; + context.Step("TransformNullCoalescing", stloc); + stloc.Value.ReplaceWith(new NullCoalescingInstruction(stloc.Value.Clone(), fallbackStore.Value.Clone())); + return true; + } + } +}