Browse Source

Fix comparisons between signed and unsigned integers.

Added Conv.Kind to clarify the semantics of an integer conversion.
pull/728/head
Daniel Grunwald 9 years ago
parent
commit
d3de90389e
  1. 17
      ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
  2. 21
      ICSharpCode.Decompiler/CecilExtensions.cs
  3. 167
      ICSharpCode.Decompiler/IL/Instructions/Conv.cs
  4. 2
      ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj
  5. 32
      ICSharpCode.Decompiler/Tests/TestCases/Comparisons.cs
  6. 9
      ICSharpCode.Decompiler/Tests/TestRunner.cs
  7. 5
      ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystemUtils.cs

17
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -34,6 +34,14 @@ namespace ICSharpCode.Decompiler.CSharp @@ -34,6 +34,14 @@ namespace ICSharpCode.Decompiler.CSharp
/// <summary>
/// Translates from ILAst to C# expressions.
/// </summary>
/// <remarks>
/// 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>.
/// </remarks>
class ExpressionBuilder : ILVisitor<TranslatedExpression>
{
internal readonly ICompilation compilation;
@ -284,8 +292,11 @@ namespace ICSharpCode.Decompiler.CSharp @@ -284,8 +292,11 @@ namespace ICSharpCode.Decompiler.CSharp
return right; // '1 == b' => 'b'
}
var rr = resolver.ResolveBinaryOperator(BinaryOperatorType.Equality, left.ResolveResult, right.ResolveResult);
if (rr.IsError) {
var rr = resolver.ResolveBinaryOperator(BinaryOperatorType.Equality, left.ResolveResult, right.ResolveResult)
as OperatorResolveResult;
if (rr == null || rr.IsError || rr.UserDefinedOperatorMethod != null
|| rr.Operands[0].Type.GetStackType() != inst.OpType)
{
var targetType = DecompilerTypeSystemUtils.GetLargerType(left.Type, right.Type);
if (targetType.Equals(left.Type)) {
right = right.ConvertTo(targetType, this);
@ -492,7 +503,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -492,7 +503,7 @@ namespace ICSharpCode.Decompiler.CSharp
protected internal override TranslatedExpression VisitConv(Conv inst)
{
var arg = Translate(inst.Argument);
if (arg.Type.GetSign() != inst.Sign) {
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);

21
ICSharpCode.Decompiler/CecilExtensions.cs

@ -140,27 +140,6 @@ namespace ICSharpCode.Decompiler @@ -140,27 +140,6 @@ namespace ICSharpCode.Decompiler
type.MetadataType == MetadataType.IntPtr;
}
/// <summary>
/// checks if the given value is a numeric zero-value.
/// NOTE that this only works for types: [sbyte, short, int, long, IntPtr, byte, ushort, uint, ulong, float, double and decimal]
/// </summary>
public static bool IsZero(this object value)
{
return value.Equals((sbyte)0) ||
value.Equals((short)0) ||
value.Equals(0) ||
value.Equals(0L) ||
value.Equals(IntPtr.Zero) ||
value.Equals((byte)0) ||
value.Equals((ushort)0) ||
value.Equals(0u) ||
value.Equals(0UL) ||
value.Equals(0.0f) ||
value.Equals(0.0) ||
value.Equals((decimal)0);
}
#endregion
/// <summary>

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

@ -20,14 +20,88 @@ using System; @@ -20,14 +20,88 @@ using System;
namespace ICSharpCode.Decompiler.IL
{
/// <summary>
/// Semantic meaning of a <c>Conv</c> instruction.
/// </summary>
public enum ConversionKind : byte
{
/// <summary>
/// Invalid conversion.
/// </summary>
Invalid,
/// <summary>
/// Conversion between two types of same size.
/// Can be used to change the sign of integer types, which may involve overflow-checking.
/// </summary>
Nop,
/// <summary>
/// Integer-to-float conversion.
/// Uses <c>InputSign</c> to decide whether the integer should be treated as signed or unsigned.
/// </summary>
IntToFloat,
/// <summary>
/// Float-to-integer conversion.
/// (truncates toward zero)
/// </summary>
FloatToInt,
/// <summary>
/// Converts from the current precision available on the evaluation stack to the precision specified by
/// the <c>TargetType</c>.
/// Uses "round-to-nearest" mode is the precision is reduced.
/// </summary>
FloatPrecisionChange,
/// <summary>
/// Conversion of integer type to larger signed integer type.
/// </summary>
SignExtend,
/// <summary>
/// Conversion of integer type to larger unsigned integer type.
/// </summary>
ZeroExtend,
/// <summary>
/// Conversion to smaller integer type.
///
/// May involve overflow checking.
/// </summary>
/// <remarks>
/// If the target type is smaller than the minimum stack width of 4 bytes,
/// then the result of the conversion is zero extended (if the target type is unsigned)
/// or sign-extended (if the target type is signed).
/// </remarks>
Truncate,
/// <summary>
/// Used to convert managed references/objects to unmanaged pointers.
/// </summary>
StopGCTracking
}
partial class Conv : UnaryInstruction
{
/// <summary>
/// Gets the conversion kind.
/// </summary>
public readonly ConversionKind Kind;
/// <summary>
/// The target type of the conversion.
/// </summary>
public readonly PrimitiveType TargetType;
/// <summary>
/// Gets whether the conversion performs overflow-checking.
/// </summary>
public readonly bool CheckForOverflow;
/// <summary>
/// Gets the sign of the input type.
///
/// For conversions to integer types, the input Sign is set iff overflow-checking is enabled.
/// For conversions to floating-point types, the input sign is always set.
/// </summary>
/// <remarks>
/// 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 Conv(ILInstruction argument, PrimitiveType targetType, bool checkForOverflow, Sign sign) : base(OpCode.Conv, argument)
@ -35,6 +109,90 @@ namespace ICSharpCode.Decompiler.IL @@ -35,6 +109,90 @@ namespace ICSharpCode.Decompiler.IL
this.TargetType = targetType;
this.CheckForOverflow = checkForOverflow;
this.Sign = sign;
this.Kind = GetConversionKind(targetType, argument.ResultType);
}
/// <summary>
/// Implements Ecma-335 Table 8: Conversion Operators.
/// </summary>
static ConversionKind GetConversionKind(PrimitiveType targetType, StackType inputType)
{
switch (targetType) {
case PrimitiveType.I1:
case PrimitiveType.I2:
case PrimitiveType.U1:
case PrimitiveType.U2:
switch (inputType) {
case StackType.I4:
case StackType.I8:
case StackType.I:
return ConversionKind.Truncate;
case StackType.F:
return ConversionKind.FloatToInt;
default:
return ConversionKind.Invalid;
}
case PrimitiveType.I4:
case PrimitiveType.U4:
switch (inputType) {
case StackType.I4:
return ConversionKind.Nop;
case StackType.I:
case StackType.I8:
return ConversionKind.Truncate;
case StackType.F:
return ConversionKind.FloatToInt;
default:
return ConversionKind.Invalid;
}
case PrimitiveType.I8:
case PrimitiveType.U8:
switch (inputType) {
case StackType.I4:
case StackType.I:
return targetType == PrimitiveType.I8 ? ConversionKind.SignExtend : ConversionKind.ZeroExtend;
case StackType.I8:
return ConversionKind.Nop;
case StackType.F:
return ConversionKind.FloatToInt;
case StackType.Ref:
case StackType.O:
return ConversionKind.StopGCTracking;
default:
return ConversionKind.Invalid;
}
case PrimitiveType.I:
case PrimitiveType.U:
switch (inputType) {
case StackType.I4:
return targetType == PrimitiveType.I ? ConversionKind.SignExtend : ConversionKind.ZeroExtend;
case StackType.I:
return ConversionKind.Nop;
case StackType.I8:
return ConversionKind.Truncate;
case StackType.F:
return ConversionKind.FloatToInt;
case StackType.Ref:
case StackType.O:
return ConversionKind.StopGCTracking;
default:
return ConversionKind.Invalid;
}
case PrimitiveType.R4:
case PrimitiveType.R8:
switch (inputType) {
case StackType.I4:
case StackType.I:
case StackType.I8:
return ConversionKind.IntToFloat;
case StackType.F:
return ConversionKind.FloatPrecisionChange;
default:
return ConversionKind.Invalid;
}
default:
return ConversionKind.Invalid;
}
}
public override StackType ResultType {
@ -54,6 +212,15 @@ namespace ICSharpCode.Decompiler.IL @@ -54,6 +212,15 @@ namespace ICSharpCode.Decompiler.IL
output.Write(Argument.ResultType);
output.Write("->");
output.Write(TargetType);
output.Write(' ');
switch (Kind) {
case ConversionKind.SignExtend:
output.Write("<sign extend>");
break;
case ConversionKind.ZeroExtend:
output.Write("<zero extend>");
break;
}
output.Write('(');
Argument.WriteTo(output);
output.Write(')');

2
ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj

@ -118,7 +118,7 @@ @@ -118,7 +118,7 @@
<Compile Include="TestCases\CompoundAssignment.cs" />
<Compile Include="TestCases\ControlFlow.cs" />
<Compile Include="TestCases\DecimalFields.cs" />
<Compile Include="TestCases\FloatComparisons.cs" />
<Compile Include="TestCases\Comparisons.cs" />
<Compile Include="TestCases\Generics.cs" />
<Compile Include="TestCases\HelloWorld.cs" />
<Compile Include="TestCases\InitializerTests.cs" />

32
ICSharpCode.Decompiler/Tests/TestCases/FloatComparisons.cs → ICSharpCode.Decompiler/Tests/TestCases/Comparisons.cs

@ -18,9 +18,11 @@ @@ -18,9 +18,11 @@
using System;
//#pragma warning disable 652
namespace ICSharpCode.Decompiler.Tests.TestCases
{
public class FloatComparisons
public class Comparisons
{
public static int Main()
{
@ -35,6 +37,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases @@ -35,6 +37,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases
TestFloatOp("!>", (a, b) => !(a > b));
TestFloatOp("!<=", (a, b) => !(a <= b));
TestFloatOp("!>=", (a, b) => !(a >= b));
TestUInt(0);
TestUInt(uint.MaxValue);
TestUShort(0);
TestUShort(ushort.MaxValue);
return 0;
}
@ -43,9 +50,30 @@ namespace ICSharpCode.Decompiler.Tests.TestCases @@ -43,9 +50,30 @@ namespace ICSharpCode.Decompiler.Tests.TestCases
float[] vals = { -1, 0, 3, float.PositiveInfinity, float.NaN };
for (int i = 0; i < vals.Length; i++) {
for (int j = 0; j < vals.Length; j++) {
Console.WriteLine("{0:r} {1} {2:r} = {3}", vals[i], name, vals[j], f(vals[i], vals[j]));
Console.WriteLine("Float: {0:r} {1} {2:r} = {3}", vals[i], name, vals[j], f(vals[i], vals[j]));
}
}
}
static T Id<T>(T arg)
{
return arg;
}
static void TestUShort(ushort i)
{
Console.WriteLine("ushort: {0} == ushort.MaxValue = {1}", i, i == ushort.MaxValue);
Console.WriteLine("ushort: {0} == -1 = {1}", i, i == -1);
Console.WriteLine("ushort: {0} == Id<short>(-1) = {1}", i, i == Id<short>(-1));
Console.WriteLine("ushort: {0} == 0x1ffff = {1}", i, i == 0x1ffff);
}
static void TestUInt(uint i)
{
Console.WriteLine("uint: {0} == uint.MaxValue = {1}", i, i == uint.MaxValue);
Console.WriteLine("uint: {0} == Id(uint.MaxValue) = {1}", i, i == Id(uint.MaxValue));
Console.WriteLine("uint: {0} == -1 = {1}", i, i == -1);
Console.WriteLine("uint: {0} == Id(-1) = {1}", i, i == Id(-1));
}
}
}

9
ICSharpCode.Decompiler/Tests/TestRunner.cs

@ -29,9 +29,9 @@ namespace ICSharpCode.Decompiler.Tests @@ -29,9 +29,9 @@ namespace ICSharpCode.Decompiler.Tests
}
[Test]
public void FloatComparisons()
public void Comparisons()
{
TestCompileDecompileCompileOutputAll("FloatComparisons.cs");
TestCompileDecompileCompileOutputAll("Comparisons.cs");
}
[Test]
@ -108,6 +108,11 @@ namespace ICSharpCode.Decompiler.Tests @@ -108,6 +108,11 @@ namespace ICSharpCode.Decompiler.Tests
int result1 = Tester.Run(outputFile.PathToAssembly, out output1, out error1);
int result2 = Tester.Run(decompiledOutputFile.PathToAssembly, out output2, out error2);
if (result1 != result2 || output1 != output2 || error1 != error2) {
Console.WriteLine("Test {0} failed.", testFileName);
Console.WriteLine("Decompiled code in {0}:line 1", decompiledCodeFile);
}
Assert.AreEqual(result1, result2);
Assert.AreEqual(output1, output2);
Assert.AreEqual(error1, error2);

5
ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystemUtils.cs

@ -24,8 +24,9 @@ namespace ICSharpCode.Decompiler @@ -24,8 +24,9 @@ namespace ICSharpCode.Decompiler
{
static int GetNativeSize(IType type)
{
const int NativeIntSize = 6; // between 4 (Int32) and 8 (Int64)
if (type.Kind == TypeKind.Pointer)
return 6;
return NativeIntSize;
var typeForConstant = (type.Kind == TypeKind.Enum) ? type.GetDefinition().EnumUnderlyingType : type;
var typeDef = typeForConstant.GetDefinition();
@ -46,7 +47,7 @@ namespace ICSharpCode.Decompiler @@ -46,7 +47,7 @@ namespace ICSharpCode.Decompiler
return 4;
case KnownTypeCode.IntPtr:
case KnownTypeCode.UIntPtr:
return 6;
return NativeIntSize;
case KnownTypeCode.Int64:
case KnownTypeCode.UInt64:
case KnownTypeCode.Double:

Loading…
Cancel
Save