Browse Source

[nullables] Add support for lifted binary operators where one of the inputs is nullable.

pull/863/head
Daniel Grunwald 8 years ago
parent
commit
e266c634de
  1. 2
      BuildTools/tidy.py
  2. 7
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/LiftedOperators.cs
  3. 68
      ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
  4. 22
      ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs
  5. 52
      ICSharpCode.Decompiler/IL/Instructions/BinaryNumericInstruction.cs
  6. 2
      ICSharpCode.Decompiler/IL/Instructions/Conv.cs
  7. 40
      ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs

2
BuildTools/tidy.py

@ -1,3 +1,5 @@
#!/usr/bin/env python
import os, sys import os, sys
def check(filename): def check(filename):

7
ICSharpCode.Decompiler.Tests/TestCases/Pretty/LiftedOperators.cs

@ -802,7 +802,12 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
public long? InArithmetic(uint? b) 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) static double? InArithmetic2(float? nf, double? nd, float f)

68
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -216,8 +216,7 @@ namespace ICSharpCode.Decompiler.CSharp
var dimensions = inst.Indices.Count; var dimensions = inst.Indices.Count;
var args = inst.Indices.Select(arg => TranslateArrayIndex(arg)).ToArray(); var args = inst.Indices.Select(arg => TranslateArrayIndex(arg)).ToArray();
var expr = new ArrayCreateExpression { Type = ConvertType(inst.Type) }; var expr = new ArrayCreateExpression { Type = ConvertType(inst.Type) };
var ct = expr.Type as ComposedType; if (expr.Type is ComposedType ct) {
if (ct != null) {
// change "new (int[,])[10] to new int[10][,]" // change "new (int[,])[10] to new int[10][,]"
ct.ArraySpecifiers.MoveTo(expr.AdditionalArraySpecifiers); ct.ArraySpecifiers.MoveTo(expr.AdditionalArraySpecifiers);
} }
@ -599,18 +598,18 @@ namespace ICSharpCode.Decompiler.CSharp
var resolverWithOverflowCheck = resolver.WithCheckForOverflow(inst.CheckForOverflow); var resolverWithOverflowCheck = resolver.WithCheckForOverflow(inst.CheckForOverflow);
var left = Translate(inst.Left); var left = Translate(inst.Left);
var right = Translate(inst.Right); var right = Translate(inst.Right);
left = PrepareArithmeticArgument(left, inst.Left.ResultType, inst.Sign); left = PrepareArithmeticArgument(left, inst.LeftInputType, inst.Sign, inst.IsLifted);
right = PrepareArithmeticArgument(right, inst.Right.ResultType, inst.Sign); right = PrepareArithmeticArgument(right, inst.RightInputType, inst.Sign, inst.IsLifted);
var rr = resolverWithOverflowCheck.ResolveBinaryOperator(op, left.ResolveResult, right.ResolveResult); 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)) || !IsCompatibleWithSign(left.Type, inst.Sign) || !IsCompatibleWithSign(right.Type, inst.Sign))
{ {
// Left and right operands are incompatible, so convert them to a common type // 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)); IType targetType = compilation.FindType(targetStackType.ToKnownTypeCode(inst.Sign));
left = left.ConvertTo(targetType, this); left = left.ConvertTo(NullableType.IsNullable(left.Type) ? NullableType.Create(compilation, targetType) : targetType, this);
right = right.ConvertTo(targetType, this); right = right.ConvertTo(NullableType.IsNullable(right.Type) ? NullableType.Create(compilation, targetType) : targetType, this);
rr = resolverWithOverflowCheck.ResolveBinaryOperator(op, left.ResolveResult, right.ResolveResult); rr = resolverWithOverflowCheck.ResolveBinaryOperator(op, left.ResolveResult, right.ResolveResult);
} }
var resultExpr = new BinaryOperatorExpression(left.Expression, op, right.Expression) var resultExpr = new BinaryOperatorExpression(left.Expression, op, right.Expression)
@ -624,18 +623,28 @@ namespace ICSharpCode.Decompiler.CSharp
/// <summary> /// <summary>
/// Handle oversized arguments needing truncation; and avoid IntPtr/pointers in arguments. /// Handle oversized arguments needing truncation; and avoid IntPtr/pointers in arguments.
/// </summary> /// </summary>
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), // If the argument is oversized (needs truncation to match stack size of its ILInstruction),
// perform the truncation now. // 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. // 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). // 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. // 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; return arg;
} }
@ -646,7 +655,7 @@ namespace ICSharpCode.Decompiler.CSharp
/// </summary> /// </summary>
static bool IsCompatibleWithSign(IType type, Sign sign) 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) static bool BinaryOperatorMightCheckForOverflow(BinaryOperatorType op)
@ -669,32 +678,45 @@ namespace ICSharpCode.Decompiler.CSharp
var right = Translate(inst.Right); var right = Translate(inst.Right);
Sign sign = inst.Sign; 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. // With small integer types, C# will promote to int and perform signed shifts.
// We thus don't need any casts in this case. // We thus don't need any casts in this case.
} else { } else {
// Insert cast to target type. // Insert cast to target type.
if (sign == Sign.None) { if (sign == Sign.None) {
// if we don't need a specific sign, prefer keeping that of the input: // if we don't need a specific sign, prefer keeping that of the input:
sign = left.Type.GetSign(); sign = leftUType.GetSign();
} }
IType targetType; IType targetType;
if (inst.ResultType == StackType.I4) if (inst.UnderlyingResultType == StackType.I4) {
targetType = compilation.FindType(sign == Sign.Unsigned ? KnownTypeCode.UInt32 : KnownTypeCode.Int32); targetType = compilation.FindType(sign == Sign.Unsigned ? KnownTypeCode.UInt32 : KnownTypeCode.Int32);
else } else {
targetType = compilation.FindType(sign == Sign.Unsigned ? KnownTypeCode.UInt64 : KnownTypeCode.Int64); 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); left = left.ConvertTo(targetType, this);
} }
// Shift operators in C# always expect type 'int' on the right-hand-side // 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) TranslatedExpression result = new BinaryOperatorExpression(left.Expression, op, right.Expression)
.WithILInstruction(inst) .WithILInstruction(inst)
.WithRR(resolver.ResolveBinaryOperator(op, left.ResolveResult, right.ResolveResult)); .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, // 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: // 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; return result;
} }
@ -731,7 +753,7 @@ namespace ICSharpCode.Decompiler.CSharp
{ {
var target = Translate(inst.Target); var target = Translate(inst.Target);
var value = Translate(inst.Value); 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; TranslatedExpression resultExpr;
if (inst.CompoundAssignmentType == CompoundAssignmentType.EvaluatesToOldValue) { if (inst.CompoundAssignmentType == CompoundAssignmentType.EvaluatesToOldValue) {

22
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.IL.Transforms;
using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.Decompiler.Util; using ICSharpCode.Decompiler.Util;
@ -6,8 +24,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ICSharpCode.Decompiler.IL.ControlFlow namespace ICSharpCode.Decompiler.IL.ControlFlow
{ {

52
ICSharpCode.Decompiler/IL/Instructions/BinaryNumericInstruction.cs

@ -37,7 +37,7 @@ namespace ICSharpCode.Decompiler.IL
ShiftRight ShiftRight
} }
public partial class BinaryNumericInstruction : BinaryInstruction public partial class BinaryNumericInstruction : BinaryInstruction, ILiftableInstruction
{ {
/// <summary> /// <summary>
/// Gets whether the instruction checks for overflow. /// 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. /// For instructions that produce the same result for either sign, returns Sign.None.
/// </summary> /// </summary>
public readonly Sign Sign; public readonly Sign Sign;
public readonly StackType LeftInputType;
public readonly StackType RightInputType;
/// <summary> /// <summary>
/// The operator used by this binary operator instruction. /// The operator used by this binary operator instruction.
/// </summary> /// </summary>
public readonly BinaryNumericOperator Operator; public readonly BinaryNumericOperator Operator;
/// <summary>
/// Gets whether this conversion is a lifted nullable operation.
/// </summary>
/// <remarks>
/// 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)
/// </remarks>
public bool IsLifted { get; set; }
readonly StackType resultType; readonly StackType resultType;
public BinaryNumericInstruction(BinaryNumericOperator op, ILInstruction left, ILInstruction right, bool checkForOverflow, Sign sign) public BinaryNumericInstruction(BinaryNumericOperator op, ILInstruction left, ILInstruction right, bool checkForOverflow, Sign sign)
@ -64,7 +82,9 @@ namespace ICSharpCode.Decompiler.IL
this.CheckForOverflow = checkForOverflow; this.CheckForOverflow = checkForOverflow;
this.Sign = sign; this.Sign = sign;
this.Operator = op; 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); Debug.Assert(resultType != StackType.Unknown);
} }
@ -91,9 +111,18 @@ namespace ICSharpCode.Decompiler.IL
return StackType.Unknown; return StackType.Unknown;
} }
public StackType UnderlyingResultType { get => resultType; }
public sealed override StackType ResultType { public sealed override StackType ResultType {
get { get => IsLifted ? StackType.O : resultType;
return 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(OpCode);
output.Write("." + GetOperatorName(Operator)); output.Write("." + GetOperatorName(Operator));
if (CheckForOverflow) if (CheckForOverflow) {
output.Write(".ovf"); output.Write(".ovf");
if (Sign == Sign.Unsigned) }
if (Sign == Sign.Unsigned) {
output.Write(".unsigned"); output.Write(".unsigned");
else if (Sign == Sign.Signed) } else if (Sign == Sign.Signed) {
output.Write(".signed"); output.Write(".signed");
}
if (IsLifted) {
output.Write(".lifted");
}
output.Write('('); output.Write('(');
Left.WriteTo(output); Left.WriteTo(output);
output.Write(", "); output.Write(", ");

2
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) /// * it is sign/zero-extended to InputType (based on T's sign)
/// * the underlying conversion is performed /// * the underlying conversion is performed
/// * the result is wrapped in a Nullable{TargetType}. /// * 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) /// (this result type is underspecified, since there may be multiple C# types for the TargetType)
/// </remarks> /// </remarks>
public bool IsLifted { get; set; } public bool IsLifted { get; set; }

40
ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs

@ -1,9 +1,22 @@
using System; // Copyright (c) 2017 Daniel Grunwald
using System.Collections.Generic; //
// 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.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.TypeSystem;
namespace ICSharpCode.Decompiler.IL.Transforms namespace ICSharpCode.Decompiler.IL.Transforms
@ -60,6 +73,23 @@ namespace ICSharpCode.Decompiler.IL.Transforms
conv.IsLifted = true; conv.IsLifted = true;
return conv; 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; return null;
} }

Loading…
Cancel
Save