Skip to content

keep default theme objects alive for the process lifetime - #37

Merged
hypengw merged 2 commits into
hypengw:mainfrom
wehrwolfmann:fix/default-theme-lifetime
Sep 21, 2026
Merged

hypengw merged 2 commits into
hypengw:mainfrom
wehrwolfmann:fix/default-theme-lifetime

Conversation

@wehrwolfmann

Copy link
Copy Markdown
Contributor

The default MdColorMgr / ThemeSize / PageContext that MProp hands out live in a
Q_GLOBAL_STATIC in src/style/theme.cpp. When a process reaches static destruction while a
QQmlEngine is still alive, those objects go away first, and every binding reading
MD.MProp.color or MD.Token.color re-evaluates against a destroyed QObject and prints
TypeError: Cannot read property '...' of null. Token.qml:36 (isDarkTheme) is in that list
too, so the singleton trips over it as well.

It is not an exotic ordering: any engine that outlives main() hits it — a heap engine that is
never deleted, exit() from a watchdog or crash path, or an engine whose lifetime belongs to a
scripting host. In an app embedding the library, one killed run printed 191 of these lines.

Reproducer (v0.3.0, Qt 6.11.2, Xvfb + software GL): an MD.Page holding 20 rows of
MD.Divider + MD.Badge + MD.ScrollBar + MD.Text in a plain QQuickView, hosted by

auto* view = new QQuickView();   // the engine outlives main()
view->setSource(scene); view->show();
QTimer::singleShot(1500, &app, []{ QCoreApplication::quit(); });
app.exec();
std::exit(0);                    // statics are destroyed with the engine still up

Before the patch: 101 TypeError lines. After: 0. Same scene, otherwise the same binary.

The change creates the defaults once and keeps them for the process lifetime. Nothing moves while
the engine is up: colors read through an attached provider and through the default are identical
before and after (16 roles plus mode, isDarkTheme, themeMode, inherited elevation /
textColor, and reset fallback); the only difference in the dump is the printed address, now
heap instead of static storage. ctest is 42/42 both ways, and example starts clean.

I left Token.qml and the raw pointers in AttachProp untouched on purpose — guarding the QML
would only hide the symptom, and turning the attached property into a QPointer is a larger
decision than this defect calls for. Glad to go that way instead if you prefer it.

wehrwolfmann and others added 2 commits September 19, 2026 20:43
The default color/size/page objects live in a Q_GLOBAL_STATIC and are handed
out to QML by MProp. When static destruction runs while a QQmlEngine is still
alive, they are destroyed first and every binding on MD.MProp.color /
MD.Token.color re-evaluates against a destroyed QObject, printing
"TypeError: Cannot read property '...' of null".

Create the defaults once and keep them for the whole process lifetime. Nothing
changes while the engine is up, and an explicitly assigned MdColorMgr is still
owned by the Theme it is set on.
@hypengw
hypengw merged commit 550dd4d into hypengw:main Sep 21, 2026
0 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants