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 @@ -80,6 +80,12 @@ namespace ICSharpCode.Decompiler.CSharp
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)
{
return new TranslatedExpression(expression.Expression, expression.ResolveResult);

176
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -42,9 +42,21 @@ namespace ICSharpCode.Decompiler.CSharp @@ -42,9 +42,21 @@ namespace ICSharpCode.Decompiler.CSharp
/// Every translated expression must have:
/// * an ILInstruction annotation
/// * a ResolveResult annotation
/// * The type of the ResolveResult must match the StackType of the corresponding ILInstruction.
/// * If the type of the ResolveResult is <c>sbyte</c> or <c>short</c>, the evaluated value of the ILInstruction
/// can be obtained by evaluating the C# expression and sign-extending the result to <c>int</c>.
/// Post-condition for Translate() calls:
/// * The type of the ResolveResult must match the StackType of the corresponding ILInstruction,
/// 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>
class ExpressionBuilder : ILVisitor<TranslatedExpression>
{
@ -95,7 +107,16 @@ namespace ICSharpCode.Decompiler.CSharp @@ -95,7 +107,16 @@ namespace ICSharpCode.Decompiler.CSharp
{
Debug.Assert(inst != null);
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;
}
@ -591,28 +612,133 @@ namespace ICSharpCode.Decompiler.CSharp @@ -591,28 +612,133 @@ namespace ICSharpCode.Decompiler.CSharp
protected internal override TranslatedExpression VisitConv(Conv inst)
{
var arg = Translate(inst.Argument);
if (inst.Kind == ConversionKind.StopGCTracking && arg.Type.Kind == TypeKind.ByReference) {
// cast to corresponding pointer type:
var pointerType = new PointerType(((ByReferenceType)arg.Type).ElementType);
arg = arg.ConvertTo(pointerType, this);
// if we want a native int, just return the pointer directly
if (inst.ResultType == StackType.I)
return arg;
// otherwise, continue converting
}
if (inst.Sign != Sign.None && arg.Type.GetSign() != inst.Sign) {
// we need to cast the input to a type of appropriate sign
var inputType = inst.Argument.ResultType.ToKnownTypeCode(inst.Sign);
arg = arg.ConvertTo(compilation.FindType(inputType), this);
}
var targetType = compilation.FindType(inst.TargetType.ToKnownTypeCode());
ExpressionWithResolveResult castExpr = arg.ConvertTo(targetType, this, inst.CheckForOverflow, addUncheckedAnnotations: false);
if (inst.Kind == ConversionKind.Nop || inst.Kind == ConversionKind.Truncate || inst.Kind == ConversionKind.FloatToInt
|| (inst.Kind == ConversionKind.ZeroExtend && arg.Type.GetSign() == Sign.Signed))
{
castExpr.Expression.AddAnnotation(inst.CheckForOverflow ? AddCheckedBlocks.CheckedAnnotation : AddCheckedBlocks.UncheckedAnnotation);
StackType inputStackType = inst.Argument.ResultType;
// Note: we're dealing with two conversions here:
// a) the implicit conversion from `arg.Type` to `inputStackType`
// (due to the ExpressionBuilder post-condition being flexible with regards to the integer type width)
// If this is a widening conversion, I'm calling the argument C# type "oversized".
// If this is a narrowing conversion, I'm calling the argument C# type "undersized".
// b) the actual conversion instruction from `inputStackType` to `inst.TargetType`
// Also, we need to be very careful with regards to the conversions we emit:
// In C#, zero vs. sign-extension depends on the input type,
// but in the ILAst Conv instruction it depends on the output type.
switch (inst.Kind) {
case ConversionKind.StopGCTracking:
if (arg.Type.Kind == TypeKind.ByReference) {
// cast to corresponding pointer type:
var pointerType = new PointerType(((ByReferenceType)arg.Type).ElementType);
return arg.ConvertTo(pointerType, this).WithILInstruction(inst);
} else {
Debug.Fail("ConversionKind.StopGCTracking should only be used with managed references");
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)

98
ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs

@ -20,6 +20,7 @@ using System; @@ -20,6 +20,7 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using ICSharpCode.Decompiler.CSharp.Transforms;
using ICSharpCode.NRefactory.CSharp;
using ICSharpCode.NRefactory.Semantics;
using ICSharpCode.NRefactory.TypeSystem;
@ -89,6 +90,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -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
/// forgets to add the annotation.
/// </remarks>
[DebuggerDisplay("{Expression} : {ResolveResult}")]
struct TranslatedExpression
{
public readonly Expression Expression;
@ -153,32 +155,74 @@ namespace ICSharpCode.Decompiler.CSharp @@ -153,32 +155,74 @@ namespace ICSharpCode.Decompiler.CSharp
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;
if (type.Equals(targetType))
return this;
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)) {
// 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);
}
if ((targetType.IsKnownType(KnownTypeCode.IntPtr) || targetType.IsKnownType(KnownTypeCode.UIntPtr)) && type.Kind != TypeKind.Pointer) {
// (u)long -> IntPtr/UIntPtr casts in C# throw overflow exceptions, even in unchecked context.
// To avoid those, convert via void*.
return this.ConvertTo(new PointerType(compilation.FindType(KnownTypeCode.Void)), expressionBuilder, checkForOverflow, addUncheckedAnnotations)
.ConvertTo(targetType, expressionBuilder, checkForOverflow, addUncheckedAnnotations);
if ((targetType.IsKnownType(KnownTypeCode.IntPtr) || targetType.IsKnownType(KnownTypeCode.UIntPtr))
&& type.Kind != TypeKind.Pointer && !checkForOverflow)
{
// (u)long -> (U)IntPtr casts in C# can throw overflow exceptions in 32-bit mode, even in unchecked context.
// 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) {
// enum to pointer: convert via underlying type
return this.ConvertTo(type.GetEnumUnderlyingType(), expressionBuilder, checkForOverflow, addUncheckedAnnotations)
.ConvertTo(targetType, expressionBuilder, checkForOverflow, addUncheckedAnnotations);
} else if (targetType.Kind == TypeKind.Pointer && (type.IsKnownType(KnownTypeCode.Boolean) || type.IsKnownType(KnownTypeCode.Char))) {
// bool/char to pointer: convert via uint
return this.ConvertTo(compilation.FindType(KnownTypeCode.UInt32), expressionBuilder, checkForOverflow, addUncheckedAnnotations)
.ConvertTo(targetType, expressionBuilder, checkForOverflow, addUncheckedAnnotations);
} else if (targetType.Kind == TypeKind.Pointer && type.Kind == TypeKind.ByReference && Expression is DirectionExpression) {
// enum to pointer: C# doesn't allow such casts
// -> convert via underlying type
return this.ConvertTo(type.GetEnumUnderlyingType(), expressionBuilder, checkForOverflow)
.ConvertTo(targetType, expressionBuilder, checkForOverflow);
} else if (targetType.Kind == TypeKind.Enum && type.Kind == TypeKind.Pointer) {
// pointer to enum: C# doesn't allow such casts
// -> convert via underlying type
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
Expression arg = ((DirectionExpression)Expression).Expression.Detach();
var pointerType = new PointerType(((ByReferenceType)type).ElementType);
@ -192,7 +236,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -192,7 +236,7 @@ namespace ICSharpCode.Decompiler.CSharp
// Convert from integer/pointer to reference.
// First, convert to the corresponding pointer type:
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:
var derefExpr = new UnaryOperatorExpression(UnaryOperatorType.Dereference, arg.Expression);
var elementRR = new ResolveResult(elementType);
@ -202,26 +246,19 @@ namespace ICSharpCode.Decompiler.CSharp @@ -202,26 +246,19 @@ namespace ICSharpCode.Decompiler.CSharp
.WithoutILInstruction()
.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);
if (rr.IsCompileTimeConstant && !rr.IsError) {
return expressionBuilder.ConvertConstantValue(rr)
.WithILInstruction(this.ILInstructions);
}
if (targetType.Kind == TypeKind.Pointer && 0.Equals(ResolveResult.ConstantValue)) {
return new NullReferenceExpression()
return new NullReferenceExpression().CastTo(expressionBuilder.ConvertType(targetType))
.WithILInstruction(this.ILInstructions)
.WithRR(new ConstantResolveResult(targetType, null));
}
return Expression.CastTo(expressionBuilder.ConvertType(targetType))
.WithoutILInstruction().WithRR(rr);
var castExpr = new CastExpression(expressionBuilder.ConvertType(targetType), Expression);
castExpr.AddAnnotation(checkForOverflow ? AddCheckedBlocks.CheckedAnnotation : AddCheckedBlocks.UncheckedAnnotation);
return castExpr.WithoutILInstruction().WithRR(rr);
}
TranslatedExpression LdcI4(ICompilation compilation, int val)
@ -231,11 +268,18 @@ namespace ICSharpCode.Decompiler.CSharp @@ -231,11 +268,18 @@ namespace ICSharpCode.Decompiler.CSharp
.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)
{
if (Type.IsKnownType(KnownTypeCode.Boolean) || Type.Kind == TypeKind.Unknown) {
return this;
}
Debug.Assert(Type.GetStackType().IsIntegerType());
IType boolType = expressionBuilder.compilation.FindType(KnownTypeCode.Boolean);
if (ResolveResult.IsCompileTimeConstant && ResolveResult.ConstantValue is int) {
bool val = (int)ResolveResult.ConstantValue != 0;

14
ICSharpCode.Decompiler/IL/ILReader.cs

@ -879,7 +879,7 @@ namespace ICSharpCode.Decompiler.IL @@ -879,7 +879,7 @@ namespace ICSharpCode.Decompiler.IL
ILInstruction inst = Pop();
if (expectedType != inst.ResultType) {
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 {
Warn($"Expected {expectedType}, but got {inst.ResultType}");
}
@ -892,7 +892,7 @@ namespace ICSharpCode.Decompiler.IL @@ -892,7 +892,7 @@ namespace ICSharpCode.Decompiler.IL
ILInstruction inst = Pop();
switch (inst.ResultType) {
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.Ref:
case StackType.Unknown:
@ -1087,9 +1087,9 @@ namespace ICSharpCode.Decompiler.IL @@ -1087,9 +1087,9 @@ namespace ICSharpCode.Decompiler.IL
var left = Pop();
// make the implicit I4->I conversion explicit:
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) {
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
@ -1137,7 +1137,7 @@ namespace ICSharpCode.Decompiler.IL @@ -1137,7 +1137,7 @@ namespace ICSharpCode.Decompiler.IL
// introduce explicit comparison with 0
condition = new Comp(
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;
case StackType.I8:
// introduce explicit comparison with 0
@ -1197,9 +1197,9 @@ namespace ICSharpCode.Decompiler.IL @@ -1197,9 +1197,9 @@ namespace ICSharpCode.Decompiler.IL
if (opCode != OpCode.Shl && opCode != OpCode.Shr) {
// make the implicit I4->I conversion explicit:
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) {
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));

64
ICSharpCode.Decompiler/IL/ILTypeExtensions.cs

@ -64,6 +64,70 @@ namespace ICSharpCode.Decompiler.IL @@ -64,6 +64,70 @@ namespace ICSharpCode.Decompiler.IL
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)
{
return primitiveType.GetStackType().IsIntegerType();

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

@ -53,11 +53,12 @@ namespace ICSharpCode.Decompiler.IL @@ -53,11 +53,12 @@ namespace ICSharpCode.Decompiler.IL
FloatPrecisionChange,
/// <summary>
/// Conversion of integer type to larger signed integer type.
/// May involve overflow checking (when converting from U4 to I on 32-bit).
/// </summary>
SignExtend,
/// <summary>
/// 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>
ZeroExtend,
/// <summary>
@ -104,13 +105,13 @@ namespace ICSharpCode.Decompiler.IL @@ -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;
/// that is purely determined by the <c>TargetType</c>.
/// </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.CheckForOverflow = checkForOverflow;
this.Sign = sign;
this.InputSign = inputSign;
this.Kind = GetConversionKind(targetType, argument.ResultType);
Debug.Assert(this.Kind != ConversionKind.Invalid);
}
@ -207,9 +208,9 @@ namespace ICSharpCode.Decompiler.IL @@ -207,9 +208,9 @@ namespace ICSharpCode.Decompiler.IL
output.Write(OpCode);
if (CheckForOverflow)
output.Write(".ovf");
if (Sign == Sign.Unsigned)
if (InputSign == Sign.Unsigned)
output.Write(".unsigned");
else if (Sign == Sign.Signed)
else if (InputSign == Sign.Signed)
output.Write(".signed");
output.Write(' ');
output.Write(Argument.ResultType);

14
ICSharpCode.Decompiler/Tests/RoundtripAssembly.cs

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

58
ICSharpCode.Decompiler/TypeSystem/TypeUtils.cs

@ -24,14 +24,26 @@ namespace ICSharpCode.Decompiler @@ -24,14 +24,26 @@ namespace ICSharpCode.Decompiler
{
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)
if (type.Kind == TypeKind.Pointer)
return NativeIntSize;
switch (type.Kind) {
case TypeKind.Pointer:
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 = typeForConstant.GetDefinition();
var typeDef = type.GetDefinition();
if (typeDef == null)
return 0;
switch (typeDef.KnownTypeCode) {
@ -58,9 +70,33 @@ namespace ICSharpCode.Decompiler @@ -58,9 +70,33 @@ namespace ICSharpCode.Decompiler
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)
{
return GetNativeSize(type1) >= GetNativeSize(type2) ? type1 : type2;
return GetSize(type1) >= GetSize(type2) ? type1 : type2;
}
/// <summary>
@ -71,7 +107,7 @@ namespace ICSharpCode.Decompiler @@ -71,7 +107,7 @@ namespace ICSharpCode.Decompiler
/// </summary>
public static bool IsSmallIntegerType(this IType type)
{
return GetNativeSize(type) < 4;
return GetSize(type) < 4;
}
/// <summary>
@ -114,7 +150,7 @@ namespace ICSharpCode.Decompiler @@ -114,7 +150,7 @@ namespace ICSharpCode.Decompiler
// 2) Both types are integer types of equal size
StackType memoryStackType = memoryType.GetStackType();
StackType accessStackType = accessType.GetStackType();
return memoryStackType == accessStackType && memoryStackType.IsIntegerType() && GetNativeSize(memoryType) == GetNativeSize(accessType);
return memoryStackType == accessStackType && memoryStackType.IsIntegerType() && GetSize(memoryType) == GetSize(accessType);
}
/// <summary>
@ -174,12 +210,14 @@ namespace ICSharpCode.Decompiler @@ -174,12 +210,14 @@ namespace ICSharpCode.Decompiler
/// <remarks>
/// Integer types (including IntPtr/UIntPtr) return the sign as expected.
/// 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.
/// All other types return <c>Sign.None</c>.
/// </remarks>
public static Sign GetSign(this IType type)
{
if (type.Kind == TypeKind.Pointer)
return Sign.Unsigned;
var typeDef = type.GetEnumUnderlyingType().GetDefinition();
if (typeDef == null)
return Sign.None;

Loading…
Cancel
Save