diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeBoundFieldGetAssign.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeBoundFieldGetAssign.java index 81d7dc12b44..2098a7ecbd8 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeBoundFieldGetAssign.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeBoundFieldGetAssign.java @@ -1,5 +1,7 @@ package jadx.core.dex.visitors.typeinference; +import java.util.List; + import jadx.core.dex.info.FieldInfo; import jadx.core.dex.instructions.IndexInsnNode; import jadx.core.dex.instructions.args.ArgType; @@ -44,7 +46,38 @@ private ArgType getResultType(ArgType instanceType) { if (resultGeneric != null && !resultGeneric.isWildcard()) { return resultGeneric; } - return initType; // TODO: check if this type is allowed in current scope + if (initType.isGenericType() || initType.isArray()) { + // type variable unresolvable from the (raw) instance type: erase it (arrays element-wise) + // to its leftmost bound, like javac, so the result type is valid in the current scope + return eraseTypeVar(initType); + } + return initType; + } + + private static ArgType eraseTypeVar(ArgType type) { + if (type.isArray()) { + // erase element, keep rank (T[] -> Object[]) + return ArgType.array(eraseTypeVar(type.getArrayElement())); + } + if (type.isGenericType()) { + List bounds = type.getExtendTypes(); + if (bounds.isEmpty()) { + return ArgType.OBJECT; + } + ArgType bound = bounds.get(0); + if (bound.isGenericType()) { + // bound is itself a type variable () + return ArgType.OBJECT; + } + // drop type args so we don't reintroduce an out-of-scope variable (Comparable -> Comparable) + return bound.isGeneric() ? ArgType.object(bound.getObject()) : bound; + } + if (type.isGeneric()) { + // parameterized element (List[]) -> raw + return ArgType.object(type.getObject()); + } + // already concrete (array element) + return type; } private InsnArg getInstanceArg() { diff --git a/jadx-core/src/test/java/jadx/tests/integration/generics/TestRawGenericFieldNullCompare.java b/jadx-core/src/test/java/jadx/tests/integration/generics/TestRawGenericFieldNullCompare.java new file mode 100644 index 00000000000..7f6cfea2d26 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/generics/TestRawGenericFieldNullCompare.java @@ -0,0 +1,58 @@ +package jadx.tests.integration.generics; + +import org.junit.jupiter.api.Test; + +import jadx.tests.api.IntegrationTest; + +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; + +public class TestRawGenericFieldNullCompare extends IntegrationTest { + + public static class TestCls { + public static class NumBox { + public T value; + } + + public static class MultiBox> { + public T value; + } + + public static class CompBox> { + public T value; + } + + public static class ArrBox { + public T[] values; + } + + public static boolean isNull(NumBox box) { + return box.value == null; + } + + public static int useMulti(MultiBox box) { + if (box.value == null) { + return -1; + } + return box.value.intValue(); + } + + public static boolean compNull(CompBox box) { + return box.value == null; + } + + public static boolean arrNull(ArrBox box) { + return box.values == null; + } + } + + @Test + public void test() { + useJavaInput(); + noDebugInfo(); + assertThat(getClassNode(TestCls.class)) + .code() + .contains("== null") + .doesNotContain("== 0") + .doesNotContain("Type inference failed"); + } +}