diff --git a/jadx-gui/src/main/java/jadx/gui/jobs/BackgroundExecutor.java b/jadx-gui/src/main/java/jadx/gui/jobs/BackgroundExecutor.java index 8adc75d18..f4a872b62 100644 --- a/jadx-gui/src/main/java/jadx/gui/jobs/BackgroundExecutor.java +++ b/jadx-gui/src/main/java/jadx/gui/jobs/BackgroundExecutor.java @@ -212,8 +212,15 @@ public class BackgroundExecutor { // force termination task.cancel(); executor.shutdown(); - boolean complete = executor.awaitTermination(5, TimeUnit.SECONDS); - LOG.debug("Task cancel complete: {}", complete ? "success" : "aborted"); + if (executor.awaitTermination(5, TimeUnit.SECONDS)) { + LOG.debug("Task cancel complete"); + return; + } + LOG.debug("Forcing tasks cancel"); + executor.shutdownNow(); + boolean complete = executor.awaitTermination(30, TimeUnit.SECONDS); + LOG.debug("Forced task cancel status: {}", + complete ? "success" : "fail, still active: " + executor.getActiveCount()); } private Supplier buildCancelCheck(long startTime) { diff --git a/jadx-gui/src/main/java/jadx/gui/ui/TabbedPane.java b/jadx-gui/src/main/java/jadx/gui/ui/TabbedPane.java index 96585adee..bbdd40cd4 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/TabbedPane.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/TabbedPane.java @@ -309,6 +309,7 @@ public class TabbedPane extends JTabbedPane { public void closeCodePanel(ContentPanel contentPanel) { openTabs.remove(contentPanel.getNode()); remove(contentPanel); + contentPanel.dispose(); } @Nullable diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/AbstractCodeArea.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/AbstractCodeArea.java index fd45ca5cf..fcf71bdc6 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/codearea/AbstractCodeArea.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/AbstractCodeArea.java @@ -1,5 +1,6 @@ package jadx.gui.ui.codearea; +import java.awt.Component; import java.awt.Dimension; import java.awt.Point; import java.awt.Rectangle; @@ -8,15 +9,20 @@ import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; +import java.awt.event.MouseListener; +import java.awt.event.MouseMotionListener; import javax.swing.AbstractAction; +import javax.swing.Action; import javax.swing.JCheckBoxMenuItem; +import javax.swing.JMenuItem; import javax.swing.JPopupMenu; import javax.swing.JViewport; import javax.swing.SwingUtilities; import javax.swing.event.CaretEvent; import javax.swing.event.CaretListener; import javax.swing.event.PopupMenuEvent; +import javax.swing.event.PopupMenuListener; import javax.swing.text.BadLocationException; import javax.swing.text.Caret; import javax.swing.text.DefaultCaret; @@ -64,8 +70,8 @@ public abstract class AbstractCodeArea extends RSyntaxTextArea { } } - protected final ContentPanel contentPanel; - protected final JNode node; + protected ContentPanel contentPanel; + protected JNode node; public AbstractCodeArea(ContentPanel contentPanel, JNode node) { this.contentPanel = contentPanel; @@ -348,4 +354,33 @@ public abstract class AbstractCodeArea extends RSyntaxTextArea { } return null; } + + public void dispose() { + // code area reference can still be used somewhere in UI objects, + // reset node reference to allow to GC jadx objects tree + node = null; + contentPanel = null; + + // also clear internals + setLinkGenerator(null); + for (MouseListener mouseListener : getMouseListeners()) { + removeMouseListener(mouseListener); + } + for (MouseMotionListener mouseMotionListener : getMouseMotionListeners()) { + removeMouseMotionListener(mouseMotionListener); + } + JPopupMenu popupMenu = getPopupMenu(); + for (PopupMenuListener popupMenuListener : popupMenu.getPopupMenuListeners()) { + popupMenu.removePopupMenuListener(popupMenuListener); + } + for (Component component : popupMenu.getComponents()) { + if (component instanceof JMenuItem) { + Action action = ((JMenuItem) component).getAction(); + if (action instanceof JNodeAction) { + ((JNodeAction) action).dispose(); + } + } + } + popupMenu.removeAll(); + } } diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/ClassCodeContentPanel.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/ClassCodeContentPanel.java index 859cede5f..5fb4d9d74 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/codearea/ClassCodeContentPanel.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/ClassCodeContentPanel.java @@ -1,6 +1,7 @@ package jadx.gui.ui.codearea; import java.awt.BorderLayout; +import java.awt.Component; import java.awt.Dimension; import java.awt.Point; @@ -170,4 +171,16 @@ public final class ClassCodeContentPanel extends AbstractCodeContentPanel implem LOG.debug("Failed to restore caret position: {}", viewState.getCaretPos(), e); } } + + @Override + public void dispose() { + javaCodePanel.dispose(); + smaliCodePanel.dispose(); + for (Component component : areaTabbedPane.getComponents()) { + if (component instanceof CodePanel) { + ((CodePanel) component).dispose(); + } + } + super.dispose(); + } } 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 b01330dc8..29c78f930 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 @@ -282,4 +282,10 @@ public final class CodeArea extends AbstractCodeArea { public JadxProject getProject() { return getMainWindow().getProject(); } + + @Override + public void dispose() { + super.dispose(); + cachedCodeInfo = null; + } } diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodeContentPanel.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodeContentPanel.java index 5c67c4209..876ad4e82 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodeContentPanel.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodeContentPanel.java @@ -62,4 +62,9 @@ public final class CodeContentPanel extends AbstractCodeContentPanel implements codePanel.getCodeScrollPane().getViewport().setViewPosition(viewState.getViewPoint()); codePanel.getCodeArea().setCaretPosition(viewState.getCaretPos()); } + + @Override + public void dispose() { + codePanel.dispose(); + } } diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodePanel.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodePanel.java index 04ebfa3fd..393bffa91 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodePanel.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodePanel.java @@ -195,4 +195,8 @@ public class CodePanel extends JPanel { return this.codeArea.getContentPanel().getTabbedPane() .getMainWindow().getSettings(); } + + public void dispose() { + codeArea.dispose(); + } } 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 a04a449d0..1799ab77b 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 @@ -11,7 +11,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import jadx.api.ICodeInfo; -import jadx.api.JavaClass; import jadx.api.JavaMethod; import jadx.api.JavaNode; import jadx.api.data.ICodeComment; @@ -26,7 +25,6 @@ import jadx.api.metadata.annotations.InsnCodeOffset; import jadx.api.metadata.annotations.NodeDeclareRef; import jadx.gui.JadxWrapper; import jadx.gui.treemodel.JClass; -import jadx.gui.treemodel.JNode; import jadx.gui.ui.dialog.CommentDialog; import jadx.gui.utils.DefaultPopupMenuListener; import jadx.gui.utils.NLS; @@ -39,28 +37,29 @@ public class CommentAction extends AbstractAction implements DefaultPopupMenuLis private static final Logger LOG = LoggerFactory.getLogger(CommentAction.class); private final CodeArea codeArea; - private final JavaClass topCls; + private final boolean enabled; private ICodeComment actionComment; public CommentAction(CodeArea codeArea) { super(NLS.str("popup.add_comment") + " (;)"); this.codeArea = codeArea; - JNode topNode = codeArea.getNode(); - if (topNode instanceof JClass) { - this.topCls = ((JClass) topNode).getCls(); - } else { - this.topCls = null; + this.enabled = codeArea.getNode() instanceof JClass; + if (enabled) { + UiUtils.addKeyBinding(codeArea, getKeyStroke(KeyEvent.VK_SEMICOLON, 0), "popup.add_comment", + () -> showCommentDialog(getCommentRef(codeArea.getCaretPosition()))); } - UiUtils.addKeyBinding(codeArea, getKeyStroke(KeyEvent.VK_SEMICOLON, 0), "popup.add_comment", - () -> showCommentDialog(getCommentRef(codeArea.getCaretPosition()))); } @Override public void popupMenuWillBecomeVisible(PopupMenuEvent e) { - ICodeComment codeComment = getCommentRef(UiUtils.getOffsetAtMousePosition(codeArea)); - setEnabled(codeComment != null); - this.actionComment = codeComment; + if (enabled) { + ICodeComment codeComment = getCommentRef(UiUtils.getOffsetAtMousePosition(codeArea)); + setEnabled(codeComment != null); + this.actionComment = codeComment; + } else { + setEnabled(false); + } } @Override @@ -83,7 +82,7 @@ public class CommentAction extends AbstractAction implements DefaultPopupMenuLis */ @Nullable private ICodeComment getCommentRef(int pos) { - if (pos == -1 || this.topCls == null) { + if (pos == -1) { return null; } try { diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/JNodeAction.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/JNodeAction.java index 8d686fe87..1d651a9c7 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/codearea/JNodeAction.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/JNodeAction.java @@ -1,6 +1,7 @@ package jadx.gui.ui.codearea; import java.awt.event.ActionEvent; +import java.beans.PropertyChangeListener; import javax.swing.AbstractAction; import javax.swing.KeyStroke; @@ -16,7 +17,7 @@ import jadx.gui.utils.UiUtils; public abstract class JNodeAction extends AbstractAction { private static final long serialVersionUID = -2600154727884853550L; - private final transient CodeArea codeArea; + private transient CodeArea codeArea; private transient @Nullable JNode node; public JNodeAction(String name, CodeArea codeArea) { @@ -26,7 +27,7 @@ public abstract class JNodeAction extends AbstractAction { public abstract void runAction(JNode node); - public boolean isActionEnabled(JNode node) { + public boolean isActionEnabled(@Nullable JNode node) { return node != null; } @@ -47,7 +48,7 @@ public abstract class JNodeAction extends AbstractAction { runAction(node); } - public void changeNode(JNode node) { + public void changeNode(@Nullable JNode node) { this.node = node; setEnabled(isActionEnabled(node)); } @@ -55,4 +56,12 @@ public abstract class JNodeAction extends AbstractAction { public CodeArea getCodeArea() { return codeArea; } + + public void dispose() { + node = null; + codeArea = null; + for (PropertyChangeListener changeListener : getPropertyChangeListeners()) { + removePropertyChangeListener(changeListener); + } + } } diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/JNodePopupListener.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/JNodePopupListener.java index 8fb929e4e..79cedeb25 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/codearea/JNodePopupListener.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/JNodePopupListener.java @@ -4,11 +4,11 @@ import java.util.ArrayList; import java.util.List; import javax.swing.event.PopupMenuEvent; +import javax.swing.event.PopupMenuListener; import jadx.gui.treemodel.JNode; -import jadx.gui.utils.DefaultPopupMenuListener; -public final class JNodePopupListener implements DefaultPopupMenuListener { +public final class JNodePopupListener implements PopupMenuListener { private final CodeArea codeArea; private final List actions = new ArrayList<>(); @@ -16,13 +16,28 @@ public final class JNodePopupListener implements DefaultPopupMenuListener { this.codeArea = codeArea; } - @Override - public void popupMenuWillBecomeVisible(PopupMenuEvent e) { - JNode node = codeArea.getNodeUnderMouse(); - actions.forEach(action -> action.changeNode(node)); - } - public void addActions(JNodeAction action) { actions.add(action); } + + private void updateNode(JNode node) { + for (JNodeAction action : actions) { + action.changeNode(node); + } + } + + @Override + public void popupMenuWillBecomeVisible(PopupMenuEvent e) { + updateNode(codeArea.getNodeUnderMouse()); + } + + @Override + public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { + updateNode(null); + } + + @Override + public void popupMenuCanceled(PopupMenuEvent e) { + updateNode(null); + } } diff --git a/jadx-gui/src/main/java/jadx/gui/ui/dialog/SearchDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/dialog/SearchDialog.java index 72b8520f0..c31098d1b 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/dialog/SearchDialog.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/dialog/SearchDialog.java @@ -143,12 +143,18 @@ public class SearchDialog extends CommonSearchDialog { @Override public void dispose() { - stopSearchTask(); if (searchDisposable != null && !searchDisposable.isDisposed()) { searchDisposable.dispose(); } resultsModel.clear(); removeActiveTabListener(); + if (searchTask != null) { + searchTask.cancel(); + mainWindow.getBackgroundExecutor().execute(NLS.str("progress.load"), () -> { + stopSearchTask(); + unloadTempData(); + }); + } super.dispose(); } @@ -477,9 +483,15 @@ public class SearchDialog extends CommonSearchDialog { loadAllButton.setEnabled(!complete); loadMoreButton.setEnabled(!complete); updateProgressLabel(complete); + unloadTempData(); progressFinishedCommon(); } + private void unloadTempData() { + mainWindow.getWrapper().unloadClasses(); + System.gc(); + } + private static Flowable onTextFieldChanges(final JTextField textField) { return Flowable.create(emitter -> { DocumentListener listener = new DocumentListener() { diff --git a/jadx-gui/src/main/java/jadx/gui/ui/panel/ContentPanel.java b/jadx-gui/src/main/java/jadx/gui/ui/panel/ContentPanel.java index 1056f217f..a10aae210 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/panel/ContentPanel.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/panel/ContentPanel.java @@ -12,8 +12,8 @@ public abstract class ContentPanel extends JPanel { private static final long serialVersionUID = 3237031760631677822L; - protected final TabbedPane tabbedPane; - protected final JNode node; + protected TabbedPane tabbedPane; + protected JNode node; protected ContentPanel(TabbedPane panel, JNode jnode) { tabbedPane = panel; @@ -44,4 +44,9 @@ public abstract class ContentPanel extends JPanel { } return node.getName(); } + + public void dispose() { + tabbedPane = null; + node = null; + } } diff --git a/jadx-gui/src/main/java/jadx/gui/ui/treenodes/SummaryNode.java b/jadx-gui/src/main/java/jadx/gui/ui/treenodes/SummaryNode.java index 9c7d2265b..cd395b593 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/treenodes/SummaryNode.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/treenodes/SummaryNode.java @@ -109,13 +109,17 @@ public class SummaryNode extends JNode { private void writeDecompilationSummary(StringEscapeUtils.Builder builder) { builder.append("

Decompilation

"); - List classes = wrapper.getRootNode().getClasses(false); + List classes = wrapper.getRootNode().getClassesWithoutInner(); int classesCount = classes.size(); + long notLoadedClasses = classes.stream().filter(c -> c.getState() == ProcessState.NOT_LOADED).count(); + long loadedClasses = classes.stream().filter(c -> c.getState() == ProcessState.LOADED).count(); long processedClasses = classes.stream().filter(c -> c.getState() == ProcessState.PROCESS_COMPLETE).count(); long generatedClasses = classes.stream().filter(c -> c.getState() == ProcessState.GENERATED_AND_UNLOADED).count(); builder.append("
    "); builder.append("
  • Top level classes: " + classesCount + "
  • "); - builder.append("
  • At process stage: " + valueAndPercent(processedClasses, classesCount) + "
  • "); + builder.append("
  • Not loaded: " + valueAndPercent(notLoadedClasses, classesCount) + "
  • "); + builder.append("
  • Loaded: " + valueAndPercent(loadedClasses, classesCount) + "
  • "); + builder.append("
  • Processed: " + valueAndPercent(processedClasses, classesCount) + "
  • "); builder.append("
  • Code generated: " + valueAndPercent(generatedClasses, classesCount) + "
  • "); builder.append("
");