Skip to content

Commit f2f1450

Browse files
obus-globusclawdbot-silly-waddleCopilotskylot
authored
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>
1 parent 7b3563f commit f2f1450

3 files changed

Lines changed: 241 additions & 2 deletions

File tree

jadx-core/src/main/java/jadx/core/dex/visitors/EnumVisitor.java

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
import jadx.core.utils.BlockUtils;
5050
import jadx.core.utils.InsnRemover;
5151
import jadx.core.utils.InsnUtils;
52+
import jadx.core.utils.ListUtils;
5253
import jadx.core.utils.Utils;
5354
import jadx.core.utils.exceptions.JadxException;
5455
import jadx.core.utils.exceptions.JadxRuntimeException;
@@ -149,6 +150,14 @@ private boolean convertToEnum(ClassNode cls) {
149150
if (arrArg.isInsnWrap()) {
150151
InsnNode wrappedInsn = ((InsnWrapArg) arrArg).getWrapInsn();
151152
enumFields = extractEnumFieldsFromInsn(data, wrappedInsn);
153+
} else if (arrArg.isRegister()) {
154+
// Kotlin 1.9+ $ENTRIES pattern: array register has multiple uses,
155+
// preventing CodeShrinkVisitor from inlining into the SPUT
156+
RegisterArg regArg = (RegisterArg) arrArg;
157+
InsnNode assignInsn = regArg.getAssignInsn();
158+
if (assignInsn != null) {
159+
enumFields = extractEnumFieldsFromInsn(data, assignInsn);
160+
}
152161
}
153162
if (enumFields == null) {
154163
cls.addWarnComment("Unknown enum class pattern. Please report as an issue!");
@@ -291,8 +300,13 @@ private List<EnumField> extractEnumFieldsFromInvoke(EnumData enumData, InvokeNod
291300
return null;
292301
}
293302
List<EnumField> enumFields = extractEnumFieldsFromInsn(enumData, wrappedInsn);
294-
if (enumFields != null) {
303+
if (enumFields != null && ListUtils.isSingleElement(valuesMth.getUseIn(), enumData.classInitMth)) {
295304
valuesMth.add(AFlag.DONT_GENERATE);
305+
if (valuesMth.getName().equals("$values")) {
306+
// Kotlin synthetic method used for init values
307+
// rename to actual values method to use in $ENTRIES init code
308+
valuesMth.getMethodInfo().setAlias("values");
309+
}
296310
}
297311
return enumFields;
298312
}
@@ -506,7 +520,18 @@ private static FieldInfo checkExternalRegUsage(EnumData data, RegisterArg reg) {
506520
}
507521
case FILLED_NEW_ARRAY: {
508522
// allow usage in values init instruction
509-
if (!data.valuesInitInsn.getArg(0).unwrap().equals(useInsn)) {
523+
InsnArg valuesArg = data.valuesInitInsn.getArg(0);
524+
InsnNode unwrapped = valuesArg.unwrap();
525+
if (unwrapped != null) {
526+
if (unwrapped != useInsn) {
527+
return null;
528+
}
529+
} else if (valuesArg.isRegister()) {
530+
InsnNode valuesAssign = ((RegisterArg) valuesArg).getAssignInsn();
531+
if (valuesAssign != useInsn) {
532+
return null;
533+
}
534+
} else {
510535
return null;
511536
}
512537
break;
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package jadx.tests.integration.enums;
2+
3+
import org.junit.jupiter.api.Test;
4+
5+
import jadx.tests.api.SmaliTest;
6+
7+
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
8+
9+
/**
10+
* Test for Kotlin 1.9+ enum $ENTRIES pattern.
11+
*/
12+
public class TestEnumKotlinEntries extends SmaliTest {
13+
14+
@Test
15+
public void test() {
16+
disableCompilation(); // kotlin.enums.EnumEntries not on test classpath
17+
assertThat(getClassNodeFromSmali())
18+
.code()
19+
.containsLines(1, "ALPHA,", "BETA,", "GAMMA;")
20+
.containsOne("EnumEntries $ENTRIES = EnumEntriesKt.enumEntries(values());")
21+
.doesNotContain("$VALUES")
22+
.doesNotContain("Failed to restore enum");
23+
}
24+
}
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
.class public final enum Lenums/TestEnumKotlinEntries;
2+
.super Ljava/lang/Enum;
3+
.source "TestEnumKotlinEntries.kt"
4+
5+
6+
# annotations
7+
.annotation system Ldalvik/annotation/Signature;
8+
value = {
9+
"Ljava/lang/Enum<",
10+
"Lenums/TestEnumKotlinEntries;",
11+
">;"
12+
}
13+
.end annotation
14+
15+
.annotation runtime Lkotlin/Metadata;
16+
d1 = {
17+
"\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"
18+
}
19+
d2 = {
20+
"Lenums/TestEnumKotlinEntries;",
21+
"",
22+
"<init>",
23+
"(Ljava/lang/String;I)V",
24+
"ALPHA",
25+
"BETA",
26+
"GAMMA"
27+
}
28+
k = 0x1
29+
mv = {
30+
0x2,
31+
0x3,
32+
0x0
33+
}
34+
xi = 0x30
35+
.end annotation
36+
37+
38+
# static fields
39+
.field private static final synthetic $ENTRIES:Lkotlin/enums/EnumEntries;
40+
41+
.field private static final synthetic $VALUES:[Lenums/TestEnumKotlinEntries;
42+
43+
.field public static final enum ALPHA:Lenums/TestEnumKotlinEntries;
44+
45+
.field public static final enum BETA:Lenums/TestEnumKotlinEntries;
46+
47+
.field public static final enum GAMMA:Lenums/TestEnumKotlinEntries;
48+
49+
50+
# direct methods
51+
.method private static final synthetic $values()[Lenums/TestEnumKotlinEntries;
52+
.registers 3
53+
54+
const/4 v0, 0x3
55+
56+
new-array v0, v0, [Lenums/TestEnumKotlinEntries;
57+
58+
const/4 v1, 0x0
59+
60+
sget-object v2, Lenums/TestEnumKotlinEntries;->ALPHA:Lenums/TestEnumKotlinEntries;
61+
62+
aput-object v2, v0, v1
63+
64+
const/4 v1, 0x1
65+
66+
sget-object v2, Lenums/TestEnumKotlinEntries;->BETA:Lenums/TestEnumKotlinEntries;
67+
68+
aput-object v2, v0, v1
69+
70+
const/4 v1, 0x2
71+
72+
sget-object v2, Lenums/TestEnumKotlinEntries;->GAMMA:Lenums/TestEnumKotlinEntries;
73+
74+
aput-object v2, v0, v1
75+
76+
return-object v0
77+
.end method
78+
79+
.method static constructor <clinit>()V
80+
.registers 3
81+
82+
.line 4
83+
new-instance v0, Lenums/TestEnumKotlinEntries;
84+
85+
const-string v1, "ALPHA"
86+
87+
const/4 v2, 0x0
88+
89+
invoke-direct {v0, v1, v2}, Lenums/TestEnumKotlinEntries;-><init>(Ljava/lang/String;I)V
90+
91+
sput-object v0, Lenums/TestEnumKotlinEntries;->ALPHA:Lenums/TestEnumKotlinEntries;
92+
93+
.line 5
94+
new-instance v0, Lenums/TestEnumKotlinEntries;
95+
96+
const-string v1, "BETA"
97+
98+
const/4 v2, 0x1
99+
100+
invoke-direct {v0, v1, v2}, Lenums/TestEnumKotlinEntries;-><init>(Ljava/lang/String;I)V
101+
102+
sput-object v0, Lenums/TestEnumKotlinEntries;->BETA:Lenums/TestEnumKotlinEntries;
103+
104+
.line 6
105+
new-instance v0, Lenums/TestEnumKotlinEntries;
106+
107+
const-string v1, "GAMMA"
108+
109+
const/4 v2, 0x2
110+
111+
invoke-direct {v0, v1, v2}, Lenums/TestEnumKotlinEntries;-><init>(Ljava/lang/String;I)V
112+
113+
sput-object v0, Lenums/TestEnumKotlinEntries;->GAMMA:Lenums/TestEnumKotlinEntries;
114+
115+
invoke-static {}, Lenums/TestEnumKotlinEntries;->$values()[Lenums/TestEnumKotlinEntries;
116+
117+
move-result-object v0
118+
119+
sput-object v0, Lenums/TestEnumKotlinEntries;->$VALUES:[Lenums/TestEnumKotlinEntries;
120+
121+
check-cast v0, [Ljava/lang/Enum;
122+
123+
invoke-static {v0}, Lkotlin/enums/EnumEntriesKt;->enumEntries([Ljava/lang/Enum;)Lkotlin/enums/EnumEntries;
124+
125+
move-result-object v0
126+
127+
sput-object v0, Lenums/TestEnumKotlinEntries;->$ENTRIES:Lkotlin/enums/EnumEntries;
128+
129+
return-void
130+
.end method
131+
132+
.method private constructor <init>(Ljava/lang/String;I)V
133+
.registers 3
134+
.param p1, "$enum$name" # Ljava/lang/String;
135+
.param p2, "$enum$ordinal" # I
136+
.annotation system Ldalvik/annotation/Signature;
137+
value = {
138+
"()V"
139+
}
140+
.end annotation
141+
142+
.line 3
143+
invoke-direct {p0, p1, p2}, Ljava/lang/Enum;-><init>(Ljava/lang/String;I)V
144+
145+
return-void
146+
.end method
147+
148+
.method public static getEntries()Lkotlin/enums/EnumEntries;
149+
.registers 1
150+
.annotation system Ldalvik/annotation/Signature;
151+
value = {
152+
"()",
153+
"Lkotlin/enums/EnumEntries<",
154+
"Lenums/TestEnumKotlinEntries;",
155+
">;"
156+
}
157+
.end annotation
158+
159+
sget-object v0, Lenums/TestEnumKotlinEntries;->$ENTRIES:Lkotlin/enums/EnumEntries;
160+
161+
return-object v0
162+
.end method
163+
164+
.method public static valueOf(Ljava/lang/String;)Lenums/TestEnumKotlinEntries;
165+
.registers 2
166+
167+
const-class v0, Lenums/TestEnumKotlinEntries;
168+
169+
invoke-static {v0, p0}, Ljava/lang/Enum;->valueOf(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
170+
171+
move-result-object v0
172+
173+
check-cast v0, Lenums/TestEnumKotlinEntries;
174+
175+
return-object v0
176+
.end method
177+
178+
.method public static values()[Lenums/TestEnumKotlinEntries;
179+
.registers 1
180+
181+
sget-object v0, Lenums/TestEnumKotlinEntries;->$VALUES:[Lenums/TestEnumKotlinEntries;
182+
183+
invoke-virtual {v0}, Ljava/lang/Object;->clone()Ljava/lang/Object;
184+
185+
move-result-object v0
186+
187+
check-cast v0, [Lenums/TestEnumKotlinEntries;
188+
189+
return-object v0
190+
.end method

0 commit comments

Comments
 (0)