fix(gui): try to prevent jadx node leaks in UI objects
This commit is contained in:
@@ -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<TaskStatus> buildCancelCheck(long startTime) {
|
||||
|
||||
@@ -309,6 +309,7 @@ public class TabbedPane extends JTabbedPane {
|
||||
public void closeCodePanel(ContentPanel contentPanel) {
|
||||
openTabs.remove(contentPanel.getNode());
|
||||
remove(contentPanel);
|
||||
contentPanel.dispose();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,4 +282,10 @@ public final class CodeArea extends AbstractCodeArea {
|
||||
public JadxProject getProject() {
|
||||
return getMainWindow().getProject();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
super.dispose();
|
||||
cachedCodeInfo = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,4 +195,8 @@ public class CodePanel extends JPanel {
|
||||
return this.codeArea.getContentPanel().getTabbedPane()
|
||||
.getMainWindow().getSettings();
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
codeArea.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<JNodeAction> 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<String> onTextFieldChanges(final JTextField textField) {
|
||||
return Flowable.<String>create(emitter -> {
|
||||
DocumentListener listener = new DocumentListener() {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,13 +109,17 @@ public class SummaryNode extends JNode {
|
||||
|
||||
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>");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user