1 /****************************************************************************
2 **
3 ** Copyright (C) 2015 The Qt Company Ltd.
4 ** Contact: http://www.qt.io/licensing/
5 **
6 ** This file is part of the test suite of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see http://www.qt.io/terms-conditions. For further
15 ** information use the contact form at http://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 or version 3 as published by the Free
20 ** Software Foundation and appearing in the file LICENSE.LGPLv21 and
21 ** LICENSE.LGPLv3 included in the packaging of this file. Please review the
22 ** following information to ensure the GNU Lesser General Public License
23 ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
24 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 **
26 ** As a special exception, The Qt Company gives you certain additional
27 ** rights. These rights are described in The Qt Company LGPL Exception
28 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 **
30 ** GNU General Public License Usage
31 ** Alternatively, this file may be used under the terms of the GNU
32 ** General Public License version 3.0 as published by the Free Software
33 ** Foundation and appearing in the file LICENSE.GPL included in the
34 ** packaging of this file.  Please review the following information to
35 ** ensure the GNU General Public License version 3.0 requirements will be
36 ** met: http://www.gnu.org/copyleft/gpl.html.
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41 
42 
43 #include <QtTest/QtTest>
44 #include <qdir.h>
45 #include <qpluginloader.h>
46 #include "theplugin/plugininterface.h"
47 
48 // Helper macros to let us know if some suffixes are valid
49 #define bundle_VALID    false
50 #define dylib_VALID     false
51 #define sl_VALID        false
52 #define a_VALID         false
53 #define so_VALID        false
54 #define dll_VALID       false
55 
56 #if defined(Q_OS_DARWIN)
57 # undef bundle_VALID
58 # undef dylib_VALID
59 # undef so_VALID
60 # define bundle_VALID   true
61 # define dylib_VALID    true
62 # define so_VALID       true
63 # define SUFFIX         ".dylib"
64 # define PREFIX         "lib"
65 
66 #elif defined(Q_OS_HPUX) && !defined(__ia64)
67 # undef sl_VALID
68 # define sl_VALID       true
69 # define SUFFIX         ".sl"
70 # define PREFIX         "lib"
71 
72 #elif defined(Q_OS_AIX)
73 # undef a_VALID
74 # undef so_VALID
75 # define a_VALID        true
76 # define so_VALID       true
77 # define SUFFIX         ".so"
78 # define PREFIX         "lib"
79 
80 #elif defined(Q_OS_WIN)
81 # undef dll_VALID
82 # define dll_VALID      true
83 # define SUFFIX         ".dll"
84 # define PREFIX         ""
85 
86 #elif defined(Q_OS_SYMBIAN)
87 # undef dll_VALID
88 # define dll_VALID      true
89 # define SUFFIX         ".dll"
90 # define PREFIX         ""
91 
92 #else  // all other Unix
93 # undef so_VALID
94 # define so_VALID       true
95 # define SUFFIX         ".so"
96 # define PREFIX         "lib"
97 #endif
98 
99 //TESTED_CLASS=
100 //TESTED_FILES=
101 
102 QT_FORWARD_DECLARE_CLASS(QPluginLoader)
103 class tst_QPluginLoader : public QObject
104 {
105     Q_OBJECT
106 
107 private slots:
108     void initTestCase();
109     void errorString();
110     void loadHints();
111     void deleteinstanceOnUnload();
112     void checkingStubsFromDifferentDrives();
113     void loadDebugObj();
114     void loadCorruptElf_data();
115     void loadCorruptElf();
116     void loadGarbage();
117 
118 private:
119     QString qualifiedLibraryName(const QString &fileName) const;
120 
121     QString m_bin;
122 };
123 
initTestCase()124 void tst_QPluginLoader::initTestCase()
125 {
126     QDir workingDirectory = QDir::current();
127 #ifdef Q_OS_WIN
128     // cd up to be able to locate the binaries of the sub-processes.
129     if (workingDirectory.absolutePath().endsWith(QLatin1String("/debug"), Qt::CaseInsensitive)
130             || workingDirectory.absolutePath().endsWith(QLatin1String("/release"), Qt::CaseInsensitive)) {
131         QVERIFY(workingDirectory.cdUp());
132         QVERIFY(QDir::setCurrent(workingDirectory.absolutePath()));
133     }
134 #endif
135     m_bin = workingDirectory.absoluteFilePath(QLatin1String("bin"));
136     QVERIFY(QFileInfo(m_bin).isDir());
137 }
138 
qualifiedLibraryName(const QString & fileName) const139 QString tst_QPluginLoader::qualifiedLibraryName(const QString &fileName) const
140 {
141     return m_bin + QLatin1Char('/') + QLatin1String(PREFIX)
142            + fileName + QLatin1String(SUFFIX);
143 }
144 
msgCannotLoadPlugin(const QString & plugin,const QString & why)145 static inline QByteArray msgCannotLoadPlugin(const QString &plugin, const QString &why)
146 {
147     return QString::fromLatin1("Cannot load plugin '%1' from '%2': %3")
148                                .arg(QDir::toNativeSeparators(plugin),
149                                     QDir::toNativeSeparators(QDir::currentPath()),
150                                     why).toLocal8Bit();
151 }
152 
153 //#define SHOW_ERRORS 1
154 
errorString()155 void tst_QPluginLoader::errorString()
156 {
157 #if defined(Q_OS_WINCE)
158     // On WinCE we need an QCoreApplication object for current dir
159     int argc = 0;
160     QCoreApplication app(argc,0);
161 #endif
162     const QString unknown(QLatin1String("Unknown error"));
163 
164     {
165     QPluginLoader loader; // default constructed
166     bool loaded = loader.load();
167 #ifdef SHOW_ERRORS
168     qDebug() << loader.errorString();
169 #endif
170     QCOMPARE(loaded, false);
171     QCOMPARE(loader.errorString(), unknown);
172 
173     QObject *obj = loader.instance();
174 #ifdef SHOW_ERRORS
175     qDebug() << loader.errorString();
176 #endif
177     QCOMPARE(obj, static_cast<QObject*>(0));
178     QCOMPARE(loader.errorString(), unknown);
179 
180     bool unloaded = loader.unload();
181 #ifdef SHOW_ERRORS
182     qDebug() << loader.errorString();
183 #endif
184     QCOMPARE(unloaded, false);
185     QCOMPARE(loader.errorString(), unknown);
186     }
187     {
188     QPluginLoader loader(qualifiedLibraryName(QLatin1String("tst_qpluginloaderlib")));     //not a plugin
189     bool loaded = loader.load();
190 #ifdef SHOW_ERRORS
191     qDebug() << loader.errorString();
192 #endif
193     QCOMPARE(loaded, false);
194     QVERIFY(loader.errorString() != unknown);
195 
196     QObject *obj = loader.instance();
197 #ifdef SHOW_ERRORS
198     qDebug() << loader.errorString();
199 #endif
200     QCOMPARE(obj, static_cast<QObject*>(0));
201     QVERIFY(loader.errorString() != unknown);
202 
203     bool unloaded = loader.unload();
204 #ifdef SHOW_ERRORS
205     qDebug() << loader.errorString();
206 #endif
207     QCOMPARE(unloaded, false);
208     QVERIFY(loader.errorString() != unknown);
209     }
210 
211     {
212     QPluginLoader loader(qualifiedLibraryName(QLatin1String("nosuchfile")));     //not a file
213     bool loaded = loader.load();
214 #ifdef SHOW_ERRORS
215     qDebug() << loader.errorString();
216 #endif
217     QCOMPARE(loaded, false);
218     QVERIFY(loader.errorString() != unknown);
219 
220     QObject *obj = loader.instance();
221 #ifdef SHOW_ERRORS
222     qDebug() << loader.errorString();
223 #endif
224     QCOMPARE(obj, static_cast<QObject*>(0));
225     QVERIFY(loader.errorString() != unknown);
226 
227     bool unloaded = loader.unload();
228 #ifdef SHOW_ERRORS
229     qDebug() << loader.errorString();
230 #endif
231     QCOMPARE(unloaded, false);
232     QVERIFY(loader.errorString() != unknown);
233     }
234 
235 #if !defined Q_OS_WIN && !defined Q_OS_MAC && !defined Q_OS_HPUX && !defined Q_OS_SYMBIAN && !defined Q_OS_QNX
236     {
237     QPluginLoader loader(qualifiedLibraryName(QLatin1String("almostplugin")));     //a plugin with unresolved symbols
238     loader.setLoadHints(QLibrary::ResolveAllSymbolsHint);
239     QCOMPARE(loader.load(), false);
240 #ifdef SHOW_ERRORS
241     qDebug() << loader.errorString();
242 #endif
243     QVERIFY(loader.errorString() != unknown);
244 
245     QCOMPARE(loader.instance(), static_cast<QObject*>(0));
246 #ifdef SHOW_ERRORS
247     qDebug() << loader.errorString();
248 #endif
249     QVERIFY(loader.errorString() != unknown);
250 
251     QCOMPARE(loader.unload(), false);
252 #ifdef SHOW_ERRORS
253     qDebug() << loader.errorString();
254 #endif
255     QVERIFY(loader.errorString() != unknown);
256     }
257 #endif
258 
259     {
260     const QString plugin = qualifiedLibraryName(QLatin1String("theplugin"));
261     QPluginLoader loader(plugin);     //a plugin
262     QVERIFY2(loader.load(), msgCannotLoadPlugin(plugin, loader.errorString()).constData());
263     QCOMPARE(loader.errorString(), unknown);
264 
265     QVERIFY(loader.instance() !=  static_cast<QObject*>(0));
266     QCOMPARE(loader.errorString(), unknown);
267 
268     // Make sure that plugin really works
269     PluginInterface* theplugin = qobject_cast<PluginInterface*>(loader.instance());
270     QString pluginName = theplugin->pluginName();
271     QCOMPARE(pluginName, QLatin1String("Plugin ok"));
272 
273     QCOMPARE(loader.unload(), true);
274     QCOMPARE(loader.errorString(), unknown);
275     }
276 }
277 
loadHints()278 void tst_QPluginLoader::loadHints()
279 {
280     QPluginLoader loader;
281     QCOMPARE(loader.loadHints(), (QLibrary::LoadHints)0);   //Do not crash
282     loader.setLoadHints(QLibrary::ResolveAllSymbolsHint);
283     loader.setFileName(qualifiedLibraryName(QLatin1String("theplugin")));     //a plugin
284     QCOMPARE(loader.loadHints(), QLibrary::ResolveAllSymbolsHint);
285 }
286 
deleteinstanceOnUnload()287 void tst_QPluginLoader::deleteinstanceOnUnload()
288 {
289     for (int pass = 0; pass < 4; ++pass) {
290         QPluginLoader loader1;
291         const QString plugin = qualifiedLibraryName(QLatin1String("theplugin"));
292         loader1.setFileName(plugin);     //a plugin
293         if (pass < 2)
294             loader1.load(); // not recommended, instance() should do the job.
295         PluginInterface *instance1 = qobject_cast<PluginInterface*>(loader1.instance());
296         QVERIFY2(instance1, msgCannotLoadPlugin(plugin, loader1.errorString()).constData());
297         QCOMPARE(instance1->pluginName(), QLatin1String("Plugin ok"));
298 
299         QPluginLoader loader2;
300         loader2.setFileName(qualifiedLibraryName(QLatin1String("theplugin")));     //a plugin
301         if (pass < 2)
302             loader2.load(); // not recommended, instance() should do the job.
303         PluginInterface *instance2 = qobject_cast<PluginInterface*>(loader2.instance());
304         QCOMPARE(instance2->pluginName(), QLatin1String("Plugin ok"));
305 
306         QSignalSpy spy1(loader1.instance(), SIGNAL(destroyed()));
307         QSignalSpy spy2(loader2.instance(), SIGNAL(destroyed()));
308 
309         // refcount not reached 0, not really unloaded
310         if (pass % 2)
311             QCOMPARE(loader1.unload(), false);
312         else
313             QCOMPARE(loader2.unload(), false);
314 
315         QCOMPARE(spy1.count(), 0);
316         QCOMPARE(spy2.count(), 0);
317 
318         QCOMPARE(instance1->pluginName(), QLatin1String("Plugin ok"));
319         QCOMPARE(instance2->pluginName(), QLatin1String("Plugin ok"));
320 
321         // refcount reached 0, did really unload
322         if (pass % 2)
323             QVERIFY(loader2.unload());
324         else
325             QVERIFY(loader1.unload());
326 
327         QCOMPARE(spy1.count(), 1);
328         QCOMPARE(spy2.count(), 1);
329     }
330 }
331 
checkingStubsFromDifferentDrives()332 void tst_QPluginLoader::checkingStubsFromDifferentDrives()
333 {
334 #if defined(Q_OS_SYMBIAN)
335 
336     // This test needs C-drive + some additional drive (driveForStubs)
337 
338     const QString driveForStubs("E:/");// != "C:/"
339     const QString stubDir("system/temp/stubtest/");
340     const QString stubName("dummyStub.qtplugin");
341     const QString fullStubFileName(stubDir + stubName);
342     QDir dir(driveForStubs);
343     bool test1(false); bool test2(false);
344 
345     // initial clean up
346     QFile::remove(driveForStubs + fullStubFileName);
347     dir.rmdir(driveForStubs + stubDir);
348 
349     // create a stub dir and do stub drive check
350     if (!dir.mkpath(stubDir))
351         QSKIP("Required drive not available for this test", SkipSingle);
352 
353     {// test without stub, should not be found
354     QPluginLoader loader("C:/" + fullStubFileName);
355     test1 = !loader.fileName().length();
356     }
357 
358     // create a stub to defined drive
359     QFile tempFile(driveForStubs + fullStubFileName);
360     tempFile.open(QIODevice::ReadWrite);
361     QFileInfo fileInfo(tempFile);
362 
363     {// now should be found even tried to find from C:
364     QPluginLoader loader("C:/" + fullStubFileName);
365     test2 = (loader.fileName() == fileInfo.absoluteFilePath());
366     }
367 
368     // clean up
369     tempFile.close();
370     if (!QFile::remove(driveForStubs + fullStubFileName))
371         QWARN("Could not remove stub file");
372     if (!dir.rmdir(driveForStubs + stubDir))
373         QWARN("Could not remove stub directory");
374 
375     // test after cleanup
376     QVERIFY(test1);
377     QVERIFY(test2);
378 
379 #endif//Q_OS_SYMBIAN
380 }
381 
loadDebugObj()382 void tst_QPluginLoader::loadDebugObj()
383 {
384 #ifdef __ELF__
385     const QString file = QString::fromLatin1(SRCDIR "elftest/debugobj.so");
386     QVERIFY(QFile::exists(file));
387     QPluginLoader lib1(file);
388     QCOMPARE(lib1.load(), false);
389 #else
390     QSKIP("Test requires __ELF", SkipAll);
391 #endif
392 }
393 
394 /* loadCorruptElf() verifies that the library loader returns the correct error message.
395  * Note that Qt's plugin cache interferes here: on a fresh checkout, the "real" error
396  * message will be reported, since no cache exists or the timestamp is different. When
397  * run for the 2nd time, "not a valid Qt plugin" will be reported from the plugin cache.
398  * "Plugin verification data mismatch" has also been observed.
399  * This could arguably be fixed by copying the file to a temporary file each time, but
400  * that would grow the cache on the target machine. */
401 
loadCorruptElf_data()402 void tst_QPluginLoader::loadCorruptElf_data()
403 {
404 #ifdef __ELF__
405     QTest::addColumn<QString>("file");
406     QTest::addColumn<QString>("error");
407     const QString folder = QLatin1String(SRCDIR "elftest/");
408     const QString invalidElfMessage = QLatin1String("is an invalid ELF object");
409     if (sizeof(void*) == 8) {
410         QTest::newRow("64bit-corrupt1")
411             << (folder + QLatin1String("corrupt1.elf64.so"))
412             << QString::fromLatin1("is not an ELF object");
413         QTest::newRow("64bit-corrupt2")
414             << (folder + QLatin1String("corrupt2.elf64.so")) << invalidElfMessage;
415         QTest::newRow("64bit-corrupt3")
416             << (folder + QLatin1String("corrupt3.elf64.so")) << invalidElfMessage;
417     } else if (sizeof(void*) == 4) {
418         QTest::newRow("32bit-corrupt3")
419             << (folder + QLatin1String("corrupt3.elf64.so"))
420             << QString::fromLatin1("architecture");
421     } else {
422         Q_ASSERT_X(false, Q_FUNC_INFO, "Please port QElfParser to this platform or blacklist this test.");
423     }
424 #endif
425 }
426 
loadCorruptElf()427 void tst_QPluginLoader::loadCorruptElf()
428 {
429 #ifdef __ELF__
430     QFETCH(QString, file);
431     QFETCH(QString, error);
432 
433     QVERIFY(QFile::exists(file));
434     QPluginLoader lib(file);
435     QCOMPARE(lib.load(), false);
436     const QString errorString = lib.errorString();
437     QVERIFY2(errorString.contains(error) || errorString.contains("not a valid Qt plugin") || errorString.contains("Plugin verification data mismatch"),
438              qPrintable(lib.errorString()));
439 #endif
440 }
441 
loadGarbage()442 void tst_QPluginLoader::loadGarbage()
443 {
444 #if defined (Q_OS_UNIX) && !defined(Q_OS_SYMBIAN)
445     for (int i = 1; i <= 5; ++i) {
446         QPluginLoader lib(QString::fromLatin1(SRCDIR "elftest/garbage%1.so").arg(i));
447         QCOMPARE(lib.load(), false);
448 #ifdef SHOW_ERRORS
449         qDebug() << lib.errorString();
450 #endif
451     }
452 #endif
453 }
454 
455 QTEST_APPLESS_MAIN(tst_QPluginLoader)
456 #include "tst_qpluginloader.moc"
457