Browse Source

First attempt at better cast handling

pull/728/head
Daniel Grunwald 9 years ago
parent
commit
935f0118d1
  1. 6
      ICSharpCode.Decompiler/CSharp/Annotations.cs
  2. 176
      ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
  3. 98
      ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs
  4. 14
      ICSharpCode.Decompiler/IL/ILReader.cs
  5. 64
      ICSharpCode.Decompiler/IL/ILTypeExtensions.cs
  6. 13
      ICSharpCode.Decompiler/IL/Instructions/Conv.cs
  7. 14
      ICSharpCode.Decompiler/Tests/RoundtripAssembly.cs
  8. 58
      ICSharpCode.Decompiler/TypeSystem/TypeUtils.cs

6
ICSharpCode.Decompiler/CSharp/Annotations.cs

@ -80,6 +80,12 @@ namespace ICSharpCode.Decompiler.CSharp
return new TranslatedExpression(expression.Expression, expression.ResolveResult); return new TranslatedExpression(expression.Expression, expression.ResolveResult);
} }
public static TranslatedExpression WithILInstruction(this TranslatedExpression expression, ILInstruction instruction)
{
expression.Expression.AddAnnotation(instruction);
return expression;
}
public static TranslatedExpression WithoutILInstruction(this ExpressionWithResolveResult expression) public static TranslatedExpression WithoutILInstruction(this ExpressionWithResolveResult expression)
{ {
return new TranslatedExpression(expression.Expression, expression.ResolveResult); return new TranslatedExpression(expression.Expression, expression.ResolveResult);

176
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -42,9 +42,21 @@ namespace ICSharpCode.Decompiler.CSharp
/// Every translated expression must have: /// Every translated expression must have:
/// * an ILInstruction annotation /// * an ILInstruction annotation
/// * a ResolveResult annotation /// * a ResolveResult annotation
/// * The type of the ResolveResult must match the StackType of the corresponding ILInstruction. /// Post-condition for Translate() calls:
/// * If the type of the ResolveResult is <c>sbyte</c> or <c>short</c>, the evaluated value of the ILInstruction /// * The type of the ResolveResult must match the StackType of the corresponding ILInstruction,
/// can be obtained by evaluating the C# expression and sign-extending the result to <c>int</c>. /// except that the width of integer types does not need to match (I4, I and I8 count as the same stack type here)
/// * Evaluating the resulting C# expression shall produce the same side effects as evaluating the ILInstruction.
/// * If the IL instruction has <c>ResultType == StackType.Void</c>, the C# expression may evaluate to an arbitrary type and value.
/// * Otherwise, evaluating the resulting C# expression shall produce a similar value as evaluating the ILInstruction.
/// * If the IL instruction evaluates to an integer stack type (I4, I, or I8),
/// the C# type of the resulting expression shall also be an integer (or enum/pointer/char/bool) type.
/// * If sizeof(C# type) == sizeof(IL stack type), the values must be the same.
/// * If sizeof(C# type) > sizeof(IL stack type), the C# value truncated to the width of the IL stack type must equal the IL value.
/// * If sizeof(C# type) &lt; sizeof(IL stack type), the C# value (sign/zero-)extended to the width of the IL stack type
/// must equal the IL value.
/// Whether sign or zero extension is used depends on the sign of the C# type (as determined by <c>IType.GetSign()</c>).
/// * If the IL instruction evaluates to a non-integer type, the C# type of the resulting expression shall match the IL stack type,
/// and the evaluated values shall be the same.
/// </remarks> /// </remarks>
class ExpressionBuilder : ILVisitor<TranslatedExpression> class ExpressionBuilder : ILVisitor<TranslatedExpression>
{ {
@ -95,7 +107,16 @@ namespace ICSharpCode.Decompiler.CSharp
{ {
Debug.Assert(inst != null); Debug.Assert(inst != null);
var cexpr = inst.AcceptVisitor(this); var cexpr = inst.AcceptVisitor(this);
Debug.Assert(cexpr.Type.GetStackType() == inst.ResultType || cexpr.Type.Kind == TypeKind.Unknown || inst.ResultType == StackType.Void); #if DEBUG
if (inst.ResultType != StackType.Void && cexpr.Type.Kind != TypeKind.Unknown) {
if (inst.ResultType.IsIntegerType()) {
Debug.Assert(cexpr.Type.GetStackType().IsIntegerType(), "IL instructions of integer type must convert into C# expressions of integer type");
Debug.Assert(cexpr.Type.GetSign() != Sign.None, "Must have a sign specified for zero/sign-extension");
} else {
Debug.Assert(cexpr.Type.GetStackType() == inst.ResultType);
}
}
#endif
return cexpr; return cexpr;
} }
@ -591,28 +612,133 @@ namespace ICSharpCode.Decompiler.CSharp
protected internal override TranslatedExpression VisitConv(Conv inst) protected internal override TranslatedExpression VisitConv(Conv inst)
{ {
var arg = Translate(inst.Argument); var arg = Translate(inst.Argument);
if (inst.Kind == ConversionKind.StopGCTracking && arg.Type.Kind == TypeKind.ByReference) { StackType inputStackType = inst.Argument.ResultType;
// cast to corresponding pointer type: // Note: we're dealing with two conversions here:
var pointerType = new PointerType(((ByReferenceType)arg.Type).ElementType); // a) the implicit conversion from `arg.Type` to `inputStackType`
arg = arg.ConvertTo(pointerType, this); // (due to the ExpressionBuilder post-condition being flexible with regards to the integer type width)
// if we want a native int, just return the pointer directly // If this is a widening conversion, I'm calling the argument C# type "oversized".
if (inst.ResultType == StackType.I) // If this is a narrowing conversion, I'm calling the argument C# type "undersized".
return arg; // b) the actual conversion instruction from `inputStackType` to `inst.TargetType`
// otherwise, continue converting
} // Also, we need to be very careful with regards to the conversions we emit:
if (inst.Sign != Sign.None && arg.Type.GetSign() != inst.Sign) { // In C#, zero vs. sign-extension depends on the input type,
// we need to cast the input to a type of appropriate sign // but in the ILAst Conv instruction it depends on the output type.
var inputType = inst.Argument.ResultType.ToKnownTypeCode(inst.Sign); switch (inst.Kind) {
arg = arg.ConvertTo(compilation.FindType(inputType), this); case ConversionKind.StopGCTracking:
} if (arg.Type.Kind == TypeKind.ByReference) {
var targetType = compilation.FindType(inst.TargetType.ToKnownTypeCode()); // cast to corresponding pointer type:
ExpressionWithResolveResult castExpr = arg.ConvertTo(targetType, this, inst.CheckForOverflow, addUncheckedAnnotations: false); var pointerType = new PointerType(((ByReferenceType)arg.Type).ElementType);
if (inst.Kind == ConversionKind.Nop || inst.Kind == ConversionKind.Truncate || inst.Kind == ConversionKind.FloatToInt return arg.ConvertTo(pointerType, this).WithILInstruction(inst);
|| (inst.Kind == ConversionKind.ZeroExtend && arg.Type.GetSign() == Sign.Signed)) } else {
{ Debug.Fail("ConversionKind.StopGCTracking should only be used with managed references");
castExpr.Expression.AddAnnotation(inst.CheckForOverflow ? AddCheckedBlocks.CheckedAnnotation : AddCheckedBlocks.UncheckedAnnotation); goto default;
}
case ConversionKind.SignExtend:
// Sign extension is easy because it can never fail due to overflow checking.
// We just need to ensure the input type before the conversion is signed.
// Also, if the argument was translated into an oversized C# type,
// we need to perform the truncatation to the input stack type.
if (arg.Type.GetSign() != Sign.Signed || arg.Type.GetSize() > inputStackType.GetSize()) {
// Note that an undersized C# type is handled just fine:
// If it is unsigned we'll zero-extend it to the width of the inputStackType here,
// and it is signed we just combine the two sign-extensions into a single sign-extending conversion.
arg = arg.ConvertTo(compilation.FindType(inputStackType.ToKnownTypeCode(Sign.Signed)), this);
}
// Then, we can just return the argument as-is: the ExpressionBuilder post-condition allows us
// to force our parent instruction to handle the actual sign-extension conversion.
// (our caller may have more information to pick a better fitting target type)
return arg.WithILInstruction(inst);
case ConversionKind.ZeroExtend:
// Zero extension may involve an overflow check.
if (!inst.CheckForOverflow || inst.InputSign == Sign.Unsigned) {
// If overflow check cannot fail, handle this just like sign extension (except for swapped signs)
if (arg.Type.GetSign() != Sign.Unsigned || arg.Type.GetSize() > inputStackType.GetSize()) {
arg = arg.ConvertTo(compilation.FindType(inputStackType.ToKnownTypeCode(Sign.Unsigned)), this);
}
return arg.WithILInstruction(inst);
}
// Zero extension that should fail if the input type is negative.
// Split this conversion into steps:
// 1) perform input truncation if necessary, and perform unchecked cast to signed input type
if (arg.Type.GetSize() > inputStackType.GetSize() || arg.Type.GetSign() != inst.InputSign) {
arg = arg.ConvertTo(compilation.FindType(inputStackType.ToKnownTypeCode(inst.InputSign)), this);
}
// 2) perform sign conversion to unsigned with overflow check (input zero/sign extension can be combined with this step),
arg = arg.ConvertTo(compilation.FindType(inputStackType.ToKnownTypeCode(Sign.Unsigned)), this, true);
// 3) leave the actual zero extension to our caller.
return arg.WithILInstruction(inst);
case ConversionKind.Nop:
// Conversion between two types of same size; possibly sign-changing; may involve overflow checking.
if (!inst.CheckForOverflow || inst.InputSign == inst.TargetType.GetSign()) {
// a true nop: no need to generate any C# code
return arg.WithILInstruction(inst);
}
// sign-changing, overflow-checking conversion
// If the input conversion is a truncation, we need to perform it (without overflow-checking).
// If the argument has a different type than that expected by the overflow check,
// we need to ensure we use the correct input type.
if (arg.Type.GetSize() > inputStackType.GetSize() || arg.Type.GetSign() != inst.InputSign) {
arg = arg.ConvertTo(compilation.FindType(inputStackType.ToKnownTypeCode(inst.InputSign)), this);
}
// Note that an input conversion that is a sign/zero-extension can be combined with the
// overflow-checking conversion
goto default; // Perform the actual overflow-checking conversion
case ConversionKind.Truncate:
// Note: there are three sizes involved here:
// A = arg.Type.GetSize()
// B = inputStackType.GetSize()
// C = inst.TargetType.GetSize().
// We know that C <= B (otherwise this wouldn't be the truncation case).
// 1) If C < B < A, we just combine the two truncations into one.
// 2) If C < B = A, there's no input conversion, just the truncation
// 3) If C <= A < B, all the extended bits get removed again by the truncation.
// 4) If A < C < B, some extended bits remain even after truncation.
// In cases 1-3, the overall conversion is a truncation or no-op.
// In case 4, the overall conversion is a zero/sign extension, but to a smaller
// size than the original conversion.
if (!inst.CheckForOverflow) {
// Truncation without overflow check.
if (inst.TargetType.IsSmallIntegerType()) {
// If the target type is a small integer type, IL will implicitly sign- or zero-extend
// the result after the truncation back to StackType.I4.
// (which means there's actually 3 conversions involved!)
if (arg.Type.GetSize() <= inst.TargetType.GetSize() && arg.Type.GetSign() == inst.TargetType.GetSign()) {
// There's no actual truncation involved, and the result of the Conv instruciton is extended
// the same way as the original instruction
// -> we can return arg directly
return arg.WithILInstruction(inst);
} else {
// We need to actually truncate; *or* we need to change the sign for the remaining extension to I4.
goto default; // Emit simple cast to inst.TargetType
}
} else {
Debug.Assert(inst.TargetType.GetSize() == inst.ResultType.GetSize());
// For non-small integer types, we can let the whole unchecked truncation
// get handled by our caller (using the ExpressionBuilder post-condition).
// Case 4 (left-over extension from implicit conversion) can also be handled by our caller.
return arg.WithILInstruction(inst);
}
}
// Truncation with overflow check
// Similar to nop-case: perform input truncation without overflow checking + ensure correct input sign.
if (arg.Type.GetSize() > inputStackType.GetSize() || arg.Type.GetSign() != inst.InputSign) {
arg = arg.ConvertTo(compilation.FindType(inputStackType.ToKnownTypeCode(inst.InputSign)), this);
}
goto default; // perform cast with overflow-check
case ConversionKind.IntToFloat: // TODO: handle these conversions correctly
case ConversionKind.FloatToInt:
case ConversionKind.FloatPrecisionChange:
default:
return arg.ConvertTo(compilation.FindType(inst.TargetType.ToKnownTypeCode()), this, inst.CheckForOverflow)
.WithILInstruction(inst);
} }
return castExpr.WithILInstruction(inst);
} }
protected internal override TranslatedExpression VisitCall(Call inst) protected internal override TranslatedExpression VisitCall(Call inst)

98
ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs

@ -20,6 +20,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using ICSharpCode.Decompiler.CSharp.Transforms;
using ICSharpCode.NRefactory.CSharp; using ICSharpCode.NRefactory.CSharp;
using ICSharpCode.NRefactory.Semantics; using ICSharpCode.NRefactory.Semantics;
using ICSharpCode.NRefactory.TypeSystem; using ICSharpCode.NRefactory.TypeSystem;
@ -89,6 +90,7 @@ namespace ICSharpCode.Decompiler.CSharp
/// TranslatedExpression as a separate type is still useful to ensure that no case in the expression builder /// TranslatedExpression as a separate type is still useful to ensure that no case in the expression builder
/// forgets to add the annotation. /// forgets to add the annotation.
/// </remarks> /// </remarks>
[DebuggerDisplay("{Expression} : {ResolveResult}")]
struct TranslatedExpression struct TranslatedExpression
{ {
public readonly Expression Expression; public readonly Expression Expression;
@ -153,32 +155,74 @@ namespace ICSharpCode.Decompiler.CSharp
throw new ArgumentException("descendant must be a descendant of the current node"); throw new ArgumentException("descendant must be a descendant of the current node");
} }
public TranslatedExpression ConvertTo(IType targetType, ExpressionBuilder expressionBuilder, bool checkForOverflow = false, bool addUncheckedAnnotations = true) /// <summary>
/// Adds casts (if necessary) to convert this expression to the specified target type.
/// </summary>
/// <remarks>
/// If the target type is narrower than the source type, the value is truncated.
/// If the target type is wider than the source type, the value is sign- or zero-extended based on the
/// sign of the source type.
/// This fits with the ExpressionBuilder's post-condition, so e.g. an assignment can simply
/// call <c>Translate(stloc.Value).ConvertTo(stloc.Variable.Type)</c> and have the overall C# semantics match the IL semantics.
/// </remarks>
public TranslatedExpression ConvertTo(IType targetType, ExpressionBuilder expressionBuilder, bool checkForOverflow = false)
{ {
var type = this.Type; var type = this.Type;
if (type.Equals(targetType)) if (type.Equals(targetType))
return this; return this;
var compilation = expressionBuilder.compilation; var compilation = expressionBuilder.compilation;
if (type.IsKnownType(KnownTypeCode.Boolean) && targetType.GetStackType().IsIntegerType()) {
// convert from boolean to integer (or enum)
return new ConditionalExpression(
this.Expression,
LdcI4(compilation, 1).ConvertTo(targetType, expressionBuilder, checkForOverflow),
LdcI4(compilation, 0).ConvertTo(targetType, expressionBuilder, checkForOverflow)
).WithoutILInstruction().WithRR(new ResolveResult(targetType));
}
// Special-case IntPtr and UIntPtr: they behave slightly weird, e.g. converting to them always checks for overflow,
// but converting from them never checks for overflow.
if (checkForOverflow && type.IsKnownType(KnownTypeCode.IntPtr) && !targetType.IsKnownType(KnownTypeCode.Int64)) {
// Convert through `long` instead.
return this.ConvertTo(compilation.FindType(KnownTypeCode.Int64), expressionBuilder, checkForOverflow)
.ConvertTo(targetType, expressionBuilder, checkForOverflow);
} else if (checkForOverflow && type.IsKnownType(KnownTypeCode.UIntPtr) && !targetType.IsKnownType(KnownTypeCode.UInt64)) {
// Convert through `ulong` instead.
return this.ConvertTo(compilation.FindType(KnownTypeCode.UInt64), expressionBuilder, checkForOverflow)
.ConvertTo(targetType, expressionBuilder, checkForOverflow);
}
if (targetType.IsKnownType(KnownTypeCode.Boolean)) { if (targetType.IsKnownType(KnownTypeCode.Boolean)) {
// convert to boolean through byte, to simulate the truncation to 8 bits // convert to boolean through byte, to simulate the truncation to 8 bits
return this.ConvertTo(compilation.FindType(KnownTypeCode.Byte), expressionBuilder, checkForOverflow, addUncheckedAnnotations) return this.ConvertTo(compilation.FindType(KnownTypeCode.Byte), expressionBuilder, checkForOverflow)
.ConvertToBoolean(expressionBuilder); .ConvertToBoolean(expressionBuilder);
} }
if ((targetType.IsKnownType(KnownTypeCode.IntPtr) || targetType.IsKnownType(KnownTypeCode.UIntPtr)) && type.Kind != TypeKind.Pointer) { if ((targetType.IsKnownType(KnownTypeCode.IntPtr) || targetType.IsKnownType(KnownTypeCode.UIntPtr))
// (u)long -> IntPtr/UIntPtr casts in C# throw overflow exceptions, even in unchecked context. && type.Kind != TypeKind.Pointer && !checkForOverflow)
// To avoid those, convert via void*. {
return this.ConvertTo(new PointerType(compilation.FindType(KnownTypeCode.Void)), expressionBuilder, checkForOverflow, addUncheckedAnnotations) // (u)long -> (U)IntPtr casts in C# can throw overflow exceptions in 32-bit mode, even in unchecked context.
.ConvertTo(targetType, expressionBuilder, checkForOverflow, addUncheckedAnnotations); // To avoid those, convert via `void*`.
return this.ConvertTo(new PointerType(compilation.FindType(KnownTypeCode.Void)), expressionBuilder, checkForOverflow)
.ConvertTo(targetType, expressionBuilder, checkForOverflow);
} }
if (targetType.Kind == TypeKind.Pointer && type.Kind == TypeKind.Enum) { if (targetType.Kind == TypeKind.Pointer && type.Kind == TypeKind.Enum) {
// enum to pointer: convert via underlying type // enum to pointer: C# doesn't allow such casts
return this.ConvertTo(type.GetEnumUnderlyingType(), expressionBuilder, checkForOverflow, addUncheckedAnnotations) // -> convert via underlying type
.ConvertTo(targetType, expressionBuilder, checkForOverflow, addUncheckedAnnotations); return this.ConvertTo(type.GetEnumUnderlyingType(), expressionBuilder, checkForOverflow)
} else if (targetType.Kind == TypeKind.Pointer && (type.IsKnownType(KnownTypeCode.Boolean) || type.IsKnownType(KnownTypeCode.Char))) { .ConvertTo(targetType, expressionBuilder, checkForOverflow);
// bool/char to pointer: convert via uint } else if (targetType.Kind == TypeKind.Enum && type.Kind == TypeKind.Pointer) {
return this.ConvertTo(compilation.FindType(KnownTypeCode.UInt32), expressionBuilder, checkForOverflow, addUncheckedAnnotations) // pointer to enum: C# doesn't allow such casts
.ConvertTo(targetType, expressionBuilder, checkForOverflow, addUncheckedAnnotations); // -> convert via underlying type
} else if (targetType.Kind == TypeKind.Pointer && type.Kind == TypeKind.ByReference && Expression is DirectionExpression) { return this.ConvertTo(targetType.GetEnumUnderlyingType(), expressionBuilder, checkForOverflow)
.ConvertTo(targetType, expressionBuilder, checkForOverflow);
}
if (targetType.Kind == TypeKind.Pointer && type.IsKnownType(KnownTypeCode.Char)
|| targetType.IsKnownType(KnownTypeCode.Char) && type.Kind == TypeKind.Pointer) {
// char <-> pointer: C# doesn't allow such casts
// -> convert via ushort
return this.ConvertTo(compilation.FindType(KnownTypeCode.UInt16), expressionBuilder, checkForOverflow)
.ConvertTo(targetType, expressionBuilder, checkForOverflow);
}
if (targetType.Kind == TypeKind.Pointer && type.Kind == TypeKind.ByReference && Expression is DirectionExpression) {
// convert from reference to pointer // convert from reference to pointer
Expression arg = ((DirectionExpression)Expression).Expression.Detach(); Expression arg = ((DirectionExpression)Expression).Expression.Detach();
var pointerType = new PointerType(((ByReferenceType)type).ElementType); var pointerType = new PointerType(((ByReferenceType)type).ElementType);
@ -192,7 +236,7 @@ namespace ICSharpCode.Decompiler.CSharp
// Convert from integer/pointer to reference. // Convert from integer/pointer to reference.
// First, convert to the corresponding pointer type: // First, convert to the corresponding pointer type:
var elementType = ((ByReferenceType)targetType).ElementType; var elementType = ((ByReferenceType)targetType).ElementType;
var arg = this.ConvertTo(new PointerType(elementType), expressionBuilder, checkForOverflow, addUncheckedAnnotations); var arg = this.ConvertTo(new PointerType(elementType), expressionBuilder, checkForOverflow);
// Then dereference the pointer: // Then dereference the pointer:
var derefExpr = new UnaryOperatorExpression(UnaryOperatorType.Dereference, arg.Expression); var derefExpr = new UnaryOperatorExpression(UnaryOperatorType.Dereference, arg.Expression);
var elementRR = new ResolveResult(elementType); var elementRR = new ResolveResult(elementType);
@ -202,26 +246,19 @@ namespace ICSharpCode.Decompiler.CSharp
.WithoutILInstruction() .WithoutILInstruction()
.WithRR(new ByReferenceResolveResult(elementRR, false)); .WithRR(new ByReferenceResolveResult(elementRR, false));
} }
if (type.IsKnownType(KnownTypeCode.Boolean) && targetType.GetStackType().IsIntegerType()) {
// convert from boolean to integer (or enum)
return new ConditionalExpression(
this.Expression,
LdcI4(compilation, 1).ConvertTo(targetType, expressionBuilder),
LdcI4(compilation, 0).ConvertTo(targetType, expressionBuilder)
).WithoutILInstruction().WithRR(new ResolveResult(targetType));
}
var rr = expressionBuilder.resolver.WithCheckForOverflow(checkForOverflow).ResolveCast(targetType, ResolveResult); var rr = expressionBuilder.resolver.WithCheckForOverflow(checkForOverflow).ResolveCast(targetType, ResolveResult);
if (rr.IsCompileTimeConstant && !rr.IsError) { if (rr.IsCompileTimeConstant && !rr.IsError) {
return expressionBuilder.ConvertConstantValue(rr) return expressionBuilder.ConvertConstantValue(rr)
.WithILInstruction(this.ILInstructions); .WithILInstruction(this.ILInstructions);
} }
if (targetType.Kind == TypeKind.Pointer && 0.Equals(ResolveResult.ConstantValue)) { if (targetType.Kind == TypeKind.Pointer && 0.Equals(ResolveResult.ConstantValue)) {
return new NullReferenceExpression() return new NullReferenceExpression().CastTo(expressionBuilder.ConvertType(targetType))
.WithILInstruction(this.ILInstructions) .WithILInstruction(this.ILInstructions)
.WithRR(new ConstantResolveResult(targetType, null)); .WithRR(new ConstantResolveResult(targetType, null));
} }
return Expression.CastTo(expressionBuilder.ConvertType(targetType)) var castExpr = new CastExpression(expressionBuilder.ConvertType(targetType), Expression);
.WithoutILInstruction().WithRR(rr); castExpr.AddAnnotation(checkForOverflow ? AddCheckedBlocks.CheckedAnnotation : AddCheckedBlocks.UncheckedAnnotation);
return castExpr.WithoutILInstruction().WithRR(rr);
} }
TranslatedExpression LdcI4(ICompilation compilation, int val) TranslatedExpression LdcI4(ICompilation compilation, int val)
@ -231,11 +268,18 @@ namespace ICSharpCode.Decompiler.CSharp
.WithRR(new ConstantResolveResult(compilation.FindType(KnownTypeCode.Int32), val)); .WithRR(new ConstantResolveResult(compilation.FindType(KnownTypeCode.Int32), val));
} }
/// <summary>
/// Converts this expression to a boolean expression.
///
/// Expects that the input expression is an integer expression; produces an expression
/// that returns <c>true</c> iff the integer value is not 0.
/// </summary>
public TranslatedExpression ConvertToBoolean(ExpressionBuilder expressionBuilder) public TranslatedExpression ConvertToBoolean(ExpressionBuilder expressionBuilder)
{ {
if (Type.IsKnownType(KnownTypeCode.Boolean) || Type.Kind == TypeKind.Unknown) { if (Type.IsKnownType(KnownTypeCode.Boolean) || Type.Kind == TypeKind.Unknown) {
return this; return this;
} }
Debug.Assert(Type.GetStackType().IsIntegerType());
IType boolType = expressionBuilder.compilation.FindType(KnownTypeCode.Boolean); IType boolType = expressionBuilder.compilation.FindType(KnownTypeCode.Boolean);
if (ResolveResult.IsCompileTimeConstant && ResolveResult.ConstantValue is int) { if (ResolveResult.IsCompileTimeConstant && ResolveResult.ConstantValue is int) {
bool val = (int)ResolveResult.ConstantValue != 0; bool val = (int)ResolveResult.ConstantValue != 0;

14
ICSharpCode.Decompiler/IL/ILReader.cs

@ -879,7 +879,7 @@ namespace ICSharpCode.Decompiler.IL
ILInstruction inst = Pop(); ILInstruction inst = Pop();
if (expectedType != inst.ResultType) { if (expectedType != inst.ResultType) {
if (expectedType == StackType.I && inst.ResultType == StackType.I4) { if (expectedType == StackType.I && inst.ResultType == StackType.I4) {
inst = new Conv(inst, PrimitiveType.I, false, Sign.Signed); inst = new Conv(inst, PrimitiveType.I, false, Sign.None);
} else { } else {
Warn($"Expected {expectedType}, but got {inst.ResultType}"); Warn($"Expected {expectedType}, but got {inst.ResultType}");
} }
@ -892,7 +892,7 @@ namespace ICSharpCode.Decompiler.IL
ILInstruction inst = Pop(); ILInstruction inst = Pop();
switch (inst.ResultType) { switch (inst.ResultType) {
case StackType.I4: case StackType.I4:
return new Conv(inst, PrimitiveType.I, false, Sign.Signed); return new Conv(inst, PrimitiveType.I, false, Sign.None);
case StackType.I: case StackType.I:
case StackType.Ref: case StackType.Ref:
case StackType.Unknown: case StackType.Unknown:
@ -1087,9 +1087,9 @@ namespace ICSharpCode.Decompiler.IL
var left = Pop(); var left = Pop();
// make the implicit I4->I conversion explicit: // make the implicit I4->I conversion explicit:
if (left.ResultType == StackType.I4 && right.ResultType == StackType.I) { if (left.ResultType == StackType.I4 && right.ResultType == StackType.I) {
left = new Conv(left, PrimitiveType.I, false, Sign.Signed); left = new Conv(left, PrimitiveType.I, false, Sign.None);
} else if (left.ResultType == StackType.I && right.ResultType == StackType.I4) { } else if (left.ResultType == StackType.I && right.ResultType == StackType.I4) {
right = new Conv(right, PrimitiveType.I, false, Sign.Signed); right = new Conv(right, PrimitiveType.I, false, Sign.None);
} }
// Based on Table 4: Binary Comparison or Branch Operation // Based on Table 4: Binary Comparison or Branch Operation
@ -1137,7 +1137,7 @@ namespace ICSharpCode.Decompiler.IL
// introduce explicit comparison with 0 // introduce explicit comparison with 0
condition = new Comp( condition = new Comp(
negate ? ComparisonKind.Equality : ComparisonKind.Inequality, negate ? ComparisonKind.Equality : ComparisonKind.Inequality,
Sign.None, condition, new Conv(new LdcI4(0), PrimitiveType.I, false, Sign.Signed)); Sign.None, condition, new Conv(new LdcI4(0), PrimitiveType.I, false, Sign.None));
break; break;
case StackType.I8: case StackType.I8:
// introduce explicit comparison with 0 // introduce explicit comparison with 0
@ -1197,9 +1197,9 @@ namespace ICSharpCode.Decompiler.IL
if (opCode != OpCode.Shl && opCode != OpCode.Shr) { if (opCode != OpCode.Shl && opCode != OpCode.Shr) {
// make the implicit I4->I conversion explicit: // make the implicit I4->I conversion explicit:
if (left.ResultType == StackType.I4 && right.ResultType == StackType.I) { if (left.ResultType == StackType.I4 && right.ResultType == StackType.I) {
left = new Conv(left, PrimitiveType.I, false, Sign.Signed); left = new Conv(left, PrimitiveType.I, false, Sign.None);
} else if (left.ResultType == StackType.I && right.ResultType == StackType.I4) { } else if (left.ResultType == StackType.I && right.ResultType == StackType.I4) {
right = new Conv(right, PrimitiveType.I, false, Sign.Signed); right = new Conv(right, PrimitiveType.I, false, Sign.None);
} }
} }
return Push(BinaryNumericInstruction.Create(opCode, left, right, checkForOverflow, sign)); return Push(BinaryNumericInstruction.Create(opCode, left, right, checkForOverflow, sign));

64
ICSharpCode.Decompiler/IL/ILTypeExtensions.cs

@ -64,6 +64,70 @@ namespace ICSharpCode.Decompiler.IL
return ((MetadataType)primitiveType).GetStackType(); return ((MetadataType)primitiveType).GetStackType();
} }
public static Sign GetSign(this PrimitiveType primitiveType)
{
switch (primitiveType) {
case PrimitiveType.I1:
case PrimitiveType.I2:
case PrimitiveType.I4:
case PrimitiveType.I8:
case PrimitiveType.R4:
case PrimitiveType.R8:
case PrimitiveType.I:
return Sign.Signed;
case PrimitiveType.U1:
case PrimitiveType.U2:
case PrimitiveType.U4:
case PrimitiveType.U8:
case PrimitiveType.U:
return Sign.Unsigned;
default:
return Sign.None;
}
}
/// <summary>
/// Gets whether the type is a small integer type.
/// Small integer types are:
/// * bool, sbyte, byte, char, short, ushort
/// * any enums that have a small integer type as underlying type
/// </summary>
public static int GetSize(this PrimitiveType type)
{
switch (type) {
case PrimitiveType.I1:
case PrimitiveType.U1:
return 1;
case PrimitiveType.I2:
case PrimitiveType.U2:
return 2;
case PrimitiveType.I4:
case PrimitiveType.U4:
case PrimitiveType.R4:
return 4;
case PrimitiveType.I8:
case PrimitiveType.R8:
case PrimitiveType.U8:
return 8;
case PrimitiveType.I:
case PrimitiveType.U:
return TypeUtils.NativeIntSize;
default:
return 0;
}
}
/// <summary>
/// Gets whether the type is a small integer type.
/// Small integer types are:
/// * bool, sbyte, byte, char, short, ushort
/// * any enums that have a small integer type as underlying type
/// </summary>
public static bool IsSmallIntegerType(this PrimitiveType type)
{
return GetSize(type) < 4;
}
public static bool IsIntegerType(this PrimitiveType primitiveType) public static bool IsIntegerType(this PrimitiveType primitiveType)
{ {
return primitiveType.GetStackType().IsIntegerType(); return primitiveType.GetStackType().IsIntegerType();

13
ICSharpCode.Decompiler/IL/Instructions/Conv.cs

@ -53,11 +53,12 @@ namespace ICSharpCode.Decompiler.IL
FloatPrecisionChange, FloatPrecisionChange,
/// <summary> /// <summary>
/// Conversion of integer type to larger signed integer type. /// Conversion of integer type to larger signed integer type.
/// May involve overflow checking (when converting from U4 to I on 32-bit).
/// </summary> /// </summary>
SignExtend, SignExtend,
/// <summary> /// <summary>
/// Conversion of integer type to larger unsigned integer type. /// Conversion of integer type to larger unsigned integer type.
/// May involve overflow checking (when converting from a small signed type). /// May involve overflow checking (when converting from a signed type).
/// </summary> /// </summary>
ZeroExtend, ZeroExtend,
/// <summary> /// <summary>
@ -104,13 +105,13 @@ namespace ICSharpCode.Decompiler.IL
/// The input sign does not have any effect on whether the conversion zero-extends or sign-extends; /// The input sign does not have any effect on whether the conversion zero-extends or sign-extends;
/// that is purely determined by the <c>TargetType</c>. /// that is purely determined by the <c>TargetType</c>.
/// </remarks> /// </remarks>
public readonly Sign Sign; public readonly Sign InputSign;
public Conv(ILInstruction argument, PrimitiveType targetType, bool checkForOverflow, Sign sign) : base(OpCode.Conv, argument) public Conv(ILInstruction argument, PrimitiveType targetType, bool checkForOverflow, Sign inputSign) : base(OpCode.Conv, argument)
{ {
this.TargetType = targetType; this.TargetType = targetType;
this.CheckForOverflow = checkForOverflow; this.CheckForOverflow = checkForOverflow;
this.Sign = sign; this.InputSign = inputSign;
this.Kind = GetConversionKind(targetType, argument.ResultType); this.Kind = GetConversionKind(targetType, argument.ResultType);
Debug.Assert(this.Kind != ConversionKind.Invalid); Debug.Assert(this.Kind != ConversionKind.Invalid);
} }
@ -207,9 +208,9 @@ namespace ICSharpCode.Decompiler.IL
output.Write(OpCode); output.Write(OpCode);
if (CheckForOverflow) if (CheckForOverflow)
output.Write(".ovf"); output.Write(".ovf");
if (Sign == Sign.Unsigned) if (InputSign == Sign.Unsigned)
output.Write(".unsigned"); output.Write(".unsigned");
else if (Sign == Sign.Signed) else if (InputSign == Sign.Signed)
output.Write(".signed"); output.Write(".signed");
output.Write(' '); output.Write(' ');
output.Write(Argument.ResultType); output.Write(Argument.ResultType);

14
ICSharpCode.Decompiler/Tests/RoundtripAssembly.cs

@ -62,23 +62,19 @@ namespace ICSharpCode.Decompiler.Tests
} }
[Test] [Test]
public void Random_Tests_Implicit_Conversions() public void Implicit_Conversions()
{ {
RunWithOutput("Random Tests\\TestCases", "ImplicitConversions.exe"); RunWithOutput("Random Tests\\TestCases", "ImplicitConversions.exe");
} }
[Test] [Test]
public void Random_Tests_Implicit_Conversions_32() public void Implicit_Conversions_32()
{ {
try { RunWithOutput("Random Tests\\TestCases", "ImplicitConversions_32.exe");
RunWithOutput("Random Tests\\TestCases", "ImplicitConversions_32.exe");
} catch (AssertionException ex) {
Assert.Ignore(ex.Message);
}
} }
[Test] [Test]
public void Random_Tests_Explicit_Conversions() public void Explicit_Conversions()
{ {
try { try {
RunWithOutput("Random Tests\\TestCases", "ExplicitConversions.exe"); RunWithOutput("Random Tests\\TestCases", "ExplicitConversions.exe");
@ -88,7 +84,7 @@ namespace ICSharpCode.Decompiler.Tests
} }
[Test] [Test]
public void Random_Tests_Explicit_Conversions_32() public void Explicit_Conversions_32()
{ {
try { try {
RunWithOutput("Random Tests\\TestCases", "ExplicitConversions_32.exe"); RunWithOutput("Random Tests\\TestCases", "ExplicitConversions_32.exe");

58
ICSharpCode.Decompiler/TypeSystem/TypeUtils.cs

@ -24,14 +24,26 @@ namespace ICSharpCode.Decompiler
{ {
public static class TypeUtils public static class TypeUtils
{ {
static int GetNativeSize(IType type) public const int NativeIntSize = 6; // between 4 (Int32) and 8 (Int64)
/// <summary>
/// Gets the size (in bytes) of the input type.
/// Returns <c>NativeIntSize</c> for pointer-sized types.
/// Returns 0 for structs and other types of unknown size.
/// </summary>
public static int GetSize(this IType type)
{ {
const int NativeIntSize = 6; // between 4 (Int32) and 8 (Int64) switch (type.Kind) {
if (type.Kind == TypeKind.Pointer) case TypeKind.Pointer:
return NativeIntSize; case TypeKind.ByReference:
case TypeKind.Class:
return NativeIntSize;
case TypeKind.Enum:
type = type.GetEnumUnderlyingType();
break;
}
var typeForConstant = (type.Kind == TypeKind.Enum) ? type.GetDefinition().EnumUnderlyingType : type; var typeDef = type.GetDefinition();
var typeDef = typeForConstant.GetDefinition();
if (typeDef == null) if (typeDef == null)
return 0; return 0;
switch (typeDef.KnownTypeCode) { switch (typeDef.KnownTypeCode) {
@ -58,9 +70,33 @@ namespace ICSharpCode.Decompiler
return 0; return 0;
} }
/// <summary>
/// Gets the size of the input stack type.
/// </summary>
/// <returns>
/// * 4 for <c>I4</c>,
/// * 8 for <c>I8</c>,
/// * <c>NativeIntSize</c> for <c>I</c> and <c>Ref</c>,
/// * 0 otherwise (O, F, Void, Unknown).
/// </returns>
public static int GetSize(this StackType type)
{
switch (type) {
case StackType.I4:
return 4;
case StackType.I8:
return 8;
case StackType.I:
case StackType.Ref:
return NativeIntSize;
default:
return 0;
}
}
public static IType GetLargerType(IType type1, IType type2) public static IType GetLargerType(IType type1, IType type2)
{ {
return GetNativeSize(type1) >= GetNativeSize(type2) ? type1 : type2; return GetSize(type1) >= GetSize(type2) ? type1 : type2;
} }
/// <summary> /// <summary>
@ -71,7 +107,7 @@ namespace ICSharpCode.Decompiler
/// </summary> /// </summary>
public static bool IsSmallIntegerType(this IType type) public static bool IsSmallIntegerType(this IType type)
{ {
return GetNativeSize(type) < 4; return GetSize(type) < 4;
} }
/// <summary> /// <summary>
@ -114,7 +150,7 @@ namespace ICSharpCode.Decompiler
// 2) Both types are integer types of equal size // 2) Both types are integer types of equal size
StackType memoryStackType = memoryType.GetStackType(); StackType memoryStackType = memoryType.GetStackType();
StackType accessStackType = accessType.GetStackType(); StackType accessStackType = accessType.GetStackType();
return memoryStackType == accessStackType && memoryStackType.IsIntegerType() && GetNativeSize(memoryType) == GetNativeSize(accessType); return memoryStackType == accessStackType && memoryStackType.IsIntegerType() && GetSize(memoryType) == GetSize(accessType);
} }
/// <summary> /// <summary>
@ -174,12 +210,14 @@ namespace ICSharpCode.Decompiler
/// <remarks> /// <remarks>
/// Integer types (including IntPtr/UIntPtr) return the sign as expected. /// Integer types (including IntPtr/UIntPtr) return the sign as expected.
/// Floating point types and <c>decimal</c> are considered to be signed. /// Floating point types and <c>decimal</c> are considered to be signed.
/// <c>char</c> and <c>bool</c> are unsigned. /// <c>char</c>, <c>bool</c> and pointer types (e.g. <c>void*</c>) are unsigned.
/// Enums have a sign based on their underlying type. /// Enums have a sign based on their underlying type.
/// All other types return <c>Sign.None</c>. /// All other types return <c>Sign.None</c>.
/// </remarks> /// </remarks>
public static Sign GetSign(this IType type) public static Sign GetSign(this IType type)
{ {
if (type.Kind == TypeKind.Pointer)
return Sign.Unsigned;
var typeDef = type.GetEnumUnderlyingType().GetDefinition(); var typeDef = type.GetEnumUnderlyingType().GetDefinition();
if (typeDef == null) if (typeDef == null)
return Sign.None; return Sign.None;

Loading…
Cancel
Save