8#include "private/qcoreapplication_p.h"
9#include "private/qduplicatetracker_p.h"
10#include "private/qloggingregistry_p.h"
11#include "private/qobject_p.h"
27# include "qlibrary_p.h"
30#include <qtcore_tracepoints_p.h>
37using namespace Qt::StringLiterals;
50 InvalidMetaDataVersion = -2,
51 InvalidTopLevelItem = -3,
52 InvalidHeaderItem = -4,
55 QCborError error = { QCborError::NoError };
57 Q_IMPLICIT IterationResult(Result r) : result(r) {}
58 Q_IMPLICIT IterationResult(QCborError e) : result(ParsingError), error(e) {}
61struct QFactoryLoaderIidSearch
63 QLatin1StringView iid;
64 bool matchesIid =
false;
65 QFactoryLoaderIidSearch(QLatin1StringView iid) : iid(iid)
66 { Q_ASSERT(!iid.isEmpty()); }
68 static IterationResult::Result skip(QCborStreamReader &reader)
72 return IterationResult::ContinueSearch;
79 matchesIid = (reader.readAllString() == iid);
80 return IterationResult::FinishedSearch;
82 IterationResult::Result operator()(QUtf8StringView, QCborStreamReader &reader)
88struct QFactoryLoaderMetaDataKeysExtractor : QFactoryLoaderIidSearch
91 QFactoryLoaderMetaDataKeysExtractor(QLatin1StringView iid)
92 : QFactoryLoaderIidSearch(iid)
98 QFactoryLoaderIidSearch::operator()(key, reader);
99 return IterationResult::ContinueSearch;
105 return IterationResult::FinishedSearch;
106 if (!reader.isMap() || !reader.isLengthKnown())
107 return IterationResult::InvalidHeaderItem;
108 if (!reader.enterContainer())
109 return IterationResult::ParsingError;
110 while (reader.isValid()) {
112 QByteArray key = reader.readAllUtf8String();
114 if (!reader.isArray() || !reader.isLengthKnown())
115 return IterationResult::InvalidHeaderItem;
116 keys = QCborValue::fromCbor(reader).toArray();
122 return IterationResult::FinishedSearch;
124 using QFactoryLoaderIidSearch::operator();
131 Q_ASSERT(raw.size() >= qsizetype(
sizeof(header)));
132 memcpy(&header, raw.data(),
sizeof(header));
133 if (Q_UNLIKELY(header.version > QPluginMetaData::CurrentMetaDataVersion))
134 return IterationResult::InvalidMetaDataVersion;
137 raw = raw.sliced(
sizeof(header));
138 QByteArray ba = QByteArray::fromRawData(raw.data(), raw.size());
139 QCborStreamReader reader(ba);
140 if (reader.isInvalid())
141 return reader.lastError();
143 return IterationResult::InvalidTopLevelItem;
144 if (!reader.enterContainer())
145 return reader.lastError();
146 while (reader.isValid()) {
147 IterationResult::Result r;
148 if (reader.isInteger()) {
150 qint64 value = reader.toInteger();
152 if (qint64(key) != value)
153 return IterationResult::InvalidHeaderItem;
155 return reader.lastError();
157 }
else if (reader.isString()) {
158 QByteArray key = reader.readAllUtf8String();
160 return reader.lastError();
161 r = f(QUtf8StringView(key), reader);
163 return IterationResult::InvalidTopLevelItem;
166 if (QCborError e = reader.lastError())
168 if (r != IterationResult::ContinueSearch)
172 if (!reader.leaveContainer())
173 return reader.lastError();
174 return IterationResult::FinishedSearch;
177static bool isIidMatch(QByteArrayView raw, QLatin1StringView iid)
179 QFactoryLoaderIidSearch search(iid);
180 iterateInPluginMetaData(raw, search);
181 return search.matchesIid;
187 auto r = iterateInPluginMetaData(raw, [&](
const auto &key, QCborStreamReader &reader) {
188 QCborValue item = QCborValue::fromCbor(reader);
189 if (item.isInvalid())
190 return IterationResult::ParsingError;
191 if constexpr (std::is_enum_v<std::decay_t<
decltype(key)>>)
192 map[
int(key)] = item;
194 map[QString::fromUtf8(key)] = item;
195 return IterationResult::ContinueSearch;
199 case IterationResult::FinishedSearch:
200 case IterationResult::ContinueSearch:
204 case IterationResult::ParsingError:
205 return setError(QFactoryLoader::tr(
"Metadata parsing error: %1").arg(r.error.toString()));
206 case IterationResult::InvalidMetaDataVersion:
207 return setError(QFactoryLoader::tr(
"Invalid metadata version"));
208 case IterationResult::InvalidTopLevelItem:
209 case IterationResult::InvalidHeaderItem:
210 return setError(QFactoryLoader::tr(
"Unexpected metadata contents"));
216 DecodedArchRequirements archReq =
217 header.version == 0 ? decodeVersion0ArchRequirements(header.plugin_arch_requirements)
218 : decodeVersion1ArchRequirements(header.plugin_arch_requirements);
222 QT_VERSION_CHECK(header.qt_major_version, header.qt_minor_version, 0);
226 data = std::move(map);
234 for (
auto it : data.toMap()) {
236 if (it.first.isInteger()) {
237 switch (it.first.toInteger()) {
238#define CONVERT_TO_STRING(IntKey, StringKey, Description)
239 case int(IntKey): key = QStringLiteral(StringKey); break;
243 key = it.first.toString();
247 o.insert(key, it.second.toJsonValue());
254 Q_DECLARE_PUBLIC(QFactoryLoader)
259#if QT_CONFIG(library)
273#if QT_CONFIG(library)
275Q_STATIC_LOGGING_CATEGORY_WITH_ENV_OVERRIDE(lcFactoryLoader,
"QT_DEBUG_PLUGINS",
276 "qt.core.plugin.factoryloader")
279struct QFactoryLoaderGlobals
283 QRecursiveMutex mutex;
284 QList<QFactoryLoader *> loaders;
288Q_GLOBAL_STATIC(QFactoryLoaderGlobals, qt_factoryloader_global)
290QFactoryLoaderPrivate::~QFactoryLoaderPrivate()
293inline void QFactoryLoaderPrivate::updateSinglePath(
const QString &path)
295 struct LibraryReleaser {
296 void operator()(QLibraryPrivate *library)
297 {
if (library) library->release(); }
301 if (loadedPaths.hasSeen(path))
304 qCDebug(lcFactoryLoader) <<
"checking directory path" << path <<
"...";
306 QDirListing plugins(path,
308 QStringList(QStringLiteral(
"*.dll")),
309#elif defined(Q_OS_ANDROID)
310 QStringList(
"libplugins_%1_*.so"_L1.arg(suffix)),
312 QDirListing::IteratorFlag::FilesOnly | QDirListing::IteratorFlag::ResolveSymlinks);
314 for (
const auto &dirEntry : plugins) {
315 const QString &fileName = dirEntry.fileName();
316#if defined(Q_PROCESSOR_X86)
317 if (fileName.endsWith(
".avx2"_L1) || fileName.endsWith(
".avx512"_L1)) {
322 qCDebug(lcFactoryLoader) <<
"looking at" << fileName;
324 Q_TRACE(QFactoryLoader_update, fileName);
326 QLibraryPrivate::UniquePtr library;
327 library.reset(QLibraryPrivate::findOrCreate(dirEntry.canonicalFilePath()));
328 if (!library->isPlugin()) {
329 qCDebug(lcFactoryLoader) << library->errorString << Qt::endl
335 bool metaDataOk =
false;
337 QString iid = library->metaData.value(QtPluginMetaDataKeys::IID).toString();
338 if (iid == QLatin1StringView(
this->iid.constData(),
this->iid.size())) {
339 QCborMap object = library->metaData.value(QtPluginMetaDataKeys::MetaData).toMap();
342 const QCborArray k = object.value(
"Keys"_L1).toArray();
343 for (QCborValueConstRef v : k)
344 keys += cs ? v.toString() : v.toString().toLower();
346 qCDebug(lcFactoryLoader) <<
"Got keys from plugin meta data" << keys;
351 static constexpr qint64 QtVersionNoPatch = QT_VERSION_CHECK(QT_VERSION_MAJOR, QT_VERSION_MINOR, 0);
352 int thisVersion = library->metaData.value(QtPluginMetaDataKeys::QtVersion).toInteger();
353 if (iid.startsWith(QStringLiteral(
"org.qt-project.Qt.QPA"))) {
355 if (thisVersion != QtVersionNoPatch) {
356 qCDebug(lcFactoryLoader) <<
"Ignoring QPA plugin due to mismatching Qt versions" << QtVersionNoPatch << thisVersion;
361 int keyUsageCount = 0;
362 for (
const QString &key : std::as_const(keys)) {
363 QLibraryPrivate *&keyMapEntry = keyMap[key];
364 if (QLibraryPrivate *existingLibrary = keyMapEntry) {
365 static constexpr bool QtBuildIsDebug = QT_CONFIG(debug);
366 bool existingIsDebug = existingLibrary->metaData.value(QtPluginMetaDataKeys::IsDebug).toBool();
367 bool thisIsDebug = library->metaData.value(QtPluginMetaDataKeys::IsDebug).toBool();
368 bool configsAreDifferent = thisIsDebug != existingIsDebug;
369 bool thisConfigDoesNotMatchQt = thisIsDebug != QtBuildIsDebug;
370 if (configsAreDifferent && thisConfigDoesNotMatchQt)
376 int existingVersion = existingLibrary->metaData.value(QtPluginMetaDataKeys::QtVersion).toInteger();
377 if (existingVersion == QtVersionNoPatch)
379 if (existingVersion < QtVersionNoPatch && thisVersion > QtVersionNoPatch)
381 if (existingVersion < QtVersionNoPatch && thisVersion < existingVersion)
385 keyMapEntry = library.get();
388 if (keyUsageCount || keys.isEmpty()) {
389 library->setLoadHints(QLibrary::PreventUnloadHint);
390 QMutexLocker locker(&mutex);
391 libraries.push_back(std::move(library));
396void QFactoryLoader::update()
401 const QStringList paths = QCoreApplication::libraryPaths();
402 for (
const QString &pluginDir : paths) {
404 QString path = pluginDir;
406 QString path = pluginDir + d->suffix;
409 d->updateSinglePath(path);
411 if (!d->extraSearchPath.isEmpty())
412 d->updateSinglePath(d->extraSearchPath);
415 qCDebug(lcFactoryLoader) <<
"ignoring" << d->iid
416 <<
"since plugins are disabled in static builds";
420QFactoryLoader::~QFactoryLoader()
422 if (!qt_factoryloader_global.isDestroyed()) {
423 QMutexLocker locker(&qt_factoryloader_global->mutex);
424 qt_factoryloader_global->loaders.removeOne(
this);
428#if defined(Q_OS_UNIX) && !defined (Q_OS_DARWIN)
429QLibraryPrivate *QFactoryLoader::library(
const QString &key)
const
431 Q_D(
const QFactoryLoader);
432 const auto it = d->keyMap.find(d->cs ? key : key.toLower());
433 if (it == d->keyMap.cend())
439void QFactoryLoader::refreshAll()
441 if (qt_factoryloader_global.exists()) {
442 QMutexLocker locker(&qt_factoryloader_global->mutex);
443 for (QFactoryLoader *loader : std::as_const(qt_factoryloader_global->loaders))
450QFactoryLoader::QFactoryLoader(
const char *iid,
451 const QString &suffix,
452 Qt::CaseSensitivity cs)
453 : QObject(*
new QFactoryLoaderPrivate)
455 Q_ASSERT_X(suffix.startsWith(u'/'),
"QFactoryLoader",
456 "For historical reasons, the suffix must start with '/' (and it can't be empty)");
458 moveToThread(QCoreApplicationPrivate::mainThread());
461#if QT_CONFIG(library)
465 if (!d->suffix.isEmpty() && d->suffix.at(0) == u'/')
466 d->suffix.remove(0, 1);
469 QMutexLocker locker(&qt_factoryloader_global->mutex);
471 qt_factoryloader_global->loaders.append(
this);
478void QFactoryLoader::setExtraSearchPath(
const QString &path)
480#if QT_CONFIG(library)
482 if (d->extraSearchPath == path)
485 QMutexLocker locker(&qt_factoryloader_global->mutex);
486 QString oldPath = std::exchange(d->extraSearchPath, path);
487 if (oldPath.isEmpty()) {
489 d->updateSinglePath(d->extraSearchPath);
492 d->loadedPaths.clear();
493 d->libraries.clear();
502QFactoryLoader::MetaDataList QFactoryLoader::metaData()
const
504 Q_D(
const QFactoryLoader);
505 QList<QPluginParsedMetaData> metaData;
506#if QT_CONFIG(library)
507 QMutexLocker locker(&d->mutex);
508 for (
const auto &library : d->libraries)
509 metaData.append(library->metaData);
512 QLatin1StringView iid(d->iid.constData(), d->iid.size());
513 const auto staticPlugins = QPluginLoader::staticPlugins();
514 for (
const QStaticPlugin &plugin : staticPlugins) {
515 QByteArrayView pluginData(
static_cast<
const char *>(plugin.rawMetaData), plugin.rawMetaDataSize);
516 QPluginParsedMetaData parsed(pluginData);
517 if (parsed.isError() || parsed.value(QtPluginMetaDataKeys::IID) != iid)
519 metaData.append(std::move(parsed));
523 Q_ASSERT(metaData.size() <= std::numeric_limits<
int>::max());
527QList<QCborArray> QFactoryLoader::metaDataKeys()
const
529 Q_D(
const QFactoryLoader);
530 QList<QCborArray> metaData;
531#if QT_CONFIG(library)
532 QMutexLocker locker(&d->mutex);
533 for (
const auto &library : d->libraries) {
534 const QCborValue md = library->metaData.value(QtPluginMetaDataKeys::MetaData);
535 metaData.append(md[
"Keys"_L1].toArray());
539 QLatin1StringView iid(d->iid.constData(), d->iid.size());
540 const auto staticPlugins = QPluginLoader::staticPlugins();
541 for (
const QStaticPlugin &plugin : staticPlugins) {
542 QByteArrayView pluginData(
static_cast<
const char *>(plugin.rawMetaData),
543 plugin.rawMetaDataSize);
544 QFactoryLoaderMetaDataKeysExtractor extractor{ iid };
545 iterateInPluginMetaData(pluginData, extractor);
546 if (extractor.matchesIid)
547 metaData += std::move(extractor.keys);
551 Q_ASSERT(metaData.size() <= std::numeric_limits<
int>::max());
555QObject *QFactoryLoader::instance(
int index)
const
557 Q_D(
const QFactoryLoader);
561#if QT_CONFIG(library)
562 QMutexLocker lock(&d->mutex);
563 if (size_t(index) < d->libraries.size()) {
564 QLibraryPrivate *library = d->libraries[index].get();
565 if (QObject *obj = library->pluginInstance()) {
567 obj->moveToThread(QCoreApplicationPrivate::mainThread());
573 index -=
static_cast<
int>(d->libraries.size());
577 QLatin1StringView iid(d->iid.constData(), d->iid.size());
578 const QList<QStaticPlugin> staticPlugins = QPluginLoader::staticPlugins();
579 for (QStaticPlugin plugin : staticPlugins) {
580 QByteArrayView pluginData(
static_cast<
const char *>(plugin.rawMetaData), plugin.rawMetaDataSize);
581 if (!isIidMatch(pluginData, iid))
585 return plugin.instance();
592QMultiMap<
int, QString> QFactoryLoader::keyMap()
const
594 QMultiMap<
int, QString> result;
595 const QList<QCborArray> metaDataList = metaDataKeys();
596 for (
int i = 0; i <
int(metaDataList.size()); ++i) {
597 const QCborArray &keys = metaDataList[i];
598 for (QCborValueConstRef key : keys)
599 result.insert(i, key.toString());
604int QFactoryLoader::indexOf(
const QString &needle)
const
606 const QList<QCborArray> metaDataList = metaDataKeys();
607 for (
int i = 0; i <
int(metaDataList.size()); ++i) {
608 const QCborArray &keys = metaDataList[i];
609 for (QCborValueConstRef key : keys) {
610 if (key.toString().compare(needle, Qt::CaseInsensitive) == 0)
619#include "moc_qfactoryloader_p.cpp"
Q_TRACE_POINT(qtcore, QFactoryLoader_update, const QString &fileName)
static bool isIidMatch(QByteArrayView raw, QLatin1StringView iid)
static IterationResult iterateInPluginMetaData(QByteArrayView raw, F &&f)
#define QT_PLUGIN_FOREACH_METADATA(F)