Wayland: fix crash when an output global is removed

surfaceHandleEnter() and surfaceHandleLeave() passed the event's output
argument straight to wl_output_get_user_data(). Wayland delivers object
arguments as NULL when the object was destroyed client-side before the
event was dispatched, even for arguments declared non-nullable, so this
segfaults whenever a wl_output global goes away while a window is open.
Easily reproduced with a hardware KVM or a monitor input switch.

Also fixes two pre-existing problems in surfaceHandleLeave():

- The removal loop stopped at monitorsCount - 1, so it never compared the
  last entry, and the trailing unconditional
  window->wl.monitors[--window->wl.monitorsCount] = NULL
  dropped the last monitor whether or not it was the one that was left.
  A leave for a monitor not in the list therefore evicted an unrelated
  one.
- That same statement ran even when monitorsCount was 0, writing to
  monitors[-1] and leaving the count negative.

Both are replaced with the remove_i_from_array() loop already used for
this array in registryHandleGlobalRemove().

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Wes Brown 2026-08-14 13:11:51 -05:00
parent b71ef95ec7
commit 2dbe5df2d8

22
glfw/wl_window.c vendored
View file

@ -549,7 +549,15 @@ static const struct zxdg_toplevel_decoration_v1_listener xdgDecorationListener =
static void
surfaceHandleEnter(void *data, struct wl_surface *surface UNUSED, struct wl_output *output) {
_GLFWwindow *window = data;
// output is NULL when the wl_output global it refers to was removed before
// this event was dispatched. Wayland delivers destroyed objects as NULL even
// for arguments declared non-nullable, so this must be handled.
// See https://wayland.freedesktop.org/docs/html/apc.html
if (!output) return;
_GLFWmonitor *monitor = wl_output_get_user_data(output);
// never store NULL in window->wl.monitors as checkScaleChange() dereferences
// every entry unconditionally
if (!monitor) return;
if (window->wl.monitorsCount + 1 > window->wl.monitorsSize) {
++window->wl.monitorsSize;
@ -567,15 +575,17 @@ surfaceHandleEnter(void *data, struct wl_surface *surface UNUSED, struct wl_outp
static void
surfaceHandleLeave(void *data, struct wl_surface *surface UNUSED, struct wl_output *output) {
_GLFWwindow *window = data;
// output is NULL when the wl_output global it refers to was removed before
// this event was dispatched, see the comment in surfaceHandleEnter(). In that
// case registryHandleGlobalRemove() has already dropped the monitor from
// window->wl.monitors, so there is nothing left to do here.
if (!output) return;
_GLFWmonitor *monitor = wl_output_get_user_data(output);
bool found;
int i;
if (!monitor) return;
for (i = 0, found = false; i < window->wl.monitorsCount - 1; ++i) {
if (monitor == window->wl.monitors[i]) found = true;
if (found) window->wl.monitors[i] = window->wl.monitors[i + 1];
for (int i = window->wl.monitorsCount - 1; i >= 0; i--) {
if (window->wl.monitors[i] == monitor) { remove_i_from_array(window->wl.monitors, i, window->wl.monitorsCount); }
}
window->wl.monitors[--window->wl.monitorsCount] = NULL;
if (checkScaleChange(window)) {
debug("Scale changed to %.3f for window %llu in surfaceHandleLeave\n", _glfwWaylandWindowScale(window), window->id);