From 15ea9a56b996bb5e6919a68bc1b225bcd89da356 Mon Sep 17 00:00:00 2001 From: eason <85663565+mango766@users.noreply.github.com> Date: Thu, 19 Mar 2026 03:05:49 +0800 Subject: [PATCH] fix: handle null bounds in WindowLocation to prevent NPE on dialog dispose (PR #2826) * fix: handle null bounds in WindowLocation to prevent NPE on dialog dispose The equals(), hashCode(), and toString() methods in WindowLocation could throw NullPointerException when bounds is null. This happens when a dialog window is disposed before its bounds are fully initialized (e.g., on macOS when closing the search dialog). Use Objects.equals()/Objects.hashCode() for null-safe comparisons and add a null guard in toString(). Fixes #2571 * additional null checks and null annotations --------- Co-authored-by: easonysliu Co-authored-by: Skylot <118523+skylot@users.noreply.github.com> --- .../java/jadx/gui/settings/JadxSettings.java | 31 +++++++++++++------ .../jadx/gui/settings/WindowLocation.java | 28 ++++++++--------- 2 files changed, 34 insertions(+), 25 deletions(-) diff --git a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java index 8903da2a8..5ea18ccd5 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java @@ -220,38 +220,49 @@ public class JadxSettings { recentProjects.remove(projectPath); } + private static String makeWindowId(Window window) { + return window.getClass().getSimpleName(); + } + + @SuppressWarnings("ConstantValue") public void saveWindowPos(Window window) { + if (window == null) { + return; + } synchronized (dataWriteSync) { - WindowLocation pos = new WindowLocation(window.getClass().getSimpleName(), window.getBounds()); - settingsData.getWindowPos().put(pos.getWindowId(), pos); + Rectangle bounds = window.getBounds(); + if (bounds != null) { + WindowLocation pos = new WindowLocation(makeWindowId(window), bounds); + settingsData.getWindowPos().put(pos.getWindowId(), pos); + } } } public boolean loadWindowPos(Window window) { - Map windowPos = settingsData.getWindowPos(); - WindowLocation pos = windowPos.get(window.getClass().getSimpleName()); - if (pos == null || pos.getBounds() == null) { + String windowId = makeWindowId(window); + WindowLocation pos = settingsData.getWindowPos().get(windowId); + if (pos == null) { return false; } - if (!isAccessibleInAnyScreen(pos)) { + Rectangle bounds = pos.getBounds(); + if (bounds == null || !isAccessibleInAnyScreen(windowId, bounds)) { return false; } - window.setBounds(pos.getBounds()); + window.setBounds(bounds); if (window instanceof MainWindow) { ((JFrame) window).setExtendedState(getMainWindowExtendedState()); } return true; } - private static boolean isAccessibleInAnyScreen(WindowLocation pos) { - Rectangle windowBounds = pos.getBounds(); + private static boolean isAccessibleInAnyScreen(String windowId, Rectangle windowBounds) { for (GraphicsDevice gd : GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices()) { Rectangle screenBounds = gd.getDefaultConfiguration().getBounds(); if (screenBounds.intersects(windowBounds)) { return true; } } - LOG.debug("Window saved position was ignored: {}", pos); + LOG.debug("Window saved position was ignored: {}, bounds: {}", windowId, windowBounds); return false; } diff --git a/jadx-gui/src/main/java/jadx/gui/settings/WindowLocation.java b/jadx-gui/src/main/java/jadx/gui/settings/WindowLocation.java index 4c9adcee1..cf6924dc2 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/WindowLocation.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/WindowLocation.java @@ -1,16 +1,20 @@ package jadx.gui.settings; import java.awt.Rectangle; +import java.util.Objects; +import org.jetbrains.annotations.Nullable; + +@SuppressWarnings("unused") public class WindowLocation { private String windowId; - private Rectangle bounds; + private @Nullable Rectangle bounds; - // Don't remove. Used in json serialization + // Don't remove. Used in JSON serialization public WindowLocation() { } - public WindowLocation(String windowId, Rectangle bounds) { + public WindowLocation(String windowId, @Nullable Rectangle bounds) { this.windowId = windowId; this.bounds = bounds; } @@ -23,37 +27,31 @@ public class WindowLocation { this.windowId = windowId; } - public Rectangle getBounds() { + public @Nullable Rectangle getBounds() { return bounds; } - public void setBounds(Rectangle bounds) { + public void setBounds(@Nullable Rectangle bounds) { this.bounds = bounds; } @Override public int hashCode() { - return windowId.hashCode(); + return Objects.hashCode(windowId); } @Override public final boolean equals(Object o) { if (o instanceof WindowLocation) { WindowLocation that = (WindowLocation) o; - return windowId.equals(that.windowId) && bounds.equals(that.bounds); + return Objects.equals(windowId, that.windowId) + && Objects.equals(bounds, that.bounds); } return false; - } @Override public String toString() { - return "WindowLocation{" - + "id='" + windowId + '\'' - + ", x=" + bounds.getX() - + ", y=" + bounds.getY() - + ", width=" + bounds.getWidth() - + ", height=" + bounds.getHeight() - + '}'; + return "WindowLocation{id=" + windowId + ", bounds=" + bounds + '}'; } }