diff --git a/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java b/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java index ffa539709..8b63b946e 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java @@ -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()); 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 6fe5a3641..1ad767197 100644 --- a/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java +++ b/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java @@ -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 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() { diff --git a/jadx-core/src/test/java/jadx/tests/api/compiler/CharSequenceJavaFileObject.java b/jadx-core/src/test/java/jadx/tests/api/compiler/CharSequenceJavaFileObject.java new file mode 100644 index 000000000..d0ed32cd6 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/api/compiler/CharSequenceJavaFileObject.java @@ -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; + } +} diff --git a/jadx-core/src/test/java/jadx/tests/api/compiler/ClassFileManager.java b/jadx-core/src/test/java/jadx/tests/api/compiler/ClassFileManager.java new file mode 100644 index 000000000..676e22594 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/api/compiler/ClassFileManager.java @@ -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 { + + 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); + } + }; + } +} diff --git a/jadx-core/src/test/java/jadx/tests/api/compiler/DynamicCompiler.java b/jadx-core/src/test/java/jadx/tests/api/compiler/DynamicCompiler.java new file mode 100644 index 000000000..8f5999e7b --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/api/compiler/DynamicCompiler.java @@ -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 jFiles = new ArrayList(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); + } +} diff --git a/jadx-core/src/test/java/jadx/tests/api/compiler/JavaClassObject.java b/jadx-core/src/test/java/jadx/tests/api/compiler/JavaClassObject.java new file mode 100644 index 000000000..7d6001901 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/api/compiler/JavaClassObject.java @@ -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; + } +} diff --git a/jadx-core/src/test/java/jadx/tests/integration/TestWrongCode.java b/jadx-core/src/test/java/jadx/tests/integration/TestWrongCode.java index c2c868b3a..0158c0f9d 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/TestWrongCode.java +++ b/jadx-core/src/test/java/jadx/tests/integration/TestWrongCode.java @@ -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); diff --git a/jadx-core/src/test/java/jadx/tests/integration/arith/TestFieldIncrement2.java b/jadx-core/src/test/java/jadx/tests/integration/arith/TestFieldIncrement2.java index 3f12e7718..83216bcaf 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/arith/TestFieldIncrement2.java +++ b/jadx-core/src/test/java/jadx/tests/integration/arith/TestFieldIncrement2.java @@ -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) { diff --git a/jadx-core/src/test/java/jadx/tests/integration/arrays/TestArrayFill2.java b/jadx-core/src/test/java/jadx/tests/integration/arrays/TestArrayFill2.java index 405e1a2cc..ef2f72132 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/arrays/TestArrayFill2.java +++ b/jadx-core/src/test/java/jadx/tests/integration/arrays/TestArrayFill2.java @@ -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 diff --git a/jadx-core/src/test/java/jadx/tests/integration/enums/TestSwitchOverEnum.java b/jadx-core/src/test/java/jadx/tests/integration/enums/TestSwitchOverEnum.java index 599eae44e..ce8cdd903 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/enums/TestSwitchOverEnum.java +++ b/jadx-core/src/test/java/jadx/tests/integration/enums/TestSwitchOverEnum.java @@ -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)); } } diff --git a/jadx-core/src/test/java/jadx/tests/integration/inner/TestAnonymousClass3.java b/jadx-core/src/test/java/jadx/tests/integration/inner/TestAnonymousClass3.java index 9073256e2..9207c738e 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/inner/TestAnonymousClass3.java +++ b/jadx-core/src/test/java/jadx/tests/integration/inner/TestAnonymousClass3.java @@ -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); diff --git a/jadx-core/src/test/java/jadx/tests/integration/loops/TestArrayForEachNegative.java b/jadx-core/src/test/java/jadx/tests/integration/loops/TestArrayForEachNegative.java index d3589a91a..6169b6ff1 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/loops/TestArrayForEachNegative.java +++ b/jadx-core/src/test/java/jadx/tests/integration/loops/TestArrayForEachNegative.java @@ -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); diff --git a/jadx-core/src/test/java/jadx/tests/integration/loops/TestSequentialLoops.java b/jadx-core/src/test/java/jadx/tests/integration/loops/TestSequentialLoops.java index 4fd4ddaf0..4a4992b08 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/loops/TestSequentialLoops.java +++ b/jadx-core/src/test/java/jadx/tests/integration/loops/TestSequentialLoops.java @@ -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); diff --git a/jadx-core/src/test/java/jadx/tests/integration/others/TestIssue13a.java b/jadx-core/src/test/java/jadx/tests/integration/others/TestIssue13a.java index 82a0416cc..98b43f3d4 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/others/TestIssue13a.java +++ b/jadx-core/src/test/java/jadx/tests/integration/others/TestIssue13a.java @@ -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); diff --git a/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatch4.java b/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatch4.java index 5b5851d08..81c506448 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatch4.java +++ b/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatch4.java @@ -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); diff --git a/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatch5.java b/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatch5.java index 3e60caae7..6c281e067 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatch5.java +++ b/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatch5.java @@ -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); diff --git a/jadx-core/src/test/java/jadx/tests/smali/TestConstructor.java b/jadx-core/src/test/java/jadx/tests/smali/TestConstructor.java index 84ff76c5f..cb8a0e2b7 100644 --- a/jadx-core/src/test/java/jadx/tests/smali/TestConstructor.java +++ b/jadx-core/src/test/java/jadx/tests/smali/TestConstructor.java @@ -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);