Skip to content

Commit 5846e6d

Browse files
committed
fix: scan annotations in usage collector visitor (#2435)
1 parent 4daad2f commit 5846e6d

File tree

4 files changed

+196
-8
lines changed

4 files changed

+196
-8
lines changed

jadx-core/src/main/java/jadx/core/dex/visitors/usage/UsageInfo.java

+42
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,14 @@
1010
import jadx.api.usage.IUsageInfoVisitor;
1111
import jadx.core.clsp.ClspClass;
1212
import jadx.core.clsp.ClspClassSource;
13+
import jadx.core.dex.info.FieldInfo;
1314
import jadx.core.dex.instructions.args.ArgType;
1415
import jadx.core.dex.nodes.ClassNode;
1516
import jadx.core.dex.nodes.FieldNode;
17+
import jadx.core.dex.nodes.ICodeNode;
1618
import jadx.core.dex.nodes.MethodNode;
1719
import jadx.core.dex.nodes.RootNode;
20+
import jadx.core.utils.exceptions.JadxRuntimeException;
1821

1922
import static jadx.core.utils.Utils.notEmpty;
2023

@@ -71,6 +74,28 @@ public void clsUse(MethodNode mth, ArgType useType) {
7174
processType(useType, depCls -> clsUse(mth, depCls));
7275
}
7376

77+
public void clsUse(ICodeNode node, ArgType useType) {
78+
Consumer<ClassNode> consumer;
79+
switch (node.getAnnType()) {
80+
case CLASS:
81+
ClassNode cls = (ClassNode) node;
82+
consumer = depCls -> clsUse(cls, depCls);
83+
break;
84+
case METHOD:
85+
MethodNode mth = (MethodNode) node;
86+
consumer = depCls -> clsUse(mth, depCls);
87+
break;
88+
case FIELD:
89+
FieldNode fld = (FieldNode) node;
90+
ClassNode fldCls = fld.getParentClass();
91+
consumer = depCls -> clsUse(fldCls, depCls);
92+
break;
93+
default:
94+
throw new JadxRuntimeException("Unexpected use type: " + node.getAnnType());
95+
}
96+
processType(useType, consumer);
97+
}
98+
7499
public void clsUse(MethodNode mth, ClassNode useCls) {
75100
ClassNode parentClass = mth.getParentClass();
76101
clsUse(parentClass, useCls);
@@ -106,6 +131,23 @@ public void fieldUse(MethodNode mth, FieldNode useFld) {
106131
clsUse(mth, useFld.getType());
107132
}
108133

134+
public void fieldUse(ICodeNode node, FieldInfo useFld) {
135+
FieldNode fld = root.resolveField(useFld);
136+
if (fld == null) {
137+
return;
138+
}
139+
switch (node.getAnnType()) {
140+
case CLASS:
141+
// TODO: support "field in class" usage?
142+
// now use field parent class for "class in class" usage
143+
clsUse((ClassNode) node, fld.getParentClass());
144+
break;
145+
case METHOD:
146+
fieldUse((MethodNode) node, fld);
147+
break;
148+
}
149+
}
150+
109151
private void processType(ArgType type, Consumer<ClassNode> consumer) {
110152
if (type == null) {
111153
return;

jadx-core/src/main/java/jadx/core/dex/visitors/usage/UsageInfoVisitor.java

+78-5
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,14 @@
88

99
import jadx.api.plugins.input.data.ICallSite;
1010
import jadx.api.plugins.input.data.ICodeReader;
11+
import jadx.api.plugins.input.data.IFieldRef;
1112
import jadx.api.plugins.input.data.IMethodHandle;
1213
import jadx.api.plugins.input.data.IMethodRef;
14+
import jadx.api.plugins.input.data.annotations.EncodedValue;
15+
import jadx.api.plugins.input.data.annotations.IAnnotation;
16+
import jadx.api.plugins.input.data.attributes.JadxAttrType;
17+
import jadx.api.plugins.input.data.attributes.types.AnnotationMethodParamsAttr;
18+
import jadx.api.plugins.input.data.attributes.types.AnnotationsAttr;
1319
import jadx.api.plugins.input.insns.InsnData;
1420
import jadx.api.plugins.input.insns.Opcode;
1521
import jadx.api.plugins.input.insns.custom.ICustomPayload;
@@ -20,19 +26,23 @@
2026
import jadx.core.dex.instructions.args.ArgType;
2127
import jadx.core.dex.nodes.ClassNode;
2228
import jadx.core.dex.nodes.FieldNode;
29+
import jadx.core.dex.nodes.ICodeNode;
2330
import jadx.core.dex.nodes.MethodNode;
2431
import jadx.core.dex.nodes.RootNode;
2532
import jadx.core.dex.visitors.AbstractVisitor;
2633
import jadx.core.dex.visitors.JadxVisitor;
2734
import jadx.core.dex.visitors.OverrideMethodVisitor;
35+
import jadx.core.dex.visitors.SignatureProcessor;
2836
import jadx.core.dex.visitors.rename.RenameVisitor;
2937
import jadx.core.utils.ListUtils;
38+
import jadx.core.utils.exceptions.JadxRuntimeException;
3039
import jadx.core.utils.input.InsnDataUtils;
3140

3241
@JadxVisitor(
3342
name = "UsageInfoVisitor",
3443
desc = "Scan class and methods to collect usage info and class dependencies",
3544
runAfter = {
45+
SignatureProcessor.class, // use types with generics
3646
OverrideMethodVisitor.class, // add method override as use
3747
RenameVisitor.class // sort by alias name
3848
}
@@ -80,19 +90,23 @@ private static void processClass(ClassNode cls, UsageInfo usageInfo) {
8090
}
8191
for (FieldNode fieldNode : cls.getFields()) {
8292
usageInfo.clsUse(cls, fieldNode.getType());
93+
processAnnotations(fieldNode, usageInfo);
94+
// TODO: process types from field 'constant value'
8395
}
84-
// TODO: process annotations and generics
96+
// TODO: process generics
97+
processAnnotations(cls, usageInfo);
8598
for (MethodNode methodNode : cls.getMethods()) {
8699
processMethod(methodNode, usageInfo);
87100
}
88101
}
89102

90103
private static void processMethod(MethodNode mth, UsageInfo usageInfo) {
91-
ClassNode cls = mth.getParentClass();
92-
usageInfo.clsUse(cls, mth.getReturnType());
93-
for (ArgType argType : mth.getMethodInfo().getArgumentsTypes()) {
94-
usageInfo.clsUse(cls, argType);
104+
processMethodAnnotations(mth, usageInfo);
105+
usageInfo.clsUse(mth, mth.getReturnType());
106+
for (ArgType argType : mth.getArgTypes()) {
107+
usageInfo.clsUse(mth, argType);
95108
}
109+
// TODO: process exception classes from 'throws'
96110
try {
97111
processInstructions(mth, usageInfo);
98112
} catch (Exception e) {
@@ -169,6 +183,65 @@ private static void processInsn(RootNode root, MethodNode mth, InsnData insnData
169183
}
170184
}
171185

186+
private static void processAnnotations(ICodeNode node, UsageInfo usageInfo) {
187+
AnnotationsAttr annAttr = node.get(JadxAttrType.ANNOTATION_LIST);
188+
processAnnotationAttr(node, annAttr, usageInfo);
189+
}
190+
191+
private static void processMethodAnnotations(MethodNode mth, UsageInfo usageInfo) {
192+
processAnnotations(mth, usageInfo);
193+
AnnotationMethodParamsAttr paramsAttr = mth.get(JadxAttrType.ANNOTATION_MTH_PARAMETERS);
194+
if (paramsAttr != null) {
195+
for (AnnotationsAttr annAttr : paramsAttr.getParamList()) {
196+
processAnnotationAttr(mth, annAttr, usageInfo);
197+
}
198+
}
199+
}
200+
201+
private static void processAnnotationAttr(ICodeNode node, AnnotationsAttr annAttr, UsageInfo usageInfo) {
202+
if (annAttr == null || annAttr.isEmpty()) {
203+
return;
204+
}
205+
for (IAnnotation ann : annAttr.getList()) {
206+
processAnnotation(node, ann, usageInfo);
207+
}
208+
}
209+
210+
private static void processAnnotation(ICodeNode node, IAnnotation ann, UsageInfo usageInfo) {
211+
usageInfo.clsUse(node, ArgType.parse(ann.getAnnotationClass()));
212+
for (EncodedValue value : ann.getValues().values()) {
213+
processAnnotationValue(node, value, usageInfo);
214+
}
215+
}
216+
217+
@SuppressWarnings("unchecked")
218+
private static void processAnnotationValue(ICodeNode node, EncodedValue value, UsageInfo usageInfo) {
219+
Object obj = value.getValue();
220+
switch (value.getType()) {
221+
case ENCODED_TYPE:
222+
usageInfo.clsUse(node, ArgType.parse((String) obj));
223+
break;
224+
case ENCODED_ENUM:
225+
case ENCODED_FIELD:
226+
if (obj instanceof IFieldRef) {
227+
usageInfo.fieldUse(node, FieldInfo.fromRef(node.root(), (IFieldRef) obj));
228+
} else if (obj instanceof FieldInfo) {
229+
usageInfo.fieldUse(node, (FieldInfo) obj);
230+
} else {
231+
throw new JadxRuntimeException("Unexpected field type class: " + value.getClass());
232+
}
233+
break;
234+
case ENCODED_ARRAY:
235+
for (EncodedValue encodedValue : (List<EncodedValue>) obj) {
236+
processAnnotationValue(node, encodedValue, usageInfo);
237+
}
238+
break;
239+
case ENCODED_ANNOTATION:
240+
processAnnotation(node, (IAnnotation) obj, usageInfo);
241+
break;
242+
}
243+
}
244+
172245
public static void replaceMethodUsage(MethodNode mergeIntoMth, MethodNode sourceMth) {
173246
List<MethodNode> mergedUsage = ListUtils.distinctMergeSortedLists(mergeIntoMth.getUseIn(), sourceMth.getUseIn());
174247
mergedUsage.remove(sourceMth);

jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java

+14-3
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
import jadx.api.metadata.annotations.VarNode;
5454
import jadx.core.dex.attributes.AFlag;
5555
import jadx.core.dex.nodes.ClassNode;
56+
import jadx.core.dex.nodes.FieldNode;
5657
import jadx.core.dex.nodes.MethodNode;
5758
import jadx.core.dex.nodes.RootNode;
5859
import jadx.core.utils.Utils;
@@ -447,13 +448,23 @@ public static void rethrow(String msg, Throwable e) {
447448
}
448449
}
449450

450-
protected MethodNode getMethod(ClassNode cls, String method) {
451+
protected MethodNode getMethod(ClassNode cls, String methodName) {
451452
for (MethodNode mth : cls.getMethods()) {
452-
if (mth.getName().equals(method)) {
453+
if (mth.getName().equals(methodName)) {
453454
return mth;
454455
}
455456
}
456-
fail("Method not found " + method + " in class " + cls);
457+
fail("Method not found " + methodName + " in class " + cls);
458+
return null;
459+
}
460+
461+
protected FieldNode getField(ClassNode cls, String fieldName) {
462+
for (FieldNode fld : cls.getFields()) {
463+
if (fld.getName().equals(fieldName)) {
464+
return fld;
465+
}
466+
}
467+
fail("Field not found " + fieldName + " in class " + cls);
457468
return null;
458469
}
459470

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package jadx.tests.integration.annotations;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
8+
import org.junit.jupiter.api.Test;
9+
10+
import jadx.core.dex.nodes.ClassNode;
11+
import jadx.core.dex.nodes.MethodNode;
12+
import jadx.tests.api.IntegrationTest;
13+
14+
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
15+
16+
public class TestAnnotationsUsage extends IntegrationTest {
17+
18+
public static class TestCls {
19+
20+
@Target({ ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER })
21+
@Retention(RetentionPolicy.RUNTIME)
22+
public @interface A {
23+
Class<?> c();
24+
}
25+
26+
@A(c = TestCls.class)
27+
public static class B {
28+
}
29+
30+
public static class C {
31+
@A(c = B.class)
32+
public String field;
33+
}
34+
35+
@A(c = B.class)
36+
void test() {
37+
}
38+
39+
void test2(@A(c = B.class) Integer value) {
40+
}
41+
}
42+
43+
@Test
44+
public void test() {
45+
ClassNode cls = getClassNode(TestCls.class);
46+
ClassNode annCls = searchCls(cls.getInnerClasses(), "A");
47+
ClassNode bCls = searchCls(cls.getInnerClasses(), "B");
48+
ClassNode cCls = searchCls(cls.getInnerClasses(), "C");
49+
MethodNode testMth = getMethod(cls, "test");
50+
MethodNode testMth2 = getMethod(cls, "test2");
51+
52+
assertThat(annCls.getUseIn()).contains(cls, bCls, cCls);
53+
assertThat(annCls.getUseInMth()).contains(testMth, testMth2);
54+
55+
assertThat(bCls.getUseIn()).contains(cCls);
56+
assertThat(bCls.getUseInMth()).contains(testMth, testMth2);
57+
58+
assertThat(cls)
59+
.code()
60+
.countString(3, "@A(c = B.class)");
61+
}
62+
}

0 commit comments

Comments
 (0)