feat: add raung input plugin, use raung in tests
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
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());
|
||||
}
|
||||
}
|
||||
@@ -1,27 +1,12 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
.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
|
||||
+8
-1
@@ -1,8 +1,11 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
+11
-2
@@ -1,8 +1,11 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
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')
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
jadx.plugins.input.raung.RaungInputPlugin
|
||||
@@ -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'
|
||||
|
||||
Reference in New Issue
Block a user