-
-
Notifications
You must be signed in to change notification settings - Fork 3.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Pointer increments and compund assignements not always decompiled in a "pretty" way. #2620
Comments
A more "complex" test case: unsafe class MixedTests {
static void Mixed1(uint[] y, uint l, uint* e) {
uint h = 0;
for (uint i = 0; i < l; i++) {
*e ^= y[h & 0xf];
y[h & 0xf] = (y[h & 0xf] ^ (*e++)) + 0x3dbb2819;
h++;
}
}
} Decompiled code: internal class MixedTests
{
private unsafe static void Mixed1(uint[] y, uint l, uint* e)
{
uint h = 0u;
for (uint i = 0u; i < l; i++)
{
*e ^= y[h & 0xF];
uint num = h & 0xF;
uint num2 = y[h & 0xF];
uint* num3 = e;
e = num3 + 1;
y[num] = (num2 ^ *num3) + 1035675673;
h++;
}
}
} |
See also #947 |
I went ahead and tested this with ILSpy 2.4 and the result for some of these test cases is slightly better: namespace ILSpyPointerIncrement
{
internal class ByteCases
{
private unsafe static void Sample1()
{
byte b = 0;
byte* ptr = &b;
int value = (int)(*(ptr++) * *(ptr++));
Console.WriteLine(value);
}
private unsafe static void Sample1(byte param)
{
byte* ptr = ¶m;
int value = (int)(*(ptr++) * *(ptr++));
Console.WriteLine(value);
}
private unsafe static void Sample5()
{
byte b = 0;
byte* ptr = &b;
byte* expr_08 = ptr;
int arg_1B_0 = (int)(*expr_08 += 5);
byte* expr_12 = ptr;
int value = arg_1B_0 * (int)(*expr_12 += 5);
Console.WriteLine(value);
}
private unsafe static void Sample5(byte param)
{
byte* ptr = ¶m;
byte* expr_06 = ptr;
int arg_19_0 = (int)(*expr_06 += 5);
byte* expr_10 = ptr;
int value = arg_19_0 * (int)(*expr_10 += 5);
Console.WriteLine(value);
}
private unsafe static void Sample52()
{
byte b = 0;
byte* ptr = &b;
int value = (int)(*(ptr += 5) * ptr[5]);
Console.WriteLine(value);
}
private unsafe static void Sample52(byte param)
{
byte* ptr = ¶m;
int value = (int)(*(ptr += 5) * ptr[5]);
Console.WriteLine(value);
}
}
}
namespace ILSpyPointerIncrement
{
internal class MixedTests
{
private unsafe static void Mixed1(uint[] y, uint l, uint* e)
{
uint num = 0u;
for (uint num2 = 0u; num2 < l; num2 += 1u)
{
*e ^= y[(int)(num & 15u)];
y[(int)(num & 15u)] = (y[(int)(num & 15u)] ^ *(e++)) + 1035675673u;
num += 1u;
}
}
}
}
namespace ILSpyPointerIncrement
{
internal class UIntCases
{
private unsafe static void Sample1()
{
uint num = 0u;
uint* ptr = #
uint value = *(ptr++) * *(ptr++);
Console.WriteLine(value);
}
private unsafe static void Sample1(uint param)
{
uint* ptr = ¶m;
uint value = *(ptr++) * *(ptr++);
Console.WriteLine(value);
}
private unsafe static void Sample5()
{
uint num = 0u;
uint* ptr = #
uint value = (*ptr += 5u) * (*ptr += 5u);
Console.WriteLine(value);
}
private unsafe static void Sample5(uint param)
{
uint* ptr = ¶m;
uint value = (*ptr += 5u) * (*ptr += 5u);
Console.WriteLine(value);
}
private unsafe static void Sample52()
{
uint num = 0u;
uint* ptr = #
uint value = *(ptr += 5) * ptr[5];
Console.WriteLine(value);
}
private unsafe static void Sample52(uint param)
{
uint* ptr = ¶m;
uint value = *(ptr += 5) * ptr[5];
Console.WriteLine(value);
}
}
} The old decompiler does a better job with the |
Hello, I've gone ahead and extended the test cases. I've also concluded that there is no difference in decompilation results between pointers to locals and pointers to parameters. Updated test cases:unsafe partial class PointerCompoundAssignTests {
static void ByteParameterReferencePostIncrementBeforeDereference(byte param) {
byte* r = ¶m;
int g = (*r++) * (*r++);
Console.WriteLine(g);
}
static void ByteParameterReferencePostIncrementAfterDereference(byte param) {
byte* r = ¶m;
int g = ((*r)++) * ((*r)++);
Console.WriteLine(g);
}
static void ByteParameterReferencePreIncrementBeforeDereference(byte param) {
byte* r = ¶m;
int g = (*++r) * (*++r);
Console.WriteLine(g);
}
static void ByteParameterReferencePreIncrementAfterDereference(byte param) {
byte* r = ¶m;
int g = (++(*r)) * (++(*r));
Console.WriteLine(g);
}
static void ByteParameterReferenceCompundAssignAfterDereference(byte param) {
byte* r = ¶m;
int g = (*r += 5) * (*r += 5);
Console.WriteLine(g);
}
static void ByteParameterReferenceCompundAssignBeforeDereference(byte param) {
byte* r = ¶m;
int g = (*(r += 5)) * (*(r += 5));
Console.WriteLine(g);
}
static void UIntParameterReferencePostIncrementBeforeDereference(uint param) {
uint* r = ¶m;
uint g = (*r++) * (*r++);
Console.WriteLine(g);
}
static void UIntParameterReferencePostIncrementAfterDereference(uint param) {
uint* r = ¶m;
uint g = ((*r)++) * ((*r)++);
Console.WriteLine(g);
}
static void UIntParameterReferencePreIncrementBeforeDereference(uint param) {
uint* r = ¶m;
uint g = (*++r) * (*++r);
Console.WriteLine(g);
}
static void UIntParameterReferencePreIncrementAfterDereference(uint param) {
uint* r = ¶m;
uint g = (++(*r)) * (++(*r));
Console.WriteLine(g);
}
static void UIntParameterReferenceCompundAssignAfterDereference(uint param) {
uint* r = ¶m;
uint g = (*r += 5) * (*r += 5);
Console.WriteLine(g);
}
static void UIntParameterReferenceCompundAssignBeforeDereference(uint param) {
uint* r = ¶m;
uint g = (*(r += 5)) * (*(r += 5));
Console.WriteLine(g);
}
}
unsafe class MixedTests {
static void Mixed1(uint[] y, uint l, uint* e) {
uint h = 0;
for (uint i = 0; i < l; i++) {
*e ^= y[h & 0xf];
y[h & 0xf] = (y[h & 0xf] ^ (*e++)) + 0x3dbb2819;
h++;
}
}
} Decompilation output for the test cases above:With internal class PointerCompoundAssignTests
{
private unsafe static void ByteParameterReferencePostIncrementBeforeDereference(byte param)
{
byte* r = ¶m;
int g = *(r++) * *(r++);
Console.WriteLine(g);
}
private unsafe static void ByteParameterReferencePostIncrementAfterDereference(byte param)
{
byte* r = ¶m;
byte b = *r;
*r = (byte)(b + 1);
byte num = b;
b = *r;
*r = (byte)(b + 1);
int g = num * b;
Console.WriteLine(g);
}
private unsafe static void ByteParameterReferencePreIncrementBeforeDereference(byte param)
{
byte* r = ¶m;
int g = *(++r) * *(++r);
Console.WriteLine(g);
}
private unsafe static void ByteParameterReferencePreIncrementAfterDereference(byte param)
{
byte* r = ¶m;
byte b = (byte)(*r + 1);
*r = b;
byte num = b;
b = (byte)(*r + 1);
*r = b;
int g = num * b;
Console.WriteLine(g);
}
private unsafe static void ByteParameterReferenceCompundAssignAfterDereference(byte param)
{
byte* r = ¶m;
byte b;
*r = (b = (byte)(*r + 5));
byte num = b;
*r = (b = (byte)(*r + 5));
int g = num * b;
Console.WriteLine(g);
}
private unsafe static void ByteParameterReferenceCompundAssignBeforeDereference(byte param)
{
byte* r = ¶m;
int g = *(r += 5) * *(r += 5);
Console.WriteLine(g);
}
private unsafe static void UIntParameterReferencePostIncrementBeforeDereference(uint param)
{
uint* r = ¶m;
uint* intPtr = r;
r = intPtr + 1;
uint num = *intPtr;
uint* intPtr2 = r;
r = intPtr2 + 1;
uint g = num * *intPtr2;
Console.WriteLine(g);
}
private unsafe static void UIntParameterReferencePostIncrementAfterDereference(uint param)
{
uint* r = ¶m;
uint g = (*r)++ * (*r)++;
Console.WriteLine(g);
}
private unsafe static void UIntParameterReferencePreIncrementBeforeDereference(uint param)
{
uint* r = ¶m;
uint g = *(++r) * *(++r);
Console.WriteLine(g);
}
private unsafe static void UIntParameterReferencePreIncrementAfterDereference(uint param)
{
uint* r = ¶m;
uint g = ++(*r) * ++(*r);
Console.WriteLine(g);
}
private unsafe static void UIntParameterReferenceCompundAssignAfterDereference(uint param)
{
uint* r = ¶m;
uint g = (*r += 5u) * (*r += 5u);
Console.WriteLine(g);
}
private unsafe static void UIntParameterReferenceCompundAssignBeforeDereference(uint param)
{
uint* r = ¶m;
uint g = *(r += 5) * *(r += 5);
Console.WriteLine(g);
}
}
internal class MixedTests
{
private unsafe static void Mixed1(uint[] y, uint l, uint* e)
{
uint h = 0u;
for (uint i = 0u; i < l; i++)
{
*e ^= y[h & 0xF];
uint num = h & 0xF;
uint num2 = y[h & 0xF];
uint* intPtr = e;
e = intPtr + 1;
y[num] = (num2 ^ *intPtr) + 1035675673;
h++;
}
}
} With internal class PointerCompoundAssignTests
{
private unsafe static void ByteParameterReferencePostIncrementBeforeDereference(byte param)
{
byte* intPtr = ¶m;
byte* r = intPtr + 1;
Console.WriteLine(*intPtr * *(r++));
}
private unsafe static void ByteParameterReferencePostIncrementAfterDereference(byte param)
{
byte* r = ¶m;
byte b = *r;
*r = (byte)(b + 1);
byte num = b;
b = *r;
*r = (byte)(b + 1);
Console.WriteLine(num * b);
}
private unsafe static void ByteParameterReferencePreIncrementBeforeDereference(byte param)
{
byte* intPtr = ¶m + 1;
byte* r;
Console.WriteLine(*intPtr * *(r = intPtr + 1));
}
private unsafe static void ByteParameterReferencePreIncrementAfterDereference(byte param)
{
byte* r = ¶m;
byte b = (byte)(*r + 1);
*r = b;
byte num = b;
b = (byte)(*r + 1);
*r = b;
Console.WriteLine(num * b);
}
private unsafe static void ByteParameterReferenceCompundAssignAfterDereference(byte param)
{
byte* r = ¶m;
byte b;
*r = (b = (byte)(*r + 5));
byte num = b;
*r = (b = (byte)(*r + 5));
Console.WriteLine(num * b);
}
private unsafe static void ByteParameterReferenceCompundAssignBeforeDereference(byte param)
{
byte* intPtr = ¶m + 5;
byte* r;
Console.WriteLine(*intPtr * *(r = intPtr + 5));
}
private unsafe static void UIntParameterReferencePostIncrementBeforeDereference(uint param)
{
uint* intPtr = ¶m;
uint* r = intPtr + 1;
uint num = *intPtr;
uint* intPtr2 = r;
r = intPtr2 + 1;
Console.WriteLine(num * *intPtr2);
}
private unsafe static void UIntParameterReferencePostIncrementAfterDereference(uint param)
{
uint* r = ¶m;
Console.WriteLine((*r)++ * (*r)++);
}
private unsafe static void UIntParameterReferencePreIncrementBeforeDereference(uint param)
{
uint* intPtr = ¶m + 1;
uint* r;
Console.WriteLine(*intPtr * *(r = intPtr + 1));
}
private unsafe static void UIntParameterReferencePreIncrementAfterDereference(uint param)
{
uint* r = ¶m;
Console.WriteLine(++(*r) * ++(*r));
}
private unsafe static void UIntParameterReferenceCompundAssignAfterDereference(uint param)
{
uint* r = ¶m;
Console.WriteLine((*r += 5u) * (*r += 5u));
}
private unsafe static void UIntParameterReferenceCompundAssignBeforeDereference(uint param)
{
uint* intPtr = ¶m + 5;
uint* r;
Console.WriteLine(*intPtr * *(r = intPtr + 5));
}
}
internal class MixedTests
{
private unsafe static void Mixed1(uint[] y, uint l, uint* e)
{
uint h = 0u;
for (uint i = 0u; i < l; i++)
{
*e ^= y[h & 0xF];
uint num = h & 0xF;
uint num2 = y[h & 0xF];
uint* intPtr = e;
e = intPtr + 1;
y[num] = (num2 ^ *intPtr) + 1035675673;
h++;
}
}
} Summary of testing results:The decompiler fails to produce a pretty result for
When the local inlining option is enabled, the following test cases yield "not pretty" results:
From these results, it is safe to assume that the |
Is there a chance for this issue to be fixed in ILSpy 8? |
I came up with a potential fix for the ILSpy/ICSharpCode.Decompiler/IL/Transforms/TransformAssignment.cs Lines 872 to 884 in 3d117a5
If this is applied then the assertions in ExpressionBuilder.HandleCompoundAssignment would have to be modified as well.ILSpy/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs Lines 1901 to 1920 in 3d117a5
The second assertion would have to be modified to also use PointerArithmeticOffset when we are dealing with pointer post-increments.Another solution would be to set the Value of the NumericCompoundAssign instruction to the output of PointerArithmeticOffset.Detect to avoid the need for modifying the assertions in ExpressionBuilder . Not sure which approach here is the best.
Please let me know if this is an appropriate fix for the issue with |
The first approach makes sense.
|
Hi, I have identified the cause for the The issue is the checks performed in the ILSpy/ICSharpCode.Decompiler/IL/Transforms/TransformAssignment.cs Lines 543 to 546 in 4ebe075
ILSpy/ICSharpCode.Decompiler/IL/Transforms/TransformAssignment.cs Lines 576 to 579 in 4ebe075
ILSpy/ICSharpCode.Decompiler/IL/Transforms/TransformAssignment.cs Lines 235 to 236 in 4ebe075
This direct comparison is problematic if we look at the code generated by the C# compiler for these methods. ![]() In the raw IL it is possible to observe a mismatch between primitive types which causes the check to fail. This mismatch is caused by the fact that IL does not have unsigned variations of stind . This issue with the checks cannot be observed on UInt32 compound assignments as it is not a small integer type and is thus excluded from the IsImplcitTruncation checks. The compound assignments on UInt32 also do not include a conv so the condition in ValidateCompoundAssign is skipped. This means that this problem not only occurs with byte but with any unsigned small integer type and char .ILSpy/ICSharpCode.Decompiler/IL/Transforms/TransformAssignment.cs Lines 516 to 522 in 4ebe075
This problem is the reason for the ugly decompilation on all 3 test methods mentioned at the start of the comment due to the mismatch in primitive types. |
A potential fix for the problem identified above: Change conditions to only compare type size and not whether it is signed or unsigned:
All tests pass with these changes applied. Not sure if this is the right way to approach this issue. I'm open to feedback on my suggestion. If this fix is appropriate I can open a pull request with it and some test cases for it. |
I'm still awaiting feedback on my proposed fix for the issue :p |
1+2: I don't think those suggestions are correct. |
On the IL level, Removing the sign comparison would make the transform incorrect. If the transform notices that only a sign mismatch prevents it from working, the transform could change the type of the stobj (changing only the sign has no effect if the result of the stobj isn't used yet), and then successfully apply the transformation. But without changing the type of the stobj, your proposal isn't correct. |
Would the following approach be valid?
|
Yes, that approach should work. |
Input code
Erroneous output
Code does compile. Behavior before and after was not tested.
Details
The text was updated successfully, but these errors were encountered: