kick/tests/xclass.c
Andrew Hamilton a7a38fadca x11: guard set_class NULL app_id/title for taskbar handles
Deleting a window's WM_CLASS emits set_class with class==NULL while the
title is set; handle_xs_set_class passed the raw NULLs into
wlr_foreign_toplevel_handle_v1_set_app_id / ext update_state, which
strdup(NULL) -> SIGSEGV (exit 139; UBSan flags the nonnull argument
first). The sibling set_title handler and every other mirror site use
foreign_str(); apply the same guard here.

Add tests/xclass.c (title set, WM_CLASS set-then-deleted probe) and a
headless smoke test asserting the compositor survives the delete with
no crash indicators (fails pre-fix: compositor dead).
2026-09-04 08:39:37 +10:00

53 lines
1.8 KiB
C

/* xclass: WM_CLASS set-then-delete probe.
*
* Maps a titled X11 window with NO class, then sets WM_CLASS, then deletes
* it: the delete emits set_class with class==NULL while the title is set.
* The compositor must mirror "" (not raw NULL) onto the taskbar handles --
* passing NULL into the foreign-toplevel set_app_id/update_state calls
* segfaults inside wlroots' strdup (regression test for that crash).
*
* usage: xclass <hold-seconds> (total runtime ~= 4 + max(hold-4, 0))
*/
#define _POSIX_C_SOURCE 200809L
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/Xutil.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char **argv) {
int hold = argc > 1 ? atoi(argv[1]) : 6;
Display *d = XOpenDisplay(NULL);
if (!d) {
fprintf(stderr, "xclass: cannot open display\n");
return 1;
}
int scr = DefaultScreen(d);
Window w = XCreateSimpleWindow(d, RootWindow(d, scr), 0, 0, 400, 300, 0,
BlackPixel(d, scr), 0x804020UL);
XStoreName(d, w, "xclass-frame");
pid_t pid = getpid();
Atom apid = XInternAtom(d, "_NET_WM_PID", False);
XChangeProperty(d, w, apid, XA_CARDINAL, 32, PropModeReplace,
(unsigned char *)&pid, 1);
/* NOTE: no WM_CLASS yet (class == NULL server-side). */
XMapWindow(d, w);
XFlush(d);
fprintf(stderr, "[xclass] mapped, no class\n");
sleep(2);
XClassHint *ch = XAllocClassHint();
ch->res_name = "xclass-probe";
ch->res_class = "XClassProbe";
XSetClassHint(d, w, ch);
XFree(ch);
XFlush(d);
fprintf(stderr, "[xclass] class set\n");
sleep(2);
Atom wmclass = XInternAtom(d, "WM_CLASS", False);
XDeleteProperty(d, w, wmclass);
XFlush(d);
fprintf(stderr, "[xclass] class deleted (NULL)\n");
sleep(hold > 4 ? (unsigned)(hold - 4) : 0);
return 0;
}