8#include "private/qcoreapplication_p.h"
9#include "private/qloggingregistry_p.h"
22#include <qtcore_tracepoints_p.h>
26using namespace Qt::StringLiterals;
39 InvalidMetaDataVersion = -2,
40 InvalidTopLevelItem = -3,
41 InvalidHeaderItem = -4,
44 QCborError error = { QCborError::NoError };
46 Q_IMPLICIT IterationResult(Result r) : result(r) {}
47 Q_IMPLICIT IterationResult(QCborError e) : result(ParsingError), error(e) {}
50struct QFactoryLoaderIidSearch
52 QLatin1StringView iid;
53 bool matchesIid =
false;
54 QFactoryLoaderIidSearch(QLatin1StringView iid) : iid(iid)
55 { Q_ASSERT(!iid.isEmpty()); }
57 static IterationResult::Result skip(QCborStreamReader &reader)
61 return IterationResult::ContinueSearch;
64 IterationResult::Result operator()(QtPluginMetaDataKeys key, QCborStreamReader &reader)
66 if (key != QtPluginMetaDataKeys::IID)
68 matchesIid = (reader.readAllString() == iid);
69 return IterationResult::FinishedSearch;
71 IterationResult::Result operator()(QUtf8StringView, QCborStreamReader &reader)
77struct QFactoryLoaderMetaDataKeysExtractor : QFactoryLoaderIidSearch
80 QFactoryLoaderMetaDataKeysExtractor(QLatin1StringView iid)
81 : QFactoryLoaderIidSearch(iid)
84 IterationResult::Result operator()(QtPluginMetaDataKeys key, QCborStreamReader &reader)
86 if (key == QtPluginMetaDataKeys::IID) {
87 QFactoryLoaderIidSearch::operator()(key, reader);
88 return IterationResult::ContinueSearch;
90 if (key != QtPluginMetaDataKeys::MetaData)
94 return IterationResult::FinishedSearch;
95 if (!reader.isMap() || !reader.isLengthKnown())
96 return IterationResult::InvalidHeaderItem;
97 if (!reader.enterContainer())
98 return IterationResult::ParsingError;
99 while (reader.isValid()) {
101 QByteArray key = reader.readAllUtf8String();
103 if (!reader.isArray() || !reader.isLengthKnown())
104 return IterationResult::InvalidHeaderItem;
105 keys = QCborValue::fromCbor(reader).toArray();
111 return IterationResult::FinishedSearch;
113 using QFactoryLoaderIidSearch::operator();
120 Q_ASSERT(raw.size() >= qsizetype(
sizeof(header)));
121 memcpy(&header, raw.data(),
sizeof(header));
122 if (Q_UNLIKELY(header.version > QPluginMetaData::CurrentMetaDataVersion))
123 return IterationResult::InvalidMetaDataVersion;
126 raw = raw.sliced(
sizeof(header));
127 QByteArray ba = QByteArray::fromRawData(raw.data(), raw.size());
128 QCborStreamReader reader(ba);
129 if (reader.isInvalid())
130 return reader.lastError();
132 return IterationResult::InvalidTopLevelItem;
133 if (!reader.enterContainer())
134 return reader.lastError();
135 while (reader.isValid()) {
136 IterationResult::Result r;
137 if (reader.isInteger()) {
139 qint64 value = reader.toInteger();
140 auto key = QtPluginMetaDataKeys(value);
141 if (qint64(key) != value)
142 return IterationResult::InvalidHeaderItem;
144 return reader.lastError();
146 }
else if (reader.isString()) {
147 QByteArray key = reader.readAllUtf8String();
149 return reader.lastError();
150 r = f(QUtf8StringView(key), reader);
152 return IterationResult::InvalidTopLevelItem;
155 if (QCborError e = reader.lastError())
157 if (r != IterationResult::ContinueSearch)
161 if (!reader.leaveContainer())
162 return reader.lastError();
163 return IterationResult::FinishedSearch;
166static bool isIidMatch(QByteArrayView raw, QLatin1StringView iid)
168 QFactoryLoaderIidSearch search(iid);
169 iterateInPluginMetaData(raw, search);
170 return search.matchesIid;
173bool QPluginParsedMetaData::parse(QByteArrayView raw)
176 auto r = iterateInPluginMetaData(raw, [&](
const auto &key, QCborStreamReader &reader) {
177 QCborValue item = QCborValue::fromCbor(reader);
178 if (item.isInvalid())
179 return IterationResult::ParsingError;
180 if constexpr (std::is_enum_v<std::decay_t<
decltype(key)>>)
181 map[
int(key)] = item;
183 map[QString::fromUtf8(key)] = item;
184 return IterationResult::ContinueSearch;
188 case IterationResult::FinishedSearch:
189 case IterationResult::ContinueSearch:
193 case IterationResult::ParsingError:
194 return setError(QFactoryLoader::tr(
"Metadata parsing error: %1").arg(r.error.toString()));
195 case IterationResult::InvalidMetaDataVersion:
196 return setError(QFactoryLoader::tr(
"Invalid metadata version"));
197 case IterationResult::InvalidTopLevelItem:
198 case IterationResult::InvalidHeaderItem:
199 return setError(QFactoryLoader::tr(
"Unexpected metadata contents"));
203 auto header = qFromUnaligned<QPluginMetaData::Header>(raw.data());
205 DecodedArchRequirements archReq =
206 header.version == 0 ? decodeVersion0ArchRequirements(header.plugin_arch_requirements)
207 : decodeVersion1ArchRequirements(header.plugin_arch_requirements);
210 map[
int(QtPluginMetaDataKeys::QtVersion)] =
211 QT_VERSION_CHECK(header.qt_major_version, header.qt_minor_version, 0);
212 map[
int(QtPluginMetaDataKeys::IsDebug)] = archReq.isDebug;
213 map[
int(QtPluginMetaDataKeys::Requirements)] = archReq.level;
215 data = std::move(map);
219QJsonObject QPluginParsedMetaData::toJson()
const
223 for (
auto it : data.toMap()) {
225 if (it.first.isInteger()) {
226 switch (it.first.toInteger()) {
227#define CONVERT_TO_STRING(IntKey, StringKey, Description)
228 case int(IntKey): key = QStringLiteral(StringKey); break;
229 QT_PLUGIN_FOREACH_METADATA(CONVERT_TO_STRING)
232 key = it.first.toString();
236 o.insert(key, it.second.toJsonValue());
241#if QT_CONFIG(library)
243Q_STATIC_LOGGING_CATEGORY_WITH_ENV_OVERRIDE(lcFactoryLoader,
"QT_DEBUG_PLUGINS",
244 "qt.core.plugin.factoryloader")
247struct QFactoryLoaderGlobals
251 QRecursiveMutex mutex;
252 QList<QFactoryLoader *> loaders;
256Q_GLOBAL_STATIC(QFactoryLoaderGlobals, qt_factoryloader_global)
258inline void QFactoryLoader::Private::updateSinglePath(
const QString &path)
260 qCDebug(lcFactoryLoader) <<
"checking directory path" << path <<
"...";
262 QDirListing plugins(path,
263#if defined(Q_OS_WIN) || defined(Q_OS_CYGWIN)
264 QStringList(QStringLiteral(
"*.dll")),
265#elif defined(Q_OS_ANDROID)
266 QStringList(
"libplugins_%1_*.so"_L1.arg(suffix)),
268 QDirListing::IteratorFlag::FilesOnly | QDirListing::IteratorFlag::ResolveSymlinks);
270 auto versionFromLib = [](
const QLibraryPrivate *lib) {
271 return lib->metaData.value(QtPluginMetaDataKeys::QtVersion).toInteger();
274 for (
const auto &dirEntry : plugins) {
275 const QString &fileName = dirEntry.fileName();
276#if defined(Q_PROCESSOR_X86)
277 if (fileName.endsWith(
".avx2"_L1) || fileName.endsWith(
".avx512"_L1)) {
282 qCDebug(lcFactoryLoader) <<
"looking at" << fileName;
284 Q_TRACE(QFactoryLoader_update, fileName);
286 QLibraryPrivate::UniquePtr library;
287 library.reset(QLibraryPrivate::findOrCreate(dirEntry.canonicalFilePath()));
288 if (!library->isPlugin()) {
289 qCDebug(lcFactoryLoader) <<
"Not a valid plugin:" << library->errorString;
294 bool metaDataOk =
false;
296 QString iid = library->metaData.value(QtPluginMetaDataKeys::IID).toString();
297 if (iid == QLatin1StringView(
this->iid.constData(),
this->iid.size())) {
298 QCborMap object = library->metaData.value(QtPluginMetaDataKeys::MetaData).toMap();
301 const QCborArray k = object.value(
"Keys"_L1).toArray();
302 for (QCborValueConstRef v : k)
303 keys += cs ? v.toString() : v.toString().toLower();
305 qCDebug(lcFactoryLoader) <<
"Got keys from plugin meta data" << keys;
310 static constexpr qint64 QtVersionNoPatch = QT_VERSION_CHECK(QT_VERSION_MAJOR, QT_VERSION_MINOR, 0);
311 qint64 thisVersion = versionFromLib(library.get());
312 if (iid.startsWith(QStringLiteral(
"org.qt-project.Qt.QPA"))) {
314 if (thisVersion != QtVersionNoPatch) {
315 qCDebug(lcFactoryLoader,
316 "Ignoring QPA plugin due to mismatching Qt versions (%d.%d; current: %d.%d)",
317 int(thisVersion) >> 16,
int(thisVersion & 0xff00) >> 8,
318 QT_VERSION_MAJOR, QT_VERSION_MINOR);
323 int keyUsageCount = 0;
324 for (
const QString &key : std::as_const(keys)) {
325 QLibraryPrivate *&keyMapEntry = keyMap[key];
326 if (QLibraryPrivate *existingLibrary = keyMapEntry) {
327 static constexpr bool QtBuildIsDebug = QT_CONFIG(debug);
328 bool existingIsDebug = existingLibrary->metaData.value(QtPluginMetaDataKeys::IsDebug).toBool();
329 bool thisIsDebug = library->metaData.value(QtPluginMetaDataKeys::IsDebug).toBool();
330 bool configsAreDifferent = thisIsDebug != existingIsDebug;
331 bool thisConfigDoesNotMatchQt = thisIsDebug != QtBuildIsDebug;
332 if (configsAreDifferent && thisConfigDoesNotMatchQt)
339 qint64 existingVersion = versionFromLib(existingLibrary);
340 if (thisVersion <= existingVersion)
344 keyMapEntry = library.get();
347 if (keyUsageCount || keys.isEmpty()) {
348 library->setLoadHints(QLibrary::PreventUnloadHint);
349 QMutexLocker locker(&mutex);
350 libraries.push_back(std::move(library));
354 loadedLibraries.resize(libraries.size());
357void QFactoryLoader::setLoadHints(QLibrary::LoadHints loadHints)
359 d->loadHints = loadHints;
362void QFactoryLoader::update()
365 QDuplicateTracker<QString, 4> loadedPaths;
366 if (!d->extraSearchPath.isEmpty()) {
367 std::ignore = loadedPaths.hasSeen(d->extraSearchPath);
368 d->updateSinglePath(d->extraSearchPath);
371 const QStringList paths = QCoreApplication::libraryPaths();
372 for (
const QString &pluginDir : paths) {
374 QString path = pluginDir;
376 QString path = pluginDir + d->suffix;
378 if (!loadedPaths.hasSeen(path))
379 d->updateSinglePath(path);
382 qCDebug(lcFactoryLoader) <<
"ignoring" << d->iid
383 <<
"since plugins are disabled in static builds";
387QFactoryLoader::~QFactoryLoader()
389 if (!qt_factoryloader_global.isDestroyed()) {
390 QMutexLocker locker(&qt_factoryloader_global->mutex);
391 qt_factoryloader_global->loaders.removeOne(
this);
394#if QT_CONFIG(library)
395 for (qsizetype i = 0; i < d->loadedLibraries.size(); ++i) {
396 if (d->loadedLibraries.at(i)) {
397 auto &plugin = d->libraries[i];
398 delete plugin->inst.data();
404 for (QtPluginInstanceFunction staticInstance : std::as_const(d->usedStaticInstances)) {
406 delete staticInstance();
410#if defined(Q_OS_UNIX) && !defined (Q_OS_DARWIN)
411QLibraryPrivate *QFactoryLoader::library(
const QString &key)
const
413 const auto it = d->keyMap.find(d->cs ? key : key.toLower());
414 if (it == d->keyMap.cend())
420void QFactoryLoader::refreshAll()
422 if (qt_factoryloader_global.exists()) {
423 QMutexLocker locker(&qt_factoryloader_global->mutex);
424 for (QFactoryLoader *loader : std::as_const(qt_factoryloader_global->loaders))
431QFactoryLoader::QFactoryLoader(
const char *iid,
432 const QString &suffix,
433 Qt::CaseSensitivity cs)
435 Q_ASSERT_X(suffix.startsWith(u'/'),
"QFactoryLoader",
436 "For historical reasons, the suffix must start with '/' (and it can't be empty)");
439#if QT_CONFIG(library)
443 if (!d->suffix.isEmpty() && d->suffix.at(0) == u'/')
444 d->suffix.remove(0, 1);
447 QMutexLocker locker(&qt_factoryloader_global->mutex);
449 qt_factoryloader_global->loaders.append(
this);
456void QFactoryLoader::setExtraSearchPath(
const QString &path)
458#if QT_CONFIG(library)
459 if (d->extraSearchPath == path)
462 QMutexLocker locker(&qt_factoryloader_global->mutex);
463 QString oldPath = std::exchange(d->extraSearchPath, path);
464 if (oldPath.isEmpty()) {
466 d->updateSinglePath(d->extraSearchPath);
469 for (qsizetype i = 0; i < d->loadedLibraries.size(); ++i) {
470 if (d->loadedLibraries.at(i)) {
471 auto &plugin = d->libraries[i];
472 delete plugin->inst.data();
475 d->loadedLibraries.fill(
false);
476 d->libraries.clear();
485QFactoryLoader::MetaDataList QFactoryLoader::metaData()
const
487 QList<QPluginParsedMetaData> metaData;
488#if QT_CONFIG(library)
489 QMutexLocker locker(&d->mutex);
490 metaData.reserve(qsizetype(d->libraries.size()));
491 for (
const auto &library : d->libraries)
492 metaData.append(library->metaData);
496 QLatin1StringView iid(d->iid.constData(), d->iid.size());
497 const auto staticPlugins = QPluginLoader::staticPlugins();
498 for (
const QStaticPlugin &plugin : staticPlugins) {
499 QByteArrayView pluginData(
static_cast<
const char *>(plugin.rawMetaData), plugin.rawMetaDataSize);
500 QPluginParsedMetaData parsed(pluginData);
501 if (parsed.isError() || parsed.value(QtPluginMetaDataKeys::IID) != iid)
503 metaData.append(std::move(parsed));
507 Q_ASSERT(metaData.size() <= std::numeric_limits<
int>::max());
511QList<QCborArray> QFactoryLoader::metaDataKeys()
const
513 QList<QCborArray> metaData;
514#if QT_CONFIG(library)
515 QMutexLocker locker(&d->mutex);
516 metaData.reserve(qsizetype(d->libraries.size()));
517 for (
const auto &library : d->libraries) {
518 const QCborValue md = library->metaData.value(QtPluginMetaDataKeys::MetaData);
519 metaData.append(md[
"Keys"_L1].toArray());
524 QLatin1StringView iid(d->iid.constData(), d->iid.size());
525 const auto staticPlugins = QPluginLoader::staticPlugins();
526 for (
const QStaticPlugin &plugin : staticPlugins) {
527 QByteArrayView pluginData(
static_cast<
const char *>(plugin.rawMetaData),
528 plugin.rawMetaDataSize);
529 QFactoryLoaderMetaDataKeysExtractor extractor{ iid };
530 iterateInPluginMetaData(pluginData, extractor);
531 if (extractor.matchesIid)
532 metaData += std::move(extractor.keys);
536 Q_ASSERT(metaData.size() <= std::numeric_limits<
int>::max());
540QObject *QFactoryLoader::instance(
int index)
const
545 QMutexLocker lock(&d->mutex);
546 QObject *obj = instanceHelper_locked(index);
548 if (obj && !obj->parent())
549 obj->moveToThread(QCoreApplicationPrivate::mainThread());
553inline QObject *QFactoryLoader::instanceHelper_locked(
int index)
const
555#if QT_CONFIG(library)
556 if (size_t(index) < d->libraries.size()) {
557 QLibraryPrivate *library = d->libraries[index].get();
558 d->loadedLibraries[index] =
true;
559 library->setLoadHints(d->loadHints);
560 return library->pluginInstance();
563 index -=
static_cast<
int>(d->libraries.size());
566 QLatin1StringView iid(d->iid.constData(), d->iid.size());
567 const QList<QStaticPlugin> staticPlugins = QPluginLoader::staticPlugins();
569 for (QStaticPlugin plugin : staticPlugins) {
570 QByteArrayView pluginData(
static_cast<
const char *>(plugin.rawMetaData), plugin.rawMetaDataSize);
571 if (!isIidMatch(pluginData, iid))
575 if (d->usedStaticInstances.size() <= i)
576 d->usedStaticInstances.resize(i + 1);
577 d->usedStaticInstances[i] = plugin.instance;
578 return plugin.instance();
586QMultiMap<
int, QString> QFactoryLoader::keyMap()
const
588 QMultiMap<
int, QString> result;
589 const QList<QCborArray> metaDataList = metaDataKeys();
590 for (
int i = 0; i <
int(metaDataList.size()); ++i) {
591 const QCborArray &keys = metaDataList[i];
592 for (QCborValueConstRef key : keys)
593 result.insert(i, key.toString());
598int QFactoryLoader::indexOf(
const QString &needle)
const
600 const QList<QCborArray> metaDataList = metaDataKeys();
601 for (
int i = 0; i <
int(metaDataList.size()); ++i) {
602 const QCborArray &keys = metaDataList[i];
603 for (QCborValueConstRef key : keys) {
604 if (key.toString().compare(needle, Qt::CaseInsensitive) == 0)
Combined button and popup list for selecting options.
Q_TRACE_POINT(qtcore, QFactoryLoader_update, const QString &fileName)
static bool isIidMatch(QByteArrayView raw, QLatin1StringView iid)
static IterationResult iterateInPluginMetaData(QByteArrayView raw, F &&f)