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 32c4539cc..955601829 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 @@ -49,6 +49,7 @@ import jadx.core.utils.BlockInsnPair; import jadx.core.utils.BlockUtils; import jadx.core.utils.InsnRemover; import jadx.core.utils.InsnUtils; +import jadx.core.utils.ListUtils; import jadx.core.utils.Utils; import jadx.core.utils.exceptions.JadxException; import jadx.core.utils.exceptions.JadxRuntimeException; @@ -149,6 +150,14 @@ public class EnumVisitor extends AbstractVisitor { if (arrArg.isInsnWrap()) { InsnNode wrappedInsn = ((InsnWrapArg) arrArg).getWrapInsn(); enumFields = extractEnumFieldsFromInsn(data, wrappedInsn); + } else if (arrArg.isRegister()) { + // Kotlin 1.9+ $ENTRIES pattern: array register has multiple uses, + // preventing CodeShrinkVisitor from inlining into the SPUT + RegisterArg regArg = (RegisterArg) arrArg; + InsnNode assignInsn = regArg.getAssignInsn(); + if (assignInsn != null) { + enumFields = extractEnumFieldsFromInsn(data, assignInsn); + } } if (enumFields == null) { cls.addWarnComment("Unknown enum class pattern. Please report as an issue!"); @@ -291,8 +300,13 @@ public class EnumVisitor extends AbstractVisitor { return null; } List enumFields = extractEnumFieldsFromInsn(enumData, wrappedInsn); - if (enumFields != null) { + if (enumFields != null && ListUtils.isSingleElement(valuesMth.getUseIn(), enumData.classInitMth)) { valuesMth.add(AFlag.DONT_GENERATE); + if (valuesMth.getName().equals("$values")) { + // Kotlin synthetic method used for init values + // rename to actual values method to use in $ENTRIES init code + valuesMth.getMethodInfo().setAlias("values"); + } } return enumFields; } @@ -506,7 +520,18 @@ public class EnumVisitor extends AbstractVisitor { } case FILLED_NEW_ARRAY: { // allow usage in values init instruction - if (!data.valuesInitInsn.getArg(0).unwrap().equals(useInsn)) { + InsnArg valuesArg = data.valuesInitInsn.getArg(0); + InsnNode unwrapped = valuesArg.unwrap(); + if (unwrapped != null) { + if (unwrapped != useInsn) { + return null; + } + } else if (valuesArg.isRegister()) { + InsnNode valuesAssign = ((RegisterArg) valuesArg).getAssignInsn(); + if (valuesAssign != useInsn) { + return null; + } + } else { return null; } break; diff --git a/jadx-core/src/test/java/jadx/tests/integration/enums/TestEnumKotlinEntries.java b/jadx-core/src/test/java/jadx/tests/integration/enums/TestEnumKotlinEntries.java new file mode 100644 index 000000000..131b79943 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/enums/TestEnumKotlinEntries.java @@ -0,0 +1,24 @@ +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; + +/** + * Test for Kotlin 1.9+ enum $ENTRIES pattern. + */ +public class TestEnumKotlinEntries extends SmaliTest { + + @Test + public void test() { + disableCompilation(); // kotlin.enums.EnumEntries not on test classpath + assertThat(getClassNodeFromSmali()) + .code() + .containsLines(1, "ALPHA,", "BETA,", "GAMMA;") + .containsOne("EnumEntries $ENTRIES = EnumEntriesKt.enumEntries(values());") + .doesNotContain("$VALUES") + .doesNotContain("Failed to restore enum"); + } +} diff --git a/jadx-core/src/test/smali/enums/TestEnumKotlinEntries.smali b/jadx-core/src/test/smali/enums/TestEnumKotlinEntries.smali new file mode 100644 index 000000000..e6289d1c7 --- /dev/null +++ b/jadx-core/src/test/smali/enums/TestEnumKotlinEntries.smali @@ -0,0 +1,190 @@ +.class public final enum Lenums/TestEnumKotlinEntries; +.super Ljava/lang/Enum; +.source "TestEnumKotlinEntries.kt" + + +# annotations +.annotation system Ldalvik/annotation/Signature; + value = { + "Ljava/lang/Enum<", + "Lenums/TestEnumKotlinEntries;", + ">;" + } +.end annotation + +.annotation runtime Lkotlin/Metadata; + d1 = { + "\u0000\u000c\n\u0002\u0018\u0002\n\u0002\u0010\u0010\n\u0002\u0008\u0005\u0008\u0086\u0081\u0002\u0018\u00002\u0008\u0012\u0004\u0012\u00020\u00000\u0001B\t\u0008\u0002\u00a2\u0006\u0004\u0008\u0002\u0010\u0003j\u0002\u0008\u0004j\u0002\u0008\u0005j\u0002\u0008\u0006" + } + d2 = { + "Lenums/TestEnumKotlinEntries;", + "", + "", + "(Ljava/lang/String;I)V", + "ALPHA", + "BETA", + "GAMMA" + } + k = 0x1 + mv = { + 0x2, + 0x3, + 0x0 + } + xi = 0x30 +.end annotation + + +# static fields +.field private static final synthetic $ENTRIES:Lkotlin/enums/EnumEntries; + +.field private static final synthetic $VALUES:[Lenums/TestEnumKotlinEntries; + +.field public static final enum ALPHA:Lenums/TestEnumKotlinEntries; + +.field public static final enum BETA:Lenums/TestEnumKotlinEntries; + +.field public static final enum GAMMA:Lenums/TestEnumKotlinEntries; + + +# direct methods +.method private static final synthetic $values()[Lenums/TestEnumKotlinEntries; + .registers 3 + + const/4 v0, 0x3 + + new-array v0, v0, [Lenums/TestEnumKotlinEntries; + + const/4 v1, 0x0 + + sget-object v2, Lenums/TestEnumKotlinEntries;->ALPHA:Lenums/TestEnumKotlinEntries; + + aput-object v2, v0, v1 + + const/4 v1, 0x1 + + sget-object v2, Lenums/TestEnumKotlinEntries;->BETA:Lenums/TestEnumKotlinEntries; + + aput-object v2, v0, v1 + + const/4 v1, 0x2 + + sget-object v2, Lenums/TestEnumKotlinEntries;->GAMMA:Lenums/TestEnumKotlinEntries; + + aput-object v2, v0, v1 + + return-object v0 +.end method + +.method static constructor ()V + .registers 3 + + .line 4 + new-instance v0, Lenums/TestEnumKotlinEntries; + + const-string v1, "ALPHA" + + const/4 v2, 0x0 + + invoke-direct {v0, v1, v2}, Lenums/TestEnumKotlinEntries;->(Ljava/lang/String;I)V + + sput-object v0, Lenums/TestEnumKotlinEntries;->ALPHA:Lenums/TestEnumKotlinEntries; + + .line 5 + new-instance v0, Lenums/TestEnumKotlinEntries; + + const-string v1, "BETA" + + const/4 v2, 0x1 + + invoke-direct {v0, v1, v2}, Lenums/TestEnumKotlinEntries;->(Ljava/lang/String;I)V + + sput-object v0, Lenums/TestEnumKotlinEntries;->BETA:Lenums/TestEnumKotlinEntries; + + .line 6 + new-instance v0, Lenums/TestEnumKotlinEntries; + + const-string v1, "GAMMA" + + const/4 v2, 0x2 + + invoke-direct {v0, v1, v2}, Lenums/TestEnumKotlinEntries;->(Ljava/lang/String;I)V + + sput-object v0, Lenums/TestEnumKotlinEntries;->GAMMA:Lenums/TestEnumKotlinEntries; + + invoke-static {}, Lenums/TestEnumKotlinEntries;->$values()[Lenums/TestEnumKotlinEntries; + + move-result-object v0 + + sput-object v0, Lenums/TestEnumKotlinEntries;->$VALUES:[Lenums/TestEnumKotlinEntries; + + check-cast v0, [Ljava/lang/Enum; + + invoke-static {v0}, Lkotlin/enums/EnumEntriesKt;->enumEntries([Ljava/lang/Enum;)Lkotlin/enums/EnumEntries; + + move-result-object v0 + + sput-object v0, Lenums/TestEnumKotlinEntries;->$ENTRIES:Lkotlin/enums/EnumEntries; + + return-void +.end method + +.method private constructor (Ljava/lang/String;I)V + .registers 3 + .param p1, "$enum$name" # Ljava/lang/String; + .param p2, "$enum$ordinal" # I + .annotation system Ldalvik/annotation/Signature; + value = { + "()V" + } + .end annotation + + .line 3 + invoke-direct {p0, p1, p2}, Ljava/lang/Enum;->(Ljava/lang/String;I)V + + return-void +.end method + +.method public static getEntries()Lkotlin/enums/EnumEntries; + .registers 1 + .annotation system Ldalvik/annotation/Signature; + value = { + "()", + "Lkotlin/enums/EnumEntries<", + "Lenums/TestEnumKotlinEntries;", + ">;" + } + .end annotation + + sget-object v0, Lenums/TestEnumKotlinEntries;->$ENTRIES:Lkotlin/enums/EnumEntries; + + return-object v0 +.end method + +.method public static valueOf(Ljava/lang/String;)Lenums/TestEnumKotlinEntries; + .registers 2 + + const-class v0, Lenums/TestEnumKotlinEntries; + + 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/TestEnumKotlinEntries; + + return-object v0 +.end method + +.method public static values()[Lenums/TestEnumKotlinEntries; + .registers 1 + + sget-object v0, Lenums/TestEnumKotlinEntries;->$VALUES:[Lenums/TestEnumKotlinEntries; + + invoke-virtual {v0}, Ljava/lang/Object;->clone()Ljava/lang/Object; + + move-result-object v0 + + check-cast v0, [Lenums/TestEnumKotlinEntries; + + return-object v0 +.end method