未验证 提交 502fd069 编写于 作者: S Skylot

test: for source auto check use compiled classes instead runtime

上级 fad9e7b8
......@@ -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);
}
......
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<File> compileClass(Class<?> cls) throws IOException {
......@@ -457,8 +468,8 @@ public abstract class IntegrationTest extends TestUtils {
List<File> compileFileList = Collections.singletonList(file);
Path outTmp = FileUtils.createTempDir("jadx-tmp-classes");
List<File> files = StaticCompiler.compile(compileFileList, outTmp.toFile(), compilerOptions);
files.forEach(File::deleteOnExit);
sourceCompiler = new TestCompiler(compilerOptions);
List<File> 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);
}
......
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<StandardJavaFileManager> {
public class ClassFileManager extends ForwardingJavaFileManager<StandardJavaFileManager> implements Closeable {
private DynamicClassLoader classLoader;
private final DynamicClassLoader classLoader;
public ClassFileManager(StandardJavaFileManager standardManager) {
super(standardManager);
classLoader = new DynamicClassLoader();
}
public List<JavaFileObject> getJavaFileObjectsFromFiles(List<File> sourceFiles) {
List<JavaFileObject> 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<StandardJavaFile
return classLoader;
}
private class DynamicClassLoader extends SecureClassLoader {
private final Map<String, JavaClassObject> clsMap = new ConcurrentHashMap<>();
private final Map<String, Class<?>> 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<String, JavaClassObject> getClsMap() {
return clsMap;
}
public DynamicClassLoader getClassLoader() {
return classLoader;
}
}
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<String, JavaClassObject> clsMap = new ConcurrentHashMap<>();
private final Map<String, Class<?>> 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<? extends JavaClassObject> getClassObjects() {
return clsMap.values();
}
}
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<ClassNode> clsNodeList;
private JavaFileManager fileManager;
public DynamicCompiler(List<ClassNode> 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<JavaFileObject> 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;
}
}
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;
}
}
......@@ -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;
}
}
......@@ -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;
}
......
package jadx.tests.api.compiler;
import java.io.Closeable;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
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.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 org.jetbrains.annotations.NotNull;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.utils.files.FileUtils;
import jadx.tests.api.IntegrationTest;
public class StaticCompiler {
import static org.junit.jupiter.api.Assertions.assertNotNull;
public static List<File> compile(List<File> files, File outDir, CompilerOptions options) throws IOException {
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);
}
JavaCompiler compiler;
if (options.isUseEclipseCompiler()) {
compiler = new EclipseCompiler();
} else {
......@@ -41,13 +45,37 @@ public class StaticCompiler {
throw new IllegalStateException("Can not find compiler, please use JDK instead");
}
}
StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
Iterable<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjectsFromFiles(files);
fileManager = new ClassFileManager(compiler.getStandardFileManager(null, null, null));
}
StaticFileManager staticFileManager = new StaticFileManager(fileManager, outDir);
public List<File> compileFiles(List<File> sourceFiles, Path outTmp) throws IOException {
List<JavaFileObject> jfObjects = fileManager.getJavaFileObjectsFromFiles(sourceFiles);
boolean success = compile(jfObjects);
if (!success) {
return Collections.emptyList();
}
List<File> 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<ClassNode> clsNodeList) {
List<JavaFileObject> 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<JavaFileObject> jfObjects) {
List<String> 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);
......@@ -55,57 +83,55 @@ public class StaticCompiler {
arguments.add(javaVerStr);
arguments.addAll(options.getArguments());
DiagnosticListener<? super JavaFileObject> diag = new DiagnosticListener<JavaFileObject>() {
@Override
public void report(Diagnostic<? extends JavaFileObject> 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();
CompilationTask compilerTask = compiler.getTask(null, fileManager, null, arguments, null, jfObjects);
return Boolean.TRUE.equals(compilerTask.call());
}
private static class StaticFileManager extends ForwardingJavaFileManager<StandardJavaFileManager> {
private final List<File> files = new ArrayList<>();
private final File outDir;
private ClassLoader getClassLoader() {
return fileManager.getClassLoader();
}
protected StaticFileManager(StandardJavaFileManager fileManager, File outDir) {
super(fileManager);
this.outDir = outDir;
}
public Class<?> getClass(String clsFullName) throws ClassNotFoundException {
return getClassLoader().loadClass(clsFullName);
}
@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);
}
@NotNull
public Method getMethod(Class<?> cls, String methodName, Class<?>[] types) throws NoSuchMethodException {
return cls.getMethod(methodName, types);
}
public List<File> outputFiles() {
return files;
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 static class ClassFileObject extends SimpleJavaFileObject {
private final File file;
protected ClassFileObject(File file, Kind kind) {
super(file.toURI(), kind);
this.file = file;
private Class<?> checkType(Class<?> type) throws ClassNotFoundException {
if (type.isPrimitive()) {
return type;
}
@Override
public OutputStream openOutputStream() throws IOException {
FileUtils.makeDirsForFile(file);
return new FileOutputStream(file);
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();
}
}
......@@ -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<Path> 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);
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册