Skip to content

Commit

Permalink
fix(gui): try to prevent jadx node leaks in UI objects
Browse files Browse the repository at this point in the history
  • Loading branch information
skylot committed Jun 3, 2022
1 parent d6c851e commit 0c3afcc
Show file tree
Hide file tree
Showing 13 changed files with 147 additions and 32 deletions.
11 changes: 9 additions & 2 deletions jadx-gui/src/main/java/jadx/gui/jobs/BackgroundExecutor.java
Original file line number Diff line number Diff line change
Expand Up @@ -212,8 +212,15 @@ private void performCancel(ThreadPoolExecutor executor) throws InterruptedExcept
// 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<TaskStatus> buildCancelCheck(long startTime) {
Expand Down
1 change: 1 addition & 0 deletions jadx-gui/src/main/java/jadx/gui/ui/TabbedPane.java
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,7 @@ private void addContentPanel(ContentPanel contentPanel) {
public void closeCodePanel(ContentPanel contentPanel) {
openTabs.remove(contentPanel.getNode());
remove(contentPanel);
contentPanel.dispose();
}

@Nullable
Expand Down
39 changes: 37 additions & 2 deletions jadx-gui/src/main/java/jadx/gui/ui/codearea/AbstractCodeArea.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -8,15 +9,20 @@
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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -348,4 +354,33 @@ public JClass getJClass() {
}
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();
}
}
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -170,4 +171,16 @@ public void restoreEditorViewState(EditorViewState viewState) {
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();
}
}
6 changes: 6 additions & 0 deletions jadx-gui/src/main/java/jadx/gui/ui/codearea/CodeArea.java
Original file line number Diff line number Diff line change
Expand Up @@ -282,4 +282,10 @@ public JadxWrapper getJadxWrapper() {
public JadxProject getProject() {
return getMainWindow().getProject();
}

@Override
public void dispose() {
super.dispose();
cachedCodeInfo = null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,9 @@ public void restoreEditorViewState(EditorViewState viewState) {
codePanel.getCodeScrollPane().getViewport().setViewPosition(viewState.getViewPoint());
codePanel.getCodeArea().setCaretPosition(viewState.getCaretPos());
}

@Override
public void dispose() {
codePanel.dispose();
}
}
4 changes: 4 additions & 0 deletions jadx-gui/src/main/java/jadx/gui/ui/codearea/CodePanel.java
Original file line number Diff line number Diff line change
Expand Up @@ -195,4 +195,8 @@ private JadxSettings getSettings() {
return this.codeArea.getContentPanel().getTabbedPane()
.getMainWindow().getSettings();
}

public void dispose() {
codeArea.dispose();
}
}
27 changes: 13 additions & 14 deletions jadx-gui/src/main/java/jadx/gui/ui/codearea/CommentAction.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
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;
Expand All @@ -26,7 +25,6 @@
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;
Expand All @@ -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
Expand All @@ -83,7 +82,7 @@ private void showCommentDialog(ICodeComment codeComment) {
*/
@Nullable
private ICodeComment getCommentRef(int pos) {
if (pos == -1 || this.topCls == null) {
if (pos == -1) {
return null;
}
try {
Expand Down
15 changes: 12 additions & 3 deletions jadx-gui/src/main/java/jadx/gui/ui/codearea/JNodeAction.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -16,7 +17,7 @@
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) {
Expand All @@ -26,7 +27,7 @@ public JNodeAction(String name, CodeArea codeArea) {

public abstract void runAction(JNode node);

public boolean isActionEnabled(JNode node) {
public boolean isActionEnabled(@Nullable JNode node) {
return node != null;
}

Expand All @@ -47,12 +48,20 @@ public void actionPerformed(ActionEvent e) {
runAction(node);
}

public void changeNode(JNode node) {
public void changeNode(@Nullable JNode node) {
this.node = node;
setEnabled(isActionEnabled(node));
}

public CodeArea getCodeArea() {
return codeArea;
}

public void dispose() {
node = null;
codeArea = null;
for (PropertyChangeListener changeListener : getPropertyChangeListeners()) {
removePropertyChangeListener(changeListener);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,40 @@
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<JNodeAction> actions = new ArrayList<>();

public JNodePopupListener(CodeArea codeArea) {
this.codeArea = codeArea;
}

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) {
JNode node = codeArea.getNodeUnderMouse();
actions.forEach(action -> action.changeNode(node));
updateNode(codeArea.getNodeUnderMouse());
}

public void addActions(JNodeAction action) {
actions.add(action);
@Override
public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
updateNode(null);
}

@Override
public void popupMenuCanceled(PopupMenuEvent e) {
updateNode(null);
}
}
14 changes: 13 additions & 1 deletion jadx-gui/src/main/java/jadx/gui/ui/dialog/SearchDialog.java
Original file line number Diff line number Diff line change
Expand Up @@ -143,12 +143,18 @@ private SearchDialog(MainWindow mainWindow, SearchPreset preset, Set<SearchOptio

@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();
}

Expand Down Expand Up @@ -477,9 +483,15 @@ private synchronized void searchComplete() {
loadAllButton.setEnabled(!complete);
loadMoreButton.setEnabled(!complete);
updateProgressLabel(complete);
unloadTempData();
progressFinishedCommon();
}

private void unloadTempData() {
mainWindow.getWrapper().unloadClasses();
System.gc();
}

private static Flowable<String> onTextFieldChanges(final JTextField textField) {
return Flowable.<String>create(emitter -> {
DocumentListener listener = new DocumentListener() {
Expand Down
9 changes: 7 additions & 2 deletions jadx-gui/src/main/java/jadx/gui/ui/panel/ContentPanel.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -44,4 +44,9 @@ public String getTabTooltip() {
}
return node.getName();
}

public void dispose() {
tabbedPane = null;
node = null;
}
}
8 changes: 6 additions & 2 deletions jadx-gui/src/main/java/jadx/gui/ui/treenodes/SummaryNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -109,13 +109,17 @@ private void writeInputSummary(StringEscapeUtils.Builder builder) throws IOExcep

private void writeDecompilationSummary(StringEscapeUtils.Builder builder) {
builder.append("<h2>Decompilation</h2>");
List<ClassNode> classes = wrapper.getRootNode().getClasses(false);
List<ClassNode> 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("<ul>");
builder.append("<li>Top level classes: " + classesCount + "</li>");
builder.append("<li>At process stage: " + valueAndPercent(processedClasses, classesCount) + "</li>");
builder.append("<li>Not loaded: " + valueAndPercent(notLoadedClasses, classesCount) + "</li>");
builder.append("<li>Loaded: " + valueAndPercent(loadedClasses, classesCount) + "</li>");
builder.append("<li>Processed: " + valueAndPercent(processedClasses, classesCount) + "</li>");
builder.append("<li>Code generated: " + valueAndPercent(generatedClasses, classesCount) + "</li>");
builder.append("</ul>");

Expand Down

0 comments on commit 0c3afcc

Please sign in to comment.