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 struct LibraryReleaser {
261 void operator()(QLibraryPrivate *library)
262 { if (library) library->release(); }
263 };
264
265 // If we've already loaded, skip it...
266 if (loadedPaths.hasSeen(path))
267 return;
268
269 qCDebug(lcFactoryLoader) << "checking directory path" << path << "...";
270
271 QDirListing plugins(path,
272#if defined(Q_OS_WIN) || defined(Q_OS_CYGWIN)
273 QStringList(QStringLiteral("*.dll")),
274#elif defined(Q_OS_ANDROID)
275 QStringList("libplugins_%1_*.so"_L1.arg(suffix)),
276#endif
277 QDirListing::IteratorFlag::FilesOnly | QDirListing::IteratorFlag::ResolveSymlinks);
278
279 auto versionFromLib = [](const QLibraryPrivate *lib) {
280 return lib->metaData.value(QtPluginMetaDataKeys::QtVersion).toInteger();
281 };
282
283 for (const auto &dirEntry : plugins) {
284 const QString &fileName = dirEntry.fileName();
285#if defined(Q_PROCESSOR_X86)
286 if (fileName.endsWith(".avx2"_L1) || fileName.endsWith(".avx512"_L1)) {
287 // ignore AVX2-optimized file, we'll do a bait-and-switch to it later
288 continue;
289 }
290#endif
291 qCDebug(lcFactoryLoader) << "looking at" << fileName;
292
293 Q_TRACE(QFactoryLoader_update, fileName);
294
295 QLibraryPrivate::UniquePtr library;
296 library.reset(QLibraryPrivate::findOrCreate(dirEntry.canonicalFilePath()));
297 if (!library->isPlugin()) {
298 qCDebug(lcFactoryLoader) << library->errorString << Qt::endl
299 << " not a plugin";
300 continue;
301 }
302
303 QStringList keys;
304 bool metaDataOk = false;
305
306 QString iid = library->metaData.value(QtPluginMetaDataKeys::IID).toString();
307 if (iid == QLatin1StringView(this->iid.constData(), this->iid.size())) {
308 QCborMap object = library->metaData.value(QtPluginMetaDataKeys::MetaData).toMap();
309 metaDataOk = true;
310
311 const QCborArray k = object.value("Keys"_L1).toArray();
312 for (QCborValueConstRef v : k)
313 keys += cs ? v.toString() : v.toString().toLower();
314 }
315 qCDebug(lcFactoryLoader) << "Got keys from plugin meta data" << keys;
316
317 if (!metaDataOk)
318 continue;
319
320 static constexpr qint64 QtVersionNoPatch = QT_VERSION_CHECK(QT_VERSION_MAJOR, QT_VERSION_MINOR, 0);
321 qint64 thisVersion = versionFromLib(library.get());
322 if (iid.startsWith(QStringLiteral("org.qt-project.Qt.QPA"))) {
323 // QPA plugins must match Qt Major.Minor
324 if (thisVersion != QtVersionNoPatch) {
325 qCDebug(lcFactoryLoader) << "Ignoring QPA plugin due to mismatching Qt versions" << QtVersionNoPatch << thisVersion;
326 continue;
327 }
328 }
329
330 int keyUsageCount = 0;
331 for (const QString &key : std::as_const(keys)) {
332 QLibraryPrivate *&keyMapEntry = keyMap[key];
333 if (QLibraryPrivate *existingLibrary = keyMapEntry) {
334 static constexpr bool QtBuildIsDebug = QT_CONFIG(debug);
335 bool existingIsDebug = existingLibrary->metaData.value(QtPluginMetaDataKeys::IsDebug).toBool();
336 bool thisIsDebug = library->metaData.value(QtPluginMetaDataKeys::IsDebug).toBool();
337 bool configsAreDifferent = thisIsDebug != existingIsDebug;
338 bool thisConfigDoesNotMatchQt = thisIsDebug != QtBuildIsDebug;
339 if (configsAreDifferent && thisConfigDoesNotMatchQt)
340 continue; // Existing library matches Qt's build config
341
342 // If the existing library was built with a future Qt version,
343 // whereas the one we're considering has a Qt version that fits
344 // better, we prioritize the better match.
345 qint64 existingVersion = versionFromLib(existingLibrary);
346 if (existingVersion == QtVersionNoPatch)
347 continue; // Prefer exact Qt version match
348 if (existingVersion < QtVersionNoPatch && thisVersion > QtVersionNoPatch)
349 continue; // Better too old than too new
350 if (existingVersion < QtVersionNoPatch && thisVersion < existingVersion)
351 continue; // Otherwise prefer newest
352 }
353
354 keyMapEntry = library.get();
355 ++keyUsageCount;
356 }
357 if (keyUsageCount || keys.isEmpty()) {
358 library->setLoadHints(QLibrary::PreventUnloadHint); // once loaded, don't unload
359 QMutexLocker locker(&mutex);
360 libraries.push_back(std::move(library));
361 }
362 };
363
364 loadedLibraries.resize(libraries.size());
365}
366
367void QFactoryLoader::setLoadHints(QLibrary::LoadHints loadHints)
368{
369 d->loadHints = loadHints;
370}
371
372void QFactoryLoader::update()
373{
374#ifdef QT_SHARED
375 if (!d->extraSearchPath.isEmpty())
376 d->updateSinglePath(d->extraSearchPath);
377
378 const QStringList paths = QCoreApplication::libraryPaths();
379 for (const QString &pluginDir : paths) {
380#ifdef Q_OS_ANDROID
381 QString path = pluginDir;
382#else
383 QString path = pluginDir + d->suffix;
384#endif
385 d->updateSinglePath(path);
386 }
387#else
388 qCDebug(lcFactoryLoader) << "ignoring" << d->iid
389 << "since plugins are disabled in static builds";
390#endif
391}
392
393QFactoryLoader::~QFactoryLoader()
394{
395 if (!qt_factoryloader_global.isDestroyed()) {
396 QMutexLocker locker(&qt_factoryloader_global->mutex);
397 qt_factoryloader_global->loaders.removeOne(this);
398 }
399
400#if QT_CONFIG(library)
401 for (qsizetype i = 0; i < d->loadedLibraries.size(); ++i) {
402 if (d->loadedLibraries.at(i)) {
403 auto &plugin = d->libraries[i];
404 delete plugin->inst.data();
405 plugin->unload();
406 }
407 }
408#endif
409
410 for (QtPluginInstanceFunction staticInstance : d->usedStaticInstances) {
411 if (staticInstance)
412 delete staticInstance();
413 }
414}
415
416#if defined(Q_OS_UNIX) && !defined (Q_OS_DARWIN)
417QLibraryPrivate *QFactoryLoader::library(const QString &key) const
418{
419 const auto it = d->keyMap.find(d->cs ? key : key.toLower());
420 if (it == d->keyMap.cend())
421 return nullptr;
422 return it->second;
423}
424#endif
425
426void QFactoryLoader::refreshAll()
427{
428 if (qt_factoryloader_global.exists()) {
429 QMutexLocker locker(&qt_factoryloader_global->mutex);
430 for (QFactoryLoader *loader : std::as_const(qt_factoryloader_global->loaders))
431 loader->update();
432 }
433}
434
435#endif // QT_CONFIG(library)
436
437QFactoryLoader::QFactoryLoader(const char *iid,
438 const QString &suffix,
439 Qt::CaseSensitivity cs)
440{
441 Q_ASSERT_X(suffix.startsWith(u'/'), "QFactoryLoader",
442 "For historical reasons, the suffix must start with '/' (and it can't be empty)");
443
444 d->iid = iid;
445#if QT_CONFIG(library)
446 d->cs = cs;
447 d->suffix = suffix;
448# ifdef Q_OS_ANDROID
449 if (!d->suffix.isEmpty() && d->suffix.at(0) == u'/')
450 d->suffix.remove(0, 1);
451# endif
452
453 QMutexLocker locker(&qt_factoryloader_global->mutex);
454 update();
455 qt_factoryloader_global->loaders.append(this);
456#else
457 Q_UNUSED(suffix);
458 Q_UNUSED(cs);
459#endif
460}
461
462void QFactoryLoader::setExtraSearchPath(const QString &path)
463{
464#if QT_CONFIG(library)
465 if (d->extraSearchPath == path)
466 return; // nothing to do
467
468 QMutexLocker locker(&qt_factoryloader_global->mutex);
469 QString oldPath = std::exchange(d->extraSearchPath, path);
470 if (oldPath.isEmpty()) {
471 // easy case, just update this directory
472 d->updateSinglePath(d->extraSearchPath);
473 } else {
474 // must re-scan everything
475 for (qsizetype i = 0; i < d->loadedLibraries.size(); ++i) {
476 if (d->loadedLibraries.at(i)) {
477 auto &plugin = d->libraries[i];
478 delete plugin->inst.data();
479 }
480 }
481 d->loadedLibraries.fill(false);
482 d->loadedPaths.clear();
483 d->libraries.clear();
484 d->keyMap.clear();
485 update();
486 }
487#else
488 Q_UNUSED(path);
489#endif
490}
491
492QFactoryLoader::MetaDataList QFactoryLoader::metaData() const
493{
494 QList<QPluginParsedMetaData> metaData;
495#if QT_CONFIG(library)
496 QMutexLocker locker(&d->mutex);
497 metaData.reserve(qsizetype(d->libraries.size()));
498 for (const auto &library : d->libraries)
499 metaData.append(library->metaData);
500 locker.unlock();
501#endif
502
503 QLatin1StringView iid(d->iid.constData(), d->iid.size());
504 const auto staticPlugins = QPluginLoader::staticPlugins();
505 for (const QStaticPlugin &plugin : staticPlugins) {
506 QByteArrayView pluginData(static_cast<const char *>(plugin.rawMetaData), plugin.rawMetaDataSize);
507 QPluginParsedMetaData parsed(pluginData);
508 if (parsed.isError() || parsed.value(QtPluginMetaDataKeys::IID) != iid)
509 continue;
510 metaData.append(std::move(parsed));
511 }
512
513 // other portions of the code will cast to int (e.g., keyMap())
514 Q_ASSERT(metaData.size() <= std::numeric_limits<int>::max());
515 return metaData;
516}
517
518QList<QCborArray> QFactoryLoader::metaDataKeys() const
519{
520 QList<QCborArray> metaData;
521#if QT_CONFIG(library)
522 QMutexLocker locker(&d->mutex);
523 metaData.reserve(qsizetype(d->libraries.size()));
524 for (const auto &library : d->libraries) {
525 const QCborValue md = library->metaData.value(QtPluginMetaDataKeys::MetaData);
526 metaData.append(md["Keys"_L1].toArray());
527 }
528 locker.unlock();
529#endif
530
531 QLatin1StringView iid(d->iid.constData(), d->iid.size());
532 const auto staticPlugins = QPluginLoader::staticPlugins();
533 for (const QStaticPlugin &plugin : staticPlugins) {
534 QByteArrayView pluginData(static_cast<const char *>(plugin.rawMetaData),
535 plugin.rawMetaDataSize);
536 QFactoryLoaderMetaDataKeysExtractor extractor{ iid };
537 iterateInPluginMetaData(pluginData, extractor);
538 if (extractor.matchesIid)
539 metaData += std::move(extractor.keys);
540 }
541
542 // other portions of the code will cast to int (e.g., keyMap())
543 Q_ASSERT(metaData.size() <= std::numeric_limits<int>::max());
544 return metaData;
545}
546
547QObject *QFactoryLoader::instance(int index) const
548{
549 if (index < 0)
550 return nullptr;
551
552 QMutexLocker lock(&d->mutex);
553 QObject *obj = instanceHelper_locked(index);
554
555 if (obj && !obj->parent())
556 obj->moveToThread(QCoreApplicationPrivate::mainThread());
557 return obj;
558}
559
560inline QObject *QFactoryLoader::instanceHelper_locked(int index) const
561{
562#if QT_CONFIG(library)
563 if (size_t(index) < d->libraries.size()) {
564 QLibraryPrivate *library = d->libraries[index].get();
565 d->loadedLibraries[index] = true;
566 library->setLoadHints(d->loadHints);
567 return library->pluginInstance();
568 }
569 // we know d->libraries.size() <= index <= numeric_limits<decltype(index)>::max() → no overflow
570 index -= static_cast<int>(d->libraries.size());
571#endif
572
573 QLatin1StringView iid(d->iid.constData(), d->iid.size());
574 const QList<QStaticPlugin> staticPlugins = QPluginLoader::staticPlugins();
575 qsizetype i = 0;
576 for (QStaticPlugin plugin : staticPlugins) {
577 QByteArrayView pluginData(static_cast<const char *>(plugin.rawMetaData), plugin.rawMetaDataSize);
578 if (!isIidMatch(pluginData, iid))
579 continue;
580
581 if (i == index) {
582 if (d->usedStaticInstances.size() <= i)
583 d->usedStaticInstances.resize(i + 1);
584 d->usedStaticInstances[i] = plugin.instance;
585 return plugin.instance();
586 }
587 ++i;
588 }
589
590 return nullptr;
591}
592
593QMultiMap<int, QString> QFactoryLoader::keyMap() const
594{
595 QMultiMap<int, QString> result;
596 const QList<QCborArray> metaDataList = metaDataKeys();
597 for (int i = 0; i < int(metaDataList.size()); ++i) {
598 const QCborArray &keys = metaDataList[i];
599 for (QCborValueConstRef key : keys)
600 result.insert(i, key.toString());
601 }
602 return result;
603}
604
605int QFactoryLoader::indexOf(const QString &needle) const
606{
607 const QList<QCborArray> metaDataList = metaDataKeys();
608 for (int i = 0; i < int(metaDataList.size()); ++i) {
609 const QCborArray &keys = metaDataList[i];
610 for (QCborValueConstRef key : keys) {
611 if (key.toString().compare(needle, Qt::CaseInsensitive) == 0)
612 return i;
613 }
614 }
615 return -1;
616}
617
618QT_END_NAMESPACE
Q_TRACE_POINT(qtcore, QFactoryLoader_update, const QString &fileName)
static bool isIidMatch(QByteArrayView raw, QLatin1StringView iid)
static IterationResult iterateInPluginMetaData(QByteArrayView raw, F &&f)