Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qfactoryloader.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// Copyright (C) 2022 Intel Corporation.
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4// Qt-Security score:critical reason:data-parser
5
7
8#include "private/qcoreapplication_p.h"
9#include "private/qloggingregistry_p.h"
10#include "qcborarray.h"
11#include "qcbormap.h"
13#include "qcborvalue.h"
14#include "qdirlisting.h"
15#include "qfileinfo.h"
16#include "qjsonarray.h"
17#include "qjsondocument.h"
18#include "qjsonobject.h"
19#include "qplugin.h"
20#include "qpluginloader.h"
21
22#include <qtcore_tracepoints_p.h>
23
25
26using namespace Qt::StringLiterals;
27
28Q_TRACE_POINT(qtcore, QFactoryLoader_update, const QString &fileName);
29
30namespace {
31struct IterationResult
32{
33 enum Result {
34 FinishedSearch = 0,
35 ContinueSearch,
36
37 // parse errors
38 ParsingError = -1,
39 InvalidMetaDataVersion = -2,
40 InvalidTopLevelItem = -3,
41 InvalidHeaderItem = -4,
42 };
43 Result result;
44 QCborError error = { QCborError::NoError };
45
46 Q_IMPLICIT IterationResult(Result r) : result(r) {}
47 Q_IMPLICIT IterationResult(QCborError e) : result(ParsingError), error(e) {}
48};
49
50struct QFactoryLoaderIidSearch
51{
52 QLatin1StringView iid;
53 bool matchesIid = false;
54 QFactoryLoaderIidSearch(QLatin1StringView iid) : iid(iid)
55 { Q_ASSERT(!iid.isEmpty()); }
56
57 static IterationResult::Result skip(QCborStreamReader &reader)
58 {
59 // skip this, whatever it is
60 reader.next();
61 return IterationResult::ContinueSearch;
62 }
63
64 IterationResult::Result operator()(QtPluginMetaDataKeys key, QCborStreamReader &reader)
65 {
66 if (key != QtPluginMetaDataKeys::IID)
67 return skip(reader);
68 matchesIid = (reader.readAllString() == iid);
69 return IterationResult::FinishedSearch;
70 }
71 IterationResult::Result operator()(QUtf8StringView, QCborStreamReader &reader)
72 {
73 return skip(reader);
74 }
75};
76
77struct QFactoryLoaderMetaDataKeysExtractor : QFactoryLoaderIidSearch
78{
79 QCborArray keys;
80 QFactoryLoaderMetaDataKeysExtractor(QLatin1StringView iid)
81 : QFactoryLoaderIidSearch(iid)
82 {}
83
84 IterationResult::Result operator()(QtPluginMetaDataKeys key, QCborStreamReader &reader)
85 {
86 if (key == QtPluginMetaDataKeys::IID) {
87 QFactoryLoaderIidSearch::operator()(key, reader);
88 return IterationResult::ContinueSearch;
89 }
90 if (key != QtPluginMetaDataKeys::MetaData)
91 return skip(reader);
92
93 if (!matchesIid)
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()) {
100 // the metadata is JSON, so keys are all strings
101 QByteArray key = reader.readAllUtf8String();
102 if (key == "Keys") {
103 if (!reader.isArray() || !reader.isLengthKnown())
104 return IterationResult::InvalidHeaderItem;
105 keys = QCborValue::fromCbor(reader).toArray();
106 break;
107 }
108 skip(reader);
109 }
110 // warning: we may not have finished iterating over the header
111 return IterationResult::FinishedSearch;
112 }
113 using QFactoryLoaderIidSearch::operator();
114};
115} // unnamed namespace
116
117template <typename F> static IterationResult iterateInPluginMetaData(QByteArrayView raw, F &&f)
118{
119 QPluginMetaData::Header header;
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;
124
125 // use fromRawData to keep QCborStreamReader from copying
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();
131 if (!reader.isMap())
132 return IterationResult::InvalidTopLevelItem;
133 if (!reader.enterContainer())
134 return reader.lastError();
135 while (reader.isValid()) {
136 IterationResult::Result r;
137 if (reader.isInteger()) {
138 // integer key, one of ours
139 qint64 value = reader.toInteger();
140 auto key = QtPluginMetaDataKeys(value);
141 if (qint64(key) != value)
142 return IterationResult::InvalidHeaderItem;
143 if (!reader.next())
144 return reader.lastError();
145 r = f(key, reader);
146 } else if (reader.isString()) {
147 QByteArray key = reader.readAllUtf8String();
148 if (key.isNull())
149 return reader.lastError();
150 r = f(QUtf8StringView(key), reader);
151 } else {
152 return IterationResult::InvalidTopLevelItem;
153 }
154
155 if (QCborError e = reader.lastError())
156 return e;
157 if (r != IterationResult::ContinueSearch)
158 return r;
159 }
160
161 if (!reader.leaveContainer())
162 return reader.lastError();
163 return IterationResult::FinishedSearch;
164}
165
166static bool isIidMatch(QByteArrayView raw, QLatin1StringView iid)
167{
168 QFactoryLoaderIidSearch search(iid);
169 iterateInPluginMetaData(raw, search);
170 return search.matchesIid;
171}
172
173bool QPluginParsedMetaData::parse(QByteArrayView raw)
174{
175 QCborMap map;
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;
182 else
183 map[QString::fromUtf8(key)] = item;
184 return IterationResult::ContinueSearch;
185 });
186
187 switch (r.result) {
188 case IterationResult::FinishedSearch:
189 case IterationResult::ContinueSearch:
190 break;
191
192 // parse errors
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"));
200 }
201
202 // header was validated
203 auto header = qFromUnaligned<QPluginMetaData::Header>(raw.data());
204
205 DecodedArchRequirements archReq =
206 header.version == 0 ? decodeVersion0ArchRequirements(header.plugin_arch_requirements)
207 : decodeVersion1ArchRequirements(header.plugin_arch_requirements);
208
209 // insert the keys not stored in the top-level CBOR map
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;
214
215 data = std::move(map);
216 return true;
217}
218
219QJsonObject QPluginParsedMetaData::toJson() const
220{
221 // convert from the internal CBOR representation to an external JSON one
222 QJsonObject o;
223 for (auto it : data.toMap()) {
224 QString key;
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)
230 }
231 } else {
232 key = it.first.toString();
233 }
234
235 if (!key.isEmpty())
236 o.insert(key, it.second.toJsonValue());
237 }
238 return o;
239}
240
241#if QT_CONFIG(library)
242
243Q_STATIC_LOGGING_CATEGORY_WITH_ENV_OVERRIDE(lcFactoryLoader, "QT_DEBUG_PLUGINS",
244 "qt.core.plugin.factoryloader")
245
246namespace {
247struct QFactoryLoaderGlobals
248{
249 // needs to be recursive because loading one plugin could cause another
250 // factory to be initialized
251 QRecursiveMutex mutex;
252 QList<QFactoryLoader *> loaders;
253};
254}
255
256Q_GLOBAL_STATIC(QFactoryLoaderGlobals, qt_factoryloader_global)
257
258inline void QFactoryLoader::Private::updateSinglePath(const QString &path)
259{
260 qCDebug(lcFactoryLoader) << "checking directory path" << path << "...";
261
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)),
267#endif
268 QDirListing::IteratorFlag::FilesOnly | QDirListing::IteratorFlag::ResolveSymlinks);
269
270 auto versionFromLib = [](const QLibraryPrivate *lib) {
271 return lib->metaData.value(QtPluginMetaDataKeys::QtVersion).toInteger();
272 };
273
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)) {
278 // ignore AVX2-optimized file, we'll do a bait-and-switch to it later
279 continue;
280 }
281#endif
282 qCDebug(lcFactoryLoader) << "looking at" << fileName;
283
284 Q_TRACE(QFactoryLoader_update, fileName);
285
286 QLibraryPrivate::UniquePtr library;
287 library.reset(QLibraryPrivate::findOrCreate(dirEntry.canonicalFilePath()));
288 if (!library->isPlugin()) {
289 qCDebug(lcFactoryLoader) << "Not a valid plugin:" << library->errorString;
290 continue;
291 }
292
293 QStringList keys;
294 bool metaDataOk = false;
295
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();
299 metaDataOk = true;
300
301 const QCborArray k = object.value("Keys"_L1).toArray();
302 for (QCborValueConstRef v : k)
303 keys += cs ? v.toString() : v.toString().toLower();
304 }
305 qCDebug(lcFactoryLoader) << "Got keys from plugin meta data" << keys;
306
307 if (!metaDataOk)
308 continue;
309
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"))) {
313 // QPA plugins must match Qt Major.Minor
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);
319 continue;
320 }
321 }
322
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)
333 continue; // Existing library matches Qt's build config
334
335 // Prioritize the library with the highest version number.
336 // Since QLibraryPrivate::isPlugin() rejects any built with a
337 // future version, the highest possible here is the current
338 // version (the best match).
339 qint64 existingVersion = versionFromLib(existingLibrary);
340 if (thisVersion <= existingVersion)
341 continue;
342 }
343
344 keyMapEntry = library.get();
345 ++keyUsageCount;
346 }
347 if (keyUsageCount || keys.isEmpty()) {
348 library->setLoadHints(QLibrary::PreventUnloadHint); // once loaded, don't unload
349 QMutexLocker locker(&mutex);
350 libraries.push_back(std::move(library));
351 }
352 };
353
354 loadedLibraries.resize(libraries.size());
355}
356
357void QFactoryLoader::setLoadHints(QLibrary::LoadHints loadHints)
358{
359 d->loadHints = loadHints;
360}
361
362void QFactoryLoader::update()
363{
364#ifdef QT_SHARED
365 QDuplicateTracker<QString, 4> loadedPaths;
366 if (!d->extraSearchPath.isEmpty()) {
367 std::ignore = loadedPaths.hasSeen(d->extraSearchPath);
368 d->updateSinglePath(d->extraSearchPath);
369 }
370
371 const QStringList paths = QCoreApplication::libraryPaths();
372 for (const QString &pluginDir : paths) {
373#ifdef Q_OS_ANDROID
374 QString path = pluginDir;
375#else
376 QString path = pluginDir + d->suffix;
377#endif
378 if (!loadedPaths.hasSeen(path))
379 d->updateSinglePath(path);
380 }
381#else
382 qCDebug(lcFactoryLoader) << "ignoring" << d->iid
383 << "since plugins are disabled in static builds";
384#endif
385}
386
387QFactoryLoader::~QFactoryLoader()
388{
389 if (!qt_factoryloader_global.isDestroyed()) {
390 QMutexLocker locker(&qt_factoryloader_global->mutex);
391 qt_factoryloader_global->loaders.removeOne(this);
392 }
393
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();
399 plugin->unload();
400 }
401 }
402#endif
403
404 for (QtPluginInstanceFunction staticInstance : std::as_const(d->usedStaticInstances)) {
405 if (staticInstance)
406 delete staticInstance();
407 }
408}
409
410#if defined(Q_OS_UNIX) && !defined (Q_OS_DARWIN)
411QLibraryPrivate *QFactoryLoader::library(const QString &key) const
412{
413 const auto it = d->keyMap.find(d->cs ? key : key.toLower());
414 if (it == d->keyMap.cend())
415 return nullptr;
416 return it->second;
417}
418#endif
419
420void QFactoryLoader::refreshAll()
421{
422 if (qt_factoryloader_global.exists()) {
423 QMutexLocker locker(&qt_factoryloader_global->mutex);
424 for (QFactoryLoader *loader : std::as_const(qt_factoryloader_global->loaders))
425 loader->update();
426 }
427}
428
429#endif // QT_CONFIG(library)
430
431QFactoryLoader::QFactoryLoader(const char *iid,
432 const QString &suffix,
433 Qt::CaseSensitivity cs)
434{
435 Q_ASSERT_X(suffix.startsWith(u'/'), "QFactoryLoader",
436 "For historical reasons, the suffix must start with '/' (and it can't be empty)");
437
438 d->iid = iid;
439#if QT_CONFIG(library)
440 d->cs = cs;
441 d->suffix = suffix;
442# ifdef Q_OS_ANDROID
443 if (!d->suffix.isEmpty() && d->suffix.at(0) == u'/')
444 d->suffix.remove(0, 1);
445# endif
446
447 QMutexLocker locker(&qt_factoryloader_global->mutex);
448 update();
449 qt_factoryloader_global->loaders.append(this);
450#else
451 Q_UNUSED(suffix);
452 Q_UNUSED(cs);
453#endif
454}
455
456void QFactoryLoader::setExtraSearchPath(const QString &path)
457{
458#if QT_CONFIG(library)
459 if (d->extraSearchPath == path)
460 return; // nothing to do
461
462 QMutexLocker locker(&qt_factoryloader_global->mutex);
463 QString oldPath = std::exchange(d->extraSearchPath, path);
464 if (oldPath.isEmpty()) {
465 // easy case, just update this directory
466 d->updateSinglePath(d->extraSearchPath);
467 } else {
468 // must re-scan everything
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();
473 }
474 }
475 d->loadedLibraries.fill(false);
476 d->libraries.clear();
477 d->keyMap.clear();
478 update();
479 }
480#else
481 Q_UNUSED(path);
482#endif
483}
484
485QFactoryLoader::MetaDataList QFactoryLoader::metaData() const
486{
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);
493 locker.unlock();
494#endif
495
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)
502 continue;
503 metaData.append(std::move(parsed));
504 }
505
506 // other portions of the code will cast to int (e.g., keyMap())
507 Q_ASSERT(metaData.size() <= std::numeric_limits<int>::max());
508 return metaData;
509}
510
511QList<QCborArray> QFactoryLoader::metaDataKeys() const
512{
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());
520 }
521 locker.unlock();
522#endif
523
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);
533 }
534
535 // other portions of the code will cast to int (e.g., keyMap())
536 Q_ASSERT(metaData.size() <= std::numeric_limits<int>::max());
537 return metaData;
538}
539
540QObject *QFactoryLoader::instance(int index) const
541{
542 if (index < 0)
543 return nullptr;
544
545 QMutexLocker lock(&d->mutex);
546 QObject *obj = instanceHelper_locked(index);
547
548 if (obj && !obj->parent())
549 obj->moveToThread(QCoreApplicationPrivate::mainThread());
550 return obj;
551}
552
553inline QObject *QFactoryLoader::instanceHelper_locked(int index) const
554{
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();
561 }
562 // we know d->libraries.size() <= index <= numeric_limits<decltype(index)>::max() → no overflow
563 index -= static_cast<int>(d->libraries.size());
564#endif
565
566 QLatin1StringView iid(d->iid.constData(), d->iid.size());
567 const QList<QStaticPlugin> staticPlugins = QPluginLoader::staticPlugins();
568 qsizetype i = 0;
569 for (QStaticPlugin plugin : staticPlugins) {
570 QByteArrayView pluginData(static_cast<const char *>(plugin.rawMetaData), plugin.rawMetaDataSize);
571 if (!isIidMatch(pluginData, iid))
572 continue;
573
574 if (i == index) {
575 if (d->usedStaticInstances.size() <= i)
576 d->usedStaticInstances.resize(i + 1);
577 d->usedStaticInstances[i] = plugin.instance;
578 return plugin.instance();
579 }
580 ++i;
581 }
582
583 return nullptr;
584}
585
586QMultiMap<int, QString> QFactoryLoader::keyMap() const
587{
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());
594 }
595 return result;
596}
597
598int QFactoryLoader::indexOf(const QString &needle) const
599{
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)
605 return i;
606 }
607 }
608 return -1;
609}
610
611QT_END_NAMESPACE
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)