fix: remove enum methods after instructions check (#884)
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
package jadx.core.dex.visitors;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -23,6 +25,7 @@ import jadx.core.dex.info.FieldInfo;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.IndexInsnNode;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
import jadx.core.dex.instructions.InvokeNode;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.InsnWrapArg;
|
||||
@@ -36,12 +39,17 @@ import jadx.core.dex.nodes.DexNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
|
||||
import jadx.core.utils.BlockInsnPair;
|
||||
import jadx.core.utils.InsnRemover;
|
||||
import jadx.core.utils.InsnUtils;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
|
||||
import static jadx.core.utils.InsnUtils.checkInsnType;
|
||||
import static jadx.core.utils.InsnUtils.getSingleArg;
|
||||
import static jadx.core.utils.InsnUtils.getWrappedInsn;
|
||||
|
||||
@JadxVisitor(
|
||||
name = "EnumVisitor",
|
||||
desc = "Restore enum classes",
|
||||
@@ -50,6 +58,18 @@ import jadx.core.utils.exceptions.JadxException;
|
||||
)
|
||||
public class EnumVisitor extends AbstractVisitor {
|
||||
|
||||
private MethodInfo enumValueOfMth;
|
||||
|
||||
@Override
|
||||
public void init(RootNode root) {
|
||||
enumValueOfMth = MethodInfo.fromDetails(
|
||||
root,
|
||||
ClassInfo.fromType(root, ArgType.ENUM),
|
||||
"valueOf",
|
||||
Arrays.asList(ArgType.CLASS, ArgType.STRING),
|
||||
ArgType.ENUM);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(ClassNode cls) throws JadxException {
|
||||
if (!convertToEnum(cls)) {
|
||||
@@ -163,7 +183,7 @@ public class EnumVisitor extends AbstractVisitor {
|
||||
if (classInitMth.countInsns() == 0) {
|
||||
classInitMth.add(AFlag.DONT_GENERATE);
|
||||
}
|
||||
removeEnumMethods(cls, clsType);
|
||||
removeEnumMethods(cls, clsType, valuesField);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -232,7 +252,7 @@ public class EnumVisitor extends AbstractVisitor {
|
||||
if (ssaVar.getUseCount() == 1) {
|
||||
return null;
|
||||
}
|
||||
final InsnNode sputInsn = ssaVar.getUseList().get(0).getParentInsn();
|
||||
InsnNode sputInsn = ssaVar.getUseList().get(0).getParentInsn();
|
||||
if (sputInsn == null || sputInsn.getType() != InsnType.SPUT) {
|
||||
return null;
|
||||
}
|
||||
@@ -283,14 +303,14 @@ public class EnumVisitor extends AbstractVisitor {
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO: detect these methods by analyzing method instructions
|
||||
private void removeEnumMethods(ClassNode cls, ArgType clsType) {
|
||||
private void removeEnumMethods(ClassNode cls, ArgType clsType, FieldNode valuesField) {
|
||||
String enumConstructor = "<init>(Ljava/lang/String;I)V";
|
||||
String enumConstructorAlt = "<init>(Ljava/lang/String;)V";
|
||||
String valuesOfMethod = "valueOf(Ljava/lang/String;)" + TypeGen.signature(clsType);
|
||||
String valuesMethod = "values()" + TypeGen.signature(ArgType.array(clsType));
|
||||
|
||||
// remove synthetic methods
|
||||
FieldInfo valuesFieldInfo = valuesField.getFieldInfo();
|
||||
|
||||
// remove compiler generated methods
|
||||
for (MethodNode mth : cls.getMethods()) {
|
||||
MethodInfo mi = mth.getMethodInfo();
|
||||
if (mi.isClassInit()) {
|
||||
@@ -303,12 +323,33 @@ public class EnumVisitor extends AbstractVisitor {
|
||||
|| shortId.equals(enumConstructorAlt)) {
|
||||
mth.add(AFlag.DONT_GENERATE);
|
||||
}
|
||||
} else if (shortId.equals(valuesMethod) || shortId.equals(valuesOfMethod)) {
|
||||
} else if (shortId.equals(valuesMethod)
|
||||
|| usesValuesField(mth, valuesFieldInfo)
|
||||
|| simpleValueOfMth(mth, clsType)) {
|
||||
mth.add(AFlag.DONT_GENERATE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean simpleValueOfMth(MethodNode mth, ArgType clsType) {
|
||||
InsnNode returnInsn = InsnUtils.searchSingleReturnInsn(mth, insn -> insn.getArgsCount() == 1);
|
||||
if (returnInsn == null) {
|
||||
return false;
|
||||
}
|
||||
InsnNode wrappedInsn = getWrappedInsn(getSingleArg(returnInsn));
|
||||
IndexInsnNode castInsn = (IndexInsnNode) checkInsnType(wrappedInsn, InsnType.CHECK_CAST);
|
||||
if (castInsn != null && Objects.equals(castInsn.getIndex(), clsType)) {
|
||||
InvokeNode invokeInsn = (InvokeNode) checkInsnType(getWrappedInsn(getSingleArg(castInsn)), InsnType.INVOKE);
|
||||
return invokeInsn != null && invokeInsn.getCallMth().equals(enumValueOfMth);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean usesValuesField(MethodNode mth, FieldInfo valuesFieldInfo) {
|
||||
Predicate<InsnNode> insnTest = insn -> Objects.equals(((IndexInsnNode) insn).getIndex(), valuesFieldInfo);
|
||||
return InsnUtils.searchInsn(mth, InsnType.SGET, insnTest) != null;
|
||||
}
|
||||
|
||||
private static void processEnumInnerCls(ConstructorInsn co, EnumField field, ClassNode innerCls) {
|
||||
if (!innerCls.getClassInfo().equals(co.getClassType())) {
|
||||
return;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package jadx.core.utils;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -15,9 +17,11 @@ import jadx.core.dex.instructions.InsnType;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.InsnWrapArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.DexNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.parser.FieldInitAttr;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
@@ -121,4 +125,72 @@ public class InsnUtils {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static InsnNode searchSingleReturnInsn(MethodNode mth, Predicate<InsnNode> test) {
|
||||
if (!mth.isNoCode() && mth.getExitBlocks().size() == 1) {
|
||||
return searchInsn(mth, InsnType.RETURN, test);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search instruction of specific type and condition in method.
|
||||
* This method support inlined instructions.
|
||||
*/
|
||||
@Nullable
|
||||
public static InsnNode searchInsn(MethodNode mth, InsnType insnType, Predicate<InsnNode> test) {
|
||||
if (mth.isNoCode()) {
|
||||
return null;
|
||||
}
|
||||
for (BlockNode block : mth.getBasicBlocks()) {
|
||||
for (InsnNode insn : block.getInstructions()) {
|
||||
InsnNode foundInsn = recursiveInsnCheck(insn, insnType, test);
|
||||
if (foundInsn != null) {
|
||||
return foundInsn;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static InsnNode recursiveInsnCheck(InsnNode insn, InsnType insnType, Predicate<InsnNode> test) {
|
||||
if (insn.getType() == insnType && test.test(insn)) {
|
||||
return insn;
|
||||
}
|
||||
for (InsnArg arg : insn.getArguments()) {
|
||||
if (arg.isInsnWrap()) {
|
||||
InsnNode wrapInsn = ((InsnWrapArg) arg).getWrapInsn();
|
||||
InsnNode foundInsn = recursiveInsnCheck(wrapInsn, insnType, test);
|
||||
if (foundInsn != null) {
|
||||
return foundInsn;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static InsnArg getSingleArg(InsnNode insn) {
|
||||
if (insn != null && insn.getArgsCount() == 1) {
|
||||
return insn.getArg(0);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static InsnNode checkInsnType(InsnNode insn, InsnType insnType) {
|
||||
if (insn != null && insn.getType() == insnType) {
|
||||
return insn;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static InsnNode getWrappedInsn(InsnArg arg) {
|
||||
if (arg != null && arg.isInsnWrap()) {
|
||||
return ((InsnWrapArg) arg).getWrapInsn();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package jadx.tests.integration.enums;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.tests.api.SmaliTest;
|
||||
|
||||
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||
|
||||
public class TestEnumObfuscated extends SmaliTest {
|
||||
// @formatter:off
|
||||
/*
|
||||
public enum TestEnumObfuscated {
|
||||
private static final synthetic TestEnumObfuscated[] $VLS = {ONE, TWO};
|
||||
public static final TestEnumObfuscated ONE = new TestEnumObfuscated("ONE", 0, 1);
|
||||
public static final TestEnumObfuscated TWO = new TestEnumObfuscated("TWO", 1, 2);
|
||||
private final int num;
|
||||
|
||||
private TestEnumObfuscated(String str, int i, int i2) {
|
||||
super(str, i);
|
||||
this.num = i2;
|
||||
}
|
||||
|
||||
public static TestEnumObfuscated vo(String str) {
|
||||
return (TestEnumObfuscated) Enum.valueOf(TestEnumObfuscated.class, str);
|
||||
}
|
||||
|
||||
public static TestEnumObfuscated[] vs() {
|
||||
return (TestEnumObfuscated[]) $VLS.clone();
|
||||
}
|
||||
|
||||
public synthetic int getNum() {
|
||||
return this.num;
|
||||
}
|
||||
}
|
||||
*/
|
||||
// @formatter:on
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
ClassNode cls = getClassNodeFromSmali();
|
||||
assertThat(cls)
|
||||
.code()
|
||||
.doesNotContain("$VLS")
|
||||
.doesNotContain("vo(")
|
||||
.doesNotContain("vs(")
|
||||
.containsOne("int getNum() {");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
###### Class jadx.tests.integration.enums.TestEnums3$TestCls.Numbers (jadx.tests.integration.enums.TestEnums3$TestCls$Numbers)
|
||||
.class public final enum Lenums/TestEnumObfuscated;
|
||||
.super Ljava/lang/Enum;
|
||||
|
||||
# static fields
|
||||
.field private static final synthetic $VLS:[Lenums/TestEnumObfuscated;
|
||||
.field public static final enum ONE:Lenums/TestEnumObfuscated;
|
||||
.field public static final enum TWO:Lenums/TestEnumObfuscated;
|
||||
|
||||
# instance fields
|
||||
.field private final num:I
|
||||
|
||||
# direct methods
|
||||
.method static constructor <clinit>()V
|
||||
.registers 7
|
||||
|
||||
.prologue
|
||||
const/4 v6, 0x3
|
||||
const/4 v5, 0x0
|
||||
const/4 v4, 0x2
|
||||
const/4 v3, 0x1
|
||||
|
||||
new-instance v0, Lenums/TestEnumObfuscated;
|
||||
const-string v1, "ONE"
|
||||
invoke-direct {v0, v1, v5, v3}, Lenums/TestEnumObfuscated;-><init>(Ljava/lang/String;II)V
|
||||
sput-object v0, Lenums/TestEnumObfuscated;->ONE:Lenums/TestEnumObfuscated;
|
||||
new-instance v0, Lenums/TestEnumObfuscated;
|
||||
|
||||
const-string v1, "TWO"
|
||||
invoke-direct {v0, v1, v3, v4}, Lenums/TestEnumObfuscated;-><init>(Ljava/lang/String;II)V
|
||||
sput-object v0, Lenums/TestEnumObfuscated;->TWO:Lenums/TestEnumObfuscated;
|
||||
const/4 v0, 0x2
|
||||
|
||||
new-array v0, v0, [Lenums/TestEnumObfuscated;
|
||||
sget-object v1, Lenums/TestEnumObfuscated;->ONE:Lenums/TestEnumObfuscated;
|
||||
aput-object v1, v0, v5
|
||||
sget-object v1, Lenums/TestEnumObfuscated;->TWO:Lenums/TestEnumObfuscated;
|
||||
aput-object v1, v0, v3
|
||||
sput-object v0, Lenums/TestEnumObfuscated;->$VLS:[Lenums/TestEnumObfuscated;
|
||||
|
||||
return-void
|
||||
.end method
|
||||
|
||||
.method private constructor <init>(Ljava/lang/String;II)V
|
||||
.registers 4
|
||||
|
||||
.prologue
|
||||
invoke-direct {p0, p1, p2}, Ljava/lang/Enum;-><init>(Ljava/lang/String;I)V
|
||||
iput p3, p0, Lenums/TestEnumObfuscated;->num:I
|
||||
return-void
|
||||
.end method
|
||||
|
||||
.method public static vo(Ljava/lang/String;)Lenums/TestEnumObfuscated;
|
||||
.registers 2
|
||||
|
||||
.prologue
|
||||
const-class v0, Lenums/TestEnumObfuscated;
|
||||
invoke-static {v0, p0}, Ljava/lang/Enum;->valueOf(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
|
||||
move-result-object v0
|
||||
check-cast v0, Lenums/TestEnumObfuscated;
|
||||
return-object v0
|
||||
.end method
|
||||
|
||||
.method public static vs()[Lenums/TestEnumObfuscated;
|
||||
.registers 1
|
||||
|
||||
.prologue
|
||||
sget-object v0, Lenums/TestEnumObfuscated;->$VLS:[Lenums/TestEnumObfuscated;
|
||||
invoke-virtual {v0}, [Lenums/TestEnumObfuscated;->clone()Ljava/lang/Object;
|
||||
move-result-object v0
|
||||
check-cast v0, [Lenums/TestEnumObfuscated;
|
||||
return-object v0
|
||||
.end method
|
||||
|
||||
.method public synthetic getNum()I
|
||||
.registers 2
|
||||
|
||||
.prologue
|
||||
iget v0, p0, Lenums/TestEnumObfuscated;->num:I
|
||||
return v0
|
||||
.end method
|
||||
Reference in New Issue
Block a user