diff options
Diffstat (limited to 'src/corelib/plugin')
24 files changed, 2675 insertions, 1765 deletions
diff --git a/src/corelib/plugin/qcoffpeparser.cpp b/src/corelib/plugin/qcoffpeparser.cpp new file mode 100644 index 0000000000..639e402a07 --- /dev/null +++ b/src/corelib/plugin/qcoffpeparser.cpp @@ -0,0 +1,383 @@ +// 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 "qcoffpeparser_p.h" + +#include <qendian.h> +#include <qloggingcategory.h> +#include <qnumeric.h> + +#include <optional> + +#include <qt_windows.h> + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +// Whether we include some extra validity checks +// (checks to ensure we don't read out-of-bounds are always included) +static constexpr bool IncludeValidityChecks = true; + +static constexpr inline auto metadataSectionName() noexcept { return ".qtmetadata"_L1; } +static constexpr QLatin1StringView truncatedSectionName = + metadataSectionName().left(sizeof(IMAGE_SECTION_HEADER::Name)); + +#ifdef QT_BUILD_INTERNAL +# define QCOFFPEPARSER_DEBUG +#endif +#if defined(QCOFFPEPARSER_DEBUG) +static Q_LOGGING_CATEGORY(lcCoffPeParser, "qt.core.plugin.coffpeparser") +# define peDebug qCDebug(lcCoffPeParser) << reinterpret_cast<const char16_t *>(error.errMsg->constData()) << ':' +#else +# define peDebug if (false) {} else QNoDebug() +#endif + +QT_WARNING_PUSH +QT_WARNING_DISABLE_CLANG("-Wunused-const-variable") + +static const WORD ExpectedMachine = +#if 0 + // nothing, just so everything is #elf +#elif defined(Q_PROCESSOR_ARM_32) + IMAGE_FILE_MACHINE_ARMNT +#elif defined(Q_PROCESSOR_ARM_64) + IMAGE_FILE_MACHINE_ARM64 +#elif defined(Q_PROCESSOR_IA64) + IMAGE_FILE_MACHINE_IA64 +#elif defined(Q_PROCESSOR_RISCV_32) + 0x5032 // IMAGE_FILE_MACHINE_RISCV32 +#elif defined(Q_PROCESSOR_RISCV_64) + 0x5064 // IMAGE_FILE_MACHINE_RISCV64 +#elif defined(Q_PROCESSOR_X86_32) + IMAGE_FILE_MACHINE_I386 +#elif defined(Q_PROCESSOR_X86_64) + IMAGE_FILE_MACHINE_AMD64 +#else +# error "Unknown Q_PROCESSOR_xxx macro, please update." + IMAGE_FILE_MACHINE_UNKNOWN +#endif + ; + +static const WORD ExpectedOptionalHeaderSignature = + sizeof(void*) == sizeof(quint64) ? IMAGE_NT_OPTIONAL_HDR64_MAGIC : + IMAGE_NT_OPTIONAL_HDR32_MAGIC; + +namespace { +struct ErrorMaker +{ + QString *errMsg; + constexpr ErrorMaker(QString *errMsg) : errMsg(errMsg) {} + + Q_DECL_COLD_FUNCTION QLibraryScanResult operator()(QString &&text) const + { + *errMsg = QLibrary::tr("'%1' is not a valid Windows DLL (%2)").arg(*errMsg, std::move(text)); + return {}; + } + + Q_DECL_COLD_FUNCTION QLibraryScanResult toosmall() const + { + *errMsg = QLibrary::tr("'%1' is too small").arg(*errMsg); + return {}; + } + + Q_DECL_COLD_FUNCTION QLibraryScanResult notplugin(QString &&explanation) const + { + *errMsg = QLibrary::tr("'%1' is not a Qt plugin (%2)").arg(*errMsg, explanation); + return {}; + } + + Q_DECL_COLD_FUNCTION QLibraryScanResult notfound() const + { + return notplugin(QLibrary::tr("metadata not found")); + } +}; + +struct HeaderDebug { const IMAGE_NT_HEADERS *h; }; +Q_DECL_UNUSED static QDebug &operator<<(QDebug &d, HeaderDebug h) +{ + switch (h.h->Signature & 0xffff) { + case IMAGE_OS2_SIGNATURE: return d << "NE executable"; + case IMAGE_VXD_SIGNATURE: return d << "LE executable"; + default: return d << "Unknown file type"; + case IMAGE_NT_SIGNATURE: break; + } + + // the FileHeader and the starting portion of OptionalHeader are the same + // for 32- and 64-bit + switch (h.h->OptionalHeader.Magic) { + case IMAGE_NT_OPTIONAL_HDR32_MAGIC: d << "COFF PE"; break; + case IMAGE_NT_OPTIONAL_HDR64_MAGIC: d << "COFF PE+"; break; + default: return d << "Unknown COFF PE type"; + } + + QDebugStateSaver saver(d); + d.nospace() << Qt::hex << Qt::showbase; + + switch (h.h->FileHeader.Machine) { + case IMAGE_FILE_MACHINE_I386: d << "i386"; break; + case IMAGE_FILE_MACHINE_ARM: d << "ARM"; break; + case IMAGE_FILE_MACHINE_ARMNT: d << "ARM Thumb-2"; break; + case IMAGE_FILE_MACHINE_THUMB: d << "Thumb"; break; + case IMAGE_FILE_MACHINE_IA64: d << "IA-64"; break; + case IMAGE_FILE_MACHINE_MIPS16: d << "MIPS16"; break; + case IMAGE_FILE_MACHINE_MIPSFPU: d << "MIPS with FPU"; break; + case IMAGE_FILE_MACHINE_MIPSFPU16: d << "MIPS16 with FPU"; break; + case IMAGE_FILE_MACHINE_AMD64: d << "x86-64"; break; + case 0xaa64: d << "AArch64"; break; + default: d << h.h->FileHeader.Machine; break; + } + + // this usually prints "executable DLL" + if (h.h->FileHeader.Characteristics & IMAGE_FILE_EXECUTABLE_IMAGE) + d << " executable"; + if (h.h->FileHeader.Characteristics & IMAGE_FILE_DLL) + d << " DLL"; + if (h.h->FileHeader.Characteristics & IMAGE_FILE_LARGE_ADDRESS_AWARE) + d << " large-address aware"; + + d << ", " << Qt::dec << h.h->FileHeader.NumberOfSections << " sections, "; + if (h.h->FileHeader.SizeOfOptionalHeader < sizeof(IMAGE_OPTIONAL_HEADER32)) + return d; + + auto optDebug = [&d](const auto *hdr) { + d << "(Windows " << hdr->MajorSubsystemVersion + << '.' << hdr->MinorSubsystemVersion; + switch (hdr->Subsystem) { + case IMAGE_SUBSYSTEM_NATIVE: d << " native)"; break; + case IMAGE_SUBSYSTEM_WINDOWS_GUI: d << " GUI)"; break; + case IMAGE_SUBSYSTEM_WINDOWS_CUI: d << " CUI)"; break; + default: d << " subsystem " << hdr->Subsystem << ')'; break; + } + + d.space() << Qt::hex << hdr->SizeOfHeaders << "header bytes," + << Qt::dec << hdr->NumberOfRvaAndSizes << "RVA entries"; + }; + + if (h.h->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) + optDebug(reinterpret_cast<const IMAGE_OPTIONAL_HEADER64 *>(&h.h->OptionalHeader)); + else + optDebug(reinterpret_cast<const IMAGE_OPTIONAL_HEADER32 *>(&h.h->OptionalHeader)); + return d; +} + +struct SectionDebug { const IMAGE_SECTION_HEADER *s; }; +Q_DECL_UNUSED static QDebug &operator<<(QDebug &d, SectionDebug s) +{ + QDebugStateSaver saver(d); + d << Qt::hex << Qt::showbase; + d << "contains"; + if (s.s->Characteristics & IMAGE_SCN_CNT_CODE) + d << "CODE"; + if (s.s->Characteristics & IMAGE_SCN_CNT_INITIALIZED_DATA) + d << "DATA"; + if (s.s->Characteristics & IMAGE_SCN_CNT_UNINITIALIZED_DATA) + d << "BSS"; + + d << "flags"; + d.nospace(); + if (s.s->Characteristics & IMAGE_SCN_MEM_READ) + d << 'R'; + if (s.s->Characteristics & IMAGE_SCN_MEM_WRITE) + d << 'W'; + if (s.s->Characteristics & IMAGE_SCN_MEM_EXECUTE) + d << 'X'; + if (s.s->Characteristics & IMAGE_SCN_MEM_SHARED) + d << 'S'; + if (s.s->Characteristics & IMAGE_SCN_MEM_DISCARDABLE) + d << 'D'; + + d.space() << "offset" << s.s->PointerToRawData << "size" << s.s->SizeOfRawData; + return d; +} +} // unnamed namespace + +QT_WARNING_POP + +const IMAGE_NT_HEADERS *checkNtHeaders(QByteArrayView data, const ErrorMaker &error) +{ + if (size_t(data.size()) < qMax(sizeof(IMAGE_DOS_HEADER), sizeof(IMAGE_NT_HEADERS))) { + peDebug << "file too small:" << size_t(data.size()); + return error.toosmall(), nullptr; + } + + // check if there's a DOS image header (almost everything does) + size_t off = 0; + auto dosHeader = reinterpret_cast<const IMAGE_DOS_HEADER *>(data.data()); + if (dosHeader->e_magic == IMAGE_DOS_SIGNATURE) { + off = dosHeader->e_lfanew; + // peDebug << "DOS file header redirects to offset" << Qt::hex << Qt::showbase << off; + if (size_t end; qAddOverflow<sizeof(IMAGE_NT_HEADERS)>(off, &end) + || end > size_t(data.size())) { + peDebug << "file too small:" << size_t(data.size()); + return error.toosmall(), nullptr; + } + } + + // now check the NT headers + auto ntHeader = reinterpret_cast<const IMAGE_NT_HEADERS *>(data.data() + off); + peDebug << HeaderDebug{ntHeader}; + if (ntHeader->Signature != IMAGE_NT_SIGNATURE) // "PE\0\0" + return error(QLibrary::tr("invalid signature")), nullptr; + if (ntHeader->FileHeader.Machine != ExpectedMachine) + return error(QLibrary::tr("file is for a different processor")), nullptr; + if (ntHeader->FileHeader.NumberOfSections == 0) + return error(QLibrary::tr("file has no sections")), nullptr; + + WORD requiredCharacteristics = + IMAGE_FILE_EXECUTABLE_IMAGE | IMAGE_FILE_DLL; + if ((ntHeader->FileHeader.Characteristics & requiredCharacteristics) != requiredCharacteristics) + return error(QLibrary::tr("wrong characteristics")), nullptr; + + // the optional header is not optional + if (ntHeader->OptionalHeader.Magic != ExpectedOptionalHeaderSignature) + return error(QLibrary::tr("file is for a different word size")), nullptr; + if (ntHeader->OptionalHeader.SizeOfCode == 0) + return error.notplugin(QLibrary::tr("file has no code")), nullptr; + + return ntHeader; +} + +static const IMAGE_SECTION_HEADER * +findSectionTable(QByteArrayView data, const IMAGE_NT_HEADERS *ntHeader, const ErrorMaker &error) +{ + // macro IMAGE_FIRST_SECTION can't overflow due to limited range + // of type, but adding to the offset from the DOS header could + // overflow on 32-bit + static_assert(sizeof(ntHeader->FileHeader.SizeOfOptionalHeader) < sizeof(size_t)); + static_assert(sizeof(ntHeader->FileHeader.NumberOfSections) < sizeof(size_t)); + + size_t off = offsetof(IMAGE_NT_HEADERS, OptionalHeader); + off += ntHeader->FileHeader.SizeOfOptionalHeader; + if (qAddOverflow<size_t>(off, reinterpret_cast<const char *>(ntHeader) - data.data(), &off)) + return error.toosmall(), nullptr; + + size_t end = ntHeader->FileHeader.NumberOfSections * sizeof(IMAGE_SECTION_HEADER); + + // validate that the file is big enough for all sections we're + // supposed to have + if (qAddOverflow(end, off, &end) || end > size_t(data.size())) + return error.toosmall(), nullptr; + + peDebug << "contains" << ntHeader->FileHeader.NumberOfSections << "sections at offset" << off; + return reinterpret_cast<const IMAGE_SECTION_HEADER *>(data.data() + off); +} + +static std::optional<QByteArrayView> +findStringTable(QByteArrayView data, const IMAGE_NT_HEADERS *ntHeader, const ErrorMaker &error) +{ + // first, find the symbol table + size_t off = ntHeader->FileHeader.PointerToSymbolTable; + if (off == 0) + return QByteArrayView(); + + // skip the symbol table to find the string table after it + constexpr size_t SymbolEntrySize = 18; + size_t size = ntHeader->FileHeader.NumberOfSymbols; + if (qMulOverflow<SymbolEntrySize>(size, &size) + || qAddOverflow(off, size, &off) + || qAddOverflow(off, sizeof(DWORD), &off) + || off > size_t(data.size())) + return error.toosmall(), std::nullopt; + + off -= sizeof(DWORD); + + // we've found the string table, ensure it's big enough + size = qFromUnaligned<DWORD>(data.data() + off); + if (size_t end; qAddOverflow(off, size, &end) || end > size_t(data.size())) + return error.toosmall(), std::nullopt; + + // the conversion to signed is fine because we checked above it wasn't + // bigger than data.size() + return data.sliced(off, size); +} + +static QLatin1StringView findSectionName(const IMAGE_SECTION_HEADER *section, QByteArrayView stringTable) +{ + auto ptr = reinterpret_cast<const char *>(section->Name); + qsizetype n = qstrnlen(ptr, sizeof(section->Name)); + if (ptr[0] == '/' && !stringTable.isEmpty()) { + // long section name + // Microsoft's link.exe does not use these and will truncate the + // section name to fit section->Name. GNU binutils' ld does use long + // section names on executable image files by default (can be disabled + // using --disable-long-section-names). LLVM's lld does generate a + // string table in MinGW mode, but does not use it for our section. + + static_assert(sizeof(section->Name) - 1 < std::numeric_limits<uint>::digits10); + bool ok; + qsizetype offset = QByteArrayView(ptr + 1, n - 1).toUInt(&ok); + if (!ok || offset >= stringTable.size()) + return {}; + + ptr = stringTable.data() + offset; + n = qstrnlen(ptr, stringTable.size() - offset); + } + + return {ptr, n}; +} + +QLibraryScanResult QCoffPeParser::parse(QByteArrayView data, QString *errMsg) +{ + ErrorMaker error(errMsg); + auto ntHeaders = checkNtHeaders(data, error); + if (!ntHeaders) + return {}; + + auto section = findSectionTable(data, ntHeaders, error); + if (!ntHeaders) + return {}; + + QByteArrayView stringTable; + if (auto optional = findStringTable(data, ntHeaders, error)) + stringTable = *optional; + else + return {}; + + // scan the sections now + const auto sectionTableEnd = section + ntHeaders->FileHeader.NumberOfSections; + for ( ; section < sectionTableEnd; ++section) { + QLatin1StringView sectionName = findSectionName(section, stringTable); + peDebug << "section" << sectionName << SectionDebug{section}; + if (IncludeValidityChecks && sectionName.isEmpty()) + return error(QLibrary::tr("a section name is empty or extends past the end of the file")); + + size_t offset = section->PointerToRawData; + if (size_t end; qAddOverflow<size_t>(offset, section->SizeOfRawData, &end) + || end > size_t(data.size())) + return error(QLibrary::tr("section contents extend past the end of the file")); + + DWORD type = section->Characteristics + & (IMAGE_SCN_CNT_CODE | IMAGE_SCN_CNT_INITIALIZED_DATA + | IMAGE_SCN_CNT_UNINITIALIZED_DATA); + if (type != IMAGE_SCN_CNT_INITIALIZED_DATA) + continue; + + // if we do have a string table, the name may be complete + if (sectionName != truncatedSectionName && sectionName != metadataSectionName()) + continue; + peDebug << "found .qtmetadata section"; + + size_t size = qMin(section->SizeOfRawData, section->Misc.VirtualSize); + if (size < sizeof(QPluginMetaData::MagicHeader)) + return error(QLibrary::tr(".qtmetadata section is too small")); + if (IncludeValidityChecks) { + QByteArrayView expectedMagic = QByteArrayView::fromArray(QPluginMetaData::MagicString); + QByteArrayView actualMagic = data.sliced(offset, expectedMagic.size()); + if (expectedMagic != actualMagic) + return error(QLibrary::tr(".qtmetadata section has incorrect magic")); + + if (section->Characteristics & IMAGE_SCN_MEM_WRITE) + return error(QLibrary::tr(".qtmetadata section is writable")); + if (section->Characteristics & IMAGE_SCN_MEM_EXECUTE) + return error(QLibrary::tr(".qtmetadata section is executable")); + } + + return { qsizetype(offset + sizeof(QPluginMetaData::MagicString)), + qsizetype(size - sizeof(QPluginMetaData::MagicString)) }; + } + + return error.notfound(); +} + +QT_END_NAMESPACE diff --git a/src/corelib/plugin/qcoffpeparser_p.h b/src/corelib/plugin/qcoffpeparser_p.h new file mode 100644 index 0000000000..19c650fee0 --- /dev/null +++ b/src/corelib/plugin/qcoffpeparser_p.h @@ -0,0 +1,34 @@ +// 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 + +// no, this is not a misspelling of "coffeeparser" +#ifndef QCOFFPEPARSER_H +#define QCOFFPEPARSER_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qlibrary_p.h" + +#if defined(Q_OS_WIN) + +QT_BEGIN_NAMESPACE + +struct QCoffPeParser +{ + static QLibraryScanResult parse(QByteArrayView data, QString *errMsg); +}; + +QT_END_NAMESPACE + +#endif // defined(Q_OF_ELF) && defined(Q_CC_GNU) + +#endif // QCOFFPEPARSER_H diff --git a/src/corelib/plugin/qelfparser_p.cpp b/src/corelib/plugin/qelfparser_p.cpp index bbcfe2f865..7f6271cde4 100644 --- a/src/corelib/plugin/qelfparser_p.cpp +++ b/src/corelib/plugin/qelfparser_p.cpp @@ -1,239 +1,789 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** 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) 2017 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 "qelfparser_p.h" -#if defined (Q_OF_ELF) && defined(Q_CC_GNU) +#ifdef Q_OF_ELF #include "qlibrary_p.h" -#include <qdebug.h> + +#include <qloggingcategory.h> +#include <qnumeric.h> +#include <qsysinfo.h> + +#if __has_include(<elf.h>) +# include <elf.h> +#elif __has_include(<sys/elf.h>) +# include <sys/elf.h> +#else +# error "Need ELF header to parse plugins." +#endif QT_BEGIN_NAMESPACE -// #define QELFPARSER_DEBUG 1 - -const char *QElfParser::parseSectionHeader(const char *data, ElfSectionHeader *sh) -{ - sh->name = qFromUnaligned<qelfword_t>(data); - data += sizeof(qelfword_t); // sh_name - sh->type = qFromUnaligned<qelfword_t>(data); - data += sizeof(qelfword_t) // sh_type - + sizeof(qelfaddr_t) // sh_flags - + sizeof(qelfaddr_t); // sh_addr - sh->offset = qFromUnaligned<qelfoff_t>(data); - data += sizeof(qelfoff_t); // sh_offset - sh->size = qFromUnaligned<qelfoff_t>(data); - data += sizeof(qelfoff_t); // sh_size - return data; -} +using namespace Qt::StringLiterals; -auto QElfParser::parse(const char *dataStart, ulong fdlen, const QString &library, - QLibraryPrivate *lib, qsizetype *pos, qsizetype *sectionlen) -> ScanResult -{ +// ### Qt7: propagate the constant and eliminate dead code +static constexpr bool ElfNotesAreMandatory = QT_VERSION >= QT_VERSION_CHECK(7,0,0); + +// Whether we include some extra validity checks +// (checks to ensure we don't read out-of-bounds are always included) +static constexpr bool IncludeValidityChecks = true; + +#ifdef QT_BUILD_INTERNAL +# define QELFPARSER_DEBUG +#endif #if defined(QELFPARSER_DEBUG) - qDebug() << "QElfParser::parse " << library; +static Q_LOGGING_CATEGORY(lcElfParser, "qt.core.plugin.elfparser") +# define qEDebug qCDebug(lcElfParser) << reinterpret_cast<const char16_t *>(error.errMsg->constData()) << ':' +#else +# define qEDebug if (false) {} else QNoDebug() #endif - if (fdlen < 64) { - if (lib) - lib->errorString = QLibrary::tr("'%1' is not an ELF object (%2)").arg(library, QLibrary::tr("file too small")); - return NotElf; - } - const char *data = dataStart; - if (qstrncmp(data, "\177ELF", 4) != 0) { - if (lib) - lib->errorString = QLibrary::tr("'%1' is not an ELF object").arg(library); - return NotElf; - } - // 32 or 64 bit - if (data[4] != 1 && data[4] != 2) { - if (lib) - lib->errorString = QLibrary::tr("'%1' is an invalid ELF object (%2)").arg(library, QLibrary::tr("odd cpu architecture")); - return Corrupt; - } - - /* If you remove this check, to read ELF objects of a different arch, please make sure you modify the typedefs - to match the _plugin_ architecture. - */ - constexpr int ExpectedClass = (sizeof(void *) == 4) ? 1 : 2; - if (data[4] != ExpectedClass) { - if (lib) - lib->errorString = QLibrary::tr("'%1' is an invalid ELF object (%2)").arg(library, QLibrary::tr("wrong cpu architecture")); - return Corrupt; - } - - // endian - constexpr int ExpectedEndianness = (Q_BYTE_ORDER == Q_LITTLE_ENDIAN) ? 1 : 2; - if (data[5] != ExpectedEndianness) { - if (lib) - lib->errorString = QLibrary::tr("'%1' is an invalid ELF object (%2)").arg(library, QLibrary::tr("odd endianness")); - return Corrupt; - } - - data += 16 // e_ident - + sizeof(qelfhalf_t) // e_type - + sizeof(qelfhalf_t) // e_machine - + sizeof(qelfword_t) // e_version - + sizeof(qelfaddr_t) // e_entry - + sizeof(qelfoff_t); // e_phoff - - qelfoff_t e_shoff = qFromUnaligned<qelfoff_t> (data); - data += sizeof(qelfoff_t) // e_shoff - + sizeof(qelfword_t); // e_flags - - qelfhalf_t e_shsize = qFromUnaligned<qelfhalf_t> (data); - - if (e_shsize > fdlen) { - if (lib) - lib->errorString = QLibrary::tr("'%1' is an invalid ELF object (%2)").arg(library, QLibrary::tr("unexpected e_shsize")); - return Corrupt; - } - - data += sizeof(qelfhalf_t) // e_ehsize - + sizeof(qelfhalf_t) // e_phentsize - + sizeof(qelfhalf_t); // e_phnum - - qelfhalf_t e_shentsize = qFromUnaligned<qelfhalf_t> (data); - - if (e_shentsize % 4) { - if (lib) - lib->errorString = QLibrary::tr("'%1' is an invalid ELF object (%2)").arg(library, QLibrary::tr("unexpected e_shentsize")); - return Corrupt; - } - data += sizeof(qelfhalf_t); // e_shentsize - qelfhalf_t e_shnum = qFromUnaligned<qelfhalf_t> (data); - data += sizeof(qelfhalf_t); // e_shnum - qelfhalf_t e_shtrndx = qFromUnaligned<qelfhalf_t> (data); - data += sizeof(qelfhalf_t); // e_shtrndx - - if ((quint32)(e_shnum * e_shentsize) > fdlen) { - if (lib) { - const QString message = - QLibrary::tr("announced %n section(s), each %1 byte(s), exceed file size", - nullptr, int(e_shnum)).arg(e_shentsize); - lib->errorString = QLibrary::tr("'%1' is an invalid ELF object (%2)").arg(library, message); - } - return Corrupt; - } +#ifndef PT_GNU_EH_FRAME +# define PT_GNU_EH_FRAME 0x6474e550 +#endif +#ifndef PT_GNU_STACK +# define PT_GNU_STACK 0x6474e551 +#endif +#ifndef PT_GNU_RELRO +# define PT_GNU_RELRO 0x6474e552 +#endif +#ifndef PT_GNU_PROPERTY +# define PT_GNU_PROPERTY 0x6474e553 +#endif -#if defined(QELFPARSER_DEBUG) - qDebug() << e_shnum << "sections starting at " << ("0x" + QByteArray::number(e_shoff, 16)).data() << "each" << e_shentsize << "bytes"; +#ifndef PN_XNUM +# define PN_XNUM 0xffff #endif - ElfSectionHeader strtab; - qulonglong soff = e_shoff + qelfword_t(e_shentsize) * qelfword_t(e_shtrndx); +QT_WARNING_PUSH +QT_WARNING_DISABLE_CLANG("-Wunused-const-variable") + +namespace { +template <QSysInfo::Endian Order> struct ElfEndianTraits +{ + static constexpr unsigned char DataOrder = ELFDATA2LSB; + template <typename T> static T fromEndian(T value) { return qFromLittleEndian(value); } +}; +template <> struct ElfEndianTraits<QSysInfo::BigEndian> +{ + static constexpr unsigned char DataOrder = ELFDATA2MSB; + template <typename T> static T fromEndian(T value) { return qFromBigEndian(value); } +}; + +template <typename EquivalentPointerType> struct ElfTypeTraits +{ + static constexpr unsigned char Class = ELFCLASS64; + + // integer types + using Half = Elf64_Half; + using Word = Elf64_Word; + using Addr = Elf64_Addr; + using Off = Elf64_Off; - if ((soff + e_shentsize) > fdlen || soff % 4 || soff == 0) { - if (lib) - lib->errorString = QLibrary::tr("'%1' is an invalid ELF object (%2)") - .arg(library, QLibrary::tr("shstrtab section header seems to be at %1") - .arg(QString::number(soff, 16))); - return Corrupt; + // structure types + using Ehdr = Elf64_Ehdr; + using Shdr = Elf64_Shdr; + using Phdr = Elf64_Phdr; + using Nhdr = Elf64_Nhdr; +}; +template <> struct ElfTypeTraits<quint32> +{ + static constexpr unsigned char Class = ELFCLASS32; + + // integer types + using Half = Elf32_Half; + using Word = Elf32_Word; + using Addr = Elf32_Addr; + using Off = Elf32_Off; + + // structure types + using Ehdr = Elf32_Ehdr; + using Shdr = Elf32_Shdr; + using Phdr = Elf32_Phdr; + using Nhdr = Elf32_Nhdr; +}; + +struct ElfMachineCheck +{ + static const Elf32_Half ExpectedMachine = +#if 0 + // nothing +#elif defined(Q_PROCESSOR_ALPHA) + EM_ALPHA +#elif defined(Q_PROCESSOR_ARM_32) + EM_ARM +#elif defined(Q_PROCESSOR_ARM_64) + EM_AARCH64 +#elif defined(Q_PROCESSOR_BLACKFIN) + EM_BLACKFIN +#elif defined(Q_PROCESSOR_HPPA) + EM_PARISC +#elif defined(Q_PROCESSOR_IA64) + EM_IA_64 +#elif defined(Q_PROCESSOR_LOONGARCH) + EM_LOONGARCH +#elif defined(Q_PROCESSOR_M68K) + EM_68K +#elif defined(Q_PROCESSOR_MIPS) + EM_MIPS +#elif defined(Q_PROCESSOR_POWER_32) + EM_PPC +#elif defined(Q_PROCESSOR_POWER_64) + EM_PPC64 +#elif defined(Q_PROCESSOR_RISCV) + EM_RISCV +#elif defined(Q_PROCESSOR_S390) + EM_S390 +#elif defined(Q_PROCESSOR_SH) + EM_SH +#elif defined(Q_PROCESSOR_SPARC_V9) + EM_SPARCV9 +#elif defined(Q_PROCESSOR_SPARC_64) + EM_SPARCV9 +#elif defined(Q_PROCESSOR_SPARC) + EM_SPARC +#elif defined(Q_PROCESSOR_WASM) +#elif defined(Q_PROCESSOR_X86_32) + EM_386 +#elif defined(Q_PROCESSOR_X86_64) + EM_X86_64 +#else +# error "Unknown Q_PROCESSOR_xxx macro, please update." + EM_NONE +#endif + ; +}; + +struct ElfHeaderCommonCheck +{ + static_assert(std::is_same_v<decltype(Elf32_Ehdr::e_ident), decltype(Elf64_Ehdr::e_ident)>, + "e_ident field is not the same in both Elf32_Ehdr and Elf64_Ehdr"); + + // bytes 0-3 + static bool checkElfMagic(const uchar *ident) + { + return memcmp(ident, ELFMAG, SELFMAG) == 0; + } + + // byte 6 + static bool checkElfVersion(const uchar *ident) + { + uchar elfversion = ident[EI_VERSION]; + return elfversion == EV_CURRENT; } - parseSectionHeader(dataStart + soff, &strtab); - m_stringTableFileOffset = strtab.offset; + struct CommonHeader { + Elf32_Half type; + Elf32_Half machine; + Elf32_Word version; + }; +}; + +template <typename EquivalentPointerType = quintptr, QSysInfo::Endian Order = QSysInfo::ByteOrder> +struct ElfHeaderCheck : public ElfHeaderCommonCheck +{ + using TypeTraits = ElfTypeTraits<EquivalentPointerType>; + using EndianTraits = ElfEndianTraits<Order>; + using Ehdr = typename TypeTraits::Ehdr; - if ((quint32)(strtab.offset + strtab.size) > fdlen || strtab.offset == 0) { - if (lib) - lib->errorString = QLibrary::tr("'%1' is an invalid ELF object (%2)") - .arg(library, QLibrary::tr("string table seems to be at %1") - .arg(QString::number(strtab.offset, 16))); - return Corrupt; + // byte 4 + static bool checkClass(const uchar *ident) + { + uchar klass = ident[EI_CLASS]; + return klass == TypeTraits::Class; } -#if defined(QELFPARSER_DEBUG) - qDebug(".shstrtab at 0x%s", QByteArray::number(m_stringTableFileOffset, 16).data()); + // byte 5 + static bool checkDataOrder(const uchar *ident) + { + uchar data = ident[EI_DATA]; + return data == EndianTraits::DataOrder; + } + + // byte 7 + static bool checkOsAbi(const uchar *ident) + { + uchar osabi = ident[EI_OSABI]; + // we don't check + Q_UNUSED(osabi); + return true; + } + + // byte 8 + static bool checkAbiVersion(const uchar *ident) + { + uchar abiversion = ident[EI_ABIVERSION]; + // we don't check (and I don't know anyone who uses this) + Q_UNUSED(abiversion); + return true; + } + + // bytes 9-16 + static bool checkPadding(const uchar *ident) + { + // why would we check this? + Q_UNUSED(ident); + return true; + } + + static bool checkIdent(const Ehdr &header) + { + return checkElfMagic(header.e_ident) + && checkClass(header.e_ident) + && checkDataOrder(header.e_ident) + && checkElfVersion(header.e_ident) + && checkOsAbi(header.e_ident) + && checkAbiVersion(header.e_ident) + && checkPadding(header.e_ident); + } + + static bool checkType(const Ehdr &header) + { + return header.e_type == ET_DYN; + } + + static bool checkMachine(const Ehdr &header) + { + return header.e_machine == ElfMachineCheck::ExpectedMachine; + } + + static bool checkFileVersion(const Ehdr &header) + { + return header.e_version == EV_CURRENT; + } + + static bool checkHeader(const Ehdr &header) + { + if (!checkIdent(header)) + return false; + if (!IncludeValidityChecks) + return true; + return checkType(header) + && checkMachine(header) + && checkFileVersion(header); + } + + Q_DECL_COLD_FUNCTION static QString explainCheckFailure(const Ehdr &header) + { + if (!checkElfMagic(header.e_ident)) + return QLibrary::tr("invalid signature"); + if (!checkClass(header.e_ident)) + return QLibrary::tr("file is for a different word size"); + if (!checkDataOrder(header.e_ident)) + return QLibrary::tr("file is for the wrong endianness"); + if (!checkElfVersion(header.e_ident) || !checkFileVersion(header)) + return QLibrary::tr("file has an unknown ELF version"); + if (!checkOsAbi(header.e_ident) || !checkAbiVersion(header.e_ident)) + return QLibrary::tr("file has an unexpected ABI"); + if (!checkType(header)) + return QLibrary::tr("file is not a shared object"); + if (!checkMachine(header)) + return QLibrary::tr("file is for a different processor"); + return QString(); + } + + static CommonHeader extractCommonHeader(const uchar *data) + { + auto header = reinterpret_cast<const Ehdr *>(data); + CommonHeader r; + r.type = EndianTraits::fromEndian(header->e_type); + r.machine = EndianTraits::fromEndian(header->e_machine); + r.version = EndianTraits::fromEndian(header->e_version); + return r; + } +}; + +struct ElfHeaderDebug { const uchar *e_ident; }; +Q_DECL_UNUSED Q_DECL_COLD_FUNCTION static QDebug &operator<<(QDebug &d, ElfHeaderDebug h) +{ + const uchar *e_ident = h.e_ident; + if (!ElfHeaderCommonCheck::checkElfMagic(e_ident)) { + d << "Not an ELF file (invalid signature)"; + return d; + } + + QDebugStateSaver saver(d); + d.nospace(); + quint8 elfclass = e_ident[EI_CLASS]; + switch (elfclass) { + case ELFCLASSNONE: + default: + d << "Invalid ELF file (class " << e_ident[EI_CLASS] << "), "; + break; + case ELFCLASS32: + d << "ELF 32-bit "; + break; + case ELFCLASS64: + d << "ELF 64-bit "; + break; + } + + quint8 dataorder = e_ident[EI_DATA]; + switch (dataorder) { + case ELFDATANONE: + default: + d << "invalid endianness (" << e_ident[EI_DATA] << ')'; + break; + case ELFDATA2LSB: + d << "LSB"; + break; + case ELFDATA2MSB: + d << "MSB"; + break; + } + + switch (e_ident[EI_OSABI]) { + case ELFOSABI_SYSV: d << " (SYSV"; break; + case ELFOSABI_HPUX: d << " (HP-UX"; break; + case ELFOSABI_NETBSD: d << " (NetBSD"; break; + case ELFOSABI_LINUX: d << " (GNU/Linux"; break; + case ELFOSABI_SOLARIS: d << " (Solaris"; break; + case ELFOSABI_AIX: d << " (AIX"; break; + case ELFOSABI_IRIX: d << " (IRIX"; break; + case ELFOSABI_FREEBSD: d << " (FreeBSD"; break; + case ELFOSABI_OPENBSD: d << " (OpenBSD"; break; + default: d << " (OS ABI " << e_ident[EI_VERSION]; break; + } + + if (e_ident[EI_ABIVERSION]) + d << " v" << e_ident[EI_ABIVERSION]; + d << ')'; + + if (e_ident[EI_VERSION] != 1) { + d << ", file version " << e_ident[EI_VERSION]; + return d; + } + + ElfHeaderCommonCheck::CommonHeader r; + if (elfclass == ELFCLASS64 && dataorder == ELFDATA2LSB) + r = ElfHeaderCheck<quint64, QSysInfo::LittleEndian>::extractCommonHeader(e_ident); + else if (elfclass == ELFCLASS32 && dataorder == ELFDATA2LSB) + r = ElfHeaderCheck<quint32, QSysInfo::LittleEndian>::extractCommonHeader(e_ident); + else if (elfclass == ELFCLASS64 && dataorder == ELFDATA2MSB) + r = ElfHeaderCheck<quint64, QSysInfo::BigEndian>::extractCommonHeader(e_ident); + else if (elfclass == ELFCLASS32 && dataorder == ELFDATA2MSB) + r = ElfHeaderCheck<quint32, QSysInfo::BigEndian>::extractCommonHeader(e_ident); + else + return d; + + d << ", version " << r.version; + + switch (r.type) { + case ET_NONE: d << ", no type"; break; + case ET_REL: d << ", relocatable"; break; + case ET_EXEC: d << ", executable"; break; + case ET_DYN: d << ", shared library or PIC executable"; break; + case ET_CORE: d << ", core dump"; break; + default: d << ", unknown type " << r.type; break; + } + + switch (r.machine) { + // list definitely not exhaustive! + case EM_NONE: d << ", no machine"; break; + case EM_ALPHA: d << ", Alpha"; break; + case EM_68K: d << ", MC68000"; break; + case EM_ARM: d << ", ARM"; break; + case EM_AARCH64: d << ", AArch64"; break; +#ifdef EM_BLACKFIN + case EM_BLACKFIN: d << ", Blackfin"; break; +#endif + case EM_IA_64: d << ", IA-64"; break; +#ifdef EM_LOONGARCH + case EM_LOONGARCH: d << ", LoongArch"; break; +#endif + case EM_MIPS: d << ", MIPS"; break; + case EM_PARISC: d << ", HPPA"; break; + case EM_PPC: d << ", PowerPC"; break; + case EM_PPC64: d << ", PowerPC 64-bit"; break; +#ifdef EM_RISCV + case EM_RISCV: d << ", RISC-V"; break; #endif +#ifdef EM_S390 + case EM_S390: d << ", S/390"; break; +#endif + case EM_SH: d << ", SuperH"; break; + case EM_SPARC: d << ", SPARC"; break; + case EM_SPARCV9: d << ", SPARCv9"; break; + case EM_386: d << ", i386"; break; + case EM_X86_64: d << ", x86-64"; break; + default: d << ", other machine type " << r.machine; break; + } - const char *s = dataStart + e_shoff; - for (int i = 0; i < e_shnum; ++i) { - ElfSectionHeader sh; - parseSectionHeader(s, &sh); - if (sh.name == 0) { - s += e_shentsize; - continue; + return d; +} + +struct ElfSectionDebug { const ElfHeaderCheck<>::TypeTraits::Shdr *shdr; }; +Q_DECL_UNUSED static QDebug &operator<<(QDebug &d, ElfSectionDebug s) +{ + // not exhaustive, just a few common things + QDebugStateSaver saver(d); + d << Qt::hex << Qt::showbase; + d << "type"; + switch (s.shdr->sh_type) { + case SHT_NULL: d << "NULL"; break; + case SHT_PROGBITS: d << "PROGBITS"; break; + case SHT_SYMTAB: d << "SYMTAB"; break; + case SHT_STRTAB: d << "STRTAB"; break; + case SHT_RELA: d << "RELA"; break; + case SHT_HASH: d << "HASH"; break; + case SHT_DYNAMIC: d << "DYNAMIC"; break; + case SHT_NOTE: d << "NOTE"; break; + case SHT_NOBITS: d << "NOBITS"; break; + case SHT_DYNSYM: d << "DYNSYM"; break; + case SHT_INIT_ARRAY: d << "INIT_ARRAY"; break; + case SHT_FINI_ARRAY: d << "FINI_ARRAY"; break; + default: d << s.shdr->sh_type; + } + + d << "flags"; + d.nospace(); + if (s.shdr->sh_flags & SHF_WRITE) + d << 'W'; + if (s.shdr->sh_flags & SHF_ALLOC) + d << 'A'; + if (s.shdr->sh_flags & SHF_EXECINSTR) + d << 'X'; + if (s.shdr->sh_flags & SHF_STRINGS) + d << 'S'; + if (s.shdr->sh_flags & SHF_TLS) + d << 'T'; + + d.space() << "offset" << s.shdr->sh_offset << "size" << s.shdr->sh_size; + return d; +} + +struct ElfProgramDebug { const ElfHeaderCheck<>::TypeTraits::Phdr *phdr; }; +Q_DECL_UNUSED static QDebug &operator<<(QDebug &d, ElfProgramDebug p) +{ + QDebugStateSaver saved(d); + d << Qt::hex << Qt::showbase << "program"; + switch (p.phdr->p_type) { + case PT_NULL: d << "NULL"; break; + case PT_LOAD: d << "LOAD"; break; + case PT_DYNAMIC: d << "DYNAMIC"; break; + case PT_INTERP: d << "INTERP"; break; + case PT_NOTE: d << "NOTE"; break; + case PT_PHDR: d << "PHDR"; break; + case PT_TLS: d << "TLS"; break; + case PT_GNU_EH_FRAME: d << "GNU_EH_FRAME"; break; + case PT_GNU_STACK: d << "GNU_STACK"; break; + case PT_GNU_RELRO: d << "GNU_RELRO"; break; + case PT_GNU_PROPERTY: d << "GNU_PROPERTY"; break; + default: d << "type" << p.phdr->p_type; break; + } + + d << "offset" << p.phdr->p_offset + << "virtaddr" << p.phdr->p_vaddr + << "filesz" << p.phdr->p_filesz + << "memsz" << p.phdr->p_memsz + << "align" << p.phdr->p_align + << "flags"; + + d.nospace(); + if (p.phdr->p_flags & PF_R) + d << 'R'; + if (p.phdr->p_flags & PF_W) + d << 'W'; + if (p.phdr->p_flags & PF_X) + d << 'X'; + + return d; +} + +struct ErrorMaker +{ + QString *errMsg; + constexpr ErrorMaker(QString *errMsg) : errMsg(errMsg) {} + + + Q_DECL_COLD_FUNCTION QLibraryScanResult operator()(QString &&text) const + { + *errMsg = QLibrary::tr("'%1' is not a valid ELF object (%2)").arg(*errMsg, std::move(text)); + return {}; + } + + Q_DECL_COLD_FUNCTION QLibraryScanResult notplugin(QString &&explanation) const + { + *errMsg = QLibrary::tr("'%1' is not a Qt plugin (%2)").arg(*errMsg, explanation); + return {}; + } + + Q_DECL_COLD_FUNCTION QLibraryScanResult notfound() const + { + return notplugin(QLibrary::tr("metadata not found")); + } +}; +} // unnamed namespace + +QT_WARNING_POP + +using T = ElfHeaderCheck<>::TypeTraits; + +template <typename F> +static bool scanProgramHeaders(QByteArrayView data, const ErrorMaker &error, F f) +{ + auto header = reinterpret_cast<const T::Ehdr *>(data.data()); + Q_UNUSED(error); + + auto phdr = reinterpret_cast<const T::Phdr *>(data.data() + header->e_phoff); + auto phdr_end = phdr + header->e_phnum; + for ( ; phdr != phdr_end; ++phdr) { + if (!f(phdr)) + return false; + } + return true; +} + +static bool preScanProgramHeaders(QByteArrayView data, const ErrorMaker &error) +{ + auto header = reinterpret_cast<const T::Ehdr *>(data.data()); + + // first, validate the extent of the full program header table + T::Word e_phnum = header->e_phnum; + if (e_phnum == PN_XNUM) + return error(QLibrary::tr("unimplemented: PN_XNUM program headers")), false; + T::Off offset = e_phnum * sizeof(T::Phdr); // can't overflow due to size of T::Half + if (qAddOverflow(offset, header->e_phoff, &offset) || offset > size_t(data.size())) + return error(QLibrary::tr("program header table extends past the end of the file")), false; + + // confirm validity + bool hasCode = false; + auto checker = [&](const T::Phdr *phdr) { + qEDebug << ElfProgramDebug{phdr}; + + if (T::Off end; qAddOverflow(phdr->p_offset, phdr->p_filesz, &end) + || end > size_t(data.size())) + return error(QLibrary::tr("a program header entry extends past the end of the file")), false; + + // this is not a validity check, it's to exclude debug symbol files + if (phdr->p_type == PT_LOAD && phdr->p_filesz != 0 && (phdr->p_flags & PF_X)) + hasCode = true; + + // this probably applies to all segments, but we'll only apply it to notes + if (phdr->p_type == PT_NOTE && qPopulationCount(phdr->p_align) == 1 + && phdr->p_offset & (phdr->p_align - 1)) { + return error(QLibrary::tr("a note segment start is not properly aligned " + "(offset 0x%1, alignment %2)") + .arg(phdr->p_offset, 6, 16, QChar(u'0')) + .arg(phdr->p_align)), false; } - const char *shnam = dataStart + m_stringTableFileOffset + sh.name; - - if (m_stringTableFileOffset + sh.name > fdlen) { - if (lib) - lib->errorString = QLibrary::tr("'%1' is an invalid ELF object (%2)") - .arg(library, QLibrary::tr("section name %1 of %2 behind end of file") - .arg(i).arg(e_shnum)); - return Corrupt; + + return true; + }; + if (!scanProgramHeaders(data, error, checker)) + return false; + if (!hasCode) + return error.notplugin(QLibrary::tr("file has no code")), false; + return true; +} + +static QLibraryScanResult scanProgramHeadersForNotes(QByteArrayView data, const ErrorMaker &error) +{ + // minimum metadata payload is 2 bytes + constexpr size_t MinPayloadSize = sizeof(QPluginMetaData::Header) + 2; + constexpr qptrdiff MinNoteSize = sizeof(QPluginMetaData::ElfNoteHeader) + 2; + constexpr size_t NoteNameSize = sizeof(QPluginMetaData::ElfNoteHeader::name); + constexpr size_t NoteAlignment = alignof(QPluginMetaData::ElfNoteHeader); + constexpr qptrdiff PayloadStartDelta = offsetof(QPluginMetaData::ElfNoteHeader, header); + static_assert(MinNoteSize > PayloadStartDelta); + static_assert((PayloadStartDelta & (NoteAlignment - 1)) == 0); + + QLibraryScanResult r = {}; + auto noteFinder = [&](const T::Phdr *phdr) { + if (phdr->p_type != PT_NOTE || phdr->p_align != NoteAlignment) + return true; + + // check for signed integer overflows, to avoid issues with the + // arithmetic below + if (qptrdiff(phdr->p_filesz) < 0) { + auto h = reinterpret_cast<const T::Ehdr *>(data.data()); + auto segments = reinterpret_cast<const T::Phdr *>(data.data() + h->e_phoff); + qEDebug << "segment" << (phdr - segments) << "contains a note with size" + << Qt::hex << Qt::showbase << phdr->p_filesz + << "which is larger than half the virtual memory space"; + return true; } -#if defined(QELFPARSER_DEBUG) - qDebug() << "++++" << i << shnam; -#endif + // iterate over the notes in this segment + T::Off offset = phdr->p_offset; + const T::Off end_offset = offset + phdr->p_filesz; + while (qptrdiff(end_offset - offset) >= MinNoteSize) { + auto nhdr = reinterpret_cast<const T::Nhdr *>(data.data() + offset); + T::Word n_namesz = nhdr->n_namesz; + T::Word n_descsz = nhdr->n_descsz; + T::Word n_type = nhdr->n_type; - if (qstrcmp(shnam, ".qtmetadata") == 0 ) { - if (!(sh.type & 0x1)) { - if (shnam[1] == 'r') { - if (lib) - lib->errorString = QLibrary::tr("'%1' is an invalid ELF object (%2)") - .arg(library, QLibrary::tr("empty .rodata. not a library.")); - return Corrupt; - } -#if defined(QELFPARSER_DEBUG) - qDebug()<<"section is not program data. skipped."; -#endif - s += e_shentsize; - continue; - } + // overflow check: calculate where the next note will be, if it exists + T::Off next_offset = offset; + next_offset += sizeof(T::Nhdr); // can't overflow (we checked above) + next_offset += NoteAlignment - 3; // offset is aligned, this can't overflow + if (qAddOverflow<T::Off>(next_offset, n_namesz, &next_offset)) + break; + next_offset &= -NoteAlignment; - if (sh.offset == 0 || (sh.offset + sh.size) > fdlen || sh.size < 1) { - if (lib) - lib->errorString = QLibrary::tr("'%1' is an invalid ELF object (%2)") - .arg(library, QLibrary::tr("missing section data. This is not a library.")); - return Corrupt; + next_offset += NoteAlignment - 3; // offset is aligned, this can't overflow + if (qAddOverflow<T::Off>(next_offset, n_descsz, &next_offset)) + break; + next_offset &= -NoteAlignment; + if (next_offset > end_offset) + break; + + if (n_namesz == NoteNameSize && n_descsz >= MinPayloadSize + && n_type == QPluginMetaData::ElfNoteHeader::NoteType + && memcmp(nhdr + 1, QPluginMetaData::ElfNoteHeader::NoteName, NoteNameSize) == 0) { + // yes, it's our note + r.pos = offset + PayloadStartDelta; + r.length = nhdr->n_descsz; + return false; } - *pos = sh.offset; - *sectionlen = sh.size; - return QtMetaDataSection; + offset = next_offset; } - s += e_shentsize; + return true; + }; + scanProgramHeaders(data, error, noteFinder); + + if (!r.length) + return r; + + qEDebug << "found Qt metadata in ELF note at" + << Qt::hex << Qt::showbase << r.pos << "size" << Qt::reset << r.length; + return r; +} + +static QLibraryScanResult scanSections(QByteArrayView data, const ErrorMaker &error) +{ + auto header = reinterpret_cast<const T::Ehdr *>(data.data()); + + // in order to find the .qtmetadata section, we need to: + // a) find the section table + // it's located at offset header->e_shoff + // validate it + T::Word e_shnum = header->e_shnum; + T::Off offset = e_shnum * sizeof(T::Shdr); // can't overflow due to size of T::Half + if (qAddOverflow(offset, header->e_shoff, &offset) || offset > size_t(data.size())) + return error(QLibrary::tr("section table extends past the end of the file")); + + // b) find the section entry for the section header string table (shstrab) + // it's a section whose entry is pointed by e_shstrndx + auto sections = reinterpret_cast<const T::Shdr *>(data.data() + header->e_shoff); + auto sections_end = sections + e_shnum; + auto shdr = sections + header->e_shstrndx; + + // validate the shstrtab + offset = shdr->sh_offset; + T::Off shstrtab_size = shdr->sh_size; + qEDebug << "shstrtab section is located at offset" << offset << "size" << shstrtab_size; + if (T::Off end; qAddOverflow<T::Off>(offset, shstrtab_size, &end) + || end > size_t(data.size())) + return error(QLibrary::tr("section header string table extends past the end of the file")); + + // c) iterate over the sections to find .qtmetadata + const char *shstrtab_start = data.data() + offset; + shdr = sections; + for (int section = 0; shdr != sections_end; ++section, ++shdr) { + QLatin1StringView name; + if (shdr->sh_name < shstrtab_size) { + const char *namestart = shstrtab_start + shdr->sh_name; + size_t len = qstrnlen(namestart, shstrtab_size - shdr->sh_name); + name = QLatin1StringView(namestart, len); + } + qEDebug << "section" << section << "name" << name << ElfSectionDebug{shdr}; + + // sanity check the section + if (name.isNull()) + return error(QLibrary::tr("a section name extends past the end of the file")); + + // sections aren't allowed to extend past the end of the file, unless + // they are NOBITS sections + if (shdr->sh_type == SHT_NOBITS) + continue; + if (T::Off end; qAddOverflow(shdr->sh_offset, shdr->sh_size, &end) + || end > size_t(data.size())) { + return error(QLibrary::tr("section contents extend past the end of the file")); + } + + if (name != ".qtmetadata"_L1) + continue; + qEDebug << "found .qtmetadata section"; + if (shdr->sh_size < sizeof(QPluginMetaData::MagicHeader)) + return error(QLibrary::tr(".qtmetadata section is too small")); + + if (IncludeValidityChecks) { + QByteArrayView expectedMagic = QByteArrayView::fromArray(QPluginMetaData::MagicString); + QByteArrayView actualMagic = data.sliced(shdr->sh_offset, expectedMagic.size()); + if (expectedMagic != actualMagic) + return error(QLibrary::tr(".qtmetadata section has incorrect magic")); + + if (shdr->sh_flags & SHF_WRITE) + return error(QLibrary::tr(".qtmetadata section is writable")); + if (shdr->sh_flags & SHF_EXECINSTR) + return error(QLibrary::tr(".qtmetadata section is executable")); + } + + return { qsizetype(shdr->sh_offset + sizeof(QPluginMetaData::MagicString)), + qsizetype(shdr->sh_size - sizeof(QPluginMetaData::MagicString)) }; + } + + // section .qtmetadata not found + return error.notfound(); +} + +QLibraryScanResult QElfParser::parse(QByteArrayView data, QString *errMsg) +{ + ErrorMaker error(errMsg); + if (size_t(data.size()) < sizeof(T::Ehdr)) { + qEDebug << "file too small:" << size_t(data.size()); + return error(QLibrary::tr("file too small")); + } + + qEDebug << ElfHeaderDebug{ reinterpret_cast<const uchar *>(data.data()) }; + + auto header = reinterpret_cast<const T::Ehdr *>(data.data()); + if (!ElfHeaderCheck<>::checkHeader(*header)) + return error(ElfHeaderCheck<>::explainCheckFailure(*header)); + + qEDebug << "contains" << header->e_phnum << "program headers of" + << header->e_phentsize << "bytes at offset" << header->e_phoff; + qEDebug << "contains" << header->e_shnum << "sections of" << header->e_shentsize + << "bytes at offset" << header->e_shoff + << "; section header string table (shstrtab) is entry" << header->e_shstrndx; + + // some sanity checks + if constexpr (IncludeValidityChecks) { + if (header->e_phentsize != sizeof(T::Phdr)) + return error(QLibrary::tr("unexpected program header entry size (%1)") + .arg(header->e_phentsize)); + } + + if (!preScanProgramHeaders(data, error)) + return {}; + + if (QLibraryScanResult r = scanProgramHeadersForNotes(data, error); r.length) + return r; + + if (!ElfNotesAreMandatory) { + if constexpr (IncludeValidityChecks) { + if (header->e_shentsize != sizeof(T::Shdr)) + return error(QLibrary::tr("unexpected section entry size (%1)") + .arg(header->e_shentsize)); + } + if (header->e_shoff == 0 || header->e_shnum == 0) { + // this is still a valid ELF file but we don't have a section table + qEDebug << "no section table present, not able to find Qt metadata"; + return error.notfound(); + } + + if (header->e_shnum && header->e_shstrndx >= header->e_shnum) + return error(QLibrary::tr("e_shstrndx greater than the number of sections e_shnum (%1 >= %2)") + .arg(header->e_shstrndx).arg(header->e_shnum)); + return scanSections(data, error); } - return NoQtSection; + return error.notfound(); } QT_END_NAMESPACE -#endif // defined(Q_OF_ELF) && defined(Q_CC_GNU) +#endif // Q_OF_ELF diff --git a/src/corelib/plugin/qelfparser_p.h b/src/corelib/plugin/qelfparser_p.h index bd967e53ae..61498a859a 100644 --- a/src/corelib/plugin/qelfparser_p.h +++ b/src/corelib/plugin/qelfparser_p.h @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** 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) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #ifndef QELFPARSER_P_H #define QELFPARSER_P_H @@ -52,44 +16,21 @@ // #include <qendian.h> -#include <private/qglobal_p.h> +#include "qlibrary_p.h" QT_REQUIRE_CONFIG(library); -#if defined(Q_OF_ELF) && defined(Q_CC_GNU) +#ifdef Q_OF_ELF QT_BEGIN_NAMESPACE -class QString; -class QLibraryPrivate; - -typedef quint16 qelfhalf_t; -typedef quint32 qelfword_t; -typedef quintptr qelfoff_t; -typedef quintptr qelfaddr_t; - -class QElfParser +struct QElfParser { -public: - enum ScanResult { QtMetaDataSection, NoQtSection, NotElf, Corrupt }; - enum { ElfLittleEndian = 0, ElfBigEndian = 1 }; - - struct ElfSectionHeader - { - qelfword_t name; - qelfword_t type; - qelfoff_t offset; - qelfoff_t size; - }; - - qelfoff_t m_stringTableFileOffset; - - const char *parseSectionHeader(const char* s, ElfSectionHeader *sh); - ScanResult parse(const char *m_s, ulong fdlen, const QString &library, QLibraryPrivate *lib, qsizetype *pos, qsizetype *sectionlen); + static QLibraryScanResult parse(QByteArrayView data, QString *errMsg); }; QT_END_NAMESPACE -#endif // defined(Q_OF_ELF) && defined(Q_CC_GNU) +#endif // Q_OF_ELF #endif // QELFPARSER_P_H diff --git a/src/corelib/plugin/qfactoryinterface.cpp b/src/corelib/plugin/qfactoryinterface.cpp index b503c245c5..1a48d311aa 100644 --- a/src/corelib/plugin/qfactoryinterface.cpp +++ b/src/corelib/plugin/qfactoryinterface.cpp @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** 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) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qfactoryinterface.h" diff --git a/src/corelib/plugin/qfactoryinterface.h b/src/corelib/plugin/qfactoryinterface.h index 86a1c8315f..098b7d4201 100644 --- a/src/corelib/plugin/qfactoryinterface.h +++ b/src/corelib/plugin/qfactoryinterface.h @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** 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) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #ifndef QFACTORYINTERFACE_H #define QFACTORYINTERFACE_H @@ -51,7 +15,7 @@ struct Q_CORE_EXPORT QFactoryInterface virtual QStringList keys() const = 0; }; -#ifndef Q_CLANG_QDOC +#ifndef Q_QDOC Q_DECLARE_INTERFACE(QFactoryInterface, "org.qt-project.Qt.QFactoryInterface") #endif diff --git a/src/corelib/plugin/qfactoryloader.cpp b/src/corelib/plugin/qfactoryloader.cpp index 7e4a9f06fd..e2d9a40cb4 100644 --- a/src/corelib/plugin/qfactoryloader.cpp +++ b/src/corelib/plugin/qfactoryloader.cpp @@ -1,120 +1,243 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ +// 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" #ifndef QT_NO_QOBJECT -#include "qfactoryinterface.h" -#include "qmap.h" -#include <qdir.h> -#include <qdebug.h> -#include "qmutex.h" -#include "qplugin.h" -#include "qplugin_p.h" -#include "qpluginloader.h" -#include "private/qobject_p.h" #include "private/qcoreapplication_p.h" +#include "private/qduplicatetracker_p.h" +#include "private/qloggingregistry_p.h" +#include "private/qobject_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 "qjsonvalue.h" #include "qjsonobject.h" -#include "qjsonarray.h" -#include "private/qduplicatetracker_p.h" +#include "qmutex.h" +#include "qplugin.h" +#include "qplugin_p.h" +#include "qpluginloader.h" + +#if QT_CONFIG(library) +# include "qlibrary_p.h" +#endif #include <qtcore_tracepoints_p.h> +#include <map> +#include <vector> + QT_BEGIN_NAMESPACE -static inline int metaDataSignatureLength() +using namespace Qt::StringLiterals; + +Q_TRACE_POINT(qtcore, QFactoryLoader_update, const QString &fileName); + +namespace { +struct IterationResult { - return sizeof("QTMETADATA ") - 1; -} + 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()); } + + static IterationResult::Result skip(QCborStreamReader &reader) + { + // skip this, whatever it is + reader.next(); + return IterationResult::ContinueSearch; + } -static QJsonDocument jsonFromCborMetaData(const char *raw, qsizetype size, QString *errMsg) + IterationResult::Result operator()(QtPluginMetaDataKeys key, QCborStreamReader &reader) + { + if (key != QtPluginMetaDataKeys::IID) + return skip(reader); + matchesIid = (reader.readAllString() == iid); + return IterationResult::FinishedSearch; + } + IterationResult::Result operator()(QUtf8StringView, QCborStreamReader &reader) + { + return skip(reader); + } +}; + +struct QFactoryLoaderMetaDataKeysExtractor : QFactoryLoaderIidSearch { - // extract the keys not stored in CBOR - int qt_metadataVersion = quint8(raw[0]); - int qt_version = qFromBigEndian<quint16>(raw + 1); - int qt_archRequirements = quint8(raw[3]); - if (Q_UNLIKELY(raw[-1] != '!' || qt_metadataVersion != 0)) { - *errMsg = QStringLiteral("Invalid metadata version"); - return QJsonDocument(); + QCborArray keys; + QFactoryLoaderMetaDataKeysExtractor(QLatin1StringView iid) + : QFactoryLoaderIidSearch(iid) + {} + + IterationResult::Result operator()(QtPluginMetaDataKeys key, QCborStreamReader &reader) + { + if (key == QtPluginMetaDataKeys::IID) { + QFactoryLoaderIidSearch::operator()(key, reader); + return IterationResult::ContinueSearch; + } + if (key != QtPluginMetaDataKeys::MetaData) + return skip(reader); + + if (!matchesIid) + return IterationResult::FinishedSearch; + if (!reader.isMap() || !reader.isLengthKnown()) + return IterationResult::InvalidHeaderItem; + if (!reader.enterContainer()) + return IterationResult::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()) + return IterationResult::InvalidHeaderItem; + keys = QCborValue::fromCbor(reader).toArray(); + break; + } + skip(reader); + } + // warning: we may not have finished iterating over the header + return IterationResult::FinishedSearch; } + using QFactoryLoaderIidSearch::operator(); +}; +} // unnamed namespace - raw += 4; - size -= 4; - QByteArray ba = QByteArray::fromRawData(raw, int(size)); - QCborParserError err; - QCborValue metadata = QCborValue::fromCbor(ba, &err); +template <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)) + return IterationResult::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()) + return IterationResult::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) + return IterationResult::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 { + return IterationResult::InvalidTopLevelItem; + } - if (err.error != QCborError::NoError) { - *errMsg = QLatin1String("Metadata parsing error: ") + err.error.toString(); - return QJsonDocument(); + if (QCborError e = reader.lastError()) + return e; + if (r != IterationResult::ContinueSearch) + return r; } - if (!metadata.isMap()) { - *errMsg = QStringLiteral("Unexpected metadata contents"); - return QJsonDocument(); + if (!reader.leaveContainer()) + return reader.lastError(); + return IterationResult::FinishedSearch; +} + +static bool isIidMatch(QByteArrayView raw, QLatin1StringView iid) +{ + QFactoryLoaderIidSearch search(iid); + iterateInPluginMetaData(raw, search); + return search.matchesIid; +} + +bool QPluginParsedMetaData::parse(QByteArrayView raw) +{ + QCborMap map; + auto r = iterateInPluginMetaData(raw, [&](const auto &key, QCborStreamReader &reader) { + QCborValue item = QCborValue::fromCbor(reader); + if (item.isInvalid()) + return IterationResult::ParsingError; + if constexpr (std::is_enum_v<std::decay_t<decltype(key)>>) + map[int(key)] = item; + else + map[QString::fromUtf8(key)] = item; + return IterationResult::ContinueSearch; + }); + + switch (r.result) { + case IterationResult::FinishedSearch: + case IterationResult::ContinueSearch: + break; + + // parse errors + case IterationResult::ParsingError: + return setError(QFactoryLoader::tr("Metadata parsing error: %1").arg(r.error.toString())); + case IterationResult::InvalidMetaDataVersion: + return setError(QFactoryLoader::tr("Invalid metadata version")); + case IterationResult::InvalidTopLevelItem: + case IterationResult::InvalidHeaderItem: + return setError(QFactoryLoader::tr("Unexpected metadata contents")); } - QJsonObject o; - o.insert(QLatin1String("version"), qt_version << 8); - o.insert(QLatin1String("debug"), bool(qt_archRequirements & 1)); - o.insert(QLatin1String("archreq"), qt_archRequirements); + // header was validated + auto 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; - // convert the top-level map integer keys - for (auto it : metadata.toMap()) { + 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) -#undef CONVERT_TO_STRING - - case int(QtPluginMetaDataKeys::Requirements): - // special case: recreate the debug key - o.insert(QLatin1String("debug"), bool(it.second.toInteger() & 1)); - key = QStringLiteral("archreq"); - break; } } else { key = it.first.toString(); @@ -123,194 +246,195 @@ static QJsonDocument jsonFromCborMetaData(const char *raw, qsizetype size, QStri if (!key.isEmpty()) o.insert(key, it.second.toJsonValue()); } - return QJsonDocument(o); -} - -QJsonDocument qJsonFromRawLibraryMetaData(const char *raw, qsizetype sectionSize, QString *errMsg) -{ - raw += metaDataSignatureLength(); - sectionSize -= metaDataSignatureLength(); - - return jsonFromCborMetaData(raw, sectionSize, errMsg); + return o; } class QFactoryLoaderPrivate : public QObjectPrivate { Q_DECLARE_PUBLIC(QFactoryLoader) + Q_DISABLE_COPY_MOVE(QFactoryLoaderPrivate) public: QFactoryLoaderPrivate() { } QByteArray iid; #if QT_CONFIG(library) ~QFactoryLoaderPrivate(); mutable QMutex mutex; - QList<QLibraryPrivate*> libraryList; - QMap<QString,QLibraryPrivate*> keyMap; + QDuplicateTracker<QString> loadedPaths; + std::vector<QLibraryPrivate::UniquePtr> libraries; + std::map<QString, QLibraryPrivate*> keyMap; QString suffix; + QString extraSearchPath; Qt::CaseSensitivity cs; - QDuplicateTracker<QString> loadedPaths; + + void updateSinglePath(const QString &pluginDir); #endif }; #if QT_CONFIG(library) -Q_GLOBAL_STATIC(QList<QFactoryLoader *>, qt_factory_loaders) - -Q_GLOBAL_STATIC(QRecursiveMutex, qt_factoryloader_mutex) +static Q_LOGGING_CATEGORY_WITH_ENV_OVERRIDE(lcFactoryLoader, "QT_DEBUG_PLUGINS", + "qt.core.plugin.factoryloader") -QFactoryLoaderPrivate::~QFactoryLoaderPrivate() +namespace { +struct QFactoryLoaderGlobals { - for (QLibraryPrivate *library : qAsConst(libraryList)) - library->release(); + // needs to be recursive because loading one plugin could cause another + // factory to be initialized + QRecursiveMutex mutex; + QList<QFactoryLoader *> loaders; +}; } -void QFactoryLoader::update() -{ -#ifdef QT_SHARED - Q_D(QFactoryLoader); - QStringList paths = QCoreApplication::libraryPaths(); - for (int i = 0; i < paths.count(); ++i) { - const QString &pluginDir = paths.at(i); - // Already loaded, skip it... - if (d->loadedPaths.hasSeen(pluginDir)) - continue; +Q_GLOBAL_STATIC(QFactoryLoaderGlobals, qt_factoryloader_global) -#ifdef Q_OS_ANDROID - QString path = pluginDir; -#else - QString path = pluginDir + d->suffix; -#endif +QFactoryLoaderPrivate::~QFactoryLoaderPrivate() + = default; - if (qt_debug_component()) - qDebug() << "QFactoryLoader::QFactoryLoader() checking directory path" << path << "..."; +inline void QFactoryLoaderPrivate::updateSinglePath(const QString &path) +{ + struct LibraryReleaser { + void operator()(QLibraryPrivate *library) + { if (library) library->release(); } + }; - if (!QDir(path).exists(QLatin1String("."))) - continue; + // If we've already loaded, skip it... + if (loadedPaths.hasSeen(path)) + return; - QStringList plugins = QDir(path).entryList( + qCDebug(lcFactoryLoader) << "checking directory path" << path << "..."; + + QDirListing plugins(path, #if defined(Q_OS_WIN) - QStringList(QStringLiteral("*.dll")), + QStringList(QStringLiteral("*.dll")), #elif defined(Q_OS_ANDROID) - QStringList(QLatin1String("libplugins_%1_*.so").arg(d->suffix)), + QStringList("libplugins_%1_*.so"_L1.arg(suffix)), #endif - QDir::Files); - QLibraryPrivate *library = nullptr; - - for (int j = 0; j < plugins.count(); ++j) { - QString fileName = QDir::cleanPath(path + QLatin1Char('/') + plugins.at(j)); - -#ifdef Q_OS_MAC - const bool isDebugPlugin = fileName.endsWith(QLatin1String("_debug.dylib")); - const bool isDebugLibrary = - #ifdef QT_DEBUG - true; - #else - false; - #endif - - // Skip mismatching plugins so that we don't end up loading both debug and release - // versions of the same Qt libraries (due to the plugin's dependencies). - if (isDebugPlugin != isDebugLibrary) - continue; + QDir::Files); + + for (const auto &dirEntry : plugins) { + const QString &fileName = dirEntry.fileName(); +#ifdef Q_OS_DARWIN + const bool isDebugPlugin = fileName.endsWith("_debug.dylib"_L1); + const bool isDebugLibrary = + #ifdef QT_DEBUG + true; + #else + false; + #endif + + // Skip mismatching plugins so that we don't end up loading both debug and release + // versions of the same Qt libraries (due to the plugin's dependencies). + if (isDebugPlugin != isDebugLibrary) + continue; #elif defined(Q_PROCESSOR_X86) - if (fileName.endsWith(QLatin1String(".avx2")) || fileName.endsWith(QLatin1String(".avx512"))) { - // ignore AVX2-optimized file, we'll do a bait-and-switch to it later - continue; - } + if (fileName.endsWith(".avx2"_L1) || fileName.endsWith(".avx512"_L1)) { + // ignore AVX2-optimized file, we'll do a bait-and-switch to it later + continue; + } #endif - if (qt_debug_component()) { - qDebug() << "QFactoryLoader::QFactoryLoader() looking at" << fileName; - } + qCDebug(lcFactoryLoader) << "looking at" << fileName; - Q_TRACE(QFactoryLoader_update, fileName); - - library = QLibraryPrivate::findOrCreate(QFileInfo(fileName).canonicalFilePath()); - if (!library->isPlugin()) { - if (qt_debug_component()) { - qDebug() << library->errorString << Qt::endl - << " not a plugin"; - } - library->release(); - continue; - } + Q_TRACE(QFactoryLoader_update, fileName); - QStringList keys; - bool metaDataOk = false; + QLibraryPrivate::UniquePtr library; + library.reset(QLibraryPrivate::findOrCreate(dirEntry.canonicalFilePath())); + if (!library->isPlugin()) { + qCDebug(lcFactoryLoader) << library->errorString << Qt::endl + << " not a plugin"; + continue; + } - QString iid = library->metaData.value(QLatin1String("IID")).toString(); - if (iid == QLatin1String(d->iid.constData(), d->iid.size())) { - QJsonObject object = library->metaData.value(QLatin1String("MetaData")).toObject(); - metaDataOk = true; + QStringList keys; + bool metaDataOk = false; - QJsonArray k = object.value(QLatin1String("Keys")).toArray(); - for (int i = 0; i < k.size(); ++i) - keys += d->cs ? k.at(i).toString() : k.at(i).toString().toLower(); - } - if (qt_debug_component()) - qDebug() << "Got keys from plugin meta data" << keys; + 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) { - library->release(); - continue; - } + if (!metaDataOk) + continue; - int keyUsageCount = 0; - for (int k = 0; k < keys.count(); ++k) { - // first come first serve, unless the first - // library was built with a future Qt version, - // whereas the new one has a Qt version that fits - // better - const QString &key = keys.at(k); - QLibraryPrivate *previous = d->keyMap.value(key); - int prev_qt_version = 0; - if (previous) { - prev_qt_version = (int)previous->metaData.value(QLatin1String("version")).toDouble(); - } - int qt_version = (int)library->metaData.value(QLatin1String("version")).toDouble(); - if (!previous || (prev_qt_version > QT_VERSION && qt_version <= QT_VERSION)) { - d->keyMap[key] = library; - ++keyUsageCount; - } - } - if (keyUsageCount || keys.isEmpty()) { - library->setLoadHints(QLibrary::PreventUnloadHint); // once loaded, don't unload - QMutexLocker locker(&d->mutex); - d->libraryList += library; - } else { - library->release(); + int keyUsageCount = 0; + for (const QString &key : std::as_const(keys)) { + // first come first serve, unless the first + // library was built with a future Qt version, + // whereas the new one has a Qt version that fits + // better + constexpr int QtVersionNoPatch = QT_VERSION_CHECK(QT_VERSION_MAJOR, QT_VERSION_MINOR, 0); + QLibraryPrivate *&previous = keyMap[key]; + int prev_qt_version = 0; + if (previous) + prev_qt_version = int(previous->metaData.value(QtPluginMetaDataKeys::QtVersion).toInteger()); + int qt_version = int(library->metaData.value(QtPluginMetaDataKeys::QtVersion).toInteger()); + if (!previous || (prev_qt_version > QtVersionNoPatch && qt_version <= QtVersionNoPatch)) { + previous = library.get(); // we WILL .release() + ++keyUsageCount; } } + if (keyUsageCount || keys.isEmpty()) { + library->setLoadHints(QLibrary::PreventUnloadHint); // once loaded, don't unload + QMutexLocker locker(&mutex); + libraries.push_back(std::move(library)); + } + }; +} + +void QFactoryLoader::update() +{ +#ifdef QT_SHARED + Q_D(QFactoryLoader); + + 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); } + if (!d->extraSearchPath.isEmpty()) + d->updateSinglePath(d->extraSearchPath); #else Q_D(QFactoryLoader); - if (qt_debug_component()) { - qDebug() << "QFactoryLoader::QFactoryLoader() ignoring" << d->iid - << "since plugins are disabled in static builds"; - } + qCDebug(lcFactoryLoader) << "ignoring" << d->iid + << "since plugins are disabled in static builds"; #endif } QFactoryLoader::~QFactoryLoader() { - QMutexLocker locker(qt_factoryloader_mutex()); - qt_factory_loaders()->removeAll(this); + if (!qt_factoryloader_global.isDestroyed()) { + QMutexLocker locker(&qt_factoryloader_global->mutex); + qt_factoryloader_global->loaders.removeOne(this); + } } -#if defined(Q_OS_UNIX) && !defined (Q_OS_MAC) +#if defined(Q_OS_UNIX) && !defined (Q_OS_DARWIN) QLibraryPrivate *QFactoryLoader::library(const QString &key) const { Q_D(const QFactoryLoader); - return d->keyMap.value(d->cs ? key : key.toLower()); + const auto it = d->keyMap.find(d->cs ? key : key.toLower()); + if (it == d->keyMap.cend()) + return nullptr; + return it->second; } #endif void QFactoryLoader::refreshAll() { - QMutexLocker locker(qt_factoryloader_mutex()); - QList<QFactoryLoader *> *loaders = qt_factory_loaders(); - for (QList<QFactoryLoader *>::const_iterator it = loaders->constBegin(); - it != loaders->constEnd(); ++it) { - (*it)->update(); + if (qt_factoryloader_global.exists()) { + QMutexLocker locker(&qt_factoryloader_global->mutex); + for (QFactoryLoader *loader : std::as_const(qt_factoryloader_global->loaders)) + loader->update(); } } @@ -321,6 +445,9 @@ QFactoryLoader::QFactoryLoader(const char *iid, Qt::CaseSensitivity cs) : QObject(*new QFactoryLoaderPrivate) { + Q_ASSERT_X(suffix.startsWith(u'/'), "QFactoryLoader", + "For historical reasons, the suffix must start with '/' (and it can't be empty)"); + moveToThread(QCoreApplicationPrivate::mainThread()); Q_D(QFactoryLoader); d->iid = iid; @@ -328,36 +455,93 @@ QFactoryLoader::QFactoryLoader(const char *iid, d->cs = cs; d->suffix = suffix; # ifdef Q_OS_ANDROID - if (!d->suffix.isEmpty() && d->suffix.at(0) == QLatin1Char('/')) + if (!d->suffix.isEmpty() && d->suffix.at(0) == u'/') d->suffix.remove(0, 1); # endif - QMutexLocker locker(qt_factoryloader_mutex()); + QMutexLocker locker(&qt_factoryloader_global->mutex); update(); - qt_factory_loaders()->append(this); + qt_factoryloader_global->loaders.append(this); #else Q_UNUSED(suffix); Q_UNUSED(cs); #endif } -QList<QJsonObject> QFactoryLoader::metaData() const +void QFactoryLoader::setExtraSearchPath(const QString &path) +{ +#if QT_CONFIG(library) + Q_D(QFactoryLoader); + 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 everything + d->loadedPaths.clear(); + d->libraries.clear(); + d->keyMap.clear(); + update(); + } +#else + Q_UNUSED(path); +#endif +} + +QFactoryLoader::MetaDataList QFactoryLoader::metaData() const { Q_D(const QFactoryLoader); - QList<QJsonObject> metaData; + QList<QPluginParsedMetaData> metaData; #if QT_CONFIG(library) QMutexLocker locker(&d->mutex); - for (int i = 0; i < d->libraryList.size(); ++i) - metaData.append(d->libraryList.at(i)->metaData); + for (const auto &library : d->libraries) + metaData.append(library->metaData); #endif + QLatin1StringView iid(d->iid.constData(), d->iid.size()); const auto staticPlugins = QPluginLoader::staticPlugins(); for (const QStaticPlugin &plugin : staticPlugins) { - const QJsonObject object = plugin.metaData(); - if (object.value(QLatin1String("IID")) != QLatin1String(d->iid.constData(), d->iid.size())) + QByteArrayView pluginData(static_cast<const char *>(plugin.rawMetaData), plugin.rawMetaDataSize); + QPluginParsedMetaData parsed(pluginData); + if (parsed.isError() || parsed.value(QtPluginMetaDataKeys::IID) != iid) continue; - metaData.append(object); + 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 +{ + Q_D(const QFactoryLoader); + QList<QCborArray> metaData; +#if QT_CONFIG(library) + QMutexLocker locker(&d->mutex); + for (const auto &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()); + const auto 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; } @@ -369,8 +553,8 @@ QObject *QFactoryLoader::instance(int index) const #if QT_CONFIG(library) QMutexLocker lock(&d->mutex); - if (index < d->libraryList.size()) { - QLibraryPrivate *library = d->libraryList.at(index); + if (size_t(index) < d->libraries.size()) { + QLibraryPrivate *library = d->libraries[index].get(); if (QObject *obj = library->pluginInstance()) { if (!obj->parent()) obj->moveToThread(QCoreApplicationPrivate::mainThread()); @@ -378,18 +562,20 @@ QObject *QFactoryLoader::instance(int index) const } return nullptr; } - index -= d->libraryList.size(); + // we know d->libraries.size() <= index <= numeric_limits<decltype(index)>::max() → no overflow + index -= static_cast<int>(d->libraries.size()); lock.unlock(); #endif - QList<QStaticPlugin> staticPlugins = QPluginLoader::staticPlugins(); - for (int i = 0; i < staticPlugins.count(); ++i) { - const QJsonObject object = staticPlugins.at(i).metaData(); - if (object.value(QLatin1String("IID")) != QLatin1String(d->iid.constData(), d->iid.size())) + QLatin1StringView iid(d->iid.constData(), d->iid.size()); + const QList<QStaticPlugin> staticPlugins = QPluginLoader::staticPlugins(); + for (QStaticPlugin plugin : staticPlugins) { + QByteArrayView pluginData(static_cast<const char *>(plugin.rawMetaData), plugin.rawMetaDataSize); + if (!isIidMatch(pluginData, iid)) continue; if (index == 0) - return staticPlugins.at(i).instance(); + return plugin.instance(); --index; } @@ -399,26 +585,22 @@ QObject *QFactoryLoader::instance(int index) const QMultiMap<int, QString> QFactoryLoader::keyMap() const { QMultiMap<int, QString> result; - const QList<QJsonObject> metaDataList = metaData(); - for (int i = 0; i < metaDataList.size(); ++i) { - const QJsonObject metaData = metaDataList.at(i).value(QLatin1String("MetaData")).toObject(); - const QJsonArray keys = metaData.value(QLatin1String("Keys")).toArray(); - const int keyCount = keys.size(); - for (int k = 0; k < keyCount; ++k) - result.insert(i, keys.at(k).toString()); + 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; } int QFactoryLoader::indexOf(const QString &needle) const { - const QList<QJsonObject> metaDataList = metaData(); - for (int i = 0; i < metaDataList.size(); ++i) { - const QJsonObject metaData = metaDataList.at(i).value(QLatin1String("MetaData")).toObject(); - const QJsonArray keys = metaData.value(QLatin1String("Keys")).toArray(); - const int keyCount = keys.size(); - for (int k = 0; k < keyCount; ++k) { - if (!keys.at(k).toString().compare(needle, Qt::CaseInsensitive)) + 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; } } diff --git a/src/corelib/plugin/qfactoryloader_p.h b/src/corelib/plugin/qfactoryloader_p.h index 7815ea0b5d..56dc7e6ad1 100644 --- a/src/corelib/plugin/qfactoryloader_p.h +++ b/src/corelib/plugin/qfactoryloader_p.h @@ -1,41 +1,6 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** 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) 2017 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 #ifndef QFACTORYLOADER_P_H #define QFACTORYLOADER_P_H @@ -54,20 +19,43 @@ #include "QtCore/qglobal.h" #ifndef QT_NO_QOBJECT -#include "QtCore/qobject.h" -#include "QtCore/qstringlist.h" +#include "QtCore/private/qplugin_p.h" +#include "QtCore/qcbormap.h" #include "QtCore/qcborvalue.h" -#include "QtCore/qjsonobject.h" -#include "QtCore/qjsondocument.h" #include "QtCore/qmap.h" -#include "QtCore/qendian.h" -#if QT_CONFIG(library) -#include "private/qlibrary_p.h" -#endif +#include "QtCore/qobject.h" +#include "QtCore/qplugin.h" QT_BEGIN_NAMESPACE -QJsonDocument qJsonFromRawLibraryMetaData(const char *raw, qsizetype size, QString *errMsg); +class QJsonObject; +class QLibraryPrivate; + +class QPluginParsedMetaData +{ + QCborValue data; + bool setError(const QString &errorString) Q_DECL_COLD_FUNCTION + { + data = errorString; + return false; + } +public: + QPluginParsedMetaData() = default; + QPluginParsedMetaData(QByteArrayView input) { parse(input); } + + bool isError() const { return !data.isMap(); } + QString errorString() const { return data.toString(); } + + bool parse(QByteArrayView input); + bool parse(QPluginMetaData metaData) + { return parse(QByteArrayView(reinterpret_cast<const char *>(metaData.data), metaData.size)); } + + QJsonObject toJson() const; // only for QLibrary & QPluginLoader + + // if data is not a map, toMap() returns empty, so shall these functions + QCborMap toCbor() const { return data.toMap(); } + QCborValue value(QtPluginMetaDataKeys k) const { return data[int(k)]; } +}; class QFactoryLoaderPrivate; class Q_CORE_EXPORT QFactoryLoader : public QObject @@ -86,15 +74,19 @@ public: void update(); static void refreshAll(); -#if defined(Q_OS_UNIX) && !defined (Q_OS_MAC) +#if defined(Q_OS_UNIX) && !defined (Q_OS_DARWIN) QLibraryPrivate *library(const QString &key) const; -#endif // Q_OS_UNIX && !Q_OS_MAC +#endif // Q_OS_UNIX && !Q_OS_DARWIN #endif // QT_CONFIG(library) + void setExtraSearchPath(const QString &path); QMultiMap<int, QString> keyMap() const; int indexOf(const QString &needle) const; - QList<QJsonObject> metaData() const; + using MetaDataList = QList<QPluginParsedMetaData>; + + MetaDataList metaData() const; + QList<QCborArray> metaDataKeys() const; QObject *instance(int index) const; }; diff --git a/src/corelib/plugin/qlibrary.cpp b/src/corelib/plugin/qlibrary.cpp index b67c3b6439..a3ef8e3c52 100644 --- a/src/corelib/plugin/qlibrary.cpp +++ b/src/corelib/plugin/qlibrary.cpp @@ -1,86 +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/q20algorithm.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 @@ -187,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 - */ + 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. - if (!s || !pattern || qsizetype(p_len) > s_len) - return -1; - - 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 }; } /* @@ -236,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 @@ -254,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) { - // Try reading the data into memory instead (up to 64 MB). + // 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) { + // 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) - QElfParser::ScanResult 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) @@ -356,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 { @@ -384,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() { @@ -404,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. @@ -425,17 +356,17 @@ inline void QLibraryStore::cleanup() 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"; } } @@ -462,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; } @@ -497,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; } @@ -528,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) @@ -540,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); @@ -584,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 @@ -612,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(); @@ -645,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; } @@ -669,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,21 +675,38 @@ bool QLibrary::isLibrary(const QString &fileName) 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() @@ -763,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 @@ -779,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. @@ -799,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; } @@ -842,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; } /*! @@ -858,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() */ @@ -872,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; } @@ -972,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 @@ -1001,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()); } /*! @@ -1025,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 } /*! @@ -1162,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. diff --git a/src/corelib/plugin/qlibrary.h b/src/corelib/plugin/qlibrary.h index ac59f45789..f31047b214 100644 --- a/src/corelib/plugin/qlibrary.h +++ b/src/corelib/plugin/qlibrary.h @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** 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) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #ifndef QLIBRARY_H #define QLIBRARY_H @@ -63,7 +27,7 @@ public: DeepBindHint = 0x10 }; Q_DECLARE_FLAGS(LoadHints, LoadHint) - Q_FLAG(LoadHint) + Q_ENUM(LoadHint) Q_FLAG(LoadHints) explicit QLibrary(QObject *parent = nullptr); diff --git a/src/corelib/plugin/qlibrary_p.h b/src/corelib/plugin/qlibrary_p.h index 3ca544b2de..87d36ee5c8 100644 --- a/src/corelib/plugin/qlibrary_p.h +++ b/src/corelib/plugin/qlibrary_p.h @@ -1,42 +1,6 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Copyright (C) 2016 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 #ifndef QLIBRARY_P_H #define QLIBRARY_P_H @@ -52,21 +16,34 @@ // We mean it. // -#include <QtCore/private/qglobal_p.h> #include "QtCore/qlibrary.h" + +#include "QtCore/private/qfactoryloader_p.h" +#include "QtCore/qloggingcategory.h" #include "QtCore/qmutex.h" +#include "QtCore/qplugin.h" #include "QtCore/qpointer.h" #include "QtCore/qstringlist.h" -#include "QtCore/qplugin.h" #ifdef Q_OS_WIN # include "QtCore/qt_windows.h" #endif +#include <memory> + QT_REQUIRE_CONFIG(library); QT_BEGIN_NAMESPACE -bool qt_debug_component(); +Q_DECLARE_LOGGING_CATEGORY(qt_lcDebugPlugins) + +struct QLibraryScanResult +{ + qsizetype pos; + qsizetype length; +#if defined(Q_OF_MACH_O) + bool isEncrypted = false; +#endif +}; class QLibraryStore; class QLibraryPrivate @@ -79,6 +56,12 @@ public: #endif enum UnloadFlag { UnloadSys, NoUnloadSys }; + struct Deleter { + // QLibraryPrivate::release() is not, yet, and cannot easily be made, noexcept: + void operator()(QLibraryPrivate *p) const { p->release(); } + }; + using UniquePtr = std::unique_ptr<QLibraryPrivate, Deleter>; + const QString fileName; const QString fullVersion; @@ -104,7 +87,7 @@ public: // the mutex protects the fields below QMutex mutex; QPointer<QObject> inst; // used by QFactoryLoader - QJsonObject metaData; + QPluginParsedMetaData metaData; QString errorString; QString qualifiedFileName; diff --git a/src/corelib/plugin/qlibrary_unix.cpp b/src/corelib/plugin/qlibrary_unix.cpp index 05b2c92284..a6fb5403cd 100644 --- a/src/corelib/plugin/qlibrary_unix.cpp +++ b/src/corelib/plugin/qlibrary_unix.cpp @@ -1,54 +1,18 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Copyright (C) 2020 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) 2016 The Qt Company Ltd. +// Copyright (C) 2020 Intel Corporation +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qplatformdefs.h" +#include <qcoreapplication.h> #include <qfile.h> #include "qlibrary_p.h" -#include <qcoreapplication.h> #include <private/qfilesystementry_p.h> #include <private/qsimd_p.h> #include <dlfcn.h> -#ifdef Q_OS_MAC +#ifdef Q_OS_DARWIN # include <private/qcore_mac_p.h> #endif @@ -59,11 +23,7 @@ QT_BEGIN_NAMESPACE -static QString qdlerror() -{ - const char *err = dlerror(); - return err ? QLatin1Char('(') + QString::fromLocal8Bit(err) + QLatin1Char(')') : QString(); -} +using namespace Qt::StringLiterals; QStringList QLibraryPrivate::suffixes_sys(const QString &fullVersion) { @@ -82,36 +42,36 @@ QStringList QLibraryPrivate::suffixes_sys(const QString &fullVersion) // .so is preferred. # if defined(__ia64) if (!fullVersion.isEmpty()) { - suffixes << QLatin1String(".so.%1").arg(fullVersion); + suffixes << ".so.%1"_L1.arg(fullVersion); } else { - suffixes << QLatin1String(".so"); + suffixes << ".so"_L1; } # endif if (!fullVersion.isEmpty()) { - suffixes << QLatin1String(".sl.%1").arg(fullVersion); - suffixes << QLatin1String(".%1").arg(fullVersion); + suffixes << ".sl.%1"_L1.arg(fullVersion); + suffixes << ".%1"_L1.arg(fullVersion); } else { - suffixes << QLatin1String(".sl"); + suffixes << ".sl"_L1; } #elif defined(Q_OS_AIX) suffixes << ".a"; #else if (!fullVersion.isEmpty()) { - suffixes << QLatin1String(".so.%1").arg(fullVersion); + suffixes << ".so.%1"_L1.arg(fullVersion); } else { - suffixes << QLatin1String(".so"); + suffixes << ".so"_L1; # ifdef Q_OS_ANDROID suffixes << QStringLiteral(LIBS_SUFFIX); # endif } #endif -# ifdef Q_OS_MAC +# ifdef Q_OS_DARWIN if (!fullVersion.isEmpty()) { - suffixes << QLatin1String(".%1.bundle").arg(fullVersion); - suffixes << QLatin1String(".%1.dylib").arg(fullVersion); + suffixes << ".%1.bundle"_L1.arg(fullVersion); + suffixes << ".%1.dylib"_L1.arg(fullVersion); } else { - suffixes << QLatin1String(".bundle") << QLatin1String(".dylib"); + suffixes << ".bundle"_L1 << ".dylib"_L1; } #endif return suffixes; @@ -119,21 +79,26 @@ QStringList QLibraryPrivate::suffixes_sys(const QString &fullVersion) QStringList QLibraryPrivate::prefixes_sys() { - return QStringList() << QLatin1String("lib"); + return QStringList() << "lib"_L1; } bool QLibraryPrivate::load_sys() { +#if defined(Q_OS_WASM) && defined(QT_STATIC) + // emscripten does not support dlopen when using static linking + return false; +#endif + QMutexLocker locker(&mutex); QString attempt; QFileSystemEntry fsEntry(fileName); QString path = fsEntry.path(); QString name = fsEntry.fileName(); - if (path == QLatin1String(".") && !fileName.startsWith(path)) + if (path == "."_L1 && !fileName.startsWith(path)) path.clear(); else - path += QLatin1Char('/'); + path += u'/'; QStringList suffixes; QStringList prefixes; @@ -198,7 +163,7 @@ bool QLibraryPrivate::load_sys() QStringList tmp; qSwap(tmp, list); list.reserve(tmp.size() * 2); - for (const QString &s : qAsConst(tmp)) { + for (const QString &s : std::as_const(tmp)) { QString modifiedPath = s; f(&modifiedPath); list.append(modifiedPath); @@ -207,10 +172,10 @@ bool QLibraryPrivate::load_sys() }; if (pluginState == IsAPlugin) { // add ".avx2" to each suffix in the list - transform(suffixes, [](QString *s) { s->append(QLatin1String(".avx2")); }); + transform(suffixes, [](QString *s) { s->append(".avx2"_L1); }); } else { // prepend "haswell/" to each prefix in the list - transform(prefixes, [](QString *s) { s->prepend(QLatin1String("haswell/")); }); + transform(prefixes, [](QString *s) { s->prepend("haswell/"_L1); }); } } #endif @@ -222,15 +187,15 @@ bool QLibraryPrivate::load_sys() for (int suffix = 0; retry && !hnd && suffix < suffixes.size(); suffix++) { if (!prefixes.at(prefix).isEmpty() && name.startsWith(prefixes.at(prefix))) continue; - if (path.isEmpty() && prefixes.at(prefix).contains(QLatin1Char('/'))) + if (path.isEmpty() && prefixes.at(prefix).contains(u'/')) continue; if (!suffixes.at(suffix).isEmpty() && name.endsWith(suffixes.at(suffix))) continue; if (loadHints & QLibrary::LoadArchiveMemberHint) { attempt = name; - int lparen = attempt.indexOf(QLatin1Char('(')); + qsizetype lparen = attempt.indexOf(u'('); if (lparen == -1) - lparen = attempt.count(); + lparen = attempt.size(); attempt = path + prefixes.at(prefix) + attempt.insert(lparen, suffixes.at(suffix)); } else { attempt = path + prefixes.at(prefix) + name + suffixes.at(suffix); @@ -240,19 +205,11 @@ bool QLibraryPrivate::load_sys() #ifdef Q_OS_ANDROID if (!hnd) { auto attemptFromBundle = attempt; - hnd = dlopen(QFile::encodeName(attemptFromBundle.replace(QLatin1Char('/'), QLatin1Char('_'))), dlFlags); - } - if (hnd) { - using JniOnLoadPtr = jint (*)(JavaVM *vm, void *reserved); - JniOnLoadPtr jniOnLoad = reinterpret_cast<JniOnLoadPtr>(dlsym(hnd, "JNI_OnLoad")); - if (jniOnLoad && jniOnLoad(QJniEnvironment::javaVM(), nullptr) == JNI_ERR) { - dlclose(hnd); - hnd = nullptr; - } + hnd = dlopen(QFile::encodeName(attemptFromBundle.replace(u'/', u'_')), dlFlags); } #endif - if (!hnd && fileName.startsWith(QLatin1Char('/')) && QFile::exists(attempt)) { + if (!hnd && fileName.startsWith(u'/') && QFile::exists(attempt)) { // We only want to continue if dlopen failed due to that the shared library did not exist. // However, we are only able to apply this check for absolute filenames (since they are // not influenced by the content of LD_LIBRARY_PATH, /etc/ld.so.cache, DT_RPATH etc...) @@ -262,7 +219,7 @@ bool QLibraryPrivate::load_sys() } } -#ifdef Q_OS_MAC +#ifdef Q_OS_DARWIN if (!hnd) { QByteArray utf8Bundle = fileName.toUtf8(); QCFType<CFURLRef> bundleUrl = CFURLCreateFromFileSystemRepresentation(NULL, reinterpret_cast<const UInt8*>(utf8Bundle.data()), utf8Bundle.length(), true); @@ -279,7 +236,8 @@ bool QLibraryPrivate::load_sys() locker.relock(); if (!hnd) { - errorString = QLibrary::tr("Cannot load library %1: %2").arg(fileName, qdlerror()); + errorString = QLibrary::tr("Cannot load library %1: %2").arg(fileName, + QLatin1StringView(dlerror())); } if (hnd) { qualifiedFileName = attempt; @@ -291,36 +249,27 @@ bool QLibraryPrivate::load_sys() bool QLibraryPrivate::unload_sys() { - if (dlclose(pHnd.loadAcquire())) { -#if defined (Q_OS_QNX) // Workaround until fixed in QNX; fixes crash in - char *error = dlerror(); // QtDeclarative auto test "qqmlenginecleanup" for instance + bool doTryUnload = true; +#ifndef RTLD_NODELETE + if (loadHints() & QLibrary::PreventUnloadHint) + doTryUnload = false; +#endif + if (doTryUnload && dlclose(pHnd.loadAcquire())) { + const char *error = dlerror(); +#if defined (Q_OS_QNX) + // Workaround until fixed in QNX; fixes crash in + // QtDeclarative auto test "qqmlenginecleanup" for instance if (!qstrcmp(error, "Shared objects still referenced")) // On QNX that's only "informative" return true; - errorString = QLibrary::tr("Cannot unload library %1: %2").arg(fileName, - QLatin1String(error)); -#else - errorString = QLibrary::tr("Cannot unload library %1: %2").arg(fileName, qdlerror()); #endif + errorString = QLibrary::tr("Cannot unload library %1: %2").arg(fileName, + QLatin1StringView(error)); return false; } errorString.clear(); return true; } -#if defined(Q_OS_LINUX) -Q_CORE_EXPORT QFunctionPointer qt_linux_find_symbol_sys(const char *symbol) -{ - return QFunctionPointer(dlsym(RTLD_DEFAULT, symbol)); -} -#endif - -#ifdef Q_OS_MAC -Q_CORE_EXPORT QFunctionPointer qt_mac_resolve_sys(void *handle, const char *symbol) -{ - return QFunctionPointer(dlsym(handle, symbol)); -} -#endif - QFunctionPointer QLibraryPrivate::resolve_sys(const char *symbol) { QFunctionPointer address = QFunctionPointer(dlsym(pHnd.loadAcquire(), symbol)); diff --git a/src/corelib/plugin/qlibrary_win.cpp b/src/corelib/plugin/qlibrary_win.cpp index 07f5a4b744..c95118e554 100644 --- a/src/corelib/plugin/qlibrary_win.cpp +++ b/src/corelib/plugin/qlibrary_win.cpp @@ -1,46 +1,11 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** 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) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qplatformdefs.h" #include "qlibrary_p.h" -#include "qfile.h" + #include "qdir.h" +#include "qfile.h" #include "qfileinfo.h" #include <private/qfilesystementry_p.h> @@ -48,6 +13,8 @@ QT_BEGIN_NAMESPACE +using namespace Qt::StringLiterals; + extern QString qt_error_string(int code); QStringList QLibraryPrivate::suffixes_sys(const QString& fullVersion) @@ -80,7 +47,7 @@ bool QLibraryPrivate::load_sys() QStringList attempts; if (pluginState != IsAPlugin) - attempts.append(fileName + QLatin1String(".dll")); + attempts.append(fileName + ".dll"_L1); // If the fileName is an absolute path we try that first, otherwise we // use the system-specific suffix first @@ -92,7 +59,7 @@ bool QLibraryPrivate::load_sys() locker.unlock(); Handle hnd = nullptr; - for (const QString &attempt : qAsConst(attempts)) { + for (const QString &attempt : std::as_const(attempts)) { hnd = LoadLibrary(reinterpret_cast<const wchar_t*>(QDir::toNativeSeparators(attempt).utf16())); // If we have a handle or the last error is something other than "unable @@ -114,9 +81,9 @@ bool QLibraryPrivate::load_sys() ::GetModuleFileName(hnd, buffer, MAX_PATH); QString moduleFileName = QString::fromWCharArray(buffer); - moduleFileName.remove(0, 1 + moduleFileName.lastIndexOf(QLatin1Char('\\'))); + moduleFileName.remove(0, 1 + moduleFileName.lastIndexOf(u'\\')); const QDir dir(fsEntry.path()); - if (dir.path() == QLatin1String(".")) + if (dir.path() == "."_L1) qualifiedFileName = moduleFileName; else qualifiedFileName = dir.filePath(moduleFileName); diff --git a/src/corelib/plugin/qmachparser.cpp b/src/corelib/plugin/qmachparser.cpp index 9d0b4de4f5..7a82b84cb3 100644 --- a/src/corelib/plugin/qmachparser.cpp +++ b/src/corelib/plugin/qmachparser.cpp @@ -1,54 +1,21 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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) 2016 Intel Corporation. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qmachparser_p.h" -#if defined(Q_OF_MACH_O) - #include <qendian.h> -#include "qlibrary_p.h" #include <mach-o/loader.h> #include <mach-o/fat.h> QT_BEGIN_NAMESPACE +using namespace Qt::StringLiterals; + +// Whether we include some extra validity checks +// (checks to ensure we don't read out-of-bounds are always included) +static constexpr bool IncludeValidityChecks = true; + #if defined(Q_PROCESSOR_X86_64) # define MACHO64 static const cpu_type_t my_cputype = CPU_TYPE_X86_64; @@ -81,15 +48,32 @@ typedef section my_section; static const uint32_t my_magic = MH_MAGIC; #endif -static int ns(const QString &reason, const QString &library, QString *errorString) +Q_DECL_COLD_FUNCTION +static QLibraryScanResult notfound(const QString &reason, QString *errorString) { - if (errorString) - *errorString = QLibrary::tr("'%1' is not a valid Mach-O binary (%2)") - .arg(library, reason.isEmpty() ? QLibrary::tr("file is corrupt") : reason); - return QMachOParser::NotSuitable; + *errorString = QLibrary::tr("'%1' is not a valid Mach-O binary (%2)") + .arg(*errorString, reason.isEmpty() ? QLibrary::tr("file is corrupt") : reason); + return {}; +} + +static bool isEncrypted(const my_mach_header *header) +{ + auto commandCursor = uintptr_t(header) + sizeof(my_mach_header); + for (uint32_t i = 0; i < header->ncmds; ++i) { + load_command *loadCommand = reinterpret_cast<load_command *>(commandCursor); + if (loadCommand->cmd == LC_ENCRYPTION_INFO || loadCommand->cmd == LC_ENCRYPTION_INFO_64) { + // The layout of encryption_info_command and encryption_info_command_64 is the same + // up until and including cryptid, so we can treat it as encryption_info_command. + auto encryptionInfoCommand = reinterpret_cast<encryption_info_command*>(loadCommand); + return encryptionInfoCommand->cryptid != 0; + } + commandCursor += loadCommand->cmdsize; + } + + return false; } -int QMachOParser::parse(const char *m_s, ulong fdlen, const QString &library, QString *errorString, qsizetype *pos, qsizetype *sectionlen) +QLibraryScanResult QMachOParser::parse(const char *m_s, ulong fdlen, QString *errorString) { // The minimum size of a Mach-O binary we're interested in. // It must have a full Mach header, at least one segment and at least one @@ -100,7 +84,7 @@ int QMachOParser::parse(const char *m_s, ulong fdlen, const QString &library, QS static const size_t MinFatHeaderSize = sizeof(fat_header) + 2 * sizeof(fat_arch); if (Q_UNLIKELY(fdlen < MinFileSize)) - return ns(QLibrary::tr("file too small"), library, errorString); + return notfound(QLibrary::tr("file too small"), errorString); // find out if this is a fat Mach-O binary first const my_mach_header *header = nullptr; @@ -109,12 +93,12 @@ int QMachOParser::parse(const char *m_s, ulong fdlen, const QString &library, QS // find our architecture in the binary const fat_arch *arch = reinterpret_cast<const fat_arch *>(fat + 1); if (Q_UNLIKELY(fdlen < MinFatHeaderSize)) { - return ns(QLibrary::tr("file too small"), library, errorString); + return notfound(QLibrary::tr("file too small"), errorString); } int count = qFromBigEndian(fat->nfat_arch); if (Q_UNLIKELY(fdlen < sizeof(*fat) + sizeof(*arch) * count)) - return ns(QString(), library, errorString); + return notfound(QString(), errorString); for (int i = 0; i < count; ++i) { if (arch[i].cputype == qToBigEndian(my_cputype)) { @@ -123,7 +107,7 @@ int QMachOParser::parse(const char *m_s, ulong fdlen, const QString &library, QS uint32_t offset = qFromBigEndian(arch[i].offset); if (Q_UNLIKELY(size > fdlen) || Q_UNLIKELY(offset > fdlen) || Q_UNLIKELY(size + offset > fdlen) || Q_UNLIKELY(size < MinFileSize)) - return ns(QString(), library, errorString); + return notfound(QString(), errorString); header = reinterpret_cast<const my_mach_header *>(m_s + offset); fdlen = size; @@ -131,36 +115,35 @@ int QMachOParser::parse(const char *m_s, ulong fdlen, const QString &library, QS } } if (!header) - return ns(QLibrary::tr("no suitable architecture in fat binary"), library, errorString); + return notfound(QLibrary::tr("no suitable architecture in fat binary"), errorString); // check the magic again if (Q_UNLIKELY(header->magic != my_magic)) - return ns(QString(), library, errorString); + return notfound(QString(), errorString); } else { header = reinterpret_cast<const my_mach_header *>(m_s); fat = 0; // check magic if (header->magic != my_magic) - return ns(QLibrary::tr("invalid magic %1").arg(qFromBigEndian(header->magic), 8, 16, QLatin1Char('0')), - library, errorString); + return notfound(QLibrary::tr("invalid magic %1").arg(qFromBigEndian(header->magic), + 8, 16, '0'_L1), + errorString); } - // from this point on, fdlen is specific to this architecture // from this point on, everything is in host byte order - *pos = reinterpret_cast<const char *>(header) - m_s; // (re-)check the CPU type // ### should we check the CPU subtype? Maybe on ARM? if (header->cputype != my_cputype) { if (fat) - return ns(QString(), library, errorString); - return ns(QLibrary::tr("wrong architecture"), library, errorString); + return notfound(QString(), errorString); + return notfound(QLibrary::tr("wrong architecture"), errorString); } // check the file type if (Q_UNLIKELY(header->filetype != MH_BUNDLE && header->filetype != MH_DYLIB)) - return ns(QLibrary::tr("not a dynamic library"), library, errorString); + return notfound(QLibrary::tr("not a dynamic library"), errorString); // find the __TEXT segment, "qtmetadata" section const my_segment_command *seg = reinterpret_cast<const my_segment_command *>(header + 1); @@ -171,14 +154,14 @@ int QMachOParser::parse(const char *m_s, ulong fdlen, const QString &library, QS // We're sure that the file size includes at least one load command // but we have to check anyway if we're past the first if (Q_UNLIKELY(fdlen < minsize + sizeof(load_command))) - return ns(QString(), library, errorString); + return notfound(QString(), errorString); // cmdsize can't be trusted until validated // so check it against fdlen anyway // (these are unsigned operations, with overflow behavior specified in the standard) minsize += seg->cmdsize; if (Q_UNLIKELY(fdlen < minsize) || Q_UNLIKELY(fdlen < seg->cmdsize)) - return ns(QString(), library, errorString); + return notfound(QString(), errorString); const uint32_t MyLoadCommand = sizeof(void *) > 4 ? LC_SEGMENT_64 : LC_SEGMENT; if (seg->cmd != MyLoadCommand) @@ -195,11 +178,25 @@ int QMachOParser::parse(const char *m_s, ulong fdlen, const QString &library, QS // found it! if (Q_UNLIKELY(fdlen < sect[j].offset) || Q_UNLIKELY(fdlen < sect[j].size) || Q_UNLIKELY(fdlen < sect[j].offset + sect[j].size)) - return ns(QString(), library, errorString); + return notfound(QString(), errorString); + + if (sect[j].size < sizeof(QPluginMetaData::MagicHeader)) + return notfound(QLibrary::tr(".qtmetadata section is too small"), errorString); + + const bool binaryIsEncrypted = isEncrypted(header); + qsizetype pos = reinterpret_cast<const char *>(header) - m_s + sect[j].offset; - *pos += sect[j].offset; - *sectionlen = sect[j].size; - return QtMetaDataSection; + // We can not read the section data of encrypted libraries until they + // have been dlopened(), so skip validity check if that's the case. + if (IncludeValidityChecks && !binaryIsEncrypted) { + QByteArrayView expectedMagic = QByteArrayView::fromArray(QPluginMetaData::MagicString); + QByteArrayView actualMagic = QByteArrayView(m_s + pos, expectedMagic.size()); + if (expectedMagic != actualMagic) + return notfound(QLibrary::tr(".qtmetadata section has incorrect magic"), errorString); + } + + pos += sizeof(QPluginMetaData::MagicString); + return { pos, qsizetype(sect[j].size - sizeof(QPluginMetaData::MagicString)), binaryIsEncrypted }; } } @@ -207,13 +204,9 @@ int QMachOParser::parse(const char *m_s, ulong fdlen, const QString &library, QS seg = reinterpret_cast<const my_segment_command *>(reinterpret_cast<const char *>(seg) + seg->cmdsize); } -// // No Qt section was found, but at least we know that where the proper architecture's boundaries are -// return NoQtSection; - if (errorString) - *errorString = QLibrary::tr("'%1' is not a Qt plugin").arg(library); - return NotSuitable; + // No .qtmetadata section was found + *errorString = QLibrary::tr("'%1' is not a Qt plugin").arg(*errorString); + return {}; } QT_END_NAMESPACE - -#endif diff --git a/src/corelib/plugin/qmachparser_p.h b/src/corelib/plugin/qmachparser_p.h index 290b68876f..85a174e66e 100644 --- a/src/corelib/plugin/qmachparser_p.h +++ b/src/corelib/plugin/qmachparser_p.h @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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) 2016 Intel Corporation. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #ifndef QMACHPARSER_P_H #define QMACHPARSER_P_H @@ -51,8 +15,7 @@ // We mean it. // -#include <qendian.h> -#include <private/qglobal_p.h> +#include "qlibrary_p.h" QT_REQUIRE_CONFIG(library); @@ -66,12 +29,11 @@ class QLibraryPrivate; class Q_AUTOTEST_EXPORT QMachOParser { public: - enum { QtMetaDataSection, NoQtSection, NotSuitable }; - static int parse(const char *m_s, ulong fdlen, const QString &library, QString *errorString, qsizetype *pos, qsizetype *sectionlen); + static QLibraryScanResult parse(const char *m_s, ulong fdlen, QString *errorString); }; QT_END_NAMESPACE -#endif // defined(Q_OF_ELF) && defined(Q_CC_GNU) +#endif // defined(Q_OF_MACH_O) #endif // QMACHPARSER_P_H diff --git a/src/corelib/plugin/qplugin.h b/src/corelib/plugin/qplugin.h index 411edba792..909c8acdcc 100644 --- a/src/corelib/plugin/qplugin.h +++ b/src/corelib/plugin/qplugin.h @@ -1,52 +1,23 @@ -/**************************************************************************** -** -** 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 #ifndef QPLUGIN_H #define QPLUGIN_H +#if 0 +#pragma qt_class(QtPlugin) +#endif + #include <QtCore/qobject.h> #include <QtCore/qpointer.h> #include <QtCore/qjsonobject.h> +#include <QtCore/q20algorithm.h> + QT_BEGIN_NAMESPACE +// Used up to Qt 6.2 inline constexpr unsigned char qPluginArchRequirements() { return 0 @@ -65,7 +36,71 @@ inline constexpr unsigned char qPluginArchRequirements() typedef QObject *(*QtPluginInstanceFunction)(); struct QPluginMetaData { - const uchar *data; + static constexpr quint8 CurrentMetaDataVersion = 1; + static constexpr char MagicString[] = { + 'Q', 'T', 'M', 'E', 'T', 'A', 'D', 'A', 'T', 'A', ' ', '!' + }; + + template <size_t OSize, typename OO, size_t ISize, typename II> + static constexpr void copy(OO (&out)[OSize], II (&in)[ISize]) + { + static_assert(OSize <= ISize, "Output would not be fully initialized"); + q20::copy_n(in, OSize, out); + } + + static constexpr quint8 archRequirements() + { + quint8 v = 0; +#if defined(__AVX512F__) + v = 4; // x86-64-v4: AVX512F, AVX512BW, AVX512CD, AVX512DQ and AVX512VL +#elif defined(__AVX__) || defined(__BMI__) || defined(__BMI2__) || defined(__MOVBE__) + v = 3; // x86-64-v3: AVX, AVX2, BMI1, BMI2, F16C, FMA, LZCNT, MOVBE, XSAVE +#elif defined(__SSE3__) + v = 2; // x86-64-v2: POPCNT, SSE3, SSSE3, SSE4.1 and SSE4.2. +#elif defined(__SSE__) || defined(__MMX___) + v = 1; // x86-64 baseline: SSE and SSE2 +#endif +#ifndef QT_NO_DEBUG + v |= 0x80; +#endif + return v; + } + + struct Header { + quint8 version = CurrentMetaDataVersion; + quint8 qt_major_version = QT_VERSION_MAJOR; + quint8 qt_minor_version = QT_VERSION_MINOR; + quint8 plugin_arch_requirements = archRequirements(); + }; + static_assert(alignof(Header) == 1, "Alignment of header incorrect with this compiler"); + + struct MagicHeader { + char magic[sizeof(QPluginMetaData::MagicString)] = {}; + constexpr MagicHeader() { copy(magic, QPluginMetaData::MagicString); } + Header header = {}; + }; + static_assert(alignof(MagicHeader) == 1, "Alignment of header incorrect with this compiler"); + + struct ElfNoteHeader { + static constexpr quint32 NoteType = 0x74510001; + static constexpr char NoteName[] = "qt-project!"; + + // ELF note header + quint32 n_namesz = sizeof(name); + quint32 n_descsz; + quint32 n_type = NoteType; + char name[sizeof(NoteName)] = {}; + + // payload + alignas(void *) // mandatory alignment as per ELF note requirements + Header header = {}; + constexpr ElfNoteHeader(quint32 payloadSize) : n_descsz(sizeof(header) + payloadSize) + { QPluginMetaData::copy(name, NoteName); } + }; + static_assert(alignof(ElfNoteHeader) == alignof(void*), "Alignment of header incorrect with this compiler"); + static_assert((sizeof(ElfNoteHeader::name) % 4) == 0, "ELF note name length not a multiple of 4"); + + const void *data; size_t size; }; typedef QPluginMetaData (*QtPluginMetaDataFunction)(); @@ -83,6 +118,7 @@ public: private: qsizetype rawMetaDataSize; const void *rawMetaData; + friend class QFactoryLoader; }; Q_DECLARE_TYPEINFO(QStaticPlugin, Q_PRIMITIVE_TYPE); @@ -91,8 +127,7 @@ void Q_CORE_EXPORT qRegisterStaticPluginFunction(QStaticPlugin staticPlugin); #if defined(Q_OF_ELF) || (defined(Q_OS_WIN) && (defined (Q_CC_GNU) || defined(Q_CC_CLANG))) # define QT_PLUGIN_METADATA_SECTION \ __attribute__ ((section (".qtmetadata"))) __attribute__((used)) -#elif defined(Q_OS_MAC) -// TODO: Implement section parsing on Mac +#elif defined(Q_OS_DARWIN) # define QT_PLUGIN_METADATA_SECTION \ __attribute__ ((section ("__TEXT,qtmetadata"))) __attribute__((used)) #elif defined(Q_CC_MSVC) @@ -104,6 +139,57 @@ void Q_CORE_EXPORT qRegisterStaticPluginFunction(QStaticPlugin staticPlugin); # define QT_PLUGIN_METADATA_SECTION #endif +// Since Qt 6.3 +template <auto (&PluginMetaData)> class QPluginMetaDataV2 +{ + struct ElfNotePayload : QPluginMetaData::ElfNoteHeader { + static constexpr size_t HeaderOffset = offsetof(QPluginMetaData::ElfNoteHeader, header); + quint8 payload[sizeof(PluginMetaData)] = {}; + constexpr ElfNotePayload() : ElfNoteHeader(sizeof(PluginMetaData)) + { QPluginMetaData::copy(payload, PluginMetaData); } + }; + + struct RegularPayload : QPluginMetaData::MagicHeader { + static constexpr size_t HeaderOffset = offsetof(QPluginMetaData::MagicHeader, header); + quint8 payload[sizeof(PluginMetaData)] = {}; + constexpr RegularPayload() { QPluginMetaData::copy(payload, PluginMetaData); } + }; + + struct StaticPayload { + static constexpr size_t HeaderOffset = 0; + QPluginMetaData::Header header = {}; + quint8 payload[sizeof(PluginMetaData)] = {}; + constexpr StaticPayload() { QPluginMetaData::copy(payload, PluginMetaData); } + }; + +#if defined(QT_STATICPLUGIN) +# define QT_PLUGIN_METADATAV2_SECTION + using Payload = StaticPayload; +#elif defined(Q_OF_ELF) +# ifdef Q_CC_CLANG +# define QT_PLUGIN_METADATAV2_SECTION \ + __attribute__((section(".note.qt.metadata"), used, aligned(alignof(void *)), \ + no_sanitize("address"))) +# else +# define QT_PLUGIN_METADATAV2_SECTION \ + __attribute__((section(".note.qt.metadata"), used, aligned(alignof(void *)))) +# endif + using Payload = ElfNotePayload; +#else +# define QT_PLUGIN_METADATAV2_SECTION QT_PLUGIN_METADATA_SECTION + using Payload = RegularPayload; +#endif + + Payload payload = {}; + +public: + operator QPluginMetaData() const + { + Q_ASSERT(reinterpret_cast<const char *>(&payload) + Payload::HeaderOffset == + reinterpret_cast<const char *>(&payload.header)); + return { &payload.header, sizeof(payload) - Payload::HeaderOffset }; + } +}; #define Q_IMPORT_PLUGIN(PLUGIN) \ extern const QT_PREPEND_NAMESPACE(QStaticPlugin) qt_static_plugin_##PLUGIN(); \ @@ -134,25 +220,38 @@ void Q_CORE_EXPORT qRegisterStaticPluginFunction(QStaticPlugin staticPlugin); } #if defined(QT_STATICPLUGIN) +# define QT_MOC_EXPORT_PLUGIN_COMMON(PLUGINCLASS, MANGLEDNAME) \ + static QT_PREPEND_NAMESPACE(QObject) *qt_plugin_instance_##MANGLEDNAME() \ + Q_PLUGIN_INSTANCE(PLUGINCLASS) \ + const QT_PREPEND_NAMESPACE(QStaticPlugin) qt_static_plugin_##MANGLEDNAME() \ + { return { qt_plugin_instance_##MANGLEDNAME, qt_plugin_query_metadata_##MANGLEDNAME}; } \ + /**/ # define QT_MOC_EXPORT_PLUGIN(PLUGINCLASS, PLUGINCLASSNAME) \ - static QT_PREPEND_NAMESPACE(QObject) *qt_plugin_instance_##PLUGINCLASSNAME() \ - Q_PLUGIN_INSTANCE(PLUGINCLASS) \ static QPluginMetaData qt_plugin_query_metadata_##PLUGINCLASSNAME() \ { return { qt_pluginMetaData_##PLUGINCLASSNAME, sizeof qt_pluginMetaData_##PLUGINCLASSNAME }; } \ - const QT_PREPEND_NAMESPACE(QStaticPlugin) qt_static_plugin_##PLUGINCLASSNAME() { \ - return { qt_plugin_instance_##PLUGINCLASSNAME, qt_plugin_query_metadata_##PLUGINCLASSNAME}; \ - } + QT_MOC_EXPORT_PLUGIN_COMMON(PLUGINCLASS, PLUGINCLASSNAME) +# define QT_MOC_EXPORT_PLUGIN_V2(PLUGINCLASS, MANGLEDNAME, MD) \ + static QT_PREPEND_NAMESPACE(QPluginMetaData) qt_plugin_query_metadata_##MANGLEDNAME() \ + { static constexpr QPluginMetaDataV2<MD> md{}; return md; } \ + QT_MOC_EXPORT_PLUGIN_COMMON(PLUGINCLASS, MANGLEDNAME) #else +# define QT_MOC_EXPORT_PLUGIN_COMMON(PLUGINCLASS, MANGLEDNAME) \ + extern "C" Q_DECL_EXPORT QT_PREPEND_NAMESPACE(QObject) *qt_plugin_instance() \ + Q_PLUGIN_INSTANCE(PLUGINCLASS) \ + /**/ # define QT_MOC_EXPORT_PLUGIN(PLUGINCLASS, PLUGINCLASSNAME) \ extern "C" Q_DECL_EXPORT \ QPluginMetaData qt_plugin_query_metadata() \ { return { qt_pluginMetaData_##PLUGINCLASSNAME, sizeof qt_pluginMetaData_##PLUGINCLASSNAME }; } \ - extern "C" Q_DECL_EXPORT QT_PREPEND_NAMESPACE(QObject) *qt_plugin_instance() \ - Q_PLUGIN_INSTANCE(PLUGINCLASS) + QT_MOC_EXPORT_PLUGIN_COMMON(PLUGINCLASS, PLUGINCLASSNAME) +# define QT_MOC_EXPORT_PLUGIN_V2(PLUGINCLASS, MANGLEDNAME, MD) \ + extern "C" Q_DECL_EXPORT QT_PREPEND_NAMESPACE(QPluginMetaData) qt_plugin_query_metadata_v2()\ + { static constexpr QT_PLUGIN_METADATAV2_SECTION QPluginMetaDataV2<MD> md{}; return md; } \ + QT_MOC_EXPORT_PLUGIN_COMMON(PLUGINCLASS, MANGLEDNAME) #endif #define Q_EXPORT_PLUGIN(PLUGIN) \ diff --git a/src/corelib/plugin/qplugin.qdoc b/src/corelib/plugin/qplugin.qdoc index 968fb4820a..0ca248a548 100644 --- a/src/corelib/plugin/qplugin.qdoc +++ b/src/corelib/plugin/qplugin.qdoc @@ -1,32 +1,9 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the documentation of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:FDL$ -** 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 Free Documentation License Usage -** Alternatively, this file may be used under the terms of the GNU Free -** Documentation License version 1.3 as published by the Free Software -** Foundation and appearing in the file included in the packaging of -** this file. Please review the following information to ensure -** the GNU Free Documentation License version 1.3 requirements -** will be met: https://www.gnu.org/licenses/fdl-1.3.html. -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only /*! \headerfile <QtPlugin> + \inmodule QtCore \title Defining Plugins \keyword qtplugin-defining-plugins \ingroup plugins @@ -42,13 +19,10 @@ This macro associates the given \a Identifier (a string literal) to the interface class called \a ClassName. The \a Identifier must - be unique. For example: - - \snippet plugandpaint/app/interfaces.h 3 + be unique. This macro is normally used right after the class definition for - \a ClassName, in a header file. See the - \l{tools/plugandpaint/app}{Plug & Paint} example for details. + \a ClassName, in a header file. If you want to use Q_DECLARE_INTERFACE with interface classes declared in a namespace then you have to make sure the Q_DECLARE_INTERFACE @@ -76,8 +50,6 @@ \snippet code/doc_src_qplugin.cpp 1 - See the \l{tools/plugandpaint/app}{Plug & Paint} example for details. - Note that the class this macro appears on must be default-constructible. FILE is optional and points to a json file. @@ -105,11 +77,9 @@ \snippet code/doc_src_qplugin.cpp 2 Static plugins must also be included by the linker when your - application is built. For Qt's predefined plugins, - you can use the \c QTPLUGIN to add - the required plugins to your build. For example: + application is built. See \l{Static Plugins} for more information + on this. - \snippet code/doc_src_qplugin.pro 3 - \sa {Static Plugins}, {How to Create Qt Plugins}, {qmake-getting-started}{Getting Started with qmake} + \sa {Static Plugins}, {How to Create Qt Plugins} */ diff --git a/src/corelib/plugin/qplugin_p.h b/src/corelib/plugin/qplugin_p.h index ce45ebf700..534ca569df 100644 --- a/src/corelib/plugin/qplugin_p.h +++ b/src/corelib/plugin/qplugin_p.h @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ +// Copyright (C) 2018 Intel Corporation. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #ifndef QPLUGIN_P_H #define QPLUGIN_P_H @@ -61,7 +25,8 @@ enum class QtPluginMetaDataKeys { IID, ClassName, MetaData, - URI + URI, + IsDebug, }; // F(IntKey, StringKey, Description) @@ -70,7 +35,63 @@ enum class QtPluginMetaDataKeys { F(QtPluginMetaDataKeys::IID, "IID", "Plugin's Interface ID") \ F(QtPluginMetaDataKeys::ClassName, "className", "Plugin class name") \ F(QtPluginMetaDataKeys::MetaData, "MetaData", "Other meta data") \ - F(QtPluginMetaDataKeys::URI, "URI", "Plugin URI") + F(QtPluginMetaDataKeys::URI, "URI", "Plugin URI") \ + /* not output by moc in CBOR */ \ + F(QtPluginMetaDataKeys::QtVersion, "version", "Qt version") \ + F(QtPluginMetaDataKeys::Requirements, "archlevel", "Architectural level") \ + F(QtPluginMetaDataKeys::IsDebug, "debug", "Debug-mode plugin") \ + /**/ + +namespace { +struct DecodedArchRequirements +{ + quint8 level; + bool isDebug; + friend constexpr bool operator==(DecodedArchRequirements r1, DecodedArchRequirements r2) + { + return r1.level == r2.level && r1.isDebug == r2.isDebug; + } +}; + +static constexpr DecodedArchRequirements decodeVersion0ArchRequirements(quint8 value) +{ + // see qPluginArchRequirements() and QPluginMetaDataV2::archRequirements() + DecodedArchRequirements r = {}; +#ifdef Q_PROCESSOR_X86 + if (value & 4) + r.level = 4; // AVX512F -> x86-64-v4 + else if (value & 2) + r.level = 3; // AVX2 -> x86-64-v3 +#endif + if (value & 1) + r.isDebug = true; + return r; +} +// self checks +static_assert(decodeVersion0ArchRequirements(0) == DecodedArchRequirements{ 0, false }); +static_assert(decodeVersion0ArchRequirements(1) == DecodedArchRequirements{ 0, true }); +#ifdef Q_PROCESSOR_X86 +static_assert(decodeVersion0ArchRequirements(2) == DecodedArchRequirements{ 3, false }); +static_assert(decodeVersion0ArchRequirements(3) == DecodedArchRequirements{ 3, true }); +static_assert(decodeVersion0ArchRequirements(4) == DecodedArchRequirements{ 4, false }); +static_assert(decodeVersion0ArchRequirements(5) == DecodedArchRequirements{ 4, true }); +#endif + +static constexpr DecodedArchRequirements decodeVersion1ArchRequirements(quint8 value) +{ + return { quint8(value & 0x7f), bool(value & 0x80) }; +} +// self checks +static_assert(decodeVersion1ArchRequirements(0) == DecodedArchRequirements{ 0, false }); +static_assert(decodeVersion1ArchRequirements(0x80) == DecodedArchRequirements{ 0, true }); +#ifdef Q_PROCESSOR_X86 +static_assert(decodeVersion1ArchRequirements(1) == DecodedArchRequirements{ 1, false }); +static_assert(decodeVersion1ArchRequirements(3) == DecodedArchRequirements{ 3, false}); +static_assert(decodeVersion1ArchRequirements(4) == DecodedArchRequirements{ 4, false }); +static_assert(decodeVersion1ArchRequirements(0x82) == DecodedArchRequirements{ 2, true }); +static_assert(decodeVersion1ArchRequirements(0x84) == DecodedArchRequirements{ 4, true }); +#endif +} // unnamed namespace QT_END_NAMESPACE diff --git a/src/corelib/plugin/qpluginloader.cpp b/src/corelib/plugin/qpluginloader.cpp index 131506f73e..03b8cfbb84 100644 --- a/src/corelib/plugin/qpluginloader.cpp +++ b/src/corelib/plugin/qpluginloader.cpp @@ -1,55 +1,24 @@ -/**************************************************************************** -** -** 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" - -#include "qplugin.h" -#include "qcoreapplication.h" +// Copyright (C) 2016 The Qt Company Ltd. +// Copyright (C) 2018 Intel Corporation. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + #include "qpluginloader.h" -#include <qfileinfo.h> -#include "qfactoryloader_p.h" + +#include "qcoreapplication.h" #include "qdebug.h" #include "qdir.h" +#include "qfactoryloader_p.h" +#include "qfileinfo.h" +#include "qjsondocument.h" + +#if QT_CONFIG(library) +# include "qlibrary_p.h" +#endif QT_BEGIN_NAMESPACE +using namespace Qt::StringLiterals; + #if QT_CONFIG(library) /*! @@ -102,9 +71,11 @@ QT_BEGIN_NAMESPACE link to plugins statically. You can use QLibrary if you need to load dynamic libraries in a statically linked application. - \sa QLibrary, {Plug & Paint Example} + \sa QLibrary */ +static constexpr QLibrary::LoadHints defaultLoadHints = QLibrary::PreventUnloadHint; + /*! Constructs a plugin loader with the given \a parent. */ @@ -128,7 +99,7 @@ QPluginLoader::QPluginLoader(const QString &fileName, QObject *parent) : QObject(parent), d(nullptr), did_load(false) { setFileName(fileName); - setLoadHints(QLibrary::PreventUnloadHint); + setLoadHints(defaultLoadHints); } /*! @@ -186,7 +157,7 @@ QJsonObject QPluginLoader::metaData() const { if (!d) return QJsonObject(); - return d->metaData; + return d->metaData.toJson(); } /*! @@ -264,12 +235,10 @@ static QString locatePlugin(const QString& fileName) suffixes.prepend(QString()); // Split up "subdir/filename" - const int slash = fileName.lastIndexOf(QLatin1Char('/')); + const qsizetype slash = fileName.lastIndexOf(u'/'); const auto baseName = QStringView{fileName}.mid(slash + 1); const auto basePath = isAbsolute ? QStringView() : QStringView{fileName}.left(slash + 1); // keep the '/' - const bool debug = qt_debug_component(); - QStringList paths; if (isAbsolute) { paths.append(fileName.left(slash)); // don't include the '/' @@ -277,29 +246,26 @@ static QString locatePlugin(const QString& fileName) paths = QCoreApplication::libraryPaths(); } - for (const QString &path : qAsConst(paths)) { - for (const QString &prefix : qAsConst(prefixes)) { - for (const QString &suffix : qAsConst(suffixes)) { + for (const QString &path : std::as_const(paths)) { + for (const QString &prefix : std::as_const(prefixes)) { + for (const QString &suffix : std::as_const(suffixes)) { #ifdef Q_OS_ANDROID { QString pluginPath = basePath + prefix + baseName + suffix; - const QString fn = path + QLatin1String("/lib") + pluginPath.replace(QLatin1Char('/'), QLatin1Char('_')); - if (debug) - qDebug() << "Trying..." << fn; + const QString fn = path + "/lib"_L1 + pluginPath.replace(u'/', u'_'); + qCDebug(qt_lcDebugPlugins) << "Trying..." << fn; if (QFileInfo(fn).isFile()) return fn; } #endif - const QString fn = path + QLatin1Char('/') + basePath + prefix + baseName + suffix; - if (debug) - qDebug() << "Trying..." << fn; + const QString fn = path + u'/' + basePath + prefix + baseName + suffix; + qCDebug(qt_lcDebugPlugins) << "Trying..." << fn; if (QFileInfo(fn).isFile()) return fn; } } } - if (debug) - qDebug() << fileName << "not found"; + qCDebug(qt_lcDebugPlugins) << fileName << "not found"; return QString(); } #endif @@ -329,7 +295,7 @@ static QString locatePlugin(const QString& fileName) void QPluginLoader::setFileName(const QString &fileName) { #if defined(QT_SHARED) - QLibrary::LoadHints lh = QLibrary::PreventUnloadHint; + QLibrary::LoadHints lh = defaultLoadHints; if (d) { lh = d->loadHints(); d->release(); @@ -344,11 +310,8 @@ void QPluginLoader::setFileName(const QString &fileName) d->updatePluginState(); #else - if (qt_debug_component()) { - qWarning("Cannot load %s into a statically linked Qt library.", - (const char *)QFile::encodeName(fileName)); - } - Q_UNUSED(fileName); + qCWarning(qt_lcDebugPlugins, "Cannot load '%ls' into a statically linked Qt library.", + qUtf16Printable(fileName)); #endif } @@ -386,15 +349,21 @@ QString QPluginLoader::errorString() const void QPluginLoader::setLoadHints(QLibrary::LoadHints loadHints) { if (!d) { - d = QLibraryPrivate::findOrCreate(QString()); // ugly, but we need a d-ptr + d = QLibraryPrivate::findOrCreate({}, {}, loadHints); // ugly, but we need a d-ptr d->errorString.clear(); + } else { + d->setLoadHints(loadHints); } - d->setLoadHints(loadHints); } QLibrary::LoadHints QPluginLoader::loadHints() const { - return d ? d->loadHints() : QLibrary::LoadHints(); + // Not having a d-pointer means that the user hasn't called + // setLoadHints() / setFileName() yet. In setFileName() we will + // then force defaultLoadHints on loading, so we must return them + // from here as well. + + return d ? d->loadHints() : defaultLoadHints; } #endif // QT_CONFIG(library) @@ -411,7 +380,19 @@ Q_GLOBAL_STATIC(StaticPluginList, staticPluginList) */ void Q_CORE_EXPORT qRegisterStaticPluginFunction(QStaticPlugin plugin) { - staticPluginList()->append(plugin); + // using operator* because we shouldn't be registering plugins while + // unloading the application! + StaticPluginList &plugins = *staticPluginList; + + // insert the plugin in the list, sorted by address, so we can detect + // duplicate registrations + auto comparator = [=](const QStaticPlugin &p1, const QStaticPlugin &p2) { + using Less = std::less<decltype(plugin.instance)>; + return Less{}(p1.instance, p2.instance); + }; + auto pos = std::lower_bound(plugins.constBegin(), plugins.constEnd(), plugin, comparator); + if (pos == plugins.constEnd() || pos->instance != plugin.instance) + plugins.insert(pos, plugin); } /*! @@ -422,12 +403,11 @@ void Q_CORE_EXPORT qRegisterStaticPluginFunction(QStaticPlugin plugin) QObjectList QPluginLoader::staticInstances() { QObjectList instances; - const StaticPluginList *plugins = staticPluginList(); - if (plugins) { - const int numPlugins = plugins->size(); - instances.reserve(numPlugins); - for (int i = 0; i < numPlugins; ++i) - instances += plugins->at(i).instance(); + if (staticPluginList.exists()) { + const StaticPluginList &plugins = *staticPluginList; + instances.reserve(plugins.size()); + for (QStaticPlugin plugin : plugins) + instances += plugin.instance(); } return instances; } @@ -478,13 +458,10 @@ QList<QStaticPlugin> QPluginLoader::staticPlugins() */ QJsonObject QStaticPlugin::metaData() const { - auto ptr = static_cast<const char *>(rawMetaData); - - QString errMsg; - QJsonDocument doc = qJsonFromRawLibraryMetaData(ptr, rawMetaDataSize, &errMsg); - Q_ASSERT(doc.isObject()); - Q_ASSERT(errMsg.isEmpty()); - return doc.object(); + QByteArrayView data(static_cast<const char *>(rawMetaData), rawMetaDataSize); + QPluginParsedMetaData parsed(data); + Q_ASSERT(!parsed.isError()); + return parsed.toJson(); } QT_END_NAMESPACE diff --git a/src/corelib/plugin/qpluginloader.h b/src/corelib/plugin/qpluginloader.h index b242de2991..e541994e62 100644 --- a/src/corelib/plugin/qpluginloader.h +++ b/src/corelib/plugin/qpluginloader.h @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** 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) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #ifndef QPLUGINLOADER_H #define QPLUGINLOADER_H diff --git a/src/corelib/plugin/qsystemlibrary.cpp b/src/corelib/plugin/qsystemlibrary.cpp index 593b0c3be7..d3dff226d9 100644 --- a/src/corelib/plugin/qsystemlibrary.cpp +++ b/src/corelib/plugin/qsystemlibrary.cpp @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** 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) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qsystemlibrary_p.h" #include <QtCore/qvarlengtharray.h> @@ -72,52 +36,56 @@ QT_BEGIN_NAMESPACE +using namespace Qt::StringLiterals; + #if !defined(QT_BOOTSTRAPPED) extern QString qAppFileName(); #endif static QString qSystemDirectory() { - QVarLengthArray<wchar_t, MAX_PATH> fullPath; - - UINT retLen = ::GetSystemDirectory(fullPath.data(), MAX_PATH); - if (retLen > MAX_PATH) { - fullPath.resize(retLen); - retLen = ::GetSystemDirectory(fullPath.data(), retLen); - } - // in some rare cases retLen might be 0 - return QString::fromWCharArray(fullPath.constData(), int(retLen)); + static const QString result = []() -> QString { + QVarLengthArray<wchar_t, MAX_PATH> fullPath = {}; + UINT retLen = ::GetSystemDirectoryW(fullPath.data(), MAX_PATH); + if (retLen > MAX_PATH) { + fullPath.resize(retLen); + retLen = ::GetSystemDirectoryW(fullPath.data(), retLen); + } + // in some rare cases retLen might be 0 + return QString::fromWCharArray(fullPath.constData(), int(retLen)); + }(); + return result; } HINSTANCE QSystemLibrary::load(const wchar_t *libraryName, bool onlySystemDirectory /* = true */) { + if (onlySystemDirectory) + return ::LoadLibraryExW(libraryName, nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); + QStringList searchOrder; #if !defined(QT_BOOTSTRAPPED) - if (!onlySystemDirectory) - searchOrder << QFileInfo(qAppFileName()).path(); + searchOrder << QFileInfo(qAppFileName()).path(); #endif searchOrder << qSystemDirectory(); - if (!onlySystemDirectory) { - const QString PATH(QLatin1String(qgetenv("PATH").constData())); - searchOrder << PATH.split(QLatin1Char(';'), Qt::SkipEmptyParts); - } - QString fileName = QString::fromWCharArray(libraryName); - fileName.append(QLatin1String(".dll")); + const QString PATH(QLatin1StringView(qgetenv("PATH"))); + searchOrder << PATH.split(u';', Qt::SkipEmptyParts); + + const QString fileName = QString::fromWCharArray(libraryName); // Start looking in the order specified for (int i = 0; i < searchOrder.count(); ++i) { QString fullPathAttempt = searchOrder.at(i); - if (!fullPathAttempt.endsWith(QLatin1Char('\\'))) { - fullPathAttempt.append(QLatin1Char('\\')); + if (!fullPathAttempt.endsWith(u'\\')) { + fullPathAttempt.append(u'\\'); } fullPathAttempt.append(fileName); HINSTANCE inst = ::LoadLibrary(reinterpret_cast<const wchar_t *>(fullPathAttempt.utf16())); - if (inst != 0) + if (inst != nullptr) return inst; } - return 0; + return nullptr; } QT_END_NAMESPACE diff --git a/src/corelib/plugin/qsystemlibrary_p.h b/src/corelib/plugin/qsystemlibrary_p.h index b27e746fd3..0b4ad7dbc2 100644 --- a/src/corelib/plugin/qsystemlibrary_p.h +++ b/src/corelib/plugin/qsystemlibrary_p.h @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** 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) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #ifndef QSYSTEMLIBRARY_P_H #define QSYSTEMLIBRARY_P_H @@ -64,27 +28,23 @@ public: explicit QSystemLibrary(const QString &libraryName) { m_libraryName = libraryName; - m_handle = 0; - m_didLoad = false; } explicit QSystemLibrary(const wchar_t *libraryName) { m_libraryName = QString::fromWCharArray(libraryName); - m_handle = 0; - m_didLoad = false; } bool load(bool onlySystemDirectory = true) { m_handle = load((const wchar_t *)m_libraryName.utf16(), onlySystemDirectory); m_didLoad = true; - return (m_handle != 0); + return (m_handle != nullptr); } bool isLoaded() { - return (m_handle != 0); + return (m_handle != nullptr); } QFunctionPointer resolve(const char *symbol) @@ -92,7 +52,7 @@ public: if (!m_didLoad) load(); if (!m_handle) - return 0; + return nullptr; return QFunctionPointer(GetProcAddress(m_handle, symbol)); } @@ -102,10 +62,11 @@ public: } static Q_CORE_EXPORT HINSTANCE load(const wchar_t *lpFileName, bool onlySystemDirectory = true); + private: - HINSTANCE m_handle; - QString m_libraryName; - bool m_didLoad; + HINSTANCE m_handle = nullptr; + QString m_libraryName = {}; + bool m_didLoad = false; }; QT_END_NAMESPACE diff --git a/src/corelib/plugin/quuid.cpp b/src/corelib/plugin/quuid.cpp index 7f7d537fe4..9c7216c3c5 100644 --- a/src/corelib/plugin/quuid.cpp +++ b/src/corelib/plugin/quuid.cpp @@ -1,42 +1,6 @@ -/**************************************************************************** -** -** Copyright (C) 2020 The Qt Company Ltd. -** Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Marc Mutz <marc.mutz@kdab.com> -** 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) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Marc Mutz <marc.mutz@kdab.com> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "quuid.h" @@ -49,6 +13,9 @@ QT_BEGIN_NAMESPACE +// ensure QList of this is efficient +static_assert(QTypeInfo<QUuid::Id128Bytes>::isRelocatable); + // 16 bytes (a uint, two shorts and a uchar[8]), each represented by two hex // digits; plus four dashes and a pair of enclosing brace: 16*2 + 4 + 2 = 38. enum { MaxStringUuidLength = 38 }; @@ -323,6 +290,125 @@ static QUuid createFromName(const QUuid &ns, const QByteArray &baseData, QCrypto */ /*! + \class QUuid::Id128Bytes + \inmodule QtCore + \since 6.6 + + This trivial structure is 128 bits (16 bytes) in size and holds the binary + representation of a UUID. Applications can \c{memcpy()} its contents to and + from many other libraries' UUID or GUID structures that take 128-bit + values. +*/ + +/*! + \fn QUuid::Id128Bytes qFromBigEndian(QUuid::Id128Bytes src) + \since 6.6 + \relates QUuid::Id128Bytes + \overload + + Converts \a src from big-endian byte order and returns the struct holding + the binary representation of UUID in host byte order. + + \sa <QtEndian> +*/ + +/*! + \fn QUuid::Id128Bytes qFromLittleEndian(QUuid::Id128Bytes src) + \since 6.6 + \relates QUuid::Id128Bytes + \overload + + Converts \a src from little-endian byte order and returns the struct holding + the binary representation of UUID in host byte order. + + \sa <QtEndian> +*/ + +/*! + \fn QUuid::Id128Bytes qToBigEndian(QUuid::Id128Bytes src) + \since 6.6 + \relates QUuid::Id128Bytes + \overload + + Converts \a src from host byte order and returns the struct holding the + binary representation of UUID in big-endian byte order. + + \sa <QtEndian> +*/ + +/*! + \fn QUuid::Id128Bytes qToLittleEndian(QUuid::Id128Bytes src) + \since 6.6 + \relates QUuid::Id128Bytes + \overload + + Converts \a src from host byte order and returns the struct holding the + binary representation of UUID in little-endian byte order. + + \sa <QtEndian> +*/ + +/*! + \fn QUuid::QUuid(Id128Bytes id128, QSysInfo::Endian order) noexcept + \since 6.6 + + Creates a QUuid based on the integral \a id128 parameter. The input + \a id128 parameter is considered to have byte order \a order. + + \sa fromBytes(), toBytes(), toRfc4122(), toUInt128() +*/ + +/*! + \fn QUuid::fromUInt128(quint128 uuid, QSysInfo::Endian order) noexcept + \since 6.6 + + Creates a QUuid based on the integral \a uuid parameter. The input \a uuid + parameter is considered to have byte order \a order. + + \note This function is only present on platforms that offer a 128-bit + integer type. + + \sa toUInt128(), fromBytes(), toBytes(), toRfc4122() +*/ + +/*! + \fn quint128 QUuid::toUInt128(QSysInfo::Endian order) const noexcept + \since 6.6 + + Returns a 128-bit integer created from this QUuid on the byte order + specified by \a order. The binary content of this function is the same as + toRfc4122() if the order is QSysInfo::BigEndian. See that function for more + details. + + \note This function is only present on platforms that offer a 128-bit + integer type. + + \sa toRfc4122(), fromUInt128(), toBytes(), fromBytes(), QUuid() +*/ + +/*! + \fn QUuid::Id128Bytes QUuid::toBytes(QSysInfo::Endian order) const noexcept + \since 6.6 + + Returns a 128-bit ID created from this QUuid on the byte order specified + by \a order. The binary content of this function is the same as toRfc4122() + if the order is QSysInfo::BigEndian. See that function for more details. + + \sa toRfc4122(), fromBytes(), QUuid() +*/ + +/*! + \fn QUuid QUuid::fromBytes(const void *bytes, QSysInfo::Endian order) noexcept + \since 6.6 + + Reads 128 bits (16 bytes) from \a bytes using byte order \a order and + returns the QUuid corresponding to those bytes. This function does the same + as fromRfc4122() if the byte order \a order is QSysInfo::BigEndian. + + \sa fromRfc4122() +*/ + +/*! \fn QUuid::QUuid(const GUID &guid) Casts a Windows \a guid to a Qt QUuid. @@ -395,7 +481,7 @@ static QUuid createFromName(const QUuid &ns, const QByteArray &baseData, QCrypto public data members in QUuid. \note In Qt versions prior to 6.3, this function was an overload - set consisting of QStringView and QLatin1String instead of + set consisting of QStringView and QLatin1StringView instead of one function taking QAnyStringView. \sa toString(), QUuid() @@ -416,13 +502,13 @@ static QUuid uuidFromString(QStringView text) noexcept return _q_uuidFromHex(latin1); } -static QUuid uuidFromString(QLatin1String text) noexcept +static QUuid uuidFromString(QLatin1StringView text) noexcept { if (Q_UNLIKELY(text.size() < MaxStringUuidLength - 2 - || (text.front() == QLatin1Char('{') && text.size() < MaxStringUuidLength - 1))) { + || (text.front() == '{' && text.size() < MaxStringUuidLength - 1))) { // Too short. Don't call _q_uuidFromHex(); QL1Ss need not be NUL-terminated, // and we don't want to read trailing garbage as potentially valid data. - text = QLatin1String(); + text = QLatin1StringView(); } return _q_uuidFromHex(text.data()); } @@ -431,7 +517,7 @@ Q_ALWAYS_INLINE // can treat UTF-8 the same as Latin-1: static QUuid uuidFromString(QUtf8StringView text) noexcept { - return uuidFromString(QLatin1String(text.data(), text.size())); + return uuidFromString(QLatin1StringView(text.data(), text.size())); } QUuid QUuid::fromString(QAnyStringView text) noexcept @@ -504,32 +590,13 @@ QUuid QUuid::createUuidV5(const QUuid &ns, const QByteArray &baseData) \since 4.8 - \sa toRfc4122(), QUuid() + \sa toRfc4122(), QUuid(), fromBytes() */ QUuid QUuid::fromRfc4122(QByteArrayView bytes) noexcept { if (bytes.isEmpty() || bytes.size() != 16) return QUuid(); - - uint d1; - ushort d2, d3; - uchar d4[8]; - - const uchar *data = reinterpret_cast<const uchar *>(bytes.data()); - - d1 = qFromBigEndian<quint32>(data); - data += sizeof(quint32); - d2 = qFromBigEndian<quint16>(data); - data += sizeof(quint16); - d3 = qFromBigEndian<quint16>(data); - data += sizeof(quint16); - - for (int i = 0; i < 8; ++i) { - d4[i] = *(data); - data++; - } - - return QUuid(d1, d2, d3, d4[0], d4[1], d4[2], d4[3], d4[4], d4[5], d4[6], d4[7]); + return fromBytes(bytes.data()); } /*! @@ -659,27 +726,16 @@ QByteArray QUuid::toByteArray(QUuid::StringFormat mode) const \endtable + The bytes in the byte array returned by this function contains the same + binary content as toBytes(). + + \sa toBytes() \since 4.8 */ QByteArray QUuid::toRfc4122() const { - // we know how many bytes a UUID has, I hope :) - QByteArray bytes(16, Qt::Uninitialized); - uchar *data = reinterpret_cast<uchar *>(bytes.data()); - - qToBigEndian(data1, data); - data += sizeof(quint32); - qToBigEndian(data2, data); - data += sizeof(quint16); - qToBigEndian(data3, data); - data += sizeof(quint16); - - for (int i = 0; i < 8; ++i) { - *(data) = data4[i]; - data++; - } - - return bytes; + Id128Bytes bytes = toBytes(); + return QByteArrayView(bytes).toByteArray(); } #ifndef QT_NO_DATASTREAM @@ -689,14 +745,19 @@ QByteArray QUuid::toRfc4122() const */ QDataStream &operator<<(QDataStream &s, const QUuid &id) { - QByteArray bytes; + constexpr int NumBytes = sizeof(QUuid); + static_assert(NumBytes == 16, "Change the serialization format when this ever hits"); + char bytes[NumBytes]; if (s.byteOrder() == QDataStream::BigEndian) { - bytes = id.toRfc4122(); + const auto id128 = id.toBytes(); + static_assert(sizeof(id128) == NumBytes); + memcpy(bytes, &id128, NumBytes); } else { - // we know how many bytes a UUID has, I hope :) - bytes = QByteArray(16, Qt::Uninitialized); - uchar *data = reinterpret_cast<uchar *>(bytes.data()); + auto *data = bytes; + // for historical reasons, our little-endian serialization format + // stores each of the UUID fields in little endian, instead of storing + // a little endian Id128 qToLittleEndian(id.data1, data); data += sizeof(quint32); qToLittleEndian(id.data2, data); @@ -710,9 +771,9 @@ QDataStream &operator<<(QDataStream &s, const QUuid &id) } } - if (s.writeRawData(bytes.data(), 16) != 16) { + if (s.writeRawData(bytes, NumBytes) != NumBytes) s.setStatus(QDataStream::WriteFailed); - } + return s; } @@ -931,7 +992,7 @@ QUuid QUuid::createUuid() return result; } -#else // Q_OS_WIN +#elif !defined(QT_BOOTSTRAPPED) QUuid QUuid::createUuid() { @@ -945,7 +1006,7 @@ QUuid QUuid::createUuid() return result; } -#endif // !Q_OS_WIN +#endif // !Q_OS_WIN && !QT_BOOTSTRAPPED /*! \fn bool QUuid::operator==(const GUID &guid) const diff --git a/src/corelib/plugin/quuid.h b/src/corelib/plugin/quuid.h index 9229d5b694..7125e8e2cc 100644 --- a/src/corelib/plugin/quuid.h +++ b/src/corelib/plugin/quuid.h @@ -1,48 +1,13 @@ -/**************************************************************************** -** -** Copyright (C) 2020 The Qt Company Ltd. -** 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. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #ifndef QUUID_H #define QUUID_H +#include <QtCore/qendian.h> #include <QtCore/qstring.h> -#if defined(Q_OS_WIN) || defined(Q_CLANG_QDOC) +#if defined(Q_OS_WIN) || defined(Q_QDOC) #ifndef GUID_DEFINED #define GUID_DEFINED typedef struct _GUID @@ -55,14 +20,13 @@ typedef struct _GUID #endif #endif -#if defined(Q_OS_DARWIN) || defined(Q_CLANG_QDOC) +#if defined(Q_OS_DARWIN) || defined(Q_QDOC) Q_FORWARD_DECLARE_CF_TYPE(CFUUID); Q_FORWARD_DECLARE_OBJC_CLASS(NSUUID); #endif QT_BEGIN_NAMESPACE - class Q_CORE_EXPORT QUuid { QUuid(Qt::Initialization) {} @@ -91,31 +55,73 @@ public: Id128 = 3 }; + union alignas(16) Id128Bytes { + quint8 data[16]; + quint16 data16[8]; + quint32 data32[4]; + quint64 data64[2]; +#if defined(__SIZEOF_INT128__) +QT_WARNING_PUSH +QT_WARNING_DISABLE_GCC("-Wpedantic") // ISO C++ does not support ‘__int128’ for ‘data128’ + unsigned __int128 data128[1]; +QT_WARNING_POP +#elif defined(QT_SUPPORTS_INT128) +# error "struct QUuid::Id128Bytes should not depend on QT_SUPPORTS_INT128 for ABI reasons." +# error "Adjust the declaration of the `data128` member above so it is always defined if it's " \ + "supported by the current compiler/architecture in any configuration." +#endif + + constexpr explicit operator QByteArrayView() const noexcept + { + return QByteArrayView(data, sizeof(data)); + } + + friend constexpr Id128Bytes qbswap(Id128Bytes b) noexcept + { + // 128-bit byte swap + auto b0 = qbswap(b.data64[0]); + auto b1 = qbswap(b.data64[1]); + b.data64[0] = b1; + b.data64[1] = b0; + return b; + } + }; + constexpr QUuid() noexcept : data1(0), data2(0), data3(0), data4{0,0,0,0,0,0,0,0} {} constexpr QUuid(uint l, ushort w1, ushort w2, uchar b1, uchar b2, uchar b3, uchar b4, uchar b5, uchar b6, uchar b7, uchar b8) noexcept : data1(l), data2(w1), data3(w2), data4{b1, b2, b3, b4, b5, b6, b7, b8} {} + explicit inline QUuid(Id128Bytes id128, QSysInfo::Endian order = QSysInfo::BigEndian) noexcept; explicit QUuid(QAnyStringView string) noexcept : QUuid{fromString(string)} {} static QUuid fromString(QAnyStringView string) noexcept; -#if QT_REMOVED_SINCE(6, 3) +#if QT_CORE_REMOVED_SINCE(6, 3) explicit QUuid(const QString &); static QUuid fromString(QStringView string) noexcept; - static QUuid fromString(QLatin1String string) noexcept; + static QUuid fromString(QLatin1StringView string) noexcept; explicit QUuid(const char *); explicit QUuid(const QByteArray &); #endif QString toString(StringFormat mode = WithBraces) const; QByteArray toByteArray(StringFormat mode = WithBraces) const; + inline Id128Bytes toBytes(QSysInfo::Endian order = QSysInfo::BigEndian) const noexcept; QByteArray toRfc4122() const; -#if QT_REMOVED_SINCE(6, 3) + + static inline QUuid fromBytes(const void *bytes, QSysInfo::Endian order = QSysInfo::BigEndian); +#if QT_CORE_REMOVED_SINCE(6, 3) static QUuid fromRfc4122(const QByteArray &); #endif static QUuid fromRfc4122(QByteArrayView) noexcept; + bool isNull() const noexcept; +#ifdef QT_SUPPORTS_INT128 + static constexpr QUuid fromUInt128(quint128 uuid, QSysInfo::Endian order = QSysInfo::BigEndian) noexcept; + constexpr quint128 toUInt128(QSysInfo::Endian order = QSysInfo::BigEndian) const noexcept; +#endif + constexpr bool operator==(const QUuid &orig) const noexcept { if (data1 != orig.data1 || data2 != orig.data2 || @@ -137,7 +143,7 @@ public: bool operator<(const QUuid &other) const noexcept; bool operator>(const QUuid &other) const noexcept; -#if defined(Q_OS_WIN) || defined(Q_CLANG_QDOC) +#if defined(Q_OS_WIN) || defined(Q_QDOC) // On Windows we have a type GUID that is used by the platform API, so we // provide convenience operators to cast from and to this type. constexpr QUuid(const GUID &guid) noexcept @@ -187,7 +193,7 @@ public: QUuid::Variant variant() const noexcept; QUuid::Version version() const noexcept; -#if defined(Q_OS_DARWIN) || defined(Q_CLANG_QDOC) +#if defined(Q_OS_DARWIN) || defined(Q_QDOC) static QUuid fromCFUUID(CFUUIDRef uuid); CFUUIDRef toCFUUID() const Q_DECL_CF_RETURNS_RETAINED; static QUuid fromNSUUID(const NSUUID *uuid); @@ -213,11 +219,92 @@ Q_CORE_EXPORT QDebug operator<<(QDebug, const QUuid &); Q_CORE_EXPORT size_t qHash(const QUuid &uuid, size_t seed = 0) noexcept; +QUuid::QUuid(Id128Bytes uuid, QSysInfo::Endian order) noexcept +{ + if (order == QSysInfo::LittleEndian) + uuid = qbswap(uuid); + data1 = qFromBigEndian<quint32>(&uuid.data[0]); + data2 = qFromBigEndian<quint16>(&uuid.data[4]); + data3 = qFromBigEndian<quint16>(&uuid.data[6]); + memcpy(data4, &uuid.data[8], sizeof(data4)); +} + +QUuid::Id128Bytes QUuid::toBytes(QSysInfo::Endian order) const noexcept +{ + Id128Bytes result = {}; + qToBigEndian(data1, &result.data[0]); + qToBigEndian(data2, &result.data[4]); + qToBigEndian(data3, &result.data[6]); + memcpy(&result.data[8], data4, sizeof(data4)); + if (order == QSysInfo::LittleEndian) + return qbswap(result); + return result; +} + +QUuid QUuid::fromBytes(const void *bytes, QSysInfo::Endian order) +{ + Id128Bytes result = {}; + memcpy(result.data, bytes, sizeof(result)); + return QUuid(result, order); +} + +#ifdef QT_SUPPORTS_INT128 +constexpr QUuid QUuid::fromUInt128(quint128 uuid, QSysInfo::Endian order) noexcept +{ + QUuid result = {}; + if (order == QSysInfo::BigEndian) { + result.data1 = qFromBigEndian<quint32>(int(uuid)); + result.data2 = qFromBigEndian<quint16>(ushort(uuid >> 32)); + result.data3 = qFromBigEndian<quint16>(ushort(uuid >> 48)); + for (int i = 0; i < 8; ++i) + result.data4[i] = uchar(uuid >> (64 + i * 8)); + } else { + result.data1 = qFromLittleEndian<quint32>(uint(uuid >> 96)); + result.data2 = qFromLittleEndian<quint16>(ushort(uuid >> 80)); + result.data3 = qFromLittleEndian<quint16>(ushort(uuid >> 64)); + for (int i = 0; i < 8; ++i) + result.data4[i] = uchar(uuid >> (56 - i * 8)); + } + return result; +} + +constexpr quint128 QUuid::toUInt128(QSysInfo::Endian order) const noexcept +{ + quint128 result = {}; + if (order == QSysInfo::BigEndian) { + for (int i = 0; i < 8; ++i) + result |= quint64(data4[i]) << (i * 8); + result = result << 64; + result |= quint64(qToBigEndian<quint16>(data3)) << 48; + result |= quint64(qToBigEndian<quint16>(data2)) << 32; + result |= qToBigEndian<quint32>(data1); + } else { + result = qToLittleEndian<quint32>(data1); + result = result << 32; + result |= quint64(qToLittleEndian<quint16>(data2)) << 16; + result |= quint64(qToLittleEndian<quint16>(data3)); + result = result << 64; + for (int i = 0; i < 8; ++i) + result |= quint64(data4[i]) << (56 - i * 8); + } + return result; +} +#endif + inline bool operator<=(const QUuid &lhs, const QUuid &rhs) noexcept { return !(rhs < lhs); } inline bool operator>=(const QUuid &lhs, const QUuid &rhs) noexcept { return !(lhs < rhs); } +#if defined(Q_QDOC) +// provide fake declarations of qXXXEndian() functions, so that qDoc could +// distinguish them from the general template +QUuid::Id128Bytes qFromBigEndian(QUuid::Id128Bytes src); +QUuid::Id128Bytes qFromLittleEndian(QUuid::Id128Bytes src); +QUuid::Id128Bytes qToBigEndian(QUuid::Id128Bytes src); +QUuid::Id128Bytes qToLittleEndian(QUuid::Id128Bytes src); +#endif + QT_END_NAMESPACE #endif // QUUID_H |