| skipped 39 lines |
40 | 40 | | import jadx.core.dex.nodes.InsnNode; |
41 | 41 | | import jadx.core.dex.nodes.MethodNode; |
42 | 42 | | import jadx.core.dex.nodes.RootNode; |
| 43 | + | import jadx.core.dex.regions.Region; |
| 44 | + | import jadx.core.dex.visitors.regions.CheckRegions; |
| 45 | + | import jadx.core.dex.visitors.regions.IfRegionVisitor; |
43 | 46 | | import jadx.core.dex.visitors.shrink.CodeShrinkVisitor; |
44 | 47 | | import jadx.core.utils.BlockInsnPair; |
45 | 48 | | import jadx.core.utils.BlockUtils; |
46 | 49 | | import jadx.core.utils.InsnRemover; |
47 | 50 | | import jadx.core.utils.InsnUtils; |
| 51 | + | import jadx.core.utils.ListUtils; |
48 | 52 | | import jadx.core.utils.Utils; |
49 | 53 | | import jadx.core.utils.exceptions.JadxException; |
| 54 | + | import jadx.core.utils.exceptions.JadxRuntimeException; |
50 | 55 | | |
51 | 56 | | import static jadx.core.utils.InsnUtils.checkInsnType; |
52 | 57 | | import static jadx.core.utils.InsnUtils.getSingleArg; |
| skipped 2 lines |
55 | 60 | | @JadxVisitor( |
56 | 61 | | name = "EnumVisitor", |
57 | 62 | | desc = "Restore enum classes", |
58 | | - | runAfter = { CodeShrinkVisitor.class, ModVisitor.class, ReSugarCode.class }, |
59 | | - | runBefore = { ExtractFieldInit.class } |
| 63 | + | runAfter = { |
| 64 | + | CodeShrinkVisitor.class, // all possible instructions already inlined |
| 65 | + | ModVisitor.class, |
| 66 | + | ReSugarCode.class, |
| 67 | + | IfRegionVisitor.class, // ternary operator inlined |
| 68 | + | CheckRegions.class // regions processing finished |
| 69 | + | }, |
| 70 | + | runBefore = { |
| 71 | + | ExtractFieldInit.class |
| 72 | + | } |
60 | 73 | | ) |
61 | 74 | | public class EnumVisitor extends AbstractVisitor { |
62 | 75 | | |
| skipped 29 lines |
92 | 105 | | AccessInfo accessFlags = cls.getAccessFlags(); |
93 | 106 | | if (accessFlags.isEnum()) { |
94 | 107 | | cls.setAccessFlags(accessFlags.remove(AccessFlags.ENUM)); |
95 | | - | cls.addWarnComment("Failed to restore enum class, 'enum' modifier removed"); |
| 108 | + | cls.addWarnComment("Failed to restore enum class, 'enum' modifier and super class removed"); |
96 | 109 | | } |
97 | 110 | | } |
98 | 111 | | return true; |
| skipped 3 lines |
102 | 115 | | if (!cls.isEnum()) { |
103 | 116 | | return false; |
104 | 117 | | } |
| 118 | + | ArgType superType = cls.getSuperClass(); |
| 119 | + | if (superType != null && superType.getObject().equals(ArgType.ENUM.getObject())) { |
| 120 | + | cls.add(AFlag.REMOVE_SUPER_CLASS); |
| 121 | + | } |
105 | 122 | | MethodNode classInitMth = cls.getClassInitMth(); |
106 | 123 | | if (classInitMth == null) { |
107 | 124 | | cls.addWarnComment("Enum class init method not found"); |
108 | 125 | | return false; |
109 | 126 | | } |
110 | | - | if (classInitMth.getBasicBlocks().isEmpty()) { |
| 127 | + | Region staticRegion = classInitMth.getRegion(); |
| 128 | + | if (staticRegion == null || classInitMth.getBasicBlocks().isEmpty()) { |
111 | 129 | | return false; |
112 | 130 | | } |
| 131 | + | if (!ListUtils.allMatch(staticRegion.getSubBlocks(), BlockNode.class::isInstance)) { |
| 132 | + | cls.addWarnComment("Unexpected branching instructions in enum static init block"); |
| 133 | + | return false; |
| 134 | + | } |
| 135 | + | List<BlockNode> staticBlocks = ListUtils.map(staticRegion.getSubBlocks(), BlockNode.class::cast); |
113 | 136 | | ArgType clsType = cls.getClassInfo().getType(); |
114 | 137 | | |
115 | 138 | | // search "$VALUES" field (holds all enum values) |
| skipped 21 lines |
137 | 160 | | return false; |
138 | 161 | | } |
139 | 162 | | FieldNode valuesField = valuesCandidates.get(0); |
140 | | - | List<InsnNode> toRemove = new ArrayList<>(); |
141 | 163 | | |
142 | 164 | | // search "$VALUES" array init and collect enum fields |
143 | 165 | | BlockInsnPair valuesInitPair = getValuesInitInsn(classInitMth, valuesField); |
| skipped 3 lines |
147 | 169 | | BlockNode staticBlock = valuesInitPair.getBlock(); |
148 | 170 | | InsnNode valuesInitInsn = valuesInitPair.getInsn(); |
149 | 171 | | |
| 172 | + | EnumData enumData = new EnumData(cls, valuesField, staticBlocks); |
| 173 | + | |
150 | 174 | | List<EnumField> enumFields = null; |
151 | 175 | | InsnArg arrArg = valuesInitInsn.getArg(0); |
152 | 176 | | if (arrArg.isInsnWrap()) { |
153 | 177 | | InsnNode wrappedInsn = ((InsnWrapArg) arrArg).getWrapInsn(); |
154 | | - | enumFields = extractEnumFieldsFromInsn(cls, staticBlock, wrappedInsn, toRemove); |
| 178 | + | enumFields = extractEnumFieldsFromInsn(enumData, wrappedInsn); |
155 | 179 | | } |
156 | 180 | | if (enumFields == null) { |
| 181 | + | cls.addWarnComment("Unknown enum class pattern. Please report as an issue!"); |
157 | 182 | | return false; |
158 | 183 | | } |
159 | | - | toRemove.add(valuesInitInsn); |
| 184 | + | enumData.toRemove.add(valuesInitInsn); |
160 | 185 | | |
161 | 186 | | // all checks complete, perform transform |
162 | 187 | | EnumClassAttr attr = new EnumClassAttr(enumFields); |
| skipped 13 lines |
176 | 201 | | fieldNode.getFieldInfo().setAlias(name); |
177 | 202 | | } |
178 | 203 | | fieldNode.add(AFlag.DONT_GENERATE); |
179 | | - | processConstructorInsn(cls, enumField, classInitMth, staticBlock, toRemove); |
| 204 | + | processConstructorInsn(enumData, enumField, classInitMth); |
180 | 205 | | } |
181 | 206 | | valuesField.add(AFlag.DONT_GENERATE); |
182 | | - | InsnRemover.removeAllAndUnbind(classInitMth, staticBlock, toRemove); |
| 207 | + | InsnRemover.removeAllAndUnbind(classInitMth, enumData.toRemove); |
183 | 208 | | if (classInitMth.countInsns() == 0) { |
184 | 209 | | classInitMth.add(AFlag.DONT_GENERATE); |
185 | | - | } else if (!toRemove.isEmpty()) { |
| 210 | + | } else if (!enumData.toRemove.isEmpty()) { |
186 | 211 | | CodeShrinkVisitor.shrinkMethod(classInitMth); |
187 | 212 | | } |
188 | 213 | | removeEnumMethods(cls, clsType, valuesField); |
189 | 214 | | return true; |
190 | 215 | | } |
191 | 216 | | |
192 | | - | private void processConstructorInsn(ClassNode cls, EnumField enumField, MethodNode classInitMth, |
193 | | - | BlockNode staticBlock, List<InsnNode> toRemove) { |
| 217 | + | private void processConstructorInsn(EnumData data, EnumField enumField, MethodNode classInitMth) { |
194 | 218 | | ConstructorInsn co = enumField.getConstrInsn(); |
195 | 219 | | ClassInfo enumClsInfo = co.getClassType(); |
196 | | - | if (!enumClsInfo.equals(cls.getClassInfo())) { |
197 | | - | ClassNode enumCls = cls.root().resolveClass(enumClsInfo); |
| 220 | + | if (!enumClsInfo.equals(data.cls.getClassInfo())) { |
| 221 | + | ClassNode enumCls = data.cls.root().resolveClass(enumClsInfo); |
198 | 222 | | if (enumCls != null) { |
199 | | - | processEnumCls(cls, enumField, enumCls); |
| 223 | + | processEnumCls(data.cls, enumField, enumCls); |
200 | 224 | | } |
201 | 225 | | } |
202 | | - | List<RegisterArg> regs = new ArrayList<>(); |
203 | | - | co.getRegisterArgs(regs); |
204 | | - | if (!regs.isEmpty()) { |
205 | | - | cls.addWarnComment("Init of enum " + enumField.getField().getName() + " can be incorrect"); |
206 | | - | } |
207 | | - | MethodNode ctrMth = cls.root().resolveMethod(co.getCallMth()); |
| 226 | + | MethodNode ctrMth = data.cls.root().resolveMethod(co.getCallMth()); |
208 | 227 | | if (ctrMth != null) { |
209 | 228 | | markArgsForSkip(ctrMth); |
210 | 229 | | } |
211 | 230 | | RegisterArg coResArg = co.getResult(); |
212 | 231 | | if (coResArg == null || coResArg.getSVar().getUseList().size() <= 2) { |
213 | | - | toRemove.add(co); |
| 232 | + | data.toRemove.add(co); |
214 | 233 | | } else { |
215 | 234 | | // constructor result used in other places -> replace constructor with enum field get (SGET) |
216 | 235 | | IndexInsnNode enumGet = new IndexInsnNode(InsnType.SGET, enumField.getField().getFieldInfo(), 0); |
217 | 236 | | enumGet.setResult(coResArg.duplicate()); |
218 | | - | BlockUtils.replaceInsn(classInitMth, staticBlock, co, enumGet); |
| 237 | + | BlockUtils.replaceInsn(classInitMth, co, enumGet); |
219 | 238 | | } |
220 | 239 | | } |
221 | 240 | | |
222 | 241 | | @Nullable |
223 | | - | private List<EnumField> extractEnumFieldsFromInsn(ClassNode cls, BlockNode staticBlock, |
224 | | - | InsnNode wrappedInsn, List<InsnNode> toRemove) { |
| 242 | + | private List<EnumField> extractEnumFieldsFromInsn(EnumData enumData, InsnNode wrappedInsn) { |
225 | 243 | | switch (wrappedInsn.getType()) { |
226 | 244 | | case FILLED_NEW_ARRAY: |
227 | | - | return extractEnumFieldsFromFilledArray(cls, wrappedInsn, staticBlock, toRemove); |
| 245 | + | return extractEnumFieldsFromFilledArray(enumData, wrappedInsn); |
228 | 246 | | |
229 | 247 | | case INVOKE: |
230 | 248 | | // handle redirection of values array fill (added in java 15) |
231 | | - | return extractEnumFieldsFromInvoke(cls, staticBlock, (InvokeNode) wrappedInsn, toRemove); |
| 249 | + | return extractEnumFieldsFromInvoke(enumData, (InvokeNode) wrappedInsn); |
232 | 250 | | |
233 | 251 | | case NEW_ARRAY: |
234 | 252 | | InsnArg arg = wrappedInsn.getArg(0); |
| skipped 8 lines |
243 | 261 | | } |
244 | 262 | | } |
245 | 263 | | |
246 | | - | private List<EnumField> extractEnumFieldsFromInvoke(ClassNode cls, BlockNode staticBlock, |
247 | | - | InvokeNode invokeNode, List<InsnNode> toRemove) { |
| 264 | + | private List<EnumField> extractEnumFieldsFromInvoke(EnumData enumData, InvokeNode invokeNode) { |
248 | 265 | | MethodInfo callMth = invokeNode.getCallMth(); |
249 | | - | MethodNode valuesMth = cls.root().resolveMethod(callMth); |
| 266 | + | MethodNode valuesMth = enumData.cls.root().resolveMethod(callMth); |
250 | 267 | | if (valuesMth == null || valuesMth.isVoidReturn()) { |
251 | 268 | | return null; |
252 | 269 | | } |
| skipped 3 lines |
256 | 273 | | if (wrappedInsn == null) { |
257 | 274 | | return null; |
258 | 275 | | } |
259 | | - | List<EnumField> enumFields = extractEnumFieldsFromInsn(cls, staticBlock, wrappedInsn, toRemove); |
| 276 | + | List<EnumField> enumFields = extractEnumFieldsFromInsn(enumData, wrappedInsn); |
260 | 277 | | if (enumFields != null) { |
261 | 278 | | valuesMth.add(AFlag.DONT_GENERATE); |
262 | 279 | | } |
| skipped 16 lines |
279 | 296 | | return null; |
280 | 297 | | } |
281 | 298 | | |
282 | | - | private List<EnumField> extractEnumFieldsFromFilledArray(ClassNode cls, InsnNode arrFillInsn, BlockNode staticBlock, |
283 | | - | List<InsnNode> toRemove) { |
| 299 | + | private List<EnumField> extractEnumFieldsFromFilledArray(EnumData enumData, InsnNode arrFillInsn) { |
284 | 300 | | List<EnumField> enumFields = new ArrayList<>(); |
285 | 301 | | for (InsnArg arg : arrFillInsn.getArguments()) { |
286 | 302 | | EnumField field = null; |
287 | 303 | | if (arg.isInsnWrap()) { |
288 | 304 | | InsnNode wrappedInsn = ((InsnWrapArg) arg).getWrapInsn(); |
289 | | - | field = processEnumFieldByWrappedInsn(cls, wrappedInsn, staticBlock, toRemove); |
| 305 | + | field = processEnumFieldByWrappedInsn(enumData, wrappedInsn); |
290 | 306 | | } else if (arg.isRegister()) { |
291 | | - | field = processEnumFieldByRegister(cls, (RegisterArg) arg, staticBlock, toRemove); |
| 307 | + | field = processEnumFieldByRegister(enumData, (RegisterArg) arg); |
292 | 308 | | } |
293 | 309 | | if (field == null) { |
294 | 310 | | return null; |
295 | 311 | | } |
296 | 312 | | enumFields.add(field); |
297 | 313 | | } |
298 | | - | toRemove.add(arrFillInsn); |
| 314 | + | enumData.toRemove.add(arrFillInsn); |
299 | 315 | | return enumFields; |
300 | 316 | | } |
301 | 317 | | |
302 | | - | private EnumField processEnumFieldByWrappedInsn(ClassNode cls, InsnNode wrappedInsn, BlockNode staticBlock, List<InsnNode> toRemove) { |
| 318 | + | private EnumField processEnumFieldByWrappedInsn(EnumData data, InsnNode wrappedInsn) { |
303 | 319 | | if (wrappedInsn.getType() == InsnType.SGET) { |
304 | | - | return processEnumFieldByField(cls, wrappedInsn, staticBlock, toRemove); |
| 320 | + | return processEnumFieldByField(data, wrappedInsn); |
305 | 321 | | } |
306 | 322 | | ConstructorInsn constructorInsn = castConstructorInsn(wrappedInsn); |
307 | 323 | | if (constructorInsn != null) { |
308 | | - | FieldNode enumFieldNode = createFakeField(cls, "EF" + constructorInsn.getOffset()); |
309 | | - | cls.addField(enumFieldNode); |
310 | | - | return createEnumFieldByConstructor(cls, enumFieldNode, constructorInsn); |
| 324 | + | FieldNode enumFieldNode = createFakeField(data.cls, "EF" + constructorInsn.getOffset()); |
| 325 | + | data.cls.addField(enumFieldNode); |
| 326 | + | return createEnumFieldByConstructor(data.cls, enumFieldNode, constructorInsn); |
311 | 327 | | } |
312 | 328 | | return null; |
313 | 329 | | } |
314 | 330 | | |
315 | 331 | | @Nullable |
316 | | - | private EnumField processEnumFieldByField(ClassNode cls, InsnNode sgetInsn, BlockNode staticBlock, List<InsnNode> toRemove) { |
| 332 | + | private EnumField processEnumFieldByField(EnumData data, InsnNode sgetInsn) { |
317 | 333 | | if (sgetInsn.getType() != InsnType.SGET) { |
318 | 334 | | return null; |
319 | 335 | | } |
320 | 336 | | FieldInfo fieldInfo = (FieldInfo) ((IndexInsnNode) sgetInsn).getIndex(); |
321 | | - | FieldNode enumFieldNode = cls.searchField(fieldInfo); |
| 337 | + | FieldNode enumFieldNode = data.cls.searchField(fieldInfo); |
322 | 338 | | if (enumFieldNode == null) { |
323 | 339 | | return null; |
324 | 340 | | } |
325 | | - | InsnNode sputInsn = searchFieldPutInsn(cls, staticBlock, enumFieldNode); |
| 341 | + | InsnNode sputInsn = searchFieldPutInsn(data, enumFieldNode); |
326 | 342 | | if (sputInsn == null) { |
327 | 343 | | return null; |
328 | 344 | | } |
| skipped 4 lines |
333 | 349 | | } |
334 | 350 | | RegisterArg sgetResult = sgetInsn.getResult(); |
335 | 351 | | if (sgetResult == null || sgetResult.getSVar().getUseCount() == 1) { |
336 | | - | toRemove.add(sgetInsn); |
| 352 | + | data.toRemove.add(sgetInsn); |
337 | 353 | | } |
338 | | - | toRemove.add(sputInsn); |
339 | | - | return createEnumFieldByConstructor(cls, enumFieldNode, co); |
| 354 | + | data.toRemove.add(sputInsn); |
| 355 | + | return createEnumFieldByConstructor(data.cls, enumFieldNode, co); |
340 | 356 | | } |
341 | 357 | | |
342 | 358 | | @Nullable |
343 | | - | private EnumField processEnumFieldByRegister(ClassNode cls, RegisterArg arg, BlockNode staticBlock, List<InsnNode> toRemove) { |
| 359 | + | private EnumField processEnumFieldByRegister(EnumData data, RegisterArg arg) { |
344 | 360 | | InsnNode assignInsn = arg.getAssignInsn(); |
345 | 361 | | if (assignInsn != null && assignInsn.getType() == InsnType.SGET) { |
346 | | - | return processEnumFieldByField(cls, assignInsn, staticBlock, toRemove); |
| 362 | + | return processEnumFieldByField(data, assignInsn); |
347 | 363 | | } |
348 | 364 | | |
349 | 365 | | SSAVar ssaVar = arg.getSVar(); |
| skipped 4 lines |
354 | 370 | | if (constrInsn == null || constrInsn.getType() != InsnType.CONSTRUCTOR) { |
355 | 371 | | return null; |
356 | 372 | | } |
357 | | - | FieldNode enumFieldNode = searchEnumField(cls, ssaVar, toRemove); |
| 373 | + | FieldNode enumFieldNode = searchEnumField(data, ssaVar); |
358 | 374 | | if (enumFieldNode == null) { |
359 | | - | enumFieldNode = createFakeField(cls, "EF" + arg.getRegNum()); |
360 | | - | cls.addField(enumFieldNode); |
| 375 | + | enumFieldNode = createFakeField(data.cls, "EF" + arg.getRegNum()); |
| 376 | + | data.cls.addField(enumFieldNode); |
361 | 377 | | } |
362 | | - | return createEnumFieldByConstructor(cls, enumFieldNode, (ConstructorInsn) constrInsn); |
| 378 | + | return createEnumFieldByConstructor(data.cls, enumFieldNode, (ConstructorInsn) constrInsn); |
363 | 379 | | } |
364 | 380 | | |
365 | 381 | | private FieldNode createFakeField(ClassNode cls, String name) { |
| skipped 6 lines |
372 | 388 | | } |
373 | 389 | | |
374 | 390 | | @Nullable |
375 | | - | private FieldNode searchEnumField(ClassNode cls, SSAVar ssaVar, List<InsnNode> toRemove) { |
| 391 | + | private FieldNode searchEnumField(EnumData data, SSAVar ssaVar) { |
376 | 392 | | InsnNode sputInsn = ssaVar.getUseList().get(0).getParentInsn(); |
377 | 393 | | if (sputInsn == null || sputInsn.getType() != InsnType.SPUT) { |
378 | 394 | | return null; |
379 | 395 | | } |
380 | 396 | | FieldInfo fieldInfo = (FieldInfo) ((IndexInsnNode) sputInsn).getIndex(); |
381 | | - | FieldNode enumFieldNode = cls.searchField(fieldInfo); |
| 397 | + | FieldNode enumFieldNode = data.cls.searchField(fieldInfo); |
382 | 398 | | if (enumFieldNode == null) { |
383 | 399 | | return null; |
384 | 400 | | } |
385 | | - | toRemove.add(sputInsn); |
| 401 | + | data.toRemove.add(sputInsn); |
386 | 402 | | return enumFieldNode; |
387 | 403 | | } |
388 | 404 | | |
| skipped 20 lines |
409 | 425 | | if (ctrMth == null) { |
410 | 426 | | return null; |
411 | 427 | | } |
| 428 | + | List<RegisterArg> regs = new ArrayList<>(); |
| 429 | + | co.getRegisterArgs(regs); |
| 430 | + | if (!regs.isEmpty()) { |
| 431 | + | throw new JadxRuntimeException("Init of enum " + enumFieldNode.getName() + " uses external variables"); |
| 432 | + | } |
412 | 433 | | return new EnumField(enumFieldNode, co); |
413 | 434 | | } |
414 | 435 | | |
415 | 436 | | @Nullable |
416 | | - | private InsnNode searchFieldPutInsn(ClassNode cls, BlockNode staticBlock, FieldNode enumFieldNode) { |
417 | | - | for (InsnNode sputInsn : staticBlock.getInstructions()) { |
418 | | - | if (sputInsn != null && sputInsn.getType() == InsnType.SPUT) { |
419 | | - | FieldInfo f = (FieldInfo) ((IndexInsnNode) sputInsn).getIndex(); |
420 | | - | FieldNode fieldNode = cls.searchField(f); |
421 | | - | if (Objects.equals(fieldNode, enumFieldNode)) { |
422 | | - | return sputInsn; |
| 437 | + | private InsnNode searchFieldPutInsn(EnumData data, FieldNode enumFieldNode) { |
| 438 | + | for (BlockNode block : data.staticBlocks) { |
| 439 | + | for (InsnNode sputInsn : block.getInstructions()) { |
| 440 | + | if (sputInsn != null && sputInsn.getType() == InsnType.SPUT) { |
| 441 | + | FieldInfo f = (FieldInfo) ((IndexInsnNode) sputInsn).getIndex(); |
| 442 | + | FieldNode fieldNode = data.cls.searchField(f); |
| 443 | + | if (Objects.equals(fieldNode, enumFieldNode)) { |
| 444 | + | return sputInsn; |
| 445 | + | } |
423 | 446 | | } |
424 | 447 | | } |
425 | 448 | | } |
| skipped 178 lines |
604 | 627 | | } |
605 | 628 | | } |
606 | 629 | | return null; |
| 630 | + | } |
| 631 | + | |
| 632 | + | private static class EnumData { |
| 633 | + | final ClassNode cls; |
| 634 | + | final FieldNode valuesField; |
| 635 | + | final List<BlockNode> staticBlocks; |
| 636 | + | final List<InsnNode> toRemove = new ArrayList<>(); |
| 637 | + | |
| 638 | + | public EnumData(ClassNode cls, FieldNode valuesField, List<BlockNode> staticBlocks) { |
| 639 | + | this.cls = cls; |
| 640 | + | this.valuesField = valuesField; |
| 641 | + | this.staticBlocks = staticBlocks; |
| 642 | + | } |
607 | 643 | | } |
608 | 644 | | } |
609 | 645 | | |