1 | // Copyright (C) 2021 The Qt Company Ltd. |
2 | // Copyright (C) 2022 Intel Corporation. |
3 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
4 | |
5 | #include "qfactoryloader_p.h" |
6 | |
7 | #ifndef QT_NO_QOBJECT |
8 | #include "qfactoryinterface.h" |
9 | |
10 | #include "private/qcoreapplication_p.h" |
11 | #include "private/qduplicatetracker_p.h" |
12 | #include "private/qloggingregistry_p.h" |
13 | #include "private/qobject_p.h" |
14 | #include "qcborarray.h" |
15 | #include "qcbormap.h" |
16 | #include "qcborvalue.h" |
17 | #include "qcborvalue.h" |
18 | #include "qdiriterator.h" |
19 | #include "qfileinfo.h" |
20 | #include "qjsonarray.h" |
21 | #include "qjsondocument.h" |
22 | #include "qjsonobject.h" |
23 | #include "qmap.h" |
24 | #include "qmutex.h" |
25 | #include "qplugin.h" |
26 | #include "qplugin_p.h" |
27 | #include "qpluginloader.h" |
28 | |
29 | #if QT_CONFIG(library) |
30 | # include "qlibrary_p.h" |
31 | #endif |
32 | |
33 | #include <qtcore_tracepoints_p.h> |
34 | |
35 | #include <vector> |
36 | |
37 | QT_BEGIN_NAMESPACE |
38 | |
39 | using namespace Qt::StringLiterals; |
40 | |
41 | Q_TRACE_POINT(qtcore, QFactoryLoader_update, const QString &fileName); |
42 | |
43 | bool QPluginParsedMetaData::parse(QByteArrayView raw) |
44 | { |
45 | QPluginMetaData::Header ; |
46 | Q_ASSERT(raw.size() >= qsizetype(sizeof(header))); |
47 | memcpy(dest: &header, src: raw.data(), n: sizeof(header)); |
48 | if (Q_UNLIKELY(header.version > QPluginMetaData::CurrentMetaDataVersion)) |
49 | return setError(QFactoryLoader::tr(s: "Invalid metadata version" )); |
50 | |
51 | // use fromRawData to keep QCborStreamReader from copying |
52 | raw = raw.sliced(pos: sizeof(header)); |
53 | QByteArray ba = QByteArray::fromRawData(data: raw.data(), size: raw.size()); |
54 | QCborParserError err; |
55 | QCborValue metadata = QCborValue::fromCbor(ba, error: &err); |
56 | |
57 | if (err.error != QCborError::NoError) |
58 | return setError(QFactoryLoader::tr(s: "Metadata parsing error: %1" ).arg(a: err.error.toString())); |
59 | if (!metadata.isMap()) |
60 | return setError(QFactoryLoader::tr(s: "Unexpected metadata contents" )); |
61 | QCborMap map = metadata.toMap(); |
62 | metadata = {}; |
63 | |
64 | DecodedArchRequirements archReq = |
65 | header.version == 0 ? decodeVersion0ArchRequirements(value: header.plugin_arch_requirements) |
66 | : decodeVersion1ArchRequirements(value: header.plugin_arch_requirements); |
67 | |
68 | // insert the keys not stored in the top-level CBOR map |
69 | map[int(QtPluginMetaDataKeys::QtVersion)] = |
70 | QT_VERSION_CHECK(header.qt_major_version, header.qt_minor_version, 0); |
71 | map[int(QtPluginMetaDataKeys::IsDebug)] = archReq.isDebug; |
72 | map[int(QtPluginMetaDataKeys::Requirements)] = archReq.level; |
73 | |
74 | data = std::move(map); |
75 | return true; |
76 | } |
77 | |
78 | QJsonObject QPluginParsedMetaData::toJson() const |
79 | { |
80 | // convert from the internal CBOR representation to an external JSON one |
81 | QJsonObject o; |
82 | for (auto it : data.toMap()) { |
83 | QString key; |
84 | if (it.first.isInteger()) { |
85 | switch (it.first.toInteger()) { |
86 | #define CONVERT_TO_STRING(IntKey, StringKey, Description) \ |
87 | case int(IntKey): key = QStringLiteral(StringKey); break; |
88 | QT_PLUGIN_FOREACH_METADATA(CONVERT_TO_STRING) |
89 | } |
90 | } else { |
91 | key = it.first.toString(); |
92 | } |
93 | |
94 | if (!key.isEmpty()) |
95 | o.insert(key, value: it.second.toJsonValue()); |
96 | } |
97 | return o; |
98 | } |
99 | |
100 | class QFactoryLoaderPrivate : public QObjectPrivate |
101 | { |
102 | Q_DECLARE_PUBLIC(QFactoryLoader) |
103 | Q_DISABLE_COPY_MOVE(QFactoryLoaderPrivate) |
104 | public: |
105 | QFactoryLoaderPrivate() { } |
106 | QByteArray iid; |
107 | #if QT_CONFIG(library) |
108 | ~QFactoryLoaderPrivate(); |
109 | mutable QMutex mutex; |
110 | QDuplicateTracker<QString> loadedPaths; |
111 | std::vector<QLibraryPrivate::UniquePtr> libraries; |
112 | QMap<QString,QLibraryPrivate*> keyMap; |
113 | QString suffix; |
114 | QString ; |
115 | Qt::CaseSensitivity cs; |
116 | |
117 | void updateSinglePath(const QString &pluginDir); |
118 | #endif |
119 | }; |
120 | |
121 | #if QT_CONFIG(library) |
122 | |
123 | static Q_LOGGING_CATEGORY_WITH_ENV_OVERRIDE(lcFactoryLoader, "QT_DEBUG_PLUGINS" , |
124 | "qt.core.plugin.factoryloader" ) |
125 | |
126 | namespace { |
127 | struct QFactoryLoaderGlobals |
128 | { |
129 | // needs to be recursive because loading one plugin could cause another |
130 | // factory to be initialized |
131 | QRecursiveMutex mutex; |
132 | QList<QFactoryLoader *> loaders; |
133 | }; |
134 | } |
135 | |
136 | Q_GLOBAL_STATIC(QFactoryLoaderGlobals, qt_factoryloader_global) |
137 | |
138 | QFactoryLoaderPrivate::~QFactoryLoaderPrivate() |
139 | = default; |
140 | |
141 | inline void QFactoryLoaderPrivate::updateSinglePath(const QString &path) |
142 | { |
143 | struct LibraryReleaser { |
144 | void operator()(QLibraryPrivate *library) |
145 | { if (library) library->release(); } |
146 | }; |
147 | |
148 | // If we've already loaded, skip it... |
149 | if (loadedPaths.hasSeen(s: path)) |
150 | return; |
151 | |
152 | qCDebug(lcFactoryLoader) << "checking directory path" << path << "..." ; |
153 | |
154 | QDirIterator plugins(path, |
155 | #if defined(Q_OS_WIN) |
156 | QStringList(QStringLiteral("*.dll" )), |
157 | #elif defined(Q_OS_ANDROID) |
158 | QStringList("libplugins_%1_*.so"_L1 .arg(suffix)), |
159 | #endif |
160 | QDir::Files); |
161 | |
162 | while (plugins.hasNext()) { |
163 | QString fileName = plugins.next(); |
164 | #ifdef Q_OS_DARWIN |
165 | const bool isDebugPlugin = fileName.endsWith("_debug.dylib"_L1 ); |
166 | const bool isDebugLibrary = |
167 | #ifdef QT_DEBUG |
168 | true; |
169 | #else |
170 | false; |
171 | #endif |
172 | |
173 | // Skip mismatching plugins so that we don't end up loading both debug and release |
174 | // versions of the same Qt libraries (due to the plugin's dependencies). |
175 | if (isDebugPlugin != isDebugLibrary) |
176 | continue; |
177 | #elif defined(Q_PROCESSOR_X86) |
178 | if (fileName.endsWith(s: ".avx2"_L1 ) || fileName.endsWith(s: ".avx512"_L1 )) { |
179 | // ignore AVX2-optimized file, we'll do a bait-and-switch to it later |
180 | continue; |
181 | } |
182 | #endif |
183 | qCDebug(lcFactoryLoader) << "looking at" << fileName; |
184 | |
185 | Q_TRACE(QFactoryLoader_update, fileName); |
186 | |
187 | QLibraryPrivate::UniquePtr library; |
188 | library.reset(p: QLibraryPrivate::findOrCreate(fileName: QFileInfo(fileName).canonicalFilePath())); |
189 | if (!library->isPlugin()) { |
190 | qCDebug(lcFactoryLoader) << library->errorString << Qt::endl |
191 | << " not a plugin" ; |
192 | continue; |
193 | } |
194 | |
195 | QStringList keys; |
196 | bool metaDataOk = false; |
197 | |
198 | QString iid = library->metaData.value(k: QtPluginMetaDataKeys::IID).toString(); |
199 | if (iid == QLatin1StringView(this->iid.constData(), this->iid.size())) { |
200 | QCborMap object = library->metaData.value(k: QtPluginMetaDataKeys::MetaData).toMap(); |
201 | metaDataOk = true; |
202 | |
203 | const QCborArray k = object.value(key: "Keys"_L1 ).toArray(); |
204 | for (QCborValueConstRef v : k) |
205 | keys += cs ? v.toString() : v.toString().toLower(); |
206 | } |
207 | qCDebug(lcFactoryLoader) << "Got keys from plugin meta data" << keys; |
208 | |
209 | if (!metaDataOk) |
210 | continue; |
211 | |
212 | int keyUsageCount = 0; |
213 | for (const QString &key : std::as_const(t&: keys)) { |
214 | // first come first serve, unless the first |
215 | // library was built with a future Qt version, |
216 | // whereas the new one has a Qt version that fits |
217 | // better |
218 | constexpr int QtVersionNoPatch = QT_VERSION_CHECK(QT_VERSION_MAJOR, QT_VERSION_MINOR, 0); |
219 | QLibraryPrivate *previous = keyMap.value(key); |
220 | int prev_qt_version = 0; |
221 | if (previous) |
222 | prev_qt_version = int(previous->metaData.value(k: QtPluginMetaDataKeys::QtVersion).toInteger()); |
223 | int qt_version = int(library->metaData.value(k: QtPluginMetaDataKeys::QtVersion).toInteger()); |
224 | if (!previous || (prev_qt_version > QtVersionNoPatch && qt_version <= QtVersionNoPatch)) { |
225 | keyMap[key] = library.get(); // we WILL .release() |
226 | ++keyUsageCount; |
227 | } |
228 | } |
229 | if (keyUsageCount || keys.isEmpty()) { |
230 | library->setLoadHints(QLibrary::PreventUnloadHint); // once loaded, don't unload |
231 | QMutexLocker locker(&mutex); |
232 | libraries.push_back(x: std::move(library)); |
233 | } |
234 | }; |
235 | } |
236 | |
237 | void QFactoryLoader::update() |
238 | { |
239 | #ifdef QT_SHARED |
240 | Q_D(QFactoryLoader); |
241 | |
242 | const QStringList paths = QCoreApplication::libraryPaths(); |
243 | for (const QString &pluginDir : paths) { |
244 | #ifdef Q_OS_ANDROID |
245 | QString path = pluginDir; |
246 | #else |
247 | QString path = pluginDir + d->suffix; |
248 | #endif |
249 | |
250 | d->updateSinglePath(path); |
251 | } |
252 | if (!d->extraSearchPath.isEmpty()) |
253 | d->updateSinglePath(path: d->extraSearchPath); |
254 | #else |
255 | Q_D(QFactoryLoader); |
256 | qCDebug(lcFactoryLoader) << "ignoring" << d->iid |
257 | << "since plugins are disabled in static builds" ; |
258 | #endif |
259 | } |
260 | |
261 | QFactoryLoader::~QFactoryLoader() |
262 | { |
263 | if (!qt_factoryloader_global.isDestroyed()) { |
264 | QMutexLocker locker(&qt_factoryloader_global->mutex); |
265 | qt_factoryloader_global->loaders.removeOne(t: this); |
266 | } |
267 | } |
268 | |
269 | #if defined(Q_OS_UNIX) && !defined (Q_OS_DARWIN) |
270 | QLibraryPrivate *QFactoryLoader::library(const QString &key) const |
271 | { |
272 | Q_D(const QFactoryLoader); |
273 | return d->keyMap.value(key: d->cs ? key : key.toLower()); |
274 | } |
275 | #endif |
276 | |
277 | void QFactoryLoader::refreshAll() |
278 | { |
279 | if (qt_factoryloader_global.exists()) { |
280 | QMutexLocker locker(&qt_factoryloader_global->mutex); |
281 | for (QFactoryLoader *loader : std::as_const(t&: qt_factoryloader_global->loaders)) |
282 | loader->update(); |
283 | } |
284 | } |
285 | |
286 | #endif // QT_CONFIG(library) |
287 | |
288 | QFactoryLoader::QFactoryLoader(const char *iid, |
289 | const QString &suffix, |
290 | Qt::CaseSensitivity cs) |
291 | : QObject(*new QFactoryLoaderPrivate) |
292 | { |
293 | Q_ASSERT_X(suffix.startsWith(u'/'), "QFactoryLoader" , |
294 | "For historical reasons, the suffix must start with '/' (and it can't be empty)" ); |
295 | |
296 | moveToThread(thread: QCoreApplicationPrivate::mainThread()); |
297 | Q_D(QFactoryLoader); |
298 | d->iid = iid; |
299 | #if QT_CONFIG(library) |
300 | d->cs = cs; |
301 | d->suffix = suffix; |
302 | # ifdef Q_OS_ANDROID |
303 | if (!d->suffix.isEmpty() && d->suffix.at(0) == u'/') |
304 | d->suffix.remove(0, 1); |
305 | # endif |
306 | |
307 | QMutexLocker locker(&qt_factoryloader_global->mutex); |
308 | update(); |
309 | qt_factoryloader_global->loaders.append(t: this); |
310 | #else |
311 | Q_UNUSED(suffix); |
312 | Q_UNUSED(cs); |
313 | #endif |
314 | } |
315 | |
316 | void QFactoryLoader::(const QString &path) |
317 | { |
318 | #if QT_CONFIG(library) |
319 | Q_D(QFactoryLoader); |
320 | if (d->extraSearchPath == path) |
321 | return; // nothing to do |
322 | |
323 | QMutexLocker locker(&qt_factoryloader_global->mutex); |
324 | QString oldPath = std::exchange(obj&: d->extraSearchPath, new_val: path); |
325 | if (oldPath.isEmpty()) { |
326 | // easy case, just update this directory |
327 | d->updateSinglePath(path: d->extraSearchPath); |
328 | } else { |
329 | // must re-scan everything |
330 | d->loadedPaths.clear(); |
331 | d->libraries.clear(); |
332 | d->keyMap.clear(); |
333 | update(); |
334 | } |
335 | #else |
336 | Q_UNUSED(path); |
337 | #endif |
338 | } |
339 | |
340 | QFactoryLoader::MetaDataList QFactoryLoader::metaData() const |
341 | { |
342 | Q_D(const QFactoryLoader); |
343 | QList<QPluginParsedMetaData> metaData; |
344 | #if QT_CONFIG(library) |
345 | QMutexLocker locker(&d->mutex); |
346 | for (const auto &library : d->libraries) |
347 | metaData.append(t: library->metaData); |
348 | #endif |
349 | |
350 | QLatin1StringView iid(d->iid.constData(), d->iid.size()); |
351 | const auto staticPlugins = QPluginLoader::staticPlugins(); |
352 | for (const QStaticPlugin &plugin : staticPlugins) { |
353 | QByteArrayView pluginData(static_cast<const char *>(plugin.rawMetaData), plugin.rawMetaDataSize); |
354 | QPluginParsedMetaData parsed(pluginData); |
355 | if (parsed.isError() || parsed.value(k: QtPluginMetaDataKeys::IID) != iid) |
356 | continue; |
357 | metaData.append(t: std::move(parsed)); |
358 | } |
359 | |
360 | Q_ASSERT(metaData.size() <= std::numeric_limits<int>::max()); |
361 | return metaData; |
362 | } |
363 | |
364 | QObject *QFactoryLoader::instance(int index) const |
365 | { |
366 | Q_D(const QFactoryLoader); |
367 | if (index < 0) |
368 | return nullptr; |
369 | |
370 | #if QT_CONFIG(library) |
371 | QMutexLocker lock(&d->mutex); |
372 | if (size_t(index) < d->libraries.size()) { |
373 | QLibraryPrivate *library = d->libraries[index].get(); |
374 | if (QObject *obj = library->pluginInstance()) { |
375 | if (!obj->parent()) |
376 | obj->moveToThread(thread: QCoreApplicationPrivate::mainThread()); |
377 | return obj; |
378 | } |
379 | return nullptr; |
380 | } |
381 | // we know d->libraries.size() <= index <= numeric_limits<decltype(index)>::max() → no overflow |
382 | index -= static_cast<int>(d->libraries.size()); |
383 | lock.unlock(); |
384 | #endif |
385 | |
386 | QLatin1StringView iid(d->iid.constData(), d->iid.size()); |
387 | const QList<QStaticPlugin> staticPlugins = QPluginLoader::staticPlugins(); |
388 | for (QStaticPlugin plugin : staticPlugins) { |
389 | QByteArrayView pluginData(static_cast<const char *>(plugin.rawMetaData), plugin.rawMetaDataSize); |
390 | QPluginParsedMetaData parsed(pluginData); |
391 | if (parsed.isError() || parsed.value(k: QtPluginMetaDataKeys::IID) != iid) |
392 | continue; |
393 | |
394 | if (index == 0) |
395 | return plugin.instance(); |
396 | --index; |
397 | } |
398 | |
399 | return nullptr; |
400 | } |
401 | |
402 | QMultiMap<int, QString> QFactoryLoader::keyMap() const |
403 | { |
404 | QMultiMap<int, QString> result; |
405 | const QList<QPluginParsedMetaData> metaDataList = metaData(); |
406 | for (int i = 0; i < int(metaDataList.size()); ++i) { |
407 | const QCborMap metaData = metaDataList.at(i).value(k: QtPluginMetaDataKeys::MetaData).toMap(); |
408 | const QCborArray keys = metaData.value(key: "Keys"_L1 ).toArray(); |
409 | for (QCborValueConstRef key : keys) |
410 | result.insert(key: i, value: key.toString()); |
411 | } |
412 | return result; |
413 | } |
414 | |
415 | int QFactoryLoader::indexOf(const QString &needle) const |
416 | { |
417 | const QList<QPluginParsedMetaData> metaDataList = metaData(); |
418 | for (int i = 0; i < int(metaDataList.size()); ++i) { |
419 | const QCborMap metaData = metaDataList.at(i).value(k: QtPluginMetaDataKeys::MetaData).toMap(); |
420 | const QCborArray keys = metaData.value(key: "Keys"_L1 ).toArray(); |
421 | for (QCborValueConstRef key : keys) { |
422 | if (key.toString().compare(s: needle, cs: Qt::CaseInsensitive) == 0) |
423 | return i; |
424 | } |
425 | } |
426 | return -1; |
427 | } |
428 | |
429 | QT_END_NAMESPACE |
430 | |
431 | #include "moc_qfactoryloader_p.cpp" |
432 | |
433 | #endif // QT_NO_QOBJECT |
434 | |