From d75da053a8c31070629e993d3577cbb9cb954ec5 Mon Sep 17 00:00:00 2001
From: ds5678 <49847914+ds5678@users.noreply.github.com>
Date: Tue, 25 Feb 2025 14:03:59 -0800
Subject: [PATCH 1/6] Use type hint in pointer arithmetic when appropriate
---
.../ICSharpCode.Decompiler.Tests.csproj | 1 +
.../PrettyTestRunner.cs | 6 ++
.../Pretty/CS73_StackAllocInitializers.cs | 8 +-
.../TestCases/Pretty/PointerArithmetic.cs | 87 +++++++++++++++++++
.../TestCases/Pretty/UnsafeCode.cs | 3 +-
.../CSharp/ExpressionBuilder.cs | 18 +++-
6 files changed, 116 insertions(+), 7 deletions(-)
create mode 100644 ICSharpCode.Decompiler.Tests/TestCases/Pretty/PointerArithmetic.cs
diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
index f19374fd5..df5f31ec5 100644
--- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
+++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
@@ -136,6 +136,7 @@
+
diff --git a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
index ae6860798..9b4184aa7 100644
--- a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
+++ b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
@@ -730,6 +730,12 @@ namespace ICSharpCode.Decompiler.Tests
await RunForLibrary(cscOptions: cscOptions);
}
+ [Test]
+ public async Task PointerArithmetic([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions)
+ {
+ await RunForLibrary(cscOptions: cscOptions);
+ }
+
async Task RunForLibrary([CallerMemberName] string testName = null, AssemblerOptions asmOptions = AssemblerOptions.None, CompilerOptions cscOptions = CompilerOptions.None, Action configureDecompiler = null)
{
await Run(testName, asmOptions | AssemblerOptions.Library, cscOptions | CompilerOptions.Library, configureDecompiler);
diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CS73_StackAllocInitializers.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CS73_StackAllocInitializers.cs
index 4beb7148b..179a6f85d 100644
--- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CS73_StackAllocInitializers.cs
+++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CS73_StackAllocInitializers.cs
@@ -222,16 +222,16 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
#if OPT
byte* num = stackalloc byte[12];
*(int*)num = 1;
- *(int*)(num - 4) = 2;
- *(int*)(num - 8) = 3;
+ *((int*)num - 1) = 2;
+ *((int*)num - 2) = 3;
int* ptr = (int*)num;
Console.WriteLine(*ptr);
return UsePointer((byte*)ptr);
#else
byte* ptr = stackalloc byte[12];
*(int*)ptr = 1;
- *(int*)(ptr - 4) = 2;
- *(int*)(ptr - 8) = 3;
+ *((int*)ptr - 1) = 2;
+ *((int*)ptr - 2) = 3;
int* ptr2 = (int*)ptr;
Console.WriteLine(*ptr2);
return UsePointer((byte*)ptr2);
diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PointerArithmetic.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PointerArithmetic.cs
new file mode 100644
index 000000000..07991187e
--- /dev/null
+++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PointerArithmetic.cs
@@ -0,0 +1,87 @@
+using System;
+
+namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
+{
+ public class PointerArithmetic
+ {
+ public unsafe static void AssignmentVoidPointerToIntPointer(void* ptr)
+ {
+ ((int*)ptr)[2] = 1;
+ }
+
+ public unsafe static int AccessVoidPointerToIntPointer(void* ptr)
+ {
+ return ((int*)ptr)[2];
+ }
+
+ public unsafe static void AssignmentLongPointerToIntPointer_2(long* ptr)
+ {
+ ((int*)ptr)[2] = 1;
+ }
+
+ public unsafe static int AccessLongPointerToIntPointer_2(long* ptr)
+ {
+ return ((int*)ptr)[2];
+ }
+
+ public unsafe static void AssignmentLongPointerToIntPointer_3(long* ptr)
+ {
+ ((int*)ptr)[3] = 1;
+ }
+
+ public unsafe static int AccessLongPointerToIntPointer_3(long* ptr)
+ {
+ return ((int*)ptr)[3];
+ }
+
+ public unsafe static void AssignmentGuidPointerToIntPointer(Guid* ptr)
+ {
+ ((int*)ptr)[2] = 1;
+ }
+
+ public unsafe static int AccessGuidPointerToIntPointer(Guid* ptr)
+ {
+ return ((int*)ptr)[2];
+ }
+
+ public unsafe static void AssignmentIntPointer(int* ptr)
+ {
+ ptr[2] = 1;
+ }
+
+ public unsafe static int AccessIntPointer(int* ptr)
+ {
+ return ptr[2];
+ }
+
+ public unsafe static void AssignmentGuidPointer(Guid* ptr)
+ {
+ ptr[2] = Guid.NewGuid();
+ }
+
+ public unsafe static Guid AccessGuidPointer(Guid* ptr)
+ {
+ return ptr[2];
+ }
+
+ public unsafe static void AssignmentVoidPointerToGuidPointer(void* ptr)
+ {
+ ((Guid*)ptr)[2] = Guid.NewGuid();
+ }
+
+ public unsafe static Guid AccessVoidPointerToGuidPointer(void* ptr)
+ {
+ return ((Guid*)ptr)[2];
+ }
+
+ public unsafe static void AssignmentIntPointerToGuidPointer(int* ptr)
+ {
+ ((Guid*)ptr)[2] = Guid.NewGuid();
+ }
+
+ public unsafe static Guid AccessIntPointerToGuidPointer(int* ptr)
+ {
+ return ((Guid*)ptr)[2];
+ }
+ }
+}
\ No newline at end of file
diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/UnsafeCode.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/UnsafeCode.cs
index 3b993096e..c0f738e1d 100644
--- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/UnsafeCode.cs
+++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/UnsafeCode.cs
@@ -212,7 +212,8 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{
int result = 0;
*(float*)(&result) = 0.5f;
- ((byte*)(&result))[3] = 3;
+ ((sbyte*)(&result))[3] = 3;
+ ((sbyte*)(&result))[3] = -1;
return result;
}
diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
index 90df1890a..4d046fbf2 100644
--- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
+++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
@@ -1220,7 +1220,7 @@ namespace ICSharpCode.Decompiler.CSharp
/// Returns null if 'inst' is not performing pointer arithmetic.
/// 'ptr - ptr' is not handled here, but in HandlePointerSubtraction()!
///
- TranslatedExpression? HandlePointerArithmetic(BinaryNumericInstruction inst, TranslatedExpression left, TranslatedExpression right)
+ TranslatedExpression? HandlePointerArithmetic(BinaryNumericInstruction inst, TranslatedExpression left, TranslatedExpression right, TranslationContext context)
{
if (!(inst.Operator == BinaryNumericOperator.Add || inst.Operator == BinaryNumericOperator.Sub))
return null;
@@ -1249,6 +1249,20 @@ namespace ICSharpCode.Decompiler.CSharp
{
return null;
}
+ if (context.TypeHint.Kind == TypeKind.Pointer)
+ {
+ // We use the type hint if one of the following is true:
+ // * The current element type is a non-primitive struct.
+ // * The current element type has a different size than the type hint element type.
+ // This prevents the type hint from overriding in undesirable situations (eg changing char* to short*).
+
+ var typeHint = (PointerType)context.TypeHint;
+ int elementTypeSize = pointerType.ElementType.GetSize();
+ if (elementTypeSize == 0 || typeHint.ElementType.GetSize() != elementTypeSize)
+ {
+ pointerType = typeHint;
+ }
+ }
TranslatedExpression offsetExpr = GetPointerArithmeticOffset(byteOffsetInst, byteOffsetExpr, pointerType.ElementType, inst.CheckForOverflow)
?? FallBackToBytePointer();
@@ -1534,7 +1548,7 @@ namespace ICSharpCode.Decompiler.CSharp
}
if (left.Type.Kind == TypeKind.Pointer || right.Type.Kind == TypeKind.Pointer)
{
- var ptrResult = HandlePointerArithmetic(inst, left, right);
+ var ptrResult = HandlePointerArithmetic(inst, left, right, context);
if (ptrResult != null)
return ptrResult.Value;
}
From daa7f7879defe03a8211ff16a5c3009fd0bad6a0 Mon Sep 17 00:00:00 2001
From: ds5678 <49847914+ds5678@users.noreply.github.com>
Date: Tue, 25 Feb 2025 14:12:58 -0800
Subject: [PATCH 2/6] Add uint pointer method
---
.../TestCases/Pretty/PointerArithmetic.cs | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PointerArithmetic.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PointerArithmetic.cs
index 07991187e..a39524972 100644
--- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PointerArithmetic.cs
+++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PointerArithmetic.cs
@@ -44,6 +44,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
return ((int*)ptr)[2];
}
+ public unsafe static uint AccessGuidPointerToUIntPointer(Guid* ptr)
+ {
+ return ((uint*)ptr)[2];
+ }
+
public unsafe static void AssignmentIntPointer(int* ptr)
{
ptr[2] = 1;
From 437bd5656cb44f1172add7de5b0d654273d16319 Mon Sep 17 00:00:00 2001
From: ds5678 <49847914+ds5678@users.noreply.github.com>
Date: Thu, 27 Feb 2025 01:18:52 -0800
Subject: [PATCH 3/6] Fix unit test
---
ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue1918.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue1918.cs b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue1918.cs
index 969057eec..c64649147 100644
--- a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue1918.cs
+++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue1918.cs
@@ -11,7 +11,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.ILPretty
fixed (Guid* ptr = A_0)
{
void* ptr2 = ptr;
- UIntPtr* ptr3 = (UIntPtr*)((byte*)ptr2 - sizeof(UIntPtr));
+ UIntPtr* ptr3 = (UIntPtr*)ptr2 - 1;
UIntPtr uIntPtr = *ptr3;
try
{
From 8c291448114183784f008d9ea5b8ca8631943612 Mon Sep 17 00:00:00 2001
From: ds5678 <49847914+ds5678@users.noreply.github.com>
Date: Sat, 15 Mar 2025 10:37:11 -0700
Subject: [PATCH 4/6] Add struct to struct tests
---
.../TestCases/Pretty/PointerArithmetic.cs | 30 +++++++++++++++++++
.../CSharp/ExpressionBuilder.cs | 3 +-
2 files changed, 32 insertions(+), 1 deletion(-)
diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PointerArithmetic.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PointerArithmetic.cs
index a39524972..4a609f123 100644
--- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PointerArithmetic.cs
+++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PointerArithmetic.cs
@@ -49,6 +49,36 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
return ((uint*)ptr)[2];
}
+ public unsafe static void AssignmentGuidPointerToDateTimePointer(Guid* ptr)
+ {
+ ((DateTime*)ptr)[2] = DateTime.Now;
+ }
+
+ public unsafe static void AssignmentGuidPointerToDateTimePointerDefault(Guid* ptr)
+ {
+ ((DateTime*)ptr)[2] = default(DateTime);
+ }
+
+ public unsafe static void AssignmentGuidPointerToDateTimePointer_2(Guid* ptr)
+ {
+ *(DateTime*)(ptr + 2) = DateTime.Now;
+ }
+
+ public unsafe static void AssignmentGuidPointerToDateTimePointerDefault_2(Guid* ptr)
+ {
+ *(DateTime*)(ptr + 2) = default(DateTime);
+ }
+
+ public unsafe static DateTime AccessGuidPointerToDateTimePointer(Guid* ptr)
+ {
+ return ((DateTime*)ptr)[2];
+ }
+
+ public unsafe static DateTime AccessGuidPointerToDateTimePointer_2(Guid* ptr)
+ {
+ return *(DateTime*)(ptr + 2);
+ }
+
public unsafe static void AssignmentIntPointer(int* ptr)
{
ptr[2] = 1;
diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
index 4d046fbf2..292430eec 100644
--- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
+++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
@@ -1258,7 +1258,8 @@ namespace ICSharpCode.Decompiler.CSharp
var typeHint = (PointerType)context.TypeHint;
int elementTypeSize = pointerType.ElementType.GetSize();
- if (elementTypeSize == 0 || typeHint.ElementType.GetSize() != elementTypeSize)
+ if ((elementTypeSize == 0 || typeHint.ElementType.GetSize() != elementTypeSize)
+ && GetPointerArithmeticOffset(byteOffsetInst, byteOffsetExpr, typeHint.ElementType, inst.CheckForOverflow) != null)
{
pointerType = typeHint;
}
From 8de6585ba4f40bbfd3cda2c5b01d644606ca0eb9 Mon Sep 17 00:00:00 2001
From: ds5678 <49847914+ds5678@users.noreply.github.com>
Date: Sat, 15 Mar 2025 10:41:52 -0700
Subject: [PATCH 5/6] Add another int-guid pair test
---
.../TestCases/Pretty/PointerArithmetic.cs | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PointerArithmetic.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PointerArithmetic.cs
index 4a609f123..4fe487658 100644
--- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PointerArithmetic.cs
+++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PointerArithmetic.cs
@@ -114,6 +114,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
((Guid*)ptr)[2] = Guid.NewGuid();
}
+ public unsafe static void AssignmentIntPointerToGuidPointer_2(int* ptr)
+ {
+ *(Guid*)(ptr + 2) = Guid.NewGuid();
+ }
+
public unsafe static Guid AccessIntPointerToGuidPointer(int* ptr)
{
return ((Guid*)ptr)[2];
From ceed9eb01a1344efb20b007db1323d4bc8adf700 Mon Sep 17 00:00:00 2001
From: ds5678 <49847914+ds5678@users.noreply.github.com>
Date: Sun, 6 Apr 2025 12:53:35 -0700
Subject: [PATCH 6/6] Dont call GetPointerArithmeticOffset twice unnecessarily
---
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs | 13 +++++++++----
1 file changed, 9 insertions(+), 4 deletions(-)
diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
index 292430eec..320d75034 100644
--- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
+++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
@@ -1249,6 +1249,7 @@ namespace ICSharpCode.Decompiler.CSharp
{
return null;
}
+ TranslatedExpression? offsetExpressionFromTypeHint = null;
if (context.TypeHint.Kind == TypeKind.Pointer)
{
// We use the type hint if one of the following is true:
@@ -1258,13 +1259,17 @@ namespace ICSharpCode.Decompiler.CSharp
var typeHint = (PointerType)context.TypeHint;
int elementTypeSize = pointerType.ElementType.GetSize();
- if ((elementTypeSize == 0 || typeHint.ElementType.GetSize() != elementTypeSize)
- && GetPointerArithmeticOffset(byteOffsetInst, byteOffsetExpr, typeHint.ElementType, inst.CheckForOverflow) != null)
+ if (elementTypeSize == 0 || typeHint.ElementType.GetSize() != elementTypeSize)
{
- pointerType = typeHint;
+ offsetExpressionFromTypeHint = GetPointerArithmeticOffset(byteOffsetInst, byteOffsetExpr, typeHint.ElementType, inst.CheckForOverflow);
+ if (offsetExpressionFromTypeHint != null)
+ {
+ pointerType = typeHint;
+ }
}
}
- TranslatedExpression offsetExpr = GetPointerArithmeticOffset(byteOffsetInst, byteOffsetExpr, pointerType.ElementType, inst.CheckForOverflow)
+ TranslatedExpression offsetExpr = offsetExpressionFromTypeHint
+ ?? GetPointerArithmeticOffset(byteOffsetInst, byteOffsetExpr, pointerType.ElementType, inst.CheckForOverflow)
?? FallBackToBytePointer();
if (left.Type.Kind == TypeKind.Pointer)