fix: restore fields order if init use other fields (#1235)

This commit is contained in:
Skylot
2021-09-15 21:43:41 +01:00
parent 099acfca1d
commit ea0795c8a9
5 changed files with 198 additions and 36 deletions
@@ -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> T limitExecTime(Callable<T> call) {
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<T> 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<ClassNode> clsList) {
if (!compile) {
return;
@@ -42,4 +42,14 @@ public class JadxClassNodeAssertions extends AbstractObjectAssert<JadxClassNodeA
testInstance.runChecks(actual);
return codeAssertions;
}
/**
* Force running auto check on decompiled code.
* Useful for smali tests.
*/
public JadxClassNodeAssertions runDecompiledAutoCheck(IntegrationTest testInstance) {
isNotNull();
testInstance.runDecompiledAutoCheck(actual);
return this;
}
}
@@ -0,0 +1,35 @@
package jadx.tests.integration.others;
import org.junit.jupiter.api.Test;
import jadx.tests.api.SmaliTest;
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
public class TestFieldInitOrder2 extends SmaliTest {
@SuppressWarnings({ "SpellCheckingInspection", "StaticVariableName" })
public static class TestCls {
static String ZPREFIX = "SOME_";
private static final String VALUE = ZPREFIX + "VALUE";
public void check() {
assertThat(VALUE).isEqualTo("SOME_VALUE");
}
}
@Test
public void test() {
assertThat(getClassNode(TestCls.class))
.code()
.containsOne("private static final String VALUE = ZPREFIX + \"VALUE\";");
}
@Test
public void testSmali() {
assertThat(getClassNodeFromSmali())
.runDecompiledAutoCheck(this)
.code()
.containsOne("private static final String VALUE = ZPREFIX + \"VALUE\";");
}
}
@@ -0,0 +1,47 @@
.class public Lothers/TestFieldInitOrder2;
.super Ljava/lang/Object;
.field private static final VALUE:Ljava/lang/String;
.field static final ZPREFIX:Ljava/lang/String; = "SOME_"
# direct methods
.method static constructor <clinit>()V
.registers 2
new-instance v0, Ljava/lang/StringBuilder;
invoke-direct {v0}, Ljava/lang/StringBuilder;-><init>()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 <init>()V
.registers 1
invoke-direct {p0}, Ljava/lang/Object;-><init>()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