fix(core): fix primitive-to-primitive conversions (PR #2326)
This commit is contained in:
@@ -105,27 +105,13 @@ public class TypeCompare {
|
||||
}
|
||||
}
|
||||
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 @@ public class TypeCompare {
|
||||
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<ArgType> getComparator() {
|
||||
return comparator;
|
||||
}
|
||||
|
||||
+128
@@ -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<Arguments> 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<ArgType> providePrimitiveTypesWithVoid() {
|
||||
return Stream.of(BYTE, SHORT, CHAR, INT, LONG, FLOAT, DOUBLE, BOOLEAN, VOID);
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,6 @@ import jadx.core.dex.instructions.args.ArgType;
|
||||
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.INT;
|
||||
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 class TypeCompareTest {
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user