From d42cf99a8c34f404a3c062d8a5edf4b992edf1f5 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Mon, 25 Feb 2019 15:12:15 +0100 Subject: [PATCH] Fix dynamic transforms because https://github.com/dotnet/roslyn/issues/27800 was fixed. --- .../PrettyTestRunner.cs | 1 - .../TestCases/Pretty/DynamicTests.cs | 16 +-- .../CSharp/CSharpDecompiler.cs | 1 + .../ICSharpCode.Decompiler.csproj | 1 + .../DynamicIsEventAssignmentTransform.cs | 127 ++++++++++++++++++ .../IL/Transforms/ExpressionTransforms.cs | 4 + 6 files changed, 141 insertions(+), 9 deletions(-) create mode 100644 ICSharpCode.Decompiler/IL/Transforms/DynamicIsEventAssignmentTransform.cs diff --git a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs index 8d747d075..028089187 100644 --- a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs @@ -241,7 +241,6 @@ namespace ICSharpCode.Decompiler.Tests } [Test] - [Ignore("broken by Roslyn upgrade")] public void DynamicTests([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) { RunForLibrary(cscOptions: cscOptions); diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DynamicTests.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DynamicTests.cs index d5cf71bcf..f328fc634 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DynamicTests.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DynamicTests.cs @@ -2,14 +2,6 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty { - internal static class Extension - { - public static dynamic ToDynamic(this int i, dynamic info) - { - throw null; - } - } - internal class DynamicTests { @@ -446,4 +438,12 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty return (int)(dynamic)o; } } + + internal static class Extension + { + public static dynamic ToDynamic(this int i, dynamic info) + { + throw null; + } + } } diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index 0c0ed0f26..bcce58ab5 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -138,6 +138,7 @@ namespace ICSharpCode.Decompiler.CSharp // Inlining must be first, because it doesn't trigger re-runs. // Any other transform that opens up new inlining opportunities should call RequestRerun(). new ExpressionTransforms(), + new DynamicIsEventAssignmentTransform(), new TransformAssignment(), // inline and compound assignments new NullCoalescingTransform(), new NullableLiftingStatementTransform(), diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index 6bfbb1bbd..5862cd725 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -269,6 +269,7 @@ + diff --git a/ICSharpCode.Decompiler/IL/Transforms/DynamicIsEventAssignmentTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/DynamicIsEventAssignmentTransform.cs new file mode 100644 index 000000000..bd9522458 --- /dev/null +++ b/ICSharpCode.Decompiler/IL/Transforms/DynamicIsEventAssignmentTransform.cs @@ -0,0 +1,127 @@ +// Copyright (c) 2019 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. + +namespace ICSharpCode.Decompiler.IL.Transforms +{ + public class DynamicIsEventAssignmentTransform : IStatementTransform + { + /// stloc V_1(dynamic.isevent (target)) + /// if (logic.not(ldloc V_1)) Block IL_004a { + /// stloc V_2(dynamic.getmember B(target)) + /// } + /// if (logic.not(ldloc V_1)) Block IL_0149 { + /// dynamic.setmember.compound B(target, dynamic.binary.operator AddAssign(ldloc V_2, value)) + /// } else Block IL_0151 { + /// dynamic.invokemember.invokespecial.discard add_B(target, value) + /// } + /// => + /// if (logic.not(dynamic.isevent (target))) Block IL_0149 { + /// dynamic.setmember.compound B(target, dynamic.binary.operator AddAssign(dynamic.getmember B(target), value)) + /// } else Block IL_0151 { + /// dynamic.invokemember.invokespecial.discard add_B(target, value) + /// } + public void Run(Block block, int pos, StatementTransformContext context) + { + if (!(pos + 3 < block.Instructions.Count && block.Instructions[pos].MatchStLoc(out var flagVar, out var inst) && inst is DynamicIsEventInstruction isEvent)) + return; + if (!(flagVar.IsSingleDefinition && flagVar.LoadCount == 2)) + return; + if (!(MatchLhsCacheIfInstruction(block.Instructions[pos + 1], flagVar, out var dynamicGetMemberStore))) + return; + if (!(dynamicGetMemberStore.MatchStLoc(out var getMemberVar, out inst) && inst is DynamicGetMemberInstruction getMemberInst)) + return; + foreach (var descendant in block.Instructions[pos + 2].Descendants) { + if (!MatchIsEventAssignmentIfInstruction(descendant, isEvent, flagVar, getMemberVar, out var setMemberInst, out var getMemberVarUse, out var isEventConditionUse)) + continue; + context.Step("DynamicIsEventAssignmentTransform", block.Instructions[pos]); + // Collapse duplicate condition + getMemberVarUse.ReplaceWith(getMemberInst); + isEventConditionUse.ReplaceWith(isEvent); + block.Instructions.RemoveRange(pos, 2); + // Reuse ExpressionTransforms + ExpressionTransforms.TransformDynamicSetMemberInstruction(setMemberInst, context); + context.RequestRerun(); + break; + } + } + + /// + /// if (logic.not(ldloc V_1)) Block IL_0149 { + /// dynamic.setmember.compound B(target, dynamic.binary.operator AddAssign(ldloc V_2, value)) + /// } else Block IL_0151 { + /// dynamic.invokemember.invokespecial.discard add_B(target, value) + /// } + /// + static bool MatchIsEventAssignmentIfInstruction(ILInstruction ifInst, DynamicIsEventInstruction isEvent, ILVariable flagVar, ILVariable getMemberVar, + out DynamicSetMemberInstruction setMemberInst, out ILInstruction getMemberVarUse, out ILInstruction isEventConditionUse) + { + setMemberInst = null; + getMemberVarUse = null; + isEventConditionUse = null; + if (!ifInst.MatchIfInstruction(out var condition, out var trueInst, out var falseInst)) + return false; + if (MatchFlagEqualsZero(condition, flagVar)) { + if (!condition.MatchCompEquals(out var left, out _)) + return false; + isEventConditionUse = left; + } else if (condition.MatchLdLoc(flagVar)) { + var tmp = trueInst; + trueInst = falseInst; + falseInst = tmp; + isEventConditionUse = condition; + } else + return false; + setMemberInst = Block.Unwrap(trueInst) as DynamicSetMemberInstruction; + if (!(setMemberInst != null)) + return false; + if (!isEvent.Argument.Match(setMemberInst.Target).Success) + return false; + if (!(Block.Unwrap(falseInst) is DynamicInvokeMemberInstruction invokeMemberInst && invokeMemberInst.Arguments.Count == 2)) + return false; + if (!isEvent.Argument.Match(invokeMemberInst.Arguments[0]).Success) + return false; + if (!(setMemberInst.Value is DynamicBinaryOperatorInstruction binOp && binOp.Left.MatchLdLoc(getMemberVar))) + return false; + getMemberVarUse = binOp.Left; + return true; + } + + /// + /// if (logic.not(ldloc V_1)) Block IL_004a { + /// stloc V_2(dynamic.getmember B(target)) + /// } + /// + static bool MatchLhsCacheIfInstruction(ILInstruction ifInst, ILVariable flagVar, out StLoc cacheStore) + { + cacheStore = null; + if (!ifInst.MatchIfInstruction(out var condition, out var trueInst)) + return false; + if (!MatchFlagEqualsZero(condition, flagVar)) + return false; + cacheStore = Block.Unwrap(trueInst) as StLoc; + return cacheStore != null; + } + + static bool MatchFlagEqualsZero(ILInstruction condition, ILVariable flagVar) + { + return condition.MatchCompEquals(out var left, out var right) + && left.MatchLdLoc(flagVar) + && right.MatchLdcI4(0); + } + } +} diff --git a/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs index af95b2daa..128963534 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs @@ -499,7 +499,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms protected internal override void VisitDynamicSetMemberInstruction(DynamicSetMemberInstruction inst) { base.VisitDynamicSetMemberInstruction(inst); + TransformDynamicSetMemberInstruction(inst, context); + } + internal static void TransformDynamicSetMemberInstruction(DynamicSetMemberInstruction inst, StatementTransformContext context) + { if (!inst.BinderFlags.HasFlag(CSharpBinderFlags.ValueFromCompoundAssignment)) return; if (!(inst.Value is DynamicBinaryOperatorInstruction binaryOp))