123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603 | // Copyright (C) 2021 The Qt Company Ltd.// Copyright (C) 2022 Intel Corporation.// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only#include"qfactoryloader_p.h"#include"private/qcoreapplication_p.h"#include"private/qloggingregistry_p.h"#include"qcborarray.h"#include"qcbormap.h"#include"qcborstreamreader.h"#include"qcborvalue.h"#include"qdirlisting.h"#include"qfileinfo.h"#include"qjsonarray.h"#include"qjsondocument.h"#include"qjsonobject.h"#include"qplugin.h"#include"qpluginloader.h"#include <qtcore_tracepoints_p.h> QT_BEGIN_NAMESPACE using namespaceQt::StringLiterals;Q_TRACE_POINT(qtcore, QFactoryLoader_update,const QString &fileName);namespace{struct IterationResult {enum Result { FinishedSearch =0, ContinueSearch,// parse errors ParsingError = -1, InvalidMetaDataVersion = -2, InvalidTopLevelItem = -3, InvalidHeaderItem = -4,}; Result result; QCborError error = {QCborError::NoError }; Q_IMPLICIT IterationResult(Result r) :result(r) {} Q_IMPLICIT IterationResult(QCborError e) :result(ParsingError),error(e) {}};struct QFactoryLoaderIidSearch { QLatin1StringView iid;bool matchesIid =false;QFactoryLoaderIidSearch(QLatin1StringView iid) :iid(iid){Q_ASSERT(!iid.isEmpty()); }staticIterationResult::Result skip(QCborStreamReader &reader){// skip this, whatever it is reader.next();returnIterationResult::ContinueSearch;}IterationResult::Result operator()(QtPluginMetaDataKeys key, QCborStreamReader &reader){if(key !=QtPluginMetaDataKeys::IID)returnskip(reader); matchesIid = (reader.readAllString() == iid);returnIterationResult::FinishedSearch;}IterationResult::Result operator()(QUtf8StringView, QCborStreamReader &reader){returnskip(reader);}};struct QFactoryLoaderMetaDataKeysExtractor : QFactoryLoaderIidSearch { QCborArray keys;QFactoryLoaderMetaDataKeysExtractor(QLatin1StringView iid):QFactoryLoaderIidSearch(iid){}IterationResult::Result operator()(QtPluginMetaDataKeys key, QCborStreamReader &reader){if(key ==QtPluginMetaDataKeys::IID) {QFactoryLoaderIidSearch::operator()(key, reader);returnIterationResult::ContinueSearch;}if(key !=QtPluginMetaDataKeys::MetaData)returnskip(reader);if(!matchesIid)returnIterationResult::FinishedSearch;if(!reader.isMap() || !reader.isLengthKnown())returnIterationResult::InvalidHeaderItem;if(!reader.enterContainer())returnIterationResult::ParsingError;while(reader.isValid()) {// the metadata is JSON, so keys are all strings QByteArray key = reader.readAllUtf8String();if(key =="Keys") {if(!reader.isArray() || !reader.isLengthKnown())returnIterationResult::InvalidHeaderItem; keys =QCborValue::fromCbor(reader).toArray();break;}skip(reader);}// warning: we may not have finished iterating over the headerreturnIterationResult::FinishedSearch;}usingQFactoryLoaderIidSearch::operator();};}// unnamed namespacetemplate<typename F>static IterationResult iterateInPluginMetaData(QByteArrayView raw, F &&f){QPluginMetaData::Header header;Q_ASSERT(raw.size() >=qsizetype(sizeof(header)));memcpy(&header, raw.data(),sizeof(header));if(Q_UNLIKELY(header.version >QPluginMetaData::CurrentMetaDataVersion))returnIterationResult::InvalidMetaDataVersion;// use fromRawData to keep QCborStreamReader from copying raw = raw.sliced(sizeof(header)); QByteArray ba =QByteArray::fromRawData(raw.data(), raw.size()); QCborStreamReader reader(ba);if(reader.isInvalid())return reader.lastError();if(!reader.isMap())returnIterationResult::InvalidTopLevelItem;if(!reader.enterContainer())return reader.lastError();while(reader.isValid()) {IterationResult::Result r;if(reader.isInteger()) {// integer key, one of ours qint64 value = reader.toInteger();auto key =QtPluginMetaDataKeys(value);if(qint64(key) != value)returnIterationResult::InvalidHeaderItem;if(!reader.next())return reader.lastError(); r =f(key, reader);}else if(reader.isString()) { QByteArray key = reader.readAllUtf8String();if(key.isNull())return reader.lastError(); r =f(QUtf8StringView(key), reader);}else{returnIterationResult::InvalidTopLevelItem;}if(QCborError e = reader.lastError())return e;if(r !=IterationResult::ContinueSearch)return r;}if(!reader.leaveContainer())return reader.lastError();returnIterationResult::FinishedSearch;}static boolisIidMatch(QByteArrayView raw, QLatin1StringView iid){ QFactoryLoaderIidSearch search(iid);iterateInPluginMetaData(raw, search);return search.matchesIid;}boolQPluginParsedMetaData::parse(QByteArrayView raw){ QCborMap map;auto r =iterateInPluginMetaData(raw, [&](constauto&key, QCborStreamReader &reader) { QCborValue item =QCborValue::fromCbor(reader);if(item.isInvalid())returnIterationResult::ParsingError;ifconstexpr(std::is_enum_v<std::decay_t<decltype(key)>>) map[int(key)] = item;else map[QString::fromUtf8(key)] = item;returnIterationResult::ContinueSearch;});switch(r.result) {caseIterationResult::FinishedSearch:caseIterationResult::ContinueSearch:break;// parse errorscaseIterationResult::ParsingError:returnsetError(QFactoryLoader::tr("Metadata parsing error: %1").arg(r.error.toString()));caseIterationResult::InvalidMetaDataVersion:returnsetError(QFactoryLoader::tr("Invalid metadata version"));caseIterationResult::InvalidTopLevelItem:caseIterationResult::InvalidHeaderItem:returnsetError(QFactoryLoader::tr("Unexpected metadata contents"));}// header was validatedauto header = qFromUnaligned<QPluginMetaData::Header>(raw.data()); DecodedArchRequirements archReq = header.version ==0?decodeVersion0ArchRequirements(header.plugin_arch_requirements):decodeVersion1ArchRequirements(header.plugin_arch_requirements);// insert the keys not stored in the top-level CBOR map map[int(QtPluginMetaDataKeys::QtVersion)] =QT_VERSION_CHECK(header.qt_major_version, header.qt_minor_version,0); map[int(QtPluginMetaDataKeys::IsDebug)] = archReq.isDebug; map[int(QtPluginMetaDataKeys::Requirements)] = archReq.level; data =std::move(map);return true;} QJsonObject QPluginParsedMetaData::toJson()const{// convert from the internal CBOR representation to an external JSON one QJsonObject o;for(auto it : data.toMap()) { QString key;if(it.first.isInteger()) {switch(it.first.toInteger()) {#define CONVERT_TO_STRING(IntKey, StringKey, Description) \ case int(IntKey): key = QStringLiteral(StringKey); break;QT_PLUGIN_FOREACH_METADATA(CONVERT_TO_STRING)}}else{ key = it.first.toString();}if(!key.isEmpty()) o.insert(key, it.second.toJsonValue());}return o;}#if QT_CONFIG(library)Q_STATIC_LOGGING_CATEGORY_WITH_ENV_OVERRIDE(lcFactoryLoader,"QT_DEBUG_PLUGINS","qt.core.plugin.factoryloader")namespace{struct QFactoryLoaderGlobals {// needs to be recursive because loading one plugin could cause another// factory to be initialized QRecursiveMutex mutex; QList<QFactoryLoader *> loaders;};}Q_GLOBAL_STATIC(QFactoryLoaderGlobals, qt_factoryloader_global)inlinevoidQFactoryLoader::Private::updateSinglePath(const QString &path){struct LibraryReleaser {voidoperator()(QLibraryPrivate *library){if(library) library->release(); }};// If we've already loaded, skip it...if(loadedPaths.hasSeen(path))return;qCDebug(lcFactoryLoader) <<"checking directory path"<< path <<"..."; QDirListing plugins(path,#if defined(Q_OS_WIN) || defined(Q_OS_CYGWIN)QStringList(QStringLiteral("*.dll")),#elif defined(Q_OS_ANDROID)QStringList("libplugins_%1_*.so"_L1.arg(suffix)),#endifQDirListing::IteratorFlag::FilesOnly |QDirListing::IteratorFlag::ResolveSymlinks);for(constauto&dirEntry : plugins) {const QString &fileName = dirEntry.fileName();#if defined(Q_PROCESSOR_X86)if(fileName.endsWith(".avx2"_L1) || fileName.endsWith(".avx512"_L1)) {// ignore AVX2-optimized file, we'll do a bait-and-switch to it latercontinue;}#endifqCDebug(lcFactoryLoader) <<"looking at"<< fileName;Q_TRACE(QFactoryLoader_update, fileName);QLibraryPrivate::UniquePtr library; library.reset(QLibraryPrivate::findOrCreate(dirEntry.canonicalFilePath()));if(!library->isPlugin()) {qCDebug(lcFactoryLoader) << library->errorString <<Qt::endl <<" not a plugin";continue;} QStringList keys;bool metaDataOk =false; QString iid = library->metaData.value(QtPluginMetaDataKeys::IID).toString();if(iid ==QLatin1StringView(this->iid.constData(),this->iid.size())) { QCborMap object = library->metaData.value(QtPluginMetaDataKeys::MetaData).toMap(); metaDataOk =true;const QCborArray k = object.value("Keys"_L1).toArray();for(QCborValueConstRef v : k) keys += cs ? v.toString() : v.toString().toLower();}qCDebug(lcFactoryLoader) <<"Got keys from plugin meta data"<< keys;if(!metaDataOk)continue;staticconstexpr qint64 QtVersionNoPatch =QT_VERSION_CHECK(QT_VERSION_MAJOR, QT_VERSION_MINOR,0);int thisVersion = library->metaData.value(QtPluginMetaDataKeys::QtVersion).toInteger();if(iid.startsWith(QStringLiteral("org.qt-project.Qt.QPA"))) {// QPA plugins must match Qt Major.Minorif(thisVersion != QtVersionNoPatch) {qCDebug(lcFactoryLoader) <<"Ignoring QPA plugin due to mismatching Qt versions"<< QtVersionNoPatch << thisVersion;continue;}}int keyUsageCount =0;for(const QString &key :std::as_const(keys)) { QLibraryPrivate *&keyMapEntry = keyMap[key];if(QLibraryPrivate *existingLibrary = keyMapEntry) {staticconstexprbool QtBuildIsDebug =QT_CONFIG(debug);bool existingIsDebug = existingLibrary->metaData.value(QtPluginMetaDataKeys::IsDebug).toBool();bool thisIsDebug = library->metaData.value(QtPluginMetaDataKeys::IsDebug).toBool();bool configsAreDifferent = thisIsDebug != existingIsDebug;bool thisConfigDoesNotMatchQt = thisIsDebug != QtBuildIsDebug;if(configsAreDifferent && thisConfigDoesNotMatchQt)continue;// Existing library matches Qt's build config// If the existing library was built with a future Qt version,// whereas the one we're considering has a Qt version that fits// better, we prioritize the better match.int existingVersion = existingLibrary->metaData.value(QtPluginMetaDataKeys::QtVersion).toInteger();if(existingVersion == QtVersionNoPatch)continue;// Prefer exact Qt version matchif(existingVersion < QtVersionNoPatch && thisVersion > QtVersionNoPatch)continue;// Better too old than too newif(existingVersion < QtVersionNoPatch && thisVersion < existingVersion)continue;// Otherwise prefer newest} keyMapEntry = library.get();++keyUsageCount;}if(keyUsageCount || keys.isEmpty()) { library->setLoadHints(QLibrary::PreventUnloadHint);// once loaded, don't unload QMutexLocker locker(&mutex); libraries.push_back(std::move(library));}}; loadedLibraries.resize(libraries.size());}voidQFactoryLoader::update(){#ifdef QT_SHAREDif(!d->extraSearchPath.isEmpty()) d->updateSinglePath(d->extraSearchPath);const QStringList paths =QCoreApplication::libraryPaths();for(const QString &pluginDir : paths) {#ifdef Q_OS_ANDROID QString path = pluginDir;#else QString path = pluginDir + d->suffix;#endif d->updateSinglePath(path);}#elseqCDebug(lcFactoryLoader) <<"ignoring"<< d->iid <<"since plugins are disabled in static builds";#endif}QFactoryLoader::~QFactoryLoader(){if(!qt_factoryloader_global.isDestroyed()) { QMutexLocker locker(&qt_factoryloader_global->mutex); qt_factoryloader_global->loaders.removeOne(this);}#if QT_CONFIG(library)for(qsizetype i =0; i < d->loadedLibraries.size(); ++i) {if(d->loadedLibraries.at(i)) {auto&plugin = d->libraries.at(i);delete plugin->inst.data(); plugin->unload();}}#endiffor(QtPluginInstanceFunction staticInstance : d->usedStaticInstances) {if(staticInstance)deletestaticInstance();}}#if defined(Q_OS_UNIX) && !defined (Q_OS_DARWIN) QLibraryPrivate *QFactoryLoader::library(const QString &key)const{constauto it = d->keyMap.find(d->cs ? key : key.toLower());if(it == d->keyMap.cend())returnnullptr;return it->second;}#endifvoidQFactoryLoader::refreshAll(){if(qt_factoryloader_global.exists()) { QMutexLocker locker(&qt_factoryloader_global->mutex);for(QFactoryLoader *loader :std::as_const(qt_factoryloader_global->loaders)) loader->update();}}#endif// QT_CONFIG(library)QFactoryLoader::QFactoryLoader(const char*iid,const QString &suffix,Qt::CaseSensitivity cs){Q_ASSERT_X(suffix.startsWith(u'/'),"QFactoryLoader","For historical reasons, the suffix must start with '/' (and it can't be empty)"); d->iid = iid;#if QT_CONFIG(library) d->cs = cs; d->suffix = suffix;# ifdef Q_OS_ANDROIDif(!d->suffix.isEmpty() && d->suffix.at(0) == u'/') d->suffix.remove(0,1);# endif QMutexLocker locker(&qt_factoryloader_global->mutex);update(); qt_factoryloader_global->loaders.append(this);#elseQ_UNUSED(suffix);Q_UNUSED(cs);#endif}voidQFactoryLoader::setExtraSearchPath(const QString &path){#if QT_CONFIG(library)if(d->extraSearchPath == path)return;// nothing to do QMutexLocker locker(&qt_factoryloader_global->mutex); QString oldPath =std::exchange(d->extraSearchPath, path);if(oldPath.isEmpty()) {// easy case, just update this directory d->updateSinglePath(d->extraSearchPath);}else{// must re-scan everythingfor(qsizetype i =0; i < d->loadedLibraries.size(); ++i) {if(d->loadedLibraries.at(i)) {auto&plugin = d->libraries.at(i);delete plugin->inst.data();}} d->loadedLibraries.fill(false); d->loadedPaths.clear(); d->libraries.clear(); d->keyMap.clear();update();}#elseQ_UNUSED(path);#endif}QFactoryLoader::MetaDataList QFactoryLoader::metaData()const{ QList<QPluginParsedMetaData> metaData;#if QT_CONFIG(library) QMutexLocker locker(&d->mutex);for(constauto&library : d->libraries) metaData.append(library->metaData);#endif QLatin1StringView iid(d->iid.constData(), d->iid.size());constauto staticPlugins =QPluginLoader::staticPlugins();for(const QStaticPlugin &plugin : staticPlugins) { QByteArrayView pluginData(static_cast<const char*>(plugin.rawMetaData), plugin.rawMetaDataSize); QPluginParsedMetaData parsed(pluginData);if(parsed.isError() || parsed.value(QtPluginMetaDataKeys::IID) != iid)continue; metaData.append(std::move(parsed));}// other portions of the code will cast to int (e.g., keyMap())Q_ASSERT(metaData.size() <=std::numeric_limits<int>::max());return metaData;} QList<QCborArray>QFactoryLoader::metaDataKeys()const{ QList<QCborArray> metaData;#if QT_CONFIG(library) QMutexLocker locker(&d->mutex);for(constauto&library : d->libraries) {const QCborValue md = library->metaData.value(QtPluginMetaDataKeys::MetaData); metaData.append(md["Keys"_L1].toArray());}#endif QLatin1StringView iid(d->iid.constData(), d->iid.size());constauto staticPlugins =QPluginLoader::staticPlugins();for(const QStaticPlugin &plugin : staticPlugins) { QByteArrayView pluginData(static_cast<const char*>(plugin.rawMetaData), plugin.rawMetaDataSize); QFactoryLoaderMetaDataKeysExtractor extractor{ iid };iterateInPluginMetaData(pluginData, extractor);if(extractor.matchesIid) metaData +=std::move(extractor.keys);}// other portions of the code will cast to int (e.g., keyMap())Q_ASSERT(metaData.size() <=std::numeric_limits<int>::max());return metaData;} QObject *QFactoryLoader::instance(int index)const{if(index <0)returnnullptr; QMutexLocker lock(&d->mutex); QObject *obj =instanceHelper_locked(index);if(obj && !obj->parent()) obj->moveToThread(QCoreApplicationPrivate::mainThread());return obj;}inline QObject *QFactoryLoader::instanceHelper_locked(int index)const{#if QT_CONFIG(library)if(size_t(index) < d->libraries.size()) { QLibraryPrivate *library = d->libraries[index].get(); d->loadedLibraries[index] =true;return library->pluginInstance();}// we know d->libraries.size() <= index <= numeric_limits<decltype(index)>::max() → no overflow index -=static_cast<int>(d->libraries.size());#endif QLatin1StringView iid(d->iid.constData(), d->iid.size());const QList<QStaticPlugin> staticPlugins =QPluginLoader::staticPlugins(); qsizetype i =0;for(QStaticPlugin plugin : staticPlugins) { QByteArrayView pluginData(static_cast<const char*>(plugin.rawMetaData), plugin.rawMetaDataSize);if(!isIidMatch(pluginData, iid))continue;if(i == index) {if(d->usedStaticInstances.size() <= i) d->usedStaticInstances.resize(i +1); d->usedStaticInstances[i] = plugin.instance;return plugin.instance();}++i;}returnnullptr;} QMultiMap<int, QString>QFactoryLoader::keyMap()const{ QMultiMap<int, QString> result;const QList<QCborArray> metaDataList =metaDataKeys();for(int i =0; i <int(metaDataList.size()); ++i) {const QCborArray &keys = metaDataList[i];for(QCborValueConstRef key : keys) result.insert(i, key.toString());}return result;}intQFactoryLoader::indexOf(const QString &needle)const{const QList<QCborArray> metaDataList =metaDataKeys();for(int i =0; i <int(metaDataList.size()); ++i) {const QCborArray &keys = metaDataList[i];for(QCborValueConstRef key : keys) {if(key.toString().compare(needle,Qt::CaseInsensitive) ==0)return i;}}return-1;} QT_END_NAMESPACE
|