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#include <QtTest/QtTest>
30
31#include <QCoreApplication>
32#include <QDebug>
33#include <QDir>
34#include <qplugin.h>
35#include <QPluginLoader>
36
37#include <private/qplugin_p.h>
38
39class tst_QPlugin : public QObject
40{
41 Q_OBJECT
42
43 QDir dir;
44
45public:
46 tst_QPlugin();
47
48private slots:
49 void initTestCase();
50 void loadDebugPlugin();
51 void loadReleasePlugin();
52 void scanInvalidPlugin_data();
53 void scanInvalidPlugin();
54};
55
56tst_QPlugin::tst_QPlugin()
57 : dir(QFINDTESTDATA("plugins"))
58{
59}
60
61void tst_QPlugin::initTestCase()
62{
63 QVERIFY2(dir.exists(),
64 qPrintable(QString::fromLatin1("Cannot find the 'plugins' directory starting from '%1'").
65 arg(QDir::toNativeSeparators(QDir::currentPath()))));
66}
67
68void tst_QPlugin::loadDebugPlugin()
69{
70 const auto fileNames = dir.entryList(nameFilters: QStringList() << "*debug*", filters: QDir::Files);
71 if (fileNames.isEmpty())
72 QSKIP("No debug plugins found - skipping test");
73
74 for (const QString &fileName : fileNames) {
75 if (!QLibrary::isLibrary(fileName))
76 continue;
77 QPluginLoader loader(dir.filePath(fileName));
78#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC)
79 // we can always load a plugin on unix
80 QVERIFY(loader.load());
81 QObject *object = loader.instance();
82 QVERIFY(object != 0);
83#else
84 // loading a plugin is dependent on which lib we are running against
85# if defined(QT_NO_DEBUG)
86 // release build, we cannot load debug plugins
87 QVERIFY(!loader.load());
88# else
89 // debug build, we can load debug plugins
90 QVERIFY(loader.load());
91 QObject *object = loader.instance();
92 QVERIFY(object != 0);
93# endif
94#endif
95 }
96}
97
98void tst_QPlugin::loadReleasePlugin()
99{
100 const auto fileNames = dir.entryList(nameFilters: QStringList() << "*release*", filters: QDir::Files);
101 if (fileNames.isEmpty())
102 QSKIP("No release plugins found - skipping test");
103
104 for (const QString &fileName : fileNames) {
105 if (!QLibrary::isLibrary(fileName))
106 continue;
107 QPluginLoader loader(dir.filePath(fileName));
108#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC)
109 // we can always load a plugin on unix
110 QVERIFY(loader.load());
111 QObject *object = loader.instance();
112 QVERIFY(object != 0);
113#else
114 // loading a plugin is dependent on which lib we are running against
115# if defined(QT_NO_DEBUG)
116 // release build, we can load debug plugins
117 QVERIFY(loader.load());
118 QObject *object = loader.instance();
119 QVERIFY(object != 0);
120# else
121 // debug build, we cannot load debug plugins
122 QVERIFY(!loader.load());
123# endif
124#endif
125 }
126}
127
128void tst_QPlugin::scanInvalidPlugin_data()
129{
130 QTest::addColumn<QByteArray>(name: "metadata");
131 QTest::addColumn<bool>(name: "loads");
132 QTest::addColumn<QString>(name: "errMsg");
133
134#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
135 // Binary JSON metadata
136 QByteArray prefix = "QTMETADATA ";
137
138 {
139 QJsonObject obj;
140 obj.insert(key: "IID", value: "org.qt-project.tst_qplugin");
141 obj.insert(key: "className", value: "tst");
142 obj.insert(key: "version", value: int(QT_VERSION));
143#ifdef QT_NO_DEBUG
144 obj.insert("debug", false);
145#else
146 obj.insert(key: "debug", value: true);
147#endif
148 obj.insert(key: "MetaData", value: QJsonObject());
149 QTest::newRow(dataTag: "json-control") << (prefix + QJsonDocument(obj).toBinaryData()) << true << "";
150 }
151
152 QTest::newRow(dataTag: "json-zeroes") << prefix << false << " ";
153
154 prefix += "qbjs";
155 QTest::newRow(dataTag: "bad-json-version0") << prefix << false << " ";
156 QTest::newRow(dataTag: "bad-json-version2") << (prefix + QByteArray("\2\0\0\0", 4)) << false << " ";
157
158 // valid qbjs version 1
159 prefix += QByteArray("\1\0\0\0");
160
161 // too large for the file (100 MB)
162 QTest::newRow(dataTag: "bad-json-size-large1") << (prefix + QByteArray("\0\0\x40\x06")) << false << " ";
163
164 // too large for binary JSON (512 MB)
165 QTest::newRow(dataTag: "bad-json-size-large2") << (prefix + QByteArray("\0\0\0\x20")) << false << " ";
166
167 // could overflow
168 QTest::newRow(dataTag: "bad-json-size-large3") << (prefix + "\xff\xff\xff\x7f") << false << " ";
169#endif
170
171 // CBOR metadata
172 QByteArray cprefix = "QTMETADATA !1234";
173 cprefix[12] = 0; // current version
174 cprefix[13] = QT_VERSION_MAJOR;
175 cprefix[14] = QT_VERSION_MINOR;
176 cprefix[15] = qPluginArchRequirements();
177
178 QByteArray cborValid = [] {
179 QCborMap m;
180 m.insert(key: int(QtPluginMetaDataKeys::IID), value_: QLatin1String("org.qt-project.tst_qplugin"));
181 m.insert(key: int(QtPluginMetaDataKeys::ClassName), value_: QLatin1String("tst"));
182 m.insert(key: int(QtPluginMetaDataKeys::MetaData), value_: QCborMap());
183 return QCborValue(m).toCbor();
184 }();
185 QTest::newRow(dataTag: "cbor-control") << (cprefix + cborValid) << true << "";
186
187 cprefix[12] = 1;
188 QTest::newRow(dataTag: "cbor-major-too-new") << (cprefix + cborValid) << false
189 << " Invalid metadata version";
190
191 cprefix[12] = 0;
192 cprefix[13] = QT_VERSION_MAJOR + 1;
193 QTest::newRow(dataTag: "cbor-major-too-new") << (cprefix + cborValid) << false << "";
194
195 cprefix[13] = QT_VERSION_MAJOR - 1;
196 QTest::newRow(dataTag: "cbor-major-too-old") << (cprefix + cborValid) << false << "";
197
198 cprefix[13] = QT_VERSION_MAJOR;
199 cprefix[14] = QT_VERSION_MINOR + 1;
200 QTest::newRow(dataTag: "cbor-minor-too-new") << (cprefix + cborValid) << false << "";
201
202 QTest::newRow(dataTag: "cbor-invalid") << (cprefix + "\xff") << false
203 << " Metadata parsing error: Invalid CBOR stream: unexpected 'break' byte";
204 QTest::newRow(dataTag: "cbor-not-map1") << (cprefix + "\x01") << false
205 << " Unexpected metadata contents";
206 QTest::newRow(dataTag: "cbor-not-map2") << (cprefix + "\x81\x01") << false
207 << " Unexpected metadata contents";
208}
209
210static const char invalidPluginSignature[] = "qplugin testfile";
211static qsizetype locateMetadata(const uchar *data, qsizetype len)
212{
213 const uchar *dataend = data + len - strlen(s: invalidPluginSignature);
214
215 for (const uchar *ptr = data; ptr < dataend; ++ptr) {
216 if (*ptr != invalidPluginSignature[0])
217 continue;
218
219 int r = memcmp(s1: ptr, s2: invalidPluginSignature, n: strlen(s: invalidPluginSignature));
220 if (r)
221 continue;
222
223 return ptr - data;
224 }
225
226 return -1;
227}
228
229void tst_QPlugin::scanInvalidPlugin()
230{
231#if defined(Q_OS_MACOS) && defined(Q_PROCESSOR_ARM)
232 QSKIP("This test crashes on ARM macOS");
233#endif
234 const auto fileNames = dir.entryList(nameFilters: {"*invalid*"}, filters: QDir::Files);
235 QString invalidPluginName;
236 if (fileNames.isEmpty())
237 QSKIP("No invalid plugin found - skipping test");
238 else
239 invalidPluginName = dir.absoluteFilePath(fileName: fileNames.first());
240
241
242 // copy the file
243 QFileInfo fn(invalidPluginName);
244 QTemporaryDir tmpdir;
245 QVERIFY(tmpdir.isValid());
246
247 QString newName = tmpdir.path() + '/' + fn.fileName();
248 QVERIFY(QFile::copy(invalidPluginName, newName));
249
250 {
251 QFile f(newName);
252 QVERIFY(f.open(QIODevice::ReadWrite | QIODevice::Unbuffered));
253 QVERIFY(f.size() > qint64(strlen(invalidPluginSignature)));
254 uchar *data = f.map(offset: 0, size: f.size());
255 QVERIFY(data);
256
257 static const qsizetype offset = locateMetadata(data, len: f.size());
258 QVERIFY(offset > 0);
259
260 QFETCH(QByteArray, metadata);
261
262 // sanity check
263 QVERIFY(metadata.size() < 512);
264
265 // replace the data
266 memcpy(dest: data + offset, src: metadata.constData(), n: metadata.size());
267 memset(s: data + offset + metadata.size(), c: 0, n: 512 - metadata.size());
268 }
269
270 // now try to load this
271 QFETCH(bool, loads);
272 QFETCH(QString, errMsg);
273 if (!errMsg.isEmpty())
274 QTest::ignoreMessage(type: QtWarningMsg,
275 message: "Found invalid metadata in lib " + QFile::encodeName(fileName: newName) +
276 ":" + errMsg.toUtf8());
277 QPluginLoader loader(newName);
278 QCOMPARE(loader.load(), loads);
279 if (loads)
280 loader.unload();
281}
282
283QTEST_MAIN(tst_QPlugin)
284#include "tst_qplugin.moc"
285

source code of qtbase/tests/auto/corelib/plugin/qplugin/tst_qplugin.cpp