diff --git a/jadx-core/src/main/java/jadx/core/dex/info/ConstStorage.java b/jadx-core/src/main/java/jadx/core/dex/info/ConstStorage.java index e9b400dfa..7968bf932 100644 --- a/jadx-core/src/main/java/jadx/core/dex/info/ConstStorage.java +++ b/jadx-core/src/main/java/jadx/core/dex/info/ConstStorage.java @@ -176,6 +176,9 @@ public class ConstStorage { @Nullable public FieldNode getConstFieldByLiteralArg(ClassNode cls, LiteralArg arg) { + if (!replaceEnabled) { + return null; + } PrimitiveType type = arg.getType().getPrimitiveType(); if (type == null) { return null; diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/FieldNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/FieldNode.java index 074a23700..da68768ea 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/FieldNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/FieldNode.java @@ -9,6 +9,7 @@ import jadx.core.dex.info.AccessInfo; import jadx.core.dex.info.AccessInfo.AFType; import jadx.core.dex.info.FieldInfo; import jadx.core.dex.instructions.args.ArgType; +import jadx.core.utils.ListUtils; public class FieldNode extends NotificationAttrNode implements ICodeNode { @@ -80,6 +81,10 @@ public class FieldNode extends NotificationAttrNode implements ICodeNode { this.useIn = useIn; } + public synchronized void addUseIn(MethodNode mth) { + useIn = ListUtils.safeAdd(useIn, mth); + } + @Override public String typeName() { return "field"; diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/ConstInlineVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/ConstInlineVisitor.java index ba85dbb17..490326161 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/ConstInlineVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/ConstInlineVisitor.java @@ -65,6 +65,7 @@ public class ConstInlineVisitor extends AbstractVisitor { SSAVar sVar = insn.getResult().getSVar(); InsnArg constArg; + Runnable onSuccess = null; InsnType insnType = insn.getType(); if (insnType == InsnType.CONST || insnType == InsnType.MOVE) { @@ -90,6 +91,7 @@ public class ConstInlineVisitor extends AbstractVisitor { InsnNode constGet = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0); constArg = InsnArg.wrapArg(constGet); constArg.setType(ArgType.STRING); + onSuccess = () -> f.addUseIn(mth); } } else if (insnType == InsnType.CONST_CLASS) { if (sVar.isUsedInPhi()) { @@ -104,6 +106,9 @@ public class ConstInlineVisitor extends AbstractVisitor { // all check passed, run replace if (replaceConst(mth, insn, constArg)) { toRemove.add(insn); + if (onSuccess != null) { + onSuccess.run(); + } } } @@ -235,7 +240,10 @@ public class ConstInlineVisitor extends AbstractVisitor { fieldNode = mth.getParentClass().getConstField((int) literal, false); } if (fieldNode != null) { - litArg.wrapInstruction(mth, new IndexInsnNode(InsnType.SGET, fieldNode.getFieldInfo(), 0)); + IndexInsnNode sgetInsn = new IndexInsnNode(InsnType.SGET, fieldNode.getFieldInfo(), 0); + if (litArg.wrapInstruction(mth, sgetInsn) != null) { + fieldNode.addUseIn(mth); + } } else { if (needExplicitCast(useInsn, litArg)) { litArg.add(AFlag.EXPLICIT_PRIMITIVE_TYPE); diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/ModVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/ModVisitor.java index 92ba5161d..a5b5329b6 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/ModVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/ModVisitor.java @@ -114,7 +114,7 @@ public class ModVisitor extends AbstractVisitor { break; case SWITCH: - replaceConstKeys(parentClass, (SwitchInsn) insn); + replaceConstKeys(mth, parentClass, (SwitchInsn) insn); break; case NEW_ARRAY: @@ -228,13 +228,14 @@ public class ModVisitor extends AbstractVisitor { return result == TypeCompareEnum.NARROW; // true if use class is subclass of field class } - private static void replaceConstKeys(ClassNode parentClass, SwitchInsn insn) { + private static void replaceConstKeys(MethodNode mth, ClassNode parentClass, SwitchInsn insn) { int[] keys = insn.getKeys(); int len = keys.length; for (int k = 0; k < len; k++) { FieldNode f = parentClass.getConstField(keys[k]); if (f != null) { insn.modifyKey(k, f); + f.addUseIn(mth); } } } @@ -291,6 +292,13 @@ public class ModVisitor extends AbstractVisitor { @SuppressWarnings("unchecked") private EncodedValue replaceConstValue(ClassNode parentCls, EncodedValue encodedValue) { + if (encodedValue.getType() == EncodedType.ENCODED_ANNOTATION) { + IAnnotation annotation = (IAnnotation) encodedValue.getValue(); + for (Map.Entry entry : annotation.getValues().entrySet()) { + entry.setValue(replaceConstValue(parentCls, entry.getValue())); + } + return encodedValue; + } if (encodedValue.getType() == EncodedType.ENCODED_ARRAY) { List listVal = (List) encodedValue.getValue(); if (!listVal.isEmpty()) { @@ -320,6 +328,7 @@ public class ModVisitor extends AbstractVisitor { InsnNode inode = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0); inode.setResult(insn.getResult()); replaceInsn(mth, block, i, inode); + f.addUseIn(mth); } } @@ -332,7 +341,9 @@ public class ModVisitor extends AbstractVisitor { FieldNode f = parentClass.getConstFieldByLiteralArg((LiteralArg) litArg); if (f != null) { InsnNode fGet = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0); - arithNode.replaceArg(litArg, InsnArg.wrapArg(fGet)); + if (arithNode.replaceArg(litArg, InsnArg.wrapArg(fGet))) { + f.addUseIn(mth); + } } } } @@ -516,6 +527,7 @@ public class ModVisitor extends AbstractVisitor { if (f != null) { InsnNode fGet = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0); filledArr.addArg(InsnArg.wrapArg(fGet)); + f.addUseIn(mth); } else { filledArr.addArg(arg); } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/PrepareForCodeGen.java b/jadx-core/src/main/java/jadx/core/dex/visitors/PrepareForCodeGen.java index 27af49ad0..944c573fb 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/PrepareForCodeGen.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/PrepareForCodeGen.java @@ -5,15 +5,24 @@ import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.stream.Stream; import org.jetbrains.annotations.Nullable; +import jadx.api.plugins.input.data.IFieldRef; +import jadx.api.plugins.input.data.annotations.AnnotationVisibility; +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.AnnotationsAttr; import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AType; +import jadx.core.dex.attributes.AttrNode; import jadx.core.dex.attributes.nodes.DeclareVariablesAttr; import jadx.core.dex.attributes.nodes.LineAttrNode; +import jadx.core.dex.info.FieldInfo; import jadx.core.dex.instructions.ArithNode; import jadx.core.dex.instructions.ArithOp; import jadx.core.dex.instructions.InsnType; @@ -25,6 +34,7 @@ import jadx.core.dex.instructions.mods.ConstructorInsn; import jadx.core.dex.instructions.mods.TernaryInsn; import jadx.core.dex.nodes.BlockNode; import jadx.core.dex.nodes.ClassNode; +import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.InsnContainer; import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.MethodNode; @@ -54,6 +64,7 @@ public class PrepareForCodeGen extends AbstractVisitor { if (cls.root().getArgs().isDebugInfo()) { setClassSourceLine(cls); } + collectFieldsUsageInAnnotations(cls); return true; } @@ -73,6 +84,7 @@ public class PrepareForCodeGen extends AbstractVisitor { checkConstUsage(block); } moveConstructorInConstructor(mth); + collectFieldsUsageInAnnotations(mth, mth); } private static void removeInstructions(BlockNode block) { @@ -310,4 +322,61 @@ public class PrepareForCodeGen extends AbstractVisitor { cls.setSourceLine(minLine - 1); } } + + private void collectFieldsUsageInAnnotations(ClassNode cls) { + MethodNode useMth = cls.getDefaultConstructor(); + if (useMth == null && !cls.getMethods().isEmpty()) { + useMth = cls.getMethods().get(0); + } + if (useMth == null) { + return; + } + collectFieldsUsageInAnnotations(useMth, cls); + MethodNode finalUseMth = useMth; + cls.getFields().forEach(f -> collectFieldsUsageInAnnotations(finalUseMth, f)); + } + + private void collectFieldsUsageInAnnotations(MethodNode mth, AttrNode attrNode) { + AnnotationsAttr annotationsList = attrNode.get(JadxAttrType.ANNOTATION_LIST); + if (annotationsList == null) { + return; + } + for (IAnnotation annotation : annotationsList.getAll()) { + if (annotation.getVisibility() == AnnotationVisibility.SYSTEM) { + continue; + } + for (Map.Entry entry : annotation.getValues().entrySet()) { + checkEncodedValue(mth, entry.getValue()); + } + } + } + + @SuppressWarnings("unchecked") + private void checkEncodedValue(MethodNode mth, EncodedValue encodedValue) { + switch (encodedValue.getType()) { + case ENCODED_FIELD: + Object fieldData = encodedValue.getValue(); + FieldInfo fieldInfo; + if (fieldData instanceof IFieldRef) { + fieldInfo = FieldInfo.fromRef(mth.root(), (IFieldRef) fieldData); + } else { + fieldInfo = (FieldInfo) fieldData; + } + FieldNode fieldNode = mth.root().resolveField(fieldInfo); + if (fieldNode != null) { + fieldNode.addUseIn(mth); + } + break; + + case ENCODED_ANNOTATION: + IAnnotation annotation = (IAnnotation) encodedValue.getValue(); + annotation.getValues().forEach((k, v) -> checkEncodedValue(mth, v)); + break; + + case ENCODED_ARRAY: + List valueList = (List) encodedValue.getValue(); + valueList.forEach(v -> checkEncodedValue(mth, v)); + break; + } + } } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/ReSugarCode.java b/jadx-core/src/main/java/jadx/core/dex/visitors/ReSugarCode.java index 2c7305a75..e39de1b17 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/ReSugarCode.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/ReSugarCode.java @@ -164,7 +164,9 @@ public class ReSugarCode extends AbstractVisitor { FieldNode f = mth.getParentClass().getConstFieldByLiteralArg((LiteralArg) valueArg); if (f != null) { InsnNode fGet = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0); - return InsnArg.wrapArg(fGet); + InsnArg arg = InsnArg.wrapArg(fGet); + f.addUseIn(mth); + return arg; } } return valueArg.duplicate(); diff --git a/jadx-core/src/test/java/jadx/tests/integration/others/TestConstReplace.java b/jadx-core/src/test/java/jadx/tests/integration/others/TestConstReplace.java new file mode 100644 index 000000000..a44dcdf35 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/others/TestConstReplace.java @@ -0,0 +1,44 @@ +package jadx.tests.integration.others; + +import org.junit.jupiter.api.Test; + +import jadx.core.dex.nodes.ClassNode; +import jadx.core.dex.nodes.FieldNode; +import jadx.core.dex.nodes.MethodNode; +import jadx.tests.api.IntegrationTest; + +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; + +public class TestConstReplace extends IntegrationTest { + + public static class TestCls { + public static final String CONST_VALUE = "string"; + + public String test() { + return CONST_VALUE; + } + } + + @Test + public void test() { + ClassNode cls = getClassNode(TestCls.class); + assertThat(cls).code().containsOne("return CONST_VALUE;"); + MethodNode testMth = cls.searchMethodByShortName("test"); + assertThat(testMth).isNotNull(); + + FieldNode constField = cls.searchFieldByName("CONST_VALUE"); + assertThat(constField).isNotNull(); + assertThat(constField.getUseIn()).containsExactly(testMth); + } + + @Test + public void testWithoutReplace() { + getArgs().setReplaceConsts(false); + ClassNode cls = getClassNode(TestCls.class); + assertThat(cls).code().containsOne("return \"string\";"); + + FieldNode constField = cls.searchFieldByName("CONST_VALUE"); + assertThat(constField).isNotNull(); + assertThat(constField.getUseIn()).isEmpty(); + } +}