| skipped 36 lines |
37 | 37 | | import jadx.core.dex.nodes.BlockNode; |
38 | 38 | | import jadx.core.dex.nodes.ClassNode; |
39 | 39 | | import jadx.core.dex.nodes.FieldNode; |
| 40 | + | import jadx.core.dex.nodes.IContainer; |
40 | 41 | | import jadx.core.dex.nodes.InsnNode; |
41 | 42 | | import jadx.core.dex.nodes.MethodNode; |
42 | 43 | | import jadx.core.dex.nodes.RootNode; |
| skipped 5 lines |
48 | 49 | | import jadx.core.utils.BlockUtils; |
49 | 50 | | import jadx.core.utils.InsnRemover; |
50 | 51 | | import jadx.core.utils.InsnUtils; |
51 | | - | import jadx.core.utils.ListUtils; |
52 | 52 | | import jadx.core.utils.Utils; |
53 | 53 | | import jadx.core.utils.exceptions.JadxException; |
54 | 54 | | import jadx.core.utils.exceptions.JadxRuntimeException; |
| skipped 39 lines |
94 | 94 | | |
95 | 95 | | @Override |
96 | 96 | | public boolean visit(ClassNode cls) throws JadxException { |
97 | | - | boolean converted; |
98 | | - | try { |
99 | | - | converted = convertToEnum(cls); |
100 | | - | } catch (Exception e) { |
101 | | - | cls.addWarnComment("Enum visitor error", e); |
102 | | - | converted = false; |
103 | | - | } |
104 | | - | if (!converted) { |
105 | | - | AccessInfo accessFlags = cls.getAccessFlags(); |
106 | | - | if (accessFlags.isEnum()) { |
107 | | - | cls.setAccessFlags(accessFlags.remove(AccessFlags.ENUM)); |
108 | | - | cls.addWarnComment("Failed to restore enum class, 'enum' modifier and super class removed"); |
| 97 | + | if (cls.isEnum()) { |
| 98 | + | boolean converted; |
| 99 | + | try { |
| 100 | + | converted = convertToEnum(cls); |
| 101 | + | } catch (Exception e) { |
| 102 | + | cls.addWarnComment("Enum visitor error", e); |
| 103 | + | converted = false; |
| 104 | + | } |
| 105 | + | if (!converted) { |
| 106 | + | AccessInfo accessFlags = cls.getAccessFlags(); |
| 107 | + | if (accessFlags.isEnum()) { |
| 108 | + | cls.setAccessFlags(accessFlags.remove(AccessFlags.ENUM)); |
| 109 | + | cls.addWarnComment("Failed to restore enum class, 'enum' modifier and super class removed"); |
| 110 | + | } |
109 | 111 | | } |
110 | 112 | | } |
111 | 113 | | return true; |
112 | 114 | | } |
113 | 115 | | |
114 | 116 | | private boolean convertToEnum(ClassNode cls) { |
115 | | - | if (!cls.isEnum()) { |
116 | | - | return false; |
117 | | - | } |
118 | 117 | | ArgType superType = cls.getSuperClass(); |
119 | 118 | | if (superType != null && superType.getObject().equals(ArgType.ENUM.getObject())) { |
120 | 119 | | cls.add(AFlag.REMOVE_SUPER_CLASS); |
| skipped 7 lines |
128 | 127 | | if (staticRegion == null || classInitMth.getBasicBlocks().isEmpty()) { |
129 | 128 | | return false; |
130 | 129 | | } |
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); |
136 | | - | ArgType clsType = cls.getClassInfo().getType(); |
137 | | - | |
138 | | - | // search "$VALUES" field (holds all enum values) |
139 | | - | List<FieldNode> valuesCandidates = cls.getFields().stream() |
140 | | - | .filter(f -> f.getAccessFlags().isStatic()) |
141 | | - | .filter(f -> f.getType().isArray()) |
142 | | - | .filter(f -> Objects.equals(f.getType().getArrayRootElement(), clsType)) |
143 | | - | .collect(Collectors.toList()); |
144 | | - | |
145 | | - | if (valuesCandidates.isEmpty()) { |
146 | | - | return false; |
147 | | - | } |
148 | | - | if (valuesCandidates.size() > 1) { |
149 | | - | valuesCandidates.removeIf(f -> !f.getAccessFlags().isSynthetic()); |
150 | | - | } |
151 | | - | if (valuesCandidates.size() > 1) { |
152 | | - | Optional<FieldNode> valuesOpt = valuesCandidates.stream().filter(f -> f.getName().equals("$VALUES")).findAny(); |
153 | | - | if (valuesOpt.isPresent()) { |
154 | | - | valuesCandidates.clear(); |
155 | | - | valuesCandidates.add(valuesOpt.get()); |
| 130 | + | // collect blocks on linear part of static method (ignore branching on method end) |
| 131 | + | List<BlockNode> staticBlocks = new ArrayList<>(); |
| 132 | + | for (IContainer subBlock : staticRegion.getSubBlocks()) { |
| 133 | + | if (subBlock instanceof BlockNode) { |
| 134 | + | staticBlocks.add((BlockNode) subBlock); |
| 135 | + | } else { |
| 136 | + | break; |
156 | 137 | | } |
157 | 138 | | } |
158 | | - | if (valuesCandidates.size() != 1) { |
159 | | - | cls.addWarnComment("Found several \"values\" enum fields: " + valuesCandidates); |
| 139 | + | if (staticBlocks.isEmpty()) { |
| 140 | + | cls.addWarnComment("Unexpected branching in enum static init block"); |
160 | 141 | | return false; |
161 | 142 | | } |
162 | | - | FieldNode valuesField = valuesCandidates.get(0); |
163 | | - | |
164 | | - | // search "$VALUES" array init and collect enum fields |
165 | | - | BlockInsnPair valuesInitPair = getValuesInitInsn(classInitMth, valuesField); |
166 | | - | if (valuesInitPair == null) { |
| 143 | + | EnumData data = new EnumData(cls, classInitMth, staticBlocks); |
| 144 | + | if (!searchValuesField(data)) { |
167 | 145 | | return false; |
168 | 146 | | } |
169 | | - | BlockNode staticBlock = valuesInitPair.getBlock(); |
170 | | - | InsnNode valuesInitInsn = valuesInitPair.getInsn(); |
171 | | - | |
172 | | - | EnumData enumData = new EnumData(cls, valuesField, staticBlocks); |
173 | | - | |
174 | 147 | | List<EnumField> enumFields = null; |
175 | | - | InsnArg arrArg = valuesInitInsn.getArg(0); |
| 148 | + | InsnArg arrArg = data.valuesInitInsn.getArg(0); |
176 | 149 | | if (arrArg.isInsnWrap()) { |
177 | 150 | | InsnNode wrappedInsn = ((InsnWrapArg) arrArg).getWrapInsn(); |
178 | | - | enumFields = extractEnumFieldsFromInsn(enumData, wrappedInsn); |
| 151 | + | enumFields = extractEnumFieldsFromInsn(data, wrappedInsn); |
179 | 152 | | } |
180 | 153 | | if (enumFields == null) { |
181 | 154 | | cls.addWarnComment("Unknown enum class pattern. Please report as an issue!"); |
182 | 155 | | return false; |
183 | 156 | | } |
184 | | - | enumData.toRemove.add(valuesInitInsn); |
| 157 | + | data.toRemove.add(data.valuesInitInsn); |
185 | 158 | | |
186 | 159 | | // all checks complete, perform transform |
187 | 160 | | EnumClassAttr attr = new EnumClassAttr(enumFields); |
| skipped 13 lines |
201 | 174 | | fieldNode.getFieldInfo().setAlias(name); |
202 | 175 | | } |
203 | 176 | | fieldNode.add(AFlag.DONT_GENERATE); |
204 | | - | processConstructorInsn(enumData, enumField, classInitMth); |
| 177 | + | processConstructorInsn(data, enumField, classInitMth); |
205 | 178 | | } |
206 | | - | valuesField.add(AFlag.DONT_GENERATE); |
207 | | - | InsnRemover.removeAllAndUnbind(classInitMth, enumData.toRemove); |
| 179 | + | data.valuesField.add(AFlag.DONT_GENERATE); |
| 180 | + | InsnRemover.removeAllAndUnbind(classInitMth, data.toRemove); |
208 | 181 | | if (classInitMth.countInsns() == 0) { |
209 | 182 | | classInitMth.add(AFlag.DONT_GENERATE); |
210 | | - | } else if (!enumData.toRemove.isEmpty()) { |
| 183 | + | } else if (!data.toRemove.isEmpty()) { |
211 | 184 | | CodeShrinkVisitor.shrinkMethod(classInitMth); |
212 | 185 | | } |
213 | | - | removeEnumMethods(cls, clsType, valuesField); |
| 186 | + | removeEnumMethods(cls, data.valuesField); |
| 187 | + | return true; |
| 188 | + | } |
| 189 | + | |
| 190 | + | /** |
| 191 | + | * Search "$VALUES" field (holds all enum values) |
| 192 | + | */ |
| 193 | + | private boolean searchValuesField(EnumData data) { |
| 194 | + | ArgType clsType = data.cls.getClassInfo().getType(); |
| 195 | + | List<FieldNode> valuesCandidates = data.cls.getFields().stream() |
| 196 | + | .filter(f -> f.getAccessFlags().isStatic()) |
| 197 | + | .filter(f -> f.getType().isArray()) |
| 198 | + | .filter(f -> Objects.equals(f.getType().getArrayRootElement(), clsType)) |
| 199 | + | .collect(Collectors.toList()); |
| 200 | + | |
| 201 | + | if (valuesCandidates.isEmpty()) { |
| 202 | + | data.cls.addWarnComment("$VALUES field not found"); |
| 203 | + | return false; |
| 204 | + | } |
| 205 | + | if (valuesCandidates.size() > 1) { |
| 206 | + | valuesCandidates.removeIf(f -> !f.getAccessFlags().isSynthetic()); |
| 207 | + | } |
| 208 | + | if (valuesCandidates.size() > 1) { |
| 209 | + | Optional<FieldNode> valuesOpt = valuesCandidates.stream().filter(f -> f.getName().equals("$VALUES")).findAny(); |
| 210 | + | if (valuesOpt.isPresent()) { |
| 211 | + | valuesCandidates.clear(); |
| 212 | + | valuesCandidates.add(valuesOpt.get()); |
| 213 | + | } |
| 214 | + | } |
| 215 | + | if (valuesCandidates.size() != 1) { |
| 216 | + | data.cls.addWarnComment("Found several \"values\" enum fields: " + valuesCandidates); |
| 217 | + | return false; |
| 218 | + | } |
| 219 | + | data.valuesField = valuesCandidates.get(0); |
| 220 | + | |
| 221 | + | // search "$VALUES" array init and collect enum fields |
| 222 | + | BlockInsnPair valuesInitPair = getValuesInitInsn(data); |
| 223 | + | if (valuesInitPair == null) { |
| 224 | + | return false; |
| 225 | + | } |
| 226 | + | data.valuesInitInsn = valuesInitPair.getInsn(); |
214 | 227 | | return true; |
215 | 228 | | } |
216 | 229 | | |
| skipped 63 lines |
280 | 293 | | return enumFields; |
281 | 294 | | } |
282 | 295 | | |
283 | | - | private BlockInsnPair getValuesInitInsn(MethodNode classInitMth, FieldNode valuesField) { |
284 | | - | FieldInfo searchField = valuesField.getFieldInfo(); |
285 | | - | for (BlockNode blockNode : classInitMth.getBasicBlocks()) { |
| 296 | + | private BlockInsnPair getValuesInitInsn(EnumData data) { |
| 297 | + | FieldInfo searchField = data.valuesField.getFieldInfo(); |
| 298 | + | for (BlockNode blockNode : data.staticBlocks) { |
286 | 299 | | for (InsnNode insn : blockNode.getInstructions()) { |
287 | 300 | | if (insn.getType() == InsnType.SPUT) { |
288 | 301 | | IndexInsnNode indexInsnNode = (IndexInsnNode) insn; |
| skipped 160 lines |
449 | 462 | | return null; |
450 | 463 | | } |
451 | 464 | | |
452 | | - | private void removeEnumMethods(ClassNode cls, ArgType clsType, FieldNode valuesField) { |
| 465 | + | private void removeEnumMethods(ClassNode cls, FieldNode valuesField) { |
| 466 | + | ArgType clsType = cls.getClassInfo().getType(); |
453 | 467 | | String valuesMethodShortId = "values()" + TypeGen.signature(ArgType.array(clsType)); |
454 | 468 | | MethodNode valuesMethod = null; |
455 | 469 | | // remove compiler generated methods |
| skipped 175 lines |
631 | 645 | | |
632 | 646 | | private static class EnumData { |
633 | 647 | | final ClassNode cls; |
634 | | - | final FieldNode valuesField; |
| 648 | + | final MethodNode classInitMth; |
635 | 649 | | final List<BlockNode> staticBlocks; |
636 | 650 | | final List<InsnNode> toRemove = new ArrayList<>(); |
| 651 | + | FieldNode valuesField; |
| 652 | + | InsnNode valuesInitInsn; |
637 | 653 | | |
638 | | - | public EnumData(ClassNode cls, FieldNode valuesField, List<BlockNode> staticBlocks) { |
| 654 | + | public EnumData(ClassNode cls, MethodNode classInitMth, List<BlockNode> staticBlocks) { |
639 | 655 | | this.cls = cls; |
640 | | - | this.valuesField = valuesField; |
| 656 | + | this.classInitMth = classInitMth; |
641 | 657 | | this.staticBlocks = staticBlocks; |
642 | 658 | | } |
643 | 659 | | } |
| skipped 2 lines |