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

feat: add raung input plugin, use raung in tests

上级 f30c14b2
......@@ -14,13 +14,11 @@ dependencies {
testImplementation 'org.apache.commons:commons-lang3:3.12.0'
testImplementation 'org.ow2.asm:asm:9.2'
testImplementation 'org.ow2.asm:asm-util:9.2'
testRuntimeOnly(project(':jadx-plugins:jadx-dex-input'))
testRuntimeOnly(project(':jadx-plugins:jadx-smali-input'))
testRuntimeOnly(project(':jadx-plugins:jadx-java-convert'))
testRuntimeOnly(project(':jadx-plugins:jadx-java-input'))
testRuntimeOnly(project(':jadx-plugins:jadx-raung-input'))
}
test {
......
package jadx.tests.api;
import java.io.File;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.junit.jupiter.api.BeforeEach;
import jadx.api.JadxInternalAccess;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.RootNode;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.notNullValue;
public abstract class RaungTest extends IntegrationTest {
private static final String RAUNG_TESTS_PROJECT = "jadx-core";
private static final String RAUNG_TESTS_DIR = "src/test/raung";
private static final String RAUNG_TESTS_EXT = ".raung";
@BeforeEach
public void init() {
super.init();
this.useJavaInput();
}
/**
* Preferred method for one file raung test
*/
protected ClassNode getClassNodeFromRaung() {
String pkg = getTestPkg();
String clsName = getTestName();
return getClassNodeFromRaung(pkg + File.separatorChar + clsName, pkg + '.' + clsName);
}
protected ClassNode getClassNodeFromRaung(String file, String clsName) {
File raungFile = getRaungFile(file);
return getClassNodeFromFiles(Collections.singletonList(raungFile), clsName);
}
protected List<ClassNode> loadFromRaungFiles() {
jadxDecompiler = loadFiles(collectRaungFiles(getTestPkg(), getTestName()));
RootNode root = JadxInternalAccess.getRoot(jadxDecompiler);
List<ClassNode> classes = root.getClasses(false);
decompileAndCheck(classes);
return classes;
}
private List<File> collectRaungFiles(String pkg, String testDir) {
String raungFilesDir = pkg + File.separatorChar + testDir + File.separatorChar;
File raungDir = getRaungDir(raungFilesDir);
String[] raungFileNames = raungDir.list((dir, name) -> name.endsWith(".raung"));
assertThat("Raung files not found in " + raungDir, raungFileNames, notNullValue());
return Stream.of(raungFileNames)
.map(file -> new File(raungDir, file))
.collect(Collectors.toList());
}
private static File getRaungFile(String baseName) {
File raungFile = new File(RAUNG_TESTS_DIR, baseName + RAUNG_TESTS_EXT);
if (raungFile.exists()) {
return raungFile;
}
File pathFromRoot = new File(RAUNG_TESTS_PROJECT, raungFile.getPath());
if (pathFromRoot.exists()) {
return pathFromRoot;
}
throw new AssertionError("Raung file not found: " + raungFile.getPath());
}
private static File getRaungDir(String baseName) {
File raungDir = new File(RAUNG_TESTS_DIR, baseName);
if (raungDir.exists()) {
return raungDir;
}
File pathFromRoot = new File(RAUNG_TESTS_PROJECT, raungDir.getPath());
if (pathFromRoot.exists()) {
return pathFromRoot;
}
throw new AssertionError("Raung dir not found: " + raungDir.getPath());
}
}
package jadx.tests.integration.others;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.util.CheckClassAdapter;
import jadx.core.utils.files.FileUtils;
import jadx.tests.api.IntegrationTest;
import jadx.tests.api.RaungTest;
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
public class TestJavaSwap extends IntegrationTest {
public class TestJavaSwap extends RaungTest {
@SuppressWarnings("StringBufferReplaceableByString")
public static class TestCls {
......@@ -44,52 +29,9 @@ public class TestJavaSwap extends IntegrationTest {
}
@Test
public void test() throws IOException {
// TODO: find up-to-date assembler/disassembler in java
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
cw.visit(Opcodes.V1_8, 0, "TestCls", null, "java/lang/Object", new String[] {});
cw.visitField(Opcodes.ACC_PRIVATE, "field", "Ljava/lang/Iterable;", null, null).visitEnd();
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "toString", "()Ljava/lang/String;", null, new String[] {});
mv.visitCode();
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitFieldInsn(Opcodes.GETFIELD, "TestCls", "field", "Ljava/lang/Iterable;");
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/String", "valueOf", "(Ljava/lang/Object;)Ljava/lang/String;", false);
mv.visitVarInsn(Opcodes.ASTORE, 1);
mv.visitIntInsn(Opcodes.BIPUSH, 8);
mv.visitVarInsn(Opcodes.ALOAD, 1);
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/String", "valueOf", "(Ljava/lang/Object;)Ljava/lang/String;", false);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "length", "()I", false);
mv.visitInsn(Opcodes.IADD);
mv.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder");
mv.visitInsn(Opcodes.DUP_X1);
mv.visitInsn(Opcodes.SWAP);
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "(I)V", false);
mv.visitLdcInsn("concat(");
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder",
"append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
mv.visitVarInsn(Opcodes.ALOAD, 1);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder",
"append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
mv.visitLdcInsn(")");
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder",
"append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
mv.visitInsn(Opcodes.ARETURN);
mv.visitMaxs(0, 0); // auto calculated
mv.visitEnd();
cw.visitEnd();
byte[] clsBytes = cw.toByteArray();
StringWriter results = new StringWriter();
CheckClassAdapter.verify(new ClassReader(clsBytes), false, new PrintWriter(results));
assertThat(results.toString()).isEmpty();
Path clsFile = FileUtils.createTempFile(".class");
Files.write(clsFile, clsBytes);
List<File> files = Collections.singletonList(clsFile.toFile());
public void test() {
useJavaInput();
assertThat(getClassNodeFromFiles(files, "TestCls"))
assertThat(getClassNodeFromRaung())
.code();
}
}
.version 52
.class others/TestJavaSwap
.auto frames
.field private field Ljava/lang/Iterable;
.method public toString()Ljava/lang/String;
aload 0
getfield others/TestJavaSwap field Ljava/lang/Iterable;
invokestatic java/lang/String valueOf (Ljava/lang/Object;)Ljava/lang/String;
astore 1
bipush 8
aload 1
invokestatic java/lang/String valueOf (Ljava/lang/Object;)Ljava/lang/String;
invokevirtual java/lang/String length ()I
iadd
new java/lang/StringBuilder
dup_x1
swap
invokespecial java/lang/StringBuilder <init> (I)V
ldc "concat("
invokevirtual java/lang/StringBuilder append (Ljava/lang/String;)Ljava/lang/StringBuilder;
aload 1
invokevirtual java/lang/StringBuilder append (Ljava/lang/String;)Ljava/lang/StringBuilder;
ldc ")"
invokevirtual java/lang/StringBuilder append (Ljava/lang/String;)Ljava/lang/StringBuilder;
invokevirtual java/lang/StringBuilder toString ()Ljava/lang/String;
areturn
.end method
package jadx.plugins.input.java;
import java.io.Closeable;
import java.nio.file.Path;
import java.util.List;
import org.jetbrains.annotations.Nullable;
import jadx.api.plugins.JadxPluginInfo;
import jadx.api.plugins.input.JadxInputPlugin;
import jadx.api.plugins.input.data.ILoadResult;
......@@ -22,10 +25,14 @@ public class JavaInputPlugin implements JadxInputPlugin {
@Override
public ILoadResult loadFiles(List<Path> inputFiles) {
return loadClassFiles(inputFiles, null);
}
public static ILoadResult loadClassFiles(List<Path> inputFiles, @Nullable Closeable closeable) {
List<JavaClassReader> readers = new JavaFileLoader().collectFiles(inputFiles);
if (readers.isEmpty()) {
return EmptyLoadResult.INSTANCE;
}
return new JavaLoadResult(readers);
return new JavaLoadResult(readers, closeable);
}
}
package jadx.plugins.input.java;
import java.io.Closeable;
import java.io.IOException;
import java.util.List;
import java.util.function.Consumer;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -14,9 +17,12 @@ public class JavaLoadResult implements ILoadResult {
private static final Logger LOG = LoggerFactory.getLogger(JavaLoadResult.class);
private final List<JavaClassReader> readers;
@Nullable
private final Closeable closeable;
public JavaLoadResult(List<JavaClassReader> readers) {
public JavaLoadResult(List<JavaClassReader> readers, @Nullable Closeable closeable) {
this.readers = readers;
this.closeable = closeable;
}
@Override
......@@ -40,7 +46,10 @@ public class JavaLoadResult implements ILoadResult {
}
@Override
public void close() {
public void close() throws IOException {
readers.clear();
if (closeable != null) {
closeable.close();
}
}
}
plugins {
id 'java-library'
}
dependencies {
api(project(":jadx-plugins:jadx-plugins-api"))
implementation(project(":jadx-plugins:jadx-java-input"))
implementation('io.github.skylot:raung-asm:0.0.1')
}
package jadx.plugins.input.raung;
import java.io.Closeable;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.PathMatcher;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.github.skylot.raung.asm.RaungAsm;
public class RaungConvert implements Closeable {
private static final Logger LOG = LoggerFactory.getLogger(RaungConvert.class);
@Nullable
private Path tmpJar;
public boolean execute(List<Path> input) {
List<Path> raungInputs = filterRaungFiles(input);
if (raungInputs.isEmpty()) {
return false;
}
try {
this.tmpJar = Files.createTempFile("jadx-raung-", ".jar");
RaungAsm.create()
.output(tmpJar)
.inputs(input)
.execute();
return true;
} catch (Exception e) {
LOG.error("Raung process error", e);
}
close();
return false;
}
private List<Path> filterRaungFiles(List<Path> input) {
PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:**.raung");
return input.stream()
.filter(matcher::matches)
.collect(Collectors.toList());
}
public List<Path> getFiles() {
if (tmpJar == null) {
return Collections.emptyList();
}
return Collections.singletonList(tmpJar);
}
@Override
public void close() {
try {
if (tmpJar != null) {
Files.deleteIfExists(tmpJar);
}
} catch (Exception e) {
LOG.error("Failed to remove tmp jar file: {}", tmpJar, e);
}
}
}
package jadx.plugins.input.raung;
import java.nio.file.Path;
import java.util.List;
import jadx.api.plugins.JadxPluginInfo;
import jadx.api.plugins.input.JadxInputPlugin;
import jadx.api.plugins.input.data.ILoadResult;
import jadx.api.plugins.input.data.impl.EmptyLoadResult;
import jadx.plugins.input.java.JavaInputPlugin;
public class RaungInputPlugin implements JadxInputPlugin {
@Override
public JadxPluginInfo getPluginInfo() {
return new JadxPluginInfo(
"raung-input",
"RaungInput",
"Load .raung files");
}
@Override
public ILoadResult loadFiles(List<Path> input) {
RaungConvert convert = new RaungConvert();
if (!convert.execute(input)) {
return EmptyLoadResult.INSTANCE;
}
return JavaInputPlugin.loadClassFiles(convert.getFiles(), convert);
}
}
......@@ -7,5 +7,6 @@ include 'jadx-plugins'
include 'jadx-plugins:jadx-plugins-api'
include 'jadx-plugins:jadx-dex-input'
include 'jadx-plugins:jadx-java-input'
include 'jadx-plugins:jadx-raung-input'
include 'jadx-plugins:jadx-smali-input'
include 'jadx-plugins:jadx-java-convert'
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册