Projects STRLCPY jadx Commits 620a177c
🤬
  • fix: restore enum class with custom code in static init (#1699)

  • Loading...
  • Skylot committed 2 years ago
    620a177c
    1 parent 683c2dfb
  • ■ ■ ■ ■ ■ ■
    jadx-core/src/main/java/jadx/core/dex/visitors/EnumVisitor.java
    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
  • ■ ■ ■ ■ ■ ■
    jadx-core/src/test/java/jadx/tests/integration/enums/TestEnumsWithCustomInit.java
     1 +package jadx.tests.integration.enums;
     2 + 
     3 +import java.util.HashMap;
     4 +import java.util.Map;
     5 + 
     6 +import org.junit.jupiter.api.Test;
     7 + 
     8 +import jadx.tests.api.IntegrationTest;
     9 + 
     10 +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
     11 + 
     12 +public class TestEnumsWithCustomInit extends IntegrationTest {
     13 + 
     14 + public enum TestCls {
     15 + ONE("I"),
     16 + TWO("II"),
     17 + THREE("III");
     18 + 
     19 + public static final Map<String, TestCls> MAP = new HashMap<>();
     20 + 
     21 + static {
     22 + for (TestCls value : values()) {
     23 + MAP.put(value.toString(), value);
     24 + }
     25 + }
     26 + 
     27 + private final String str;
     28 + 
     29 + TestCls(String str) {
     30 + this.str = str;
     31 + }
     32 + 
     33 + public String toString() {
     34 + return str;
     35 + }
     36 + }
     37 + 
     38 + @Test
     39 + public void test() {
     40 + assertThat(getClassNode(TestCls.class))
     41 + .code()
     42 + .containsOne("ONE(\"I\"),")
     43 + .doesNotContain("new TestEnumsWithCustomInit$TestCls(");
     44 + }
     45 +}
     46 + 
Please wait...
Page is in error, reload to recover