From 9a692d6be4401db6464c25750f12e460696a74c4 Mon Sep 17 00:00:00 2001 From: Skylot <118523+skylot@users.noreply.github.com> Date: Fri, 28 Mar 2025 20:43:46 +0000 Subject: [PATCH] fix: collect usage info in generic types --- .../core/dex/visitors/usage/UsageInfo.java | 27 ++++++++--- .../dex/visitors/usage/UsageInfoVisitor.java | 4 +- .../generics/TestUsageInGenerics.java | 47 +++++++++++++++++++ 3 files changed, 71 insertions(+), 7 deletions(-) create mode 100644 jadx-core/src/test/java/jadx/tests/integration/generics/TestUsageInGenerics.java diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/usage/UsageInfo.java b/jadx-core/src/main/java/jadx/core/dex/visitors/usage/UsageInfo.java index c8dc4238e..9a4e54197 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/usage/UsageInfo.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/usage/UsageInfo.java @@ -148,15 +148,19 @@ public class UsageInfo implements IUsageInfoData { } } + /** + * Visit all class nodes found in subtypes of the provided type. + */ private void processType(ArgType type, Consumer consumer) { - if (type == null) { + if (type == null || type == ArgType.OBJECT) { return; } if (type.isArray()) { processType(type.getArrayRootElement(), consumer); return; } - if (type.isObject() && !type.isGenericType()) { + if (type.isObject()) { + // TODO: support custom handlers via API ClspClass clsDetails = root.getClsp().getClsDetails(type); if (clsDetails != null && clsDetails.getSource() == ClspClassSource.APACHE_HTTP_LEGACY_CLIENT) { root.getGradleInfoStorage().setUseApacheHttpLegacy(true); @@ -166,19 +170,30 @@ public class UsageInfo implements IUsageInfoData { consumer.accept(clsNode); } List genericTypes = type.getGenericTypes(); - if (type.isGeneric() && notEmpty(genericTypes)) { + if (notEmpty(genericTypes)) { for (ArgType argType : genericTypes) { processType(argType, consumer); } } + List extendTypes = type.getExtendTypes(); + if (notEmpty(extendTypes)) { + for (ArgType extendType : extendTypes) { + processType(extendType, consumer); + } + } + ArgType wildcardType = type.getWildcardType(); + if (wildcardType != null) { + processType(wildcardType, consumer); + } + // TODO: process 'outer' types (check TestOuterGeneric test) } } - private static > List sortedList(Set deps) { - if (deps == null || deps.isEmpty()) { + private static > List sortedList(Set nodes) { + if (nodes == null || nodes.isEmpty()) { return Collections.emptyList(); } - List list = new ArrayList<>(deps); + List list = new ArrayList<>(nodes); Collections.sort(list); return list; } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/usage/UsageInfoVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/usage/UsageInfoVisitor.java index 65d46ea16..1358ea80b 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/usage/UsageInfoVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/usage/UsageInfoVisitor.java @@ -88,12 +88,14 @@ public class UsageInfoVisitor extends AbstractVisitor { for (ArgType interfaceType : cls.getInterfaces()) { usageInfo.clsUse(cls, interfaceType); } + for (ArgType genericTypeParameter : cls.getGenericTypeParameters()) { + usageInfo.clsUse(cls, genericTypeParameter); + } for (FieldNode fieldNode : cls.getFields()) { usageInfo.clsUse(cls, fieldNode.getType()); processAnnotations(fieldNode, usageInfo); // TODO: process types from field 'constant value' } - // TODO: process generics processAnnotations(cls, usageInfo); for (MethodNode methodNode : cls.getMethods()) { processMethod(methodNode, usageInfo); diff --git a/jadx-core/src/test/java/jadx/tests/integration/generics/TestUsageInGenerics.java b/jadx-core/src/test/java/jadx/tests/integration/generics/TestUsageInGenerics.java new file mode 100644 index 000000000..66badcab3 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/generics/TestUsageInGenerics.java @@ -0,0 +1,47 @@ +package jadx.tests.integration.generics; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +import jadx.core.dex.nodes.ClassNode; +import jadx.core.dex.nodes.MethodNode; +import jadx.tests.api.IntegrationTest; + +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; + +public class TestUsageInGenerics extends IntegrationTest { + + public static class TestCls { + + public static class A { + } + + public static class B { + } + + public static class C { + public List list; + } + + public T test() { + return null; + } + } + + @Test + public void test() { + ClassNode cls = getClassNode(TestCls.class); + ClassNode testCls = searchCls(cls.getInnerClasses(), "A"); + ClassNode bCls = searchCls(cls.getInnerClasses(), "B"); + ClassNode cCls = searchCls(cls.getInnerClasses(), "C"); + MethodNode testMth = getMethod(cls, "test"); + + assertThat(testCls.getUseIn()).contains(cls, bCls, cCls); + assertThat(testCls.getUseInMth()).contains(testMth); + + assertThat(cls) + .code() + .containsOne("public T test() {"); + } +}