diff options
Diffstat (limited to 'tests/auto/corelib/plugin/qpluginloader/tst_qpluginloader.cpp')
-rw-r--r-- | tests/auto/corelib/plugin/qpluginloader/tst_qpluginloader.cpp | 530 |
1 files changed, 417 insertions, 113 deletions
diff --git a/tests/auto/corelib/plugin/qpluginloader/tst_qpluginloader.cpp b/tests/auto/corelib/plugin/qpluginloader/tst_qpluginloader.cpp index bab3544946..f4ecf5bfb3 100644 --- a/tests/auto/corelib/plugin/qpluginloader/tst_qpluginloader.cpp +++ b/tests/auto/corelib/plugin/qpluginloader/tst_qpluginloader.cpp @@ -1,31 +1,6 @@ -/**************************************************************************** -** -** Copyright (C) 2020 The Qt Company Ltd. -** Copyright (C) 2021 Intel Corporation. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** 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 General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** 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-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 GPL-3.0-only #include <QTest> #include <QSignalSpy> @@ -33,14 +8,16 @@ #include <qdir.h> #include <qendian.h> #include <qpluginloader.h> -#include <qprocess.h> #include <qtemporaryfile.h> +#include <QScopeGuard> #include "theplugin/plugininterface.h" #if defined(QT_BUILD_INTERNAL) && defined(Q_OF_MACH_O) # include <QtCore/private/qmachparser_p.h> #endif +using namespace Qt::StringLiterals; + // Helper macros to let us know if some suffixes are valid #define bundle_VALID false #define dylib_VALID false @@ -95,15 +72,23 @@ #endif #if defined(Q_OF_ELF) -# include <elf.h> +#if __has_include(<elf.h>) +# include <elf.h> +#else +# include <sys/elf.h> +#endif # include <memory> # include <functional> # ifdef _LP64 using ElfHeader = Elf64_Ehdr; +using ElfPhdr = Elf64_Phdr; +using ElfNhdr = Elf64_Nhdr; using ElfShdr = Elf64_Shdr; # else using ElfHeader = Elf32_Ehdr; +using ElfPhdr = Elf32_Phdr; +using ElfNhdr = Elf32_Nhdr; using ElfShdr = Elf32_Shdr; # endif @@ -142,7 +127,10 @@ static std::unique_ptr<QTemporaryFile> patchElf(const QString &source, ElfPatche QVERIFY2(srcdata, qPrintable(srclib.errorString())); // copy our source plugin so we can modify it - tmplib.reset(new QTemporaryFile(QTest::currentDataTag() + QString(".XXXXXX" SUFFIX))); + const char *basename = QTest::currentDataTag(); + if (!basename) + basename = QTest::currentTestFunction(); + tmplib.reset(new QTemporaryFile(QDir::currentPath() + u'/' + basename + u".XXXXXX" SUFFIX ""_s)); QVERIFY2(tmplib->open(), qPrintable(tmplib->errorString())); // sanity-check @@ -170,16 +158,26 @@ static std::unique_ptr<QTemporaryFile> patchElf(const QString &source, ElfPatche if (QTest::currentTestFailed()) return; \ std::move(r); \ }) -#endif +#endif // Q_OF_ELF static QString sys_qualifiedLibraryName(const QString &fileName) { +#ifdef Q_OS_ANDROID + // On Android all the libraries must be located in the APK's libs subdir + const QStringList paths = QCoreApplication::libraryPaths(); + if (!paths.isEmpty()) { + return QLatin1String("%1/%2%3_%4%5").arg(paths.first(), PREFIX, fileName, + ANDROID_ARCH, SUFFIX); + } + return fileName; +#else QString name = QLatin1String("bin/") + QLatin1String(PREFIX) + fileName + QLatin1String(SUFFIX); const QString libname = QFINDTESTDATA(name); QFileInfo fi(libname); if (fi.exists()) return fi.canonicalFilePath(); return libname; +#endif } QT_FORWARD_DECLARE_CLASS(QPluginLoader) @@ -192,19 +190,25 @@ private slots: void errorString(); void loadHints(); void deleteinstanceOnUnload(); -#if defined (__ELF__) +#if defined (Q_OF_ELF) void loadDebugObj(); void loadCorruptElf_data(); void loadCorruptElf(); +# if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) + void loadCorruptElfOldPlugin_data(); + void loadCorruptElfOldPlugin(); +# endif #endif void loadMachO_data(); void loadMachO(); void relativePath(); void absolutePath(); void reloadPlugin(); + void loadSectionTableStrippedElf(); void preloadedPlugin_data(); void preloadedPlugin(); void staticPlugins(); + void reregisteredStaticPlugins(); }; Q_IMPORT_PLUGIN(StaticPlugin) @@ -276,7 +280,9 @@ void tst_QPluginLoader::errorString() QVERIFY(!unloaded); } -#if !defined(Q_OS_WIN) && !defined(Q_OS_MAC) && !defined(Q_OS_HPUX) +// A bug in QNX causes the test to crash on exit after attempting to load +// a shared library with undefined symbols (tracked as QTBUG-114682). +#if !defined(Q_OS_WIN) && !defined(Q_OS_DARWIN) && !defined(Q_OS_HPUX) && !defined(Q_OS_QNX) { QPluginLoader loader( sys_qualifiedLibraryName("almostplugin")); //a plugin with unresolved symbols loader.setLoadHints(QLibrary::ResolveAllSymbolsHint); @@ -294,29 +300,36 @@ void tst_QPluginLoader::errorString() } #endif - { - QPluginLoader loader( sys_qualifiedLibraryName("theplugin")); //a plugin - - // Check metadata - const QJsonObject metaData = loader.metaData(); - QCOMPARE(metaData.value("IID").toString(), QStringLiteral("org.qt-project.Qt.autotests.plugininterface")); - const QJsonObject kpluginObject = metaData.value("MetaData").toObject().value("KPlugin").toObject(); - QCOMPARE(kpluginObject.value("Name[mr]").toString(), QString::fromUtf8("चौकट भूमिती")); - - // Load - QCOMPARE(loader.load(), true); - QCOMPARE(loader.errorString(), unknown); - - QVERIFY(loader.instance() != static_cast<QObject*>(0)); - QCOMPARE(loader.errorString(), unknown); - - // Make sure that plugin really works - PluginInterface* theplugin = qobject_cast<PluginInterface*>(loader.instance()); - QString pluginName = theplugin->pluginName(); - QCOMPARE(pluginName, QLatin1String("Plugin ok")); - - QCOMPARE(loader.unload(), true); - QCOMPARE(loader.errorString(), unknown); + static constexpr std::initializer_list<const char *> validplugins = { + "theplugin", +#if defined(Q_OF_ELF) && QT_VERSION < QT_VERSION_CHECK(7, 0, 0) + "theoldplugin" +#endif + }; + for (const char *basename : validplugins) { + QPluginLoader loader( sys_qualifiedLibraryName(basename)); //a plugin + + // Check metadata + const QJsonObject metaData = loader.metaData(); + QVERIFY2(!metaData.isEmpty(), "No metadata from " + loader.fileName().toLocal8Bit()); + QCOMPARE(metaData.value("IID").toString(), QStringLiteral("org.qt-project.Qt.autotests.plugininterface")); + const QJsonObject kpluginObject = metaData.value("MetaData").toObject().value("KPlugin").toObject(); + QCOMPARE(kpluginObject.value("Name[mr]").toString(), QString::fromUtf8("चौकट भूमिती")); + + // Load + QVERIFY2(loader.load(), qPrintable(loader.errorString())); + QCOMPARE(loader.errorString(), unknown); + + QVERIFY(loader.instance() != static_cast<QObject*>(0)); + QCOMPARE(loader.errorString(), unknown); + + // Make sure that plugin really works + PluginInterface* theplugin = qobject_cast<PluginInterface*>(loader.instance()); + QString pluginName = theplugin->pluginName(); + QCOMPARE(pluginName, QLatin1String("Plugin ok")); + + QCOMPARE(loader.unload(), true); + QCOMPARE(loader.errorString(), unknown); } } @@ -326,10 +339,37 @@ void tst_QPluginLoader::loadHints() QSKIP("This test requires Qt to create shared libraries."); #endif QPluginLoader loader; - QCOMPARE(loader.loadHints(), QLibrary::LoadHints{}); //Do not crash + QCOMPARE(loader.loadHints(), QLibrary::PreventUnloadHint); //Do not crash loader.setLoadHints(QLibrary::ResolveAllSymbolsHint); + QCOMPARE(loader.loadHints(), QLibrary::ResolveAllSymbolsHint); + // We can clear load hints when file name is not set. + loader.setLoadHints(QLibrary::LoadHints{}); + QCOMPARE(loader.loadHints(), QLibrary::LoadHints{}); + // Set the hints again + loader.setLoadHints(QLibrary::ResolveAllSymbolsHint); + QCOMPARE(loader.loadHints(), QLibrary::ResolveAllSymbolsHint); loader.setFileName( sys_qualifiedLibraryName("theplugin")); //a plugin QCOMPARE(loader.loadHints(), QLibrary::ResolveAllSymbolsHint); + + QPluginLoader loader4; + QCOMPARE(loader4.loadHints(), QLibrary::PreventUnloadHint); + loader4.setLoadHints(QLibrary::LoadHints{}); + QCOMPARE(loader4.loadHints(), QLibrary::LoadHints{}); + loader4.setFileName(sys_qualifiedLibraryName("theplugin")); + // Hints are merged with hints from the previous loader. + QCOMPARE(loader4.loadHints(), QLibrary::ResolveAllSymbolsHint); + // We cannot clear load hints after associating the loader with a file. + loader.setLoadHints(QLibrary::LoadHints{}); + QCOMPARE(loader.loadHints(), QLibrary::ResolveAllSymbolsHint); + + QPluginLoader loader2; + QCOMPARE(loader2.loadHints(), QLibrary::PreventUnloadHint); + loader2.setFileName(sys_qualifiedLibraryName("theplugin")); + // Hints are merged with hints from previous loaders. + QCOMPARE(loader2.loadHints(), QLibrary::PreventUnloadHint | QLibrary::ResolveAllSymbolsHint); + + QPluginLoader loader3(sys_qualifiedLibraryName("theplugin")); + QCOMPARE(loader3.loadHints(), QLibrary::PreventUnloadHint | QLibrary::ResolveAllSymbolsHint); } void tst_QPluginLoader::deleteinstanceOnUnload() @@ -359,18 +399,19 @@ void tst_QPluginLoader::deleteinstanceOnUnload() QVERIFY(spy2.isValid()); if (pass == 0) { QCOMPARE(loader2.unload(), false); // refcount not reached 0, not really unloaded - QCOMPARE(spy1.count(), 0); - QCOMPARE(spy2.count(), 0); + QCOMPARE(spy1.size(), 0); + QCOMPARE(spy2.size(), 0); } QCOMPARE(instance1->pluginName(), QLatin1String("Plugin ok")); QCOMPARE(instance2->pluginName(), QLatin1String("Plugin ok")); QVERIFY(loader1.unload()); // refcount reached 0, did really unload - QCOMPARE(spy1.count(), 1); - QCOMPARE(spy2.count(), 1); + QCOMPARE(spy1.size(), 1); + QCOMPARE(spy2.size(), 1); } } -#if defined (__ELF__) +#if defined(Q_OF_ELF) + void tst_QPluginLoader::loadDebugObj() { #if !defined(QT_SHARED) @@ -381,16 +422,23 @@ void tst_QPluginLoader::loadDebugObj() QCOMPARE(lib1.load(), false); } -void tst_QPluginLoader::loadCorruptElf_data() +template <typename Lambda> +static void newRow(const char *rowname, QString &&snippet, Lambda &&patcher) +{ + QTest::newRow(rowname) + << std::move(snippet) << ElfPatcher::fromLambda(std::forward<Lambda>(patcher)); +} + +static ElfPhdr *getProgramEntry(ElfHeader *h, int index) +{ + auto phdr = reinterpret_cast<ElfPhdr *>(h->e_phoff + reinterpret_cast<uchar *>(h)); + return phdr + index; +} + +static void loadCorruptElfCommonRows() { -#if !defined(QT_SHARED) - QSKIP("This test requires a shared build of Qt, as QPluginLoader::setFileName is a no-op in static builds"); -#endif QTest::addColumn<QString>("snippet"); QTest::addColumn<ElfPatcher>("patcher"); - auto newRow = [](const char *rowname, QString &&snippet, auto patcher) { - QTest::newRow(rowname) << std::move(snippet) << ElfPatcher::fromLambda(patcher); - }; using H = ElfHeader *; // because I'm lazy newRow("not-elf", "invalid signature", [](H h) { @@ -426,7 +474,7 @@ void tst_QPluginLoader::loadCorruptElf_data() memcpy(h, &o, sizeof(o)); }); newRow("invalid-word-size", "file is for a different word size", [](H h) { - h->e_ident[EI_CLASS] = ELFCLASSNONE;; + h->e_ident[EI_CLASS] = ELFCLASSNONE; }); newRow("unknown-word-size", "file is for a different word size", [](H h) { h->e_ident[EI_CLASS] |= 0x40; @@ -505,6 +553,195 @@ void tst_QPluginLoader::loadCorruptElf_data() ++h->e_version; }); + newRow("program-entry-size-zero", "unexpected program header entry size", [](H h) { + h->e_phentsize = 0; + }); + newRow("program-entry-small", "unexpected program header entry size", [](H h) { + h->e_phentsize = alignof(ElfPhdr); + }); + + newRow("program-table-starts-past-eof", "program header table extends past the end of the file", + [](H h, QFile *f) { + h->e_phoff = f->size(); + }); + newRow("program-table-ends-past-eof", "program header table extends past the end of the file", + [](H h, QFile *f) { + h->e_phoff = f->size() + 1- h->e_phentsize * h->e_phnum; + }); + + newRow("segment-starts-past-eof", "a program header entry extends past the end of the file", + [](H h, QFile *f) { + for (int i = 0; i < h->e_phnum; ++i) { + ElfPhdr *p = getProgramEntry(h, i); + if (p->p_type != PT_LOAD) + continue; + p->p_offset = f->size(); + break; + } + }); + newRow("segment-ends-past-eof", "a program header entry extends past the end of the file", + [](H h, QFile *f) { + for (int i = 0; i < h->e_phnum; ++i) { + ElfPhdr *p = getProgramEntry(h, i); + if (p->p_type != PT_LOAD) + continue; + p->p_filesz = f->size() + 1 - p->p_offset; + break; + } + }); + newRow("segment-bounds-overflow", "a program header entry extends past the end of the file", + [](H h) { + for (int i = 0; i < h->e_phnum; ++i) { + ElfPhdr *p = getProgramEntry(h, i); + if (p->p_type != PT_LOAD) + continue; + p->p_filesz = ~size_t(0); // -1 + break; + } + }); + + newRow("no-code", "file has no code", [](H h) { + for (int i = 0; i < h->e_phnum; ++i) { + ElfPhdr *p = getProgramEntry(h, i); + if (p->p_type == PT_LOAD) + p->p_flags &= ~PF_X; + } + }); +} + +void tst_QPluginLoader::loadCorruptElf_data() +{ +#if !defined(QT_SHARED) + QSKIP("This test requires a shared build of Qt, as QPluginLoader::setFileName is a no-op in static builds"); +#endif + loadCorruptElfCommonRows(); + using H = ElfHeader *; // because I'm lazy + + // PT_NOTE tests + // general validity is tested in the common rows, for all segments + + newRow("misaligned-note-segment", "note segment start is not properly aligned", [](H h) { + for (int i = 0; i < h->e_phnum; ++i) { + ElfPhdr *p = getProgramEntry(h, i); + if (p->p_type == PT_NOTE) + ++p->p_offset; + } + }); + + static const auto getFirstNote = [](void *header, ElfPhdr *phdr) { + return reinterpret_cast<ElfNhdr *>(static_cast<uchar *>(header) + phdr->p_offset); + }; + static const auto getNextNote = [](void *header, ElfPhdr *phdr, ElfNhdr *n) { + // how far into the segment are we? + size_t offset = reinterpret_cast<uchar *>(n) - static_cast<uchar *>(header) - phdr->p_offset; + + size_t delta = sizeof(*n) + n->n_namesz + phdr->p_align - 1; + delta &= -phdr->p_align; + delta += n->n_descsz + phdr->p_align - 1; + delta &= -phdr->p_align; + + offset += delta; + if (offset < phdr->p_filesz) + n = reinterpret_cast<ElfNhdr *>(reinterpret_cast<uchar *>(n) + delta); + else + n = nullptr; + return n; + }; + + // all the intra-note errors cause the notes simply to be skipped + auto newNoteRow = [](const char *rowname, auto &&lambda) { + newRow(rowname, "is not a Qt plugin (metadata not found)", std::move(lambda)); + }; + newNoteRow("no-notes", [](H h) { + for (int i = 0; i < h->e_phnum; ++i) { + ElfPhdr *p = getProgramEntry(h, i); + if (p->p_type == PT_NOTE) + p->p_type = PT_NULL; + } + }); + + newNoteRow("note-larger-than-segment-nonqt", [](H h) { + for (int i = 0; i < h->e_phnum; ++i) { + ElfPhdr *p = getProgramEntry(h, i); + if (p->p_type != PT_NOTE) + continue; + ElfNhdr *n = getFirstNote(h, p); + n->n_descsz = p->p_filesz; + n->n_type = 0; // ensure it's not the Qt note + } + }); + newNoteRow("note-larger-than-segment-qt", [](H h) { + for (int i = 0; i < h->e_phnum; ++i) { + ElfPhdr *p = getProgramEntry(h, i); + if (p->p_type != PT_NOTE || p->p_align != alignof(QPluginMetaData::ElfNoteHeader)) + continue; + + // find the Qt metadata note + constexpr QPluginMetaData::ElfNoteHeader header(0); + ElfNhdr *n = getFirstNote(h, p); + for ( ; n; n = getNextNote(h, p, n)) { + if (n->n_type == header.n_type && n->n_namesz == header.n_namesz) { + if (memcmp(n + 1, header.name, sizeof(header.name)) == 0) + break; + } + } + + if (!n) + break; + n->n_descsz = p->p_filesz; + return; + } + qWarning("Could not find the Qt metadata note in this file. Test will fail."); + }); + newNoteRow("note-size-overflow1", [](H h) { + // due to limited range, this will not overflow on 64-bit + for (int i = 0; i < h->e_phnum; ++i) { + ElfPhdr *p = getProgramEntry(h, i); + if (p->p_type != PT_NOTE) + continue; + ElfNhdr *n = getFirstNote(h, p); + n->n_namesz = ~decltype(n->n_namesz)(0); + } + }); + newNoteRow("note-size-overflow2", [](H h) { + // due to limited range, this will not overflow on 64-bit + for (int i = 0; i < h->e_phnum; ++i) { + ElfPhdr *p = getProgramEntry(h, i); + if (p->p_type != PT_NOTE) + continue; + ElfNhdr *n = getFirstNote(h, p); + n->n_namesz = ~decltype(n->n_namesz)(0) / 2; + n->n_descsz = ~decltype(n->n_descsz)(0) / 2; + } + }); +} + +static void loadCorruptElf_helper(const QString &origLibrary) +{ + QFETCH(QString, snippet); + QFETCH(ElfPatcher, patcher); + + std::unique_ptr<QTemporaryFile> tmplib = patchElf(origLibrary, patcher); + + QPluginLoader lib(tmplib->fileName()); + QVERIFY(!lib.load()); + QVERIFY2(lib.errorString().contains(snippet), qPrintable(lib.errorString())); +} + +void tst_QPluginLoader::loadCorruptElf() +{ + loadCorruptElf_helper(sys_qualifiedLibraryName("theplugin")); +} + +# if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) +void tst_QPluginLoader::loadCorruptElfOldPlugin_data() +{ +#if !defined(QT_SHARED) + QSKIP("This test requires a shared build of Qt, as QPluginLoader::setFileName is a no-op in static builds"); +#endif + loadCorruptElfCommonRows(); + using H = ElfHeader *; // because I'm lazy + newRow("section-entry-size-zero", "unexpected section entry size", [](H h) { h->e_shentsize = 0; }); @@ -514,7 +751,7 @@ void tst_QPluginLoader::loadCorruptElf_data() newRow("section-entry-misaligned", "unexpected section entry size", [](H h) { ++h->e_shentsize; }); - newRow("no-sections", "is not a Qt plugin (.qtmetadata section not found)", [](H h){ + newRow("no-sections", "is not a Qt plugin (metadata not found)", [](H h){ h->e_shnum = h->e_shoff = h->e_shstrndx = 0; }); @@ -535,17 +772,17 @@ void tst_QPluginLoader::loadCorruptElf_data() // arbitrary section bounds checks // section index = 0 is usually a NULL section, so we try 1 - newRow("section1-starts-past-eof", "a section data extends past the end of the file", + newRow("section1-starts-past-eof", "section contents extend past the end of the file", [](H h, QFile *f) { ElfShdr *s = getSection(h, 1); s->sh_offset = f->size(); }); - newRow("section1-ends-past-eof", "a section data extends past the end of the file", + newRow("section1-ends-past-eof", "section contents extend past the end of the file", [](H h, QFile *f) { ElfShdr *s = getSection(h, 1); s->sh_size = f->size() + 1 - s->sh_offset; }); - newRow("section1-bounds-overflow", "a section data extends past the end of the file", [](H h) { + newRow("section1-bounds-overflow", "section contents extend past the end of the file", [](H h) { ElfShdr *s = getSection(h, 1); s->sh_size = -sizeof(*s); }); @@ -579,7 +816,7 @@ void tst_QPluginLoader::loadCorruptElf_data() section1->sh_name = shstrtab->sh_size; }); - newRow("debug-symbols", ".qtmetadata section not found", [](H h) { + newRow("debug-symbols", "metadata not found", [](H h) { // attempt to make it look like extracted debug info for (int i = 1; i < h->e_shnum; ++i) { ElfShdr *s = getSection(h, i); @@ -603,19 +840,13 @@ void tst_QPluginLoader::loadCorruptElf_data() }); } -void tst_QPluginLoader::loadCorruptElf() +void tst_QPluginLoader::loadCorruptElfOldPlugin() { - QFETCH(QString, snippet); - QFETCH(ElfPatcher, patcher); - - std::unique_ptr<QTemporaryFile> tmplib = - patchElf(sys_qualifiedLibraryName("theplugin"), patcher); - - QPluginLoader lib(tmplib->fileName()); - QVERIFY(!lib.load()); - QVERIFY2(lib.errorString().contains(snippet), qPrintable(lib.errorString())); + // ### Qt7: don't forget to remove theoldplugin from the build + loadCorruptElf_helper(sys_qualifiedLibraryName("theoldplugin")); } -#endif // __ELF__ +# endif // Qt 7 +#endif // Q_OF_ELF void tst_QPluginLoader::loadMachO_data() { @@ -629,26 +860,23 @@ void tst_QPluginLoader::loadMachO_data() # ifdef Q_PROCESSOR_X86_64 QTest::newRow("machtest/good.x86_64.dylib") << true; - QTest::newRow("machtest/good.i386.dylib") << false; + QTest::newRow("machtest/good.arm64.dylib") << false; QTest::newRow("machtest/good.fat.no-x86_64.dylib") << false; - QTest::newRow("machtest/good.fat.no-i386.dylib") << true; -# elif defined(Q_PROCESSOR_X86_32) - QTest::newRow("machtest/good.i386.dylib") << true; + QTest::newRow("machtest/good.fat.no-arm64.dylib") << true; +# elif defined(Q_PROCESSOR_ARM) + QTest::newRow("machtest/good.arm64.dylib") << true; QTest::newRow("machtest/good.x86_64.dylib") << false; - QTest::newRow("machtest/good.fat.no-i386.dylib") << false; + QTest::newRow("machtest/good.fat.no-arm64.dylib") << false; QTest::newRow("machtest/good.fat.no-x86_64.dylib") << true; # endif -# ifndef Q_PROCESSOR_POWER_64 - QTest::newRow("machtest/good.ppc64.dylib") << false; -# endif QTest::newRow("machtest/good.fat.all.dylib") << true; QTest::newRow("machtest/good.fat.stub-x86_64.dylib") << false; - QTest::newRow("machtest/good.fat.stub-i386.dylib") << false; + QTest::newRow("machtest/good.fat.stub-arm64.dylib") << false; QDir d(QFINDTESTDATA("machtest")); - QStringList badlist = d.entryList(QStringList() << "bad*.dylib"); - foreach (const QString &bad, badlist) + const QStringList badlist = d.entryList(QStringList() << "bad*.dylib"); + for (const QString &bad : badlist) QTest::newRow(qPrintable("machtest/" + bad)) << false; #endif } @@ -672,12 +900,7 @@ void tst_QPluginLoader::loadMachO() } QVERIFY(r.pos > 0); - QVERIFY(size_t(r.length) >= sizeof(void*)); QVERIFY(r.pos + r.length < data.size()); - QCOMPARE(r.pos & (sizeof(void*) - 1), 0UL); - - void *value = *(void**)(data.constData() + r.pos); - QCOMPARE(value, sizeof(void*) > 4 ? (void*)(0xc0ffeec0ffeeL) : (void*)0xc0ffee); // now that we know it's valid, let's try to make it invalid ulong offeredlen = r.pos; @@ -695,11 +918,19 @@ void tst_QPluginLoader::relativePath() #if !defined(QT_SHARED) QSKIP("This test requires Qt to create shared libraries."); #endif +#ifdef Q_OS_ANDROID + // On Android we do not need to explicitly set library paths, as they are + // already set. + // But we need to use ARCH suffix in pulgin name + const QString pluginName("theplugin_" ANDROID_ARCH SUFFIX); +#else // Windows binaries run from release and debug subdirs, so we can't rely on the current dir. const QString binDir = QFINDTESTDATA("bin"); QVERIFY(!binDir.isEmpty()); QCoreApplication::addLibraryPath(binDir); - QPluginLoader loader("theplugin" SUFFIX); + const QString pluginName("theplugin" SUFFIX); +#endif + QPluginLoader loader(pluginName); loader.load(); // not recommended, instance() should do the job. PluginInterface *instance = qobject_cast<PluginInterface*>(loader.instance()); QVERIFY(instance); @@ -712,13 +943,27 @@ void tst_QPluginLoader::absolutePath() #if !defined(QT_SHARED) QSKIP("This test requires Qt to create shared libraries."); #endif +#ifdef Q_OS_ANDROID + // On Android we need to clear library paths to make sure that the absolute + // path works + const QStringList libraryPaths = QCoreApplication::libraryPaths(); + QVERIFY(!libraryPaths.isEmpty()); + QCoreApplication::setLibraryPaths(QStringList()); + const QString pluginPath(libraryPaths.first() + "/" PREFIX "theplugin_" ANDROID_ARCH SUFFIX); +#else // Windows binaries run from release and debug subdirs, so we can't rely on the current dir. const QString binDir = QFINDTESTDATA("bin"); QVERIFY(!binDir.isEmpty()); QVERIFY(QDir::isAbsolutePath(binDir)); - QPluginLoader loader(binDir + "/" PREFIX "theplugin" SUFFIX); + const QString pluginPath(binDir + "/" PREFIX "theplugin" SUFFIX); +#endif + QPluginLoader loader(pluginPath); loader.load(); // not recommended, instance() should do the job. PluginInterface *instance = qobject_cast<PluginInterface*>(loader.instance()); +#ifdef Q_OS_ANDROID + // Restore library paths + QCoreApplication::setLibraryPaths(libraryPaths); +#endif QVERIFY(instance); QCOMPARE(instance->pluginName(), QLatin1String("Plugin ok")); QVERIFY(loader.unload()); @@ -739,7 +984,7 @@ void tst_QPluginLoader::reloadPlugin() QSignalSpy spy(loader.instance(), &QObject::destroyed); QVERIFY(spy.isValid()); QVERIFY(loader.unload()); // refcount reached 0, did really unload - QCOMPARE(spy.count(), 1); + QCOMPARE(spy.size(), 1); // reload plugin QVERIFY(loader.load()); @@ -752,6 +997,54 @@ void tst_QPluginLoader::reloadPlugin() QVERIFY(loader.unload()); } +void tst_QPluginLoader::loadSectionTableStrippedElf() +{ +#ifdef Q_OS_ANDROID + if (QNativeInterface::QAndroidApplication::sdkVersion() >= 24) + QSKIP("Android 7+ (API 24+) linker doesn't allow missing or bad section header"); +#endif +#if !defined(QT_SHARED) + QSKIP("This test requires a shared build of Qt, as QPluginLoader::setFileName is a no-op in static builds"); +#elif !defined(Q_OF_ELF) + QSKIP("Test specific to the ELF file format"); +#else + ElfPatcher patcher { [](ElfHeader *header, QFile *f) { + // modify the header to make it look like the section table was stripped + header->e_shoff = header->e_shnum = header->e_shstrndx = 0; + + // and append a bad header at the end + QPluginMetaData::MagicHeader badHeader = {}; + --badHeader.header.qt_major_version; + f->seek(f->size()); + f->write(reinterpret_cast<const char *>(&badHeader), sizeof(badHeader)); + } }; + + QString tmpLibName; + { + std::unique_ptr<QTemporaryFile> tmplib = + patchElf(sys_qualifiedLibraryName("theplugin"), patcher); + + tmpLibName = tmplib->fileName(); + tmplib->setAutoRemove(false); + } +#if defined(Q_OS_QNX) + // On QNX plugin access is still too early, even when QTemporaryFile is closed + QTest::qSleep(1000); +#endif + auto removeTmpLib = qScopeGuard([=]{ + QFile::remove(tmpLibName); + }); + + // now attempt to load it + QPluginLoader loader(tmpLibName); + QVERIFY2(loader.load(), qPrintable(loader.errorString())); + PluginInterface *instance = qobject_cast<PluginInterface*>(loader.instance()); + QVERIFY(instance); + QCOMPARE(instance->pluginName(), QLatin1String("Plugin ok")); + QVERIFY(loader.unload()); +#endif +} + void tst_QPluginLoader::preloadedPlugin_data() { QTest::addColumn<bool>("doLoad"); @@ -802,19 +1095,18 @@ void tst_QPluginLoader::staticPlugins() const QObjectList instances = QPluginLoader::staticInstances(); QVERIFY(instances.size()); - bool found = false; - for (QObject *obj : instances) { - found = obj->metaObject()->className() == QLatin1String("StaticPlugin"); - if (found) - break; - } - QVERIFY(found); + // ensure the our plugin only shows up once + int foundCount = std::count_if(instances.begin(), instances.end(), [](QObject *obj) { + return obj->metaObject()->className() == QLatin1String("StaticPlugin"); + }); + QCOMPARE(foundCount, 1); const auto plugins = QPluginLoader::staticPlugins(); QCOMPARE(plugins.size(), instances.size()); // find the metadata QJsonObject metaData; + bool found = false; for (const auto &p : plugins) { metaData = p.metaData(); found = metaData.value("className").toString() == QLatin1String("StaticPlugin"); @@ -830,6 +1122,18 @@ void tst_QPluginLoader::staticPlugins() QCOMPARE(metaData.value("URI").toString(), "qt.test.pluginloader.staticplugin"); } +void tst_QPluginLoader::reregisteredStaticPlugins() +{ + // the Q_IMPORT_PLUGIN macro will have already done this + qRegisterStaticPluginFunction(qt_static_plugin_StaticPlugin()); + staticPlugins(); + if (QTest::currentTestFailed()) + return; + + qRegisterStaticPluginFunction(qt_static_plugin_StaticPlugin()); + staticPlugins(); +} + QTEST_MAIN(tst_QPluginLoader) #include "tst_qpluginloader.moc" |