1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Copyright (C) 2018 Intel Corporation.
5** Contact: https://www.qt.io/licensing/
6**
7** This file is part of the test suite of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:GPL-EXCEPT$
10** Commercial License Usage
11** Licensees holding valid commercial Qt licenses may use this file in
12** accordance with the commercial license agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and The Qt Company. For licensing terms
15** and conditions see https://www.qt.io/terms-conditions. For further
16** information use the contact form at https://www.qt.io/contact-us.
17**
18** GNU General Public License Usage
19** Alternatively, this file may be used under the terms of the GNU
20** General Public License version 3 as published by the Free Software
21** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
22** included in the packaging of this file. Please review the following
23** information to ensure the GNU General Public License requirements will
24** be met: https://www.gnu.org/licenses/gpl-3.0.html.
25**
26** $QT_END_LICENSE$
27**
28****************************************************************************/
29
30#include <QtTest/QtTest>
31#include <qdir.h>
32#include <qpluginloader.h>
33#include "theplugin/plugininterface.h"
34
35#if defined(QT_BUILD_INTERNAL) && defined(Q_OF_MACH_O)
36# include <QtCore/private/qmachparser_p.h>
37#endif
38
39// Helper macros to let us know if some suffixes are valid
40#define bundle_VALID false
41#define dylib_VALID false
42#define sl_VALID false
43#define a_VALID false
44#define so_VALID false
45#define dll_VALID false
46
47#if defined(Q_OS_DARWIN)
48# undef bundle_VALID
49# undef dylib_VALID
50# undef so_VALID
51# define bundle_VALID true
52# define dylib_VALID true
53# define so_VALID true
54//# ifdef QT_NO_DEBUG
55# define SUFFIX ".dylib"
56//# else
57//# define SUFFIX "_debug.dylib"
58//#endif
59# define PREFIX "lib"
60
61#elif defined(Q_OS_HPUX) && !defined(__ia64)
62# undef sl_VALID
63# define sl_VALID true
64# define SUFFIX ".sl"
65# define PREFIX "lib"
66
67#elif defined(Q_OS_AIX)
68# undef a_VALID
69# undef so_VALID
70# define a_VALID true
71# define so_VALID true
72# define SUFFIX ".so"
73# define PREFIX "lib"
74
75#elif defined(Q_OS_WIN)
76# undef dll_VALID
77# define dll_VALID true
78//# ifdef QT_NO_DEBUG
79# define SUFFIX ".dll"
80//# else
81//# define SUFFIX "d.dll"
82//# endif
83# define PREFIX ""
84
85#else // all other Unix
86# undef so_VALID
87# define so_VALID true
88# define SUFFIX ".so"
89# define PREFIX "lib"
90#endif
91
92static QString sys_qualifiedLibraryName(const QString &fileName)
93{
94 QString name = QLatin1String("bin/") + QLatin1String(PREFIX) + fileName + QLatin1String(SUFFIX);
95 const QString libname = QFINDTESTDATA(name);
96 QFileInfo fi(libname);
97 if (fi.exists())
98 return fi.canonicalFilePath();
99 return libname;
100}
101
102QT_FORWARD_DECLARE_CLASS(QPluginLoader)
103class tst_QPluginLoader : public QObject
104{
105 Q_OBJECT
106public slots:
107 void cleanup();
108private slots:
109 void errorString();
110 void loadHints();
111 void deleteinstanceOnUnload();
112 void loadDebugObj();
113 void loadCorruptElf();
114 void loadMachO_data();
115 void loadMachO();
116#if defined (Q_OS_UNIX)
117 void loadGarbage();
118#endif
119 void relativePath();
120 void absolutePath();
121 void reloadPlugin();
122 void preloadedPlugin_data();
123 void preloadedPlugin();
124 void staticPlugins();
125};
126
127Q_IMPORT_PLUGIN(StaticPlugin)
128
129void tst_QPluginLoader::cleanup()
130{
131 // check if the library/plugin was leaked
132 // we can't use QPluginLoader::isLoaded here because on some platforms the plugin is always loaded by QPluginLoader.
133 // Also, if this test fails once, it will keep on failing because we can't force the unload,
134 // so we report it only once.
135 static bool failedAlready = false;
136 if (!failedAlready) {
137 QLibrary lib(sys_qualifiedLibraryName(fileName: "theplugin"));
138 failedAlready = true;
139 QVERIFY2(!lib.isLoaded(), "Plugin was leaked - will not check again");
140 failedAlready = false;
141 }
142}
143
144void tst_QPluginLoader::errorString()
145{
146#if !defined(QT_SHARED)
147 QSKIP("This test requires Qt to create shared libraries.");
148#endif
149
150 const QString unknown(QLatin1String("Unknown error"));
151
152 {
153 QPluginLoader loader; // default constructed
154 bool loaded = loader.load();
155 QCOMPARE(loader.errorString(), unknown);
156 QVERIFY(!loaded);
157
158 QObject *obj = loader.instance();
159 QCOMPARE(loader.errorString(), unknown);
160 QCOMPARE(obj, static_cast<QObject*>(0));
161
162 bool unloaded = loader.unload();
163 QCOMPARE(loader.errorString(), unknown);
164 QVERIFY(!unloaded);
165 }
166 {
167 QPluginLoader loader( sys_qualifiedLibraryName(fileName: "tst_qpluginloaderlib")); //not a plugin
168 bool loaded = loader.load();
169 QVERIFY(loader.errorString() != unknown);
170 QVERIFY(!loaded);
171
172 QObject *obj = loader.instance();
173 QVERIFY(loader.errorString() != unknown);
174 QCOMPARE(obj, static_cast<QObject*>(0));
175
176 bool unloaded = loader.unload();
177 QVERIFY(loader.errorString() != unknown);
178 QVERIFY(!unloaded);
179 }
180
181 {
182 QPluginLoader loader( sys_qualifiedLibraryName(fileName: "nosuchfile")); //not a file
183 bool loaded = loader.load();
184 QVERIFY(loader.errorString() != unknown);
185 QVERIFY(!loaded);
186
187 QObject *obj = loader.instance();
188 QVERIFY(loader.errorString() != unknown);
189 QCOMPARE(obj, static_cast<QObject*>(0));
190
191 bool unloaded = loader.unload();
192 QVERIFY(loader.errorString() != unknown);
193 QVERIFY(!unloaded);
194 }
195
196#if !defined(Q_OS_WIN) && !defined(Q_OS_MAC) && !defined(Q_OS_HPUX)
197 {
198 QPluginLoader loader( sys_qualifiedLibraryName(fileName: "almostplugin")); //a plugin with unresolved symbols
199 loader.setLoadHints(QLibrary::ResolveAllSymbolsHint);
200 bool loaded = loader.load();
201 QVERIFY(loader.errorString() != unknown);
202 QVERIFY(!loaded);
203
204 QObject *obj = loader.instance();
205 QVERIFY(loader.errorString() != unknown);
206 QCOMPARE(obj, static_cast<QObject*>(0));
207
208 bool unloaded = loader.unload();
209 QVERIFY(loader.errorString() != unknown);
210 QVERIFY(!unloaded);
211 }
212#endif
213
214 {
215 QPluginLoader loader( sys_qualifiedLibraryName(fileName: "theplugin")); //a plugin
216
217 // Check metadata
218 const QJsonObject metaData = loader.metaData();
219 QCOMPARE(metaData.value("IID").toString(), QStringLiteral("org.qt-project.Qt.autotests.plugininterface"));
220 const QJsonObject kpluginObject = metaData.value(key: "MetaData").toObject().value(key: "KPlugin").toObject();
221 QCOMPARE(kpluginObject.value("Name[mr]").toString(), QString::fromUtf8("चौकट भूमिती"));
222
223 // Load
224 QCOMPARE(loader.load(), true);
225 QCOMPARE(loader.errorString(), unknown);
226
227 QVERIFY(loader.instance() != static_cast<QObject*>(0));
228 QCOMPARE(loader.errorString(), unknown);
229
230 // Make sure that plugin really works
231 PluginInterface* theplugin = qobject_cast<PluginInterface*>(object: loader.instance());
232 QString pluginName = theplugin->pluginName();
233 QCOMPARE(pluginName, QLatin1String("Plugin ok"));
234
235 QCOMPARE(loader.unload(), true);
236 QCOMPARE(loader.errorString(), unknown);
237 }
238}
239
240void tst_QPluginLoader::loadHints()
241{
242#if !defined(QT_SHARED)
243 QSKIP("This test requires Qt to create shared libraries.");
244#endif
245 QPluginLoader loader;
246 QCOMPARE(loader.loadHints(), QLibrary::LoadHints{}); //Do not crash
247 loader.setLoadHints(QLibrary::ResolveAllSymbolsHint);
248 loader.setFileName( sys_qualifiedLibraryName(fileName: "theplugin")); //a plugin
249 QCOMPARE(loader.loadHints(), QLibrary::ResolveAllSymbolsHint);
250}
251
252void tst_QPluginLoader::deleteinstanceOnUnload()
253{
254#if !defined(QT_SHARED)
255 QSKIP("This test requires Qt to create shared libraries.");
256#endif
257 for (int pass = 0; pass < 2; ++pass) {
258 QPluginLoader loader1;
259 loader1.setFileName( sys_qualifiedLibraryName(fileName: "theplugin")); //a plugin
260 if (pass == 0)
261 loader1.load(); // not recommended, instance() should do the job.
262 PluginInterface *instance1 = qobject_cast<PluginInterface*>(object: loader1.instance());
263 QVERIFY(instance1);
264 QCOMPARE(instance1->pluginName(), QLatin1String("Plugin ok"));
265
266 QPluginLoader loader2;
267 loader2.setFileName( sys_qualifiedLibraryName(fileName: "theplugin")); //a plugin
268 if (pass == 0)
269 loader2.load(); // not recommended, instance() should do the job.
270 PluginInterface *instance2 = qobject_cast<PluginInterface*>(object: loader2.instance());
271 QCOMPARE(instance2->pluginName(), QLatin1String("Plugin ok"));
272
273 QSignalSpy spy1(loader1.instance(), &QObject::destroyed);
274 QSignalSpy spy2(loader2.instance(), &QObject::destroyed);
275 QVERIFY(spy1.isValid());
276 QVERIFY(spy2.isValid());
277 if (pass == 0) {
278 QCOMPARE(loader2.unload(), false); // refcount not reached 0, not really unloaded
279 QCOMPARE(spy1.count(), 0);
280 QCOMPARE(spy2.count(), 0);
281 }
282 QCOMPARE(instance1->pluginName(), QLatin1String("Plugin ok"));
283 QCOMPARE(instance2->pluginName(), QLatin1String("Plugin ok"));
284 QVERIFY(loader1.unload()); // refcount reached 0, did really unload
285 QCOMPARE(spy1.count(), 1);
286 QCOMPARE(spy2.count(), 1);
287 }
288}
289
290void tst_QPluginLoader::loadDebugObj()
291{
292#if !defined(QT_SHARED)
293 QSKIP("This test requires a shared build of Qt, as QPluginLoader::setFileName is a no-op in static builds");
294#endif
295#if defined (__ELF__)
296 QVERIFY(QFile::exists(QFINDTESTDATA("elftest/debugobj.so")));
297 QPluginLoader lib1(QFINDTESTDATA("elftest/debugobj.so"));
298 QCOMPARE(lib1.load(), false);
299#endif
300}
301
302void tst_QPluginLoader::loadCorruptElf()
303{
304#if !defined(QT_SHARED)
305 QSKIP("This test requires a shared build of Qt, as QPluginLoader::setFileName is a no-op in static builds");
306#endif
307#if defined (__ELF__)
308 if (sizeof(void*) == 8) {
309 QVERIFY(QFile::exists(QFINDTESTDATA("elftest/corrupt1.elf64.so")));
310
311 QPluginLoader lib1(QFINDTESTDATA("elftest/corrupt1.elf64.so"));
312 QCOMPARE(lib1.load(), false);
313 QVERIFY2(lib1.errorString().contains("not an ELF object"), qPrintable(lib1.errorString()));
314
315 QPluginLoader lib2(QFINDTESTDATA("elftest/corrupt2.elf64.so"));
316 QCOMPARE(lib2.load(), false);
317 QVERIFY2(lib2.errorString().contains("invalid"), qPrintable(lib2.errorString()));
318
319 QPluginLoader lib3(QFINDTESTDATA("elftest/corrupt3.elf64.so"));
320 QCOMPARE(lib3.load(), false);
321 QVERIFY2(lib3.errorString().contains("invalid"), qPrintable(lib3.errorString()));
322 } else if (sizeof(void*) == 4) {
323 QPluginLoader libW(QFINDTESTDATA("elftest/corrupt3.elf64.so"));
324 QCOMPARE(libW.load(), false);
325 QVERIFY2(libW.errorString().contains("architecture"), qPrintable(libW.errorString()));
326 } else {
327 QFAIL("Please port QElfParser to this platform or blacklist this test.");
328 }
329#endif
330}
331
332void tst_QPluginLoader::loadMachO_data()
333{
334#if defined(QT_BUILD_INTERNAL) && defined(Q_OF_MACH_O)
335 QTest::addColumn<int>("parseResult");
336
337 QTest::newRow("/dev/null") << int(QMachOParser::NotSuitable);
338 QTest::newRow("elftest/debugobj.so") << int(QMachOParser::NotSuitable);
339 QTest::newRow("tst_qpluginloader.cpp") << int(QMachOParser::NotSuitable);
340 QTest::newRow("tst_qpluginloader") << int(QMachOParser::NotSuitable);
341
342# ifdef Q_PROCESSOR_X86_64
343 QTest::newRow("machtest/good.x86_64.dylib") << int(QMachOParser::QtMetaDataSection);
344 QTest::newRow("machtest/good.arm64.dylib") << int(QMachOParser::NotSuitable);
345 QTest::newRow("machtest/good.fat.no-x86_64.dylib") << int(QMachOParser::NotSuitable);
346 QTest::newRow("machtest/good.fat.no-arm64.dylib") << int(QMachOParser::QtMetaDataSection);
347# elif defined(Q_PROCESSOR_ARM)
348 QTest::newRow("machtest/good.arm64.dylib") << int(QMachOParser::QtMetaDataSection);
349 QTest::newRow("machtest/good.x86_64.dylib") << int(QMachOParser::NotSuitable);
350 QTest::newRow("machtest/good.fat.no-arm64.dylib") << int(QMachOParser::NotSuitable);
351 QTest::newRow("machtest/good.fat.no-x86_64.dylib") << int(QMachOParser::QtMetaDataSection);
352# endif
353
354 QTest::newRow("machtest/good.fat.all.dylib") << int(QMachOParser::QtMetaDataSection);
355 QTest::newRow("machtest/good.fat.stub-x86_64.dylib") << int(QMachOParser::NotSuitable);
356 QTest::newRow("machtest/good.fat.stub-arm64.dylib") << int(QMachOParser::NotSuitable);
357
358 QDir d(QFINDTESTDATA("machtest"));
359 QStringList badlist = d.entryList(QStringList() << "bad*.dylib");
360 foreach (const QString &bad, badlist)
361 QTest::newRow(qPrintable("machtest/" + bad)) << int(QMachOParser::NotSuitable);
362#endif
363}
364
365void tst_QPluginLoader::loadMachO()
366{
367#if defined(QT_BUILD_INTERNAL) && defined(Q_OF_MACH_O)
368 QFile f(QFINDTESTDATA(QTest::currentDataTag()));
369 QVERIFY(f.open(QIODevice::ReadOnly));
370 QByteArray data = f.readAll();
371
372 qsizetype pos;
373 qsizetype len;
374 QString errorString;
375 int r = QMachOParser::parse(data.constData(), data.size(), f.fileName(), &errorString, &pos, &len);
376
377 QFETCH(int, parseResult);
378 QCOMPARE(r, parseResult);
379
380 if (r == QMachOParser::NotSuitable)
381 return;
382
383 QVERIFY(pos > 0);
384 QVERIFY(len >= sizeof(void*));
385 QVERIFY(pos + long(len) < data.size());
386 QCOMPARE(pos & (sizeof(void*) - 1), 0UL);
387
388 void *value = *(void**)(data.constData() + pos);
389 QCOMPARE(value, sizeof(void*) > 4 ? (void*)(0xc0ffeec0ffeeL) : (void*)0xc0ffee);
390
391 // now that we know it's valid, let's try to make it invalid
392 ulong offeredlen = pos;
393 do {
394 --offeredlen;
395 r = QMachOParser::parse(data.constData(), offeredlen, f.fileName(), &errorString, &pos, &len);
396 QVERIFY2(r == QMachOParser::NotSuitable, qPrintable(QString("Failed at size 0x%1").arg(offeredlen, 0, 16)));
397 } while (offeredlen);
398#endif
399}
400
401#if defined (Q_OS_UNIX)
402void tst_QPluginLoader::loadGarbage()
403{
404#if !defined(QT_SHARED)
405 QSKIP("This test requires a shared build of Qt, as QPluginLoader::setFileName is a no-op in static builds");
406#endif
407 for (int i=0; i<5; i++) {
408 const QString name = QLatin1String("elftest/garbage") + QString::number(i + 1) + QLatin1String(".so");
409 QPluginLoader lib(QFINDTESTDATA(name));
410 QCOMPARE(lib.load(), false);
411 QVERIFY(lib.errorString() != QString("Unknown error"));
412 }
413}
414#endif
415
416void tst_QPluginLoader::relativePath()
417{
418#if !defined(QT_SHARED)
419 QSKIP("This test requires Qt to create shared libraries.");
420#endif
421 // Windows binaries run from release and debug subdirs, so we can't rely on the current dir.
422 const QString binDir = QFINDTESTDATA("bin");
423 QVERIFY(!binDir.isEmpty());
424 QCoreApplication::addLibraryPath(binDir);
425 QPluginLoader loader("theplugin");
426 loader.load(); // not recommended, instance() should do the job.
427 PluginInterface *instance = qobject_cast<PluginInterface*>(object: loader.instance());
428 QVERIFY(instance);
429 QCOMPARE(instance->pluginName(), QLatin1String("Plugin ok"));
430 QVERIFY(loader.unload());
431}
432
433void tst_QPluginLoader::absolutePath()
434{
435#if !defined(QT_SHARED)
436 QSKIP("This test requires Qt to create shared libraries.");
437#endif
438 // Windows binaries run from release and debug subdirs, so we can't rely on the current dir.
439 const QString binDir = QFINDTESTDATA("bin");
440 QVERIFY(!binDir.isEmpty());
441 QVERIFY(QDir::isAbsolutePath(binDir));
442 QPluginLoader loader(binDir + "/theplugin");
443 loader.load(); // not recommended, instance() should do the job.
444 PluginInterface *instance = qobject_cast<PluginInterface*>(object: loader.instance());
445 QVERIFY(instance);
446 QCOMPARE(instance->pluginName(), QLatin1String("Plugin ok"));
447 QVERIFY(loader.unload());
448}
449
450void tst_QPluginLoader::reloadPlugin()
451{
452#if !defined(QT_SHARED)
453 QSKIP("This test requires Qt to create shared libraries.");
454#endif
455 QPluginLoader loader;
456 loader.setFileName( sys_qualifiedLibraryName(fileName: "theplugin")); //a plugin
457 loader.load(); // not recommended, instance() should do the job.
458 PluginInterface *instance = qobject_cast<PluginInterface*>(object: loader.instance());
459 QVERIFY(instance);
460 QCOMPARE(instance->pluginName(), QLatin1String("Plugin ok"));
461
462 QSignalSpy spy(loader.instance(), &QObject::destroyed);
463 QVERIFY(spy.isValid());
464 QVERIFY(loader.unload()); // refcount reached 0, did really unload
465 QCOMPARE(spy.count(), 1);
466
467 // reload plugin
468 QVERIFY(loader.load());
469 QVERIFY(loader.isLoaded());
470
471 PluginInterface *instance2 = qobject_cast<PluginInterface*>(object: loader.instance());
472 QVERIFY(instance2);
473 QCOMPARE(instance2->pluginName(), QLatin1String("Plugin ok"));
474
475 QVERIFY(loader.unload());
476}
477
478void tst_QPluginLoader::preloadedPlugin_data()
479{
480 QTest::addColumn<bool>(name: "doLoad");
481 QTest::addColumn<QString>(name: "libname");
482 QTest::newRow(dataTag: "create-plugin") << false << sys_qualifiedLibraryName(fileName: "theplugin");
483 QTest::newRow(dataTag: "load-plugin") << true << sys_qualifiedLibraryName(fileName: "theplugin");
484 QTest::newRow(dataTag: "create-non-plugin") << false << sys_qualifiedLibraryName(fileName: "tst_qpluginloaderlib");
485 QTest::newRow(dataTag: "load-non-plugin") << true << sys_qualifiedLibraryName(fileName: "tst_qpluginloaderlib");
486}
487
488void tst_QPluginLoader::preloadedPlugin()
489{
490#if !defined(QT_SHARED)
491 QSKIP("This test requires Qt to create shared libraries.");
492#endif
493 // check that using QPluginLoader does not interfere with QLibrary
494 QFETCH(QString, libname);
495 QLibrary lib(libname);
496 QVERIFY(lib.load());
497
498 typedef int *(*pf_t)();
499 pf_t pf = (pf_t)lib.resolve(symbol: "pointerAddress");
500 QVERIFY(pf);
501
502 int *pluginVariable = pf();
503 QVERIFY(pluginVariable);
504 QCOMPARE(*pluginVariable, 0xc0ffee);
505
506 {
507 // load the plugin
508 QPluginLoader loader(libname);
509 QFETCH(bool, doLoad);
510 if (doLoad && loader.load()) {
511 // unload() returns false because QLibrary has it loaded
512 QVERIFY(!loader.unload());
513 }
514 }
515
516 QVERIFY(lib.isLoaded());
517
518 // if the library was unloaded behind our backs, the following will crash:
519 QCOMPARE(*pluginVariable, 0xc0ffee);
520 QVERIFY(lib.unload());
521}
522
523void tst_QPluginLoader::staticPlugins()
524{
525 const QObjectList instances = QPluginLoader::staticInstances();
526 QVERIFY(instances.size());
527
528 bool found = false;
529 for (QObject *obj : instances) {
530 found = obj->metaObject()->className() == QLatin1String("StaticPlugin");
531 if (found)
532 break;
533 }
534 QVERIFY(found);
535
536 const auto plugins = QPluginLoader::staticPlugins();
537 QCOMPARE(plugins.size(), instances.size());
538
539 // find the metadata
540 QJsonObject metaData;
541 for (const auto &p : plugins) {
542 metaData = p.metaData();
543 found = metaData.value(key: "className").toString() == QLatin1String("StaticPlugin");
544 if (found)
545 break;
546 }
547 QVERIFY(found);
548
549 // We don't store the patch release version anymore (since 5.13)
550 QCOMPARE(metaData.value("version").toInt() / 0x100, QT_VERSION / 0x100);
551 QCOMPARE(metaData.value("IID").toString(), "SomeIID");
552 QCOMPARE(metaData.value("ExtraMetaData"), QJsonArray({ "StaticPlugin", "foo" }));
553 QCOMPARE(metaData.value("URI").toString(), "qt.test.pluginloader.staticplugin");
554}
555
556
557QTEST_MAIN(tst_QPluginLoader)
558#include "tst_qpluginloader.moc"
559

source code of qtbase/tests/auto/corelib/plugin/qpluginloader/tst_qpluginloader.cpp