feat: new module/library for work with call graphs (#2890)

This commit is contained in:
Skylot
2026-06-17 21:34:23 +01:00
parent 59003bdb1f
commit d25eda4839
24 changed files with 706 additions and 4 deletions
+1
View File
@@ -11,6 +11,7 @@ dependencies {
implementation(project(":jadx-core"))
implementation(project(":jadx-plugins-tools"))
implementation(project(":jadx-commons:jadx-app-commons"))
implementation(project(":jadx-commons:jadx-analysis"))
runtimeOnly(project(":jadx-plugins:jadx-dex-input"))
runtimeOnly(project(":jadx-plugins:jadx-java-input"))
@@ -1,11 +1,14 @@
package jadx.cli;
import java.nio.file.Path;
import java.util.function.Consumer;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.analysis.callgraph.JadxCallGraph;
import jadx.analysis.callgraph.api.ICallGraph;
import jadx.api.JadxArgs;
import jadx.api.JadxDecompiler;
import jadx.api.impl.AnnotatedCodeWriter;
@@ -16,6 +19,7 @@ import jadx.cli.LogHelper.LogLevelEnum;
import jadx.cli.config.JadxConfigAdapter;
import jadx.cli.plugins.JadxFilesGetter;
import jadx.core.utils.exceptions.JadxArgsValidateException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.plugins.tools.JadxExternalPluginsLoader;
public class JadxCLI {
@@ -73,6 +77,7 @@ public class JadxCLI {
if (checkForErrors(jadx)) {
return 2;
}
writeCallGraph(jadx, cliArgs);
if (!SingleClassMode.process(jadx, cliArgs)) {
save(jadx);
}
@@ -131,4 +136,29 @@ public class JadxCLI {
System.out.print(" \r");
}
}
private static void writeCallGraph(JadxDecompiler jadx, JadxCLIArgs cliArgs) {
JadxCLIArgs.CallGraphSaveMode mode = cliArgs.callGraphSaveMode;
if (mode == null || mode == JadxCLIArgs.CallGraphSaveMode.NONE) {
return;
}
Path outPath = jadx.getArgs().getOutDir().toPath();
ICallGraph callGraph = JadxCallGraph.builder(jadx)
.resolvedOnly(true)
.build();
Path cgPath;
switch (mode) {
case JSON:
cgPath = outPath.resolve("callgraph.json");
callGraph.writeJson(cgPath);
break;
case DOT:
cgPath = outPath.resolve("callgraph.dot");
callGraph.writeDot(cgPath);
break;
default:
throw new JadxRuntimeException("Unexpected call graph save mode: " + mode);
}
LOG.info("Call graph saved: {}", cgPath.toAbsolutePath());
}
}
@@ -283,6 +283,13 @@ public class JadxCLIArgs implements IJadxConfig {
@Parameter(names = { "--raw-cfg" }, description = "save methods control flow graph (use raw instructions)")
protected boolean rawCfgOutput = false;
@Parameter(
names = { "--call-graph" },
description = "save app call graph in format: 'dot' or 'json'",
converter = CallGraphSaveModeConverter.class
)
protected CallGraphSaveMode callGraphSaveMode = CallGraphSaveMode.NONE;
@Parameter(names = { "-f", "--fallback" }, description = "set '--decompilation-mode' to 'fallback' (deprecated)")
protected boolean fallbackMode = false;
@@ -827,6 +834,14 @@ public class JadxCLIArgs implements IJadxConfig {
this.rawCfgOutput = rawCfgOutput;
}
public CallGraphSaveMode getCallGraphSaveMode() {
return callGraphSaveMode;
}
public void setCallGraphSaveMode(CallGraphSaveMode callGraphSaveMode) {
this.callGraphSaveMode = callGraphSaveMode;
}
public boolean isReplaceConsts() {
return replaceConsts;
}
@@ -1022,6 +1037,18 @@ public class JadxCLIArgs implements IJadxConfig {
}
}
public enum CallGraphSaveMode {
NONE,
DOT,
JSON,
}
public static class CallGraphSaveModeConverter extends BaseEnumConverter<CallGraphSaveMode> {
public CallGraphSaveModeConverter() {
super(CallGraphSaveMode::valueOf, CallGraphSaveMode::values);
}
}
public abstract static class BaseEnumConverter<E extends Enum<E>> implements IStringConverter<E> {
private final Function<String, E> parse;
private final Supplier<E[]> values;