diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeCompare.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeCompare.java index 7ec4181bfd9..d5be14c5cd6 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeCompare.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeCompare.java @@ -105,27 +105,13 @@ public TypeCompareEnum compareTypes(ArgType first, ArgType second) { } } if (firstPrimitive && secondPrimitive) { - PrimitiveType firstPrimitiveType = first.getPrimitiveType(); - PrimitiveType secondPrimitiveType = second.getPrimitiveType(); - if (firstPrimitiveType == PrimitiveType.BOOLEAN - || secondPrimitiveType == PrimitiveType.BOOLEAN) { - return CONFLICT; - } - if (swapEquals(firstPrimitiveType, secondPrimitiveType, PrimitiveType.CHAR, PrimitiveType.BYTE) - || swapEquals(firstPrimitiveType, secondPrimitiveType, PrimitiveType.CHAR, PrimitiveType.SHORT)) { - return CONFLICT; - } - return firstPrimitiveType.compareTo(secondPrimitiveType) > 0 ? WIDER : NARROW; + return comparePrimitives(first.getPrimitiveType(), second.getPrimitiveType()); } LOG.warn("Type compare function not complete, can't compare {} and {}", first, second); return TypeCompareEnum.CONFLICT; } - private boolean swapEquals(PrimitiveType first, PrimitiveType second, PrimitiveType a, PrimitiveType b) { - return (first == a && second == b) || (first == b && second == a); - } - private TypeCompareEnum compareArrayWithOtherType(ArgType array, ArgType other) { if (!other.isTypeKnown()) { if (other.contains(PrimitiveType.ARRAY)) { @@ -320,6 +306,60 @@ private List removeObject(List extendTypes) { return extendTypes; } + private TypeCompareEnum comparePrimitives(PrimitiveType type1, PrimitiveType type2) { + if (type1 == PrimitiveType.BOOLEAN || type2 == PrimitiveType.BOOLEAN) { + return type1 == type2 ? EQUAL : CONFLICT; + } + + if (type1 == PrimitiveType.VOID || type2 == PrimitiveType.VOID) { + return type1 == type2 ? EQUAL : CONFLICT; + } + + if (type1 == PrimitiveType.BYTE && type2 == PrimitiveType.CHAR) { + return WIDER; + } + + if (type1 == PrimitiveType.SHORT && type2 == PrimitiveType.CHAR) { + return WIDER; + } + + final int type1Width = getTypeWidth(type1); + final int type2Width = getTypeWidth(type2); + if (type1Width > type2Width) { + return WIDER; + } else if (type1Width < type2Width) { + return NARROW; + } else { + return EQUAL; + } + } + + private byte getTypeWidth(PrimitiveType type) { + switch (type) { + case BYTE: + return 0; + case SHORT: + return 1; + case CHAR: + return 2; + case INT: + return 3; + case LONG: + return 4; + case FLOAT: + return 5; + case DOUBLE: + return 6; + case BOOLEAN: + case OBJECT: + case ARRAY: + case VOID: + throw new JadxRuntimeException("Type " + type + " should not be here"); + } + + throw new JadxRuntimeException("Unhandled type: " + type); + } + public Comparator getComparator() { return comparator; } diff --git a/jadx-core/src/test/java/jadx/core/dex/visitors/typeinference/PrimitiveConversionsTests.java b/jadx-core/src/test/java/jadx/core/dex/visitors/typeinference/PrimitiveConversionsTests.java new file mode 100644 index 00000000000..a695d9a8b06 --- /dev/null +++ b/jadx-core/src/test/java/jadx/core/dex/visitors/typeinference/PrimitiveConversionsTests.java @@ -0,0 +1,128 @@ +package jadx.core.dex.visitors.typeinference; + +import java.util.stream.Stream; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import jadx.api.JadxArgs; +import jadx.core.dex.instructions.args.ArgType; +import jadx.core.dex.nodes.RootNode; + +import static jadx.core.dex.instructions.args.ArgType.BOOLEAN; +import static jadx.core.dex.instructions.args.ArgType.BYTE; +import static jadx.core.dex.instructions.args.ArgType.CHAR; +import static jadx.core.dex.instructions.args.ArgType.DOUBLE; +import static jadx.core.dex.instructions.args.ArgType.FLOAT; +import static jadx.core.dex.instructions.args.ArgType.INT; +import static jadx.core.dex.instructions.args.ArgType.LONG; +import static jadx.core.dex.instructions.args.ArgType.SHORT; +import static jadx.core.dex.instructions.args.ArgType.VOID; +import static jadx.core.dex.visitors.typeinference.TypeCompareEnum.CONFLICT; +import static jadx.core.dex.visitors.typeinference.TypeCompareEnum.EQUAL; +import static jadx.core.dex.visitors.typeinference.TypeCompareEnum.NARROW; +import static jadx.core.dex.visitors.typeinference.TypeCompareEnum.WIDER; +import static org.assertj.core.api.Assertions.assertThat; + +public class PrimitiveConversionsTests { + + private static TypeCompare comparator; + + @BeforeAll + static void before() { + JadxArgs args = new JadxArgs(); + RootNode root = new RootNode(args); + comparator = new TypeCompare(root); + } + + @DisplayName("Check conversion of numeric types") + @ParameterizedTest(name = "{0} -> {1} (should be {2})") + @MethodSource("provideArgsForNumericConversionsTest") + void testNumericConversions(ArgType firstType, ArgType secondType, TypeCompareEnum expectedResult) { + assertThat(comparator.compareTypes(firstType, secondType)).isEqualTo(expectedResult); + } + + @DisplayName("Ensure that `boolean` is not convertible to other primitive types") + @ParameterizedTest(name = "{0} <-> boolean") + @MethodSource("providePrimitiveTypesWithVoid") + void testBooleanConversions(ArgType type) { + final var expectedResult = type.equals(BOOLEAN) ? EQUAL : CONFLICT; + assertThat(comparator.compareTypes(type, BOOLEAN)).isEqualTo(expectedResult); + assertThat(comparator.compareTypes(BOOLEAN, type)).isEqualTo(expectedResult); + } + + @DisplayName("Ensure that `void` is not convertible to other primitive types") + @ParameterizedTest(name = "{0} <-> void") + @MethodSource("providePrimitiveTypesWithVoid") + void testVoidConversions(ArgType type) { + final var expectedResult = type.equals(VOID) ? EQUAL : CONFLICT; + assertThat(comparator.compareTypes(type, VOID)).isEqualTo(expectedResult); + assertThat(comparator.compareTypes(VOID, type)).isEqualTo(expectedResult); + } + + private static Stream provideArgsForNumericConversionsTest() { + return Stream.of( + Arguments.of(BYTE, BYTE, EQUAL), + Arguments.of(BYTE, SHORT, NARROW), + Arguments.of(BYTE, CHAR, WIDER), + Arguments.of(BYTE, INT, NARROW), + Arguments.of(BYTE, LONG, NARROW), + Arguments.of(BYTE, FLOAT, NARROW), + Arguments.of(BYTE, DOUBLE, NARROW), + + Arguments.of(SHORT, BYTE, WIDER), + Arguments.of(SHORT, SHORT, EQUAL), + Arguments.of(SHORT, CHAR, WIDER), + Arguments.of(SHORT, INT, NARROW), + Arguments.of(SHORT, LONG, NARROW), + Arguments.of(SHORT, FLOAT, NARROW), + Arguments.of(SHORT, DOUBLE, NARROW), + + Arguments.of(CHAR, BYTE, WIDER), + Arguments.of(CHAR, SHORT, WIDER), + Arguments.of(CHAR, CHAR, EQUAL), + Arguments.of(CHAR, INT, NARROW), + Arguments.of(CHAR, LONG, NARROW), + Arguments.of(CHAR, FLOAT, NARROW), + Arguments.of(CHAR, DOUBLE, NARROW), + + Arguments.of(INT, BYTE, WIDER), + Arguments.of(INT, SHORT, WIDER), + Arguments.of(INT, CHAR, WIDER), + Arguments.of(INT, INT, EQUAL), + Arguments.of(INT, LONG, NARROW), + Arguments.of(INT, FLOAT, NARROW), + Arguments.of(INT, DOUBLE, NARROW), + + Arguments.of(LONG, BYTE, WIDER), + Arguments.of(LONG, SHORT, WIDER), + Arguments.of(LONG, CHAR, WIDER), + Arguments.of(LONG, INT, WIDER), + Arguments.of(LONG, LONG, EQUAL), + Arguments.of(LONG, FLOAT, NARROW), + Arguments.of(LONG, DOUBLE, NARROW), + + Arguments.of(FLOAT, BYTE, WIDER), + Arguments.of(FLOAT, SHORT, WIDER), + Arguments.of(FLOAT, CHAR, WIDER), + Arguments.of(FLOAT, INT, WIDER), + Arguments.of(FLOAT, LONG, WIDER), + Arguments.of(FLOAT, FLOAT, EQUAL), + Arguments.of(FLOAT, DOUBLE, NARROW), + + Arguments.of(DOUBLE, BYTE, WIDER), + Arguments.of(DOUBLE, SHORT, WIDER), + Arguments.of(DOUBLE, CHAR, WIDER), + Arguments.of(DOUBLE, INT, WIDER), + Arguments.of(DOUBLE, LONG, WIDER), + Arguments.of(DOUBLE, FLOAT, WIDER), + Arguments.of(DOUBLE, DOUBLE, EQUAL)); + } + + private static Stream providePrimitiveTypesWithVoid() { + return Stream.of(BYTE, SHORT, CHAR, INT, LONG, FLOAT, DOUBLE, BOOLEAN, VOID); + } +} diff --git a/jadx-core/src/test/java/jadx/core/dex/visitors/typeinference/TypeCompareTest.java b/jadx-core/src/test/java/jadx/core/dex/visitors/typeinference/TypeCompareTest.java index 2bfc2180d15..3463e02b061 100644 --- a/jadx-core/src/test/java/jadx/core/dex/visitors/typeinference/TypeCompareTest.java +++ b/jadx-core/src/test/java/jadx/core/dex/visitors/typeinference/TypeCompareTest.java @@ -14,7 +14,6 @@ import jadx.core.dex.instructions.args.ArgType.WildcardBound; import jadx.core.dex.nodes.RootNode; -import static jadx.core.dex.instructions.args.ArgType.BOOLEAN; import static jadx.core.dex.instructions.args.ArgType.BYTE; import static jadx.core.dex.instructions.args.ArgType.CHAR; import static jadx.core.dex.instructions.args.ArgType.CLASS; @@ -23,7 +22,6 @@ import static jadx.core.dex.instructions.args.ArgType.NARROW; import static jadx.core.dex.instructions.args.ArgType.NARROW_INTEGRAL; import static jadx.core.dex.instructions.args.ArgType.OBJECT; -import static jadx.core.dex.instructions.args.ArgType.SHORT; import static jadx.core.dex.instructions.args.ArgType.STRING; import static jadx.core.dex.instructions.args.ArgType.THROWABLE; import static jadx.core.dex.instructions.args.ArgType.UNKNOWN; @@ -53,26 +51,14 @@ public void init() { @Test public void compareTypes() { - firstIsNarrow(INT, UNKNOWN); - - firstIsNarrow(array(UNKNOWN), UNKNOWN); - firstIsNarrow(array(UNKNOWN), NARROW); - } - - @Test - public void comparePrimitives() { check(INT, UNKNOWN_OBJECT, TypeCompareEnum.CONFLICT); check(INT, OBJECT, TypeCompareEnum.CONFLICT); - check(INT, CHAR, TypeCompareEnum.WIDER); - check(INT, SHORT, TypeCompareEnum.WIDER); - - check(BOOLEAN, INT, TypeCompareEnum.CONFLICT); - check(BOOLEAN, CHAR, TypeCompareEnum.CONFLICT); - check(CHAR, BYTE, TypeCompareEnum.CONFLICT); - check(CHAR, SHORT, TypeCompareEnum.CONFLICT); - + firstIsNarrow(INT, UNKNOWN); firstIsNarrow(CHAR, NARROW_INTEGRAL); + + firstIsNarrow(array(UNKNOWN), UNKNOWN); + firstIsNarrow(array(UNKNOWN), NARROW); firstIsNarrow(array(CHAR), UNKNOWN_OBJECT); } diff --git a/jadx-core/src/test/java/jadx/tests/integration/others/TestPrimitiveCasts2.java b/jadx-core/src/test/java/jadx/tests/integration/others/TestPrimitiveCasts2.java new file mode 100644 index 00000000000..7c6f21b4ff0 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/others/TestPrimitiveCasts2.java @@ -0,0 +1,27 @@ +package jadx.tests.integration.others; + +import org.junit.jupiter.api.Test; + +import jadx.tests.api.IntegrationTest; + +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; + +// Source: https://github.com/skylot/jadx/issues/1620 +public class TestPrimitiveCasts2 extends IntegrationTest { + + @SuppressWarnings("DataFlowIssue") + public static class TestCls { + long instanceCount; + + { + float f = 50.231F; + instanceCount &= (long) f; + } + } + + @Test + public void test() { + assertThat(getClassNode(TestCls.class)) + .code(); + } +}