fix: scan annotations in usage collector visitor (#2435)

This commit is contained in:
Skylot
2025-03-22 22:04:15 +00:00
parent 4daad2fc79
commit 5846e6d22e
4 changed files with 196 additions and 8 deletions
@@ -10,11 +10,14 @@ import jadx.api.usage.IUsageInfoData;
import jadx.api.usage.IUsageInfoVisitor;
import jadx.core.clsp.ClspClass;
import jadx.core.clsp.ClspClassSource;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.ICodeNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.exceptions.JadxRuntimeException;
import static jadx.core.utils.Utils.notEmpty;
@@ -71,6 +74,28 @@ public class UsageInfo implements IUsageInfoData {
processType(useType, depCls -> clsUse(mth, depCls));
}
public void clsUse(ICodeNode node, ArgType useType) {
Consumer<ClassNode> consumer;
switch (node.getAnnType()) {
case CLASS:
ClassNode cls = (ClassNode) node;
consumer = depCls -> clsUse(cls, depCls);
break;
case METHOD:
MethodNode mth = (MethodNode) node;
consumer = depCls -> clsUse(mth, depCls);
break;
case FIELD:
FieldNode fld = (FieldNode) node;
ClassNode fldCls = fld.getParentClass();
consumer = depCls -> clsUse(fldCls, depCls);
break;
default:
throw new JadxRuntimeException("Unexpected use type: " + node.getAnnType());
}
processType(useType, consumer);
}
public void clsUse(MethodNode mth, ClassNode useCls) {
ClassNode parentClass = mth.getParentClass();
clsUse(parentClass, useCls);
@@ -106,6 +131,23 @@ public class UsageInfo implements IUsageInfoData {
clsUse(mth, useFld.getType());
}
public void fieldUse(ICodeNode node, FieldInfo useFld) {
FieldNode fld = root.resolveField(useFld);
if (fld == null) {
return;
}
switch (node.getAnnType()) {
case CLASS:
// TODO: support "field in class" usage?
// now use field parent class for "class in class" usage
clsUse((ClassNode) node, fld.getParentClass());
break;
case METHOD:
fieldUse((MethodNode) node, fld);
break;
}
}
private void processType(ArgType type, Consumer<ClassNode> consumer) {
if (type == null) {
return;
@@ -8,8 +8,14 @@ import org.slf4j.LoggerFactory;
import jadx.api.plugins.input.data.ICallSite;
import jadx.api.plugins.input.data.ICodeReader;
import jadx.api.plugins.input.data.IFieldRef;
import jadx.api.plugins.input.data.IMethodHandle;
import jadx.api.plugins.input.data.IMethodRef;
import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.api.plugins.input.data.annotations.IAnnotation;
import jadx.api.plugins.input.data.attributes.JadxAttrType;
import jadx.api.plugins.input.data.attributes.types.AnnotationMethodParamsAttr;
import jadx.api.plugins.input.data.attributes.types.AnnotationsAttr;
import jadx.api.plugins.input.insns.InsnData;
import jadx.api.plugins.input.insns.Opcode;
import jadx.api.plugins.input.insns.custom.ICustomPayload;
@@ -20,19 +26,23 @@ import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.ICodeNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.AbstractVisitor;
import jadx.core.dex.visitors.JadxVisitor;
import jadx.core.dex.visitors.OverrideMethodVisitor;
import jadx.core.dex.visitors.SignatureProcessor;
import jadx.core.dex.visitors.rename.RenameVisitor;
import jadx.core.utils.ListUtils;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.input.InsnDataUtils;
@JadxVisitor(
name = "UsageInfoVisitor",
desc = "Scan class and methods to collect usage info and class dependencies",
runAfter = {
SignatureProcessor.class, // use types with generics
OverrideMethodVisitor.class, // add method override as use
RenameVisitor.class // sort by alias name
}
@@ -80,19 +90,23 @@ public class UsageInfoVisitor extends AbstractVisitor {
}
for (FieldNode fieldNode : cls.getFields()) {
usageInfo.clsUse(cls, fieldNode.getType());
processAnnotations(fieldNode, usageInfo);
// TODO: process types from field 'constant value'
}
// TODO: process annotations and generics
// TODO: process generics
processAnnotations(cls, usageInfo);
for (MethodNode methodNode : cls.getMethods()) {
processMethod(methodNode, usageInfo);
}
}
private static void processMethod(MethodNode mth, UsageInfo usageInfo) {
ClassNode cls = mth.getParentClass();
usageInfo.clsUse(cls, mth.getReturnType());
for (ArgType argType : mth.getMethodInfo().getArgumentsTypes()) {
usageInfo.clsUse(cls, argType);
processMethodAnnotations(mth, usageInfo);
usageInfo.clsUse(mth, mth.getReturnType());
for (ArgType argType : mth.getArgTypes()) {
usageInfo.clsUse(mth, argType);
}
// TODO: process exception classes from 'throws'
try {
processInstructions(mth, usageInfo);
} catch (Exception e) {
@@ -169,6 +183,65 @@ public class UsageInfoVisitor extends AbstractVisitor {
}
}
private static void processAnnotations(ICodeNode node, UsageInfo usageInfo) {
AnnotationsAttr annAttr = node.get(JadxAttrType.ANNOTATION_LIST);
processAnnotationAttr(node, annAttr, usageInfo);
}
private static void processMethodAnnotations(MethodNode mth, UsageInfo usageInfo) {
processAnnotations(mth, usageInfo);
AnnotationMethodParamsAttr paramsAttr = mth.get(JadxAttrType.ANNOTATION_MTH_PARAMETERS);
if (paramsAttr != null) {
for (AnnotationsAttr annAttr : paramsAttr.getParamList()) {
processAnnotationAttr(mth, annAttr, usageInfo);
}
}
}
private static void processAnnotationAttr(ICodeNode node, AnnotationsAttr annAttr, UsageInfo usageInfo) {
if (annAttr == null || annAttr.isEmpty()) {
return;
}
for (IAnnotation ann : annAttr.getList()) {
processAnnotation(node, ann, usageInfo);
}
}
private static void processAnnotation(ICodeNode node, IAnnotation ann, UsageInfo usageInfo) {
usageInfo.clsUse(node, ArgType.parse(ann.getAnnotationClass()));
for (EncodedValue value : ann.getValues().values()) {
processAnnotationValue(node, value, usageInfo);
}
}
@SuppressWarnings("unchecked")
private static void processAnnotationValue(ICodeNode node, EncodedValue value, UsageInfo usageInfo) {
Object obj = value.getValue();
switch (value.getType()) {
case ENCODED_TYPE:
usageInfo.clsUse(node, ArgType.parse((String) obj));
break;
case ENCODED_ENUM:
case ENCODED_FIELD:
if (obj instanceof IFieldRef) {
usageInfo.fieldUse(node, FieldInfo.fromRef(node.root(), (IFieldRef) obj));
} else if (obj instanceof FieldInfo) {
usageInfo.fieldUse(node, (FieldInfo) obj);
} else {
throw new JadxRuntimeException("Unexpected field type class: " + value.getClass());
}
break;
case ENCODED_ARRAY:
for (EncodedValue encodedValue : (List<EncodedValue>) obj) {
processAnnotationValue(node, encodedValue, usageInfo);
}
break;
case ENCODED_ANNOTATION:
processAnnotation(node, (IAnnotation) obj, usageInfo);
break;
}
}
public static void replaceMethodUsage(MethodNode mergeIntoMth, MethodNode sourceMth) {
List<MethodNode> mergedUsage = ListUtils.distinctMergeSortedLists(mergeIntoMth.getUseIn(), sourceMth.getUseIn());
mergedUsage.remove(sourceMth);
@@ -53,6 +53,7 @@ import jadx.api.metadata.annotations.InsnCodeOffset;
import jadx.api.metadata.annotations.VarNode;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.Utils;
@@ -447,13 +448,23 @@ public abstract class IntegrationTest extends TestUtils {
}
}
protected MethodNode getMethod(ClassNode cls, String method) {
protected MethodNode getMethod(ClassNode cls, String methodName) {
for (MethodNode mth : cls.getMethods()) {
if (mth.getName().equals(method)) {
if (mth.getName().equals(methodName)) {
return mth;
}
}
fail("Method not found " + method + " in class " + cls);
fail("Method not found " + methodName + " in class " + cls);
return null;
}
protected FieldNode getField(ClassNode cls, String fieldName) {
for (FieldNode fld : cls.getFields()) {
if (fld.getName().equals(fieldName)) {
return fld;
}
}
fail("Field not found " + fieldName + " in class " + cls);
return null;
}
@@ -0,0 +1,62 @@
package jadx.tests.integration.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
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 TestAnnotationsUsage extends IntegrationTest {
public static class TestCls {
@Target({ ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
public @interface A {
Class<?> c();
}
@A(c = TestCls.class)
public static class B {
}
public static class C {
@A(c = B.class)
public String field;
}
@A(c = B.class)
void test() {
}
void test2(@A(c = B.class) Integer value) {
}
}
@Test
public void test() {
ClassNode cls = getClassNode(TestCls.class);
ClassNode annCls = searchCls(cls.getInnerClasses(), "A");
ClassNode bCls = searchCls(cls.getInnerClasses(), "B");
ClassNode cCls = searchCls(cls.getInnerClasses(), "C");
MethodNode testMth = getMethod(cls, "test");
MethodNode testMth2 = getMethod(cls, "test2");
assertThat(annCls.getUseIn()).contains(cls, bCls, cCls);
assertThat(annCls.getUseInMth()).contains(testMth, testMth2);
assertThat(bCls.getUseIn()).contains(cCls);
assertThat(bCls.getUseInMth()).contains(testMth, testMth2);
assertThat(cls)
.code()
.countString(3, "@A(c = B.class)");
}
}