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)