| 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 | |
| 35 | QT_BEGIN_NAMESPACE |
| 36 | |
| 37 | using namespace Qt::StringLiterals; |
| 38 | |
| 39 | Q_TRACE_POINT(qtcore, QFactoryLoader_update, const QString &fileName); |
| 40 | |
| 41 | namespace { |
| 42 | struct IterationResult |
| 43 | { |
| 44 | enum Result { |
| 45 | FinishedSearch = 0, |
| 46 | ContinueSearch, |
| 47 | |
| 48 | // parse errors |
| 49 | ParsingError = -1, |
| 50 | InvalidMetaDataVersion = -2, |
| 51 | InvalidTopLevelItem = -3, |
| 52 | = -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 | |
| 61 | struct 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 | |
| 88 | struct : QFactoryLoaderIidSearch |
| 89 | { |
| 90 | QCborArray ; |
| 91 | (QLatin1StringView iid) |
| 92 | : QFactoryLoaderIidSearch(iid) |
| 93 | {} |
| 94 | |
| 95 | IterationResult::Result (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 | |
| 128 | template <typename F> static IterationResult iterateInPluginMetaData(QByteArrayView raw, F &&f) |
| 129 | { |
| 130 | QPluginMetaData::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 | |
| 177 | static bool isIidMatch(QByteArrayView raw, QLatin1StringView iid) |
| 178 | { |
| 179 | QFactoryLoaderIidSearch search(iid); |
| 180 | iterateInPluginMetaData(raw, f&: search); |
| 181 | return search.matchesIid; |
| 182 | } |
| 183 | |
| 184 | bool 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 = 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 | |
| 230 | QJsonObject 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 | |
| 252 | class QFactoryLoaderPrivate : public QObjectPrivate |
| 253 | { |
| 254 | Q_DECLARE_PUBLIC(QFactoryLoader) |
| 255 | Q_DISABLE_COPY_MOVE(QFactoryLoaderPrivate) |
| 256 | public: |
| 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 ; |
| 267 | Qt::CaseSensitivity cs; |
| 268 | |
| 269 | void updateSinglePath(const QString &pluginDir); |
| 270 | #endif |
| 271 | }; |
| 272 | |
| 273 | #if QT_CONFIG(library) |
| 274 | |
| 275 | static Q_LOGGING_CATEGORY_WITH_ENV_OVERRIDE(lcFactoryLoader, "QT_DEBUG_PLUGINS" , |
| 276 | "qt.core.plugin.factoryloader" ) |
| 277 | |
| 278 | namespace { |
| 279 | struct 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 | |
| 288 | Q_GLOBAL_STATIC(QFactoryLoaderGlobals, qt_factoryloader_global) |
| 289 | |
| 290 | QFactoryLoaderPrivate::~QFactoryLoaderPrivate() |
| 291 | = default; |
| 292 | |
| 293 | inline 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 | |
| 392 | void QFactoryLoader::update() |
| 393 | { |
| 394 | #ifdef QT_SHARED |
| 395 | Q_D(QFactoryLoader); |
| 396 | |
| 397 | if (!d->extraSearchPath.isEmpty()) |
| 398 | d->updateSinglePath(path: d->extraSearchPath); |
| 399 | |
| 400 | const QStringList paths = QCoreApplication::libraryPaths(); |
| 401 | for (const QString &pluginDir : paths) { |
| 402 | #ifdef Q_OS_ANDROID |
| 403 | QString path = pluginDir; |
| 404 | #else |
| 405 | QString path = pluginDir + d->suffix; |
| 406 | #endif |
| 407 | d->updateSinglePath(path); |
| 408 | } |
| 409 | #else |
| 410 | Q_D(QFactoryLoader); |
| 411 | qCDebug(lcFactoryLoader) << "ignoring" << d->iid |
| 412 | << "since plugins are disabled in static builds" ; |
| 413 | #endif |
| 414 | } |
| 415 | |
| 416 | QFactoryLoader::~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) |
| 425 | QLibraryPrivate *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 | |
| 435 | void 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 | |
| 446 | QFactoryLoader::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 | |
| 474 | void QFactoryLoader::(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 | |
| 498 | QFactoryLoader::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 | |
| 523 | QList<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 { 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 | |
| 551 | QObject *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 | |
| 588 | QMultiMap<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 | |
| 600 | int 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 | |
| 613 | QT_END_NAMESPACE |
| 614 | |
| 615 | #include "moc_qfactoryloader_p.cpp" |
| 616 | |
| 617 | #endif // QT_NO_QOBJECT |
| 618 | |