fix: correct hex format for negative numbers (#2531)

This commit is contained in:
Skylot
2025-06-19 21:55:23 +01:00
parent 5d13acc6f3
commit cb9693a9d1
7 changed files with 134 additions and 120 deletions
@@ -9,6 +9,7 @@ import org.jetbrains.annotations.Nullable;
import jadx.api.JadxArgs;
import jadx.api.args.IntegerFormat;
import jadx.core.deobf.NameMapper;
import jadx.core.utils.exceptions.JadxRuntimeException;
public class StringUtils {
private static final StringUtils DEFAULT_INSTANCE = new StringUtils(new JadxArgs());
@@ -386,58 +387,90 @@ public class StringUtils {
return new SimpleDateFormat("HH:mm:ss").format(new Date());
}
private String toFormatString(long l) {
private String formatNumber(long number, int bytesLen, boolean cast) {
String numStr;
if (integerFormat.isHexadecimal()) {
return "0x" + Long.toHexString(l);
String hexStr = Long.toHexString(number);
if (number < 0) {
// cut leading 'f' for negative numbers to match number type length
int len = hexStr.length();
numStr = "0x" + hexStr.substring(len - bytesLen * 2, len);
// force cast, because unsigned negative numbers are bigger
// than signed max value allowed by compiler
cast = true;
} else {
numStr = "0x" + hexStr;
}
} else {
numStr = Long.toString(number);
}
return Long.toString(l);
if (bytesLen == 8 && (number == Long.MIN_VALUE || Math.abs(number) >= Integer.MAX_VALUE)) {
// force cast for long values bigger than min/max int
// to resolve compiler error: "integer number too large"
cast = true;
}
if (cast) {
if (bytesLen == 8) {
return numStr + 'L';
}
return getCastStr(bytesLen) + numStr;
}
return numStr;
}
public String formatShort(long l, boolean cast) {
if (l == Short.MAX_VALUE) {
return "Short.MAX_VALUE";
private static String getCastStr(int bytesLen) {
switch (bytesLen) {
case 1:
return "(byte) ";
case 2:
return "(short) ";
case 4:
return "(int) ";
case 8:
return "(long) ";
default:
throw new JadxRuntimeException("Unexpected number type length: " + bytesLen);
}
if (l == Short.MIN_VALUE) {
return "Short.MIN_VALUE";
}
String str = toFormatString(l);
return cast ? "(short) " + str : str;
}
public String formatByte(long l, boolean cast) {
if (l == Byte.MAX_VALUE) {
return "Byte.MAX_VALUE";
return formatNumber(l, 1, cast);
}
public String formatShort(long l, boolean cast) {
if (integerFormat == IntegerFormat.AUTO) {
switch ((short) l) {
case Short.MAX_VALUE:
return "Short.MAX_VALUE";
case Short.MIN_VALUE:
return "Short.MIN_VALUE";
}
}
if (l == Byte.MIN_VALUE) {
return "Byte.MIN_VALUE";
}
String str = toFormatString(l);
return cast ? "(byte) " + str : str;
return formatNumber(l, 2, cast);
}
public String formatInteger(long l, boolean cast) {
if (l == Integer.MAX_VALUE) {
return "Integer.MAX_VALUE";
if (integerFormat == IntegerFormat.AUTO) {
switch ((int) l) {
case Integer.MAX_VALUE:
return "Integer.MAX_VALUE";
case Integer.MIN_VALUE:
return "Integer.MIN_VALUE";
}
}
if (l == Integer.MIN_VALUE) {
return "Integer.MIN_VALUE";
}
String str = toFormatString(l);
return cast ? "(int) " + str : str;
return formatNumber(l, 4, cast);
}
public String formatLong(long l, boolean cast) {
if (l == Long.MAX_VALUE) {
return "Long.MAX_VALUE";
if (integerFormat == IntegerFormat.AUTO) {
if (l == Long.MAX_VALUE) {
return "Long.MAX_VALUE";
}
if (l == Long.MIN_VALUE) {
return "Long.MIN_VALUE";
}
}
if (l == Long.MIN_VALUE) {
return "Long.MIN_VALUE";
}
String str = toFormatString(l);
if (cast || Math.abs(l) >= Integer.MAX_VALUE) {
return str + 'L';
}
return str;
return formatNumber(l, 8, cast);
}
public static String formatDouble(double d) {
@@ -0,0 +1,57 @@
package jadx.tests.integration.arith;
import org.junit.jupiter.api.Test;
import jadx.api.args.IntegerFormat;
import jadx.tests.api.IntegrationTest;
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
public class TestNumbersFormat extends IntegrationTest {
@SuppressWarnings({ "FieldCanBeLocal", "UnusedAssignment", "unused" })
public static class TestCls {
private Object obj;
public void test() {
obj = new byte[] { 0, -1, -0xA, (byte) 0xff, Byte.MIN_VALUE, Byte.MAX_VALUE };
obj = new short[] { 0, -1, -0xA, (short) 0xffff, Short.MIN_VALUE, Short.MAX_VALUE };
obj = new int[] { 0, -1, -0xA, 0xffff_ffff, Integer.MIN_VALUE, Integer.MAX_VALUE };
obj = new long[] { 0, -1, -0xA, 0xffff_ffff_ffff_ffffL, Long.MIN_VALUE, Long.MAX_VALUE };
}
}
@Test
public void test() {
getArgs().setIntegerFormat(IntegerFormat.AUTO);
assertThat(getClassNode(TestCls.class))
.code()
.containsOne("new byte[]{0, -1, -10, -1, -128, 127}")
.containsOne("new short[]{0, -1, -10, -1, Short.MIN_VALUE, Short.MAX_VALUE}")
.containsOne("new int[]{0, -1, -10, -1, Integer.MIN_VALUE, Integer.MAX_VALUE}")
.containsOne("new long[]{0, -1, -10, -1, Long.MIN_VALUE, Long.MAX_VALUE}");
}
@Test
public void testDecimalFormat() {
getArgs().setIntegerFormat(IntegerFormat.DECIMAL);
assertThat(getClassNode(TestCls.class))
.code()
.containsOne("new byte[]{0, -1, -10, -1, -128, 127}")
.containsOne("new short[]{0, -1, -10, -1, -32768, 32767}")
.containsOne("new int[]{0, -1, -10, -1, -2147483648, 2147483647}")
.containsOne("new long[]{0, -1, -10, -1, -9223372036854775808L, 9223372036854775807L}");
}
@Test
public void testHexFormat() {
getArgs().setIntegerFormat(IntegerFormat.HEXADECIMAL);
assertThat(getClassNode(TestCls.class))
.code()
.containsOne("new byte[]{0x0, (byte) 0xff, (byte) 0xf6, (byte) 0xff, (byte) 0x80, 0x7f}")
.containsOne("new short[]{0x0, (short) 0xffff, (short) 0xfff6, (short) 0xffff, (short) 0x8000, 0x7fff}")
.containsOne("new int[]{0x0, (int) 0xffffffff, (int) 0xfffffff6, (int) 0xffffffff, (int) 0x80000000, 0x7fffffff}")
.containsOne(
"new long[]{0x0, 0xffffffffffffffffL, 0xfffffffffffffff6L, 0xffffffffffffffffL, 0x8000000000000000L, 0x7fffffffffffffffL}");
}
}
@@ -3,7 +3,8 @@ package jadx.tests.integration.arith;
import org.junit.jupiter.api.Test;
import jadx.tests.api.IntegrationTest;
import jadx.tests.api.utils.assertj.JadxAssertions;
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
public class TestSpecialValues extends IntegrationTest {
@@ -11,7 +12,6 @@ public class TestSpecialValues extends IntegrationTest {
public void test() {
shorts(Short.MIN_VALUE, Short.MAX_VALUE);
bytes(Byte.MIN_VALUE, Byte.MAX_VALUE);
ints(Integer.MIN_VALUE, Integer.MAX_VALUE);
longs(Long.MIN_VALUE, Long.MAX_VALUE);
@@ -25,9 +25,6 @@ public class TestSpecialValues extends IntegrationTest {
private void shorts(short... v) {
}
private void bytes(byte... v) {
}
private void ints(int... v) {
}
@@ -43,14 +40,13 @@ public class TestSpecialValues extends IntegrationTest {
@Test
public void test() {
JadxAssertions.assertThat(getClassNode(TestCls.class))
assertThat(getClassNode(TestCls.class))
.code()
.containsOne(
"Float.NaN, Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY, Float.MIN_VALUE, Float.MAX_VALUE, Float.MIN_NORMAL")
.containsOne("Double.NaN, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, "
+ "Double.MIN_VALUE, Double.MAX_VALUE, Double.MIN_NORMAL")
.containsOne("Short.MIN_VALUE, Short.MAX_VALUE")
.containsOne("Byte.MIN_VALUE, Byte.MAX_VALUE")
.containsOne("Integer.MIN_VALUE, Integer.MAX_VALUE")
.containsOne("Long.MIN_VALUE, Long.MAX_VALUE");
}
@@ -2,7 +2,6 @@ package jadx.tests.integration.arith;
import org.junit.jupiter.api.Test;
import jadx.NotYetImplemented;
import jadx.tests.api.IntegrationTest;
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
@@ -15,7 +14,6 @@ public class TestSpecialValues2 extends IntegrationTest {
}
}
@NotYetImplemented("Constant value replace")
@Test
public void test() {
noDebugInfo();
@@ -1,71 +0,0 @@
package jadx.tests.integration.arrays;
import org.junit.jupiter.api.Test;
import jadx.tests.api.IntegrationTest;
import jadx.tests.api.utils.assertj.JadxAssertions;
import static org.assertj.core.api.Assertions.assertThat;
public class TestRedundantType extends IntegrationTest {
public static class TestCls {
public byte[] method() {
return new byte[] { 10, 11, 12 };
}
}
@Test
public void test() {
JadxAssertions.assertThat(getClassNode(TestCls.class))
.code()
.contains("return new byte[]{10, 11, 12};");
}
public static class TestByte {
public byte[] method() {
byte[] arr = new byte[50];
arr[10] = 126;
arr[20] = 127;
arr[30] = (byte) 128;
arr[40] = (byte) 129;
return arr;
}
}
@Test
public void testByte() {
JadxAssertions.assertThat(getClassNode(TestByte.class))
.code()
.contains("arr[10] = 126")
.contains("arr[20] = Byte.MAX_VALUE")
.contains("arr[30] = Byte.MIN_VALUE")
.contains("arr[40] = -127");
assertThat(new TestByte().method()[40]).isEqualTo((byte) -127);
}
public static class TestShort {
public short[] method() {
short[] arr = new short[50];
arr[10] = 32766;
arr[20] = 32767;
arr[30] = (short) 32768;
arr[40] = (short) 32769;
return arr;
}
}
@Test
public void testShort() {
JadxAssertions.assertThat(getClassNode(TestShort.class))
.code()
.contains("arr[10] = 32766")
.contains("arr[20] = Short.MAX_VALUE")
.contains("arr[30] = Short.MIN_VALUE")
.contains("arr[40] = -32767");
assertThat(new TestShort().method()[40]).isEqualTo((short) -32767);
}
}
@@ -21,7 +21,7 @@ public class TestCast extends IntegrationTest {
}
public void test3(boolean a) {
write(a ? 0 : Byte.MAX_VALUE);
write(a ? 0 : (byte) 127);
}
public void test4(boolean a) {
@@ -49,7 +49,7 @@ public class TestCast extends IntegrationTest {
.code()
.contains("write(a ? (byte) 0 : (byte) 1);")
.contains("write(a ? (byte) 0 : this.myByte);")
.contains("write(a ? (byte) 0 : Byte.MAX_VALUE);")
.contains("write(a ? (byte) 0 : (byte) 127);")
.contains("write(a ? (short) 0 : (short) 1);")
.contains("write(a ? this.myShort : (short) 0);")
.contains("write(a ? Short.MIN_VALUE : (short) 0);");
@@ -3,7 +3,8 @@ package jadx.tests.integration.types;
import org.junit.jupiter.api.Test;
import jadx.tests.api.IntegrationTest;
import jadx.tests.api.utils.assertj.JadxAssertions;
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
public class TestTypeResolver9 extends IntegrationTest {
@@ -13,13 +14,13 @@ public class TestTypeResolver9 extends IntegrationTest {
}
public int test2(byte[] array, int offset) {
return (array[offset] * 128) + (array[offset + 1] & 0xFF);
return array[offset] * 128 + (array[offset + 1] & 0xFF);
}
}
@Test
public void test() {
JadxAssertions.assertThat(getClassNode(TestCls.class))
assertThat(getClassNode(TestCls.class))
.code()
.containsOne("return 16777216 * b;")
.doesNotContain("Byte.MIN_VALUE");