From 502fd069be9a86172aa4b1b61cd33a609d159459 Mon Sep 17 00:00:00 2001 From: Skylot Date: Fri, 25 Feb 2022 20:00:22 +0000 Subject: [PATCH] test: for source auto check use compiled classes instead runtime --- .../java/jadx/core/deobf/DeobfPresets.java | 5 +- .../java/jadx/tests/api/IntegrationTest.java | 59 +++++--- .../tests/api/compiler/ClassFileManager.java | 62 +++----- .../api/compiler/DynamicClassLoader.java | 54 +++++++ .../tests/api/compiler/DynamicCompiler.java | 93 ------------ .../tests/api/compiler/JavaClassObject.java | 12 +- .../jadx/tests/api/compiler/JavaUtils.java | 8 +- .../tests/api/compiler/StaticCompiler.java | 111 -------------- ...eObject.java => StringJavaFileObject.java} | 6 +- .../jadx/tests/api/compiler/TestCompiler.java | 137 ++++++++++++++++++ .../input/javaconvert/JavaConvertLoader.java | 4 +- 11 files changed, 265 insertions(+), 286 deletions(-) create mode 100644 jadx-core/src/test/java/jadx/tests/api/compiler/DynamicClassLoader.java delete mode 100644 jadx-core/src/test/java/jadx/tests/api/compiler/DynamicCompiler.java delete mode 100644 jadx-core/src/test/java/jadx/tests/api/compiler/StaticCompiler.java rename jadx-core/src/test/java/jadx/tests/api/compiler/{CharSequenceJavaFileObject.java => StringJavaFileObject.java} (65%) create mode 100644 jadx-core/src/test/java/jadx/tests/api/compiler/TestCompiler.java diff --git a/jadx-core/src/main/java/jadx/core/deobf/DeobfPresets.java b/jadx-core/src/main/java/jadx/core/deobf/DeobfPresets.java index 97b18911e..95cf566e8 100644 --- a/jadx-core/src/main/java/jadx/core/deobf/DeobfPresets.java +++ b/jadx-core/src/main/java/jadx/core/deobf/DeobfPresets.java @@ -16,6 +16,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import jadx.api.JadxArgs; +import jadx.api.args.DeobfuscationMapFileMode; import jadx.core.dex.info.ClassInfo; import jadx.core.dex.info.FieldInfo; import jadx.core.dex.info.MethodInfo; @@ -38,7 +39,9 @@ public class DeobfPresets { public static DeobfPresets build(RootNode root) { Path deobfMapPath = getPathDeobfMapPath(root); - LOG.debug("Deobfuscation map file set to: {}", deobfMapPath); + if (root.getArgs().getDeobfuscationMapFileMode() != DeobfuscationMapFileMode.IGNORE) { + LOG.debug("Deobfuscation map file set to: {}", deobfMapPath); + } return new DeobfPresets(deobfMapPath); } 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 4f166b393..bda2c326e 100644 --- a/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java +++ b/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java @@ -1,5 +1,6 @@ package jadx.tests.api; +import java.io.Closeable; import java.io.File; import java.io.IOException; import java.lang.reflect.InvocationTargetException; @@ -34,6 +35,7 @@ import jadx.api.ICodeWriter; import jadx.api.JadxArgs; import jadx.api.JadxDecompiler; import jadx.api.JadxInternalAccess; +import jadx.api.args.DeobfuscationMapFileMode; import jadx.api.data.annotations.InsnCodeOffset; import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AType; @@ -49,9 +51,8 @@ import jadx.core.utils.files.FileUtils; import jadx.core.xmlgen.ResourceStorage; import jadx.core.xmlgen.entry.ResourceEntry; import jadx.tests.api.compiler.CompilerOptions; -import jadx.tests.api.compiler.DynamicCompiler; import jadx.tests.api.compiler.JavaUtils; -import jadx.tests.api.compiler.StaticCompiler; +import jadx.tests.api.compiler.TestCompiler; import jadx.tests.api.utils.TestUtils; import static org.apache.commons.lang3.StringUtils.leftPad; @@ -106,7 +107,8 @@ public abstract class IntegrationTest extends TestUtils { private boolean printDisassemble; private Boolean useJavaInput = null; - private DynamicCompiler dynamicCompiler; + private @Nullable TestCompiler sourceCompiler; + private @Nullable TestCompiler decompiledCompiler; static { // enable debug checks @@ -128,13 +130,21 @@ public abstract class IntegrationTest extends TestUtils { args.setSkipResources(true); args.setFsCaseSensitive(false); // use same value on all systems args.setCommentsLevel(CommentsLevel.DEBUG); + args.setDeobfuscationOn(false); + args.setDeobfuscationMapFileMode(DeobfuscationMapFileMode.IGNORE); } @AfterEach - public void after() { + public void after() throws IOException { FileUtils.clearTempRootDir(); - if (jadxDecompiler != null) { - jadxDecompiler.close(); + close(jadxDecompiler); + close(sourceCompiler); + close(decompiledCompiler); + } + + private void close(Closeable cloaseble) throws IOException { + if (cloaseble != null) { + cloaseble.close(); } } @@ -340,16 +350,20 @@ public abstract class IntegrationTest extends TestUtils { } private boolean runSourceAutoCheck(String clsName) { + if (sourceCompiler == null) { + // no source code (smali case) + return true; + } Class origCls; try { - origCls = Class.forName(clsName); + origCls = sourceCompiler.getClass(clsName); } catch (ClassNotFoundException e) { - // ignore + rethrow("Missing class: " + clsName, e); return true; } Method checkMth; try { - checkMth = origCls.getMethod(CHECK_METHOD_NAME); + checkMth = sourceCompiler.getMethod(origCls, CHECK_METHOD_NAME, new Class[] {}); } catch (NoSuchMethodException e) { // ignore return true; @@ -371,10 +385,10 @@ public abstract class IntegrationTest extends TestUtils { public void runDecompiledAutoCheck(ClassNode cls) { try { - limitExecTime(() -> invoke(cls, "check")); + limitExecTime(() -> invoke(decompiledCompiler, cls.getFullName(), CHECK_METHOD_NAME)); System.out.println("Decompiled check: PASSED"); } catch (Throwable e) { - throw new JadxRuntimeException("Decompiled check failed", e); + rethrow("Decompiled check failed", e); } } @@ -398,8 +412,9 @@ public abstract class IntegrationTest extends TestUtils { if (e instanceof InvocationTargetException) { rethrow(msg, e.getCause()); } else if (e instanceof ExecutionException) { - rethrow(e.getMessage(), e.getCause()); + rethrow(msg, e.getCause()); } else if (e instanceof AssertionError) { + System.err.println(msg); throw (AssertionError) e; } else { throw new RuntimeException(msg, e); @@ -421,8 +436,8 @@ public abstract class IntegrationTest extends TestUtils { return; } try { - dynamicCompiler = new DynamicCompiler(clsList); - boolean result = dynamicCompiler.compile(compilerOptions); + decompiledCompiler = new TestCompiler(compilerOptions); + boolean result = decompiledCompiler.compileNodes(clsList); assertTrue(result, "Compilation failed"); System.out.println("Compilation: PASSED"); } catch (Exception e) { @@ -430,13 +445,9 @@ public abstract class IntegrationTest extends TestUtils { } } - public Object invoke(ClassNode cls, String method) throws Exception { - return invoke(cls, method, new Class[0]); - } - - public Object invoke(ClassNode cls, String methodName, Class[] types, Object... args) throws Exception { - assertNotNull(dynamicCompiler, "dynamicCompiler not ready"); - return dynamicCompiler.invoke(cls, methodName, types, args); + public Object invoke(TestCompiler compiler, String clsFullName, String method) throws Exception { + assertNotNull(compiler, "compiler not ready"); + return compiler.invoke(clsFullName, method, new Class[] {}, new Object[] {}); } private List compileClass(Class cls) throws IOException { @@ -457,8 +468,8 @@ public abstract class IntegrationTest extends TestUtils { List compileFileList = Collections.singletonList(file); Path outTmp = FileUtils.createTempDir("jadx-tmp-classes"); - List files = StaticCompiler.compile(compileFileList, outTmp.toFile(), compilerOptions); - files.forEach(File::deleteOnExit); + sourceCompiler = new TestCompiler(compilerOptions); + List files = sourceCompiler.compileFiles(compileFileList, outTmp); if (saveTestJar) { saveToJar(files, outTmp); } @@ -523,7 +534,7 @@ public abstract class IntegrationTest extends TestUtils { protected void enableDeobfuscation() { args.setDeobfuscationOn(true); - args.setDeobfuscationForceSave(true); + args.setDeobfuscationMapFileMode(DeobfuscationMapFileMode.OVERWRITE); args.setDeobfuscationMinLength(2); args.setDeobfuscationMaxLength(64); } 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 index d3b7040cf..5669b3d49 100644 --- a/jadx-core/src/test/java/jadx/tests/api/compiler/ClassFileManager.java +++ b/jadx-core/src/test/java/jadx/tests/api/compiler/ClassFileManager.java @@ -1,8 +1,9 @@ package jadx.tests.api.compiler; -import java.security.SecureClassLoader; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; +import java.io.Closeable; +import java.io.File; +import java.util.ArrayList; +import java.util.List; import javax.tools.FileObject; import javax.tools.ForwardingJavaFileManager; @@ -11,19 +12,27 @@ import javax.tools.StandardJavaFileManager; import static javax.tools.JavaFileObject.Kind; -public class ClassFileManager extends ForwardingJavaFileManager { +public class ClassFileManager extends ForwardingJavaFileManager implements Closeable { - private DynamicClassLoader classLoader; + private final DynamicClassLoader classLoader; public ClassFileManager(StandardJavaFileManager standardManager) { super(standardManager); classLoader = new DynamicClassLoader(); } + public List getJavaFileObjectsFromFiles(List sourceFiles) { + List list = new ArrayList<>(); + for (JavaFileObject javaFileObject : fileManager.getJavaFileObjectsFromFiles(sourceFiles)) { + list.add(javaFileObject); + } + return list; + } + @Override public JavaFileObject getJavaFileForOutput(Location location, String className, Kind kind, FileObject sibling) { JavaClassObject clsObject = new JavaClassObject(className, kind); - classLoader.getClsMap().put(className, clsObject); + classLoader.add(className, clsObject); return clsObject; } @@ -32,44 +41,7 @@ public class ClassFileManager extends ForwardingJavaFileManager clsMap = new ConcurrentHashMap<>(); - private final Map> clsCache = new ConcurrentHashMap<>(); - - @Override - protected Class findClass(String name) throws ClassNotFoundException { - Class cls = replaceClass(name); - if (cls != null) { - return cls; - } - return super.findClass(name); - } - - public Class loadClass(String name) throws ClassNotFoundException { - Class cls = replaceClass(name); - if (cls != null) { - return cls; - } - return super.loadClass(name); - } - - public Class replaceClass(String name) throws ClassNotFoundException { - Class cacheCls = clsCache.get(name); - if (cacheCls != null) { - return cacheCls; - } - JavaClassObject clsObject = clsMap.get(name); - if (clsObject == null) { - return null; - } - byte[] clsBytes = clsObject.getBytes(); - Class cls = super.defineClass(name, clsBytes, 0, clsBytes.length); - clsCache.put(name, cls); - return cls; - } - - public Map getClsMap() { - return clsMap; - } + public DynamicClassLoader getClassLoader() { + return classLoader; } } diff --git a/jadx-core/src/test/java/jadx/tests/api/compiler/DynamicClassLoader.java b/jadx-core/src/test/java/jadx/tests/api/compiler/DynamicClassLoader.java new file mode 100644 index 000000000..8b6984893 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/api/compiler/DynamicClassLoader.java @@ -0,0 +1,54 @@ +package jadx.tests.api.compiler; + +import java.security.SecureClassLoader; +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.jetbrains.annotations.Nullable; + +public class DynamicClassLoader extends SecureClassLoader { + private final Map clsMap = new ConcurrentHashMap<>(); + private final Map> clsCache = new ConcurrentHashMap<>(); + + public void add(String className, JavaClassObject clsObject) { + this.clsMap.put(className, clsObject); + } + + @Override + public Class findClass(String name) throws ClassNotFoundException { + Class cls = replaceClass(name); + if (cls != null) { + return cls; + } + return super.findClass(name); + } + + public Class loadClass(String name) throws ClassNotFoundException { + Class cls = replaceClass(name); + if (cls != null) { + return cls; + } + return super.loadClass(name); + } + + @Nullable + public Class replaceClass(String name) { + Class cacheCls = clsCache.get(name); + if (cacheCls != null) { + return cacheCls; + } + JavaClassObject clsObject = clsMap.get(name); + if (clsObject == null) { + return null; + } + byte[] clsBytes = clsObject.getBytes(); + Class cls = super.defineClass(name, clsBytes, 0, clsBytes.length); + clsCache.put(name, cls); + return cls; + } + + public Collection getClassObjects() { + return clsMap.values(); + } +} 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 deleted file mode 100644 index 2c8b1efc6..000000000 --- a/jadx-core/src/test/java/jadx/tests/api/compiler/DynamicCompiler.java +++ /dev/null @@ -1,93 +0,0 @@ -package jadx.tests.api.compiler; - -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import javax.tools.JavaCompiler; -import javax.tools.JavaFileManager; -import javax.tools.JavaFileObject; -import javax.tools.ToolProvider; - -import org.jetbrains.annotations.NotNull; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import jadx.core.dex.nodes.ClassNode; -import jadx.tests.api.IntegrationTest; - -import static javax.tools.JavaCompiler.CompilationTask; -import static org.junit.jupiter.api.Assertions.assertNotNull; - -public class DynamicCompiler { - - private static final Logger LOG = LoggerFactory.getLogger(DynamicCompiler.class); - - private final List clsNodeList; - private JavaFileManager fileManager; - - public DynamicCompiler(List clsNodeList) { - this.clsNodeList = clsNodeList; - } - - public boolean compile(CompilerOptions compilerOptions) { - JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); - if (compiler == null) { - LOG.error("Can not find compiler, please use JDK instead"); - return false; - } - fileManager = new ClassFileManager(compiler.getStandardFileManager(null, null, null)); - - List jFiles = new ArrayList<>(clsNodeList.size()); - for (ClassNode clsNode : clsNodeList) { - jFiles.add(new CharSequenceJavaFileObject(clsNode.getFullName(), clsNode.getCode().toString())); - } - - CompilationTask compilerTask = compiler.getTask(null, fileManager, null, compilerOptions.getArguments(), null, jFiles); - return Boolean.TRUE.equals(compilerTask.call()); - } - - private ClassLoader getClassLoader() { - return fileManager.getClassLoader(null); - } - - public Object makeInstance(ClassNode cls) throws Exception { - String fullName = cls.getFullName(); - return getClassLoader().loadClass(fullName).getConstructor().newInstance(); - } - - @NotNull - public Method getMethod(Object inst, String methodName, Class[] types) throws Exception { - for (Class type : types) { - checkType(type); - } - return inst.getClass().getMethod(methodName, types); - } - - public Object invoke(ClassNode cls, String methodName, Class[] types, Object[] args) { - try { - Object inst = makeInstance(cls); - Method reflMth = getMethod(inst, methodName, types); - assertNotNull(reflMth, "Failed to get method " + methodName + '(' + Arrays.toString(types) + ')'); - return reflMth.invoke(inst, args); - } catch (Throwable e) { - IntegrationTest.rethrow("Invoke error", e); - return null; - } - } - - private Class checkType(Class type) throws ClassNotFoundException { - if (type.isPrimitive()) { - return type; - } - if (type.isArray()) { - return checkType(type.getComponentType()); - } - Class decompiledCls = getClassLoader().loadClass(type.getName()); - if (type != decompiledCls) { - throw new IllegalArgumentException("Internal test class cannot be used in method invoke"); - } - return decompiledCls; - } -} 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 index 4c38409e9..db04c0c36 100644 --- a/jadx-core/src/test/java/jadx/tests/api/compiler/JavaClassObject.java +++ b/jadx-core/src/test/java/jadx/tests/api/compiler/JavaClassObject.java @@ -1,7 +1,6 @@ package jadx.tests.api.compiler; import java.io.ByteArrayOutputStream; -import java.io.IOException; import java.io.OutputStream; import java.net.URI; @@ -9,10 +8,17 @@ import javax.tools.SimpleJavaFileObject; public class JavaClassObject extends SimpleJavaFileObject { - protected final ByteArrayOutputStream bos = new ByteArrayOutputStream(); + private final String name; + private final ByteArrayOutputStream bos = new ByteArrayOutputStream(); public JavaClassObject(String name, Kind kind) { super(URI.create("string:///" + name.replace('.', '/') + kind.extension), kind); + this.name = name; + } + + @Override + public String getName() { + return name; } public byte[] getBytes() { @@ -20,7 +26,7 @@ public class JavaClassObject extends SimpleJavaFileObject { } @Override - public OutputStream openOutputStream() throws IOException { + public OutputStream openOutputStream() { return bos; } } diff --git a/jadx-core/src/test/java/jadx/tests/api/compiler/JavaUtils.java b/jadx-core/src/test/java/jadx/tests/api/compiler/JavaUtils.java index ed392cfe3..0eeb699d0 100644 --- a/jadx-core/src/test/java/jadx/tests/api/compiler/JavaUtils.java +++ b/jadx-core/src/test/java/jadx/tests/api/compiler/JavaUtils.java @@ -10,6 +10,10 @@ public class JavaUtils { public static final int JAVA_VERSION_INT = getJavaVersionInt(); + public static boolean checkJavaVersion(int requiredVersion) { + return JAVA_VERSION_INT >= requiredVersion; + } + private static int getJavaVersionInt() { String javaSpecVerStr = SystemUtils.JAVA_SPECIFICATION_VERSION; if (javaSpecVerStr == null) { @@ -21,8 +25,4 @@ public class JavaUtils { } return Integer.parseInt(javaSpecVerStr); } - - public static boolean checkJavaVersion(int requiredVersion) { - return JAVA_VERSION_INT >= requiredVersion; - } } diff --git a/jadx-core/src/test/java/jadx/tests/api/compiler/StaticCompiler.java b/jadx-core/src/test/java/jadx/tests/api/compiler/StaticCompiler.java deleted file mode 100644 index 16d20038d..000000000 --- a/jadx-core/src/test/java/jadx/tests/api/compiler/StaticCompiler.java +++ /dev/null @@ -1,111 +0,0 @@ -package jadx.tests.api.compiler; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import javax.tools.Diagnostic; -import javax.tools.DiagnosticListener; -import javax.tools.FileObject; -import javax.tools.ForwardingJavaFileManager; -import javax.tools.JavaCompiler; -import javax.tools.JavaCompiler.CompilationTask; -import javax.tools.JavaFileObject; -import javax.tools.SimpleJavaFileObject; -import javax.tools.StandardJavaFileManager; -import javax.tools.ToolProvider; - -import org.eclipse.jdt.internal.compiler.tool.EclipseCompiler; - -import jadx.core.utils.files.FileUtils; - -public class StaticCompiler { - - public static List compile(List files, File outDir, CompilerOptions options) throws IOException { - int javaVersion = options.getJavaVersion(); - if (!JavaUtils.checkJavaVersion(javaVersion)) { - throw new IllegalArgumentException("Current java version not meet requirement: " - + "current: " + JavaUtils.JAVA_VERSION_INT + ", required: " + javaVersion); - } - - JavaCompiler compiler; - if (options.isUseEclipseCompiler()) { - compiler = new EclipseCompiler(); - } else { - compiler = ToolProvider.getSystemJavaCompiler(); - if (compiler == null) { - throw new IllegalStateException("Can not find compiler, please use JDK instead"); - } - } - StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null); - Iterable compilationUnits = fileManager.getJavaFileObjectsFromFiles(files); - - StaticFileManager staticFileManager = new StaticFileManager(fileManager, outDir); - - List arguments = new ArrayList<>(); - arguments.add(options.isIncludeDebugInfo() ? "-g" : "-g:none"); - String javaVerStr = javaVersion <= 8 ? "1." + javaVersion : Integer.toString(javaVersion); - arguments.add("-source"); - arguments.add(javaVerStr); - arguments.add("-target"); - arguments.add(javaVerStr); - arguments.addAll(options.getArguments()); - - DiagnosticListener diag = new DiagnosticListener() { - @Override - public void report(Diagnostic diagnostic) { - System.out.println(diagnostic); - } - }; - CompilationTask task = compiler.getTask(null, staticFileManager, diag, arguments, null, compilationUnits); - Boolean result = task.call(); - fileManager.close(); - if (Boolean.TRUE.equals(result)) { - return staticFileManager.outputFiles(); - } - return Collections.emptyList(); - } - - private static class StaticFileManager extends ForwardingJavaFileManager { - private final List files = new ArrayList<>(); - private final File outDir; - - protected StaticFileManager(StandardJavaFileManager fileManager, File outDir) { - super(fileManager); - this.outDir = outDir; - } - - @Override - public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind, FileObject sibling) { - if (kind == JavaFileObject.Kind.CLASS) { - File file = new File(outDir, className.replace('.', '/') + ".class"); - files.add(file); - return new ClassFileObject(file, kind); - } - throw new UnsupportedOperationException("Can't save location with kind: " + kind); - } - - public List outputFiles() { - return files; - } - } - - private static class ClassFileObject extends SimpleJavaFileObject { - private final File file; - - protected ClassFileObject(File file, Kind kind) { - super(file.toURI(), kind); - this.file = file; - } - - @Override - public OutputStream openOutputStream() throws IOException { - FileUtils.makeDirsForFile(file); - return new FileOutputStream(file); - } - } -} diff --git a/jadx-core/src/test/java/jadx/tests/api/compiler/CharSequenceJavaFileObject.java b/jadx-core/src/test/java/jadx/tests/api/compiler/StringJavaFileObject.java similarity index 65% rename from jadx-core/src/test/java/jadx/tests/api/compiler/CharSequenceJavaFileObject.java rename to jadx-core/src/test/java/jadx/tests/api/compiler/StringJavaFileObject.java index cf0ad80b8..aa4addb18 100644 --- a/jadx-core/src/test/java/jadx/tests/api/compiler/CharSequenceJavaFileObject.java +++ b/jadx-core/src/test/java/jadx/tests/api/compiler/StringJavaFileObject.java @@ -4,11 +4,11 @@ import java.net.URI; import javax.tools.SimpleJavaFileObject; -public class CharSequenceJavaFileObject extends SimpleJavaFileObject { +public class StringJavaFileObject extends SimpleJavaFileObject { - private CharSequence content; + private final String content; - public CharSequenceJavaFileObject(String className, CharSequence content) { + public StringJavaFileObject(String className, String content) { super(URI.create("string:///" + className.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE); this.content = content; } diff --git a/jadx-core/src/test/java/jadx/tests/api/compiler/TestCompiler.java b/jadx-core/src/test/java/jadx/tests/api/compiler/TestCompiler.java new file mode 100644 index 000000000..0b2547607 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/api/compiler/TestCompiler.java @@ -0,0 +1,137 @@ +package jadx.tests.api.compiler; + +import java.io.Closeable; +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Method; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import javax.tools.JavaCompiler; +import javax.tools.JavaCompiler.CompilationTask; +import javax.tools.JavaFileObject; +import javax.tools.ToolProvider; + +import org.eclipse.jdt.internal.compiler.tool.EclipseCompiler; +import org.jetbrains.annotations.NotNull; + +import jadx.core.dex.nodes.ClassNode; +import jadx.core.utils.files.FileUtils; +import jadx.tests.api.IntegrationTest; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +public class TestCompiler implements Closeable { + private final CompilerOptions options; + private final JavaCompiler compiler; + private final ClassFileManager fileManager; + + public TestCompiler(CompilerOptions options) { + this.options = options; + int javaVersion = options.getJavaVersion(); + if (!JavaUtils.checkJavaVersion(javaVersion)) { + throw new IllegalArgumentException("Current java version not meet requirement: " + + "current: " + JavaUtils.JAVA_VERSION_INT + ", required: " + javaVersion); + } + if (options.isUseEclipseCompiler()) { + compiler = new EclipseCompiler(); + } else { + compiler = ToolProvider.getSystemJavaCompiler(); + if (compiler == null) { + throw new IllegalStateException("Can not find compiler, please use JDK instead"); + } + } + fileManager = new ClassFileManager(compiler.getStandardFileManager(null, null, null)); + } + + public List compileFiles(List sourceFiles, Path outTmp) throws IOException { + List jfObjects = fileManager.getJavaFileObjectsFromFiles(sourceFiles); + boolean success = compile(jfObjects); + if (!success) { + return Collections.emptyList(); + } + List files = new ArrayList<>(); + for (JavaClassObject classObject : fileManager.getClassLoader().getClassObjects()) { + Path path = outTmp.resolve(classObject.getName().replace('.', '/') + ".class"); + FileUtils.makeDirsForFile(path); + Files.write(path, classObject.getBytes()); + files.add(path.toFile()); + } + return files; + } + + public boolean compileNodes(List clsNodeList) { + List jfObjects = new ArrayList<>(clsNodeList.size()); + for (ClassNode clsNode : clsNodeList) { + jfObjects.add(new StringJavaFileObject(clsNode.getFullName(), clsNode.getCode().getCodeStr())); + } + return compile(jfObjects); + } + + private boolean compile(List jfObjects) { + List arguments = new ArrayList<>(); + arguments.add(options.isIncludeDebugInfo() ? "-g" : "-g:none"); + int javaVersion = options.getJavaVersion(); + String javaVerStr = javaVersion <= 8 ? "1." + javaVersion : Integer.toString(javaVersion); + arguments.add("-source"); + arguments.add(javaVerStr); + arguments.add("-target"); + arguments.add(javaVerStr); + arguments.addAll(options.getArguments()); + + CompilationTask compilerTask = compiler.getTask(null, fileManager, null, arguments, null, jfObjects); + return Boolean.TRUE.equals(compilerTask.call()); + } + + private ClassLoader getClassLoader() { + return fileManager.getClassLoader(); + } + + public Class getClass(String clsFullName) throws ClassNotFoundException { + return getClassLoader().loadClass(clsFullName); + } + + @NotNull + public Method getMethod(Class cls, String methodName, Class[] types) throws NoSuchMethodException { + return cls.getMethod(methodName, types); + } + + public Object invoke(String clsFullName, String methodName, Class[] types, Object[] args) { + try { + for (Class type : types) { + checkType(type); + } + Class cls = getClass(clsFullName); + Method mth = getMethod(cls, methodName, types); + Object inst = cls.getConstructor().newInstance(); + assertNotNull(mth, "Failed to get method " + methodName + '(' + Arrays.toString(types) + ')'); + return mth.invoke(inst, args); + } catch (Throwable e) { + IntegrationTest.rethrow("Invoke error", e); + return null; + } + } + + private Class checkType(Class type) throws ClassNotFoundException { + if (type.isPrimitive()) { + return type; + } + if (type.isArray()) { + return checkType(type.getComponentType()); + } + Class cls = getClassLoader().loadClass(type.getName()); + if (type != cls) { + throw new IllegalArgumentException("Internal test class cannot be used in method invoke"); + } + return cls; + } + + @Override + public void close() throws IOException { + fileManager.close(); + } +} diff --git a/jadx-plugins/jadx-java-convert/src/main/java/jadx/plugins/input/javaconvert/JavaConvertLoader.java b/jadx-plugins/jadx-java-convert/src/main/java/jadx/plugins/input/javaconvert/JavaConvertLoader.java index 678a12421..67d439502 100644 --- a/jadx-plugins/jadx-java-convert/src/main/java/jadx/plugins/input/javaconvert/JavaConvertLoader.java +++ b/jadx-plugins/jadx-java-convert/src/main/java/jadx/plugins/input/javaconvert/JavaConvertLoader.java @@ -65,7 +65,7 @@ public class JavaConvertLoader { } } result.addTempPath(jarFile); - LOG.debug("Packed class files {} into jar {}", clsFiles.size(), jarFile); + LOG.debug("Packed {} class files into jar: {}", clsFiles.size(), jarFile); convertJar(result, jarFile); } catch (Exception e) { LOG.error("Error process class files", e); @@ -169,7 +169,7 @@ public class JavaConvertLoader { } } List dexFiles = collectFilesInDir(tempDirectory); - LOG.debug("Converted {} to dex files: {}", path.toAbsolutePath(), dexFiles.size()); + LOG.debug("Converted {} to {} dex", path.toAbsolutePath(), dexFiles.size()); result.addConvertedFiles(dexFiles); }