wayland: NULL fb->scene_buf/tree when wlroots auto-destroys cascade

ASan caught a SEGV in wl_list_remove inside wlr_scene_node_destroy
called from wl_view_destroy_backing_impl when closing a dock-icon
rename input dialog.

The flow:
  1. wInputDialog opens.  fake_leader fb_FAKE created with tree+scene_buf;
     panel content fb_PANEL created with scene_buf parented under
     fb_FAKE's tree; wManageInternalWindow wraps fake_leader in a
     WMaker frame fb_FRAME (also with tree).
  2. Dialog closes.  wUnmanageWindow runs wl_frame_destroy on fb_FRAME.
     wlr_scene_node_destroy on fb_FRAME->tree cascades — wlroots
     auto-destroys fb_FAKE->tree, fb_FAKE->scene_buf, AND
     fb_PANEL->scene_buf.
  3. WMDestroyInputPanel later calls destroyView →
     wl_view_destroy_backing_impl(panel_id), which tries
     wlr_scene_node_destroy on fb_PANEL->scene_buf — already destroyed
     and freed.  wl_list_remove on a zeroed link → SEGV.

Fix: register destroy listeners on every fb->scene_buf and fb->tree
when they're created.  When wlroots destroys the node (typically via
parent-tree cascade), the listener fires and NULLs the field.
wl_view_destroy_backing_impl and wl_frame_destroy already check for
NULL before destroying, so the second-destroy is now safely skipped.

Same pattern as the earlier fix for view->scene_node (commit 8cb56fc),
applied to all wl_frame_buf scene-graph fields.
This commit is contained in:
Window Maker 2026-05-24 10:06:36 +02:00
parent c55d42c169
commit 852fb2adeb
1 changed files with 67 additions and 3 deletions

View File

@ -300,6 +300,13 @@ struct wl_frame_buf {
pixman_image_t *image; /* ARGB32 pixel data */
struct wlr_scene_buffer *scene_buf; /* scene graph node */
struct wlr_scene_tree *tree; /* grouping tree (top-level frames only) */
/* Destroy listeners on scene_buf and tree so they get NULL'd when
* wlroots auto-destroys them (e.g. via cascade from parent tree
* destruction during wl_frame_destroy). Without these, code that
* later tries to wlr_scene_node_destroy them again segfaults inside
* wl_list_remove on already-zeroed list links. */
struct wl_listener scene_buf_destroy;
struct wl_listener tree_destroy;
WNativeWindow id; /* synthetic ID (>= 0x10000000) */
WNativeWindow parent_id; /* parent frame_buf ID (0 = root) */
int x, y; /* position in output coords */
@ -4023,6 +4030,46 @@ frame_buf_find(WNativeWindow id)
return NULL;
}
/*
* fb_scene_buf_attach / fb_tree_attach
*
* Register destroy listeners on a scene_buf or tree so that fb->scene_buf
* (or fb->tree) is NULL'd when wlroots auto-destroys the node — typically
* via cascade when an ancestor scene_tree is destroyed. Without this,
* follow-up cleanup paths (wl_view_destroy_backing_impl, wl_frame_destroy)
* call wlr_scene_node_destroy on a freed node and segfault inside
* wl_list_remove against a zeroed list link.
*/
static void fb_scene_buf_destroy_handler(struct wl_listener *l, void *d)
{
(void)d;
struct wl_frame_buf *fb = wl_container_of(l, fb, scene_buf_destroy);
fb->scene_buf = NULL;
wl_list_remove(&fb->scene_buf_destroy.link);
}
static void fb_tree_destroy_handler(struct wl_listener *l, void *d)
{
(void)d;
struct wl_frame_buf *fb = wl_container_of(l, fb, tree_destroy);
fb->tree = NULL;
/* The tree's destroy cascades into scene_buf — its destroy handler
* will fire too and NULL fb->scene_buf separately. */
wl_list_remove(&fb->tree_destroy.link);
}
static void fb_attach_scene_buf_listener(struct wl_frame_buf *fb)
{
if (!fb || !fb->scene_buf) return;
fb->scene_buf_destroy.notify = fb_scene_buf_destroy_handler;
wl_signal_add(&fb->scene_buf->node.events.destroy,
&fb->scene_buf_destroy);
}
static void fb_attach_tree_listener(struct wl_frame_buf *fb)
{
if (!fb || !fb->tree) return;
fb->tree_destroy.notify = fb_tree_destroy_handler;
wl_signal_add(&fb->tree->node.events.destroy, &fb->tree_destroy);
}
/* Recursively update fb->x, fb->y for all descendants of `parent`, used
* to keep absolute-position tracking in sync after the parent moves.
* Hit-testing (frame_buf_at, frame_buf_button_at) and click coordinate
@ -4229,9 +4276,11 @@ wl_frame_create_toplevel(WScreen *scr,
? wl_state.level_trees[WL_LAYER_NORMAL] : &wl_state.scene->tree;
fb->tree = wlr_scene_tree_create(_parent);
if (fb->tree) {
fb_attach_tree_listener(fb);
wlr_scene_node_set_position(&fb->tree->node, x, y);
fb->scene_buf = wlr_scene_buffer_create(fb->tree, &fb->base);
if (fb->scene_buf) {
fb_attach_scene_buf_listener(fb);
wlr_scene_node_set_position(&fb->scene_buf->node, 0, 0);
wlr_scene_node_set_enabled(&fb->scene_buf->node, false);
}
@ -4296,6 +4345,7 @@ wl_frame_create_child(WNativeWindow parent_win, WScreen *scr,
? parent->tree : &wl_state.scene->tree;
fb->scene_buf = wlr_scene_buffer_create(ptree, &fb->base);
if (fb->scene_buf) {
fb_attach_scene_buf_listener(fb);
wlr_scene_node_set_position(&fb->scene_buf->node, x, y);
wlr_scene_node_set_enabled(&fb->scene_buf->node, false);
}
@ -7013,6 +7063,7 @@ wl_client_reparent(WNativeWindow client, WNativeWindow new_parent, int x, int y)
: &wl_state.scene->tree);
fb->scene_buf = wlr_scene_buffer_create(_wp, &fb->base);
if (fb->scene_buf) {
fb_attach_scene_buf_listener(fb);
/* If under the frame tree, use relative coords; otherwise absolute. */
if (parent_fb->tree)
wlr_scene_node_set_position(&fb->scene_buf->node, x, y);
@ -7072,7 +7123,12 @@ void wl_view_destroy_backing_impl(unsigned long id)
struct wl_frame_buf *fb = frame_buf_find((WNativeWindow)id);
if (!fb) return;
wl_list_remove(&fb->link);
wlr_scene_node_destroy(&fb->scene_buf->node);
/* fb->scene_buf may have been auto-destroyed by wlroots already if a
* parent tree was destroyed earlier (e.g. wl_frame_destroy on the
* surrounding WMaker frame). The destroy listener NULLs scene_buf
* in that case. */
if (fb->scene_buf)
wlr_scene_node_destroy(&fb->scene_buf->node);
wlr_buffer_drop(&fb->base);
}
@ -7250,9 +7306,11 @@ static WNativeWindow wl_fake_leader_create(WScreen *scr)
? wl_state.level_trees[WL_LAYER_NORMAL] : &wl_state.scene->tree;
fb->tree = wlr_scene_tree_create(_pp);
if (fb->tree) {
fb_attach_tree_listener(fb);
wlr_scene_node_set_position(&fb->tree->node, 0, 0);
fb->scene_buf = wlr_scene_buffer_create(fb->tree, &fb->base);
if (fb->scene_buf) {
fb_attach_scene_buf_listener(fb);
wlr_scene_node_set_position(&fb->scene_buf->node, 0, 0);
wlr_scene_node_set_enabled(&fb->scene_buf->node, false);
}
@ -7480,9 +7538,12 @@ wl_screen_create_internals(WScreen *scr)
? wl_state.level_trees[WL_LAYER_OVERLAY] : &wl_state.scene->tree;
fb->tree = wlr_scene_tree_create(parent);
if (fb->tree) {
fb_attach_tree_listener(fb);
fb->scene_buf = wlr_scene_buffer_create(fb->tree, &fb->base);
if (fb->scene_buf)
if (fb->scene_buf) {
fb_attach_scene_buf_listener(fb);
wlr_scene_node_set_enabled(&fb->scene_buf->node, false);
}
wlr_scene_node_set_enabled(&fb->tree->node, false);
}
wl_list_insert(&wl_state.frame_list, &fb->link);
@ -8583,10 +8644,13 @@ static void wl_balloon_init(WScreen *scr)
? wl_state.level_trees[WL_LAYER_OVERLAY] : &wl_state.scene->tree;
fb->tree = wlr_scene_tree_create(parent);
if (fb->tree) {
fb_attach_tree_listener(fb);
wlr_scene_node_set_position(&fb->tree->node, 0, 0);
fb->scene_buf = wlr_scene_buffer_create(fb->tree, &fb->base);
if (fb->scene_buf)
if (fb->scene_buf) {
fb_attach_scene_buf_listener(fb);
wlr_scene_node_set_position(&fb->scene_buf->node, 0, 0);
}
wlr_scene_node_set_enabled(&fb->tree->node, false);
}