diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Correctness/UnsafeCode.cs b/ICSharpCode.Decompiler.Tests/TestCases/Correctness/UnsafeCode.cs
index 4996adc67..300b89c48 100644
--- a/ICSharpCode.Decompiler.Tests/TestCases/Correctness/UnsafeCode.cs
+++ b/ICSharpCode.Decompiler.Tests/TestCases/Correctness/UnsafeCode.cs
@@ -198,17 +198,22 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
public unsafe int PointerSubtraction(long* p, long* q)
{
- return (int)((long)(p - q));
+ return (int)(p - q);
+ }
+
+ public unsafe long PointerSubtractionLong(long* p, long* q)
+ {
+ return p - q;
}
public unsafe int PointerSubtraction2(long* p, short* q)
{
- return (int)((long)((byte*)p - (byte*)q));
+ return (int)((byte*)p - (byte*)q);
}
public unsafe int PointerSubtraction3(void* p, void* q)
{
- return (int)((long)((byte*)p - (byte*)q));
+ return (int)((byte*)p - (byte*)q);
}
unsafe ~UnsafeCode()
diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
index a2ad91300..f5fbadee1 100644
--- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
+++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
@@ -645,7 +645,8 @@ namespace ICSharpCode.Decompiler.CSharp
case BinaryNumericOperator.Mul:
return HandleBinaryNumeric(inst, BinaryOperatorType.Multiply);
case BinaryNumericOperator.Div:
- return HandleBinaryNumeric(inst, BinaryOperatorType.Divide);
+ return HandlePointerSubtraction(inst)
+ ?? HandleBinaryNumeric(inst, BinaryOperatorType.Divide);
case BinaryNumericOperator.Rem:
return HandleBinaryNumeric(inst, BinaryOperatorType.Modulus);
case BinaryNumericOperator.BitAnd:
@@ -724,7 +725,6 @@ namespace ICSharpCode.Decompiler.CSharp
TranslatedExpression? GetPointerArithmeticOffset()
{
int? elementSize = ComputeSizeOf(pointerType.ElementType);
- int val;
if (elementSize == 1) {
return byteOffsetExpr;
} else if (byteOffsetInst is BinaryNumericInstruction mul && mul.Operator == BinaryNumericOperator.Mul) {
@@ -732,27 +732,24 @@ namespace ICSharpCode.Decompiler.CSharp
return null;
if (mul.IsLifted)
return null;
- if (elementSize > 0 && mul.Right.UnwrapConv(ConversionKind.SignExtend).MatchLdcI4(elementSize.Value)) {
+ if (elementSize > 0 && mul.Right.MatchLdcI(elementSize.Value)) {
return Translate(mul.Left);
- } else {
- return null;
}
- } else if (byteOffsetInst.UnwrapConv(ConversionKind.SignExtend).MatchLdcI4(out val)) {
+ } else if (byteOffsetInst.MatchLdcI(out long val)) {
// If the offset is a constant, it's possible that the compiler
// constant-folded the multiplication.
if (elementSize > 0 && (val % elementSize == 0) && val > 0) {
val /= elementSize.Value;
- return new PrimitiveExpression(val)
- .WithILInstruction(byteOffsetInst)
- .WithRR(new ConstantResolveResult(compilation.FindType(KnownTypeCode.Int32), val));
- } else {
- return null;
+ if (val <= int.MaxValue) {
+ return new PrimitiveExpression((int)val)
+ .WithILInstruction(byteOffsetInst)
+ .WithRR(new ConstantResolveResult(compilation.FindType(KnownTypeCode.Int32), val));
+ }
}
- } else {
- return null;
}
+ return null;
}
-
+
TranslatedExpression FallBackToBytePointer()
{
pointerType = new PointerType(compilation.FindType(KnownTypeCode.Byte));
@@ -760,6 +757,73 @@ namespace ICSharpCode.Decompiler.CSharp
}
}
+ ///
+ /// Called for divisions, detect and handles the code pattern:
+ /// div(sub(a, b), sizeof(T))
+ /// when a,b are of type T*.
+ /// This is what the C# compiler generates for pointer subtraction.
+ ///
+ TranslatedExpression? HandlePointerSubtraction(BinaryNumericInstruction inst)
+ {
+ Debug.Assert(inst.Operator == BinaryNumericOperator.Div);
+ if (inst.CheckForOverflow || inst.LeftInputType != StackType.I)
+ return null;
+ if (!(inst.Left is BinaryNumericInstruction sub && sub.Operator == BinaryNumericOperator.Sub))
+ return null;
+ if (sub.CheckForOverflow)
+ return null;
+ // First, attempt to parse the 'sizeof' on the RHS
+ IType elementType;
+ if (inst.Right.MatchLdcI(out long elementSize)) {
+ elementType = null;
+ // OK, might be pointer subtraction if the element size matches
+ } else if (inst.Right.MatchSizeOf(out elementType)) {
+ // OK, might be pointer subtraction if the element type matches
+ } else {
+ return null;
+ }
+ var left = Translate(sub.Left);
+ var right = Translate(sub.Right);
+ IType pointerType;
+ if (IsMatchingPointerType(left.Type)) {
+ pointerType = left.Type;
+ } else if (IsMatchingPointerType(right.Type)) {
+ pointerType = right.Type;
+ } else if (elementSize == 1 && left.Type.Kind == TypeKind.Pointer && right.Type.Kind == TypeKind.Pointer) {
+ // two pointers (neither matching), we're dividing by 1 (debug builds only),
+ // -> subtract two byte pointers
+ pointerType = new PointerType(compilation.FindType(KnownTypeCode.Byte));
+ } else {
+ // neither is a matching pointer type
+ // -> not a pointer subtraction after all
+ return null;
+ }
+ // We got a pointer subtraction.
+ left = left.ConvertTo(pointerType, this);
+ right = right.ConvertTo(pointerType, this);
+ var rr = new OperatorResolveResult(
+ compilation.FindType(KnownTypeCode.Int64),
+ ExpressionType.Subtract,
+ left.ResolveResult, right.ResolveResult
+ );
+ var result = new BinaryOperatorExpression(
+ left.Expression, BinaryOperatorType.Subtract, right.Expression
+ ).WithILInstruction(new[] { inst, sub })
+ .WithRR(rr);
+ return result;
+
+ bool IsMatchingPointerType(IType type)
+ {
+ if (type is PointerType pt) {
+ if (elementType != null)
+ return elementType.Equals(pt.ElementType);
+ else if (elementSize > 0)
+ return ComputeSizeOf(pt.ElementType) == elementSize;
+ }
+ return false;
+ }
+ }
+
int? ComputeSizeOf(IType type)
{
var rr = resolver.ResolveSizeOf(type);
@@ -1081,7 +1145,7 @@ namespace ICSharpCode.Decompiler.CSharp
// 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 (inputType.GetSign() != Sign.Signed || inputType.GetSize() > inputStackType.GetSize()) {
+ if (inputType.GetSign() != Sign.Signed || ValueMightBeOversized(arg.ResolveResult, inputStackType)) {
// 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.
@@ -1143,6 +1207,32 @@ namespace ICSharpCode.Decompiler.CSharp
}
}
+ ///
+ /// Gets whether the ResolveResult computes a value that might be oversized for the specified stack type.
+ ///
+ bool ValueMightBeOversized(ResolveResult rr, StackType stackType)
+ {
+ IType inputType = NullableType.GetUnderlyingType(rr.Type);
+ if (inputType.GetSize() <= stackType.GetSize()) {
+ // The input type is smaller or equal to the stack type,
+ // it can't be an oversized value.
+ return false;
+ }
+ if (rr is OperatorResolveResult orr) {
+ if (stackType == StackType.I && orr.OperatorType == ExpressionType.Subtract
+ && orr.Operands.Count == 2
+ && orr.Operands[0].Type.Kind == TypeKind.Pointer
+ && orr.Operands[1].Type.Kind == TypeKind.Pointer)
+ {
+ // Even though a pointer subtraction produces a value of type long in C#,
+ // the value will always fit in a native int.
+ return false;
+ }
+ }
+ // We don't have any information about the value, so it might be oversized.
+ return true;
+ }
+
protected internal override TranslatedExpression VisitCall(Call inst, TranslationContext context)
{
return new CallBuilder(this, typeSystem, settings).Build(inst);
diff --git a/ICSharpCode.Decompiler/IL/Instructions/PatternMatching.cs b/ICSharpCode.Decompiler/IL/Instructions/PatternMatching.cs
index 3382d6926..5e4f30113 100644
--- a/ICSharpCode.Decompiler/IL/Instructions/PatternMatching.cs
+++ b/ICSharpCode.Decompiler/IL/Instructions/PatternMatching.cs
@@ -33,7 +33,7 @@ namespace ICSharpCode.Decompiler.IL
}
///
- /// Matches either LdcI4 or LdcI8.
+ /// Matches ldc.i4, ldc.i8, and extending conversions.
///
public bool MatchLdcI(out long val)
{
@@ -43,6 +43,17 @@ namespace ICSharpCode.Decompiler.IL
val = intVal;
return true;
}
+ if (this is Conv conv) {
+ if (conv.Kind == ConversionKind.SignExtend) {
+ return conv.Argument.MatchLdcI(out val);
+ } else if (conv.Kind == ConversionKind.ZeroExtend && conv.InputType == StackType.I4) {
+ if (conv.Argument.MatchLdcI(out val)) {
+ // clear top 32 bits
+ val &= uint.MaxValue;
+ return true;
+ }
+ }
+ }
return false;
}