From 3a69ac23c02a3056bf25c10576af9dd5d2f80948 Mon Sep 17 00:00:00 2001 From: Skylot Date: Tue, 23 Feb 2021 16:59:33 +0000 Subject: [PATCH] fix: restore enums with removed fields (#926) --- .../main/java/jadx/core/codegen/ClassGen.java | 2 + .../java/jadx/core/dex/nodes/ClassNode.java | 5 +- .../jadx/core/dex/visitors/EnumVisitor.java | 53 +++++-- .../tests/integration/enums/TestEnums10.java | 22 +++ .../src/test/smali/enums/TestEnums10.smali | 149 ++++++++++++++++++ 5 files changed, 219 insertions(+), 12 deletions(-) create mode 100644 jadx-core/src/test/java/jadx/tests/integration/enums/TestEnums10.java create mode 100644 jadx-core/src/test/smali/enums/TestEnums10.smali diff --git a/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java b/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java index 987f1a5c4..188fd03bf 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java @@ -426,6 +426,8 @@ public class ClassGen { InsnGen igen = null; for (Iterator it = enumFields.getFields().iterator(); it.hasNext();) { EnumField f = it.next(); + + CodeGenUtils.addComments(code, f.getField()); code.startLine(f.getField().getAlias()); ConstructorInsn constrInsn = f.getConstrInsn(); MethodNode callMth = cls.root().resolveMethod(constrInsn.getCallMth()); diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java index 0072ec39f..cb47f3b8e 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java @@ -330,6 +330,10 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN return fields; } + public void addField(FieldNode fld) { + fields.add(fld); + } + public FieldNode getConstField(Object obj) { return getConstField(obj, true); } @@ -651,5 +655,4 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN public String toString() { return clsInfo.getFullName(); } - } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/EnumVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/EnumVisitor.java index c6336ee1b..3816ceb42 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/EnumVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/EnumVisitor.java @@ -262,9 +262,9 @@ public class EnumVisitor extends AbstractVisitor { EnumField field = null; if (arg.isInsnWrap()) { InsnNode wrappedInsn = ((InsnWrapArg) arg).getWrapInsn(); - field = processEnumFieldByField(cls, wrappedInsn, staticBlock, toRemove); + field = processEnumFieldByWrappedInsn(cls, wrappedInsn, staticBlock, toRemove); } else if (arg.isRegister()) { - field = processEnumFiledByRegister(cls, (RegisterArg) arg, staticBlock, toRemove); + field = processEnumFieldByRegister(cls, (RegisterArg) arg, staticBlock, toRemove); } if (field == null) { return null; @@ -275,6 +275,19 @@ public class EnumVisitor extends AbstractVisitor { return enumFields; } + private EnumField processEnumFieldByWrappedInsn(ClassNode cls, InsnNode wrappedInsn, BlockNode staticBlock, List toRemove) { + if (wrappedInsn.getType() == InsnType.SGET) { + return processEnumFieldByField(cls, wrappedInsn, staticBlock, toRemove); + } + ConstructorInsn constructorInsn = castConstructorInsn(wrappedInsn); + if (constructorInsn != null) { + FieldNode enumFieldNode = createFakeField(cls, "EF" + constructorInsn.getOffset()); + cls.addField(enumFieldNode); + return createEnumFieldByConstructor(cls, enumFieldNode, constructorInsn); + } + return null; + } + @Nullable private EnumField processEnumFieldByField(ClassNode cls, InsnNode sgetInsn, BlockNode staticBlock, List toRemove) { if (sgetInsn.getType() != InsnType.SGET) { @@ -303,16 +316,40 @@ public class EnumVisitor extends AbstractVisitor { } @Nullable - private EnumField processEnumFiledByRegister(ClassNode cls, RegisterArg arg, BlockNode staticBlock, List toRemove) { + private EnumField processEnumFieldByRegister(ClassNode cls, RegisterArg arg, BlockNode staticBlock, List toRemove) { InsnNode assignInsn = arg.getAssignInsn(); if (assignInsn != null && assignInsn.getType() == InsnType.SGET) { return processEnumFieldByField(cls, assignInsn, staticBlock, toRemove); } SSAVar ssaVar = arg.getSVar(); - if (ssaVar.getUseCount() == 1) { + if (ssaVar.getUseCount() == 0) { return null; } + InsnNode constrInsn = ssaVar.getAssign().getParentInsn(); + if (constrInsn == null || constrInsn.getType() != InsnType.CONSTRUCTOR) { + return null; + } + FieldNode enumFieldNode = searchEnumField(cls, ssaVar, toRemove); + if (enumFieldNode == null) { + enumFieldNode = createFakeField(cls, "EF" + arg.getRegNum()); + cls.addField(enumFieldNode); + } + toRemove.add(constrInsn); + return createEnumFieldByConstructor(cls, enumFieldNode, (ConstructorInsn) constrInsn); + } + + private FieldNode createFakeField(ClassNode cls, String name) { + FieldNode enumFieldNode; + FieldInfo fldInfo = FieldInfo.from(cls.root(), cls.getClassInfo(), name, cls.getType()); + enumFieldNode = new FieldNode(cls, fldInfo, 0); + enumFieldNode.add(AFlag.SYNTHETIC); + enumFieldNode.addAttr(AType.COMMENTS, "Fake field, exist only in values array"); + return enumFieldNode; + } + + @Nullable + private FieldNode searchEnumField(ClassNode cls, SSAVar ssaVar, List toRemove) { InsnNode sputInsn = ssaVar.getUseList().get(0).getParentInsn(); if (sputInsn == null || sputInsn.getType() != InsnType.SPUT) { return null; @@ -322,14 +359,8 @@ public class EnumVisitor extends AbstractVisitor { if (enumFieldNode == null) { return null; } - - InsnNode constrInsn = ssaVar.getAssign().getParentInsn(); - if (constrInsn == null || constrInsn.getType() != InsnType.CONSTRUCTOR) { - return null; - } toRemove.add(sputInsn); - toRemove.add(constrInsn); - return createEnumFieldByConstructor(cls, enumFieldNode, (ConstructorInsn) constrInsn); + return enumFieldNode; } private EnumField createEnumFieldByConstructor(ClassNode cls, FieldNode enumFieldNode, ConstructorInsn co) { diff --git a/jadx-core/src/test/java/jadx/tests/integration/enums/TestEnums10.java b/jadx-core/src/test/java/jadx/tests/integration/enums/TestEnums10.java new file mode 100644 index 000000000..010802925 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/enums/TestEnums10.java @@ -0,0 +1,22 @@ +package jadx.tests.integration.enums; + +import org.junit.jupiter.api.Test; + +import jadx.tests.api.SmaliTest; + +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; + +/** + * Some enum field was removed, but still exist in values array + */ +public class TestEnums10 extends SmaliTest { + + @Test + public void test() { + assertThat(getClassNodeFromSmali()) + .code() + .doesNotContain("Failed to restore enum class") + .containsOne("enum TestEnums10 {") + .countString(4, "/* Fake field"); + } +} diff --git a/jadx-core/src/test/smali/enums/TestEnums10.smali b/jadx-core/src/test/smali/enums/TestEnums10.smali new file mode 100644 index 000000000..b9ced261b --- /dev/null +++ b/jadx-core/src/test/smali/enums/TestEnums10.smali @@ -0,0 +1,149 @@ +.class public final enum Lenums/TestEnums10; +.super Ljava/lang/Enum; +.source "" + +.field public static final synthetic A02:[Lenums/TestEnums10; + +.field public static final enum A03:Lenums/TestEnums10; +.field public static final enum A04:Lenums/TestEnums10; +.field public static final enum A05:Lenums/TestEnums10; +.field public static final enum A06:Lenums/TestEnums10; +.field public static final enum A07:Lenums/TestEnums10; +.field public static final enum A08:Lenums/TestEnums10; +.field public static final enum A09:Lenums/TestEnums10; +.field public static final enum A0A:Lenums/TestEnums10; +.field public static final enum A0B:Lenums/TestEnums10; +.field public static final enum A0C:Lenums/TestEnums10; + +.field public final A00:I + +.method public static constructor ()V + .locals 33 + const/16 v28, 0x0 + const/16 v27, 0x1 + const-string v3, "CONNECT" + new-instance v26, Lenums/TestEnums10; + move-object/from16 v2, v26 + move/from16 v1, v28 + move/from16 v0, v27 + invoke-direct {v2, v3, v1, v0}, Lenums/TestEnums10;->(Ljava/lang/String;II)V + sput-object v26, Lenums/TestEnums10;->A04:Lenums/TestEnums10; + const/4 v4, 0x2 + const-string v2, "CONNACK" + new-instance v25, Lenums/TestEnums10; + move-object/from16 v1, v25 + invoke-direct {v1, v2, v0, v4}, Lenums/TestEnums10;->(Ljava/lang/String;II)V + sput-object v25, Lenums/TestEnums10;->A03:Lenums/TestEnums10; + const/4 v6, 0x3 + const-string v1, "PUBLISH" + new-instance v24, Lenums/TestEnums10; + move-object/from16 v0, v24 + invoke-direct {v0, v1, v4, v6}, Lenums/TestEnums10;->(Ljava/lang/String;II)V + sput-object v24, Lenums/TestEnums10;->A08:Lenums/TestEnums10; + const/4 v7, 0x4 + const-string v1, "PUBACK" + new-instance v23, Lenums/TestEnums10; + move-object/from16 v0, v23 + invoke-direct {v0, v1, v6, v7}, Lenums/TestEnums10;->(Ljava/lang/String;II)V + sput-object v23, Lenums/TestEnums10;->A07:Lenums/TestEnums10; + const/4 v8, 0x5 + const-string v1, "PUBREC" + new-instance v22, Lenums/TestEnums10; + move-object/from16 v0, v22 + invoke-direct {v0, v1, v7, v8}, Lenums/TestEnums10;->(Ljava/lang/String;II)V + const/4 v9, 0x6 + const-string v1, "PUBREL" + new-instance v21, Lenums/TestEnums10; + move-object/from16 v0, v21 + invoke-direct {v0, v1, v8, v9}, Lenums/TestEnums10;->(Ljava/lang/String;II)V + const/4 v10, 0x7 + const-string v1, "PUBCOMP" + new-instance v20, Lenums/TestEnums10; + move-object/from16 v0, v20 + invoke-direct {v0, v1, v9, v10}, Lenums/TestEnums10;->(Ljava/lang/String;II)V + const/16 v11, 0x8 + const-string v1, "SUBSCRIBE" + new-instance v19, Lenums/TestEnums10; + move-object/from16 v0, v19 + invoke-direct {v0, v1, v10, v11}, Lenums/TestEnums10;->(Ljava/lang/String;II)V + sput-object v19, Lenums/TestEnums10;->A0A:Lenums/TestEnums10; + const/16 v12, 0x9 + const-string v1, "SUBACK" + new-instance v18, Lenums/TestEnums10; + move-object/from16 v0, v18 + invoke-direct {v0, v1, v11, v12}, Lenums/TestEnums10;->(Ljava/lang/String;II)V + sput-object v18, Lenums/TestEnums10;->A09:Lenums/TestEnums10; + const/16 v13, 0xa + const-string v1, "UNSUBSCRIBE" + new-instance v17, Lenums/TestEnums10; + move-object/from16 v0, v17 + invoke-direct {v0, v1, v12, v13}, Lenums/TestEnums10;->(Ljava/lang/String;II)V + sput-object v17, Lenums/TestEnums10;->A0C:Lenums/TestEnums10; + const/16 v14, 0xb + const-string v0, "UNSUBACK" + new-instance v5, Lenums/TestEnums10; + invoke-direct {v5, v0, v13, v14}, Lenums/TestEnums10;->(Ljava/lang/String;II)V + sput-object v5, Lenums/TestEnums10;->A0B:Lenums/TestEnums10; + const/16 v15, 0xc + const-string v0, "PINGREQ" + new-instance v3, Lenums/TestEnums10; + invoke-direct {v3, v0, v14, v15}, Lenums/TestEnums10;->(Ljava/lang/String;II)V + sput-object v3, Lenums/TestEnums10;->A05:Lenums/TestEnums10; + const/16 v2, 0xd + const-string v0, "PINGRESP" + new-instance v1, Lenums/TestEnums10; + invoke-direct {v1, v0, v15, v2}, Lenums/TestEnums10;->(Ljava/lang/String;II)V + sput-object v1, Lenums/TestEnums10;->A06:Lenums/TestEnums10; + const/16 v15, 0xe + const-string v0, "DISCONNECT" + new-instance v16, Lenums/TestEnums10; + move-object/from16 v29, v16 + move-object/from16 v30, v0 + move/from16 v31, v2 + move/from16 v32, v15 + invoke-direct/range {v29 .. v32}, Lenums/TestEnums10;->(Ljava/lang/String;II)V + new-array v15, v15, [Lenums/TestEnums10; + aput-object v26, v15, v28 + aput-object v25, v15, v27 + aput-object v24, v15, v4 + aput-object v23, v15, v6 + aput-object v22, v15, v7 + aput-object v21, v15, v8 + aput-object v20, v15, v9 + aput-object v19, v15, v10 + aput-object v18, v15, v11 + aput-object v17, v15, v12 + aput-object v5, v15, v13 + aput-object v3, v15, v14 + const/16 v0, 0xc + aput-object v1, v15, v0 + aput-object v16, v15, v2 + sput-object v15, Lenums/TestEnums10;->A02:[Lenums/TestEnums10; + + return-void +.end method + +.method public constructor (Ljava/lang/String;II)V + .locals 0 + invoke-direct {p0, p1, p2}, Ljava/lang/Enum;->(Ljava/lang/String;I)V + iput p3, p0, Lenums/TestEnums10;->A00:I + return-void +.end method + +.method public static valueOf(Ljava/lang/String;)Lenums/TestEnums10; + .locals 1 + const-class v0, Lenums/TestEnums10; + 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/TestEnums10; + return-object v0 +.end method + +.method public static values()[Lenums/TestEnums10; + .locals 1 + sget-object v0, Lenums/TestEnums10;->A02:[Lenums/TestEnums10; + invoke-virtual {v0}, [Ljava/lang/Object;->clone()Ljava/lang/Object; + move-result-object v0 + check-cast v0, [Lenums/TestEnums10; + return-object v0 +.end method