diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/ExtractFieldInit.java b/jadx-core/src/main/java/jadx/core/dex/visitors/ExtractFieldInit.java index 506f5e5ebfa64bfaaf1f81fef4e401cfaf5269e0..2662d506fa13ff39224d25bbc99163be8ca94241 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/ExtractFieldInit.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/ExtractFieldInit.java @@ -2,8 +2,10 @@ package jadx.core.dex.visitors; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; @@ -269,9 +271,70 @@ public class ExtractFieldInit extends AbstractVisitor { return Objects.equals(exclude, Boolean.TRUE); } - private static void fixFieldsOrder(ClassNode cls, List fieldsInit) { + private static void fixFieldsOrder(ClassNode cls, List inits) { + List orderedFields = processFieldsDependencies(cls, inits); + applyFieldsOrder(cls, orderedFields); + } + + private static List processFieldsDependencies(ClassNode cls, List inits) { + List orderedFields = Utils.collectionMap(inits, v -> v.fieldNode); + // collect dependant fields + Map> deps = new HashMap<>(inits.size()); + for (FieldInitInfo initInfo : inits) { + IndexInsnNode insn = initInfo.putInsn; + boolean staticField = insn.getType() == InsnType.SPUT; + InsnType useType = staticField ? InsnType.SGET : InsnType.IGET; + insn.visitInsns(subInsn -> { + if (subInsn.getType() == useType) { + FieldInfo fieldInfo = (FieldInfo) ((IndexInsnNode) subInsn).getIndex(); + if (fieldInfo.getDeclClass().equals(cls.getClassInfo())) { + FieldNode depField = cls.searchField(fieldInfo); + if (depField != null) { + deps.computeIfAbsent(initInfo.fieldNode, k -> new ArrayList<>()) + .add(depField); + } + } + } + }); + } + if (deps.isEmpty()) { + return orderedFields; + } + // build new list with deps fields before usage field + List result = new ArrayList<>(); + for (FieldNode field : orderedFields) { + int idx = result.indexOf(field); + List fieldDeps = deps.get(field); + if (fieldDeps == null) { + if (idx == -1) { + result.add(field); + } + continue; + } + if (idx == -1) { + for (FieldNode depField : fieldDeps) { + if (!result.contains(depField)) { + result.add(depField); + } + } + result.add(field); + continue; + } + for (FieldNode depField : fieldDeps) { + int depIdx = result.indexOf(depField); + if (depIdx == -1) { + result.add(idx, depField); + } else if (depIdx > idx) { + result.remove(depIdx); + result.add(idx, depField); + } + } + } + return result; + } + + private static void applyFieldsOrder(ClassNode cls, List orderedFields) { List clsFields = cls.getFields(); - List orderedFields = Utils.collectionMap(fieldsInit, v -> v.fieldNode); // check if already ordered boolean ordered = Collections.indexOfSubList(clsFields, orderedFields) != -1; if (!ordered) { diff --git a/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java b/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java index 2a6e2765be1e6017f0731f28e832971a24c267c4..519d71f4a455fb5c72de495b7f596cefab6e9289 100644 --- a/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java +++ b/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java @@ -327,40 +327,12 @@ public abstract class IntegrationTest extends TestUtils { String clsName = cls.getClassInfo().getFullName(); try { // run 'check' method from original class - Class origCls; - try { - origCls = Class.forName(clsName); - } catch (ClassNotFoundException e) { - // ignore + if (runSourceAutoCheck(clsName)) { return; } - Method checkMth; - try { - checkMth = origCls.getMethod(CHECK_METHOD_NAME); - } catch (NoSuchMethodException e) { - // ignore - return; - } - if (!checkMth.getReturnType().equals(void.class) - || !Modifier.isPublic(checkMth.getModifiers()) - || Modifier.isStatic(checkMth.getModifiers())) { - fail("Wrong 'check' method"); - return; - } - try { - limitExecTime(() -> checkMth.invoke(origCls.getConstructor().newInstance())); - System.out.println("Source check: PASSED"); - } catch (Throwable e) { - throw new JadxRuntimeException("Source check failed", e); - } // run 'check' method from decompiled class if (compile) { - try { - limitExecTime(() -> invoke(cls, "check")); - System.out.println("Decompiled check: PASSED"); - } catch (Throwable e) { - throw new JadxRuntimeException("Decompiled check failed", e); - } + runDecompiledAutoCheck(cls); } } catch (Exception e) { e.printStackTrace(); @@ -368,6 +340,45 @@ public abstract class IntegrationTest extends TestUtils { } } + private boolean runSourceAutoCheck(String clsName) { + Class origCls; + try { + origCls = Class.forName(clsName); + } catch (ClassNotFoundException e) { + // ignore + return true; + } + Method checkMth; + try { + checkMth = origCls.getMethod(CHECK_METHOD_NAME); + } catch (NoSuchMethodException e) { + // ignore + return true; + } + if (!checkMth.getReturnType().equals(void.class) + || !Modifier.isPublic(checkMth.getModifiers()) + || Modifier.isStatic(checkMth.getModifiers())) { + fail("Wrong 'check' method"); + return true; + } + try { + limitExecTime(() -> checkMth.invoke(origCls.getConstructor().newInstance())); + System.out.println("Source check: PASSED"); + } catch (Throwable e) { + throw new JadxRuntimeException("Source check failed", e); + } + return false; + } + + public void runDecompiledAutoCheck(ClassNode cls) { + try { + limitExecTime(() -> invoke(cls, "check")); + System.out.println("Decompiled check: PASSED"); + } catch (Throwable e) { + throw new JadxRuntimeException("Decompiled check failed", e); + } + } + private T limitExecTime(Callable call) { ExecutorService executor = Executors.newSingleThreadExecutor(); Future future = executor.submit(call); @@ -406,10 +417,6 @@ public abstract class IntegrationTest extends TestUtils { return null; } - void compile(ClassNode cls) { - compile(Collections.singletonList(cls)); - } - void compile(List clsList) { if (!compile) { return; diff --git a/jadx-core/src/test/java/jadx/tests/api/utils/assertj/JadxClassNodeAssertions.java b/jadx-core/src/test/java/jadx/tests/api/utils/assertj/JadxClassNodeAssertions.java index abbe7f5847852cfea03e19a258d7462d8cc673a6..60ad18587183b036c2d74644fe15e130f68c8592 100644 --- a/jadx-core/src/test/java/jadx/tests/api/utils/assertj/JadxClassNodeAssertions.java +++ b/jadx-core/src/test/java/jadx/tests/api/utils/assertj/JadxClassNodeAssertions.java @@ -42,4 +42,14 @@ public class JadxClassNodeAssertions extends AbstractObjectAssert()V + .registers 2 + + new-instance v0, Ljava/lang/StringBuilder; + invoke-direct {v0}, Ljava/lang/StringBuilder;->()V + + sget-object v1, Lothers/TestFieldInitOrder2;->ZPREFIX:Ljava/lang/String; + + invoke-virtual {v0, v1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; + move-result-object v0 + + const-string v1, "VALUE" + + invoke-virtual {v0, v1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; + move-result-object v0 + + invoke-virtual {v0}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String; + move-result-object v0 + + sput-object v0, Lothers/TestFieldInitOrder2;->VALUE:Ljava/lang/String; + return-void +.end method + +.method public constructor ()V + .registers 1 + invoke-direct {p0}, Ljava/lang/Object;->()V + return-void +.end method + + +.method public check()V + .registers 3 + sget-object v0, Lothers/TestFieldInitOrder2;->VALUE:Ljava/lang/String; + invoke-static {v0}, Ljadx/tests/api/utils/assertj/JadxAssertions;->assertThat(Ljava/lang/String;)Ljadx/tests/api/utils/assertj/JadxCodeAssertions; + move-result-object v0 + const-string v1, "SOME_VALUE" + invoke-virtual {v0, v1}, Ljadx/tests/api/utils/assertj/JadxCodeAssertions;->isEqualTo(Ljava/lang/String;)Lorg/assertj/core/api/AbstractStringAssert; + return-void +.end method