diff options
Diffstat (limited to 'src/corelib/plugin/qlibrary.cpp')
-rw-r--r-- | src/corelib/plugin/qlibrary.cpp | 264 |
1 files changed, 138 insertions, 126 deletions
diff --git a/src/corelib/plugin/qlibrary.cpp b/src/corelib/plugin/qlibrary.cpp index 9f89abdf01..a3ef8e3c52 100644 --- a/src/corelib/plugin/qlibrary.cpp +++ b/src/corelib/plugin/qlibrary.cpp @@ -1,42 +1,6 @@ -/**************************************************************************** -** -** Copyright (C) 2020 The Qt Company Ltd. -** Copyright (C) 2021 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$ -** -****************************************************************************/ +// 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 "qlibrary_p.h" @@ -47,12 +11,11 @@ #include <qfile.h> #include <qfileinfo.h> #include <qjsondocument.h> -#include <qmap.h> #include <qmutex.h> #include <qoperatingsystemversion.h> #include <qstringlist.h> -#ifdef Q_OS_MAC +#ifdef Q_OS_DARWIN # include <private/qcore_mac_p.h> #endif #include <private/qcoreapplication_p.h> @@ -66,8 +29,15 @@ #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?) @@ -209,10 +179,20 @@ static QLibraryScanResult qt_find_pattern(const char *s, qsizetype s_len, QStrin 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 - QByteArrayView pattern = QPluginMetaData::MagicString; - static const QByteArrayMatcher matcher(pattern.toByteArray()); - qsizetype i = matcher.indexIn(s, s_len); + 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{}; @@ -231,7 +211,7 @@ static QLibraryScanResult qt_find_pattern(const char *s, qsizetype s_len, QStrin 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)) { @@ -239,7 +219,7 @@ static bool findPatternUnloaded(const QString &library, QLibraryPrivate *lib) lib->errorString = file.errorString(); qCWarning(qt_lcDebugPlugins, "%ls: cannot open: %ls", qUtf16Printable(library), qUtf16Printable(file.errorString())); - return false; + return {}; } // Files can be bigger than the virtual memory size on 32-bit systems, so @@ -256,7 +236,7 @@ static bool findPatternUnloaded(const QString &library, QLibraryPrivate *lib) // This can't be used as a plugin. qCWarning(qt_lcDebugPlugins, "%ls: failed to map to memory: %ls", qUtf16Printable(library), qUtf16Printable(file.errorString())); - return false; + return {}; } #else QByteArray data; @@ -273,15 +253,19 @@ static bool findPatternUnloaded(const QString &library, QLibraryPrivate *lib) 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(); - qCWarning(qt_lcDebugPlugins, "Found invalid metadata in lib %ls: %ls", + qCDebug(qt_lcDebugPlugins, "Found invalid metadata in lib %ls: %ls", qUtf16Printable(library), qUtf16Printable(errMsg)); } else { qCDebug(qt_lcDebugPlugins, "Found metadata in lib %ls, metadata=\n%s\n", qUtf16Printable(library), QJsonDocument(lib->metaData.toJson()).toJson().constData()); - return true; + return r; } } else { qCDebug(qt_lcDebugPlugins, "Failed to find metadata in lib %ls: %ls", @@ -290,7 +274,7 @@ static bool findPatternUnloaded(const QString &library, QLibraryPrivate *lib) lib->errorString = QLibrary::tr("Failed to extract plugin meta data from '%1': %2") .arg(library, errMsg); - return false; + return {}; } static void installCoverageTool(QLibraryPrivate *libPrivate) @@ -338,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() { @@ -358,19 +342,12 @@ 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 - lib->unload(QLibraryPrivate::NoUnloadSys); -#elif defined(Q_OS_DARWIN) +#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. @@ -379,14 +356,13 @@ inline void QLibraryStore::cleanup() lib->unload(); #endif } - delete lib; - it.value() = nullptr; + delete std::exchange(lib, nullptr); } } // dump all objects that remain if (lcDebugLibrary().isDebugEnabled()) { - for (QLibraryPrivate *lib : qAsConst(data->libraryMap)) { + for (auto &[_, lib] : data->libraryMap) { if (lib) qDebug(lcDebugLibrary) << "On QtCore unload," << lib->fileName << "was leaked, with" @@ -417,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; } @@ -452,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; } @@ -483,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) @@ -495,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); @@ -618,40 +614,41 @@ 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(QLatin1String(".dylib"))) + if (fileName.endsWith(".dylib"_L1)) return true; # endif QString completeSuffix = QFileInfo(fileName).completeSuffix(); if (completeSuffix.isEmpty()) return false; - auto isValidSuffix = [](QStringView s) { - // if this throws an empty-array error, you need to fix the #ifdef's: - const QLatin1String candidates[] = { + // 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." - */ - QLatin1String("sl"), +*/ + "sl"_L1, # if defined __ia64 - QLatin1String("so"), + "so"_L1, # endif # elif defined(Q_OS_AIX) - QLatin1String("a"), - QLatin1String("so"), + "a"_L1, + "so"_L1, # elif defined(Q_OS_DARWIN) - QLatin1String("so"), - QLatin1String("bundle"), + "so"_L1, + "bundle"_L1, # elif defined(Q_OS_UNIX) - QLatin1String("so"), + "so"_L1, # endif - }; // candidates + }; // candidates + + auto isValidSuffix = [&candidates](QStringView s) { return std::find(std::begin(candidates), std::end(candidates), s) != std::end(candidates); }; @@ -729,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 @@ -745,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. @@ -768,17 +780,17 @@ void QLibraryPrivate::updatePluginState() 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)) { - qCWarning(qt_lcDebugPlugins, "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")); + .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." @@ -804,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; } /*! @@ -820,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() */ @@ -834,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; } @@ -934,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 @@ -963,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()); } /*! @@ -987,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 } /*! @@ -1124,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. |