From f2d297b0d859874fa13691f759064cbe16f20dee Mon Sep 17 00:00:00 2001 From: Shane Ringrose Date: Sat, 20 Jun 2026 20:09:48 +1200 Subject: [PATCH 1/2] Fix thread-unsafe QStandardPaths calls in dataDir/configDir MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit QStandardPaths::writableLocation() is not thread-safe in Qt5. ElementsCollectionModel::reload() launches: QtConcurrent::map(m_items_list_to_setUp, setUpData) Each worker calls FileElementCollectionItem::setUpData() → collectionPath() → isCollectionRoot() → QETApp::userMacrosDir() → QETApp::dataDir() → QStandardPaths::writableLocation() ← SIGSEGV (null deref) The crash was confirmed by Valgrind (address 0x0, inside libQt5Core's writableLocation internals). Fix: replace the bare QStandardPaths calls in dataDir() and configDir() with a C++11 static-local lambda. The compiler guarantees the lambda body runs exactly once across all threads (magic statics, ISO C++11 §6.7). After the first (main-thread) call the result is returned lock-free. Relates-to: #492 (same QtConcurrent lifetime pattern fixed in QETProject::writeBackup by PR #512). --- sources/qetapp.cpp | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/sources/qetapp.cpp b/sources/qetapp.cpp index e2cc687aa..efe529d34 100644 --- a/sources/qetapp.cpp +++ b/sources/qetapp.cpp @@ -887,11 +887,14 @@ QString QETApp::configDir() #ifdef QET_ALLOW_OVERRIDE_CD_OPTION if (config_dir != QString()) return(config_dir); #endif - QString configdir = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation); - while (configdir.endsWith('/')) { - configdir.remove(configdir.length()-1, 1); - } - return configdir; + // C++11 static-local init runs exactly once across all threads — safe to + // call from QtConcurrent background threads (QStandardPaths is not). + static const QString cached = []() { + QString d = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation); + while (d.endsWith('/')) d.chop(1); + return d; + }(); + return cached; } /** @@ -911,11 +914,14 @@ QString QETApp::dataDir() #ifdef QET_ALLOW_OVERRIDE_DD_OPTION if (data_dir != QString()) return(data_dir); #endif - QString datadir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); - while (datadir.endsWith('/')) { - datadir.remove(datadir.length()-1, 1); - } - return datadir; + // C++11 static-local init runs exactly once across all threads — safe to + // call from QtConcurrent background threads (QStandardPaths is not). + static const QString cached = []() { + QString d = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); + while (d.endsWith('/')) d.chop(1); + return d; + }(); + return cached; } /** From f301196f61a94b0206a6ee4fe49c2655601fb8ab Mon Sep 17 00:00:00 2001 From: Shane Ringrose Date: Sat, 20 Jun 2026 22:26:32 +1200 Subject: [PATCH 2/2] Rename static locals to match original variable names per review Reviewer requested configdir/datadir instead of cached for consistency with the surrounding code style. Co-Authored-By: Claude Sonnet 4.6 --- sources/qetapp.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sources/qetapp.cpp b/sources/qetapp.cpp index efe529d34..3b95551e5 100644 --- a/sources/qetapp.cpp +++ b/sources/qetapp.cpp @@ -889,12 +889,12 @@ QString QETApp::configDir() #endif // C++11 static-local init runs exactly once across all threads — safe to // call from QtConcurrent background threads (QStandardPaths is not). - static const QString cached = []() { + static const QString configdir = []() { QString d = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation); while (d.endsWith('/')) d.chop(1); return d; }(); - return cached; + return configdir; } /** @@ -916,12 +916,12 @@ QString QETApp::dataDir() #endif // C++11 static-local init runs exactly once across all threads — safe to // call from QtConcurrent background threads (QStandardPaths is not). - static const QString cached = []() { + static const QString datadir = []() { QString d = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); while (d.endsWith('/')) d.chop(1); return d; }(); - return cached; + return datadir; } /**