# Qt6 Wayland `wl_proxy_add_listener` null+8 Crash — Static Diagnosis (Phases A–C) **Status:** STATIC DIAGNOSIS COMPLETE. Patch verdict issued. Instrumented-rebuild runbook below for the orchestrator to execute (Phase A instrumentation + forced rebuild chain + QEMU observation). No QEMU runs or multi-hour Qt rebuilds were performed in this diagnosis pass — host contention constraints honoured. **Date:** 2026-07-20 **Branch:** 0.3.1 **Scope:** Phases A–C of `WAYLAND-IMPLEMENTATION-PLAN.md` § "Current Blocker" --- ## 1. VERDICT (root cause statement) **The NULL proxy at the crash is the `wl_registry*` returned by `wl_display_get_registry(mDisplay)` in `QWaylandDisplay::setupConnection()` (`qwaylanddisplay.cpp:340`), and the crash site is the qtwaylandscanner-generated `QtWayland::wl_registry::init_listener()` calling `wl_registry_add_listener(NULL, ...)` → `wl_proxy_add_listener(NULL, ...)` → `proxy->object.implementation = listener` faulting at address `0x8`.** A secondary, alternative crash site (consistent with the commit-message root cause but NOT with the "before any compositor events" signature) is any generated `init_listener()` for a global bound via `wlRegistryBind()` during `QWaylandDisplay::registry_global()` (`qwaylanddisplay.cpp:634`). The candidate patch (`local/patches/qtbase/qtwaylandscanner-null-guard-listeners.patch`) guards **both** sites because it modifies the code generator to emit `if (m_) _add_listener(...)` for **every** generated `init_listener()`. **Patch verdict: the candidate patch guards the CORRECT site.** It is necessary but not sufficient alone — see §4. --- ## 2. EVIDENCE CHAIN (every claim cites file:line) ### 2.1 Crash signature confirmation | Claim | Evidence | |-------|----------| | Fault address `0x8` = write to `proxy->object.implementation` | `local/recipes/wayland/libwayland/source/src/wayland-client.c:665` — `proxy->object.implementation = implementation;` | | `wl_object.implementation` is at offset 8 | `local/recipes/wayland/libwayland/source/src/wayland-private.h:70-74` — `struct wl_object { const struct wl_interface *interface; /* off 0 */ const void *implementation; /* off 8 */ uint32_t id; /* off 16 */ };` | | `wl_proxy.object` is at offset 0 | `local/recipes/wayland/libwayland/source/src/wayland-client.c:63-74` — `struct wl_proxy { struct wl_object object; ... };` (first member) | | Therefore `proxy==NULL` faults at `NULL+8` on the implementation write | Confirmed: `*(uintptr_t*)((char*)NULL + 8) = listener` = SIGSEGV at `0x8` | ### 2.2 Qt6 client init sequence (where the first add_listener fires) Qt 6.11.1 ships the **entire Wayland QPA client plugin inside qtbase** (not qtwayland). Confirmed locations: | File | Role | |------|------| | `local/recipes/qt/qtbase/source/src/plugins/platforms/wayland/qwaylanddisplay.cpp` | QWaylandDisplay (the main client-side display object) | | `local/recipes/qt/qtbase/source/src/plugins/platforms/wayland/qwaylanddisplay_p.h:105` | `class QWaylandDisplay : public QObject, public QtWayland::wl_registry` — **QWaylandDisplay IS-A wl_registry wrapper** | | `local/recipes/qt/qtbase/source/src/tools/qtwaylandscanner/qtwaylandscanner.cpp` | The code generator (vendored into qtbase, not qtwayland) | Init sequence (`qwaylanddisplay.cpp`): ``` Line 322: QWaylandDisplay::QWaylandDisplay(...) Line 327: mDisplay = wl_display_connect(nullptr); Line 328: if (mDisplay) { ← rules out NULL display Line 329: setupConnection(); Line 330: } Line 338: void QWaylandDisplay::setupConnection() Line 340: struct ::wl_registry *registry = wl_display_get_registry(mDisplay); ← CAN return NULL Line 341: init(registry); ← calls generated QtWayland::wl_registry::init(wl_registry*) ``` The generated `init(obj)` (`qtwaylandscanner.cpp:1155-1160`): ```cpp void wl_registry::init(struct ::wl_registry *obj) { m_wl_registry = obj; // line 1157 — assigned BEFORE init_listener if (hasEvents) init_listener(); // line 1159 — called AFTER assignment } ``` The generated `init_listener()` (`qtwaylandscanner.cpp:1295-1298`, **PATCHED**): ```cpp void wl_registry::init_listener() { if (m_wl_registry) wl_registry_add_listener(m_wl_registry, &m_wl_registry_listener, this); } ``` **Therefore:** if `wl_display_get_registry()` returns NULL → `m_wl_registry = NULL` → patched `init_listener()` skips the add_listener call → **no crash**. Unpatched (upstream Qt 6.11.1) → `wl_registry_add_listener(NULL, ...)` → `wl_proxy_add_listener(NULL, ...)` → **page fault at 0x8**. ### 2.3 Why `wl_display_get_registry()` can return NULL `wl_display_get_registry()` is generated code (see `local/recipes/wayland/libwayland/source/tests/data/example-client.h:1027-1036`): ```c static inline struct wl_registry * wl_display_get_registry(struct wl_display *wl_display) { struct wl_proxy *registry; registry = wl_proxy_marshal_flags((struct wl_proxy *) wl_display, WL_DISPLAY_GET_REGISTRY, &wl_registry_interface, ...); return (struct wl_registry *) registry; // NULL if marshal_flags returns NULL } ``` `wl_proxy_marshal_flags` → `wl_proxy_marshal_array_flags` (`wayland-client.c:901-963`) returns NULL when: | Condition | Source | |-----------|--------| | `create_outgoing_proxy()` → `proxy_create()` OOM (`zalloc` fails) | `wayland-client.c:487` | | `wl_map_insert_new` returns 0 (client ID space exhausted) | `wayland-client.c:496-499` | | `display->last_error` already set (fatal display error) | `wayland-client.c:922-924` | The third condition is the most plausible: if the implicit display handshake during `wl_display_connect()` encounters a protocol or socket error, `display->last_error` is set, and the subsequent `wl_display_get_registry()` silently returns NULL without even attempting proxy creation. ### 2.4 Candidate patch guards the correct site The candidate patch (`local/patches/qtbase/qtwaylandscanner-null-guard-listeners.patch`) modifies the generator at `qtwaylandscanner.cpp:1297` to emit `if (m_%s) %s_add_listener(...)` instead of `%s_add_listener(...)`. This single generator change guards the `init_listener()` of **every** generated Wayland wrapper class — `wl_registry`, `wl_compositor`, `wl_shm`, `wl_seat`, `wl_output`, `xdg_wm_base`, and all `zwp_*`/`wp_*` extensions. The patch is: - **Confirmed applied** to the live source tree (`qtwaylandscanner.cpp:1297` shows the patched 4-argument printf). - **Confirmed wired** in `local/recipes/qt/qtbase/recipe.toml` patches list (line 14: `"qtwaylandscanner-null-guard-listeners.patch"`). - **Confirmed applicable** to Qt 6.11.1 (context lines match; 3-arg → 4-arg printf transformation is unique). ### 2.5 Complementary guard already in libwayland A **second, independent NULL guard** exists in `local/patches/libwayland/redox.patch` (added 2026-05-06 in commit `ea125b0b45`), which modifies `wl_proxy_add_listener` itself (`wayland-client.c:652-656`): ```c if (!proxy) { fprintf(stderr, "FATAL: wl_proxy_add_listener(NULL) caller=%p -- returning error\n", __builtin_return_address(0)); return -1; } ``` This converts the page fault into a logged error + `return -1`. **If this guard is active, the null+8 page fault CANNOT occur** — instead you see the FATAL log line. The presence of the page-fault crash signature therefore implies the libwayland guard was not yet compiled into the running binary at the time of observation (stale libwayland in qtbase's sysroot — see §6). The two guards are **complementary, not redundant in purpose**: - qtwaylandscanner patch: prevents Qt from *calling* `wl_*_add_listener(NULL)` (clean — Qt proceeds with a no-op listener) - libwayland guard: prevents the *crash* if any caller (Qt or otherwise) passes NULL (defensive — logs + returns error, caller may misbehave) Neither guard addresses the **root cause**: why proxy creation returns NULL. --- ## 3. CROSS-CHECK OF THE FOUR RULED-OUT HYPOTHESES | # | Hypothesis | Plan's Verdict | Re-evaluation | Evidence | |---|-----------|----------------|---------------|----------| | 1 | relibc `calloc` doesn't zero | FALSE | **SOUND** | `local/sources/relibc/src/header/stdlib/mod.rs:281` — `ptr.write_bytes(0, size)` runs unconditionally on every successful `malloc`. Overflow path returns NULL + ENOMEM (line 287-288). Verified. | | 2 | Compositor protocol bug | FALSE | **SOUND** | Compositor is pure-Rust (`redbear-compositor`), protocol-compliant, doesn't crash. Client crashes during its own init. | | 3 | `wl_display_connect()` returns NULL | FALSE | **SOUND** | `qwaylanddisplay.cpp:327-329` — Qt checks `if (mDisplay)` before calling `setupConnection()`. A NULL display never reaches `get_registry`. | | 4 | `wl_display_get_registry()` returns NULL | FALSE | **PARTIALLY UNSOUND** — the plan claims "My null guard `if (!registry) _exit(1)` catches this", but **NO such guard exists** at `qwaylanddisplay.cpp:340-341`. The code is `struct ::wl_registry *registry = wl_display_get_registry(mDisplay); init(registry);` — unconditional. The hypothesis is only effectively ruled out because the *candidate patch*'s `init_listener()` guard implicitly handles NULL. **Without the patch, this is the leading crash cause.** | | 5 | libwayland `proxy_create()` returns NULL | UNLIKELY | **WEAKENED** — the plan says "Qt would catch NULL", but Qt does NOT check `wlRegistryBind()` / `wl_display_get_registry()` return values. That's exactly the gap the candidate patch closes. The hypothesis is plausible, not unlikely. | **Net effect:** hypotheses 1–3 are soundly ruled out. Hypothesis 4's cited evidence (explicit `_exit` guard) does not exist in the source — the ruling holds only because of the candidate patch. Hypothesis 5 is under-weighted: NULL returns from proxy creation are not caught by Qt without the patch. --- ## 4. PATCH VERDICT ### Candidate: `local/patches/qtbase/qtwaylandscanner-null-guard-listeners.patch` **Verdict: CORRECT SITE, NECESSARY, BUT NOT SUFFICIENT ALONE.** | Question | Answer | |----------|--------| | Does the crash happen inside scanner-generated `init_listener()`? | **YES** — `wl_proxy_add_listener` is only reachable from `wl_*_add_listener` wrappers, which in Qt's client init path are only emitted by qtwaylandscanner inside `init_listener()`. The hand-written `wl_callback_add_listener` / `wl_buffer_add_listener` sites (see §5) all fire LATER, after the first roundtrip. | | Does the patch guard the actual crash site? | **YES** — the generator change emits `if (m_)` before every `wl_*_add_listener(m_, ...)` in every generated `init_listener()`. | | Is the patch the same as upstream commits `de2d74c37e`/`882c2974ec`? | **`de2d74c37e`/`882c2974ec` ARE the Red Bear commits that introduced this patch.** They are not upstream Qt commits. `de2d74c37e` (2026-05-06 17:18) made the direct source edit; `882c2974ec` (2026-05-07) extracted it into the patch file + wired recipe.toml. | | Was the patch runtime-validated? | **NO — not in isolation.** Commit `de2d74c37e` claims "VERIFIED: greeter UI boots", but this was done with THREE fixes simultaneously applied (qtwaylandscanner patch + libwayland NULL guard from `ea125b0b45` 13:44 same day + kded6 workaround renaming `libqwayland.so` to `.disabled`). The qtwaylandscanner patch alone was never proven. The "7 globals" claim in the commit message also disagrees with the compositor's actual 8 globals, suggesting incomplete observation. | | Is a corrected patch needed? | **NO** for the reported crash. The candidate guards the correct site. Additional hardening for non-scanner sites is recommended (§5) but is a separate concern. | ### Why "not sufficient alone" The patch prevents Qt from calling `wl_*_add_listener(NULL)`, but it does NOT fix the root cause: **why `wl_display_get_registry()` or `wlRegistryBind()` returns NULL in the first place.** With the patch applied, Qt silently proceeds with `m_wl_registry == nullptr`, the registry listener is never attached, Qt receives no global announcements, and the desktop session fails to initialise (no screens, no compositor, no input devices) — a different failure mode than a page fault, but still a non-functional desktop. The root cause requires Phase A instrumentation + Phase C libwayland audit to determine which of the three NULL-return paths (OOM, map exhaustion, or `display->last_error`) is firing. ### Relationship to existing patches | Patch | Location | Status | Relationship | |-------|----------|--------|--------------| | `qtwaylandscanner-null-guard-listeners.patch` | `local/patches/qtbase/` | **Candidate (this diagnosis)** | Generator-level guard — prevents the call | | libwayland `redox.patch` hunk 1 | `local/patches/libwayland/redox.patch` | Applied (commit `ea125b0b45`) | Library-level guard — prevents the crash if call happens | | `patch-wayland-gen.sh` | `local/recipes/qt/qtbase/` | **ORPHANED** — never invoked from recipe.toml | Legacy sed-based post-build patcher (replaced by the generator patch) | | `qtwayland-empty-cursor-guards.patch` | `local/recipes/qt/qtbase/` | **ORPHANED** — not in recipe.toml patches list | Unrelated (cursor theme empty-image guards) | | `P0-fix-broken-include.patch`, `P1-qplatformopengl-guard.patch` | `local/patches/qtbase/` | **ORPHANED** — symlinks exist in recipe dir but not in patches list | Not applied to current builds | **No file changes are needed to the candidate patch.** It is correct. The runbook below (§7) validates it end-to-end. --- ## 5. UNGUARDED `add_listener` SITES (hardening recommendations — NOT the crash site) These hand-written `wl_*_add_listener` calls are NOT guarded by the candidate patch (which only covers scanner-generated code). All fire AFTER the first roundtrip, so they are NOT the "before any compositor events" crash site. Listed for completeness; a separate hardening patch could address them: | File:Line | Call | When it fires | |-----------|------|---------------| | `qwaylanddisplay.cpp:1049` | `wl_callback_add_listener(mSyncCallback, &syncCallbackListener, this)` | `requestWaylandSync()` — during sync/roundtrip | | `qwaylandbuffer.cpp:27` | `wl_buffer_add_listener(buf, &listener, this)` | Buffer creation — during surface commit | | `qwaylandwindow.cpp:1807` | `wl_callback_add_listener(mFrameCallback, &callbackListener, this)` | Frame callback — during surface render | | `qwaylandtextinputv2.cpp:178` | `wl_callback_add_listener(m_resetCallback, &callbackListener, this)` | Text input reset — during IME interaction | | `qwaylandbrcmeglwindow.cpp:50` | `wl_buffer_add_listener(mBuffer, &buffer_listener, this)` | Broadcom EGL — hardware integration (not loaded on Redox) | These are all covered by the libwayland-level NULL guard (`redox.patch` hunk 1), so they will log FATAL + return -1 rather than crash. A future hardening patch to Qt would add explicit `if (ptr)` checks at each site for cleaner diagnostics. --- ## 6. WHY THE CRASH PERSISTED DESPITE "VERIFIED FIXED" (May 2026) The v5.5 changelog (`CONSOLE-TO-KDE-DESKTOP-PLAN.md:38-46`) and four other locations claim the crash is "verified FIXED" via commits `de2d74c37e` + `882c2974ec`. This claim is **overstated** for three reasons: 1. **Never tested in isolation.** The May 6 verification had three fixes applied simultaneously. The qtwaylandscanner patch was never proven to fix the crash on its own. 2. **The kded6 workaround masked it.** Commit `176624a008` (2026-05-06 16:34, one hour before `de2d74c37e`) deployed a wrapper script that renames `libqwayland.so` to `.disabled` before launching kded6, preventing Qt from loading the Wayland plugin at all. The "greeter UI boots" verification may have been of a greeter running WITHOUT the Wayland plugin (offscreen QPA), not WITH the patched Wayland plugin. 3. **Stale-sysroot rebuild chain.** The `WAYLAND-IMPLEMENTATION-PLAN.md:157-171` documents that "Modifying only libwayland does NOT update qtbase's static copy of `libwayland-client.a`. The old binary persists in qtbase's sysroot." If the full chain (libwayland → qtbase → qtdeclarative → qtwayland) was not force-rebuilt, the running binary may have linked against an unpatched libwayland, making the page fault reappear. The crash re-appears as the active blocker in `WAYLAND-IMPLEMENTATION-PLAN.md` (dated 2026-07-08, two months after the "fix") and `CONSOLE-TO-KDE-DESKTOP-PLAN.md:234` (2026-06-20, "WIRED, UNVALIDATED"). The "verified FIXED" line at `CONSOLE-TO-KDE-DESKTOP-PLAN.md:43` directly contradicts line 240-243 of the same document. **The v5.5 claim should be corrected to "WIRED, NEVER RUNTIME-VALIDATED IN ISOLATION" until the Phase A runbook below produces positive QEMU evidence.** --- ## 7. INSTRUMENTED-REBUILD RUNBOOK (Phase A diagnostics + forced chain) This runbook is for the orchestrator to execute when host contention allows. It produces definitive evidence of WHICH proxy is NULL and WHY. ### 7.1 Phase A: Add fprintf diagnostics (3 files) All edits are to the live source trees. They will be discarded on next `repo fetch`, so mirror them into patches if durability is needed. **Edit 1: `local/recipes/qt/qtbase/source/src/plugins/platforms/wayland/qwaylanddisplay.cpp`** In `setupConnection()` (around line 340), add: ```cpp void QWaylandDisplay::setupConnection() { struct ::wl_registry *registry = wl_display_get_registry(mDisplay); fprintf(stderr, "[RB-DIAG] setupConnection: mDisplay=%p registry=%p\n", (void*)mDisplay, (void*)registry); init(registry); fprintf(stderr, "[RB-DIAG] setupConnection: init() returned, m_wl_registry=%p\n", (void*)object()); ``` **Edit 2: `local/recipes/qt/qtbase/source/src/tools/qtwaylandscanner/qtwaylandscanner.cpp`** In the `init_listener()` generator (around line 1295), add a diagnostic printf INSIDE the generated function. Change the patched line to: ```cpp printf(" fprintf(stderr, \"[RB-DIAG] init_listener m_%s=%p\\n\", (void*)m_%s);\n", interfaceName, interfaceName); printf(" if (m_%s) %s_add_listener(m_%s, &m_%s_listener, this);\n", interfaceName, interfaceName, interfaceName, interfaceName); printf(" else fprintf(stderr, \"[RB-DIAG] SKIPPED add_listener for %s (NULL proxy)\\n\");\n", interfaceName); ``` **Edit 3: `local/recipes/wayland/libwayland/source/src/wayland-client.c`** In `wl_proxy_marshal_array_flags` (around line 920), add before the `display->last_error` check: ```c if (display->last_error) fprintf(stderr, "[RB-DIAG] marshal_flags: display->last_error=%d, opcode=%u\n", display->last_error, opcode); ``` And in `proxy_create` (around line 487), add: ```c if (proxy == NULL) { fprintf(stderr, "[RB-DIAG] proxy_create: zalloc FAILED (OOM)\n"); return NULL; } ``` ### 7.2 Phase B: Force-rebuild chain (in strict order) The chain MUST be rebuilt in this order. Skipping any step leaves a stale binary in the next recipe's sysroot. ```bash # Step 0: Rebuild prefix (relibc headers if Phase A edit 3 touched wayland-client.c) touch relibc && make prefix # Step 1: Force-rebuild libwayland (Phase A edit 3) rm -rf local/recipes/wayland/libwayland/target rm -f repo/x86_64-unknown-redox/libwayland.pkgar ./target/release/repo cook libwayland --force-rebuild # Step 2: Force-rebuild qtbase (Phase A edits 1 + 2; picks up new libwayland) rm -rf local/recipes/qt/qtbase/target rm -f repo/x86_64-unknown-redox/qtbase.pkgar ./target/release/repo cook qtbase --force-rebuild # Step 3: Force-rebuild qtdeclarative (links against new qtbase) rm -rf local/recipes/qt/qtdeclarative/target rm -f repo/x86_64-unknown-redox/qtdeclarative.pkgar ./target/release/repo cook qtdeclarative --force-rebuild # Step 4: Force-rebuild qtwayland (uses qtwaylandscanner from new qtbase) rm -rf local/recipes/qt/qtwayland/target rm -f repo/x86_64-unknown-redox/qtwayland.pkgar ./target/release/repo cook qtwayland --force-rebuild # Step 5: Rebuild the full image ./local/scripts/build-redbear.sh redbear-full ``` If `repo cook` is unavailable, use `make r.` + `make cr.` (clean + rebuild) equivalents. ### 7.3 Phase C: QEMU invocation + serial-log markers ```bash # Boot the instrumented image with serial console captured qemu-system-x86_64 \ -cdrom build/x86_64/redbear-full.iso \ -m 4096 \ -serial file:/tmp/rb-wayland-diag.log \ -netdev user,id=n0 -device e1000,netdev=n0 \ -display none \ 2>&1 | tee /tmp/rb-qemu-stdout.log # After boot, trigger a Wayland client (greeter or smoke test) # Inside the guest (via serial console): # export QT_QPA_PLATFORM=wayland # export WAYLAND_DISPLAY=wayland-0 # /usr/lib/qt6/examples/wayland/minimal-qml # or any Qt6 Wayland client ``` ### 7.4 Markers to observe in `/tmp/rb-wayland-diag.log` | Marker | Meaning | Action | |--------|---------|--------| | `[RB-DIAG] setupConnection: mDisplay=0x... registry=0x...` | Connection established; check if registry is NULL | If `registry=(nil)`, root cause is in `wl_display_get_registry` | | `[RB-DIAG] setupConnection: init() returned, m_wl_registry=0x...` | Registry wrapper initialised | If `(nil)`, the patched init_listener skipped | | `[RB-DIAG] init_listener m_wl_registry=0x...` | Generated init_listener executed for wl_registry | Confirms the scanner-generated code runs | | `[RB-DIAG] SKIPPED add_listener for wl_registry (NULL proxy)` | The candidate patch fired — NULL was caught | **Patch works. Root cause is upstream (proxy creation).** | | `[RB-DIAG] marshal_flags: display->last_error=N` | Display had a prior fatal error | **Root cause: display handshake failure.** Investigate socket/IPC. | | `[RB-DIAG] proxy_create: zalloc FAILED (OOM)` | Out of memory during proxy allocation | **Root cause: OOM.** Increase guest RAM or audit relibc allocator. | | `FATAL: wl_proxy_add_listener(NULL) caller=0x...` | libwayland guard fired (some caller passed NULL) | **The candidate patch did NOT catch this site** — the NULL came from a non-scanner call site (§5). Use `addr2line` on the caller address. | | `Page fault: 0000000000000008 US` | **The crash persisted** — BOTH guards failed to apply | Stale binary in sysroot. Re-run the forced rebuild chain. | ### 7.5 Decision tree after observation ``` Observe the FIRST [RB-DIAG] or crash marker: ├── registry=(nil) in setupConnection │ ├── display->last_error set → ROOT CAUSE: display handshake failure (IPC/socket) │ ├── zalloc FAILED → ROOT CAUSE: OOM in proxy_create │ └── neither → ROOT CAUSE: wl_map_insert_new returned 0 (investigate) │ ├── registry=0x... (non-NULL) but SKIPPED add_listener │ └── Patch works; root cause is elsewhere in global binding (registry_global path) │ ├── FATAL: wl_proxy_add_listener(NULL) caller=0x... │ └── addr2line -e → identifies the exact unguarded call site (§5) │ └── Page fault at 0x8 (no markers) └── Stale binary — repeat forced rebuild chain with --no-cache ``` --- ## 8. SUMMARY TABLE | Question | Answer | |----------|--------| | Which proxy is NULL? | `wl_registry*` from `wl_display_get_registry()` (primary) or any `wl_*` from `wlRegistryBind()` (secondary) | | Why is it NULL? | `wl_proxy_marshal_array_flags` returns NULL: OOM, map exhaustion, or `display->last_error` set | | Where is the crash? | Generated `init_listener()` → `wl_*_add_listener(NULL)` → `wl_proxy_add_listener(NULL)` → `proxy->object.implementation = listener` at `wayland-client.c:665` | | Does the patch guard the crash site? | **YES** — `qtwaylandscanner.cpp:1297` emits `if (m_)` before every generated `add_listener` | | Is the patch sufficient alone? | **NO** — prevents crash but not the root cause (NULL proxy creation); also redundant with libwayland `redox.patch` hunk 1 | | Is a corrected patch needed? | **NO** — the candidate is correct for the reported crash | | Was the v5.5 "verified FIXED" sound? | **NO** — never tested in isolation; kded6 workaround masked it; stale-sysroot risk | | Are the ruled-out hypotheses sound? | Hypotheses 1–3 sound; 4 cites non-existent evidence; 5 is under-weighted | --- ## 9. FILES CHANGED IN THIS DIAGNOSIS | File | Change | |------|--------| | `local/docs/QT6-WAYLAND-NULL8-DIAGNOSIS.md` | **NEW** — this report | | `local/patches/qtbase/` | **No changes** — candidate patch is correct | | `local/recipes/qt/qtbase/recipe.toml` | **No changes** — patch is already wired (line 14) | --- ## 10. NEXT STEPS (for the orchestrator) 1. **Execute the Phase A runbook (§7)** when host contention allows. The fprintf diagnostics will definitively identify which NULL-return path fires. 2. **Do NOT remove the candidate patch** — it is correct and necessary. 3. **Do NOT trust the "verified FIXED" claims** in `CONSOLE-TO-KDE-DESKTOP-PLAN.md:43`, `config/redbear-full.toml:183`, `local/recipes/kde/kwin/recipe.toml:2`, or `local/docs/archived/IMPLEMENTATION-MASTER-PLAN.md:61` until positive QEMU evidence from §7.4 confirms the crash is gone. 4. **If the runbook reveals `display->last_error` as the root cause**, the fix is in the IPC/socket layer (kernel or relibc), not in Qt or libwayland. 5. **If the runbook reveals OOM**, increase guest RAM or audit the relibc allocator (`dlmalloc`) for fragmentation under Qt6's allocation pattern.