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

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

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