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
5#include "qfactoryloader_p.h"
6
7#include "private/qcoreapplication_p.h"
8#include "private/qloggingregistry_p.h"
9#include "qcborarray.h"
10#include "qcbormap.h"
11#include "qcborstreamreader.h"
12#include "qcborvalue.h"
13#include "qdirlisting.h"
14#include "qfileinfo.h"
15#include "qjsonarray.h"
16#include "qjsondocument.h"
17#include "qjsonobject.h"
18#include "qplugin.h"
19#include "qpluginloader.h"
20
21#include <qtcore_tracepoints_p.h>
22
23QT_BEGIN_NAMESPACE
24
25using namespace Qt::StringLiterals;
26
27Q_TRACE_POINT(qtcore, QFactoryLoader_update, const QString &fileName);
28
29namespace {
30struct IterationResult
31{
32 enum Result {
33 FinishedSearch = 0,
34 ContinueSearch,
35
36 // parse errors
37 ParsingError = -1,
38 InvalidMetaDataVersion = -2,
39 InvalidTopLevelItem = -3,
40 InvalidHeaderItem = -4,
41 };
42 Result result;
43 QCborError error = { .c: QCborError::NoError };
44
45 Q_IMPLICIT IterationResult(Result r) : result(r) {}
46 Q_IMPLICIT IterationResult(QCborError e) : result(ParsingError), error(e) {}
47};
48
49struct QFactoryLoaderIidSearch
50{
51 QLatin1StringView iid;
52 bool matchesIid = false;
53 QFactoryLoaderIidSearch(QLatin1StringView iid) : iid(iid)
54 { Q_ASSERT(!iid.isEmpty()); }
55
56 static IterationResult::Result skip(QCborStreamReader &reader)
57 {
58 // skip this, whatever it is
59 reader.next();
60 return IterationResult::ContinueSearch;
61 }
62
63 IterationResult::Result operator()(QtPluginMetaDataKeys key, QCborStreamReader &reader)
64 {
65 if (key != QtPluginMetaDataKeys::IID)
66 return skip(reader);
67 matchesIid = (reader.readAllString() == iid);
68 return IterationResult::FinishedSearch;
69 }
70 IterationResult::Result operator()(QUtf8StringView, QCborStreamReader &reader)
71 {
72 return skip(reader);
73 }
74};
75
76struct QFactoryLoaderMetaDataKeysExtractor : QFactoryLoaderIidSearch
77{
78 QCborArray keys;
79 QFactoryLoaderMetaDataKeysExtractor(QLatin1StringView iid)
80 : QFactoryLoaderIidSearch(iid)
81 {}
82
83 IterationResult::Result operator()(QtPluginMetaDataKeys key, QCborStreamReader &reader)
84 {
85 if (key == QtPluginMetaDataKeys::IID) {
86 QFactoryLoaderIidSearch::operator()(key, reader);
87 return IterationResult::ContinueSearch;
88 }
89 if (key != QtPluginMetaDataKeys::MetaData)
90 return skip(reader);
91
92 if (!matchesIid)
93 return IterationResult::FinishedSearch;
94 if (!reader.isMap() || !reader.isLengthKnown())
95 return IterationResult::InvalidHeaderItem;
96 if (!reader.enterContainer())
97 return IterationResult::ParsingError;
98 while (reader.isValid()) {
99 // the metadata is JSON, so keys are all strings
100 QByteArray key = reader.readAllUtf8String();
101 if (key == "Keys") {
102 if (!reader.isArray() || !reader.isLengthKnown())
103 return IterationResult::InvalidHeaderItem;
104 keys = QCborValue::fromCbor(reader).toArray();
105 break;
106 }
107 skip(reader);
108 }
109 // warning: we may not have finished iterating over the header
110 return IterationResult::FinishedSearch;
111 }
112 using QFactoryLoaderIidSearch::operator();
113};
114} // unnamed namespace
115
116template <typename F> static IterationResult iterateInPluginMetaData(QByteArrayView raw, F &&f)
117{
118 QPluginMetaData::Header header;
119 Q_ASSERT(raw.size() >= qsizetype(sizeof(header)));
120 memcpy(dest: &header, src: raw.data(), n: sizeof(header));
121 if (Q_UNLIKELY(header.version > QPluginMetaData::CurrentMetaDataVersion))
122 return IterationResult::InvalidMetaDataVersion;
123
124 // use fromRawData to keep QCborStreamReader from copying
125 raw = raw.sliced(pos: sizeof(header));
126 QByteArray ba = QByteArray::fromRawData(data: raw.data(), size: raw.size());
127 QCborStreamReader reader(ba);
128 if (reader.isInvalid())
129 return reader.lastError();
130 if (!reader.isMap())
131 return IterationResult::InvalidTopLevelItem;
132 if (!reader.enterContainer())
133 return reader.lastError();
134 while (reader.isValid()) {
135 IterationResult::Result r;
136 if (reader.isInteger()) {
137 // integer key, one of ours
138 qint64 value = reader.toInteger();
139 auto key = QtPluginMetaDataKeys(value);
140 if (qint64(key) != value)
141 return IterationResult::InvalidHeaderItem;
142 if (!reader.next())
143 return reader.lastError();
144 r = f(key, reader);
145 } else if (reader.isString()) {
146 QByteArray key = reader.readAllUtf8String();
147 if (key.isNull())
148 return reader.lastError();
149 r = f(QUtf8StringView(key), reader);
150 } else {
151 return IterationResult::InvalidTopLevelItem;
152 }
153
154 if (QCborError e = reader.lastError())
155 return e;
156 if (r != IterationResult::ContinueSearch)
157 return r;
158 }
159
160 if (!reader.leaveContainer())
161 return reader.lastError();
162 return IterationResult::FinishedSearch;
163}
164
165static bool isIidMatch(QByteArrayView raw, QLatin1StringView iid)
166{
167 QFactoryLoaderIidSearch search(iid);
168 iterateInPluginMetaData(raw, f&: search);
169 return search.matchesIid;
170}
171
172bool QPluginParsedMetaData::parse(QByteArrayView raw)
173{
174 QCborMap map;
175 auto r = iterateInPluginMetaData(raw, f: [&](const auto &key, QCborStreamReader &reader) {
176 QCborValue item = QCborValue::fromCbor(reader);
177 if (item.isInvalid())
178 return IterationResult::ParsingError;
179 if constexpr (std::is_enum_v<std::decay_t<decltype(key)>>)
180 map[int(key)] = item;
181 else
182 map[QString::fromUtf8(key)] = item;
183 return IterationResult::ContinueSearch;
184 });
185
186 switch (r.result) {
187 case IterationResult::FinishedSearch:
188 case IterationResult::ContinueSearch:
189 break;
190
191 // parse errors
192 case IterationResult::ParsingError:
193 return setError(QFactoryLoader::tr(sourceText: "Metadata parsing error: %1").arg(a: r.error.toString()));
194 case IterationResult::InvalidMetaDataVersion:
195 return setError(QFactoryLoader::tr(sourceText: "Invalid metadata version"));
196 case IterationResult::InvalidTopLevelItem:
197 case IterationResult::InvalidHeaderItem:
198 return setError(QFactoryLoader::tr(sourceText: "Unexpected metadata contents"));
199 }
200
201 // header was validated
202 auto header = qFromUnaligned<QPluginMetaData::Header>(src: raw.data());
203
204 DecodedArchRequirements archReq =
205 header.version == 0 ? decodeVersion0ArchRequirements(value: header.plugin_arch_requirements)
206 : decodeVersion1ArchRequirements(value: header.plugin_arch_requirements);
207
208 // insert the keys not stored in the top-level CBOR map
209 map[int(QtPluginMetaDataKeys::QtVersion)] =
210 QT_VERSION_CHECK(header.qt_major_version, header.qt_minor_version, 0);
211 map[int(QtPluginMetaDataKeys::IsDebug)] = archReq.isDebug;
212 map[int(QtPluginMetaDataKeys::Requirements)] = archReq.level;
213
214 data = std::move(map);
215 return true;
216}
217
218QJsonObject QPluginParsedMetaData::toJson() const
219{
220 // convert from the internal CBOR representation to an external JSON one
221 QJsonObject o;
222 for (auto it : data.toMap()) {
223 QString key;
224 if (it.first.isInteger()) {
225 switch (it.first.toInteger()) {
226#define CONVERT_TO_STRING(IntKey, StringKey, Description) \
227 case int(IntKey): key = QStringLiteral(StringKey); break;
228 QT_PLUGIN_FOREACH_METADATA(CONVERT_TO_STRING)
229 }
230 } else {
231 key = it.first.toString();
232 }
233
234 if (!key.isEmpty())
235 o.insert(key, value: it.second.toJsonValue());
236 }
237 return o;
238}
239
240#if QT_CONFIG(library)
241
242Q_STATIC_LOGGING_CATEGORY_WITH_ENV_OVERRIDE(lcFactoryLoader, "QT_DEBUG_PLUGINS",
243 "qt.core.plugin.factoryloader")
244
245namespace {
246struct QFactoryLoaderGlobals
247{
248 // needs to be recursive because loading one plugin could cause another
249 // factory to be initialized
250 QRecursiveMutex mutex;
251 QList<QFactoryLoader *> loaders;
252};
253}
254
255Q_GLOBAL_STATIC(QFactoryLoaderGlobals, qt_factoryloader_global)
256
257inline void QFactoryLoader::Private::updateSinglePath(const QString &path)
258{
259 struct LibraryReleaser {
260 void operator()(QLibraryPrivate *library)
261 { if (library) library->release(); }
262 };
263
264 // If we've already loaded, skip it...
265 if (loadedPaths.hasSeen(s: path))
266 return;
267
268 qCDebug(lcFactoryLoader) << "checking directory path" << path << "...";
269
270 QDirListing plugins(path,
271#if defined(Q_OS_WIN) || defined(Q_OS_CYGWIN)
272 QStringList(QStringLiteral("*.dll")),
273#elif defined(Q_OS_ANDROID)
274 QStringList("libplugins_%1_*.so"_L1.arg(suffix)),
275#endif
276 QDirListing::IteratorFlag::FilesOnly | QDirListing::IteratorFlag::ResolveSymlinks);
277
278 for (const auto &dirEntry : plugins) {
279 const QString &fileName = dirEntry.fileName();
280#if defined(Q_PROCESSOR_X86)
281 if (fileName.endsWith(s: ".avx2"_L1) || fileName.endsWith(s: ".avx512"_L1)) {
282 // ignore AVX2-optimized file, we'll do a bait-and-switch to it later
283 continue;
284 }
285#endif
286 qCDebug(lcFactoryLoader) << "looking at" << fileName;
287
288 Q_TRACE(QFactoryLoader_update, fileName);
289
290 QLibraryPrivate::UniquePtr library;
291 library.reset(p: QLibraryPrivate::findOrCreate(fileName: dirEntry.canonicalFilePath()));
292 if (!library->isPlugin()) {
293 qCDebug(lcFactoryLoader) << library->errorString << Qt::endl
294 << " not a plugin";
295 continue;
296 }
297
298 QStringList keys;
299 bool metaDataOk = false;
300
301 QString iid = library->metaData.value(k: QtPluginMetaDataKeys::IID).toString();
302 if (iid == QLatin1StringView(this->iid.constData(), this->iid.size())) {
303 QCborMap object = library->metaData.value(k: QtPluginMetaDataKeys::MetaData).toMap();
304 metaDataOk = true;
305
306 const QCborArray k = object.value(key: "Keys"_L1).toArray();
307 for (QCborValueConstRef v : k)
308 keys += cs ? v.toString() : v.toString().toLower();
309 }
310 qCDebug(lcFactoryLoader) << "Got keys from plugin meta data" << keys;
311
312 if (!metaDataOk)
313 continue;
314
315 static constexpr qint64 QtVersionNoPatch = QT_VERSION_CHECK(QT_VERSION_MAJOR, QT_VERSION_MINOR, 0);
316 int thisVersion = library->metaData.value(k: QtPluginMetaDataKeys::QtVersion).toInteger();
317 if (iid.startsWith(QStringLiteral("org.qt-project.Qt.QPA"))) {
318 // QPA plugins must match Qt Major.Minor
319 if (thisVersion != QtVersionNoPatch) {
320 qCDebug(lcFactoryLoader) << "Ignoring QPA plugin due to mismatching Qt versions" << QtVersionNoPatch << thisVersion;
321 continue;
322 }
323 }
324
325 int keyUsageCount = 0;
326 for (const QString &key : std::as_const(t&: keys)) {
327 QLibraryPrivate *&keyMapEntry = keyMap[key];
328 if (QLibraryPrivate *existingLibrary = keyMapEntry) {
329 static constexpr bool QtBuildIsDebug = QT_CONFIG(debug);
330 bool existingIsDebug = existingLibrary->metaData.value(k: QtPluginMetaDataKeys::IsDebug).toBool();
331 bool thisIsDebug = library->metaData.value(k: QtPluginMetaDataKeys::IsDebug).toBool();
332 bool configsAreDifferent = thisIsDebug != existingIsDebug;
333 bool thisConfigDoesNotMatchQt = thisIsDebug != QtBuildIsDebug;
334 if (configsAreDifferent && thisConfigDoesNotMatchQt)
335 continue; // Existing library matches Qt's build config
336
337 // If the existing library was built with a future Qt version,
338 // whereas the one we're considering has a Qt version that fits
339 // better, we prioritize the better match.
340 int existingVersion = existingLibrary->metaData.value(k: QtPluginMetaDataKeys::QtVersion).toInteger();
341 if (existingVersion == QtVersionNoPatch)
342 continue; // Prefer exact Qt version match
343 if (existingVersion < QtVersionNoPatch && thisVersion > QtVersionNoPatch)
344 continue; // Better too old than too new
345 if (existingVersion < QtVersionNoPatch && thisVersion < existingVersion)
346 continue; // Otherwise prefer newest
347 }
348
349 keyMapEntry = library.get();
350 ++keyUsageCount;
351 }
352 if (keyUsageCount || keys.isEmpty()) {
353 library->setLoadHints(QLibrary::PreventUnloadHint); // once loaded, don't unload
354 QMutexLocker locker(&mutex);
355 libraries.push_back(x: std::move(library));
356 }
357 };
358
359 loadedLibraries.resize(size: libraries.size());
360}
361
362void QFactoryLoader::setLoadHints(QLibrary::LoadHints loadHints)
363{
364 d->loadHints = loadHints;
365}
366
367void QFactoryLoader::update()
368{
369#ifdef QT_SHARED
370 if (!d->extraSearchPath.isEmpty())
371 d->updateSinglePath(path: d->extraSearchPath);
372
373 const QStringList paths = QCoreApplication::libraryPaths();
374 for (const QString &pluginDir : paths) {
375#ifdef Q_OS_ANDROID
376 QString path = pluginDir;
377#else
378 QString path = pluginDir + d->suffix;
379#endif
380 d->updateSinglePath(path);
381 }
382#else
383 qCDebug(lcFactoryLoader) << "ignoring" << d->iid
384 << "since plugins are disabled in static builds";
385#endif
386}
387
388QFactoryLoader::~QFactoryLoader()
389{
390 if (!qt_factoryloader_global.isDestroyed()) {
391 QMutexLocker locker(&qt_factoryloader_global->mutex);
392 qt_factoryloader_global->loaders.removeOne(t: this);
393 }
394
395#if QT_CONFIG(library)
396 for (qsizetype i = 0; i < d->loadedLibraries.size(); ++i) {
397 if (d->loadedLibraries.at(i)) {
398 auto &plugin = d->libraries[i];
399 delete plugin->inst.data();
400 plugin->unload();
401 }
402 }
403#endif
404
405 for (QtPluginInstanceFunction staticInstance : d->usedStaticInstances) {
406 if (staticInstance)
407 delete staticInstance();
408 }
409}
410
411#if defined(Q_OS_UNIX) && !defined (Q_OS_DARWIN)
412QLibraryPrivate *QFactoryLoader::library(const QString &key) const
413{
414 const auto it = d->keyMap.find(x: d->cs ? key : key.toLower());
415 if (it == d->keyMap.cend())
416 return nullptr;
417 return it->second;
418}
419#endif
420
421void QFactoryLoader::refreshAll()
422{
423 if (qt_factoryloader_global.exists()) {
424 QMutexLocker locker(&qt_factoryloader_global->mutex);
425 for (QFactoryLoader *loader : std::as_const(t&: qt_factoryloader_global->loaders))
426 loader->update();
427 }
428}
429
430#endif // QT_CONFIG(library)
431
432QFactoryLoader::QFactoryLoader(const char *iid,
433 const QString &suffix,
434 Qt::CaseSensitivity cs)
435{
436 Q_ASSERT_X(suffix.startsWith(u'/'), "QFactoryLoader",
437 "For historical reasons, the suffix must start with '/' (and it can't be empty)");
438
439 d->iid = iid;
440#if QT_CONFIG(library)
441 d->cs = cs;
442 d->suffix = suffix;
443# ifdef Q_OS_ANDROID
444 if (!d->suffix.isEmpty() && d->suffix.at(0) == u'/')
445 d->suffix.remove(0, 1);
446# endif
447
448 QMutexLocker locker(&qt_factoryloader_global->mutex);
449 update();
450 qt_factoryloader_global->loaders.append(t: this);
451#else
452 Q_UNUSED(suffix);
453 Q_UNUSED(cs);
454#endif
455}
456
457void QFactoryLoader::setExtraSearchPath(const QString &path)
458{
459#if QT_CONFIG(library)
460 if (d->extraSearchPath == path)
461 return; // nothing to do
462
463 QMutexLocker locker(&qt_factoryloader_global->mutex);
464 QString oldPath = std::exchange(obj&: d->extraSearchPath, new_val: path);
465 if (oldPath.isEmpty()) {
466 // easy case, just update this directory
467 d->updateSinglePath(path: d->extraSearchPath);
468 } else {
469 // must re-scan everything
470 for (qsizetype i = 0; i < d->loadedLibraries.size(); ++i) {
471 if (d->loadedLibraries.at(i)) {
472 auto &plugin = d->libraries[i];
473 delete plugin->inst.data();
474 }
475 }
476 d->loadedLibraries.fill(t: false);
477 d->loadedPaths.clear();
478 d->libraries.clear();
479 d->keyMap.clear();
480 update();
481 }
482#else
483 Q_UNUSED(path);
484#endif
485}
486
487QFactoryLoader::MetaDataList QFactoryLoader::metaData() const
488{
489 QList<QPluginParsedMetaData> metaData;
490#if QT_CONFIG(library)
491 QMutexLocker locker(&d->mutex);
492 metaData.reserve(asize: qsizetype(d->libraries.size()));
493 for (const auto &library : d->libraries)
494 metaData.append(t: library->metaData);
495 locker.unlock();
496#endif
497
498 QLatin1StringView iid(d->iid.constData(), d->iid.size());
499 const auto staticPlugins = QPluginLoader::staticPlugins();
500 for (const QStaticPlugin &plugin : staticPlugins) {
501 QByteArrayView pluginData(static_cast<const char *>(plugin.rawMetaData), plugin.rawMetaDataSize);
502 QPluginParsedMetaData parsed(pluginData);
503 if (parsed.isError() || parsed.value(k: QtPluginMetaDataKeys::IID) != iid)
504 continue;
505 metaData.append(t: std::move(parsed));
506 }
507
508 // other portions of the code will cast to int (e.g., keyMap())
509 Q_ASSERT(metaData.size() <= std::numeric_limits<int>::max());
510 return metaData;
511}
512
513QList<QCborArray> QFactoryLoader::metaDataKeys() const
514{
515 QList<QCborArray> metaData;
516#if QT_CONFIG(library)
517 QMutexLocker locker(&d->mutex);
518 metaData.reserve(asize: qsizetype(d->libraries.size()));
519 for (const auto &library : d->libraries) {
520 const QCborValue md = library->metaData.value(k: QtPluginMetaDataKeys::MetaData);
521 metaData.append(t: md["Keys"_L1].toArray());
522 }
523 locker.unlock();
524#endif
525
526 QLatin1StringView iid(d->iid.constData(), d->iid.size());
527 const auto staticPlugins = QPluginLoader::staticPlugins();
528 for (const QStaticPlugin &plugin : staticPlugins) {
529 QByteArrayView pluginData(static_cast<const char *>(plugin.rawMetaData),
530 plugin.rawMetaDataSize);
531 QFactoryLoaderMetaDataKeysExtractor extractor{ iid };
532 iterateInPluginMetaData(raw: pluginData, f&: extractor);
533 if (extractor.matchesIid)
534 metaData += std::move(extractor.keys);
535 }
536
537 // other portions of the code will cast to int (e.g., keyMap())
538 Q_ASSERT(metaData.size() <= std::numeric_limits<int>::max());
539 return metaData;
540}
541
542QObject *QFactoryLoader::instance(int index) const
543{
544 if (index < 0)
545 return nullptr;
546
547 QMutexLocker lock(&d->mutex);
548 QObject *obj = instanceHelper_locked(index);
549
550 if (obj && !obj->parent())
551 obj->moveToThread(thread: QCoreApplicationPrivate::mainThread());
552 return obj;
553}
554
555inline QObject *QFactoryLoader::instanceHelper_locked(int index) const
556{
557#if QT_CONFIG(library)
558 if (size_t(index) < d->libraries.size()) {
559 QLibraryPrivate *library = d->libraries[index].get();
560 d->loadedLibraries[index] = true;
561 library->setLoadHints(d->loadHints);
562 return library->pluginInstance();
563 }
564 // we know d->libraries.size() <= index <= numeric_limits<decltype(index)>::max() → no overflow
565 index -= static_cast<int>(d->libraries.size());
566#endif
567
568 QLatin1StringView iid(d->iid.constData(), d->iid.size());
569 const QList<QStaticPlugin> staticPlugins = QPluginLoader::staticPlugins();
570 qsizetype i = 0;
571 for (QStaticPlugin plugin : staticPlugins) {
572 QByteArrayView pluginData(static_cast<const char *>(plugin.rawMetaData), plugin.rawMetaDataSize);
573 if (!isIidMatch(raw: pluginData, iid))
574 continue;
575
576 if (i == index) {
577 if (d->usedStaticInstances.size() <= i)
578 d->usedStaticInstances.resize(size: i + 1);
579 d->usedStaticInstances[i] = plugin.instance;
580 return plugin.instance();
581 }
582 ++i;
583 }
584
585 return nullptr;
586}
587
588QMultiMap<int, QString> QFactoryLoader::keyMap() const
589{
590 QMultiMap<int, QString> result;
591 const QList<QCborArray> metaDataList = metaDataKeys();
592 for (int i = 0; i < int(metaDataList.size()); ++i) {
593 const QCborArray &keys = metaDataList[i];
594 for (QCborValueConstRef key : keys)
595 result.insert(key: i, value: key.toString());
596 }
597 return result;
598}
599
600int QFactoryLoader::indexOf(const QString &needle) const
601{
602 const QList<QCborArray> metaDataList = metaDataKeys();
603 for (int i = 0; i < int(metaDataList.size()); ++i) {
604 const QCborArray &keys = metaDataList[i];
605 for (QCborValueConstRef key : keys) {
606 if (key.toString().compare(s: needle, cs: Qt::CaseInsensitive) == 0)
607 return i;
608 }
609 }
610 return -1;
611}
612
613QT_END_NAMESPACE
614

source code of qtbase/src/corelib/plugin/qfactoryloader.cpp