From e266c634dea8829e032d22729c2ce75017f6d309 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sun, 17 Sep 2017 21:31:28 +0200 Subject: [PATCH] [nullables] Add support for lifted binary operators where one of the inputs is nullable. --- BuildTools/tidy.py | 2 + .../TestCases/Pretty/LiftedOperators.cs | 7 +- .../CSharp/ExpressionBuilder.cs | 68 ++++++++++++------- .../IL/ControlFlow/AsyncAwaitDecompiler.cs | 22 +++++- .../Instructions/BinaryNumericInstruction.cs | 52 +++++++++++--- .../IL/Instructions/Conv.cs | 2 +- .../IL/Transforms/NullableLiftingTransform.cs | 40 +++++++++-- 7 files changed, 151 insertions(+), 42 deletions(-) diff --git a/BuildTools/tidy.py b/BuildTools/tidy.py index 34eeb3b42..5f732ce7c 100644 --- a/BuildTools/tidy.py +++ b/BuildTools/tidy.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python + import os, sys def check(filename): diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LiftedOperators.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LiftedOperators.cs index 201f8f163..5f7ac3251 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LiftedOperators.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LiftedOperators.cs @@ -802,7 +802,12 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public long? InArithmetic(uint? b) { - return long.MinValue + b; + return 100L + b; + } + + public long? AfterArithmetic(uint? b) + { + return 100 + b; } static double? InArithmetic2(float? nf, double? nd, float f) diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index 4e10b6edf..9b23742f6 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -216,8 +216,7 @@ namespace ICSharpCode.Decompiler.CSharp var dimensions = inst.Indices.Count; var args = inst.Indices.Select(arg => TranslateArrayIndex(arg)).ToArray(); var expr = new ArrayCreateExpression { Type = ConvertType(inst.Type) }; - var ct = expr.Type as ComposedType; - if (ct != null) { + if (expr.Type is ComposedType ct) { // change "new (int[,])[10] to new int[10][,]" ct.ArraySpecifiers.MoveTo(expr.AdditionalArraySpecifiers); } @@ -599,18 +598,18 @@ namespace ICSharpCode.Decompiler.CSharp var resolverWithOverflowCheck = resolver.WithCheckForOverflow(inst.CheckForOverflow); var left = Translate(inst.Left); var right = Translate(inst.Right); - left = PrepareArithmeticArgument(left, inst.Left.ResultType, inst.Sign); - right = PrepareArithmeticArgument(right, inst.Right.ResultType, inst.Sign); + left = PrepareArithmeticArgument(left, inst.LeftInputType, inst.Sign, inst.IsLifted); + right = PrepareArithmeticArgument(right, inst.RightInputType, inst.Sign, inst.IsLifted); var rr = resolverWithOverflowCheck.ResolveBinaryOperator(op, left.ResolveResult, right.ResolveResult); - if (rr.IsError || rr.Type.GetStackType() != inst.ResultType + if (rr.IsError || NullableType.GetUnderlyingType(rr.Type).GetStackType() != inst.UnderlyingResultType || !IsCompatibleWithSign(left.Type, inst.Sign) || !IsCompatibleWithSign(right.Type, inst.Sign)) { // Left and right operands are incompatible, so convert them to a common type - StackType targetStackType = inst.ResultType == StackType.I ? StackType.I8 : inst.ResultType; + StackType targetStackType = inst.UnderlyingResultType == StackType.I ? StackType.I8 : inst.UnderlyingResultType; IType targetType = compilation.FindType(targetStackType.ToKnownTypeCode(inst.Sign)); - left = left.ConvertTo(targetType, this); - right = right.ConvertTo(targetType, this); + left = left.ConvertTo(NullableType.IsNullable(left.Type) ? NullableType.Create(compilation, targetType) : targetType, this); + right = right.ConvertTo(NullableType.IsNullable(right.Type) ? NullableType.Create(compilation, targetType) : targetType, this); rr = resolverWithOverflowCheck.ResolveBinaryOperator(op, left.ResolveResult, right.ResolveResult); } var resultExpr = new BinaryOperatorExpression(left.Expression, op, right.Expression) @@ -624,18 +623,28 @@ namespace ICSharpCode.Decompiler.CSharp /// /// Handle oversized arguments needing truncation; and avoid IntPtr/pointers in arguments. /// - TranslatedExpression PrepareArithmeticArgument(TranslatedExpression arg, StackType argStackType, Sign sign) + TranslatedExpression PrepareArithmeticArgument(TranslatedExpression arg, StackType argStackType, Sign sign, bool isLifted) { - if (argStackType.IsIntegerType() && argStackType.GetSize() < arg.Type.GetSize()) { + if (isLifted && !NullableType.IsNullable(arg.Type)) { + isLifted = false; // don't cast to nullable if this input wasn't already nullable + } + IType argUType = isLifted ? NullableType.GetUnderlyingType(arg.Type) : arg.Type; + if (argStackType.IsIntegerType() && argStackType.GetSize() < argUType.GetSize()) { // If the argument is oversized (needs truncation to match stack size of its ILInstruction), // perform the truncation now. - arg = arg.ConvertTo(compilation.FindType(argStackType.ToKnownTypeCode(sign)), this); + IType targetType = compilation.FindType(argStackType.ToKnownTypeCode(sign)); + if (isLifted) + targetType = NullableType.Create(compilation, targetType); + arg = arg.ConvertTo(targetType, this); } - if (arg.Type.GetStackType() == StackType.I) { + if (argUType.GetStackType() == StackType.I) { // None of the operators we might want to apply are supported by IntPtr/UIntPtr. // Also, pointer arithmetic has different semantics (works in number of elements, not bytes). // So any inputs of size StackType.I must be converted to long/ulong. - arg = arg.ConvertTo(compilation.FindType(StackType.I8.ToKnownTypeCode(sign)), this); + IType targetType = compilation.FindType(StackType.I8.ToKnownTypeCode(sign)); + if (isLifted) + targetType = NullableType.Create(compilation, targetType); + arg = arg.ConvertTo(targetType, this); } return arg; } @@ -646,7 +655,7 @@ namespace ICSharpCode.Decompiler.CSharp /// static bool IsCompatibleWithSign(IType type, Sign sign) { - return sign == Sign.None || type.GetSign() == sign; + return sign == Sign.None || NullableType.GetUnderlyingType(type).GetSign() == sign; } static bool BinaryOperatorMightCheckForOverflow(BinaryOperatorType op) @@ -669,32 +678,45 @@ namespace ICSharpCode.Decompiler.CSharp var right = Translate(inst.Right); Sign sign = inst.Sign; - if (left.Type.IsCSharpSmallIntegerType() && sign != Sign.Unsigned && inst.ResultType == StackType.I4) { + var leftUType = NullableType.GetUnderlyingType(left.Type); + if (leftUType.IsCSharpSmallIntegerType() && sign != Sign.Unsigned && inst.UnderlyingResultType == StackType.I4) { // With small integer types, C# will promote to int and perform signed shifts. // We thus don't need any casts in this case. } else { // Insert cast to target type. if (sign == Sign.None) { // if we don't need a specific sign, prefer keeping that of the input: - sign = left.Type.GetSign(); + sign = leftUType.GetSign(); } IType targetType; - if (inst.ResultType == StackType.I4) + if (inst.UnderlyingResultType == StackType.I4) { targetType = compilation.FindType(sign == Sign.Unsigned ? KnownTypeCode.UInt32 : KnownTypeCode.Int32); - else + } else { targetType = compilation.FindType(sign == Sign.Unsigned ? KnownTypeCode.UInt64 : KnownTypeCode.Int64); + } + if (NullableType.IsNullable(left.Type)) { + targetType = NullableType.Create(compilation, targetType); + } left = left.ConvertTo(targetType, this); } + // Shift operators in C# always expect type 'int' on the right-hand-side - right = right.ConvertTo(compilation.FindType(KnownTypeCode.Int32), this); - + if (NullableType.IsNullable(right.Type)) { + right = right.ConvertTo(NullableType.Create(compilation, compilation.FindType(KnownTypeCode.Int32)), this); + } else { + right = right.ConvertTo(compilation.FindType(KnownTypeCode.Int32), this); + } + TranslatedExpression result = new BinaryOperatorExpression(left.Expression, op, right.Expression) .WithILInstruction(inst) .WithRR(resolver.ResolveBinaryOperator(op, left.ResolveResult, right.ResolveResult)); - if (inst.ResultType == StackType.I) { + if (inst.UnderlyingResultType == StackType.I) { // C# doesn't have shift operators for IntPtr, so we first shifted a long/ulong, // and now have to case back down to IntPtr/UIntPtr: - result = result.ConvertTo(compilation.FindType(sign == Sign.Unsigned ? KnownTypeCode.UIntPtr : KnownTypeCode.IntPtr), this); + IType targetType = compilation.FindType(sign == Sign.Unsigned ? KnownTypeCode.UIntPtr : KnownTypeCode.IntPtr); + if (inst.IsLifted) + targetType = NullableType.Create(compilation, targetType); + result = result.ConvertTo(targetType, this); } return result; } @@ -731,7 +753,7 @@ namespace ICSharpCode.Decompiler.CSharp { var target = Translate(inst.Target); var value = Translate(inst.Value); - value = PrepareArithmeticArgument(value, inst.Value.ResultType, inst.Sign); + value = PrepareArithmeticArgument(value, inst.Value.ResultType, inst.Sign, isLifted: false); TranslatedExpression resultExpr; if (inst.CompoundAssignmentType == CompoundAssignmentType.EvaluatesToOldValue) { diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs b/ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs index 7b41e7769..a72e20c2a 100644 --- a/ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs +++ b/ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs @@ -1,4 +1,22 @@ -using ICSharpCode.Decompiler.CSharp; +// Copyright (c) 2017 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 ICSharpCode.Decompiler.CSharp; using ICSharpCode.Decompiler.IL.Transforms; using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.Util; @@ -6,8 +24,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace ICSharpCode.Decompiler.IL.ControlFlow { diff --git a/ICSharpCode.Decompiler/IL/Instructions/BinaryNumericInstruction.cs b/ICSharpCode.Decompiler/IL/Instructions/BinaryNumericInstruction.cs index ef9fa0543..f61b1b855 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/BinaryNumericInstruction.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/BinaryNumericInstruction.cs @@ -37,7 +37,7 @@ namespace ICSharpCode.Decompiler.IL ShiftRight } - public partial class BinaryNumericInstruction : BinaryInstruction + public partial class BinaryNumericInstruction : BinaryInstruction, ILiftableInstruction { /// /// Gets whether the instruction checks for overflow. @@ -50,12 +50,30 @@ namespace ICSharpCode.Decompiler.IL /// For instructions that produce the same result for either sign, returns Sign.None. /// public readonly Sign Sign; - + + public readonly StackType LeftInputType; + public readonly StackType RightInputType; + /// /// The operator used by this binary operator instruction. /// public readonly BinaryNumericOperator Operator; - + + /// + /// Gets whether this conversion is a lifted nullable operation. + /// + /// + /// A lifted binary operation allows its arguments to be a value of type Nullable{T}, where + /// T.GetStackType() == [Left|Right]InputType. + /// If both input values is non-null: + /// * they are sign/zero-extended to the corresponding InputType (based on T's sign) + /// * the underlying numeric operator is applied + /// * the result is wrapped in a Nullable{UnderlyingResultType}. + /// If either input is null, the instruction evaluates to default(UnderlyingResultType?). + /// (this result type is underspecified, since there may be multiple C# types for the stack type) + /// + public bool IsLifted { get; set; } + readonly StackType resultType; public BinaryNumericInstruction(BinaryNumericOperator op, ILInstruction left, ILInstruction right, bool checkForOverflow, Sign sign) @@ -64,7 +82,9 @@ namespace ICSharpCode.Decompiler.IL this.CheckForOverflow = checkForOverflow; this.Sign = sign; this.Operator = op; - this.resultType = ComputeResultType(op, left.ResultType, right.ResultType); + this.LeftInputType = left.ResultType; + this.RightInputType = right.ResultType; + this.resultType = ComputeResultType(op, LeftInputType, RightInputType); Debug.Assert(resultType != StackType.Unknown); } @@ -91,9 +111,18 @@ namespace ICSharpCode.Decompiler.IL return StackType.Unknown; } + public StackType UnderlyingResultType { get => resultType; } + public sealed override StackType ResultType { - get { - return resultType; + get => IsLifted ? StackType.O : resultType; + } + + internal override void CheckInvariant(ILPhase phase) + { + base.CheckInvariant(phase); + if (!IsLifted) { + Debug.Assert(LeftInputType == Left.ResultType); + Debug.Assert(RightInputType == Right.ResultType); } } @@ -145,12 +174,17 @@ namespace ICSharpCode.Decompiler.IL { output.Write(OpCode); output.Write("." + GetOperatorName(Operator)); - if (CheckForOverflow) + if (CheckForOverflow) { output.Write(".ovf"); - if (Sign == Sign.Unsigned) + } + if (Sign == Sign.Unsigned) { output.Write(".unsigned"); - else if (Sign == Sign.Signed) + } else if (Sign == Sign.Signed) { output.Write(".signed"); + } + if (IsLifted) { + output.Write(".lifted"); + } output.Write('('); Left.WriteTo(output); output.Write(", "); diff --git a/ICSharpCode.Decompiler/IL/Instructions/Conv.cs b/ICSharpCode.Decompiler/IL/Instructions/Conv.cs index aaf534894..d9b3e3a79 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/Conv.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/Conv.cs @@ -100,7 +100,7 @@ namespace ICSharpCode.Decompiler.IL /// * it is sign/zero-extended to InputType (based on T's sign) /// * the underlying conversion is performed /// * the result is wrapped in a Nullable{TargetType}. - /// If the value is null, the conversion evaluates to null of type Nullable{TargetType}. + /// If the value is null, the conversion evaluates to default(TargetType?). /// (this result type is underspecified, since there may be multiple C# types for the TargetType) /// public bool IsLifted { get; set; } diff --git a/ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs index 769ea0b45..03d822fb3 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs @@ -1,9 +1,22 @@ -using System; -using System.Collections.Generic; +// Copyright (c) 2017 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.Diagnostics; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using ICSharpCode.Decompiler.TypeSystem; namespace ICSharpCode.Decompiler.IL.Transforms @@ -60,6 +73,23 @@ namespace ICSharpCode.Decompiler.IL.Transforms conv.IsLifted = true; return conv; } + } else if (inst is BinaryNumericInstruction binary) { + if (SemanticHelper.IsPure(binary.Right.Flags)) { + var arg = LiftUnary(binary.Left, inputVar, context); + if (arg != null) { + binary.Left = arg; + binary.IsLifted = true; + return binary; + } + } + if (SemanticHelper.IsPure(binary.Left.Flags)) { + var arg = LiftUnary(binary.Right, inputVar, context); + if (arg != null) { + binary.Right = arg; + binary.IsLifted = true; + return binary; + } + } } return null; }