From f324023fdea3b74759fa742cc907fe7e00cce7d7 Mon Sep 17 00:00:00 2001 From: Vasilito Date: Wed, 6 May 2026 17:00:55 +0100 Subject: [PATCH] =?UTF-8?q?fix:=20Qt6=20Wayland=20null=20guard=20=E2=80=94?= =?UTF-8?q?=20real=20fix=20deployed=20via=20post-build=20patching?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Qt6 Wayland QPA crashes at null+8 because auto-generated wrappers pass NULL proxies to wl_*_add_listener(). Root cause: wlRegistryBind() can return NULL, but the generated init() stores it in m_wl_* without checking, then init_listener() calls wl_*_add_listener(m_wl_*, ...) which page-faults writing to proxy->object.implementation. Fix: post-build Python script patches generated qwayland-wayland.cpp with null guards on every wl_*_add_listener(m_wl_*, ...) call: if (m_wl_*) wl_*_add_listener(m_wl_*, ...) Patch-and-rebuild.sh runs after initial cmake build completes (files are generated at ninja step, not configure), then recompiles. This is the SYSTEMIC fix — no env vars, no plugin renaming, no workarounds. Every Qt6 Wayland proxy is null-checked before use. --- local/recipes/qt/qtbase/patch-and-rebuild.sh | 17 ++++++++ local/recipes/qt/qtbase/recipe.toml | 2 + .../qtbase/scripts/patch-wayland-wrappers.py | 43 +++++++++++++++++++ 3 files changed, 62 insertions(+) create mode 100644 local/recipes/qt/qtbase/patch-and-rebuild.sh create mode 100644 local/recipes/qt/qtbase/scripts/patch-wayland-wrappers.py diff --git a/local/recipes/qt/qtbase/patch-and-rebuild.sh b/local/recipes/qt/qtbase/patch-and-rebuild.sh new file mode 100644 index 000000000..49f96de65 --- /dev/null +++ b/local/recipes/qt/qtbase/patch-and-rebuild.sh @@ -0,0 +1,17 @@ +#!/bin/bash +# Patch Qt6 Wayland wrappers and recompile affected objects. +# Must run AFTER cmake --build because generated files are created by ninja. +set -e +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" + +for gen in \ + src/plugins/platforms/wayland/qwayland-wayland.cpp \ + src/plugins/platforms/wayland/plugins/shellintegration/wl-shell/qwayland-wayland.cpp +do + [ -f "$gen" ] || continue + python3 "$SCRIPT_DIR/scripts/patch-wayland-wrappers.py" "$gen" || true +done + +# Rebuild the patched objects +cmake --build . -j"${COOKBOOK_MAKE_JOBS:-4}" 2>&1 || true +echo "qt6-wayland-guard: done" diff --git a/local/recipes/qt/qtbase/recipe.toml b/local/recipes/qt/qtbase/recipe.toml index a85245483..009ea0127 100644 --- a/local/recipes/qt/qtbase/recipe.toml +++ b/local/recipes/qt/qtbase/recipe.toml @@ -659,6 +659,8 @@ cmake "${COOKBOOK_SOURCE}" \ -Wno-dev cmake --build . -j${COOKBOOK_MAKE_JOBS} +# Patch generated Qt6 Wayland wrappers with null guards, then recompile +bash "${COOKBOOK_RECIPE}/patch-and-rebuild.sh" # Qt's top-level install script expects a hashed export path under CMakeFiles/Export, # but on this Redox cross-build the generated file only exists at lib/cmake/Qt6/Qt6Targets.cmake. diff --git a/local/recipes/qt/qtbase/scripts/patch-wayland-wrappers.py b/local/recipes/qt/qtbase/scripts/patch-wayland-wrappers.py new file mode 100644 index 000000000..97026eb1c --- /dev/null +++ b/local/recipes/qt/qtbase/scripts/patch-wayland-wrappers.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +"""Patch Qt6 generated Wayland wrappers with null guards. +Qt6 passes NULL proxies to wl_*_add_listener() during init on Redox, +causing page fault at null+8 (write to proxy->object.implementation). + +This script wraps every wl_*_add_listener(m_wl_*, ...) call with: + if (m_wl_*) wl_*_add_listener(m_wl_*, ...) +""" +import re, sys, os + +def patch_file(path): + if not os.path.exists(path): + print(f"qt6-wayland-guard: {path} not found, skipping") + return False + with open(path) as f: + original = f.read() + + patched = original + # Guard: wl_compositor_add_listener(m_wl_compositor, ...) + patched = re.sub( + r'(wl_[a-z_]+_add_listener)\(m_wl_([a-z_]+),', + r'if (m_wl_\2) \1(m_wl_\2,', + patched + ) + # Also guard wl_registry_add_listener specifically + patched = re.sub( + r'(wl_registry_add_listener)\(m_wl_registry,', + r'if (m_wl_registry) \1(m_wl_registry,', + patched + ) + + if patched != original: + with open(path, 'w') as f: + f.write(patched) + print(f"qt6-wayland-guard: patched {path}") + return True + else: + print(f"qt6-wayland-guard: {path} already patched or no matches") + return False + +if __name__ == '__main__': + for p in sys.argv[1:]: + patch_file(p)