fix: scan annotations in usage collector visitor (#2435)
This commit is contained in:
@@ -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)");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user