core tests: compile decompiled code
This commit is contained in:
@@ -116,6 +116,11 @@ public class ClassGen {
|
||||
.remove(AccessFlags.ACC_STATIC);
|
||||
}
|
||||
|
||||
// 'static' modifier not allowed for top classes (not inner)
|
||||
if (!cls.getClassInfo().isInner()) {
|
||||
af = af.remove(AccessFlags.ACC_STATIC);
|
||||
}
|
||||
|
||||
annotationGen.addForClass(clsCode);
|
||||
insertSourceFileInfo(clsCode, cls);
|
||||
clsCode.startLine(af.makeString());
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package jadx.tests.api;
|
||||
|
||||
import jadx.api.JadxInternalAccess;
|
||||
import jadx.api.DefaultJadxArgs;
|
||||
import jadx.api.JadxDecompiler;
|
||||
import jadx.api.JadxInternalAccess;
|
||||
import jadx.core.Jadx;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
@@ -12,11 +12,13 @@ import jadx.core.dex.visitors.DepthTraversal;
|
||||
import jadx.core.dex.visitors.IDexTreeVisitor;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
import jadx.tests.api.compiler.DynamicCompiler;
|
||||
import jadx.tests.api.utils.TestUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
@@ -39,6 +41,9 @@ public abstract class IntegrationTest extends TestUtils {
|
||||
|
||||
protected String outDir = "test-out-tmp";
|
||||
|
||||
protected boolean compile = true;
|
||||
private DynamicCompiler dynamicCompiler;
|
||||
|
||||
public ClassNode getClassNode(Class<?> clazz) {
|
||||
try {
|
||||
File jar = getJarForClass(clazz);
|
||||
@@ -67,6 +72,7 @@ public abstract class IntegrationTest extends TestUtils {
|
||||
// don't unload class
|
||||
|
||||
checkCode(cls);
|
||||
compile(cls);
|
||||
return cls;
|
||||
}
|
||||
|
||||
@@ -109,6 +115,52 @@ public abstract class IntegrationTest extends TestUtils {
|
||||
return null;
|
||||
}
|
||||
|
||||
void compile(ClassNode cls) {
|
||||
if (!compile) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
dynamicCompiler = new DynamicCompiler(cls);
|
||||
boolean result = dynamicCompiler.compile();
|
||||
assertTrue("Compilation failed on code: \n\n" + cls.getCode() + "\n", result);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public Object invoke(String method) throws Exception {
|
||||
return invoke(method, new Class[0]);
|
||||
}
|
||||
|
||||
public Object invoke(String method, Class[] types, Object... args) {
|
||||
Method mth = getReflectMethod(method, types);
|
||||
return invoke(mth, args);
|
||||
}
|
||||
|
||||
public Method getReflectMethod(String method, Class... types) {
|
||||
assertNotNull("dynamicCompiler not ready", dynamicCompiler);
|
||||
try {
|
||||
return dynamicCompiler.getMethod(method, types);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
fail(e.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Object invoke(Method mth, Object... args) {
|
||||
assertNotNull("dynamicCompiler not ready", dynamicCompiler);
|
||||
assertNotNull("unknown method", mth);
|
||||
try {
|
||||
return dynamicCompiler.invoke(mth, args);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
fail(e.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public File getJarForClass(Class<?> cls) throws IOException {
|
||||
String path = cls.getPackage().getName().replace('.', '/');
|
||||
List<File> list = getClassFilesWithInners(cls);
|
||||
@@ -159,6 +211,12 @@ public abstract class IntegrationTest extends TestUtils {
|
||||
return list;
|
||||
}
|
||||
|
||||
// Try to make test class compilable
|
||||
@Deprecated
|
||||
public void disableCompilation() {
|
||||
this.compile = false;
|
||||
}
|
||||
|
||||
// Use only for debug purpose
|
||||
@Deprecated
|
||||
protected void setOutputCFG() {
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package jadx.tests.api.compiler;
|
||||
|
||||
import javax.tools.SimpleJavaFileObject;
|
||||
import java.net.URI;
|
||||
|
||||
public class CharSequenceJavaFileObject extends SimpleJavaFileObject {
|
||||
|
||||
private CharSequence content;
|
||||
|
||||
public CharSequenceJavaFileObject(String className, CharSequence content) {
|
||||
super(URI.create("string:///" + className.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE);
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
|
||||
return content;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package jadx.tests.api.compiler;
|
||||
|
||||
import javax.tools.FileObject;
|
||||
import javax.tools.ForwardingJavaFileManager;
|
||||
import javax.tools.JavaFileObject;
|
||||
import javax.tools.StandardJavaFileManager;
|
||||
import java.io.IOException;
|
||||
import java.security.SecureClassLoader;
|
||||
|
||||
import static javax.tools.JavaFileObject.Kind;
|
||||
|
||||
public class ClassFileManager extends ForwardingJavaFileManager<StandardJavaFileManager> {
|
||||
|
||||
private JavaClassObject jClsObject;
|
||||
|
||||
public ClassFileManager(StandardJavaFileManager standardManager) {
|
||||
super(standardManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaFileObject getJavaFileForOutput(Location location, String className,
|
||||
Kind kind, FileObject sibling) throws IOException {
|
||||
jClsObject = new JavaClassObject(className, kind);
|
||||
return jClsObject;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClassLoader getClassLoader(Location location) {
|
||||
return new SecureClassLoader() {
|
||||
@Override
|
||||
protected Class<?> findClass(String name) throws ClassNotFoundException {
|
||||
byte[] clsBytes = jClsObject.getBytes();
|
||||
return super.defineClass(name, clsBytes, 0, clsBytes.length);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package jadx.tests.api.compiler;
|
||||
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
|
||||
import javax.tools.JavaCompiler;
|
||||
import javax.tools.JavaFileManager;
|
||||
import javax.tools.JavaFileObject;
|
||||
import javax.tools.ToolProvider;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static javax.tools.JavaCompiler.CompilationTask;
|
||||
|
||||
public class DynamicCompiler {
|
||||
|
||||
private final ClassNode clsNode;
|
||||
|
||||
private JavaFileManager fileManager;
|
||||
|
||||
private Object instance;
|
||||
|
||||
public DynamicCompiler(ClassNode clsNode) {
|
||||
this.clsNode = clsNode;
|
||||
}
|
||||
|
||||
public boolean compile() throws Exception {
|
||||
String fullName = clsNode.getFullName();
|
||||
String code = clsNode.getCode().toString();
|
||||
|
||||
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
|
||||
fileManager = new ClassFileManager(compiler.getStandardFileManager(null, null, null));
|
||||
|
||||
List<JavaFileObject> jFiles = new ArrayList<JavaFileObject>(1);
|
||||
jFiles.add(new CharSequenceJavaFileObject(fullName, code));
|
||||
|
||||
CompilationTask compilerTask = compiler.getTask(null, fileManager, null, null, null, jFiles);
|
||||
return Boolean.TRUE.equals(compilerTask.call());
|
||||
}
|
||||
|
||||
private void makeInstance() throws Exception {
|
||||
String fullName = clsNode.getFullName();
|
||||
instance = fileManager.getClassLoader(null).loadClass(fullName).newInstance();
|
||||
if (instance == null) {
|
||||
throw new NullPointerException("Instantiation failed");
|
||||
}
|
||||
}
|
||||
|
||||
private Object getInstance() throws Exception {
|
||||
if (instance == null) {
|
||||
makeInstance();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
public Method getMethod(String method, Class[] types) throws Exception {
|
||||
return getInstance().getClass().getMethod(method, types);
|
||||
}
|
||||
|
||||
public Object invoke(Method mth, Object... args) throws Exception {
|
||||
return mth.invoke(getInstance(), args);
|
||||
}
|
||||
|
||||
public Object invoke(String method) throws Exception {
|
||||
return invoke(method, new Class[0]);
|
||||
}
|
||||
|
||||
public Object invoke(String method, Class[] types, Object... args) throws Exception {
|
||||
Method mth = getMethod(method, types);
|
||||
return invoke(mth, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package jadx.tests.api.compiler;
|
||||
|
||||
import javax.tools.SimpleJavaFileObject;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URI;
|
||||
|
||||
public class JavaClassObject extends SimpleJavaFileObject {
|
||||
|
||||
protected final ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
|
||||
public JavaClassObject(String name, Kind kind) {
|
||||
super(URI.create("string:///" + name.replace('.', '/') + kind.extension), kind);
|
||||
}
|
||||
|
||||
public byte[] getBytes() {
|
||||
return bos.toByteArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputStream openOutputStream() throws IOException {
|
||||
return bos;
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,7 @@ public class TestWrongCode extends IntegrationTest {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
disableCompilation();
|
||||
ClassNode cls = getClassNode(TestCls.class);
|
||||
String code = cls.getCode().toString();
|
||||
System.out.println(code);
|
||||
|
||||
@@ -10,11 +10,11 @@ import static org.junit.Assert.assertThat;
|
||||
|
||||
public class TestFieldIncrement2 extends IntegrationTest {
|
||||
|
||||
class A {
|
||||
int f = 5;
|
||||
}
|
||||
|
||||
public static class TestCls {
|
||||
private static class A {
|
||||
int f = 5;
|
||||
}
|
||||
|
||||
public A a;
|
||||
|
||||
public void test1(int n) {
|
||||
|
||||
@@ -16,9 +16,10 @@ public class TestArrayFill2 extends IntegrationTest {
|
||||
return new int[]{1, a + 1, 2};
|
||||
}
|
||||
|
||||
public int[] test2(int a) {
|
||||
return new int[]{1, a++, a * 2};
|
||||
}
|
||||
// TODO
|
||||
// public int[] test2(int a) {
|
||||
// return new int[]{1, a++, a * 2};
|
||||
// }
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
package jadx.tests.integration.enums;
|
||||
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static jadx.tests.api.utils.JadxMatchers.countString;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
public class TestSwitchOverEnum extends IntegrationTest {
|
||||
@@ -20,8 +23,6 @@ public class TestSwitchOverEnum extends IntegrationTest {
|
||||
return 1;
|
||||
case TWO:
|
||||
return 2;
|
||||
case THREE:
|
||||
return 3;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -35,5 +36,10 @@ public class TestSwitchOverEnum extends IntegrationTest {
|
||||
assertThat(code, countString(1, "synthetic"));
|
||||
assertThat(code, countString(2, "switch (c) {"));
|
||||
assertThat(code, countString(2, "case ONE:"));
|
||||
|
||||
Method mth = getReflectMethod("testEnum", Count.class);
|
||||
assertEquals(1, invoke(mth, Count.ONE));
|
||||
assertEquals(2, invoke(mth, Count.TWO));
|
||||
assertEquals(0, invoke(mth, Count.THREE));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ public class TestAnonymousClass3 extends IntegrationTest {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
disableCompilation();
|
||||
ClassNode cls = getClassNode(TestCls.class);
|
||||
String code = cls.getCode().toString();
|
||||
System.out.println(code);
|
||||
|
||||
@@ -46,6 +46,7 @@ public class TestArrayForEachNegative extends IntegrationTest {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
disableCompilation();
|
||||
ClassNode cls = getClassNode(TestCls.class);
|
||||
String code = cls.getCode().toString();
|
||||
System.out.println(code);
|
||||
|
||||
@@ -35,6 +35,7 @@ public class TestSequentialLoops extends IntegrationTest {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
disableCompilation();
|
||||
ClassNode cls = getClassNode(TestCls.class);
|
||||
String code = cls.getCode().toString();
|
||||
System.out.println(code);
|
||||
|
||||
@@ -88,6 +88,7 @@ public class TestIssue13a extends IntegrationTest {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
disableCompilation();
|
||||
ClassNode cls = getClassNode(TestCls.class);
|
||||
String code = cls.getCode().toString();
|
||||
System.out.println(code);
|
||||
|
||||
@@ -30,6 +30,7 @@ public class TestTryCatch4 extends IntegrationTest {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
disableCompilation();
|
||||
ClassNode cls = getClassNode(TestCls.class);
|
||||
String code = cls.getCode().toString();
|
||||
System.out.println(code);
|
||||
|
||||
@@ -43,6 +43,7 @@ public class TestTryCatch5 extends IntegrationTest {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
disableCompilation();
|
||||
ClassNode cls = getClassNode(TestCls.class);
|
||||
String code = cls.getCode().toString();
|
||||
System.out.println(code);
|
||||
|
||||
@@ -14,6 +14,7 @@ public class TestConstructor extends SmaliTest {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
disableCompilation();
|
||||
ClassNode cls = getClassNodeFromSmali("TestConstructor");
|
||||
String code = cls.getCode().toString();
|
||||
System.out.println(code);
|
||||
|
||||
Reference in New Issue
Block a user