diff options
author | Ulf Hermann <ulf.hermann@qt.io> | 2021-08-10 12:21:58 +0200 |
---|---|---|
committer | Ulf Hermann <ulf.hermann@qt.io> | 2021-08-18 11:32:55 +0200 |
commit | 3eece79ddbe110c9021ed9b9715c6fd4f70f3ca6 (patch) | |
tree | ddc5ed8be89d3f074e72f14b07655d49c09c1b49 | |
parent | c39e4b02621a15edc978dbfa45b6a00681f41121 (diff) |
Avoid querying the file system for qmldir files for locked modules
If the user explicitly locks a module using qmlProtectModule, we don't
load any additional qmldir files afterwards. In particular, this means
you can only do that with modules that don't contain qmldir files or
with modules for which the qmldir files have already been loaded in all
engines that need them.
This is in contrast to the "weak" locking we automatically perform when
loading a plugin. When importing the module again after loading a plugin
we do want to re-evaluate the qmldir directives. If the module is
imported from a different engine than before, we also have to search for
the qmldir file again.
Amends commit 914e0300792856ddac9b99b20a8d88dd6f087fa6.
[ChangeLog][QtQml] The pre-5.15 behavior of qmlProtectModule() has
mostly been restored: If you explicitly protect a module, the QML engine
will never look for any qmldir files or plugins for that module again.
This severely limits what you can do with such a module. However, once
all affected engines have loaded the module, protecting it can be a
useful optimization. In contrast to pre-5.15, the qmldir cache for
such modules continues to be re-used even after they are locked.
Therefore, in QML engines that have loaded the module before, you can
expect any "prefer" or "import" directives and any composite types to
still be available after protecting the module.
Fixes: QTBUG-85591
Change-Id: Ia4edd860e2ddda5e0c419e1ce9764f10f32ace1f
Reviewed-by: Thorbjørn Lindeijer <bjorn@lindeijer.nl>
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Andrei Golubev <andrei.golubev@qt.io>
Reviewed-by: Fawzi Mohamed <fawzi.mohamed@qt.io>
(cherry picked from commit d0dc91158d0b44d9e1b73c3b0dacdd6699741ad7)
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
-rw-r--r-- | src/qml/doc/src/qmlfunctions.qdoc | 33 | ||||
-rw-r--r-- | src/qml/qml/qqmlimport_p.h | 13 | ||||
-rw-r--r-- | src/qml/qml/qqmlmetatype.cpp | 25 | ||||
-rw-r--r-- | src/qml/qml/qqmlmetatype_p.h | 4 | ||||
-rw-r--r-- | src/qml/qml/qqmltypeloader.cpp | 7 | ||||
-rw-r--r-- | src/qml/qml/qqmltypemodule_p.h | 23 | ||||
-rw-r--r-- | tests/auto/qml/qqmlmoduleplugin/protectedModule/plugin.cpp | 4 |
7 files changed, 71 insertions, 38 deletions
diff --git a/src/qml/doc/src/qmlfunctions.qdoc b/src/qml/doc/src/qmlfunctions.qdoc index 3fc4c86f74..c32eca84ee 100644 --- a/src/qml/doc/src/qmlfunctions.qdoc +++ b/src/qml/doc/src/qmlfunctions.qdoc @@ -1243,20 +1243,25 @@ \fn bool qmlProtectModule(const char* uri, int majVersion); \relates QQmlEngine - This function protects a module from having types registered into it. This - can be used to prevent other plugins from injecting types into your module. - It can also be a performance improvement, as it allows the engine to skip - checking for the possibility of new types or plugins when this import is - reached. - - The performance benefit is primarily seen when registering application - specific types from within the application instead of through a plugin. - Using qmlProtectModule allows the engine to skip checking for a plugin when - that uri is imported, which can be noticeable with slow file systems. - - After this function is called, any attempt to register C++ types into this - uri, major version combination will lead to a runtime error. Call this after - you have registered all of your types with the engine. + This function protects a module from further modification. This can be used + to prevent other plugins from injecting types into your module. It can also + be a performance improvement, as it allows the engine to skip checking for + the possibility of new types or plugins when this import is reached. + + Once qmlProtectModule has been called, a QML engine will not search for a new + \c qmldir file to load the module anymore. It will re-use any \c qmldir files + it has loaded before, though. Therefore, types present at this point continue + to work. Mind that different QML engines may load different modules. The + module protection, however, is global and affects all engines. The overhead + of locating \c qmldir files and loading plugins may be noticeable with slow file + systems. Therefore, protecting a module once you are sure you won't need to + load it anymore can be a good optimization. Mind also that the module lock + not only affects plugins but also any other qmldir directives, like \c import + or \c prefer, as well as any composite types or scripts declared in a \c qmldir + file. + + In addition, after this function is called, any attempt to register C++ types + into this uri, major version combination will lead to a runtime error. Returns true if the module with \a uri as a \l{Identified Modules} {module identifier} and \a majVersion as a major version number was found diff --git a/src/qml/qml/qqmlimport_p.h b/src/qml/qml/qqmlimport_p.h index 2d27fed648..5ebd7f2a2e 100644 --- a/src/qml/qml/qqmlimport_p.h +++ b/src/qml/qml/qqmlimport_p.h @@ -217,6 +217,11 @@ class Q_QML_PRIVATE_EXPORT QQmlImportDatabase public: enum PathType { Local, Remote, LocalOrRemote }; + enum LocalQmldirSearchLocation { + QmldirFileAndCache, + QmldirCacheOnly, + }; + enum LocalQmldirResult { QmldirFound, QmldirNotFound, @@ -240,7 +245,8 @@ public: template<typename Callback> LocalQmldirResult locateLocalQmldir( - const QString &uri, QTypeRevision version, const Callback &callback); + const QString &uri, QTypeRevision version, LocalQmldirSearchLocation location, + const Callback &callback); static QTypeRevision lockModule(const QString &uri, const QString &typeNamespace, QTypeRevision version, QList<QQmlError> *errors); @@ -273,7 +279,8 @@ private: template<typename Callback> QQmlImportDatabase::LocalQmldirResult QQmlImportDatabase::locateLocalQmldir( - const QString &uri, QTypeRevision version, const Callback &callback) + const QString &uri, QTypeRevision version, + QQmlImportDatabase::LocalQmldirSearchLocation location, const Callback &callback) { // Check cache first @@ -301,7 +308,7 @@ QQmlImportDatabase::LocalQmldirResult QQmlImportDatabase::locateLocalQmldir( // Do not try to construct the cache if it already had any entries for the URI. // Otherwise we might duplicate cache entries. - if (result != QmldirNotFound) + if (location == QmldirCacheOnly || result != QmldirNotFound) return result; const bool hasInterceptors = !engine->urlInterceptors().isEmpty(); diff --git a/src/qml/qml/qqmlmetatype.cpp b/src/qml/qml/qqmlmetatype.cpp index e412263825..8b92a2ff8c 100644 --- a/src/qml/qml/qqmlmetatype.cpp +++ b/src/qml/qml/qqmlmetatype.cpp @@ -431,7 +431,7 @@ bool checkRegistration(QQmlType::RegistrationType typeType, QQmlMetaTypeData *da if (uri && !typeName.isEmpty()) { QString nameSpace = QString::fromUtf8(uri); QQmlTypeModule *qqtm = data->findTypeModule(nameSpace, version); - if (qqtm && qqtm->isLocked()) { + if (qqtm && qqtm->lockLevel() != QQmlTypeModule::LockLevel::Open) { QString failure(QCoreApplication::translate( "qmlRegisterType", "Cannot install %1 '%2' into protected module '%3' version '%4'")); @@ -645,18 +645,19 @@ void QQmlMetaType::unregisterSequentialContainer(int id) unregisterType(id); } -bool QQmlMetaType::protectModule(const QString &uri, QTypeRevision version, bool protectAllVersions) +bool QQmlMetaType::protectModule(const QString &uri, QTypeRevision version, + bool weakProtectAllVersions) { QQmlMetaTypeDataPtr data; if (version.hasMajorVersion()) { - if (QQmlTypeModule *module = data->findTypeModule(uri, version)) { - if (!protectAllVersions) { - module->lock(); - return true; + if (QQmlTypeModule *module = data->findTypeModule(uri, version)) { + if (!weakProtectAllVersions) { + module->setLockLevel(QQmlTypeModule::LockLevel::Strong); + return true; + } + } else { + return false; } - } else { - return false; - } } const auto range = std::equal_range( @@ -664,7 +665,7 @@ bool QQmlMetaType::protectModule(const QString &uri, QTypeRevision version, bool std::less<ModuleUri>()); for (auto it = range.first; it != range.second; ++it) - (*it)->lock(); + (*it)->setLockLevel(QQmlTypeModule::LockLevel::Weak); return range.first != range.second; } @@ -981,12 +982,12 @@ QTypeRevision QQmlMetaType::latestModuleVersion(const QString &uri) /* Returns true if a module \a uri of this version is installed and locked; */ -bool QQmlMetaType::isLockedModule(const QString &uri, QTypeRevision version) +bool QQmlMetaType::isStronglyLockedModule(const QString &uri, QTypeRevision version) { QQmlMetaTypeDataPtr data; if (QQmlTypeModule* qqtm = data->findTypeModule(uri, version)) - return qqtm->isLocked(); + return qqtm->lockLevel() == QQmlTypeModule::LockLevel::Strong; return false; } diff --git a/src/qml/qml/qqmlmetatype_p.h b/src/qml/qml/qqmlmetatype_p.h index 7ee16f9dc9..bd7e07373c 100644 --- a/src/qml/qml/qqmlmetatype_p.h +++ b/src/qml/qml/qqmlmetatype_p.h @@ -147,7 +147,7 @@ public: static void registerModule(const char *uri, QTypeRevision version); static bool protectModule(const QString &uri, QTypeRevision version, - bool protectAllVersions = false); + bool weakProtectAllVersions = false); static void registerModuleImport(const QString &uri, QTypeRevision version, const QQmlDirParser::Import &import); @@ -197,7 +197,7 @@ public: static bool isList(QMetaType type); static QTypeRevision latestModuleVersion(const QString &uri); - static bool isLockedModule(const QString &uri, QTypeRevision version); + static bool isStronglyLockedModule(const QString &uri, QTypeRevision version); static QTypeRevision matchingModuleVersion(const QString &module, QTypeRevision version); static QQmlTypeModule *typeModule(const QString &uri, QTypeRevision version); diff --git a/src/qml/qml/qqmltypeloader.cpp b/src/qml/qml/qqmltypeloader.cpp index 69331a1cd4..59c9f858f6 100644 --- a/src/qml/qml/qqmltypeloader.cpp +++ b/src/qml/qml/qqmltypeloader.cpp @@ -580,9 +580,14 @@ bool QQmlTypeLoader::Blob::addImport(QQmlTypeLoader::Blob::PendingImportPtr impo scriptImported(blob, import->location, import->qualifier, QString()); } else if (import->type == QV4::CompiledData::Import::ImportLibrary) { + const QQmlImportDatabase::LocalQmldirSearchLocation searchMode = + QQmlMetaType::isStronglyLockedModule(import->uri, import->version) + ? QQmlImportDatabase::QmldirCacheOnly + : QQmlImportDatabase::QmldirFileAndCache; + const QQmlImportDatabase::LocalQmldirResult qmldirResult = importDatabase->locateLocalQmldir( - import->uri, import->version, + import->uri, import->version, searchMode, [&](const QString &qmldirFilePath, const QString &qmldirUrl) { // This is a local library import const QTypeRevision actualVersion = m_importCache.addLibraryImport( diff --git a/src/qml/qml/qqmltypemodule_p.h b/src/qml/qml/qqmltypemodule_p.h index 0ba6245cbb..c6dae7ef55 100644 --- a/src/qml/qml/qqmltypemodule_p.h +++ b/src/qml/qml/qqmltypemodule_p.h @@ -72,6 +72,12 @@ struct String; class QQmlTypeModule { public: + enum class LockLevel { + Open = 0, + Weak = 1, + Strong = 2 + }; + QQmlTypeModule() = default; QQmlTypeModule(const QString &uri, quint8 majorVersion) : m_module(uri), m_majorVersion(majorVersion) @@ -80,8 +86,17 @@ public: void add(QQmlTypePrivate *); void remove(const QQmlTypePrivate *type); - bool isLocked() const { return m_locked.loadRelaxed() != 0; } - void lock() { m_locked.storeRelaxed(1); } + LockLevel lockLevel() const { return LockLevel(m_lockLevel.loadRelaxed()); } + bool setLockLevel(LockLevel mode) + { + while (true) { + const int currentLock = m_lockLevel.loadAcquire(); + if (currentLock > int(mode)) + return false; + if (currentLock == int(mode) || m_lockLevel.testAndSetRelease(currentLock, int(mode))) + return true; + } + } QString module() const { @@ -114,8 +129,8 @@ private: // Can only ever increase QAtomicInt m_maxMinorVersion = 0; - // Bool. Can only be set to 1 once. - QAtomicInt m_locked = 0; + // LockLevel. Can only be increased. + QAtomicInt m_lockLevel = int(LockLevel::Open); using TypeHash = QStringHash<QList<QQmlTypePrivate *>>; TypeHash m_typeHash; diff --git a/tests/auto/qml/qqmlmoduleplugin/protectedModule/plugin.cpp b/tests/auto/qml/qqmlmoduleplugin/protectedModule/plugin.cpp index 4b58348962..8321e02d79 100644 --- a/tests/auto/qml/qqmlmoduleplugin/protectedModule/plugin.cpp +++ b/tests/auto/qml/qqmlmoduleplugin/protectedModule/plugin.cpp @@ -47,9 +47,9 @@ public: void registerTypes(const char *uri) override { - // The module is protected. The plugin can still be loaded, but it cannot register - // any types. + // Because the module is protected, this plugin should never be loaded Q_UNUSED(uri); + Q_UNREACHABLE(); } }; |