This is an automated email from the git hooks/post-receive script.
git pushed a commit to branch wl/browser-all
in repository enlightenment.
View the commit online.
commit 06bc778fc5d0eed2a82381d9bf3e77b671b30c0d
Author: Cedric BAIL <[email protected]>
AuthorDate: Sun Aug 16 23:25:25 2026 -0600
e_comp_wl, e_pixmap, e_comp_wl_extensions - listeners that outlive what they listen to
Brave and Chromium crashed the compositor about half the time, always the same
way and never anywhere near the cause:
#1 wl_signal_emit (wayland-server-core.h:527) l->notify(l, data)
#2 _e_comp_wl_buffer_cb_destroy (e_comp_wl.c:1324)
calling an address that is not code. The registers say what it was: the called
address is exactly listener+0x10, and a wl_listener is a 16-byte wl_list
followed by notify - so the "listener" being called was a wl_list that had been
wl_list_init'd, its own fields pointing at itself. Something else's list head,
reached by walking a corrupted list.
AddressSanitizer found the first one directly. It could not report until E's own
SIGSEGV handler was out of the way, which needs no code change - e_main.c skips
installing it when NOTIFY_SOCKET is set.
* xdg_activation_v1 freed a token from activate() and left the client's
token object pointing at it. The object and the token outlive each other in
both directions: a committed token is redeemed by another client while the
one that made it still holds the object and is entitled to destroy it
whenever it likes, and destroying it then read freed memory. Clear the
object's user data when the token goes, and forget the object when it goes.
Reachable by any client that asks for an activation token, which is every
browser - it is how "open this link in the browser that is already running"
works.
* Three destroy-listener callbacks forgot their subject without unlinking
themselves: the surface state's buffer listener in e_comp_wl.c, and
e_pixmap's buffer and held-buffer listeners. The list they are in belongs
to the buffer being destroyed and is freed with it, so a listener left
behind leaves every *other* listener in that list holding a prev/next into
freed memory, and holds two into it itself. The next wl_list_remove
anywhere in that set writes through them.
libwayland's convention is that a destroy listener unlinks itself; these
now do, and re-init the link so removing it again is harmless. The call
sites that check .notify before removing keep working unchanged.
Measured: eight consecutive browser runs, no cores. Before, four runs produced
two crashes. The remaining browser failures are a Chromium unmaximise stall
with no crash in it, chased separately.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
---
src/bin/e_comp_wl.c | 8 ++++++++
src/bin/e_comp_wl_extensions.c | 12 +++++++++++-
src/bin/e_pixmap.c | 6 ++++++
src/tests/wayland/test_browser.c | 20 +++++++++++++++++---
4 files changed, 42 insertions(+), 4 deletions(-)
diff --git a/src/bin/e_comp_wl.c b/src/bin/e_comp_wl.c
index fb5955dc1..eae1e6936 100644
--- a/src/bin/e_comp_wl.c
+++ b/src/bin/e_comp_wl.c
@@ -2007,6 +2007,14 @@ _e_comp_wl_surface_state_cb_buffer_destroy(struct wl_listener *listener, void *d
state =
container_of(listener, E_Comp_Wl_Surface_State, buffer_destroy_listener);
state->buffer = NULL;
+ /* Unlink. The signal we are being called from belongs to the buffer being
+ * destroyed and is about to be freed with it, so a listener left in it
+ * leaves every other listener in that list holding a prev/next into freed
+ * memory - and this listener holding two into it. The corruption surfaces
+ * much later, in some other buffer's wl_signal_emit, calling a notify that
+ * is no longer a function. Re-init so removing it again is harmless. */
+ wl_list_remove(&state->buffer_destroy_listener.link);
+ wl_list_init(&state->buffer_destroy_listener.link);
}
static void
diff --git a/src/bin/e_comp_wl_extensions.c b/src/bin/e_comp_wl_extensions.c
index dc6f93dc8..ef6588556 100644
--- a/src/bin/e_comp_wl_extensions.c
+++ b/src/bin/e_comp_wl_extensions.c
@@ -1051,6 +1051,11 @@ static void
_xdg_activation_token_free(Activation_Token *t)
{
if (!t) return;
+ /* Whoever destroys the object next must not find this pointer. The two
+ * outlive each other in both directions: a committed token is redeemed by
+ * activate(), which frees it while the client that made it is still holding
+ * the object and entitled to destroy it whenever it likes. */
+ if (t->res) wl_resource_set_user_data(t->res, NULL);
eina_stringshare_del(t->app_id);
free(t);
}
@@ -1069,7 +1074,12 @@ _e_xdg_activation_token_res_destroy(struct wl_resource *resource)
if (!t) return;
/* A committed token outlives its object on purpose: the string has been
* handed over and the other client still has to be able to redeem it.
- * Only an uncommitted one dies with the object it was never used from. */
+ * Only an uncommitted one dies with the object it was never used from.
+ *
+ * Forget the object either way. It is being destroyed, and a token that
+ * stays behind holding a pointer to it hands that pointer to
+ * wl_resource_set_user_data when it is eventually redeemed. */
+ t->res = NULL;
if (t->committed) return;
_xdg_activation_token_free(t);
}
diff --git a/src/bin/e_pixmap.c b/src/bin/e_pixmap.c
index e5ae0baf2..dc6713656 100644
--- a/src/bin/e_pixmap.c
+++ b/src/bin/e_pixmap.c
@@ -95,6 +95,10 @@ _e_pixmap_cb_buffer_destroy(struct wl_listener *listener, void *data EINA_UNUSED
cp = container_of(listener, E_Pixmap, buffer_destroy_listener);
cp->buffer = NULL;
+ /* Unlink as well as forget: the list this is in dies with the buffer, and
+ * leaving a link in it corrupts every other listener that was in it. */
+ wl_list_remove(&cp->buffer_destroy_listener.link);
+ wl_list_init(&cp->buffer_destroy_listener.link);
cp->buffer_destroy_listener.notify = NULL;
}
@@ -106,6 +110,8 @@ _e_pixmap_cb_held_buffer_destroy(struct wl_listener *listener, void *data EINA_U
cp = container_of(listener, E_Pixmap, held_buffer_destroy_listener);
if (cp->data) e_comp_wl_shm_access_unregister(cp->data);
cp->held_buffer = NULL;
+ wl_list_remove(&cp->held_buffer_destroy_listener.link);
+ wl_list_init(&cp->held_buffer_destroy_listener.link);
cp->held_buffer_destroy_listener.notify = NULL;
}
#endif
diff --git a/src/tests/wayland/test_browser.c b/src/tests/wayland/test_browser.c
index c6b534507..fdc35a311 100644
--- a/src/tests/wayland/test_browser.c
+++ b/src/tests/wayland/test_browser.c
@@ -27,9 +27,16 @@
#include "e_wl_testkit.h"
-/* Browsers are slow to start and this may be a cold profile. */
-#define APPEAR_MS 40000
-#define SETTLE_MS 10000
+/* Browsers are slow to start and this may be a cold profile. Chromium has been
+ * measured taking seven seconds to answer a configure in a nested compositor
+ * with software rendering, so these are generous on purpose - a deadline is
+ * here to turn a hang into a message, not to police how fast a browser is.
+ * E_TEST_BROWSER_SLOW multiplies both, for a sanitizer build or a slow
+ * machine. */
+static int appear_ms = 40000;
+static int settle_ms = 30000;
+#define APPEAR_MS appear_ms
+#define SETTLE_MS settle_ms
#define MAXIMIZED (WL_TEST_CLIENT_STATE_MAXIMIZED_H | \
WL_TEST_CLIENT_STATE_MAXIMIZED_V)
@@ -45,6 +52,13 @@ main(int argc, char **argv)
char app_id[256];
const char *want = (argc > 1) ? argv[1] : "firefox";
+ {
+ const char *slow = getenv("E_TEST_BROWSER_SLOW");
+ int mult = slow ? atoi(slow) : 1;
+
+ if (mult > 1) appear_ms *= mult, settle_ms *= mult;
+ }
+
tk = tk_connect("test-browser");
c = tk_wait_window(tk, want, APPEAR_MS);
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.