From f558203a9d99ef0cac46b4732fce5f3dfe68fe2d Mon Sep 17 00:00:00 2001 From: Yoav Sternberg Date: Fri, 9 Jun 2023 17:50:57 +0300 Subject: [PATCH] feat(api): access node under caret/mouse and jump (PR #1903) * Access node under caret/mouse and jump * Apply lint --- .../jadx/api/plugins/gui/JadxGuiContext.java | 15 ++++ .../gui/plugins/context/GuiPluginContext.java | 90 +++++++++++++++++++ .../java/jadx/gui/ui/codearea/CodeArea.java | 39 ++++++++ .../examples/scripts/gui/bookmark.jadx.kts | 43 +++++++++ .../examples/scripts/gui/caret_mouse.jadx.kts | 19 ++++ .../jadx/plugins/script/runtime/data/Gui.kt | 11 +++ 6 files changed, 217 insertions(+) create mode 100644 jadx-plugins/jadx-script/examples/scripts/gui/bookmark.jadx.kts create mode 100644 jadx-plugins/jadx-script/examples/scripts/gui/caret_mouse.jadx.kts diff --git a/jadx-core/src/main/java/jadx/api/plugins/gui/JadxGuiContext.java b/jadx-core/src/main/java/jadx/api/plugins/gui/JadxGuiContext.java index 1fbfb0baa..fe81d28fe 100644 --- a/jadx-core/src/main/java/jadx/api/plugins/gui/JadxGuiContext.java +++ b/jadx-core/src/main/java/jadx/api/plugins/gui/JadxGuiContext.java @@ -49,4 +49,19 @@ public interface JadxGuiContext { * Access to GUI settings */ JadxGuiSettings settings(); + + ICodeNodeRef getNodeUnderCaret(); + + ICodeNodeRef getNodeUnderMouse(); + + ICodeNodeRef getEnclosingNodeUnderCaret(); + + ICodeNodeRef getEnclosingNodeUnderMouse(); + + /** + * Jump to a code ref + * + * @return if successfully jumped to the code ref + */ + boolean open(ICodeNodeRef ref); } diff --git a/jadx-gui/src/main/java/jadx/gui/plugins/context/GuiPluginContext.java b/jadx-gui/src/main/java/jadx/gui/plugins/context/GuiPluginContext.java index 487768ea7..d75811d90 100644 --- a/jadx-gui/src/main/java/jadx/gui/plugins/context/GuiPluginContext.java +++ b/jadx-gui/src/main/java/jadx/gui/plugins/context/GuiPluginContext.java @@ -1,5 +1,6 @@ package jadx.gui.plugins.context; +import java.awt.Container; import java.util.function.Consumer; import java.util.function.Function; @@ -14,7 +15,15 @@ import jadx.api.metadata.ICodeNodeRef; import jadx.api.plugins.gui.ISettingsGroup; import jadx.api.plugins.gui.JadxGuiContext; import jadx.api.plugins.gui.JadxGuiSettings; +import jadx.core.dex.nodes.ClassNode; +import jadx.core.dex.nodes.FieldNode; +import jadx.core.dex.nodes.MethodNode; import jadx.core.plugins.PluginContext; +import jadx.gui.treemodel.JNode; +import jadx.gui.ui.codearea.AbstractCodeArea; +import jadx.gui.ui.codearea.AbstractCodeContentPanel; +import jadx.gui.ui.codearea.CodeArea; +import jadx.gui.utils.JNodeCache; import jadx.gui.utils.UiUtils; public class GuiPluginContext implements JadxGuiContext { @@ -86,4 +95,85 @@ public class GuiPluginContext implements JadxGuiContext { public @Nullable ISettingsGroup getCustomSettingsGroup() { return customSettingsGroup; } + + @Nullable + private CodeArea getCodeArea() { + Container contentPane = commonContext.getMainWindow().getTabbedPane().getSelectedContentPanel(); + if (contentPane instanceof AbstractCodeContentPanel) { + AbstractCodeArea codeArea = ((AbstractCodeContentPanel) contentPane).getCodeArea(); + if (codeArea instanceof CodeArea) { + return (CodeArea) codeArea; + } + } + return null; + } + + @Override + public ICodeNodeRef getNodeUnderCaret() { + CodeArea codeArea = getCodeArea(); + if (codeArea != null) { + JNode nodeUnderCaret = codeArea.getNodeUnderCaret(); + if (nodeUnderCaret != null) { + return nodeUnderCaret.getCodeNodeRef(); + } + } + return null; + } + + @Override + public ICodeNodeRef getNodeUnderMouse() { + CodeArea codeArea = getCodeArea(); + if (codeArea != null) { + JNode nodeUnderMouse = codeArea.getNodeUnderMouse(); + if (nodeUnderMouse != null) { + return nodeUnderMouse.getCodeNodeRef(); + } + } + return null; + } + + @Override + public ICodeNodeRef getEnclosingNodeUnderCaret() { + CodeArea codeArea = getCodeArea(); + if (codeArea != null) { + JNode nodeUnderMouse = codeArea.getEnclosingNodeUnderCaret(); + if (nodeUnderMouse != null) { + return nodeUnderMouse.getCodeNodeRef(); + } + } + return null; + } + + @Override + public ICodeNodeRef getEnclosingNodeUnderMouse() { + CodeArea codeArea = getCodeArea(); + if (codeArea != null) { + JNode nodeUnderMouse = codeArea.getEnclosingNodeUnderMouse(); + if (nodeUnderMouse != null) { + return nodeUnderMouse.getCodeNodeRef(); + } + } + return null; + } + + @Override + public boolean open(ICodeNodeRef ref) { + JNodeCache cache = commonContext.getMainWindow().getWrapper().getCache().getNodeCache(); + JNode node; + if (ref instanceof ClassNode) { + node = cache.makeFrom(((ClassNode) ref).getJavaNode()); + } else if (ref instanceof MethodNode) { + node = cache.makeFrom(((MethodNode) ref).getJavaNode()); + } else if (ref instanceof FieldNode) { + node = cache.makeFrom(((FieldNode) ref).getJavaNode()); + } else { + // Package node - cannot jump to it + // TODO: Var node - might be possible + return false; + } + + commonContext.getMainWindow().getTabbedPane().codeJump(node); + return true; + } + } 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 9f3b54239..93abf70d3 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 @@ -213,6 +213,20 @@ public final class CodeArea extends AbstractCodeArea { return getJNodeAtOffset(start); } + @Nullable + public JNode getEnclosingNodeUnderCaret() { + int caretPos = getCaretPosition(); + Token token = modelToToken(caretPos); + if (token == null) { + return null; + } + int start = adjustOffsetForToken(token); + if (start == -1) { + start = caretPos; + } + return getEnclosingJNodeAtOffset(start); + } + @Nullable public JNode getNodeUnderMouse() { Point pos = UiUtils.getMousePosition(this); @@ -220,6 +234,22 @@ public final class CodeArea extends AbstractCodeArea { return getJNodeAtOffset(offset); } + @Nullable + public JNode getEnclosingNodeUnderMouse() { + Point pos = UiUtils.getMousePosition(this); + int offset = adjustOffsetForToken(viewToToken(pos)); + return getEnclosingJNodeAtOffset(offset); + } + + @Nullable + public JNode getEnclosingJNodeAtOffset(int offset) { + JavaNode javaNode = getEnclosingJavaNode(offset); + if (javaNode != null) { + return convertJavaNode(javaNode); + } + return null; + } + @Nullable public JNode getJNodeAtOffset(int offset) { JavaNode javaNode = getJavaNodeAtOffset(offset); @@ -253,6 +283,15 @@ public final class CodeArea extends AbstractCodeArea { } } + public JavaNode getEnclosingJavaNode(int offset) { + try { + return getJadxWrapper().getDecompiler().getEnclosingNode(getCodeInfo(), offset); + } catch (Exception e) { + LOG.error("Can't get java node by offset: {}", offset, e); + return null; + } + } + public JavaClass getJavaClassIfAtPos(int pos) { try { ICodeInfo codeInfo = getCodeInfo(); diff --git a/jadx-plugins/jadx-script/examples/scripts/gui/bookmark.jadx.kts b/jadx-plugins/jadx-script/examples/scripts/gui/bookmark.jadx.kts new file mode 100644 index 000000000..ab301f862 --- /dev/null +++ b/jadx-plugins/jadx-script/examples/scripts/gui/bookmark.jadx.kts @@ -0,0 +1,43 @@ +import jadx.api.metadata.ICodeNodeRef +import jadx.core.dex.nodes.MethodNode + +val jadx = getJadxInstance() +var savedBookmark: ICodeNodeRef? = null + +jadx.gui.ifAvailable { + addPopupMenuAction( + "Set bookmark", + enabled = { true }, + keyBinding = "B", + action = ::setBookmark, + ) + + addMenuAction( + "Jump to bookmark", + action = ::jumpToBookmark, + ) +} + +fun setBookmark(node: ICodeNodeRef) { + val enclosing = jadx.gui.enclosingNodeUnderCaret ?: run { + jadx.log.info { "No enclosing node" } + return + } + + // You can bookmark a field, method or a class + val target = if (enclosing is MethodNode) enclosing else node + + jadx.log.info { "Setting bookmark to: $target" } + savedBookmark = target +} + +fun jumpToBookmark() { + if (savedBookmark == null) { + jadx.log.info { "No bookmark" } + } else { + val res = jadx.gui.open(savedBookmark!!) + if (!res) { + jadx.log.info { "Failed to jump to bookmark" } + } + } +} diff --git a/jadx-plugins/jadx-script/examples/scripts/gui/caret_mouse.jadx.kts b/jadx-plugins/jadx-script/examples/scripts/gui/caret_mouse.jadx.kts new file mode 100644 index 000000000..5580ad569 --- /dev/null +++ b/jadx-plugins/jadx-script/examples/scripts/gui/caret_mouse.jadx.kts @@ -0,0 +1,19 @@ +import jadx.api.metadata.ICodeNodeRef + +val jadx = getJadxInstance() + +jadx.gui.ifAvailable { + addPopupMenuAction( + "Print enclosing symbols under caret or mouse", + enabled = { true }, + keyBinding = "G", + action = ::runAction, + ) +} + +fun runAction(node: ICodeNodeRef) { + jadx.log.info { "Node under caret: ${jadx.gui.nodeUnderCaret}" } + jadx.log.info { "Enclosing node under caret: ${jadx.gui.enclosingNodeUnderCaret}" } + jadx.log.info { "Node under mouse: ${jadx.gui.nodeUnderMouse}" } + jadx.log.info { "Enclosing Node under mouse: ${jadx.gui.enclosingNodeUnderMouse}" } +} diff --git a/jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/data/Gui.kt b/jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/data/Gui.kt index d0b50bb2c..632af83c6 100644 --- a/jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/data/Gui.kt +++ b/jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/data/Gui.kt @@ -40,6 +40,17 @@ class Gui( context().copyToClipboard(str) } + fun open(ref: ICodeNodeRef): Boolean = context().open(ref) + + val nodeUnderCaret: ICodeNodeRef? + get() = context().nodeUnderCaret + val nodeUnderMouse: ICodeNodeRef? + get() = context().nodeUnderMouse + val enclosingNodeUnderCaret: ICodeNodeRef? + get() = context().enclosingNodeUnderCaret + val enclosingNodeUnderMouse: ICodeNodeRef? + get() = context().enclosingNodeUnderMouse + private fun context(): JadxGuiContext = guiContext ?: throw IllegalStateException("GUI plugins context not available!") }