fix: handle Kotlin 1.9+ $ENTRIES pattern in enum restoration (PR #2814)
* fix: handle Kotlin 1.9+ $ENTRIES pattern in enum restoration Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * keep $ENTRIES field, it still used in getEntries() method --------- Co-authored-by: clawdbot-silly-waddle <clawdbot-silly-waddle@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Skylot <118523+skylot@users.noreply.github.com>
This commit is contained in:
@@ -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<EnumField> 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;
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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;",
|
||||
"",
|
||||
"<init>",
|
||||
"(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 <clinit>()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;-><init>(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;-><init>(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;-><init>(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 <init>(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;-><init>(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
|
||||
Reference in New Issue
Block a user