diff options
Diffstat (limited to 'src/corelib/plugin/qlibrary.cpp')
-rw-r--r-- | src/corelib/plugin/qlibrary.cpp | 571 |
1 files changed, 280 insertions, 291 deletions
diff --git a/src/corelib/plugin/qlibrary.cpp b/src/corelib/plugin/qlibrary.cpp index adac2f41da..a3ef8e3c52 100644 --- a/src/corelib/plugin/qlibrary.cpp +++ b/src/corelib/plugin/qlibrary.cpp @@ -1,84 +1,61 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Copyright (C) 2018 Intel Corporation. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtCore module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ -#include "qplatformdefs.h" +// Copyright (C) 2020 The Qt Company Ltd. +// Copyright (C) 2021 Intel Corporation. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qlibrary.h" - -#include "qfactoryloader_p.h" #include "qlibrary_p.h" -#include <qstringlist.h> + +#include <q20algorithm.h> +#include <qbytearraymatcher.h> +#include <qdebug.h> +#include <qendian.h> #include <qfile.h> #include <qfileinfo.h> +#include <qjsondocument.h> #include <qmutex.h> -#include <qmap.h> -#include <private/qcoreapplication_p.h> -#include <private/qsystemerror_p.h> -#ifdef Q_OS_MAC +#include <qoperatingsystemversion.h> +#include <qstringlist.h> + +#ifdef Q_OS_DARWIN # include <private/qcore_mac_p.h> #endif -#ifndef NO_ERRNO_H -#include <errno.h> -#endif // NO_ERROR_H -#include <qdebug.h> -#include <qlist.h> -#include <qdir.h> -#include <qendian.h> -#include <qjsondocument.h> -#include <qjsonvalue.h> +#include <private/qcoreapplication_p.h> +#include <private/qloggingregistry_p.h> +#include <private/qsystemerror_p.h> + +#include "qcoffpeparser_p.h" #include "qelfparser_p.h" +#include "qfactoryloader_p.h" #include "qmachparser_p.h" #include <qtcore_tracepoints_p.h> +#include <QtCore/q20map.h> + QT_BEGIN_NAMESPACE +using namespace Qt::StringLiterals; + +Q_TRACE_POINT(qtcore, QLibraryPrivate_load_entry, const QString &fileName); +Q_TRACE_POINT(qtcore, QLibraryPrivate_load_exit, bool success); + +// On Unix systema and on Windows with MinGW, we can mix and match debug and +// release plugins without problems. (unless compiled in debug-and-release mode +// - why?) +static constexpr bool PluginMustMatchQtDebug = + QOperatingSystemVersion::currentType() == QOperatingSystemVersion::Windows +#if defined(Q_CC_MINGW) + && QT_CONFIG(debug_and_release) +#endif + ; + #ifdef QT_NO_DEBUG -# define QLIBRARY_AS_DEBUG false +static constexpr bool QtBuildIsDebug = false; #else -# define QLIBRARY_AS_DEBUG true +static constexpr bool QtBuildIsDebug = true; #endif -#if defined(Q_OS_UNIX) || (defined(Q_CC_MINGW) && !QT_CONFIG(debug_and_release)) -// We don't use separate debug and release libs on UNIX, so we want -// to allow loading plugins, regardless of how they were built. -# define QT_NO_DEBUG_PLUGIN_CHECK -#endif +Q_LOGGING_CATEGORY_WITH_ENV_OVERRIDE(qt_lcDebugPlugins, "QT_DEBUG_PLUGINS", "qt.core.plugin.loader") +static Q_LOGGING_CATEGORY_WITH_ENV_OVERRIDE(lcDebugLibrary, "QT_DEBUG_PLUGINS", "qt.core.library") /*! \class QLibrary @@ -185,43 +162,43 @@ QT_BEGIN_NAMESPACE \sa loadHints */ - -static qsizetype qt_find_pattern(const char *s, qsizetype s_len, - const char *pattern, ulong p_len) +static QLibraryScanResult qt_find_pattern(const char *s, qsizetype s_len, QString *errMsg) { /* - we search from the end of the file because on the supported - systems, the read-only data/text segments are placed at the end - of the file. HOWEVER, when building with debugging enabled, all - the debug symbols are placed AFTER the data/text segments. - - what does this mean? when building in release mode, the search - is fast because the data we are looking for is at the end of the - file... when building in debug mode, the search is slower - because we have to skip over all the debugging symbols first - */ - - if (!s || !pattern || qsizetype(p_len) > s_len) - return -1; + We used to search from the end of the file so we'd skip the code and find + the read-only data that usually follows. Unfortunately, in debug builds, + the debug sections come after and are usually much bigger than everything + else, making this process slower than necessary with debug plugins. - size_t i, hs = 0, hp = 0, delta = s_len - p_len; - - for (i = 0; i < p_len; ++i) { - hs += s[delta + i]; - hp += pattern[i]; - } - i = delta; - for (;;) { - if (hs == hp && qstrncmp(s + i, pattern, p_len) == 0) - return i; // can't overflow, by construction - if (i == 0) - break; - --i; - hs -= s[i + p_len]; - hs += s[i]; + More importantly, the pattern string may exist in the debug information due + to it being used in the plugin in the first place. + */ +#if defined(Q_OF_ELF) + return QElfParser::parse({s, s_len}, errMsg); +#elif defined(Q_OF_MACH_O) + return QMachOParser::parse(s, s_len, errMsg); +#elif defined(Q_OS_WIN) + return QCoffPeParser::parse({s, s_len}, errMsg); +#else +# warning "Qt does not know how to efficiently parse your platform's binary format; using slow fall-back." +#endif + static constexpr auto matcher = [] { + // QPluginMetaData::MagicString is not NUL-terminated, but + // qMakeStaticByteArrayMatcher requires its argument to be, so + // duplicate here, but statically check we didn't mess up: + constexpr auto &pattern = "QTMETADATA !"; + constexpr auto magic = std::string_view(QPluginMetaData::MagicString, + sizeof(QPluginMetaData::MagicString)); + static_assert(pattern == magic); + return qMakeStaticByteArrayMatcher(pattern); + }(); + qsizetype i = matcher.indexIn({s, s_len}); + if (i < 0) { + *errMsg = QLibrary::tr("'%1' is not a Qt plugin").arg(*errMsg); + return QLibraryScanResult{}; } - - return -1; + i += sizeof(QPluginMetaData::MagicString); + return { i, s_len - i }; } /* @@ -234,17 +211,15 @@ static qsizetype qt_find_pattern(const char *s, qsizetype s_len, information could not be read. Returns true if version information is present and successfully read. */ -static bool findPatternUnloaded(const QString &library, QLibraryPrivate *lib) +static QLibraryScanResult findPatternUnloaded(const QString &library, QLibraryPrivate *lib) { QFile file(library); if (!file.open(QIODevice::ReadOnly)) { if (lib) lib->errorString = file.errorString(); - if (qt_debug_component()) { - qWarning("%s: %ls", QFile::encodeName(library).constData(), - qUtf16Printable(QSystemError::stdString())); - } - return false; + qCWarning(qt_lcDebugPlugins, "%ls: cannot open: %ls", qUtf16Printable(library), + qUtf16Printable(file.errorString())); + return {}; } // Files can be bigger than the virtual memory size on 32-bit systems, so @@ -252,88 +227,54 @@ static bool findPatternUnloaded(const QString &library, QLibraryPrivate *lib) constexpr qint64 MaxMemoryMapSize = Q_INT64_C(1) << (sizeof(qsizetype) > 4 ? 40 : 29); - QByteArray data; qsizetype fdlen = qMin(file.size(), MaxMemoryMapSize); const char *filedata = reinterpret_cast<char *>(file.map(0, fdlen)); +#ifdef Q_OS_UNIX + if (filedata == nullptr) { + // If we can't mmap(), then the dynamic loader won't be able to either. + // This can't be used as a plugin. + qCWarning(qt_lcDebugPlugins, "%ls: failed to map to memory: %ls", + qUtf16Printable(library), qUtf16Printable(file.errorString())); + return {}; + } +#else + QByteArray data; if (filedata == nullptr) { - // Try reading the data into memory instead (up to 64 MB). + // It's unknown at this point whether Windows supports LoadLibrary() on + // files that fail to CreateFileMapping / MapViewOfFile, so we err on + // the side of doing a regular read into memory (up to 64 MB). data = file.read(64 * 1024 * 1024); filedata = data.constData(); fdlen = data.size(); } +#endif - /* - ELF and Mach-O binaries with GCC have .qplugin sections. - */ - bool hasMetaData = false; - qsizetype pos = 0; - char pattern[] = "qTMETADATA "; - pattern[0] = 'Q'; // Ensure the pattern "QTMETADATA" is not found in this library should QPluginLoader ever encounter it. - const ulong plen = ulong(qstrlen(pattern)); -#if defined (Q_OF_ELF) && defined(Q_CC_GNU) - int r = QElfParser().parse(filedata, fdlen, library, lib, &pos, &fdlen); - if (r == QElfParser::Corrupt || r == QElfParser::NotElf) { - if (lib && qt_debug_component()) { - qWarning("QElfParser: %ls", qUtf16Printable(lib->errorString)); - } - return false; - } else if (r == QElfParser::QtMetaDataSection) { - qsizetype rel = qt_find_pattern(filedata + pos, fdlen, pattern, plen); - if (rel < 0) - pos = -1; - else - pos += rel; - hasMetaData = true; - } -#elif defined(Q_OF_MACH_O) - { - QString errorString; - int r = QMachOParser::parse(filedata, fdlen, library, &errorString, &pos, &fdlen); - if (r == QMachOParser::NotSuitable) { - if (qt_debug_component()) - qWarning("QMachOParser: %ls", qUtf16Printable(errorString)); - if (lib) - lib->errorString = errorString; - return false; - } - // even if the metadata section was not found, the Mach-O parser will - // at least return the boundaries of the right architecture - qsizetype rel = qt_find_pattern(filedata + pos, fdlen, pattern, plen); - if (rel < 0) - pos = -1; - else - pos += rel; - hasMetaData = true; - } -#else - pos = qt_find_pattern(filedata, fdlen, pattern, plen); - if (pos > 0) - hasMetaData = true; -#endif // defined(Q_OF_ELF) && defined(Q_CC_GNU) - - bool ret = false; - - if (pos >= 0 && hasMetaData) { - const char *data = filedata + pos; - QString errMsg; - QJsonDocument doc = qJsonFromRawLibraryMetaData(data, fdlen, &errMsg); - if (doc.isNull()) { - qWarning("Found invalid metadata in lib %ls: %ls", - qUtf16Printable(library), qUtf16Printable(errMsg)); + QString errMsg = library; + QLibraryScanResult r = qt_find_pattern(filedata, fdlen, &errMsg); + if (r.length) { +#if defined(Q_OF_MACH_O) + if (r.isEncrypted) + return r; +#endif + if (!lib->metaData.parse(QByteArrayView(filedata + r.pos, r.length))) { + errMsg = lib->metaData.errorString(); + qCDebug(qt_lcDebugPlugins, "Found invalid metadata in lib %ls: %ls", + qUtf16Printable(library), qUtf16Printable(errMsg)); } else { - lib->metaData = doc.object(); - if (qt_debug_component()) - qWarning("Found metadata in lib %s, metadata=\n%s\n", - library.toLocal8Bit().constData(), doc.toJson().constData()); - ret = !doc.isNull(); + qCDebug(qt_lcDebugPlugins, "Found metadata in lib %ls, metadata=\n%s\n", + qUtf16Printable(library), + QJsonDocument(lib->metaData.toJson()).toJson().constData()); + return r; } + } else { + qCDebug(qt_lcDebugPlugins, "Failed to find metadata in lib %ls: %ls", + qUtf16Printable(library), qUtf16Printable(errMsg)); } - if (!ret && lib) - lib->errorString = QLibrary::tr("Failed to extract plugin meta data from '%1'").arg(library); - file.close(); - return ret; + lib->errorString = QLibrary::tr("Failed to extract plugin meta data from '%1': %2") + .arg(library, errMsg); + return {}; } static void installCoverageTool(QLibraryPrivate *libPrivate) @@ -354,8 +295,7 @@ static void installCoverageTool(QLibraryPrivate *libPrivate) int ret = __coveragescanner_register_library(libPrivate->fileName.toLocal8Bit()); - if (qt_debug_component()) { - if (ret >= 0) { + if (ret >= 0) { qDebug("coverage data for %ls registered", qUtf16Printable(libPrivate->fileName)); } else { @@ -382,13 +322,13 @@ private: static inline QLibraryStore *instance(); // all members and instance() are protected by qt_library_mutex - typedef QMap<QString, QLibraryPrivate *> LibraryMap; + typedef std::map<QString, QLibraryPrivate *> LibraryMap; LibraryMap libraryMap; }; -static QBasicMutex qt_library_mutex; -static QLibraryStore *qt_library_data = nullptr; -static bool qt_library_data_once; +Q_CONSTINIT static QBasicMutex qt_library_mutex; +Q_CONSTINIT static QLibraryStore *qt_library_data = nullptr; +Q_CONSTINIT static bool qt_library_data_once; QLibraryStore::~QLibraryStore() { @@ -402,33 +342,31 @@ inline void QLibraryStore::cleanup() return; // find any libraries that are still loaded but have a no one attached to them - LibraryMap::Iterator it = data->libraryMap.begin(); - for (; it != data->libraryMap.end(); ++it) { - QLibraryPrivate *lib = it.value(); + for (auto &[_, lib] : data->libraryMap) { if (lib->libraryRefCount.loadRelaxed() == 1) { if (lib->libraryUnloadCount.loadRelaxed() > 0) { Q_ASSERT(lib->pHnd.loadRelaxed()); lib->libraryUnloadCount.storeRelaxed(1); -#ifdef __GLIBC__ - // glibc has a bug in unloading from global destructors - // see https://bugzilla.novell.com/show_bug.cgi?id=622977 - // and http://sourceware.org/bugzilla/show_bug.cgi?id=11941 +#if defined(Q_OS_DARWIN) + // We cannot fully unload libraries, as we don't know if there are + // lingering references (in system threads e.g.) to Objective-C classes + // defined in the library. lib->unload(QLibraryPrivate::NoUnloadSys); #else lib->unload(); #endif } - delete lib; - it.value() = nullptr; + delete std::exchange(lib, nullptr); } } - if (qt_debug_component()) { - // dump all objects that remain - for (QLibraryPrivate *lib : qAsConst(data->libraryMap)) { + // dump all objects that remain + if (lcDebugLibrary().isDebugEnabled()) { + for (auto &[_, lib] : data->libraryMap) { if (lib) - qDebug() << "On QtCore unload," << lib->fileName << "was leaked, with" - << lib->libraryRefCount.loadRelaxed() << "users"; + qDebug(lcDebugLibrary) + << "On QtCore unload," << lib->fileName << "was leaked, with" + << lib->libraryRefCount.loadRelaxed() << "users"; } } @@ -455,24 +393,34 @@ QLibraryStore *QLibraryStore::instance() inline QLibraryPrivate *QLibraryStore::findOrCreate(const QString &fileName, const QString &version, QLibrary::LoadHints loadHints) { + auto lazyNewLib = [&] { + auto result = new QLibraryPrivate(fileName, version, loadHints); + result->libraryRefCount.ref(); + return result; + }; + + if (fileName.isEmpty()) // request for empty d-pointer in QLibrary::setLoadHints(); + return lazyNewLib(); // must return an independent (new) object + QMutexLocker locker(&qt_library_mutex); QLibraryStore *data = instance(); - // check if this library is already loaded - QLibraryPrivate *lib = nullptr; - if (Q_LIKELY(data)) { - lib = data->libraryMap.value(fileName); - if (lib) - lib->mergeLoadHints(loadHints); + if (Q_UNLIKELY(!data)) { + locker.unlock(); + return lazyNewLib(); } - if (!lib) - lib = new QLibraryPrivate(fileName, version, loadHints); - // track this library - if (Q_LIKELY(data) && !fileName.isEmpty()) - data->libraryMap.insert(fileName, lib); + QString mapName = version.isEmpty() ? fileName : fileName + u'\0' + version; + + QLibraryPrivate *&lib = data->libraryMap[std::move(mapName)]; + if (lib) { + // already loaded + lib->libraryRefCount.ref(); + lib->mergeLoadHints(loadHints); + } else { + lib = lazyNewLib(); + } - lib->libraryRefCount.ref(); return lib; } @@ -490,9 +438,12 @@ inline void QLibraryStore::releaseLibrary(QLibraryPrivate *lib) Q_ASSERT(lib->libraryUnloadCount.loadRelaxed() == 0); if (Q_LIKELY(data) && !lib->fileName.isEmpty()) { - QLibraryPrivate *that = data->libraryMap.take(lib->fileName); - Q_ASSERT(lib == that); - Q_UNUSED(that); + using q20::erase_if; + const auto n = erase_if(data->libraryMap, [lib](const auto &e) { + return e.second == lib; + }); + Q_ASSERT_X(n, "~QLibrary", "Did not find this library in the library map"); + Q_UNUSED(n); } delete lib; } @@ -521,7 +472,7 @@ void QLibraryPrivate::mergeLoadHints(QLibrary::LoadHints lh) if (pHnd.loadRelaxed()) return; - loadHintsInt.storeRelaxed(lh.toInt()); + loadHintsInt.fetchAndOrRelaxed(lh.toInt()); } QFunctionPointer QLibraryPrivate::resolve(const char *symbol) @@ -533,6 +484,13 @@ QFunctionPointer QLibraryPrivate::resolve(const char *symbol) void QLibraryPrivate::setLoadHints(QLibrary::LoadHints lh) { + // Set the load hints directly for a dummy if this object is not associated + // with a file. Such object is not shared between multiple instances. + if (fileName.isEmpty()) { + loadHintsInt.storeRelaxed(lh.toInt()); + return; + } + // this locks a global mutex QMutexLocker lock(&qt_library_mutex); mergeLoadHints(lh); @@ -541,7 +499,7 @@ void QLibraryPrivate::setLoadHints(QLibrary::LoadHints lh) QObject *QLibraryPrivate::pluginInstance() { // first, check if the instance is cached and hasn't been deleted - QObject *obj = (QMutexLocker(&mutex), inst.data()); + QObject *obj = [&](){ QMutexLocker locker(&mutex); return inst.data(); }(); if (obj) return obj; @@ -577,13 +535,9 @@ bool QLibraryPrivate::load() Q_TRACE(QLibraryPrivate_load_entry, fileName); bool ret = load_sys(); - if (qt_debug_component()) { - if (ret) { - qDebug() << "loaded library" << fileName; - } else { - qDebug() << qUtf8Printable(errorString); - } - } + qCDebug(lcDebugLibrary) + << fileName + << (ret ? "loaded library" : qUtf8Printable(u"cannot load: " + errorString)); if (ret) { //when loading a library we add a reference to it so that the QLibraryPrivate won't get deleted //this allows to unload the library at a later time @@ -605,9 +559,8 @@ bool QLibraryPrivate::unload(UnloadFlag flag) QMutexLocker locker(&mutex); delete inst.data(); if (flag == NoUnloadSys || unload_sys()) { - if (qt_debug_component()) - qWarning() << "QLibraryPrivate::unload succeeded on" << fileName - << (flag == NoUnloadSys ? "(faked)" : ""); + qCDebug(lcDebugLibrary) << fileName << "unloaded library" + << (flag == NoUnloadSys ? "(faked)" : ""); // when the library is unloaded, we release the reference on it so that 'this' // can get deleted libraryRefCount.deref(); @@ -638,8 +591,7 @@ QtPluginInstanceFunction QLibraryPrivate::loadPlugin() instanceFactory.storeRelease(ptr); // two threads may store the same value return ptr; } - if (qt_debug_component()) - qWarning() << "QLibraryPrivate::loadPlugin failed on" << fileName << ":" << errorString; + qCDebug(qt_lcDebugPlugins) << "QLibraryPrivate::loadPlugin failed on" << fileName << ":" << errorString; pluginState = IsNotAPlugin; return nullptr; } @@ -662,35 +614,43 @@ QtPluginInstanceFunction QLibraryPrivate::loadPlugin() bool QLibrary::isLibrary(const QString &fileName) { #if defined(Q_OS_WIN) - return fileName.endsWith(QLatin1String(".dll"), Qt::CaseInsensitive); + return fileName.endsWith(".dll"_L1, Qt::CaseInsensitive); #else // Generic Unix +# if defined(Q_OS_DARWIN) + // On Apple platforms, dylib look like libmylib.1.0.0.dylib + if (fileName.endsWith(".dylib"_L1)) + return true; +# endif QString completeSuffix = QFileInfo(fileName).completeSuffix(); if (completeSuffix.isEmpty()) return false; - const auto suffixes = QStringView{completeSuffix}.split(QLatin1Char('.')); - QStringList validSuffixList; + // if this throws an empty-array error, you need to fix the #ifdef's: + const QLatin1StringView candidates[] = { # if defined(Q_OS_HPUX) /* See "HP-UX Linker and Libraries User's Guide", section "Link-time Differences between PA-RISC and IPF": "In PA-RISC (PA-32 and PA-64) shared libraries are suffixed with .sl. In IPF (32-bit and 64-bit), the shared libraries are suffixed with .so. For compatibility, the IPF linker also supports the .sl suffix." - */ - validSuffixList << QLatin1String("sl"); +*/ + "sl"_L1, # if defined __ia64 - validSuffixList << QLatin1String("so"); + "so"_L1, # endif # elif defined(Q_OS_AIX) - validSuffixList << QLatin1String("a") << QLatin1String("so"); + "a"_L1, + "so"_L1, # elif defined(Q_OS_DARWIN) - // On Apple platforms, dylib look like libmylib.1.0.0.dylib - if (suffixes.last() == QLatin1String("dylib")) - return true; - - validSuffixList << QLatin1String("so") << QLatin1String("bundle"); + "so"_L1, + "bundle"_L1, # elif defined(Q_OS_UNIX) - validSuffixList << QLatin1String("so"); + "so"_L1, # endif + }; // candidates + + auto isValidSuffix = [&candidates](QStringView s) { + return std::find(std::begin(candidates), std::end(candidates), s) != std::end(candidates); + }; // Examples of valid library names: // libfoo.so @@ -699,36 +659,54 @@ bool QLibrary::isLibrary(const QString &fileName) // libfoo-0.3.so // libfoo-0.3.so.0.3.0 - int suffix; - int suffixPos = -1; - for (suffix = 0; suffix < validSuffixList.count() && suffixPos == -1; ++suffix) - suffixPos = suffixes.indexOf(validSuffixList.at(suffix)); + auto suffixes = qTokenize(completeSuffix, u'.'); + auto it = suffixes.begin(); + const auto end = suffixes.end(); + + auto isNumeric = [](QStringView s) { bool ok; (void)s.toInt(&ok); return ok; }; - bool valid = suffixPos != -1; - for (int i = suffixPos + 1; i < suffixes.count() && valid; ++i) - if (i != suffixPos) - (void)suffixes.at(i).toInt(&valid); - return valid; + while (it != end) { + if (isValidSuffix(*it++)) + return q20::ranges::all_of(it, end, isNumeric); + } + return false; // no valid suffix found #endif } static bool qt_get_metadata(QLibraryPrivate *priv, QString *errMsg) { - auto getMetaData = [](QFunctionPointer fptr) { - auto f = reinterpret_cast<QPluginMetaData (*)()>(fptr); - return f(); + auto error = [=](QString &&explanation) { + *errMsg = QLibrary::tr("'%1' is not a Qt plugin (%2)").arg(priv->fileName, std::move(explanation)); + return false; }; - QFunctionPointer pfn = priv->resolve("qt_plugin_query_metadata"); - if (!pfn) - return false; + QPluginMetaData metaData; + QFunctionPointer pfn = priv->resolve("qt_plugin_query_metadata_v2"); + if (pfn) { + metaData = reinterpret_cast<QPluginMetaData (*)()>(pfn)(); +#if QT_VERSION <= QT_VERSION_CHECK(7, 0, 0) + } else if ((pfn = priv->resolve("qt_plugin_query_metadata"))) { + metaData = reinterpret_cast<QPluginMetaData (*)()>(pfn)(); + if (metaData.size < sizeof(QPluginMetaData::MagicHeader)) + return error(QLibrary::tr("metadata too small")); + + // adjust the meta data to point to the header + auto data = reinterpret_cast<const char *>(metaData.data); + data += sizeof(QPluginMetaData::MagicString); + metaData.data = data; + metaData.size -= sizeof(QPluginMetaData::MagicString); +#endif + } else { + return error(QLibrary::tr("entrypoint to query the plugin meta data not found")); + } - auto metaData = getMetaData(pfn); - QJsonDocument doc = qJsonFromRawLibraryMetaData(reinterpret_cast<const char *>(metaData.data), metaData.size, errMsg); - if (doc.isNull()) - return false; - priv->metaData = doc.object(); - return true; + if (metaData.size < sizeof(QPluginMetaData::Header)) + return error(QLibrary::tr("metadata too small")); + + if (priv->metaData.parse(metaData)) + return true; + *errMsg = priv->metaData.errorString(); + return false; } bool QLibraryPrivate::isPlugin() @@ -748,8 +726,8 @@ void QLibraryPrivate::updatePluginState() bool success = false; -#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC) - if (fileName.endsWith(QLatin1String(".debug"))) { +#if defined(Q_OS_UNIX) && !defined(Q_OS_DARWIN) + if (fileName.endsWith(".debug"_L1)) { // refuse to load a file that ends in .debug // these are the debug symbols from the libraries // the problem is that they are valid shared library files @@ -764,7 +742,22 @@ void QLibraryPrivate::updatePluginState() if (!pHnd.loadRelaxed()) { // scan for the plugin metadata without loading - success = findPatternUnloaded(fileName, this); + QLibraryScanResult result = findPatternUnloaded(fileName, this); +#if defined(Q_OF_MACH_O) + if (result.length && result.isEncrypted) { + // We found the .qtmetadata section, but since the library is encrypted + // we need to dlopen() it before we can parse the metadata for further + // validation. + qCDebug(qt_lcDebugPlugins, "Library is encrypted. Doing prospective load before parsing metadata"); + locker.unlock(); + load(); + locker.relock(); + success = qt_get_metadata(this, &errorString); + } else +#endif + { + success = result.length != 0; + } } else { // library is already loaded (probably via QLibrary) // simply get the target function and call it. @@ -784,28 +777,24 @@ void QLibraryPrivate::updatePluginState() pluginState = IsNotAPlugin; // be pessimistic - uint qt_version = (uint)metaData.value(QLatin1String("version")).toDouble(); - bool debug = metaData.value(QLatin1String("debug")).toBool(); + uint qt_version = uint(metaData.value(QtPluginMetaDataKeys::QtVersion).toInteger()); + bool debug = metaData.value(QtPluginMetaDataKeys::IsDebug).toBool(); if ((qt_version & 0x00ff00) > (QT_VERSION & 0x00ff00) || (qt_version & 0xff0000) != (QT_VERSION & 0xff0000)) { - if (qt_debug_component()) { - qWarning("In %s:\n" + qCDebug(qt_lcDebugPlugins, "In %s:\n" " Plugin uses incompatible Qt library (%d.%d.%d) [%s]", QFile::encodeName(fileName).constData(), (qt_version&0xff0000) >> 16, (qt_version&0xff00) >> 8, qt_version&0xff, debug ? "debug" : "release"); - } errorString = QLibrary::tr("The plugin '%1' uses incompatible Qt library. (%2.%3.%4) [%5]") - .arg(fileName) - .arg((qt_version&0xff0000) >> 16) - .arg((qt_version&0xff00) >> 8) - .arg(qt_version&0xff) - .arg(debug ? QLatin1String("debug") : QLatin1String("release")); -#ifndef QT_NO_DEBUG_PLUGIN_CHECK - } else if (debug != QLIBRARY_AS_DEBUG) { + .arg(fileName, + QString::number((qt_version & 0xff0000) >> 16), + QString::number((qt_version & 0xff00) >> 8), + QString::number(qt_version & 0xff), + debug ? "debug"_L1 : "release"_L1); + } else if (PluginMustMatchQtDebug && debug != QtBuildIsDebug) { //don't issue a qWarning since we will hopefully find a non-debug? --Sam errorString = QLibrary::tr("The plugin '%1' uses incompatible Qt library." " (Cannot mix debug and release libraries.)").arg(fileName); -#endif } else { pluginState = IsAPlugin; } @@ -827,9 +816,11 @@ bool QLibrary::load() return false; if (d.tag() == Loaded) return d->pHnd.loadRelaxed(); - else + if (d->load()) { d.setTag(Loaded); - return d->load(); + return true; + } + return false; } /*! @@ -843,7 +834,9 @@ bool QLibrary::load() call will fail, and unloading will only happen when every instance has called unload(). - Note that on Mac OS X 10.3 (Panther), dynamic libraries cannot be unloaded. + Note that on \macos, dynamic libraries cannot be unloaded. + QLibrary::unload() will return \c true, but the library will remain + loaded into the process. \sa resolve(), load() */ @@ -857,13 +850,17 @@ bool QLibrary::unload() } /*! - Returns \c true if the library is loaded; otherwise returns \c false. + Returns \c true if load() succeeded; otherwise returns \c false. + + \note Prior to Qt 6.6, this function would return \c true even without a + call to load() if another QLibrary object on the same library had caused it + to be loaded. \sa load() */ bool QLibrary::isLoaded() const { - return d && d->pHnd.loadRelaxed(); + return d.tag() == Loaded; } @@ -957,13 +954,7 @@ QLibrary::~QLibrary() void QLibrary::setFileName(const QString &fileName) { - QLibrary::LoadHints lh; - if (d) { - lh = d->loadHints(); - d->release(); - d = {}; - } - d = QLibraryPrivate::findOrCreate(fileName, QString(), lh); + setFileNameAndVersion(fileName, QString()); } QString QLibrary::fileName() const @@ -986,13 +977,7 @@ QString QLibrary::fileName() const */ void QLibrary::setFileNameAndVersion(const QString &fileName, int verNum) { - QLibrary::LoadHints lh; - if (d) { - lh = d->loadHints(); - d->release(); - d = {}; - } - d = QLibraryPrivate::findOrCreate(fileName, verNum >= 0 ? QString::number(verNum) : QString(), lh); + setFileNameAndVersion(fileName, verNum >= 0 ? QString::number(verNum) : QString()); } /*! @@ -1010,9 +995,9 @@ void QLibrary::setFileNameAndVersion(const QString &fileName, const QString &ver if (d) { lh = d->loadHints(); d->release(); - d = {}; } - d = QLibraryPrivate::findOrCreate(fileName, version, lh); + QLibraryPrivate *dd = QLibraryPrivate::findOrCreate(fileName, version, lh); + d = QTaggedPointer(dd, NotLoaded); // we haven't load()ed } /*! @@ -1147,6 +1132,10 @@ QString QLibrary::errorString() const lazy symbol resolution, and will not export external symbols for resolution in other dynamically-loaded libraries. + \note Hints can only be cleared when this object is not associated with a + file. Hints can only be added once the file name is set (\a hints will + be or'ed with the old hints). + \note Setting this property after the library has been loaded has no effect and loadHints() will not reflect those changes. |