Skip to content

Commit

Permalink
fix: remove enum methods after instructions check (skylot#884)
Browse files Browse the repository at this point in the history
skylot committed Mar 16, 2020
1 parent 9d8066f commit 2f780da
Showing 4 changed files with 250 additions and 7 deletions.
55 changes: 48 additions & 7 deletions jadx-core/src/main/java/jadx/core/dex/visitors/EnumVisitor.java
Original file line number Diff line number Diff line change
@@ -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.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.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 @@
)
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 @@ private boolean convertToEnum(ClassNode cls) {
if (classInitMth.countInsns() == 0) {
classInitMth.add(AFlag.DONT_GENERATE);
}
removeEnumMethods(cls, clsType);
removeEnumMethods(cls, clsType, valuesField);
return true;
}

@@ -232,7 +252,7 @@ private EnumField processEnumFiledByRegister(ClassNode cls, RegisterArg arg, Lis
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 @@ private InsnNode searchFieldPutInsn(ClassNode cls, BlockNode staticBlock, FieldN
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 @@ private void removeEnumMethods(ClassNode cls, ArgType clsType) {
|| 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;
72 changes: 72 additions & 0 deletions jadx-core/src/main/java/jadx/core/utils/InsnUtils.java
Original file line number Diff line number Diff line change
@@ -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.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 static Object getConstValueByInsn(DexNode dex, InsnNode insn) {
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;
}
}
Original file line number Diff line number Diff line change
@@ -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() {");
}
}
81 changes: 81 additions & 0 deletions jadx-core/src/test/smali/enums/TestEnumObfuscated.smali
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit 2f780da

Please sign in to comment.