diff --git a/jadx-cli/src/main/resources/logback.xml b/jadx-cli/src/main/resources/logback.xml
index 58785b3e5..8325b3e38 100644
--- a/jadx-cli/src/main/resources/logback.xml
+++ b/jadx-cli/src/main/resources/logback.xml
@@ -9,6 +9,7 @@
+
diff --git a/jadx-core/src/main/java/jadx/core/ProcessClass.java b/jadx-core/src/main/java/jadx/core/ProcessClass.java
index ff602ad28..e62bbce58 100644
--- a/jadx-core/src/main/java/jadx/core/ProcessClass.java
+++ b/jadx-core/src/main/java/jadx/core/ProcessClass.java
@@ -19,6 +19,7 @@ import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.DecompileModeOverrideAttr;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.LoadStage;
+import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.DepthTraversal;
import jadx.core.dex.visitors.IDexTreeVisitor;
@@ -207,6 +208,44 @@ public class ProcessClass {
}
}
+ public boolean processMethodUntilVisitor(MethodNode mth, String visitorName, boolean includeVisitor) {
+ IDexTreeVisitor foundPass = null;
+ IDexTreeVisitor prevPass = null;
+ for (IDexTreeVisitor pass : passes) {
+ if (pass.getName().equals(visitorName)) {
+ if (includeVisitor) {
+ foundPass = pass;
+ } else {
+ foundPass = prevPass;
+ }
+ break;
+ }
+ prevPass = pass;
+ }
+ if (foundPass == null) {
+ return false;
+ }
+ return processMethodToVisitor(mth, foundPass);
+ }
+
+ public boolean processMethodToVisitor(MethodNode mth, IDexTreeVisitor lastPassToProcess) {
+ synchronized (mth.getTopParentClass().getClassInfo()) {
+ try {
+ mth.unload();
+ mth.load();
+ for (IDexTreeVisitor pass : passes) {
+ DepthTraversal.visit(pass, mth);
+ if (pass == lastPassToProcess) {
+ return true;
+ }
+ }
+ } catch (Exception e) {
+ throw new JadxRuntimeException("Failed to process method to visitor: " + lastPassToProcess, e);
+ }
+ return false;
+ }
+ }
+
// TODO: make passes list private and not visible
public List getPasses() {
return passes;
diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java
index ba6fe4a7a..48de30684 100644
--- a/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java
+++ b/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java
@@ -131,6 +131,7 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
sVars = Collections.emptyList();
instructions = null;
blocks = null;
+ blocksMaxCId = 0;
enterBlock = null;
exitBlock = null;
region = null;
@@ -713,10 +714,9 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
return codeReader;
}
- // Cannot modify through get, use setUseIn
@Override
public List getUseIn() {
- return Collections.unmodifiableList(useIn);
+ return useIn;
}
// Do not modify passed list after setting
@@ -740,7 +740,7 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
}
public Set getUsed() {
- this.removeInavlidMethodsUsed();
+ this.removeInvalidMethodsUsed();
return methodsUsed;
}
@@ -762,12 +762,8 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
// Remove any methods from the list of used methods (calees) if this method (caller) has been
// removed from the calee's list of callers
- private void removeInavlidMethodsUsed() {
- for (MethodNode methodUsed : new ArrayList<>(methodsUsed)) {
- if (!methodUsed.getUseIn().contains(this)) {
- methodsUsed.remove(methodUsed);
- }
- }
+ private void removeInvalidMethodsUsed() {
+ methodsUsed.removeIf(methodUsed -> !methodUsed.getUseIn().contains(this));
}
public JavaMethod getJavaNode() {
diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/finaly/MarkFinallyVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/finaly/MarkFinallyVisitor.java
index 24aa5b076..7892605fa 100644
--- a/jadx-core/src/main/java/jadx/core/dex/visitors/finaly/MarkFinallyVisitor.java
+++ b/jadx-core/src/main/java/jadx/core/dex/visitors/finaly/MarkFinallyVisitor.java
@@ -27,8 +27,6 @@ import jadx.core.dex.trycatch.TryEdge;
import jadx.core.dex.trycatch.TryEdgeScopeGroupMap;
import jadx.core.dex.visitors.AbstractVisitor;
import jadx.core.dex.visitors.ConstInlineVisitor;
-import jadx.core.dex.visitors.DepthTraversal;
-import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.dex.visitors.JadxVisitor;
import jadx.core.dex.visitors.finaly.traverser.TraverserController;
import jadx.core.dex.visitors.finaly.traverser.TraverserException;
@@ -37,7 +35,6 @@ import jadx.core.dex.visitors.ssa.SSATransform;
import jadx.core.utils.BlockUtils;
import jadx.core.utils.ListUtils;
import jadx.core.utils.Pair;
-import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.exceptions.JadxRuntimeException;
/**
@@ -474,18 +471,8 @@ public class MarkFinallyVisitor extends AbstractVisitor {
*/
private static void undoFinallyVisitor(MethodNode mth) {
try {
- // TODO: make more common and less hacky
- mth.unload();
- mth.load();
- for (IDexTreeVisitor visitor : mth.root().getPasses()) {
- if (visitor instanceof MarkFinallyVisitor) {
- break;
- // All visitors after MarkFinally will be invoked as usual after this because
- // the original decompilation request will proceed.
- }
- DepthTraversal.visit(visitor, mth);
- }
- } catch (DecodeException e) {
+ mth.root().getProcessClasses().processMethodUntilVisitor(mth, "MarkFinallyVisitor", false);
+ } catch (Exception e) {
mth.addError("Undo finally extract failed", e);
}
}
diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/usage/UsageInfo.java b/jadx-core/src/main/java/jadx/core/dex/visitors/usage/UsageInfo.java
index 2acd8bc8c..41f8c0c96 100644
--- a/jadx-core/src/main/java/jadx/core/dex/visitors/usage/UsageInfo.java
+++ b/jadx-core/src/main/java/jadx/core/dex/visitors/usage/UsageInfo.java
@@ -54,7 +54,7 @@ public class UsageInfo implements IUsageInfoData {
mthUsage.visit((mth, methods) -> mth.setUseIn(resolveMthList(sortedList(methods))));
mthUses.visit((mth, methods) -> mth.setUsed(resolveMthList(sortedList(methods))));
unresolvedMthUsage.visit((mth, unresolvedMethods) -> mth.setUnresolvedUsed(new ArrayList<>(unresolvedMethods)));
- selfCalls.forEach((mth, selfCall) -> mth.setCallsSelf(selfCall));
+ selfCalls.forEach(MethodNode::setCallsSelf);
}
@Override
diff --git a/jadx-core/src/main/java/jadx/core/utils/DotGraphUtils.java b/jadx-core/src/main/java/jadx/core/utils/DotGraphUtils.java
index 4574cf9c4..23a1fd43c 100644
--- a/jadx-core/src/main/java/jadx/core/utils/DotGraphUtils.java
+++ b/jadx-core/src/main/java/jadx/core/utils/DotGraphUtils.java
@@ -9,6 +9,8 @@ import java.util.Set;
import java.util.regex.Matcher;
import java.util.stream.Collectors;
+import org.jetbrains.annotations.Nullable;
+
import jadx.api.ICodeWriter;
import jadx.api.JavaMethod;
import jadx.api.impl.SimpleCodeWriter;
@@ -89,8 +91,7 @@ public class DotGraphUtils {
.resolve(mth.getParentClass().getClassInfo().getAliasFullPath() + "_graphs")
.resolve(fileName)
.toFile();
- file = FileUtils.cutFileName(file);
- return file;
+ return FileUtils.cutFileName(file);
}
public void dumpToFile(MethodNode mth) {
@@ -100,16 +101,14 @@ public class DotGraphUtils {
public void dumpToFile(MethodNode mth, File dir) {
String graph = dumpToString(mth);
-
if (graph == null) {
return;
}
-
File file = getFullFile(mth, dir);
SaveCode.save(graph, file);
}
- public String dumpToString(MethodNode mth) {
+ public @Nullable String dumpToString(MethodNode mth) {
dot.startLine("digraph \"CFG for");
dot.add(escape(mth.getMethodInfo().getFullId()));
dot.add("\" {");
@@ -144,9 +143,7 @@ public class DotGraphUtils {
// region will already process all it's containing blocks.
continue;
}
-
processBlock(mth, block);
-
}
}
diff --git a/jadx-gui/src/main/java/jadx/gui/ui/action/ActionModel.java b/jadx-gui/src/main/java/jadx/gui/ui/action/ActionModel.java
index 6a5114322..9c36a1a66 100644
--- a/jadx-gui/src/main/java/jadx/gui/ui/action/ActionModel.java
+++ b/jadx-gui/src/main/java/jadx/gui/ui/action/ActionModel.java
@@ -4,10 +4,14 @@ import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.util.Arrays;
import java.util.List;
+import java.util.Objects;
import java.util.stream.Collectors;
import javax.swing.ImageIcon;
+import org.jetbrains.annotations.Nullable;
+
+import jadx.core.utils.Utils;
import jadx.gui.utils.NLS;
import jadx.gui.utils.UiUtils;
import jadx.gui.utils.shortcut.Shortcut;
@@ -81,21 +85,14 @@ public enum ActionModel {
OPEN_DEVICE(MENU_TOOLBAR, "debugger.process_selector", "debugger.process_selector", "ui/startDebugger",
Shortcut.none()),
- FIND_USAGE(CODE_AREA, "popup.find_usage", "popup.find_usage", null,
- Shortcut.keyboard(KeyEvent.VK_X)),
- FIND_USAGE_PLUS(CODE_AREA, "popup.usage_dialog_plus", "popup.usage_dialog_plus", null,
- Shortcut.keyboard(KeyEvent.VK_C)),
- GOTO_DECLARATION(CODE_AREA, "popup.go_to_declaration", "popup.go_to_declaration", null,
- Shortcut.keyboard(KeyEvent.VK_D)),
- CONVERT_NUMBER(CODE_AREA, "popup.convert_number", "popup.convert_number", null, Shortcut.none()),
- VIEW_CLASS_INHERITANCE_GRAPH(CODE_AREA, "popup.view_class_graph", "popup.view_class_graph_description", null,
- Shortcut.none()),
- VIEW_CLASS_METHOD_GRAPH(CODE_AREA, "popup.view_class_method_graph", "popup.view_class_method_graph_description",
- null, Shortcut.none()),
- VIEW_CALL_GRAPH(CODE_AREA, "popup.view_call_graph", "popup.view_call_graph_description", null, Shortcut.none()),
- VIEW_CONTROL_FLOW_GRAPH(CODE_AREA, "popup.view_cfg", "popup.view_cfg_description", null, Shortcut.none()),
- VIEW_RAW_CONTROL_FLOW_GRAPH(CODE_AREA, "popup.view_raw_cfg", "popup.view_raw_cfg_description", null, Shortcut.none()),
- VIEW_REGION_CONTROL_FLOW_GRAPH(CODE_AREA, "popup.view_region_cfg", "popup.view_region_cfg_description", null, Shortcut.none()),
+ FIND_USAGE(CODE_AREA, "popup.find_usage", null, null, Shortcut.keyboard(KeyEvent.VK_X)),
+ FIND_USAGE_PLUS(CODE_AREA, "popup.usage_dialog_plus", null, null, Shortcut.keyboard(KeyEvent.VK_C)),
+ GOTO_DECLARATION(CODE_AREA, "popup.go_to_declaration", null, null, Shortcut.keyboard(KeyEvent.VK_D)),
+ CONVERT_NUMBER(CODE_AREA, "popup.convert_number", null, null, null),
+ VIEW_CLASS_INHERITANCE_GRAPH(CODE_AREA, "popup.view_class_graph", "popup.view_class_graph_description", null, null),
+ VIEW_CLASS_METHOD_GRAPH(CODE_AREA, "popup.view_class_method_graph", "popup.view_class_method_graph_description", null, null),
+ VIEW_CALL_GRAPH(CODE_AREA, "popup.view_call_graph", "popup.view_call_graph_description", null, null),
+ VIEW_CONTROL_FLOW_GRAPH(CODE_AREA, "popup.view_cfg", null, null, null),
CODE_COMMENT(CODE_AREA, "popup.add_comment", "popup.add_comment", null,
Shortcut.keyboard(KeyEvent.VK_SEMICOLON)),
@@ -131,15 +128,16 @@ public enum ActionModel {
private final ActionCategory category;
private final String nameRes;
private final String descRes;
- private final String iconPath;
+ private final @Nullable String iconPath;
private final Shortcut defaultShortcut;
- ActionModel(ActionCategory category, String nameRes, String descRes, String iconPath, Shortcut defaultShortcut) {
- this.category = category;
- this.nameRes = nameRes;
- this.descRes = descRes;
+ ActionModel(ActionCategory category, String nameRes, @Nullable String descRes, @Nullable String iconPath,
+ @Nullable Shortcut defaultShortcut) {
+ this.category = Objects.requireNonNull(category);
+ this.nameRes = Objects.requireNonNull(nameRes);
+ this.descRes = Utils.getOrElse(descRes, nameRes);
this.iconPath = iconPath;
- this.defaultShortcut = defaultShortcut;
+ this.defaultShortcut = defaultShortcut != null ? defaultShortcut : Shortcut.none();
}
public static List select(ActionCategory category) {
@@ -152,7 +150,7 @@ public enum ActionModel {
return category;
}
- public String getName() {
+ public @Nullable String getName() {
if (nameRes != null) {
String name = NLS.str(nameRes);
if (name().endsWith("_V")) {
@@ -163,14 +161,14 @@ public enum ActionModel {
return null;
}
- public String getDescription() {
+ public @Nullable String getDescription() {
if (descRes != null) {
return NLS.str(descRes);
}
return null;
}
- public ImageIcon getIcon() {
+ public @Nullable ImageIcon getIcon() {
if (iconPath != null) {
return UiUtils.openSvgIcon(iconPath);
}
diff --git a/jadx-gui/src/main/java/jadx/gui/ui/action/ViewCallGraphAction.java b/jadx-gui/src/main/java/jadx/gui/ui/action/ViewCallGraphAction.java
index 8369d85ad..d69bc916c 100644
--- a/jadx-gui/src/main/java/jadx/gui/ui/action/ViewCallGraphAction.java
+++ b/jadx-gui/src/main/java/jadx/gui/ui/action/ViewCallGraphAction.java
@@ -5,44 +5,34 @@ import javax.swing.JOptionPane;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.gui.treemodel.JMethod;
import jadx.gui.treemodel.JNode;
import jadx.gui.ui.codearea.CodeArea;
-import jadx.gui.ui.dialog.CallGraphDialog;
+import jadx.gui.ui.graphs.CallGraphDialog;
import jadx.gui.utils.NLS;
public final class ViewCallGraphAction extends JNodeAction {
- private static final Logger LOG = LoggerFactory.getLogger(ViewCallGraphAction.class);
private static final long serialVersionUID = -11122327621269039L;
+ private static final Logger LOG = LoggerFactory.getLogger(ViewCallGraphAction.class);
+
public ViewCallGraphAction(CodeArea codeArea) {
super(ActionModel.VIEW_CALL_GRAPH, codeArea);
}
- @Override
- public void runAction(JNode node) {
- try {
-
- JMethod methodNode;
-
- if (node instanceof JMethod) {
- methodNode = (JMethod) node;
- } else {
- throw new JadxRuntimeException("Unsupported node type: " + (node != null ? node.getClass() : "null"));
- }
-
- CallGraphDialog.open(getCodeArea().getMainWindow(), methodNode);
- } catch (Exception e) {
- LOG.error("Failed to view graph", e);
- JOptionPane.showMessageDialog(getCodeArea().getMainWindow(), e.getLocalizedMessage(), NLS.str("error_dialog.title"),
- JOptionPane.ERROR_MESSAGE);
- }
- }
-
@Override
public boolean isActionEnabled(JNode node) {
return node instanceof JMethod;
}
+ @Override
+ public void runAction(JNode node) {
+ try {
+ CallGraphDialog.open(getCodeArea().getMainWindow(), (JMethod) node);
+ } catch (Exception e) {
+ LOG.error("Failed to view graph", e);
+ JOptionPane.showMessageDialog(getCodeArea().getMainWindow(), e.getLocalizedMessage(), NLS.str("error_dialog.title"),
+ JOptionPane.ERROR_MESSAGE);
+ }
+ }
}
diff --git a/jadx-gui/src/main/java/jadx/gui/ui/action/ViewClassInheritanceGraphAction.java b/jadx-gui/src/main/java/jadx/gui/ui/action/ViewClassInheritanceGraphAction.java
index a48a64d8f..647c6ab2c 100644
--- a/jadx-gui/src/main/java/jadx/gui/ui/action/ViewClassInheritanceGraphAction.java
+++ b/jadx-gui/src/main/java/jadx/gui/ui/action/ViewClassInheritanceGraphAction.java
@@ -11,23 +11,27 @@ import jadx.gui.treemodel.JField;
import jadx.gui.treemodel.JMethod;
import jadx.gui.treemodel.JNode;
import jadx.gui.ui.codearea.CodeArea;
-import jadx.gui.ui.dialog.ClassInheritanceGraphDialog;
+import jadx.gui.ui.graphs.ClassInheritanceGraphDialog;
import jadx.gui.utils.NLS;
public final class ViewClassInheritanceGraphAction extends JNodeAction {
- private static final Logger LOG = LoggerFactory.getLogger(ViewClassInheritanceGraphAction.class);
private static final long serialVersionUID = -331826691076655264L;
+ private static final Logger LOG = LoggerFactory.getLogger(ViewClassInheritanceGraphAction.class);
+
public ViewClassInheritanceGraphAction(CodeArea codeArea) {
super(ActionModel.VIEW_CLASS_INHERITANCE_GRAPH, codeArea);
}
+ @Override
+ public boolean isActionEnabled(JNode node) {
+ return node instanceof JMethod || node instanceof JClass || node instanceof JField;
+ }
+
@Override
public void runAction(JNode node) {
try {
-
JClass classNode;
-
if (node instanceof JMethod) {
classNode = node.getJParent();
} else if (node instanceof JField) {
@@ -37,7 +41,6 @@ public final class ViewClassInheritanceGraphAction extends JNodeAction {
} else {
throw new JadxRuntimeException("Unsupported node type: " + (node != null ? node.getClass() : "null"));
}
-
ClassInheritanceGraphDialog.open(getCodeArea().getMainWindow(), classNode);
} catch (Exception e) {
LOG.error("Failed to view graph", e);
@@ -45,10 +48,4 @@ public final class ViewClassInheritanceGraphAction extends JNodeAction {
JOptionPane.ERROR_MESSAGE);
}
}
-
- @Override
- public boolean isActionEnabled(JNode node) {
- return node instanceof JMethod || node instanceof JClass || node instanceof JField;
- }
-
}
diff --git a/jadx-gui/src/main/java/jadx/gui/ui/action/ViewClassMethodGraphAction.java b/jadx-gui/src/main/java/jadx/gui/ui/action/ViewClassMethodGraphAction.java
index 29252a610..80caf2644 100644
--- a/jadx-gui/src/main/java/jadx/gui/ui/action/ViewClassMethodGraphAction.java
+++ b/jadx-gui/src/main/java/jadx/gui/ui/action/ViewClassMethodGraphAction.java
@@ -11,23 +11,27 @@ import jadx.gui.treemodel.JField;
import jadx.gui.treemodel.JMethod;
import jadx.gui.treemodel.JNode;
import jadx.gui.ui.codearea.CodeArea;
-import jadx.gui.ui.dialog.ClassMethodGraphDialog;
+import jadx.gui.ui.graphs.ClassMethodGraphDialog;
import jadx.gui.utils.NLS;
public final class ViewClassMethodGraphAction extends JNodeAction {
- private static final Logger LOG = LoggerFactory.getLogger(ViewClassMethodGraphAction.class);
private static final long serialVersionUID = -331826691076655264L;
+ private static final Logger LOG = LoggerFactory.getLogger(ViewClassMethodGraphAction.class);
+
public ViewClassMethodGraphAction(CodeArea codeArea) {
super(ActionModel.VIEW_CLASS_METHOD_GRAPH, codeArea);
}
+ @Override
+ public boolean isActionEnabled(JNode node) {
+ return node instanceof JMethod || node instanceof JClass || node instanceof JField;
+ }
+
@Override
public void runAction(JNode node) {
try {
-
JClass classNode;
-
if (node instanceof JMethod) {
classNode = node.getJParent();
} else if (node instanceof JField) {
@@ -37,7 +41,6 @@ public final class ViewClassMethodGraphAction extends JNodeAction {
} else {
throw new JadxRuntimeException("Unsupported node type: " + (node != null ? node.getClass() : "null"));
}
-
ClassMethodGraphDialog.open(getCodeArea().getMainWindow(), classNode);
} catch (Exception e) {
LOG.error("Failed to view graph", e);
@@ -45,10 +48,4 @@ public final class ViewClassMethodGraphAction extends JNodeAction {
JOptionPane.ERROR_MESSAGE);
}
}
-
- @Override
- public boolean isActionEnabled(JNode node) {
- return node instanceof JMethod || node instanceof JClass || node instanceof JField;
- }
-
}
diff --git a/jadx-gui/src/main/java/jadx/gui/ui/action/ViewControlFlowGraphAction.java b/jadx-gui/src/main/java/jadx/gui/ui/action/ViewControlFlowGraphAction.java
index 4df8f9987..7151d9c79 100644
--- a/jadx-gui/src/main/java/jadx/gui/ui/action/ViewControlFlowGraphAction.java
+++ b/jadx-gui/src/main/java/jadx/gui/ui/action/ViewControlFlowGraphAction.java
@@ -1,58 +1,41 @@
package jadx.gui.ui.action;
-import java.io.File;
-
import javax.swing.JOptionPane;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import jadx.core.dex.nodes.MethodNode;
-import jadx.core.utils.DotGraphUtils;
-import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.gui.treemodel.JMethod;
import jadx.gui.treemodel.JNode;
import jadx.gui.ui.codearea.CodeArea;
-import jadx.gui.ui.dialog.ControlFlowGraphDialog;
+import jadx.gui.ui.graphs.ControlFlowGraphDialog;
import jadx.gui.utils.NLS;
public final class ViewControlFlowGraphAction extends JNodeAction {
- private static final Logger LOG = LoggerFactory.getLogger(ViewControlFlowGraphAction.class);
private static final long serialVersionUID = -490213655L;
- public ViewControlFlowGraphAction(CodeArea codeArea) {
- super(ActionModel.VIEW_CONTROL_FLOW_GRAPH, codeArea);
+ private static final Logger LOG = LoggerFactory.getLogger(ViewControlFlowGraphAction.class);
+
+ public ViewControlFlowGraphAction(ActionModel actionModel, CodeArea codeArea) {
+ super(actionModel, codeArea);
+ }
+
+ @Override
+ public boolean isActionEnabled(JNode node) {
+ return node instanceof JMethod;
}
@Override
public void runAction(JNode node) {
try {
-
- JMethod methodNode;
-
- if (node instanceof JMethod) {
- methodNode = (JMethod) node;
- } else {
- throw new JadxRuntimeException("Unsupported node type: " + (node != null ? node.getClass() : "null"));
- }
-
- ControlFlowGraphDialog.open(getCodeArea().getMainWindow(), methodNode, false, false);
+ ControlFlowGraphDialog.open(getCodeArea().getMainWindow(), (JMethod) node);
} catch (Exception e) {
LOG.error("Failed to view graph", e);
- JOptionPane.showMessageDialog(getCodeArea().getMainWindow(), e.getLocalizedMessage(), NLS.str("error_dialog.title"),
+ JOptionPane.showMessageDialog(
+ getCodeArea().getMainWindow(),
+ e.getLocalizedMessage(),
+ NLS.str("error_dialog.title"),
JOptionPane.ERROR_MESSAGE);
}
}
-
- @Override
- public boolean isActionEnabled(JNode node) {
- if (!(node instanceof JMethod)) {
- return false;
- }
- MethodNode mth = ((JMethod) node).getJavaMethod().getMethodNode();
- File file = new DotGraphUtils(false, false).getFullFile(mth);
-
- return file.exists() && !file.isDirectory();
- }
-
}
diff --git a/jadx-gui/src/main/java/jadx/gui/ui/action/ViewRawControlFlowGraphAction.java b/jadx-gui/src/main/java/jadx/gui/ui/action/ViewRawControlFlowGraphAction.java
deleted file mode 100644
index 7b481b725..000000000
--- a/jadx-gui/src/main/java/jadx/gui/ui/action/ViewRawControlFlowGraphAction.java
+++ /dev/null
@@ -1,58 +0,0 @@
-package jadx.gui.ui.action;
-
-import java.io.File;
-
-import javax.swing.JOptionPane;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import jadx.core.dex.nodes.MethodNode;
-import jadx.core.utils.DotGraphUtils;
-import jadx.core.utils.exceptions.JadxRuntimeException;
-import jadx.gui.treemodel.JMethod;
-import jadx.gui.treemodel.JNode;
-import jadx.gui.ui.codearea.CodeArea;
-import jadx.gui.ui.dialog.ControlFlowGraphDialog;
-import jadx.gui.utils.NLS;
-
-public final class ViewRawControlFlowGraphAction extends JNodeAction {
- private static final Logger LOG = LoggerFactory.getLogger(ViewRawControlFlowGraphAction.class);
- private static final long serialVersionUID = -535703386523657L;
-
- public ViewRawControlFlowGraphAction(CodeArea codeArea) {
- super(ActionModel.VIEW_RAW_CONTROL_FLOW_GRAPH, codeArea);
- }
-
- @Override
- public void runAction(JNode node) {
- try {
-
- JMethod methodNode;
-
- if (node instanceof JMethod) {
- methodNode = (JMethod) node;
- } else {
- throw new JadxRuntimeException("Unsupported node type: " + (node != null ? node.getClass() : "null"));
- }
-
- ControlFlowGraphDialog.open(getCodeArea().getMainWindow(), methodNode, false, true);
- } catch (Exception e) {
- LOG.error("Failed to view graph", e);
- JOptionPane.showMessageDialog(getCodeArea().getMainWindow(), e.getLocalizedMessage(), NLS.str("error_dialog.title"),
- JOptionPane.ERROR_MESSAGE);
- }
- }
-
- @Override
- public boolean isActionEnabled(JNode node) {
- if (!(node instanceof JMethod)) {
- return false;
- }
- MethodNode mth = ((JMethod) node).getJavaMethod().getMethodNode();
- File file = new DotGraphUtils(false, true).getFullFile(mth);
-
- return file.exists() && !file.isDirectory();
- }
-
-}
diff --git a/jadx-gui/src/main/java/jadx/gui/ui/action/ViewRegionControlFlowGraphAction.java b/jadx-gui/src/main/java/jadx/gui/ui/action/ViewRegionControlFlowGraphAction.java
deleted file mode 100644
index 291993b41..000000000
--- a/jadx-gui/src/main/java/jadx/gui/ui/action/ViewRegionControlFlowGraphAction.java
+++ /dev/null
@@ -1,58 +0,0 @@
-package jadx.gui.ui.action;
-
-import java.io.File;
-
-import javax.swing.JOptionPane;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import jadx.core.dex.nodes.MethodNode;
-import jadx.core.utils.DotGraphUtils;
-import jadx.core.utils.exceptions.JadxRuntimeException;
-import jadx.gui.treemodel.JMethod;
-import jadx.gui.treemodel.JNode;
-import jadx.gui.ui.codearea.CodeArea;
-import jadx.gui.ui.dialog.ControlFlowGraphDialog;
-import jadx.gui.utils.NLS;
-
-public final class ViewRegionControlFlowGraphAction extends JNodeAction {
- private static final Logger LOG = LoggerFactory.getLogger(ViewRegionControlFlowGraphAction.class);
- private static final long serialVersionUID = -14970352087936L;
-
- public ViewRegionControlFlowGraphAction(CodeArea codeArea) {
- super(ActionModel.VIEW_REGION_CONTROL_FLOW_GRAPH, codeArea);
- }
-
- @Override
- public void runAction(JNode node) {
- try {
-
- JMethod methodNode;
-
- if (node instanceof JMethod) {
- methodNode = (JMethod) node;
- } else {
- throw new JadxRuntimeException("Unsupported node type: " + (node != null ? node.getClass() : "null"));
- }
-
- ControlFlowGraphDialog.open(getCodeArea().getMainWindow(), methodNode, true, false);
- } catch (Exception e) {
- LOG.error("Failed to view graph", e);
- JOptionPane.showMessageDialog(getCodeArea().getMainWindow(), e.getLocalizedMessage(), NLS.str("error_dialog.title"),
- JOptionPane.ERROR_MESSAGE);
- }
- }
-
- @Override
- public boolean isActionEnabled(JNode node) {
- if (!(node instanceof JMethod)) {
- return false;
- }
- MethodNode mth = ((JMethod) node).getJavaMethod().getMethodNode();
- File file = new DotGraphUtils(true, false).getFullFile(mth);
-
- return file.exists() && !file.isDirectory();
- }
-
-}
diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodeArea.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodeArea.java
index e34d44b88..bf49b3135 100644
--- a/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodeArea.java
+++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodeArea.java
@@ -36,20 +36,18 @@ import jadx.gui.treemodel.JLoadableNode;
import jadx.gui.treemodel.JNode;
import jadx.gui.treemodel.JResource;
import jadx.gui.ui.MainWindow;
+import jadx.gui.ui.action.ActionModel;
import jadx.gui.ui.action.CommentSearchAction;
import jadx.gui.ui.action.CopyReferenceAction;
import jadx.gui.ui.action.FindUsageAction;
import jadx.gui.ui.action.FridaAction;
import jadx.gui.ui.action.GoToDeclarationAction;
-import jadx.gui.ui.action.JNodeAction;
import jadx.gui.ui.action.JsonPrettifyAction;
import jadx.gui.ui.action.RenameAction;
import jadx.gui.ui.action.ViewCallGraphAction;
import jadx.gui.ui.action.ViewClassInheritanceGraphAction;
import jadx.gui.ui.action.ViewClassMethodGraphAction;
import jadx.gui.ui.action.ViewControlFlowGraphAction;
-import jadx.gui.ui.action.ViewRawControlFlowGraphAction;
-import jadx.gui.ui.action.ViewRegionControlFlowGraphAction;
import jadx.gui.ui.action.XposedAction;
import jadx.gui.ui.codearea.mode.JCodeMode;
import jadx.gui.ui.codearea.sync.CodeAreaSyncee;
@@ -61,7 +59,6 @@ import jadx.gui.utils.CaretPositionFix;
import jadx.gui.utils.DefaultPopupMenuListener;
import jadx.gui.utils.JNodeCache;
import jadx.gui.utils.JumpPosition;
-import jadx.gui.utils.NLS;
import jadx.gui.utils.UiUtils;
import jadx.gui.utils.shortcut.ShortcutsController;
@@ -202,11 +199,7 @@ public final class CodeArea extends AbstractCodeArea implements CodeAreaSyncerAb
popup.add(new ViewClassInheritanceGraphAction(this));
popup.add(new ViewClassMethodGraphAction(this));
popup.add(new ViewCallGraphAction(this));
- popup.addSubmenu(new JNodeAction[] {
- new ViewControlFlowGraphAction(this),
- new ViewRawControlFlowGraphAction(this),
- new ViewRegionControlFlowGraphAction(this),
- }, NLS.str("popup.cfg_submenu"));
+ popup.add(new ViewControlFlowGraphAction(ActionModel.VIEW_CONTROL_FLOW_GRAPH, this));
popup.addSeparator();
popup.add(new ConvertNumberAction(this));
diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/CommentAction.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/CommentAction.java
index edfe339c5..0b952a24b 100644
--- a/jadx-gui/src/main/java/jadx/gui/ui/codearea/CommentAction.java
+++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/CommentAction.java
@@ -127,8 +127,7 @@ public class CommentAction extends CodeAreaAction implements DefaultPopupMenuLis
*
* @return blank code comment object (comment string empty)
*/
- @Nullable
- protected ICodeComment getCommentRef(int pos) {
+ protected @Nullable ICodeComment getCommentRef(int pos) {
if (pos == -1) {
return null;
}
diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/ConvertNumberAction.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/ConvertNumberAction.java
index 4f4581865..23b1bb436 100644
--- a/jadx-gui/src/main/java/jadx/gui/ui/codearea/ConvertNumberAction.java
+++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/ConvertNumberAction.java
@@ -4,55 +4,45 @@ import java.awt.event.ActionEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import java.util.function.Consumer;
+import java.util.regex.Pattern;
import javax.swing.event.PopupMenuEvent;
import javax.swing.text.BadLocationException;
import org.fife.ui.rsyntaxtextarea.Token;
import org.jetbrains.annotations.Nullable;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import jadx.api.data.CommentStyle;
import jadx.api.data.ICodeComment;
import jadx.api.data.impl.JadxCodeComment;
-import jadx.api.data.impl.JadxCodeData;
-import jadx.gui.settings.JadxProject;
import jadx.gui.treemodel.JClass;
import jadx.gui.ui.action.ActionModel;
+import jadx.gui.ui.dialog.CommentDialog;
import jadx.gui.utils.NLS;
public class ConvertNumberAction extends CommentAction {
+ private static final String DEFAULT_TEXT = NLS.str("popup.convert_number");
+ private static final String TOOLTIP_TEXT = NLS.str("popup.convert_number_tooltip");
- private static final Logger LOG = LoggerFactory.getLogger(ConvertNumberAction.class);
-
- private static final String DEFAULT_TEXT = "";
- private final String tooltipText;
+ private @Nullable String codeComment;
public ConvertNumberAction(CodeArea codeArea) {
-
super(ActionModel.CONVERT_NUMBER, codeArea);
-
- tooltipText = NLS.str("popup.convert_number");
-
- // default menu item to disabled
setEnabled(false);
setNameAndDesc(DEFAULT_TEXT);
}
@Override
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
-
if (codeArea.getNode() instanceof JClass) {
// try parse number from word under caret
// and set text of popup menu dynamically
String word = getWordByPosition(codeArea.getCaretPosition());
List conversions = getConversionsFromWord(word);
- if (conversions != null && !conversions.isEmpty()) {
- String joined = String.join(" | ", conversions);
- setName(joined);
- setShortDescription(tooltipText);
+ if (!conversions.isEmpty()) {
+ codeComment = String.join(" | ", conversions);
+ setName(DEFAULT_TEXT + ": " + codeComment);
+ setShortDescription(TOOLTIP_TEXT);
setEnabled(true);
}
}
@@ -63,59 +53,29 @@ public class ConvertNumberAction extends CommentAction {
// reset menu to disabled on cancel
setEnabled(false);
setNameAndDesc(DEFAULT_TEXT);
+ codeComment = null;
}
@Override
public void actionPerformed(ActionEvent e) {
-
- if (!super.enabled) {
+ if (!enabled) {
return;
}
-
- String newText = e.getActionCommand();
+ String newText = codeComment;
if (newText == null) {
return;
}
-
ICodeComment comment = getCommentRef(codeArea.getCaretPosition());
if (comment == null) {
return;
}
-
ICodeComment newComment = new JadxCodeComment(comment.getNodeRef(), comment.getCodeRef(), newText, CommentStyle.LINE);
- updateCommentsData(codeArea, list -> list.add(newComment));
+ CommentDialog.updateCommentsData(codeArea, list -> list.add(newComment));
}
/**
- * Adds comments to project file and code area
- */
- private static void updateCommentsData(CodeArea codeArea, Consumer> updater) {
- try {
- JadxProject project = codeArea.getProject();
- JadxCodeData codeData = project.getCodeData();
- if (codeData == null) {
- codeData = new JadxCodeData();
- }
- List list = new ArrayList<>(codeData.getComments());
- updater.accept(list);
- Collections.sort(list);
- codeData.setComments(list);
- project.setCodeData(codeData);
- codeArea.getMainWindow().getWrapper().reloadCodeData();
- } catch (Exception e) {
- LOG.error("Comment action failed", e);
- }
- try {
- // refresh code
- codeArea.backgroundRefreshClass();
- } catch (Exception e) {
- LOG.error("Failed to reload code", e);
- }
- }
-
- /**
- * similar to AbstractCodeArea::getWordByPosition
+ * Similar to AbstractCodeArea::getWordByPosition
* but includes "-" for negative numbers
*/
public @Nullable String getWordByPosition(int offset) {
@@ -123,19 +83,15 @@ public class ConvertNumberAction extends CommentAction {
if (token == null) {
return null;
}
-
String str = token.getLexeme();
-
try {
String prev = codeArea.getText(token.getOffset() - 1, 1);
if (prev.equals("-")) {
str = "-" + str;
}
-
} catch (BadLocationException e) {
// ignore
}
-
int len = str.length();
if (len > 2 && str.startsWith("\"") && str.endsWith("\"")) {
return str.substring(1, len - 1);
@@ -143,53 +99,52 @@ public class ConvertNumberAction extends CommentAction {
return str;
}
+ private static final Pattern NUMBER_FORMAT = Pattern.compile("(-?\\d+L?|0x[0-9A-Fa-f]+)");
+
/**
* Tries to parse a number from input string,
* returns list of strings of the number converted to different formats.
* e.g. if input number is in hex, converts to decimal and binary.
*/
- static @Nullable List getConversionsFromWord(String word) {
-
- List conversions = new ArrayList<>();
-
- if (word == null || word.isEmpty()) {
- return null;
+ static List getConversionsFromWord(@Nullable String word) {
+ if (word == null || word.isEmpty() || word.equals("0") || word.equals("0L")) {
+ return Collections.emptyList();
}
-
- int i32 = 0;
- long i64 = 0;
- int radix = 10;
+ if (!NUMBER_FORMAT.matcher(word).matches()) {
+ return Collections.emptyList();
+ }
+ int i32;
+ long i64;
+ int radix;
boolean parsedLong = false;
-
- // handle hex
if (word.startsWith("0x")) {
word = word.substring(2);
radix = 16;
+ } else {
+ radix = 10;
}
-
// handle long int syntax like "12345L"
if (word.endsWith("L")) {
word = word.substring(0, word.length() - 1);
+ i64 = tryParseLong(word, radix);
+ i32 = (int) i64;
parsedLong = true;
- }
-
- // try parse int
- try {
- i32 = Integer.parseInt(word, radix);
- i64 = i32;
-
- } catch (NumberFormatException e) {
-
- // try parse long
- try {
- i64 = Long.parseLong(word, radix);
+ if (i64 == 0) {
+ return Collections.emptyList();
+ }
+ } else {
+ i32 = tryParseInt(word, radix);
+ if (i32 != 0) {
+ i64 = i32;
+ } else {
+ i64 = tryParseLong(word, radix);
parsedLong = true;
-
- } catch (NumberFormatException ignore) {
- return null;
+ if (i64 == 0) {
+ return Collections.emptyList();
+ }
}
}
-
+ List conversions = new ArrayList<>();
// if we parsed decimal, output hex and vice versa
if (radix == 10) {
if (parsedLong) {
@@ -197,13 +152,11 @@ public class ConvertNumberAction extends CommentAction {
} else {
conversions.add(String.format("0x%x", i32));
}
-
- } else if (radix == 16) {
- conversions.add(String.format("%d", i32));
+ } else {
+ conversions.add(parsedLong ? Long.toString(i64) : Integer.toString(i32));
}
// pad binary in 8-bit groups
- // int leadingZeros = parsed_long ? : Integer.numberOfLeadingZeros(i32);
int padBits = (int) Math.ceil((64 - Long.numberOfLeadingZeros(i64)) / 8.0) * 8;
if (padBits < 8) {
padBits = 8;
@@ -211,17 +164,31 @@ public class ConvertNumberAction extends CommentAction {
if (!parsedLong && padBits > 32) {
padBits = 32;
}
-
// format padded binary
String binaryString = parsedLong ? Long.toBinaryString(i64) : Integer.toBinaryString(i32);
String fmt = String.format("0b%%%ds", padBits);
conversions.add(String.format(fmt, binaryString).replace(' ', '0'));
- // format printable ascii chars
+ // format printable ASCII chars
if (i32 >= ' ' && i32 <= '~') {
- conversions.add(String.format("'%c'", (int) i32));
+ conversions.add(String.format("'%c'", i32));
}
+ return conversions;
+ }
- return conversions; // no match
+ private static int tryParseInt(String str, int radix) {
+ try {
+ return Integer.parseInt(str, radix);
+ } catch (NumberFormatException e) {
+ return 0;
+ }
+ }
+
+ private static long tryParseLong(String str, int radix) {
+ try {
+ return Long.parseLong(str, radix);
+ } catch (NumberFormatException e) {
+ return 0;
+ }
}
}
diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/JNodePopupBuilder.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/JNodePopupBuilder.java
index ff361763f..3864cff83 100644
--- a/jadx-gui/src/main/java/jadx/gui/ui/codearea/JNodePopupBuilder.java
+++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/JNodePopupBuilder.java
@@ -38,21 +38,6 @@ public class JNodePopupBuilder {
popupListener.addActions(nodeAction);
}
- public void addSubmenu(JNodeAction[] nodeActions, String name) {
- JMenu submenu = new JMenu(name);
-
- for (JNodeAction nodeAction : nodeActions) {
- if (nodeAction.getActionModel() != null) {
- shortcutsController.bindImmediate(nodeAction);
- }
-
- submenu.add(nodeAction);
- popupListener.addActions(nodeAction);
- }
-
- menu.add(submenu);
- }
-
public void add(JadxGuiAction action) {
if (action.getActionModel() != null) {
shortcutsController.bindImmediate(action);
@@ -63,6 +48,18 @@ public class JNodePopupBuilder {
}
}
+ public void addSubmenu(String name, JNodeAction... nodeActions) {
+ JMenu submenu = new JMenu(name);
+ for (JNodeAction nodeAction : nodeActions) {
+ if (nodeAction.getActionModel() != null) {
+ shortcutsController.bindImmediate(nodeAction);
+ }
+ submenu.add(nodeAction);
+ popupListener.addActions(nodeAction);
+ }
+ menu.add(submenu);
+ }
+
public JPopupMenu getMenu() {
return menu;
}
diff --git a/jadx-gui/src/main/java/jadx/gui/ui/dialog/CommentDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/dialog/CommentDialog.java
index 2acf05f94..cdbb5feab 100644
--- a/jadx-gui/src/main/java/jadx/gui/ui/dialog/CommentDialog.java
+++ b/jadx-gui/src/main/java/jadx/gui/ui/dialog/CommentDialog.java
@@ -44,7 +44,7 @@ public class CommentDialog extends CommonDialog {
dialog.setVisible(true);
}
- private static void updateCommentsData(CodeArea codeArea, Consumer> updater) {
+ public static void updateCommentsData(CodeArea codeArea, Consumer> updater) {
try {
JadxProject project = codeArea.getProject();
JadxCodeData codeData = project.getCodeData();
diff --git a/jadx-gui/src/main/java/jadx/gui/ui/dialog/ControlFlowGraphDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/dialog/ControlFlowGraphDialog.java
deleted file mode 100644
index a83880d0d..000000000
--- a/jadx-gui/src/main/java/jadx/gui/ui/dialog/ControlFlowGraphDialog.java
+++ /dev/null
@@ -1,52 +0,0 @@
-package jadx.gui.ui.dialog;
-
-import java.io.File;
-import java.util.Scanner;
-
-import javax.swing.SwingUtilities;
-
-import jadx.api.JavaMethod;
-import jadx.core.dex.nodes.MethodNode;
-import jadx.core.utils.DotGraphUtils;
-import jadx.gui.treemodel.JMethod;
-import jadx.gui.ui.MainWindow;
-import jadx.gui.utils.NLS;
-
-public class ControlFlowGraphDialog extends GraphDialog {
-
- private static final long serialVersionUID = -68749445239697710L;
-
- public ControlFlowGraphDialog(MainWindow mainWindow, String method) {
- super(mainWindow, String.format("%s: %s", NLS.str("graph_viewer.cfg.title"), method));
- }
-
- public static void open(MainWindow window, JMethod method, boolean useRegions, boolean rawInsn) {
-
- JavaMethod javaMethod = method.getJavaMethod();
-
- GraphDialog graphDialog = new ControlFlowGraphDialog(window, DotGraphUtils.methodFormatName(javaMethod, false));
- graphDialog.addMenuBar();
- graphDialog.setVisible(true);
-
- SwingUtilities.invokeLater(() -> {
- String graph = generateGraph(javaMethod, useRegions, rawInsn);
- if (graph != null) {
- graphDialog.getPanel().setGraph(graph);
- } else {
- graphDialog.getPanel().invalidateImage(graphError(NLS.str("graph_viewer.file_not_found_error")));
- }
- });
- }
-
- private static String generateGraph(JavaMethod javaMethod, boolean useRegions, boolean rawInsn) {
- MethodNode mth = javaMethod.getMethodNode();
- File file = new DotGraphUtils(useRegions, rawInsn).getFullFile(mth);
-
- try (Scanner reader = new Scanner(file)) {
- String contents = reader.useDelimiter("\\Z").next();
- return contents;
- } catch (Exception e) {
- return null;
- }
- }
-}
diff --git a/jadx-gui/src/main/java/jadx/gui/ui/dialog/GraphDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/dialog/GraphDialog.java
deleted file mode 100644
index 8e335852c..000000000
--- a/jadx-gui/src/main/java/jadx/gui/ui/dialog/GraphDialog.java
+++ /dev/null
@@ -1,417 +0,0 @@
-package jadx.gui.ui.dialog;
-
-import java.awt.BorderLayout;
-import java.awt.Component;
-import java.awt.Dimension;
-import java.awt.FlowLayout;
-import java.awt.Graphics;
-import java.awt.Graphics2D;
-import java.awt.Point;
-import java.awt.event.MouseAdapter;
-import java.awt.event.MouseEvent;
-import java.awt.event.MouseListener;
-import java.awt.event.MouseWheelEvent;
-import java.awt.geom.AffineTransform;
-import java.awt.image.BufferedImage;
-import java.io.File;
-import java.io.PrintWriter;
-import java.io.StringWriter;
-
-import javax.swing.JButton;
-import javax.swing.JComponent;
-import javax.swing.JFileChooser;
-import javax.swing.JFrame;
-import javax.swing.JMenuBar;
-import javax.swing.JOptionPane;
-import javax.swing.JPanel;
-import javax.swing.JTextArea;
-import javax.swing.SwingUtilities;
-import javax.swing.WindowConstants;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import guru.nidi.graphviz.engine.Format;
-import guru.nidi.graphviz.engine.Graphviz;
-import guru.nidi.graphviz.model.MutableGraph;
-import guru.nidi.graphviz.parse.Parser;
-
-import jadx.gui.ui.MainWindow;
-import jadx.gui.utils.NLS;
-import jadx.gui.utils.UiUtils;
-import jadx.gui.utils.layout.WrapLayout;
-
-public abstract class GraphDialog extends JFrame {
-
- private static final long serialVersionUID = 5840390965763493590L;
-
- private static final Logger LOG = LoggerFactory.getLogger(GraphDialog.class);
-
- private final MainWindow mainWindow;
- private GraphPanel panel;
-
- private static final Dimension MIN_WINDOW_SIZE = new Dimension(800, 500);
-
- private JMenuBar menuBar = null;
-
- public static JTextArea graphError() {
- return graphError(NLS.str("graph_viewer.default_error"));
- }
-
- public static JTextArea graphError(String errorMessage) {
- JTextArea errorText = new JTextArea();
- errorText.setText(errorMessage);
- errorText.setVisible(true);
- errorText.setEditable(false);
- errorText.setLineWrap(false);
- return errorText;
- }
-
- public static JTextArea graphError(Exception error) {
- JTextArea errorText = new JTextArea();
- StringWriter stringWriter = new StringWriter();
- PrintWriter printWriter = new PrintWriter(stringWriter);
- stringWriter.write(NLS.str("graph_viewer.default_error"));
- stringWriter.write(": ");
- error.printStackTrace(printWriter);
- errorText.setText(stringWriter.toString());
- errorText.setVisible(true);
- errorText.setEditable(false);
- errorText.setLineWrap(false);
- return errorText;
- }
-
- public GraphDialog(MainWindow mainWindow) {
- this(mainWindow, NLS.str("graph_viewer.default_title"));
- }
-
- public JMenuBar addMenuBar() {
- JMenuBar menuBar = new JMenuBar();
- menuBar.setLayout(new WrapLayout(FlowLayout.LEFT));
- add(menuBar, BorderLayout.PAGE_START);
- this.menuBar = menuBar;
-
- JFileChooser fileChooser = new JFileChooser();
-
- JButton saveButton = new JButton(NLS.str("graph_viewer.save_graph"));
- saveButton.setEnabled(false);
- saveButton.addActionListener(e -> {
- try {
- int option = fileChooser.showSaveDialog(this);
-
- if (option == JFileChooser.APPROVE_OPTION) {
- File file = fileChooser.getSelectedFile();
- getPanel().renderer.render(Format.SVG).toFile(file);
-
- }
- } catch (Exception ex) {
- LOG.error("Failed to save file: ", ex);
- JOptionPane.showMessageDialog(this, NLS.str("graph_viewer.file_failure"),
- NLS.str("graph_viewer.file_failure"),
- JOptionPane.INFORMATION_MESSAGE);
- }
- });
-
- // Assemble menubar panel
- JPanel menuBarPanel = new JPanel();
- menuBarPanel.setOpaque(false);
- menuBarPanel.add(saveButton);
-
- // Add menubar panel to menuBar
- menuBar.add(menuBarPanel);
-
- return menuBar;
- }
-
- private void enableMenu() {
- JMenuBar menu = this.menuBar;
- setAllEnabled(true, menu);
- }
-
- private void disableMenu() {
- JMenuBar menu = this.menuBar;
- setAllEnabled(false, menu);
- }
-
- private void setAllEnabled(boolean isEnabled, JComponent component) {
- component.setEnabled(isEnabled);
-
- Component[] components = component.getComponents();
- for (Component subComponent : components) {
- if (subComponent instanceof JComponent) {
- setAllEnabled(isEnabled, (JComponent) subComponent);
- } else {
- subComponent.setEnabled(isEnabled);
- }
- }
- }
-
- public GraphDialog(MainWindow mainWindow, String title) {
- super(title);
- this.mainWindow = mainWindow;
-
- setMinimumSize(MIN_WINDOW_SIZE);
-
- setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
- UiUtils.addEscapeShortCutToDispose(this);
- setLocationRelativeTo(null);
-
- loadWindowPos();
-
- LOG.debug("Dialog w: {} h: {}", getWidth(), getHeight());
-
- LOG.debug("cwd: {}", System.getProperty("user.dir"));
-
- panel = new GraphPanel(this);
- panel.setFocusable(true);
- panel.addMouseListener(new MouseListener() {
- public void mouseClicked(MouseEvent e) {
- requestFocusInWindow();
- }
-
- public void mouseEntered(MouseEvent e) {
- }
-
- public void mouseExited(MouseEvent e) {
- }
-
- public void mousePressed(MouseEvent e) {
- }
-
- public void mouseReleased(MouseEvent e) {
- }
- });
-
- setLayout(new BorderLayout());
- add(panel, BorderLayout.CENTER);
-
- }
-
- public void loadWindowPos() {
- if (!mainWindow.getSettings().loadWindowPos(this)) {
- setPreferredSize(MIN_WINDOW_SIZE);
- }
- }
-
- @Override
- public void dispose() {
- try {
- mainWindow.getSettings().saveWindowPos(this);
- } catch (Exception e) {
- LOG.warn("Failed to save window size and position", e);
- }
- super.dispose();
- }
-
- class GraphPanel extends JPanel {
-
- private Dimension fullImageSize = new Dimension();
- private double scale = 1.0;
- private double minimumScale = 0.01;
- private double maximumScale = 7.0;
- private double translateX = 0;
- private double translateY = 0;
- private Point lastDragPoint = null;
-
- private BufferedImage image;
-
- private Graphviz renderer;
-
- private final GraphDialog parentDialog;
-
- public GraphPanel(GraphDialog parentDialog) {
-
- this.parentDialog = parentDialog;
-
- MouseAdapter ma = new MouseAdapter() {
- @Override
- public void mousePressed(MouseEvent e) {
- lastDragPoint = e.getPoint();
- }
-
- @Override
- public void mouseDragged(MouseEvent e) {
- if (image != null) {
- Point p = e.getPoint();
- translateX += (p.x - lastDragPoint.x) / scale;
- translateY += (p.y - lastDragPoint.y) / scale;
- lastDragPoint = p;
- repaint();
- }
- }
-
- @Override
- public void mouseWheelMoved(MouseWheelEvent e) {
- if (image != null) {
- double prevScale = scale;
- scale *= Math.pow(1.1, -e.getWheelRotation());
-
- if (scale > maximumScale) {
- scale = maximumScale;
- }
-
- if (scale < minimumScale) {
- scale = minimumScale;
- }
-
- if (scale != prevScale) {
- Point p = e.getPoint();
- double px = (p.x - translateX * prevScale) / prevScale;
- double py = (p.y - translateY * prevScale) / prevScale;
- translateX = (p.x / scale) - px;
- translateY = (p.y / scale) - py;
- LOG.debug("Rescaling {}%", scale * 100);
- renderGraphScaled();
- if (image == null) {
- return;
- }
- repaint();
-
- }
-
- }
- }
-
- };
-
- addMouseListener(ma);
- addMouseMotionListener(ma);
- addMouseWheelListener(ma);
- }
-
- @Override
- protected void paintComponent(Graphics g) {
- super.paintComponent(g);
- if (image != null) {
- Graphics2D g2d = (Graphics2D) g;
- AffineTransform transform = new AffineTransform();
- transform.translate(translateX * scale, translateY * scale);
- g2d.drawImage(image, transform, null);
- }
- }
-
- public void setGraph(File dotString) {
- try {
- LOG.debug("Parsing DOT file: {} ", dotString.getAbsolutePath());
- setGraph(new Parser().read(dotString));
- } catch (Exception e) {
- LOG.error("Error parsing DOT file", e);
- invalidateImage(graphError(e));
- }
- }
-
- public void setGraph(String dotString) {
- try {
- setGraph(new Parser().read(dotString));
- } catch (Exception e) {
- LOG.error("Error parsing DOT string", e);
- invalidateImage(graphError(e));
- }
- }
-
- public void setGraph(MutableGraph g) {
-
- renderer = Graphviz.fromGraph(g);
- parentDialog.enableMenu();
-
- scale = 1.0;
-
- // set initial image scale and posiition
- Runnable doCenter = new Runnable() {
- public void run() {
-
- renderGraphFullSize();
- if (image == null) {
- return;
- }
-
- LOG.debug("full image w {} h {}", fullImageSize.width, fullImageSize.height);
-
- // scale required to fit image to window width or height
- double heightScale = (double) getHeight() / (double) fullImageSize.height;
- double widthScale = (double) getWidth() / (double) fullImageSize.width;
- if (widthScale < heightScale) {
- scale = widthScale;
- LOG.debug("scaling to fit width {}/{} {}", getWidth(), fullImageSize.width, scale);
-
- } else {
- scale = heightScale;
- LOG.debug("scaling to fit height {}/{} {}", getHeight(), fullImageSize.height, scale);
- }
-
- scale = scale * 0.95;
- maximumScale = Math.sqrt(Integer.MAX_VALUE / (fullImageSize.width * fullImageSize.height)) / 8;
- minimumScale = Math.min(scale, maximumScale);
-
- renderGraphScaled();
- if (image == null) {
- return;
- }
-
- // center image in window
- translateY = (getHeight() / 2 - (fullImageSize.height * scale) / 2) / scale;
- translateX = (getWidth() / 2 - (fullImageSize.width * scale) / 2) / scale;
-
- repaint();
- }
- };
-
- SwingUtilities.invokeLater(doCenter);
-
- }
-
- private void renderGraphFullSize() {
- try {
- image = null;
- image = renderer.render(Format.SVG).toImage();
- if (image.getWidth() == 0 || image.getHeight() == 0) {
- // If rendered image is too small, calculating the scale would later cause a
- // division by zero
- LOG.error("Graph render failed, image too small");
- invalidateImage(graphError(NLS.str("graph_viewer.image_too_small")));
- return;
- }
-
- fullImageSize.setSize(image.getWidth(), image.getHeight());
-
- } catch (IllegalArgumentException illegalArgumentException) {
- // If rendered image is too large, a Dimension object is passed invalid arguments
- LOG.error("Graph render failed, illegal arguments: ", illegalArgumentException);
- invalidateImage(graphError(NLS.str("graph_viewer.image_too_large")));
-
- } catch (Exception e) {
- // A large image may cause a number of other other exception types caught here along with other
- // failure cases
- LOG.error("Graph render failed: ", e);
- invalidateImage(graphError(e));
- }
-
- }
-
- private void renderGraphScaled() {
- try {
- if (fullImageSize.width * scale * fullImageSize.height * scale >= Integer.MAX_VALUE) {
- scale = maximumScale;
- }
- image = renderer.width((int) (fullImageSize.width * scale)).render(Format.SVG).toImage();
- } catch (Exception e) {
- LOG.error("Graph render failed: ", e);
- invalidateImage(graphError(e));
- }
-
- }
-
- public void invalidateImage(JTextArea errorMsg) {
- this.add(errorMsg);
- image = null;
- this.parentDialog.disableMenu();
- this.revalidate();
- repaint();
- }
-
- }
-
- protected GraphPanel getPanel() {
- return this.panel;
- }
-}
diff --git a/jadx-gui/src/main/java/jadx/gui/ui/dialog/CallGraphDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/graphs/CallGraphDialog.java
similarity index 91%
rename from jadx-gui/src/main/java/jadx/gui/ui/dialog/CallGraphDialog.java
rename to jadx-gui/src/main/java/jadx/gui/ui/graphs/CallGraphDialog.java
index 92d292dfe..aec696454 100644
--- a/jadx-gui/src/main/java/jadx/gui/ui/dialog/CallGraphDialog.java
+++ b/jadx-gui/src/main/java/jadx/gui/ui/graphs/CallGraphDialog.java
@@ -1,4 +1,4 @@
-package jadx.gui.ui.dialog;
+package jadx.gui.ui.graphs;
import java.awt.BorderLayout;
import java.awt.Color;
@@ -9,7 +9,6 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
-import java.util.Objects;
import java.util.Set;
import javax.swing.Box;
@@ -24,9 +23,6 @@ import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
import jadx.api.JavaMethod;
import jadx.api.JavaNode;
import jadx.api.plugins.input.data.IMethodRef;
@@ -38,19 +34,17 @@ import jadx.gui.utils.UiUtils;
import jadx.gui.utils.layout.WrapLayout;
public class CallGraphDialog extends GraphDialog {
-
private static final long serialVersionUID = -850803763322590708L;
- private static final Logger LOG = LoggerFactory.getLogger(CallGraphDialog.class);
-
private static final String FONT = "fontname=\"Courier\" fontsize=12";
+
+ private final JavaMethod javaMethod;
private int callerDepthLimit = 3;
private int calleeDepthLimit = 3;
private int nextNodeID;
private Map methodToNodeID;
private Map unresolvedMethodToNodeID;
private Set edges;
- private JavaMethod javaMethod;
private boolean longNames = false;
public CallGraphDialog(MainWindow mainWindow, JavaMethod javaMethod) {
@@ -59,6 +53,7 @@ public class CallGraphDialog extends GraphDialog {
this.javaMethod = javaMethod;
}
+ @Override
public JMenuBar addMenuBar() {
JMenuBar menuBar = super.addMenuBar();
@@ -128,7 +123,6 @@ public class CallGraphDialog extends GraphDialog {
}
public static void open(MainWindow window, JMethod method) {
-
JavaMethod javaMethod = method.getJavaMethod();
CallGraphDialog graphDialog = new CallGraphDialog(window, javaMethod);
graphDialog.addMenuBar();
@@ -141,13 +135,11 @@ public class CallGraphDialog extends GraphDialog {
SwingUtilities.invokeLater(() -> {
String graph = generateGraph(javaMethod);
getPanel().setGraph(graph);
-
});
}
private String generateGraph(JavaMethod javaMethod) {
StringBuilder sb = new StringBuilder();
-
Color themeBackground = UIManager.getColor("Panel.background");
Color themeForeground = UIManager.getColor("Label.foreground");
Color themeHighlight = UIManager.getColor("Component.focusedBorderColor");
@@ -167,7 +159,6 @@ public class CallGraphDialog extends GraphDialog {
String shadeColor = String.format("fillcolor=\"#%02x%02x%02x\"", themeShade.getRed(), themeShade.getGreen(), themeShade.getBlue());
try (Formatter f = new Formatter(sb)) {
-
// graph header
f.format("digraph G {\n");
f.format("%s\n", bgColor);
@@ -180,16 +171,12 @@ public class CallGraphDialog extends GraphDialog {
edges = new HashSet<>();
addNode(f, javaMethod, highlightColor);
-
// add caller relationships
addCallers(0, f, javaMethod);
-
// add calee relationships
addCallees(0, f, javaMethod);
-
// close graph
f.format("}");
-
return f.toString();
}
}
@@ -198,7 +185,6 @@ public class CallGraphDialog extends GraphDialog {
if (depth >= callerDepthLimit) {
return;
}
-
List uses = javaMethod.getUseIn();
// add "calls" relationships
@@ -207,10 +193,8 @@ public class CallGraphDialog extends GraphDialog {
continue;
}
JavaMethod caller = (JavaMethod) node;
-
int nodeID = addNode(f, caller);
addEdge(f, nodeID, methodToNodeID.get(javaMethod));
-
addCallers(depth + 1, f, caller);
}
}
@@ -219,7 +203,6 @@ public class CallGraphDialog extends GraphDialog {
if (depth >= calleeDepthLimit) {
return;
}
-
List used = javaMethod.getUsed();
// add "calls" relationships
@@ -228,10 +211,8 @@ public class CallGraphDialog extends GraphDialog {
continue;
}
JavaMethod callee = (JavaMethod) node;
-
int nodeID = addNode(f, callee);
addEdge(f, methodToNodeID.get(javaMethod), nodeID);
-
addCallees(depth + 1, f, callee);
}
addUnresolvedCallees(depth, f, javaMethod);
@@ -241,7 +222,6 @@ public class CallGraphDialog extends GraphDialog {
if (depth >= calleeDepthLimit) {
return;
}
-
List used = javaMethod.getUnresolvedUsed();
// add "calls" relationships
@@ -250,7 +230,6 @@ public class CallGraphDialog extends GraphDialog {
if (name == null) {
continue;
}
-
int nodeID = addNode(f, callee);
addEdge(f, methodToNodeID.get(javaMethod), nodeID);
}
@@ -260,7 +239,9 @@ public class CallGraphDialog extends GraphDialog {
return addNode(f, method, "");
}
- // Add a node representing method to the graph in f. Returns the ID of the new node
+ /**
+ * Add a node representing method to the graph in f. Returns the ID of the new node
+ */
private int addNode(Formatter f, JavaMethod method, String extra) {
int nodeID;
if (methodToNodeID.containsKey(method)) {
@@ -270,14 +251,11 @@ public class CallGraphDialog extends GraphDialog {
nextNodeID++;
methodToNodeID.put(method, nodeID);
}
-
String name = DotGraphUtils.methodFormatName(method, longNames);
f.format("Node_%d [ label=\"{%s}\" %s]\n", nodeID, UiUtils.toDotNodeName(name), extra);
-
if (javaMethod.callsSelf()) {
addEdge(f, nodeID, nodeID);
}
-
return nodeID;
}
@@ -285,7 +263,9 @@ public class CallGraphDialog extends GraphDialog {
return addNode(f, method, "");
}
- // Add a node representing an unresolved method to the graph in f. Returns the ID of the new node
+ /**
+ * Add a node representing an unresolved method to the graph in f. Returns the ID of the new node
+ */
private int addNode(Formatter f, IMethodRef method, String extra) {
int nodeID;
if (unresolvedMethodToNodeID.containsKey(method)) {
@@ -315,28 +295,4 @@ public class CallGraphDialog extends GraphDialog {
edges.add(edge);
}
}
-
- private static class Edge {
- public int source;
- public int dest;
-
- public Edge(int source, int dest) {
- this.source = source;
- this.dest = dest;
- }
-
- @Override
- public boolean equals(Object otherObject) {
- if (!(otherObject instanceof Edge)) {
- return false;
- }
- Edge other = (Edge) otherObject;
- return (this.source == other.source) && (this.dest == other.dest);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(source, dest);
- }
- }
}
diff --git a/jadx-gui/src/main/java/jadx/gui/ui/dialog/ClassInheritanceGraphDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/graphs/ClassInheritanceGraphDialog.java
similarity index 96%
rename from jadx-gui/src/main/java/jadx/gui/ui/dialog/ClassInheritanceGraphDialog.java
rename to jadx-gui/src/main/java/jadx/gui/ui/graphs/ClassInheritanceGraphDialog.java
index 08446eaee..d7a19ed0b 100644
--- a/jadx-gui/src/main/java/jadx/gui/ui/dialog/ClassInheritanceGraphDialog.java
+++ b/jadx-gui/src/main/java/jadx/gui/ui/graphs/ClassInheritanceGraphDialog.java
@@ -1,4 +1,4 @@
-package jadx.gui.ui.dialog;
+package jadx.gui.ui.graphs;
import java.awt.BorderLayout;
import java.awt.Color;
@@ -15,9 +15,6 @@ import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
import com.android.apksig.internal.util.Pair;
import jadx.core.dex.attributes.AType;
@@ -34,13 +31,11 @@ import jadx.gui.utils.UiUtils;
import jadx.gui.utils.layout.WrapLayout;
public class ClassInheritanceGraphDialog extends GraphDialog {
-
private static final long serialVersionUID = 938883901412562913L;
- private static final Logger LOG = LoggerFactory.getLogger(ClassInheritanceGraphDialog.class);
-
private static final String FONT = "fontname=\"Courier\" fontsize=12";
- private ClassNode cls;
+
+ private final ClassNode cls;
private boolean longNames = false;
private boolean overrides = false;
@@ -53,6 +48,7 @@ public class ClassInheritanceGraphDialog extends GraphDialog {
this.cls = cls;
}
+ @Override
public JMenuBar addMenuBar() {
JMenuBar menuBar = super.addMenuBar();
@@ -85,12 +81,9 @@ public class ClassInheritanceGraphDialog extends GraphDialog {
}
public static void open(MainWindow window, JClass node) {
-
ClassNode cls = node.getCls().getClassNode();
-
ClassInheritanceGraphDialog graphDialog = new ClassInheritanceGraphDialog(window, cls);
graphDialog.addMenuBar();
-
graphDialog.setVisible(true);
graphDialog.reload();
}
@@ -103,9 +96,6 @@ public class ClassInheritanceGraphDialog extends GraphDialog {
}
private String generateGraph(ClassNode rootClass) {
- StringBuilder sb = new StringBuilder();
-
- ClassNode cls = rootClass;
objectToNodeID = new HashMap<>();
@@ -126,8 +116,8 @@ public class ClassInheritanceGraphDialog extends GraphDialog {
themeHighlight.getBlue());
String shadeColor = String.format("fillcolor=\"#%02x%02x%02x\"", themeShade.getRed(), themeShade.getGreen(), themeShade.getBlue());
+ StringBuilder sb = new StringBuilder();
try (Formatter f = new Formatter(sb)) {
-
// graph header
f.format("digraph G {\n");
f.format("%s\n", bgColor);
@@ -135,11 +125,9 @@ public class ClassInheritanceGraphDialog extends GraphDialog {
f.format("edge[arrowtail=\"onormal\" arrowhead=\"onormal\" %s %s %s]\n", FONT, fontColor, lineColor);
// add nodes
- processClass(f, cls, highlightColor);
-
+ processClass(f, rootClass, highlightColor);
// close graph
f.format("}");
-
return f.toString();
}
}
@@ -175,7 +163,6 @@ public class ClassInheritanceGraphDialog extends GraphDialog {
// add superclass relationship
ArgType superClass = cls.getSuperClass();
-
if (superClass != ArgType.OBJECT) {
int superClsID;
cls = cls.root().resolveClass(superClass);
@@ -185,7 +172,6 @@ public class ClassInheritanceGraphDialog extends GraphDialog {
} else {
superClsID = addNode(f, superClass);
}
-
f.format("Node_%d -> Node_%d [label=\"extends\" ]\n", classID, superClsID);
}
return classID;
@@ -205,14 +191,11 @@ public class ClassInheritanceGraphDialog extends GraphDialog {
nextNodeID++;
objectToNodeID.put(cls, nodeID);
}
-
if (cls.getAccessFlags().isInterface()) {
extra += " style=\"dashed, filled\"";
}
-
String name = DotGraphUtils.classFormatName(cls, longNames);
f.format("Node_%d [ label=\"{%s\\ ", nodeID, UiUtils.toDotNodeName(name));
-
if (overrides) {
f.format("|");
List> table = new ArrayList<>();
@@ -227,30 +210,24 @@ public class ClassInheritanceGraphDialog extends GraphDialog {
String baseClassName = DotGraphUtils.classFormatName(baseMthDetails.getMethodInfo().getDeclClass(), longNames);
details.format("%s, ", baseClassName);
}
-
String detailsString = details.toString();
-
// Remove trailing ', '
detailsString = detailsString.substring(0, detailsString.length() - 2);
-
table.add(Pair.of(methodName, detailsString));
details.close();
}
}
}
-
if (!table.isEmpty()) {
int longestLength = table.stream().map(Pair::getFirst).map(String::length).max((a, b) -> a - b).get();
for (Pair entry : table) {
f.format("%-" + longestLength + "s %s\\l", entry.getFirst(), entry.getSecond());
}
-
} else {
f.format("No overrides.");
}
}
f.format("}\" %s]\n", extra);
-
return nodeID;
}
@@ -268,15 +245,11 @@ public class ClassInheritanceGraphDialog extends GraphDialog {
nextNodeID++;
objectToNodeID.put(argType, nodeID);
}
-
Color themeOutOfFocus = UIManager.getColor("Component.disabledBorderColor");
String outOfFocus =
String.format("color=\"#%02x%02x%02x\"", themeOutOfFocus.getRed(), themeOutOfFocus.getGreen(), themeOutOfFocus.getBlue());
-
String name = DotGraphUtils.interfaceFormatName(argType, cls, longNames);
f.format("Node_%d [ label=\"{%s}\" %s %s]\n", nodeID, UiUtils.toDotNodeName(name), outOfFocus, extra);
-
return nodeID;
}
-
}
diff --git a/jadx-gui/src/main/java/jadx/gui/ui/dialog/ClassMethodGraphDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/graphs/ClassMethodGraphDialog.java
similarity index 75%
rename from jadx-gui/src/main/java/jadx/gui/ui/dialog/ClassMethodGraphDialog.java
rename to jadx-gui/src/main/java/jadx/gui/ui/graphs/ClassMethodGraphDialog.java
index ce37d5bda..5cc3e0d8a 100644
--- a/jadx-gui/src/main/java/jadx/gui/ui/dialog/ClassMethodGraphDialog.java
+++ b/jadx-gui/src/main/java/jadx/gui/ui/graphs/ClassMethodGraphDialog.java
@@ -1,4 +1,4 @@
-package jadx.gui.ui.dialog;
+package jadx.gui.ui.graphs;
import java.awt.BorderLayout;
import java.awt.Color;
@@ -9,7 +9,6 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
-import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
@@ -31,16 +30,16 @@ import jadx.gui.utils.UiUtils;
import jadx.gui.utils.layout.WrapLayout;
public class ClassMethodGraphDialog extends GraphDialog {
-
private static final long serialVersionUID = -850803763322590708L;
private static final String FONT = "fontname=\"Courier\" fontsize=12";
- private int callerDepthLimit = 10;
+ private static final int CALLER_DEPTH_LIMIT = 10;
+
+ private final ClassNode cls;
private int nextNodeID = 0;
private Map methodToNodeID;
private Set edges;
private List javaMethods = Collections.emptyList();
- private ClassNode cls;
private boolean longNames = false;
public ClassMethodGraphDialog(MainWindow mainWindow, ClassNode cls) {
@@ -48,6 +47,7 @@ public class ClassMethodGraphDialog extends GraphDialog {
this.cls = cls;
}
+ @Override
public JMenuBar addMenuBar() {
JMenuBar menuBar = super.addMenuBar();
@@ -71,41 +71,28 @@ public class ClassMethodGraphDialog extends GraphDialog {
}
public static void open(MainWindow window, JClass node) {
-
ClassNode cls = node.getCls().getClassNode();
ClassMethodGraphDialog graphDialog = new ClassMethodGraphDialog(window, cls);
graphDialog.addMenuBar();
-
graphDialog.setVisible(true);
graphDialog.reload();
}
public void reload() {
- SwingUtilities.invokeLater(() -> {
- String graph = generateGraph(cls);
- getPanel().setGraph(graph);
- });
+ SwingUtilities.invokeLater(() -> getPanel().setGraph(generateGraph(cls)));
}
private String generateGraph(ClassNode classNode) {
- StringBuilder sb = new StringBuilder();
-
Color themeBackground = UIManager.getColor("Panel.background");
Color themeForeground = UIManager.getColor("Label.foreground");
Color themeShade = UIManager.getColor("TextArea.background");
- String bgColor =
- String.format("bgcolor=\"#%02x%02x%02x\"", themeBackground.getRed(), themeBackground.getGreen(),
- themeBackground.getBlue());
- String lineColor =
- String.format("color=\"#%02x%02x%02x\"", themeForeground.getRed(), themeForeground.getGreen(), themeForeground.getBlue());
- String fontColor =
- String.format("fontcolor=\"#%02x%02x%02x\"", themeForeground.getRed(), themeForeground.getGreen(),
- themeForeground.getBlue());
- String shadeColor = String.format("fillcolor=\"#%02x%02x%02x\"", themeShade.getRed(), themeShade.getGreen(), themeShade.getBlue());
-
- try (Formatter f = new Formatter(sb)) {
+ String bgColor = "bgcolor=" + formatColor(themeBackground);
+ String lineColor = "color=" + formatColor(themeForeground);
+ String fontColor = "fontcolor=" + formatColor(themeForeground);
+ String shadeColor = "fillcolor=" + formatColor(themeShade);
+ try (Formatter f = new Formatter(new StringBuilder())) {
// graph header
f.format("digraph G {\n");
f.format("%s\n", bgColor);
@@ -117,45 +104,39 @@ public class ClassMethodGraphDialog extends GraphDialog {
edges = new HashSet<>();
List methods = classNode.getMethods();
- javaMethods = methods.stream().map(method -> method.getJavaNode()).collect(Collectors.toList());
-
+ javaMethods = methods.stream().map(MethodNode::getJavaNode).collect(Collectors.toList());
for (JavaMethod javaMethod : javaMethods) {
-
addNode(f, javaMethod);
-
// add caller relationships
addCallers(0, f, javaMethod);
}
-
// close graph
f.format("}");
-
return f.toString();
}
}
+ private static String formatColor(Color color) {
+ return String.format("\"#%02x%02x%02x\"", color.getRed(), color.getGreen(), color.getBlue());
+ }
+
private void addCallers(int depth, Formatter f, JavaMethod javaMethod) {
- if (depth >= callerDepthLimit) {
+ if (depth >= CALLER_DEPTH_LIMIT) {
return;
}
-
List uses = javaMethod.getUseIn();
-
// add "calls" relationships
for (JavaNode node : uses) {
if (!(node instanceof JavaMethod)) {
continue;
}
JavaMethod caller = (JavaMethod) node;
-
// Do not process callers that are not methods from the class
if (!javaMethods.contains(node)) {
continue;
}
-
int nodeID = addNode(f, caller);
addEdge(f, nodeID, methodToNodeID.get(javaMethod));
-
addCallers(depth + 1, f, caller);
}
}
@@ -170,14 +151,11 @@ public class ClassMethodGraphDialog extends GraphDialog {
nextNodeID++;
methodToNodeID.put(method, nodeID);
}
-
String name = DotGraphUtils.methodFormatName(method, longNames);
f.format("Node_%d [ label=\"{%s}\"]\n", nodeID, UiUtils.toDotNodeName(name));
-
if (method.callsSelf()) {
addEdge(f, nodeID, nodeID);
}
-
return nodeID;
}
@@ -189,28 +167,4 @@ public class ClassMethodGraphDialog extends GraphDialog {
edges.add(edge);
}
}
-
- private static class Edge {
- public int source;
- public int dest;
-
- public Edge(int source, int dest) {
- this.source = source;
- this.dest = dest;
- }
-
- @Override
- public boolean equals(Object otherObject) {
- if (!(otherObject instanceof Edge)) {
- return false;
- }
- Edge other = (Edge) otherObject;
- return (this.source == other.source) && (this.dest == other.dest);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(source, dest);
- }
- }
}
diff --git a/jadx-gui/src/main/java/jadx/gui/ui/graphs/ControlFlowGraphDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/graphs/ControlFlowGraphDialog.java
new file mode 100644
index 000000000..0bdb24133
--- /dev/null
+++ b/jadx-gui/src/main/java/jadx/gui/ui/graphs/ControlFlowGraphDialog.java
@@ -0,0 +1,171 @@
+package jadx.gui.ui.graphs;
+
+import java.awt.FlowLayout;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.swing.Box;
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+import javax.swing.JMenuBar;
+import javax.swing.JPanel;
+
+import org.jetbrains.annotations.Nullable;
+
+import jadx.core.dex.nodes.MethodNode;
+import jadx.core.dex.visitors.IDexTreeVisitor;
+import jadx.core.utils.DotGraphUtils;
+import jadx.gui.treemodel.JMethod;
+import jadx.gui.ui.MainWindow;
+import jadx.gui.utils.NLS;
+import jadx.gui.utils.UiUtils;
+import jadx.gui.utils.layout.WrapLayout;
+
+public class ControlFlowGraphDialog extends GraphDialog {
+ private static final long serialVersionUID = -68749445239697710L;
+
+ public static void open(MainWindow window, JMethod jMth) {
+ ControlFlowGraphDialog graphDialog = new ControlFlowGraphDialog(window, jMth);
+ graphDialog.addMenuBar();
+ graphDialog.setVisible(true);
+ graphDialog.usePreset(GraphPreset.NORMAL);
+ }
+
+ private static final String[] PRESETS_NSL = NLS.str("graph_viewer.cfg.preset_names").split("\\|");
+
+ private enum GraphPreset {
+ RAW(PRESETS_NSL[0], true, false, "SSATransform"),
+ NORMAL(PRESETS_NSL[1], false, false, "RegionMakerVisitor"),
+ REGION(PRESETS_NSL[2], false, true, "PrepareForCodeGen");
+
+ final boolean useRawInsns;
+ final boolean useRegions;
+ final String beforePass;
+ final String nlsStr;
+
+ GraphPreset(String nlsStr, boolean useRawInsns, boolean useRegions, String beforePass) {
+ this.nlsStr = nlsStr;
+ this.useRawInsns = useRawInsns;
+ this.useRegions = useRegions;
+ this.beforePass = beforePass;
+ }
+
+ @Override
+ public String toString() {
+ return nlsStr;
+ }
+ }
+
+ private final MethodNode mth;
+ private @Nullable GraphPreset graphPreset;
+ private JComboBox presetsCB;
+ private JComboBox passesCB;
+ private String[] passNames;
+ private int currentPassIdx;
+
+ private ControlFlowGraphDialog(MainWindow mainWindow, JMethod jMth) {
+ super(mainWindow);
+ mth = jMth.getJavaMethod().getMethodNode();
+ String mthName = DotGraphUtils.methodFormatName(jMth.getJavaMethod(), false);
+ setTitle(String.format("%s: %s", NLS.str("graph_viewer.cfg.title"), mthName));
+ }
+
+ private void usePreset(@Nullable GraphPreset graphPreset) {
+ if (graphPreset == null || this.graphPreset == graphPreset) {
+ return;
+ }
+ this.graphPreset = graphPreset;
+ this.currentPassIdx = selectPassBefore(graphPreset.beforePass);
+ presetsCB.setSelectedItem(graphPreset);
+ passesCB.setSelectedItem(passNames[currentPassIdx]);
+ reloadGraph();
+ }
+
+ private int selectPassBefore(String beforePass) {
+ List passes = getPassList();
+ for (int i = 1, passesSize = passes.size(); i < passesSize; i++) {
+ if (passes.get(i).getName().equals(beforePass)) {
+ return i - 1;
+ }
+ }
+ return passes.size() - 1;
+ }
+
+ @Override
+ public JMenuBar addMenuBar() {
+ JMenuBar menuBar = super.addMenuBar();
+ presetsCB = new JComboBox<>(GraphPreset.values());
+ presetsCB.addActionListener(e -> usePreset((GraphPreset) presetsCB.getSelectedItem()));
+
+ List passList = getPassList();
+ int size = passList.size();
+ Map passMap = new HashMap<>();
+ passNames = new String[size];
+ for (int i = 0; i < size; i++) {
+ IDexTreeVisitor pass = passList.get(i);
+ String name = i + ": " + pass.getName();
+ passMap.put(name, i);
+ passNames[i] = name;
+ }
+ passesCB = new JComboBox<>(passNames);
+ passesCB.addActionListener(e -> {
+ String newValue = (String) passesCB.getSelectedItem();
+ if (newValue != null) {
+ Integer newIdx = passMap.get(newValue);
+ if (newIdx != null && newIdx != currentPassIdx) {
+ currentPassIdx = newIdx;
+ reloadGraph();
+ }
+ }
+ });
+
+ JPanel menuBarPanel = new JPanel();
+ menuBarPanel.setOpaque(false);
+ menuBarPanel.setLayout(new WrapLayout(FlowLayout.LEFT));
+ menuBarPanel.add(new JLabel(NLS.str("graph_viewer.cfg.preset_selector_label")));
+ menuBarPanel.add(presetsCB);
+ menuBarPanel.add(Box.createHorizontalBox());
+ menuBarPanel.add(new JLabel(NLS.str("graph_viewer.cfg.pass_selector_label")));
+ menuBarPanel.add(passesCB);
+ menuBar.add(menuBarPanel);
+ return menuBar;
+ }
+
+ @Override
+ protected void disableMenu() {
+ // don't disable menu if invalid combination of insn type and pass selected
+ }
+
+ private void reloadGraph() {
+ UiUtils.uiRun(() -> {
+ String graph = generateGraph();
+ if (graph != null) {
+ getPanel().setGraph(graph);
+ } else {
+ getPanel().invalidateImage(graphError(NLS.str("graph_viewer.default_error")));
+ }
+ });
+ }
+
+ private @Nullable String generateGraph() {
+ GraphPreset preset = graphPreset;
+ if (preset == null) {
+ return null;
+ }
+ try {
+ IDexTreeVisitor pass = getPassList().get(currentPassIdx);
+ boolean success = mth.root().getProcessClasses().processMethodToVisitor(mth, pass);
+ if (!success) {
+ return null;
+ }
+ return new DotGraphUtils(preset.useRegions, preset.useRawInsns).dumpToString(mth);
+ } finally {
+ mth.unload();
+ }
+ }
+
+ private List getPassList() {
+ return mth.root().getProcessClasses().getPasses();
+ }
+}
diff --git a/jadx-gui/src/main/java/jadx/gui/ui/graphs/Edge.java b/jadx-gui/src/main/java/jadx/gui/ui/graphs/Edge.java
new file mode 100644
index 000000000..052ae5165
--- /dev/null
+++ b/jadx-gui/src/main/java/jadx/gui/ui/graphs/Edge.java
@@ -0,0 +1,33 @@
+package jadx.gui.ui.graphs;
+
+class Edge {
+ private final int source;
+ private final int dest;
+
+ public Edge(int source, int dest) {
+ this.source = source;
+ this.dest = dest;
+ }
+
+ public int getDest() {
+ return dest;
+ }
+
+ public int getSource() {
+ return source;
+ }
+
+ @Override
+ public boolean equals(Object otherObject) {
+ if (!(otherObject instanceof Edge)) {
+ return false;
+ }
+ Edge other = (Edge) otherObject;
+ return this.source == other.source && this.dest == other.dest;
+ }
+
+ @Override
+ public int hashCode() {
+ return source + 31 * dest;
+ }
+}
diff --git a/jadx-gui/src/main/java/jadx/gui/ui/graphs/GraphDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/graphs/GraphDialog.java
new file mode 100644
index 000000000..01bc2cdd2
--- /dev/null
+++ b/jadx-gui/src/main/java/jadx/gui/ui/graphs/GraphDialog.java
@@ -0,0 +1,170 @@
+package jadx.gui.ui.graphs;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.event.MouseEvent;
+import java.io.File;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.List;
+
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JFileChooser;
+import javax.swing.JFrame;
+import javax.swing.JMenuBar;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JTextArea;
+import javax.swing.WindowConstants;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import jadx.core.utils.ListUtils;
+import jadx.gui.ui.MainWindow;
+import jadx.gui.ui.filedialog.FileDialogWrapper;
+import jadx.gui.ui.filedialog.FileOpenMode;
+import jadx.gui.utils.NLS;
+import jadx.gui.utils.UiUtils;
+import jadx.gui.utils.layout.WrapLayout;
+import jadx.gui.utils.ui.MouseListenerAdapter;
+
+public abstract class GraphDialog extends JFrame {
+ private static final long serialVersionUID = 5840390965763493590L;
+
+ private static final Logger LOG = LoggerFactory.getLogger(GraphDialog.class);
+ private static final Dimension MIN_WINDOW_SIZE = new Dimension(800, 500);
+
+ private final MainWindow mainWindow;
+ private final GraphPanel panel;
+
+ private JMenuBar menuBar = null;
+
+ public static JTextArea graphError(String errorMessage) {
+ JTextArea errorText = new JTextArea();
+ errorText.setText(errorMessage);
+ errorText.setVisible(true);
+ errorText.setEditable(false);
+ errorText.setLineWrap(false);
+ return errorText;
+ }
+
+ public static JTextArea graphError(Exception error) {
+ JTextArea errorText = new JTextArea();
+ StringWriter stringWriter = new StringWriter();
+ PrintWriter printWriter = new PrintWriter(stringWriter);
+ stringWriter.write(NLS.str("graph_viewer.default_error"));
+ stringWriter.write(": ");
+ error.printStackTrace(printWriter);
+ errorText.setText(stringWriter.toString());
+ errorText.setVisible(true);
+ errorText.setEditable(false);
+ errorText.setLineWrap(false);
+ return errorText;
+ }
+
+ protected GraphDialog(MainWindow mainWindow) {
+ this(mainWindow, NLS.str("graph_viewer.default_title"));
+ }
+
+ public GraphDialog(MainWindow mainWindow, String title) {
+ super(title);
+ this.mainWindow = mainWindow;
+ setMinimumSize(MIN_WINDOW_SIZE);
+ setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
+ UiUtils.addEscapeShortCutToDispose(this);
+ setLocationRelativeTo(null);
+ loadWindowPos();
+
+ panel = new GraphPanel(this);
+ panel.setFocusable(true);
+ panel.addMouseListener(new MouseListenerAdapter() {
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ requestFocusInWindow();
+ }
+ });
+ setLayout(new BorderLayout());
+ add(panel, BorderLayout.CENTER);
+ }
+
+ public JMenuBar addMenuBar() {
+ JMenuBar menuBar = new JMenuBar();
+ menuBar.setLayout(new WrapLayout(FlowLayout.LEFT));
+ add(menuBar, BorderLayout.PAGE_START);
+ this.menuBar = menuBar;
+
+ JButton saveButton = new JButton(NLS.str("graph_viewer.save_graph"));
+ saveButton.setEnabled(false);
+ saveButton.addActionListener(e -> {
+ try {
+ FileDialogWrapper fileDialog = new FileDialogWrapper(mainWindow, FileOpenMode.CUSTOM_SAVE);
+ fileDialog.setTitle(NLS.str("graph_viewer.save_graph"));
+ fileDialog.setFileExtList(Collections.singletonList("svg"));
+ fileDialog.setSelectionMode(JFileChooser.FILES_ONLY);
+ List savePaths = fileDialog.show();
+ if (!savePaths.isEmpty()) {
+ File saveFile = ListUtils.first(savePaths).toFile();
+ getPanel().exportSVG(saveFile);
+ }
+ } catch (Exception ex) {
+ LOG.error("Failed to save file: ", ex);
+ JOptionPane.showMessageDialog(this, NLS.str("graph_viewer.file_failure"),
+ NLS.str("graph_viewer.file_failure"),
+ JOptionPane.INFORMATION_MESSAGE);
+ }
+ });
+ JPanel menuBarPanel = new JPanel();
+ menuBarPanel.setOpaque(false);
+ menuBarPanel.add(saveButton);
+ menuBar.add(menuBarPanel);
+ return menuBar;
+ }
+
+ protected void enableMenu() {
+ JMenuBar menu = this.menuBar;
+ setAllEnabled(true, menu);
+ }
+
+ protected void disableMenu() {
+ JMenuBar menu = this.menuBar;
+ setAllEnabled(false, menu);
+ }
+
+ private void setAllEnabled(boolean isEnabled, JComponent component) {
+ component.setEnabled(isEnabled);
+ Component[] components = component.getComponents();
+ for (Component subComponent : components) {
+ if (subComponent instanceof JComponent) {
+ setAllEnabled(isEnabled, (JComponent) subComponent);
+ } else {
+ subComponent.setEnabled(isEnabled);
+ }
+ }
+ }
+
+ public void loadWindowPos() {
+ if (!mainWindow.getSettings().loadWindowPos(this)) {
+ setPreferredSize(MIN_WINDOW_SIZE);
+ }
+ }
+
+ @Override
+ public void dispose() {
+ try {
+ mainWindow.getSettings().saveWindowPos(this);
+ } catch (Exception e) {
+ LOG.warn("Failed to save window size and position", e);
+ }
+ super.dispose();
+ }
+
+ protected GraphPanel getPanel() {
+ return this.panel;
+ }
+}
diff --git a/jadx-gui/src/main/java/jadx/gui/ui/graphs/GraphPanel.java b/jadx-gui/src/main/java/jadx/gui/ui/graphs/GraphPanel.java
new file mode 100644
index 000000000..e0df48c31
--- /dev/null
+++ b/jadx-gui/src/main/java/jadx/gui/ui/graphs/GraphPanel.java
@@ -0,0 +1,201 @@
+package jadx.gui.ui.graphs;
+
+import java.awt.*;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseWheelEvent;
+import java.awt.geom.AffineTransform;
+import java.awt.image.BufferedImage;
+import java.io.*;
+import java.net.URI;
+
+import javax.swing.*;
+
+import org.jetbrains.annotations.Nullable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.kitfox.svg.SVGDiagram;
+import com.kitfox.svg.SVGUniverse;
+
+import guru.nidi.graphviz.engine.Format;
+import guru.nidi.graphviz.engine.Graphviz;
+import guru.nidi.graphviz.engine.Renderer;
+
+import jadx.core.utils.exceptions.JadxRuntimeException;
+
+import static java.awt.RenderingHints.*;
+
+public class GraphPanel extends JPanel {
+ private static final Logger LOG = LoggerFactory.getLogger(GraphPanel.class);
+
+ private final GraphDialog parentDialog;
+ private final Dimension fullImageSize = new Dimension();
+ private final double minimumScale = 0.1;
+ private final double maximumScale = 10.0;
+ private double scale = 1.0;
+ private double translateX = 0;
+ private double translateY = 0;
+ private Point lastDragPoint = null;
+ private @Nullable BufferedImage image;
+ private Renderer renderer;
+ private SVGDiagram svgDiagram;
+
+ public GraphPanel(GraphDialog parentDialog) {
+ this.parentDialog = parentDialog;
+ MouseAdapter ma = new GraphPanelMouseAdapter();
+ addMouseListener(ma);
+ addMouseMotionListener(ma);
+ addMouseWheelListener(ma);
+ }
+
+ @Override
+ protected void paintComponent(Graphics g) {
+ super.paintComponent(g);
+ if (image != null) {
+ Graphics2D g2d = (Graphics2D) g;
+ AffineTransform transform = new AffineTransform();
+ transform.translate(translateX * scale, translateY * scale);
+ g2d.drawImage(image, transform, null);
+ }
+ }
+
+ public void setGraph(String dotString) {
+ try {
+ init(dotString);
+ } catch (Exception e) {
+ LOG.error("Error parsing DOT string", e);
+ invalidateImage(GraphDialog.graphError(e));
+ }
+ }
+
+ public void exportSVG(File svgImage) {
+ try {
+ renderer.toFile(svgImage);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void init(String dotString) {
+ image = null;
+ byte[] bytes;
+ try (ByteArrayOutputStream bout = new ByteArrayOutputStream()) {
+ renderer = Graphviz.fromString(dotString).width(getWidth()).render(Format.SVG);
+ renderer.toOutputStream(bout);
+ bytes = bout.toByteArray();
+ } catch (Exception e) {
+ throw new JadxRuntimeException("Failed to render graph", e);
+ }
+ try (InputStream in = new ByteArrayInputStream(bytes)) {
+ SVGUniverse universe = new SVGUniverse();
+ URI uri = universe.loadSVG(in, "//graph/");
+ svgDiagram = universe.getDiagram(uri);
+ svgDiagram.setIgnoringClipHeuristic(true);
+ fullImageSize.setSize(svgDiagram.getWidth(), svgDiagram.getHeight());
+ } catch (Exception e) {
+ throw new JadxRuntimeException("Failed to process SVG image", e);
+ }
+ parentDialog.enableMenu();
+ removeAll();
+ SwingUtilities.invokeLater(() -> {
+ double width = getWidth();
+ double height = getHeight();
+ if (scale == 1.0) {
+ // fit in window on dialog open (keep on graph changes)
+ scale = Math.min(width / (double) fullImageSize.width, height / (double) fullImageSize.height);
+ }
+ renderGraphScaled();
+ if (image == null) {
+ return;
+ }
+ // center image in window
+ translateX = (width / 2. - fullImageSize.width * scale / 2.) / scale;
+ translateY = (height / 2. - fullImageSize.height * scale / 2.) / scale;
+ repaint();
+ });
+ }
+
+ private void renderGraphScaled() {
+ try {
+ if (fullImageSize.width * scale * fullImageSize.height * scale >= Integer.MAX_VALUE) {
+ scale = maximumScale;
+ }
+ image = new BufferedImage(
+ (int) (fullImageSize.width * scale),
+ (int) (fullImageSize.height * scale),
+ BufferedImage.TYPE_INT_ARGB);
+ Graphics2D imgG2d = image.createGraphics();
+ configGraphics(imgG2d);
+ AffineTransform transform = new AffineTransform();
+ transform.scale(scale, scale);
+ imgG2d.setTransform(transform);
+ svgDiagram.render(imgG2d);
+ } catch (Exception e) {
+ LOG.error("Graph render failed: ", e);
+ invalidateImage(GraphDialog.graphError(e));
+ }
+ }
+
+ private void configGraphics(Graphics2D graphics) {
+ graphics.setRenderingHint(KEY_ALPHA_INTERPOLATION, VALUE_ALPHA_INTERPOLATION_QUALITY);
+ graphics.setRenderingHint(KEY_ANTIALIASING, VALUE_ANTIALIAS_ON);
+ graphics.setRenderingHint(KEY_COLOR_RENDERING, VALUE_COLOR_RENDER_QUALITY);
+ graphics.setRenderingHint(KEY_INTERPOLATION, VALUE_INTERPOLATION_BICUBIC);
+ graphics.setRenderingHint(KEY_RENDERING, VALUE_RENDER_QUALITY);
+ graphics.setRenderingHint(KEY_TEXT_ANTIALIASING, VALUE_TEXT_ANTIALIAS_ON);
+ }
+
+ public void invalidateImage(JTextArea errorMsg) {
+ removeAll();
+ add(errorMsg);
+ image = null;
+ parentDialog.disableMenu();
+ revalidate();
+ repaint();
+ }
+
+ private class GraphPanelMouseAdapter extends MouseAdapter {
+ @Override
+ public void mousePressed(MouseEvent e) {
+ lastDragPoint = e.getPoint();
+ }
+
+ @Override
+ public void mouseDragged(MouseEvent e) {
+ if (image != null) {
+ Point p = e.getPoint();
+ translateX += (p.x - lastDragPoint.x) / scale;
+ translateY += (p.y - lastDragPoint.y) / scale;
+ lastDragPoint = p;
+ repaint();
+ }
+ }
+
+ @Override
+ public void mouseWheelMoved(MouseWheelEvent e) {
+ if (image != null) {
+ double prevScale = scale;
+ scale *= Math.pow(1.1, -e.getWheelRotation());
+ if (scale > maximumScale) {
+ scale = maximumScale;
+ }
+ if (scale < minimumScale) {
+ scale = minimumScale;
+ }
+ if (scale != prevScale) {
+ Point p = e.getPoint();
+ double px = (p.x - translateX * prevScale) / prevScale;
+ double py = (p.y - translateY * prevScale) / prevScale;
+ translateX = p.x / scale - px;
+ translateY = p.y / scale - py;
+ renderGraphScaled();
+ if (image == null) {
+ return;
+ }
+ repaint();
+ }
+ }
+ }
+ }
+}
diff --git a/jadx-gui/src/main/java/jadx/gui/utils/ui/MouseListenerAdapter.java b/jadx-gui/src/main/java/jadx/gui/utils/ui/MouseListenerAdapter.java
new file mode 100644
index 000000000..d688f7c1b
--- /dev/null
+++ b/jadx-gui/src/main/java/jadx/gui/utils/ui/MouseListenerAdapter.java
@@ -0,0 +1,26 @@
+package jadx.gui.utils.ui;
+
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+
+public abstract class MouseListenerAdapter implements MouseListener {
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ }
+
+ @Override
+ public void mousePressed(MouseEvent e) {
+ }
+
+ @Override
+ public void mouseReleased(MouseEvent e) {
+ }
+
+ @Override
+ public void mouseEntered(MouseEvent e) {
+ }
+
+ @Override
+ public void mouseExited(MouseEvent e) {
+ }
+}
diff --git a/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties b/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties
index aa75bc3f2..efdc1f00c 100644
--- a/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties
+++ b/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties
@@ -375,19 +375,14 @@ popup.go_to_declaration=Zur Erklärung gehen
popup.exclude=Ausschließen
popup.exclude_packages=Pakete ausschließen
popup.convert_number=Conversion als Kommentar hinzufügen
+#popup.convert_number_tooltip=Add other number formats as comment
#popup.view_call_graph=View call graph
#popup.view_call_graph_description=Show call chains to this function
#popup.view_class_graph=View inheritance graph
#popup.view_class_graph_description=Show inheritance tree for this class
#popup.view_class_method_graph=View methods graph
#popup.view_class_method_graph_description=Show all methods for this class
-#popup.cfg_submenu=View control flow graph
-#popup.view_cfg=Regular
-#popup.view_cfg_description=Show regular control flow graph for this function (enable in File->Preferences->Other)
-#popup.view_raw_cfg=Raw
-#popup.view_raw_cfg_description=Show control flow graph with raw instructions for this function (enable in File->Preferences->Other)
-#popup.view_region_cfg=Region
-#popup.view_region_cfg_description=Show regioned control flow graph for this function (enable in File->Preferences->Other)
+#popup.view_cfg=View method control flow graph
popup.add_comment=Kommentar
popup.update_comment=Kommentar aktualisieren
popup.search_comment=Kommentar suchen
@@ -547,9 +542,6 @@ action_category.plugin_script=Plugin-Skript
#graph_viewer.callee_depth=Down depth
#graph_viewer.caller_depth=Up depth
#graph_viewer.default_error=Failed to view graph
-#graph_viewer.file_not_found_error=Failed to load graph file
-#graph_viewer.image_too_large=Failed to render graph: graph too large
-#graph_viewer.image_too_small=Failed to render graph: graph too small
#graph_viewer.file_failure=Error in File Operation
#graph_viewer.save_graph=Save graph
@@ -558,3 +550,6 @@ action_category.plugin_script=Plugin-Skript
#graph_viewer.call_graph.title=Call Graph
#graph_viewer.inheritance_graph.title=Inheritance Graph
#graph_viewer.cfg.title=Control Flow Graph
+#graph_viewer.cfg.preset_selector_label=CFG preset
+#graph_viewer.cfg.preset_names=Raw|Normal|Region
+#graph_viewer.cfg.pass_selector_label=View after pass
diff --git a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties
index a060a0e1d..c7b90bd51 100644
--- a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties
+++ b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties
@@ -374,20 +374,15 @@ popup.find_usage=Find Usage
popup.go_to_declaration=Go to declaration
popup.exclude=Exclude
popup.exclude_packages=Exclude packages
-popup.convert_number=Add conversion as comment
+popup.convert_number=Convert number
+popup.convert_number_tooltip=Add other number formats as comment
popup.view_call_graph=View call graph
popup.view_call_graph_description=Show call chains to this function
popup.view_class_graph=View inheritance graph
popup.view_class_graph_description=Show inheritance tree for this class
popup.view_class_method_graph=View methods graph
popup.view_class_method_graph_description=Show all methods for this class
-popup.cfg_submenu=View control flow graph
-popup.view_cfg=Regular
-popup.view_cfg_description=Show regular control flow graph for this function (enable in File->Preferences->Other)
-popup.view_raw_cfg=Raw
-popup.view_raw_cfg_description=Show control flow graph with raw instructions for this function (enable in File->Preferences->Other)
-popup.view_region_cfg=Region
-popup.view_region_cfg_description=Show regioned control flow graph for this function (enable in File->Preferences->Other)
+popup.view_cfg=View method control flow graph
popup.add_comment=Comment
popup.update_comment=Update comment
popup.search_comment=Search comments
@@ -547,9 +542,6 @@ graph_viewer.overrides=Show overrides
graph_viewer.callee_depth=Down depth
graph_viewer.caller_depth=Up depth
graph_viewer.default_error=Failed to view graph
-graph_viewer.file_not_found_error=Failed to load graph file
-graph_viewer.image_too_large=Failed to render graph: graph too large
-graph_viewer.image_too_small=Failed to render graph: graph too small
graph_viewer.file_failure=Error in File Operation
graph_viewer.save_graph=Save graph
@@ -558,3 +550,6 @@ graph_viewer.method_graph.title=Methods Graph
graph_viewer.call_graph.title=Call Graph
graph_viewer.inheritance_graph.title=Inheritance Graph
graph_viewer.cfg.title=Control Flow Graph
+graph_viewer.cfg.preset_selector_label=CFG preset
+graph_viewer.cfg.preset_names=Raw|Normal|Region
+graph_viewer.cfg.pass_selector_label=View after pass
diff --git a/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties b/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties
index 2c4b6c2c6..cbbd7e1ac 100644
--- a/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties
+++ b/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties
@@ -374,20 +374,15 @@ popup.xposed=Copiar como fragmento de xposed
#popup.go_to_declaration=Go to declaration
#popup.exclude=Exclude
#popup.exclude_packages=Exclude packages
-#popup.convert_number=Add conversion as comment
+#popup.convert_number=Convert number
+#popup.convert_number_tooltip=Add other number formats as comment
#popup.view_call_graph=View call graph
#popup.view_call_graph_description=Show call chains to this function
#popup.view_class_graph=View inheritance graph
#popup.view_class_graph_description=Show inheritance tree for this class
#popup.view_class_method_graph=View methods graph
#popup.view_class_method_graph_description=Show all methods for this class
-#popup.cfg_submenu=View control flow graph
-#popup.view_cfg=Regular
-#popup.view_cfg_description=Show regular control flow graph for this function (enable in File->Preferences->Other)
-#popup.view_raw_cfg=Raw
-#popup.view_raw_cfg_description=Show control flow graph with raw instructions for this function (enable in File->Preferences->Other)
-#popup.view_region_cfg=Region
-#popup.view_region_cfg_description=Show regioned control flow graph for this function (enable in File->Preferences->Other)
+#popup.view_cfg=View method control flow graph
#popup.add_comment=Comment
#popup.update_comment=Update comment
#popup.search_comment=Search comments
@@ -547,9 +542,6 @@ certificate.serialPubKeyY=Y
#graph_viewer.callee_depth=Down depth
#graph_viewer.caller_depth=Up depth
#graph_viewer.default_error=Failed to view graph
-#graph_viewer.file_not_found_error=Failed to load graph file
-#graph_viewer.image_too_large=Failed to render graph: graph too large
-#graph_viewer.image_too_small=Failed to render graph: graph too small
#graph_viewer.file_failure=Error in File Operation
#graph_viewer.save_graph=Save graph
@@ -558,3 +550,6 @@ certificate.serialPubKeyY=Y
#graph_viewer.call_graph.title=Call Graph
#graph_viewer.inheritance_graph.title=Inheritance Graph
#graph_viewer.cfg.title=Control Flow Graph
+#graph_viewer.cfg.preset_selector_label=CFG preset
+#graph_viewer.cfg.preset_names=Raw|Normal|Region
+#graph_viewer.cfg.pass_selector_label=View after pass
diff --git a/jadx-gui/src/main/resources/i18n/Messages_id_ID.properties b/jadx-gui/src/main/resources/i18n/Messages_id_ID.properties
index 584621ed1..a605a5f86 100644
--- a/jadx-gui/src/main/resources/i18n/Messages_id_ID.properties
+++ b/jadx-gui/src/main/resources/i18n/Messages_id_ID.properties
@@ -374,20 +374,15 @@ popup.find_usage=Cari Penggunaan
popup.go_to_declaration=Pergi ke Deklarasi
popup.exclude=Kecualikan
popup.exclude_packages=Kecualikan paket
-#popup.convert_number=Add conversion as comment
+#popup.convert_number=Convert number
+#popup.convert_number_tooltip=Add other number formats as comment
#popup.view_call_graph=View call graph
#popup.view_call_graph_description=Show call chains to this function
#popup.view_class_graph=View inheritance graph
#popup.view_class_graph_description=Show inheritance tree for this class
#popup.view_class_method_graph=View methods graph
#popup.view_class_method_graph_description=Show all methods for this class
-#popup.cfg_submenu=View control flow graph
-#popup.view_cfg=Regular
-#popup.view_cfg_description=Show regular control flow graph for this function (enable in File->Preferences->Other)
-#popup.view_raw_cfg=Raw
-#popup.view_raw_cfg_description=Show control flow graph with raw instructions for this function (enable in File->Preferences->Other)
-#popup.view_region_cfg=Region
-#popup.view_region_cfg_description=Show regioned control flow graph for this function (enable in File->Preferences->Other)
+#popup.view_cfg=View method control flow graph
popup.add_comment=Komentar
#popup.update_comment=Update comment
popup.search_comment=Cari komentar
@@ -547,9 +542,6 @@ action_category.plugin_script=Plugin Script
#graph_viewer.callee_depth=Down depth
#graph_viewer.caller_depth=Up depth
#graph_viewer.default_error=Failed to view graph
-#graph_viewer.file_not_found_error=Failed to load graph file
-#graph_viewer.image_too_large=Failed to render graph: graph too large
-#graph_viewer.image_too_small=Failed to render graph: graph too small
#graph_viewer.file_failure=Error in File Operation
#graph_viewer.save_graph=Save graph
@@ -558,3 +550,6 @@ action_category.plugin_script=Plugin Script
#graph_viewer.call_graph.title=Call Graph
#graph_viewer.inheritance_graph.title=Inheritance Graph
#graph_viewer.cfg.title=Control Flow Graph
+#graph_viewer.cfg.preset_selector_label=CFG preset
+#graph_viewer.cfg.preset_names=Raw|Normal|Region
+#graph_viewer.cfg.pass_selector_label=View after pass
diff --git a/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties b/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties
index c0220779c..175325a50 100644
--- a/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties
+++ b/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties
@@ -374,20 +374,15 @@ popup.find_usage=사용 찾기
popup.go_to_declaration=선언문으로 이동
popup.exclude=제외
popup.exclude_packages=패키지 제외
-#popup.convert_number=Add conversion as comment
+#popup.convert_number=Convert number
+#popup.convert_number_tooltip=Add other number formats as comment
#popup.view_call_graph=View call graph
#popup.view_call_graph_description=Show call chains to this function
#popup.view_class_graph=View inheritance graph
#popup.view_class_graph_description=Show inheritance tree for this class
#popup.view_class_method_graph=View methods graph
#popup.view_class_method_graph_description=Show all methods for this class
-#popup.cfg_submenu=View control flow graph
-#popup.view_cfg=Regular
-#popup.view_cfg_description=Show regular control flow graph for this function (enable in File->Preferences->Other)
-#popup.view_raw_cfg=Raw
-#popup.view_raw_cfg_description=Show control flow graph with raw instructions for this function (enable in File->Preferences->Other)
-#popup.view_region_cfg=Region
-#popup.view_region_cfg_description=Show regioned control flow graph for this function (enable in File->Preferences->Other)
+#popup.view_cfg=View method control flow graph
popup.add_comment=주석
#popup.update_comment=Update comment
popup.search_comment=주석 검색
@@ -547,9 +542,6 @@ adb_dialog.starting_debugger=디버거 시작 중 ...
#graph_viewer.callee_depth=Down depth
#graph_viewer.caller_depth=Up depth
#graph_viewer.default_error=Failed to view graph
-#graph_viewer.file_not_found_error=Failed to load graph file
-#graph_viewer.image_too_large=Failed to render graph: graph too large
-#graph_viewer.image_too_small=Failed to render graph: graph too small
#graph_viewer.file_failure=Error in File Operation
#graph_viewer.save_graph=Save graph
@@ -558,3 +550,6 @@ adb_dialog.starting_debugger=디버거 시작 중 ...
#graph_viewer.call_graph.title=Call Graph
#graph_viewer.inheritance_graph.title=Inheritance Graph
#graph_viewer.cfg.title=Control Flow Graph
+#graph_viewer.cfg.preset_selector_label=CFG preset
+#graph_viewer.cfg.preset_names=Raw|Normal|Region
+#graph_viewer.cfg.pass_selector_label=View after pass
diff --git a/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties b/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties
index df1b8b8dd..e8c07f26a 100644
--- a/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties
+++ b/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties
@@ -374,20 +374,15 @@ popup.find_usage=Buscar uso
popup.go_to_declaration=Ir para declaração
popup.exclude=Ignorar
popup.exclude_packages=Pacotes ignorados
-#popup.convert_number=Add conversion as comment
+#popup.convert_number=Convert number
+#popup.convert_number_tooltip=Add other number formats as comment
#popup.view_call_graph=View call graph
#popup.view_call_graph_description=Show call chains to this function
#popup.view_class_graph=View inheritance graph
#popup.view_class_graph_description=Show inheritance tree for this class
#popup.view_class_method_graph=View methods graph
#popup.view_class_method_graph_description=Show all methods for this class
-#popup.cfg_submenu=View control flow graph
-#popup.view_cfg=Regular
-#popup.view_cfg_description=Show regular control flow graph for this function (enable in File->Preferences->Other)
-#popup.view_raw_cfg=Raw
-#popup.view_raw_cfg_description=Show control flow graph with raw instructions for this function (enable in File->Preferences->Other)
-#popup.view_region_cfg=Region
-#popup.view_region_cfg_description=Show regioned control flow graph for this function (enable in File->Preferences->Other)
+#popup.view_cfg=View method control flow graph
popup.add_comment=Comentar
#popup.update_comment=Update comment
popup.search_comment=Buscar comentários
@@ -547,9 +542,6 @@ adb_dialog.starting_debugger=Iniciando depurador...
#graph_viewer.callee_depth=Down depth
#graph_viewer.caller_depth=Up depth
#graph_viewer.default_error=Failed to view graph
-#graph_viewer.file_not_found_error=Failed to load graph file
-#graph_viewer.image_too_large=Failed to render graph: graph too large
-#graph_viewer.image_too_small=Failed to render graph: graph too small
#graph_viewer.file_failure=Error in File Operation
#graph_viewer.save_graph=Save graph
@@ -558,3 +550,6 @@ adb_dialog.starting_debugger=Iniciando depurador...
#graph_viewer.call_graph.title=Call Graph
#graph_viewer.inheritance_graph.title=Inheritance Graph
#graph_viewer.cfg.title=Control Flow Graph
+#graph_viewer.cfg.preset_selector_label=CFG preset
+#graph_viewer.cfg.preset_names=Raw|Normal|Region
+#graph_viewer.cfg.pass_selector_label=View after pass
diff --git a/jadx-gui/src/main/resources/i18n/Messages_ru_RU.properties b/jadx-gui/src/main/resources/i18n/Messages_ru_RU.properties
index a1dde29af..e039a9dd4 100644
--- a/jadx-gui/src/main/resources/i18n/Messages_ru_RU.properties
+++ b/jadx-gui/src/main/resources/i18n/Messages_ru_RU.properties
@@ -374,20 +374,15 @@ popup.find_usage=Найти использования
popup.go_to_declaration=Перейти к объявлению
popup.exclude=Исключить
popup.exclude_packages=Исключить пакеты
-#popup.convert_number=Add conversion as comment
+#popup.convert_number=Convert number
+#popup.convert_number_tooltip=Add other number formats as comment
#popup.view_call_graph=View call graph
#popup.view_call_graph_description=Show call chains to this function
#popup.view_class_graph=View inheritance graph
#popup.view_class_graph_description=Show inheritance tree for this class
#popup.view_class_method_graph=View methods graph
#popup.view_class_method_graph_description=Show all methods for this class
-#popup.cfg_submenu=View control flow graph
-#popup.view_cfg=Regular
-#popup.view_cfg_description=Show regular control flow graph for this function (enable in File->Preferences->Other)
-#popup.view_raw_cfg=Raw
-#popup.view_raw_cfg_description=Show control flow graph with raw instructions for this function (enable in File->Preferences->Other)
-#popup.view_region_cfg=Region
-#popup.view_region_cfg_description=Show regioned control flow graph for this function (enable in File->Preferences->Other)
+#popup.view_cfg=View method control flow graph
popup.add_comment=Комментарий
#popup.update_comment=Update comment
popup.search_comment=Поиск комментариев
@@ -547,9 +542,6 @@ action_category.plugin_script=Скрипты и плагины
#graph_viewer.callee_depth=Down depth
#graph_viewer.caller_depth=Up depth
#graph_viewer.default_error=Failed to view graph
-#graph_viewer.file_not_found_error=Failed to load graph file
-#graph_viewer.image_too_large=Failed to render graph: graph too large
-#graph_viewer.image_too_small=Failed to render graph: graph too small
#graph_viewer.file_failure=Error in File Operation
#graph_viewer.save_graph=Save graph
@@ -558,3 +550,6 @@ action_category.plugin_script=Скрипты и плагины
#graph_viewer.call_graph.title=Call Graph
#graph_viewer.inheritance_graph.title=Inheritance Graph
#graph_viewer.cfg.title=Control Flow Graph
+#graph_viewer.cfg.preset_selector_label=CFG preset
+#graph_viewer.cfg.preset_names=Raw|Normal|Region
+#graph_viewer.cfg.pass_selector_label=View after pass
diff --git a/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties b/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties
index ee8b59497..17311ad9b 100644
--- a/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties
+++ b/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties
@@ -375,19 +375,14 @@ popup.go_to_declaration=跳到声明
popup.exclude=排除此包
popup.exclude_packages=排除包
popup.convert_number=将数值转换添加为注释
+#popup.convert_number_tooltip=Add other number formats as comment
popup.view_call_graph=查看调用图
popup.view_call_graph_description=显示到此函数的调用链
popup.view_class_graph=查看继承图
popup.view_class_graph_description=显示该类的继承树
popup.view_class_method_graph=查看方法图
popup.view_class_method_graph_description=显示该类的所有方法
-popup.cfg_submenu=查看控制图
-popup.view_cfg=常规
-popup.view_cfg_description=显示该函数的常规控制流图(可在 文件->首选项->其他 中启用)
-popup.view_raw_cfg=原始
-popup.view_raw_cfg_description=显示该函数包含原始指令的控制流图(可在 文件->首选项->其他 中启用)
-popup.view_region_cfg=区域
-popup.view_region_cfg_description=显示该函数的区域化控制流图(可在 文件->首选项->其他 中启用)
+#popup.view_cfg=View method control flow graph
popup.add_comment=添加注释
popup.update_comment=更新注释
popup.search_comment=搜索注释
@@ -547,9 +542,6 @@ graph_viewer.overrides=显示重写关系
graph_viewer.callee_depth=向下深度
graph_viewer.caller_depth=向上深度
graph_viewer.default_error=图形显示失败
-graph_viewer.file_not_found_error=图形文件加载失败
-graph_viewer.image_too_large=图形渲染失败:图过大
-graph_viewer.image_too_small=图形渲染失败:图过小
graph_viewer.file_failure=文件操作错误
graph_viewer.save_graph=保存图形
@@ -558,3 +550,6 @@ graph_viewer.method_graph.title=方法图
graph_viewer.call_graph.title=调用图
graph_viewer.inheritance_graph.title=继承图
graph_viewer.cfg.title=控制图
+#graph_viewer.cfg.preset_selector_label=CFG preset
+#graph_viewer.cfg.preset_names=Raw|Normal|Region
+#graph_viewer.cfg.pass_selector_label=View after pass
diff --git a/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties b/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties
index 5a803b1df..346832651 100644
--- a/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties
+++ b/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties
@@ -374,20 +374,15 @@ popup.find_usage=尋找使用情況
popup.go_to_declaration=前往宣告
popup.exclude=排除
popup.exclude_packages=排除套件
-#popup.convert_number=Add conversion as comment
+#popup.convert_number=Convert number
+#popup.convert_number_tooltip=Add other number formats as comment
#popup.view_call_graph=View call graph
#popup.view_call_graph_description=Show call chains to this function
#popup.view_class_graph=View inheritance graph
#popup.view_class_graph_description=Show inheritance tree for this class
#popup.view_class_method_graph=View methods graph
#popup.view_class_method_graph_description=Show all methods for this class
-#popup.cfg_submenu=View control flow graph
-#popup.view_cfg=Regular
-#popup.view_cfg_description=Show regular control flow graph for this function (enable in File->Preferences->Other)
-#popup.view_raw_cfg=Raw
-#popup.view_raw_cfg_description=Show control flow graph with raw instructions for this function (enable in File->Preferences->Other)
-#popup.view_region_cfg=Region
-#popup.view_region_cfg_description=Show regioned control flow graph for this function (enable in File->Preferences->Other)
+#popup.view_cfg=View method control flow graph
popup.add_comment=註解
popup.update_comment=更新註解
popup.search_comment=搜尋註解
@@ -547,9 +542,6 @@ action_category.plugin_script=外掛程式腳本
#graph_viewer.callee_depth=Down depth
#graph_viewer.caller_depth=Up depth
#graph_viewer.default_error=Failed to view graph
-#graph_viewer.file_not_found_error=Failed to load graph file
-#graph_viewer.image_too_large=Failed to render graph: graph too large
-#graph_viewer.image_too_small=Failed to render graph: graph too small
#graph_viewer.file_failure=Error in File Operation
#graph_viewer.save_graph=Save graph
@@ -558,3 +550,6 @@ action_category.plugin_script=外掛程式腳本
#graph_viewer.call_graph.title=Call Graph
#graph_viewer.inheritance_graph.title=Inheritance Graph
#graph_viewer.cfg.title=Control Flow Graph
+#graph_viewer.cfg.preset_selector_label=CFG preset
+#graph_viewer.cfg.preset_names=Raw|Normal|Region
+#graph_viewer.cfg.pass_selector_label=View after pass
diff --git a/jadx-gui/src/test/java/jadx/gui/ui/codearea/ConvertNumberActionTest.java b/jadx-gui/src/test/java/jadx/gui/ui/codearea/ConvertNumberActionTest.java
index 8abb0ca0e..f2565777f 100644
--- a/jadx-gui/src/test/java/jadx/gui/ui/codearea/ConvertNumberActionTest.java
+++ b/jadx-gui/src/test/java/jadx/gui/ui/codearea/ConvertNumberActionTest.java
@@ -20,167 +20,105 @@ public class ConvertNumberActionTest {
@Test
public void simpleDecimalToHex() {
-
- List expected = new ArrayList();
+ List expected = new ArrayList<>();
expected.add("0x7b");
expected.add("0b01111011");
expected.add("'{'");
List result = ConvertNumberAction.getConversionsFromWord("123");
-
- assertThat(result).isNotEmpty();
-
- expected.removeAll(result);
- assertThat(expected).isEmpty();
+ assertThat(result).isEqualTo(expected);
}
@Test
public void negativeDecimalToHex() {
-
- List expected = new ArrayList();
+ List expected = new ArrayList<>();
expected.add("0xffffff85");
expected.add("0b11111111111111111111111110000101");
List result = ConvertNumberAction.getConversionsFromWord("-123");
-
- assertThat(result).isNotEmpty();
-
- expected.removeAll(result);
- assertThat(expected).isEmpty();
+ assertThat(result).isEqualTo(expected);
}
@Test
public void negativeLongDecimalToHex() {
-
- List expected = new ArrayList();
+ List expected = new ArrayList<>();
expected.add("0xFFFFFFE8B7891800".toLowerCase());
expected.add("0b1111111111111111111111111110100010110111100010010001100000000000");
List result = ConvertNumberAction.getConversionsFromWord("-100000000000");
-
- assertThat(result).isNotEmpty();
-
- expected.removeAll(result);
- assertThat(expected).isEmpty();
+ assertThat(result).isEqualTo(expected);
}
@Test
public void simpleHexToDecimal() {
-
- List expected = new ArrayList();
+ List expected = new ArrayList<>();
expected.add("123");
expected.add("0b01111011");
expected.add("'{'");
List result = ConvertNumberAction.getConversionsFromWord("0x7b");
-
- assertThat(result).isNotEmpty();
-
- expected.removeAll(result);
- assertThat(expected).isEmpty();
+ assertThat(result).isEqualTo(expected);
}
@Test
- public void zeroToHex() {
-
- List expected = new ArrayList();
- expected.add("0x0");
- expected.add("0b00000000");
-
+ public void zero() {
List result = ConvertNumberAction.getConversionsFromWord(Integer.toString(0));
-
- assertThat(result).isNotEmpty();
-
- expected.removeAll(result);
- assertThat(expected).isEmpty();
-
+ assertThat(result).isEmpty();
}
@Test
public void minIntToHex() {
-
- List expected = new ArrayList();
+ List expected = new ArrayList<>();
expected.add("0x80000000");
expected.add("0b10000000000000000000000000000000");
List result = ConvertNumberAction.getConversionsFromWord(Integer.toString(Integer.MIN_VALUE));
-
- assertThat(result).isNotEmpty();
-
- expected.removeAll(result);
- assertThat(expected).isEmpty();
-
+ assertThat(result).isEqualTo(expected);
}
@Test
public void maxIntToHex() {
-
- List expected = new ArrayList();
+ List expected = new ArrayList<>();
expected.add("0x7fffffff");
expected.add("0b01111111111111111111111111111111");
List result = ConvertNumberAction.getConversionsFromWord(Integer.toString(Integer.MAX_VALUE));
-
- assertThat(result).isNotEmpty();
-
- expected.removeAll(result);
- assertThat(expected).isEmpty();
-
+ assertThat(result).isEqualTo(expected);
}
@Test
public void minLongToHex() {
-
- List expected = new ArrayList();
+ List expected = new ArrayList<>();
expected.add("0x8000000000000000");
expected.add("0b1000000000000000000000000000000000000000000000000000000000000000");
List result = ConvertNumberAction.getConversionsFromWord(Long.toString(Long.MIN_VALUE));
-
- assertThat(result).isNotEmpty();
-
- expected.removeAll(result);
- assertThat(expected).isEmpty();
-
+ assertThat(result).isEqualTo(expected);
}
@Test
public void maxLongToHex() {
-
- List expected = new ArrayList();
+ List expected = new ArrayList<>();
expected.add("0x7fffffffffffffff");
expected.add("0b0111111111111111111111111111111111111111111111111111111111111111");
List result = ConvertNumberAction.getConversionsFromWord(Long.toString(Long.MAX_VALUE));
-
- assertThat(result).isNotEmpty();
-
- expected.removeAll(result);
- assertThat(expected).isEmpty();
-
+ assertThat(result).isEqualTo(expected);
}
@Test
public void simpleLongSuffix() {
-
- List expected = new ArrayList();
+ List expected = new ArrayList<>();
expected.add("0x7b");
expected.add("0b01111011");
expected.add("'{'");
List result = ConvertNumberAction.getConversionsFromWord("123L");
-
- assertThat(result).isNotEmpty();
-
- expected.removeAll(result);
- assertThat(expected).isEmpty();
-
+ assertThat(result).isEqualTo(expected);
}
@Test
public void binaryPadding() {
-
- assertThat(ConvertNumberAction.getConversionsFromWord("0")).containsOnlyOnce("0b00000000");
assertThat(ConvertNumberAction.getConversionsFromWord("1")).containsOnlyOnce("0b00000001");
assertThat(ConvertNumberAction.getConversionsFromWord("127")).containsOnlyOnce("0b01111111");
assertThat(ConvertNumberAction.getConversionsFromWord("0xff")).containsOnlyOnce("0b11111111");
@@ -197,16 +135,13 @@ public class ConvertNumberActionTest {
assertThat(ConvertNumberAction.getConversionsFromWord("0x7fffffffffffffff"))
.containsOnlyOnce("0b0111111111111111111111111111111111111111111111111111111111111111");
-
}
@Test
public void printableAscii() {
-
for (int i = 32; i < 127; i++) {
String printed = String.format("'%c'", i);
assertThat(ConvertNumberAction.getConversionsFromWord(Integer.toString(i))).containsOnlyOnce(printed);
}
}
-
}