From 1a77b931a7b00be569c6fb685e101906b3968008 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sat, 11 Jun 2016 23:28:24 +0200 Subject: [PATCH] Fix decompiling comparison operators: * add casts when comparing integers * fix "unordered" float comparisons --- .../CSharp/ExpressionBuilder.cs | 49 +++++++++++++++--- .../Tests/ICSharpCode.Decompiler.Tests.csproj | 2 +- .../Tests/TestCases/FloatComparisons.cs | 51 +++++++++++++++++++ ICSharpCode.Decompiler/Tests/TestRunner.cs | 6 +++ 4 files changed, 101 insertions(+), 7 deletions(-) create mode 100644 ICSharpCode.Decompiler/Tests/TestCases/FloatComparisons.cs diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index 5ac3ae4df..e76d6efc3 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -325,13 +325,48 @@ namespace ICSharpCode.Decompiler.CSharp { var left = Translate(inst.Left); var right = Translate(inst.Right); - // TODO: ensure the arguments are signed - // or with _Un: ensure the arguments are unsigned; and that float comparisons are performed unordered - return new BinaryOperatorExpression(left.Expression, op, right.Expression) + // Ensure the inputs have the correct sign: + KnownTypeCode inputType = KnownTypeCode.None; + switch (inst.OpType) { + case StackType.I8: + inputType = un ? KnownTypeCode.UInt64 : KnownTypeCode.Int64; + break; + case StackType.I: + inputType = un ? KnownTypeCode.UIntPtr : KnownTypeCode.IntPtr; + break; + case StackType.I4: + inputType = un ? KnownTypeCode.UInt32 : KnownTypeCode.Int32; + break; + case StackType.F: + if (un) { + // for floats, _Un means "unordered": + // clt_un returns 1 if left < right or if either input is not-a-number + // The C# operators never return true for NaN, so we need to use a negation: + // clt_un => !(left >= right) + // cgt_un => !(left <= right) + if (op == BinaryOperatorType.LessThan) + op = BinaryOperatorType.GreaterThanOrEqual; + else if (op == BinaryOperatorType.GreaterThan) + op = BinaryOperatorType.LessThanOrEqual; + else + throw new ArgumentException("op"); + } + break; + } + if (inputType != KnownTypeCode.None) { + left = left.ConvertTo(compilation.FindType(inputType), this); + right = right.ConvertTo(compilation.FindType(inputType), this); + } + var result = new BinaryOperatorExpression(left.Expression, op, right.Expression) .WithILInstruction(inst) .WithRR(new OperatorResolveResult(compilation.FindType(TypeCode.Boolean), BinaryOperatorExpression.GetLinqNodeType(op, false), left.ResolveResult, right.ResolveResult)); + if (un && inst.OpType == StackType.F) { + // add negation if we turned around the operator symbol above + result = LogicNot(result).WithILInstruction(inst); + } + return result; } ExpressionWithResolveResult Assignment(TranslatedExpression left, TranslatedExpression right) @@ -483,9 +518,11 @@ namespace ICSharpCode.Decompiler.CSharp var target = TranslateTarget(method, inst.Arguments[0], func.OpCode == OpCode.LdFtn); return new ObjectCreateExpression(ConvertType(inst.Method.DeclaringType), new MemberReferenceExpression(target, method.Name)) .WithILInstruction(inst) - .WithRR(new ConversionResolveResult(method.DeclaringType, - new MemberResolveResult(target.ResolveResult, method), - Conversion.MethodGroupConversion(method, func.OpCode == OpCode.LdVirtFtn, false))); // TODO handle extension methods capturing the first argument + .WithRR(new ConversionResolveResult( + method.DeclaringType, + new MemberResolveResult(target.ResolveResult, method), + // TODO handle extension methods capturing the first argument + Conversion.MethodGroupConversion(method, func.OpCode == OpCode.LdVirtFtn, false))); } TranslatedExpression TranslateTarget(IMember member, ILInstruction target, bool nonVirtualInvocation) diff --git a/ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj index eeca34f62..058bbcf7a 100644 --- a/ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj +++ b/ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj @@ -95,7 +95,6 @@ {d68133bd-1e63-496e-9ede-4fbdbf77b486} Mono.Cecil - {53dca265-3c3c-42f9-b647-f72ba678122b} ICSharpCode.NRefactory.CSharp @@ -119,6 +118,7 @@ + diff --git a/ICSharpCode.Decompiler/Tests/TestCases/FloatComparisons.cs b/ICSharpCode.Decompiler/Tests/TestCases/FloatComparisons.cs new file mode 100644 index 000000000..2fa084012 --- /dev/null +++ b/ICSharpCode.Decompiler/Tests/TestCases/FloatComparisons.cs @@ -0,0 +1,51 @@ +// Copyright (c) 2016 Daniel Grunwald +// +// 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; + +namespace ICSharpCode.Decompiler.Tests.TestCases +{ + public class FloatComparisons + { + public static int Main() + { + // disable CompareOfFloatsByEqualityOperator + TestFloatOp("==", (a, b) => a == b); + TestFloatOp("!=", (a, b) => a != b); + TestFloatOp("<", (a, b) => a < b); + TestFloatOp(">", (a, b) => a > b); + TestFloatOp("<=", (a, b) => a <= b); + TestFloatOp(">=", (a, b) => a >= b); + TestFloatOp("!<", (a, b) => !(a < b)); + TestFloatOp("!>", (a, b) => !(a > b)); + TestFloatOp("!<=", (a, b) => !(a <= b)); + TestFloatOp("!>=", (a, b) => !(a >= b)); + return 0; + } + + static void TestFloatOp(string name, Func f) + { + float[] vals = { -1, 0, 3, float.PositiveInfinity, float.NaN }; + for (int i = 0; i < vals.Length; i++) { + for (int j = 0; j < vals.Length; j++) { + Console.WriteLine("{0:r} {1} {2:r} = {3}", vals[i], name, vals[j], f(vals[i], vals[j])); + } + } + } + } +} diff --git a/ICSharpCode.Decompiler/Tests/TestRunner.cs b/ICSharpCode.Decompiler/Tests/TestRunner.cs index 25c6a9763..76d54d399 100644 --- a/ICSharpCode.Decompiler/Tests/TestRunner.cs +++ b/ICSharpCode.Decompiler/Tests/TestRunner.cs @@ -28,6 +28,12 @@ namespace ICSharpCode.Decompiler.Tests } } + [Test] + public void FloatComparisons() + { + TestCompileDecompileCompileOutputAll("FloatComparisons.cs"); + } + [Test] public void HelloWorld() {