refactor: make input plugin api similar to pass plugins

This commit is contained in:
Skylot
2022-08-22 17:47:58 +01:00
parent 0c4d46ead5
commit a89dbc1152
38 changed files with 370 additions and 379 deletions
@@ -3,7 +3,6 @@ package jadx.plugins.input.dex;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import jadx.api.plugins.options.OptionDescription;
import jadx.api.plugins.options.impl.BaseOptionsParser;
@@ -15,11 +14,12 @@ public class DexInputOptions extends BaseOptionsParser {
private boolean verifyChecksum = true;
public void apply(Map<String, String> options) {
verifyChecksum = getBooleanOption(options, VERIFY_CHECKSUM_OPT, true);
@Override
public void parseOptions() {
verifyChecksum = getBooleanOption(VERIFY_CHECKSUM_OPT, true);
}
public List<OptionDescription> buildOptionsDescriptions() {
public List<OptionDescription> getOptionsDescriptions() {
return Collections.singletonList(
new JadxOptionDescription(
VERIFY_CHECKSUM_OPT,
@@ -5,19 +5,17 @@ import java.io.InputStream;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.jetbrains.annotations.Nullable;
import jadx.api.plugins.JadxPlugin;
import jadx.api.plugins.JadxPluginContext;
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.api.plugins.options.JadxPluginOptions;
import jadx.api.plugins.options.OptionDescription;
import jadx.api.plugins.input.ICodeLoader;
import jadx.api.plugins.input.data.impl.EmptyCodeLoader;
import jadx.api.plugins.utils.CommonFileUtils;
public class DexInputPlugin implements JadxInputPlugin, JadxPluginOptions {
public class DexInputPlugin implements JadxPlugin {
public static final String PLUGIN_ID = "dex-input";
private final DexInputOptions options = new DexInputOptions();
@@ -29,39 +27,34 @@ public class DexInputPlugin implements JadxInputPlugin, JadxPluginOptions {
}
@Override
public ILoadResult loadFiles(List<Path> input) {
public void init(JadxPluginContext context) {
context.registerOptions(options);
context.addCodeInput(this::loadFiles);
}
public ICodeLoader loadFiles(List<Path> input) {
return loadFiles(input, null);
}
public ILoadResult loadFiles(List<Path> inputFiles, @Nullable Closeable closeable) {
public ICodeLoader loadFiles(List<Path> inputFiles, @Nullable Closeable closeable) {
List<DexReader> dexReaders = loader.collectDexFiles(inputFiles);
if (dexReaders.isEmpty()) {
return EmptyLoadResult.INSTANCE;
return EmptyCodeLoader.INSTANCE;
}
return new DexLoadResult(dexReaders, closeable);
}
public ILoadResult loadDex(byte[] content, @Nullable String fileName) {
public ICodeLoader loadDex(byte[] content, @Nullable String fileName) {
String fileLabel = fileName == null ? "input.dex" : fileName;
DexReader dexReader = loader.loadDexReader(fileLabel, content);
return new DexLoadResult(Collections.singletonList(dexReader), null);
}
public ILoadResult loadDexFromInputStream(InputStream in, @Nullable String fileLabel) {
public ICodeLoader loadDexFromInputStream(InputStream in, @Nullable String fileLabel) {
try {
return loadDex(CommonFileUtils.loadBytes(in), fileLabel);
} catch (Exception e) {
throw new DexException("Failed to read input stream", e);
}
}
@Override
public void setOptions(Map<String, String> options) {
this.options.apply(options);
}
@Override
public List<OptionDescription> getOptionsDescriptions() {
return this.options.buildOptionsDescriptions();
}
}
@@ -7,11 +7,10 @@ import java.util.function.Consumer;
import org.jetbrains.annotations.Nullable;
import jadx.api.plugins.input.ICodeLoader;
import jadx.api.plugins.input.data.IClassData;
import jadx.api.plugins.input.data.ILoadResult;
import jadx.api.plugins.input.data.IResourceData;
public class DexLoadResult implements ILoadResult {
public class DexLoadResult implements ICodeLoader {
private final List<DexReader> dexReaders;
@Nullable
private final Closeable closeable;
@@ -28,10 +27,6 @@ public class DexLoadResult implements ILoadResult {
}
}
@Override
public void visitResources(Consumer<IResourceData> consumer) {
}
@Override
public void close() throws IOException {
if (closeable != null) {
@@ -9,10 +9,10 @@ import java.util.concurrent.atomic.AtomicInteger;
import org.junit.jupiter.api.Test;
import jadx.api.plugins.input.ICodeLoader;
import jadx.api.plugins.input.data.AccessFlags;
import jadx.api.plugins.input.data.AccessFlagsScope;
import jadx.api.plugins.input.data.ICodeReader;
import jadx.api.plugins.input.data.ILoadResult;
import jadx.plugins.input.dex.utils.SmaliTestUtils;
import static org.assertj.core.api.Assertions.assertThat;
@@ -38,7 +38,7 @@ class DexInputPluginTest {
System.out.println("Input file: " + sample.toAbsolutePath());
long start = System.currentTimeMillis();
List<Path> files = Collections.singletonList(sample);
try (ILoadResult result = new DexInputPlugin().loadFiles(files)) {
try (ICodeLoader result = new DexInputPlugin().loadFiles(files)) {
AtomicInteger count = new AtomicInteger();
result.visitClasses(cls -> {
System.out.println();
@@ -3,7 +3,6 @@ package jadx.plugins.input.javaconvert;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import jadx.api.plugins.options.OptionDescription;
import jadx.api.plugins.options.impl.BaseOptionsParser;
@@ -21,12 +20,14 @@ public class JavaConvertOptions extends BaseOptionsParser {
private Mode mode = Mode.BOTH;
private boolean d8Desugar = false;
public void apply(Map<String, String> options) {
mode = getOption(options, MODE_OPT, name -> Mode.valueOf(name.toUpperCase(Locale.ROOT)), Mode.BOTH);
d8Desugar = getBooleanOption(options, D8_DESUGAR_OPT, false);
@Override
public void parseOptions() {
mode = getOption(MODE_OPT, name -> Mode.valueOf(name.toUpperCase(Locale.ROOT)), Mode.BOTH);
d8Desugar = getBooleanOption(D8_DESUGAR_OPT, false);
}
public List<OptionDescription> buildOptionsDescriptions() {
@Override
public List<OptionDescription> getOptionsDescriptions() {
return Arrays.asList(
new JadxOptionDescription(
MODE_OPT,
@@ -2,17 +2,16 @@ package jadx.plugins.input.javaconvert;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import jadx.api.plugins.JadxPlugin;
import jadx.api.plugins.JadxPluginContext;
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.api.plugins.options.JadxPluginOptions;
import jadx.api.plugins.options.OptionDescription;
import jadx.api.plugins.input.ICodeLoader;
import jadx.api.plugins.input.JadxCodeInput;
import jadx.api.plugins.input.data.impl.EmptyCodeLoader;
import jadx.plugins.input.dex.DexInputPlugin;
public class JavaConvertPlugin implements JadxInputPlugin, JadxPluginOptions {
public class JavaConvertPlugin implements JadxPlugin, JadxCodeInput {
public static final String PLUGIN_ID = "java-convert";
@@ -30,22 +29,18 @@ public class JavaConvertPlugin implements JadxInputPlugin, JadxPluginOptions {
}
@Override
public ILoadResult loadFiles(List<Path> input) {
public void init(JadxPluginContext context) {
context.registerOptions(options);
context.addCodeInput(this);
}
@Override
public ICodeLoader loadFiles(List<Path> input) {
ConvertResult result = loader.process(input);
if (result.isEmpty()) {
result.close();
return EmptyLoadResult.INSTANCE;
return EmptyCodeLoader.INSTANCE;
}
return dexInput.loadFiles(result.getConverted(), result);
}
@Override
public void setOptions(Map<String, String> options) {
this.options.apply(options);
}
@Override
public List<OptionDescription> getOptionsDescriptions() {
return this.options.buildOptionsDescriptions();
}
}
@@ -9,13 +9,14 @@ import java.util.function.Function;
import org.jetbrains.annotations.Nullable;
import jadx.api.plugins.JadxPlugin;
import jadx.api.plugins.JadxPluginContext;
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.api.plugins.input.ICodeLoader;
import jadx.api.plugins.input.data.impl.EmptyCodeLoader;
import jadx.plugins.input.java.utils.JavaClassParseException;
public class JavaInputPlugin implements JadxInputPlugin {
public class JavaInputPlugin implements JadxPlugin {
public static final JadxPluginInfo PLUGIN_INFO = new JadxPluginInfo(
"java-input",
@@ -28,26 +29,26 @@ public class JavaInputPlugin implements JadxInputPlugin {
}
@Override
public ILoadResult loadFiles(List<Path> inputFiles) {
public void init(JadxPluginContext context) {
context.addCodeInput(JavaInputPlugin::loadClassFiles);
}
public static ICodeLoader loadClassFiles(List<Path> inputFiles) {
return loadClassFiles(inputFiles, null);
}
public static ILoadResult loadClassFiles(List<Path> inputFiles, @Nullable Closeable closeable) {
public static ICodeLoader loadClassFiles(List<Path> inputFiles, @Nullable Closeable closeable) {
List<JavaClassReader> readers = new JavaInputLoader().collectFiles(inputFiles);
if (readers.isEmpty()) {
return EmptyLoadResult.INSTANCE;
return EmptyCodeLoader.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) {
public static ICodeLoader load(Function<JavaInputLoader, List<JavaClassReader>> loader) {
return wrapClassReaders(loader.apply(new JavaInputLoader()));
}
@@ -56,7 +57,7 @@ public class JavaInputPlugin implements JadxInputPlugin {
* 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) {
public static ICodeLoader loadFromInputStream(InputStream in, String fileName) {
try {
return wrapClassReaders(new JavaInputLoader().loadInputStream(in, fileName));
} catch (Exception e) {
@@ -69,14 +70,14 @@ public class JavaInputPlugin implements JadxInputPlugin {
* 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) {
public static ICodeLoader 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) {
public static ICodeLoader wrapClassReaders(List<JavaClassReader> readers) {
if (readers.isEmpty()) {
return EmptyLoadResult.INSTANCE;
return EmptyCodeLoader.INSTANCE;
}
return new JavaLoadResult(readers);
}
@@ -9,11 +9,10 @@ import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.plugins.input.ICodeLoader;
import jadx.api.plugins.input.data.IClassData;
import jadx.api.plugins.input.data.ILoadResult;
import jadx.api.plugins.input.data.IResourceData;
public class JavaLoadResult implements ILoadResult {
public class JavaLoadResult implements ICodeLoader {
private static final Logger LOG = LoggerFactory.getLogger(JavaLoadResult.class);
private final List<JavaClassReader> readers;
@@ -40,10 +39,6 @@ public class JavaLoadResult implements ILoadResult {
}
}
@Override
public void visitResources(Consumer<IResourceData> consumer) {
}
@Override
public boolean isEmpty() {
return readers.isEmpty();
@@ -16,7 +16,7 @@ import org.junit.jupiter.api.Test;
import jadx.api.JadxArgs;
import jadx.api.JadxDecompiler;
import jadx.api.plugins.input.data.ILoadResult;
import jadx.api.plugins.input.ICodeLoader;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.fail;
@@ -40,7 +40,7 @@ class CustomLoadTest {
List<Path> files = Stream.of("HelloWorld.class", "HelloWorld$HelloInner.class")
.map(this::getSample)
.collect(Collectors.toList());
ILoadResult loadResult = JavaInputPlugin.loadClassFiles(files);
ICodeLoader loadResult = JavaInputPlugin.loadClassFiles(files);
loadDecompiler(loadResult);
assertThat(jadx.getClassesWithInners())
.hasSize(2)
@@ -52,7 +52,7 @@ class CustomLoadTest {
void loadFromInputStream() throws IOException {
String fileName = "HelloWorld$HelloInner.class";
try (InputStream in = Files.newInputStream(getSample(fileName))) {
ILoadResult loadResult = JavaInputPlugin.loadFromInputStream(in, fileName);
ICodeLoader loadResult = JavaInputPlugin.loadFromInputStream(in, fileName);
loadDecompiler(loadResult);
assertThat(jadx.getClassesWithInners())
.hasSize(1)
@@ -66,7 +66,7 @@ class CustomLoadTest {
void loadSingleClass() throws IOException {
String fileName = "HelloWorld.class";
byte[] content = Files.readAllBytes(getSample(fileName));
ILoadResult loadResult = JavaInputPlugin.loadSingleClass(content, fileName);
ICodeLoader loadResult = JavaInputPlugin.loadSingleClass(content, fileName);
loadDecompiler(loadResult);
assertThat(jadx.getClassesWithInners())
.hasSize(1)
@@ -77,7 +77,7 @@ class CustomLoadTest {
@Test
void load() {
ILoadResult loadResult = JavaInputPlugin.load(loader -> {
ICodeLoader loadResult = JavaInputPlugin.load(loader -> {
List<JavaClassReader> inputs = new ArrayList<>(2);
try {
String hello = "HelloWorld.class";
@@ -110,9 +110,9 @@ class CustomLoadTest {
jadx.getClassesWithInners().forEach(cls -> System.out.println(cls.getCode()));
}
public void loadDecompiler(ILoadResult load) {
public void loadDecompiler(ICodeLoader codeLoader) {
try {
jadx.addCustomLoad(load);
jadx.addCustomCodeLoader(codeLoader);
jadx.load();
} catch (Exception e) {
fail(e);
@@ -3,13 +3,15 @@ package jadx.plugins.input.raung;
import java.nio.file.Path;
import java.util.List;
import jadx.api.plugins.JadxPlugin;
import jadx.api.plugins.JadxPluginContext;
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.api.plugins.input.ICodeLoader;
import jadx.api.plugins.input.JadxCodeInput;
import jadx.api.plugins.input.data.impl.EmptyCodeLoader;
import jadx.plugins.input.java.JavaInputPlugin;
public class RaungInputPlugin implements JadxInputPlugin {
public class RaungInputPlugin implements JadxPlugin, JadxCodeInput {
@Override
public JadxPluginInfo getPluginInfo() {
@@ -20,10 +22,15 @@ public class RaungInputPlugin implements JadxInputPlugin {
}
@Override
public ILoadResult loadFiles(List<Path> input) {
public void init(JadxPluginContext context) {
context.addCodeInput(this);
}
@Override
public ICodeLoader loadFiles(List<Path> input) {
RaungConvert convert = new RaungConvert();
if (!convert.execute(input)) {
return EmptyLoadResult.INSTANCE;
return EmptyCodeLoader.INSTANCE;
}
return JavaInputPlugin.loadClassFiles(convert.getFiles(), convert);
}
@@ -8,12 +8,10 @@ import net.fabricmc.mappingio.tree.MappingTree;
import net.fabricmc.mappingio.tree.MemoryMappingTree;
import jadx.api.JadxArgs;
import jadx.api.JadxDecompiler;
import jadx.api.args.UserRenamesMappingsMode;
import jadx.api.plugins.JadxPlugin;
import jadx.api.plugins.JadxPluginContext;
import jadx.api.plugins.JadxPluginInfo;
import jadx.api.plugins.pass.JadxPassContext;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.plugins.mappings.load.CodeMappingsVisitor;
import jadx.plugins.mappings.load.MappingsVisitor;
@@ -27,12 +25,10 @@ public class RenameMappingsPlugin implements JadxPlugin {
@Override
public void init(JadxPluginContext context) {
JadxArgs args = ((JadxDecompiler) context.getDecompiler()).getArgs();
MappingTree mappingTree = openMapping(args);
MappingTree mappingTree = openMapping(context.getArgs());
if (mappingTree != null) {
JadxPassContext passContext = context.getPassContext();
passContext.addPass(new MappingsVisitor(mappingTree));
passContext.addPass(new CodeMappingsVisitor(mappingTree));
context.addPass(new MappingsVisitor(mappingTree));
context.addPass(new CodeMappingsVisitor(mappingTree));
}
}
@@ -1,26 +1,20 @@
package jadx.plugins.script
import jadx.api.plugins.JadxPlugin
import jadx.api.plugins.JadxPluginContext
import jadx.api.plugins.JadxPluginInfo
import jadx.api.plugins.options.JadxPluginOptions
import jadx.api.plugins.options.OptionDescription
import jadx.plugins.script.passes.JadxScriptAfterLoadPass
import jadx.plugins.script.runner.ScriptEval
import jadx.plugins.script.runtime.data.JadxScriptAllOptions
class JadxScriptPlugin : JadxPluginOptions {
var scriptOptions: JadxScriptAllOptions = JadxScriptAllOptions(emptyMap())
class JadxScriptPlugin : JadxPlugin {
private val scriptOptions = JadxScriptAllOptions()
override fun getPluginInfo() = JadxPluginInfo("jadx-script", "Jadx Script", "Scripting support for jadx")
override fun setOptions(options: Map<String, String>) {
scriptOptions = JadxScriptAllOptions(options)
}
override fun init(init: JadxPluginContext) {
init.registerOptions(scriptOptions)
val scriptStates = ScriptEval().process(init, scriptOptions) ?: return
init.passContext.addPass(JadxScriptAfterLoadPass(scriptStates))
init.addPass(JadxScriptAfterLoadPass(scriptStates))
}
override fun getOptionsDescriptions(): List<OptionDescription> = scriptOptions.descriptions
}
@@ -1,14 +1,21 @@
package jadx.plugins.script.runtime.data
import jadx.api.plugins.options.JadxPluginOptions
import jadx.api.plugins.options.OptionDescription
import jadx.api.plugins.options.OptionDescription.OptionType
import jadx.api.plugins.options.impl.JadxOptionDescription
import jadx.plugins.script.runtime.JadxScriptInstance
data class JadxScriptAllOptions(
val values: Map<String, String>,
class JadxScriptAllOptions : JadxPluginOptions {
lateinit var values: Map<String, String>
val descriptions: MutableList<OptionDescription> = mutableListOf()
)
override fun setOptions(options: Map<String, String>) {
values = options
}
override fun getOptionsDescriptions(): MutableList<OptionDescription> = descriptions
}
class ScriptOption<T>(
val name: String,
@@ -69,7 +69,7 @@ class JadxScriptInstance(
}
fun addPass(pass: JadxPass) {
scriptData.pluginContext.passContext.addPass(pass)
scriptData.pluginContext.addPass(pass)
}
val internalDecompiler: JadxDecompiler
@@ -3,13 +3,15 @@ package jadx.plugins.input.smali;
import java.nio.file.Path;
import java.util.List;
import jadx.api.plugins.JadxPlugin;
import jadx.api.plugins.JadxPluginContext;
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.api.plugins.input.ICodeLoader;
import jadx.api.plugins.input.JadxCodeInput;
import jadx.api.plugins.input.data.impl.EmptyCodeLoader;
import jadx.plugins.input.dex.DexInputPlugin;
public class SmaliInputPlugin implements JadxInputPlugin {
public class SmaliInputPlugin implements JadxPlugin, JadxCodeInput {
private final DexInputPlugin dexInput = new DexInputPlugin();
@@ -19,10 +21,15 @@ public class SmaliInputPlugin implements JadxInputPlugin {
}
@Override
public ILoadResult loadFiles(List<Path> input) {
public void init(JadxPluginContext context) {
context.addCodeInput(this);
}
@Override
public ICodeLoader loadFiles(List<Path> input) {
SmaliConvert convert = new SmaliConvert();
if (!convert.execute(input)) {
return EmptyLoadResult.INSTANCE;
return EmptyCodeLoader.INSTANCE;
}
return dexInput.loadFiles(convert.getDexFiles(), convert);
}