feat(plugins): allow to load classes using input stream or byte array in jadx-input plugin (#1457)
This commit is contained in:
+10
-2
@@ -19,8 +19,8 @@ import org.slf4j.LoggerFactory;
|
||||
import jadx.api.plugins.utils.CommonFileUtils;
|
||||
import jadx.api.plugins.utils.ZipSecurity;
|
||||
|
||||
public class JavaFileLoader {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JavaFileLoader.class);
|
||||
public class JavaInputLoader {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JavaInputLoader.class);
|
||||
|
||||
private static final int MAX_MAGIC_SIZE = 4;
|
||||
private static final byte[] JAVA_CLASS_FILE_MAGIC = { (byte) 0xCA, (byte) 0xFE, (byte) 0xBA, (byte) 0xBE };
|
||||
@@ -37,6 +37,14 @@ public class JavaFileLoader {
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<JavaClassReader> loadInputStream(InputStream in, String name) throws IOException {
|
||||
return loadReader(in, name, null, null);
|
||||
}
|
||||
|
||||
public JavaClassReader loadClass(byte[] content, String fileName) {
|
||||
return new JavaClassReader(getNextUniqId(), fileName, content);
|
||||
}
|
||||
|
||||
private List<JavaClassReader> loadFromFile(File file) {
|
||||
try (InputStream inputStream = new BufferedInputStream(new FileInputStream(file))) {
|
||||
return loadReader(inputStream, file.getName(), file, null);
|
||||
+46
-1
@@ -1,8 +1,11 @@
|
||||
package jadx.plugins.input.java;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -10,6 +13,7 @@ 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.utils.JavaClassParseException;
|
||||
|
||||
public class JavaInputPlugin implements JadxInputPlugin {
|
||||
|
||||
@@ -29,10 +33,51 @@ public class JavaInputPlugin implements JadxInputPlugin {
|
||||
}
|
||||
|
||||
public static ILoadResult loadClassFiles(List<Path> inputFiles, @Nullable Closeable closeable) {
|
||||
List<JavaClassReader> readers = new JavaFileLoader().collectFiles(inputFiles);
|
||||
List<JavaClassReader> readers = new JavaInputLoader().collectFiles(inputFiles);
|
||||
if (readers.isEmpty()) {
|
||||
return EmptyLoadResult.INSTANCE;
|
||||
}
|
||||
return new JavaLoadResult(readers, closeable);
|
||||
}
|
||||
|
||||
public static ILoadResult loadClassFiles(List<Path> inputFiles) {
|
||||
return loadClassFiles(inputFiles, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for provide several inputs by using load methods from {@link JavaInputLoader} class.
|
||||
*/
|
||||
public static ILoadResult load(Function<JavaInputLoader, List<JavaClassReader>> loader) {
|
||||
return wrapClassReaders(loader.apply(new JavaInputLoader()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenient method for load class file or jar from input stream.
|
||||
* Should be used only once per JadxDecompiler instance.
|
||||
* For load several times use {@link JavaInputPlugin#load(Function)} method.
|
||||
*/
|
||||
public static ILoadResult loadFromInputStream(InputStream in, String fileName) {
|
||||
try {
|
||||
return wrapClassReaders(new JavaInputLoader().loadInputStream(in, fileName));
|
||||
} catch (Exception e) {
|
||||
throw new JavaClassParseException("Failed to read input stream", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenient method for load single class file by content.
|
||||
* Should be used only once per JadxDecompiler instance.
|
||||
* For load several times use {@link JavaInputPlugin#load(Function)} method.
|
||||
*/
|
||||
public static ILoadResult loadSingleClass(byte[] content, String fileName) {
|
||||
JavaClassReader reader = new JavaInputLoader().loadClass(content, fileName);
|
||||
return new JavaLoadResult(Collections.singletonList(reader));
|
||||
}
|
||||
|
||||
public static ILoadResult wrapClassReaders(List<JavaClassReader> readers) {
|
||||
if (readers.isEmpty()) {
|
||||
return EmptyLoadResult.INSTANCE;
|
||||
}
|
||||
return new JavaLoadResult(readers);
|
||||
}
|
||||
}
|
||||
|
||||
+4
-1
@@ -20,6 +20,10 @@ public class JavaLoadResult implements ILoadResult {
|
||||
@Nullable
|
||||
private final Closeable closeable;
|
||||
|
||||
public JavaLoadResult(List<JavaClassReader> readers) {
|
||||
this(readers, null);
|
||||
}
|
||||
|
||||
public JavaLoadResult(List<JavaClassReader> readers, @Nullable Closeable closeable) {
|
||||
this.readers = readers;
|
||||
this.closeable = closeable;
|
||||
@@ -47,7 +51,6 @@ public class JavaLoadResult implements ILoadResult {
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
readers.clear();
|
||||
if (closeable != null) {
|
||||
closeable.close();
|
||||
}
|
||||
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
package jadx.plugins.input.java;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.api.JadxDecompiler;
|
||||
import jadx.api.plugins.input.data.ILoadResult;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
class CustomLoadTest {
|
||||
|
||||
private JadxDecompiler jadx;
|
||||
|
||||
@BeforeEach
|
||||
void init() {
|
||||
jadx = new JadxDecompiler(new JadxArgs());
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void close() {
|
||||
jadx.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void loadFiles() {
|
||||
List<Path> files = Stream.of("HelloWorld.class", "HelloWorld$HelloInner.class")
|
||||
.map(this::getSample)
|
||||
.collect(Collectors.toList());
|
||||
ILoadResult loadResult = JavaInputPlugin.loadClassFiles(files);
|
||||
loadDecompiler(loadResult);
|
||||
assertThat(jadx.getClassesWithInners())
|
||||
.hasSize(2)
|
||||
.satisfiesOnlyOnce(cls -> assertThat(cls.getName()).isEqualTo("HelloWorld"))
|
||||
.satisfiesOnlyOnce(cls -> assertThat(cls.getName()).isEqualTo("HelloInner"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void loadFromInputStream() throws IOException {
|
||||
String fileName = "HelloWorld$HelloInner.class";
|
||||
try (InputStream in = Files.newInputStream(getSample(fileName))) {
|
||||
ILoadResult loadResult = JavaInputPlugin.loadFromInputStream(in, fileName);
|
||||
loadDecompiler(loadResult);
|
||||
assertThat(jadx.getClassesWithInners())
|
||||
.hasSize(1)
|
||||
.satisfiesOnlyOnce(cls -> assertThat(cls.getName()).isEqualTo("HelloWorld$HelloInner"));
|
||||
|
||||
System.out.println(jadx.getClassesWithInners().get(0).getCode());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void loadSingleClass() throws IOException {
|
||||
String fileName = "HelloWorld.class";
|
||||
byte[] content = Files.readAllBytes(getSample(fileName));
|
||||
ILoadResult loadResult = JavaInputPlugin.loadSingleClass(content, fileName);
|
||||
loadDecompiler(loadResult);
|
||||
assertThat(jadx.getClassesWithInners())
|
||||
.hasSize(1)
|
||||
.satisfiesOnlyOnce(cls -> assertThat(cls.getName()).isEqualTo("HelloWorld"));
|
||||
|
||||
System.out.println(jadx.getClassesWithInners().get(0).getCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void load() {
|
||||
ILoadResult loadResult = JavaInputPlugin.load(loader -> {
|
||||
List<JavaClassReader> inputs = new ArrayList<>(2);
|
||||
try {
|
||||
String hello = "HelloWorld.class";
|
||||
byte[] content = Files.readAllBytes(getSample(hello));
|
||||
inputs.add(loader.loadClass(content, hello));
|
||||
|
||||
String helloInner = "HelloWorld$HelloInner.class";
|
||||
InputStream in = Files.newInputStream(getSample(helloInner));
|
||||
inputs.addAll(loader.loadInputStream(in, helloInner));
|
||||
} catch (Exception e) {
|
||||
fail(e);
|
||||
}
|
||||
return inputs;
|
||||
});
|
||||
loadDecompiler(loadResult);
|
||||
assertThat(jadx.getClassesWithInners())
|
||||
.hasSize(2)
|
||||
.satisfiesOnlyOnce(cls -> assertThat(cls.getName()).isEqualTo("HelloWorld"))
|
||||
.satisfiesOnlyOnce(cls -> {
|
||||
assertThat(cls.getName()).isEqualTo("HelloInner");
|
||||
assertThat(cls.getCode()).isEqualTo(""); // no code for moved inner class
|
||||
});
|
||||
|
||||
assertThat(jadx.getClasses())
|
||||
.hasSize(1)
|
||||
.satisfiesOnlyOnce(cls -> assertThat(cls.getName()).isEqualTo("HelloWorld"))
|
||||
.satisfiesOnlyOnce(cls -> assertThat(cls.getInnerClasses()).hasSize(1)
|
||||
.satisfiesOnlyOnce(inner -> assertThat(inner.getName()).isEqualTo("HelloInner")));
|
||||
|
||||
jadx.getClassesWithInners().forEach(cls -> System.out.println(cls.getCode()));
|
||||
}
|
||||
|
||||
public void loadDecompiler(ILoadResult load) {
|
||||
try {
|
||||
jadx.addCustomLoad(load);
|
||||
jadx.load();
|
||||
} catch (Exception e) {
|
||||
fail(e);
|
||||
}
|
||||
}
|
||||
|
||||
public Path getSample(String name) {
|
||||
try {
|
||||
return Paths.get(ClassLoader.getSystemResource("samples/" + name).toURI());
|
||||
} catch (Exception e) {
|
||||
return fail(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user