1 /****************************************************************************
2 **
3 ** Copyright (C) 2017 Crimson AS <info@crimson.no>
4 ** Copyright (C) 2016 The Qt Company Ltd.
5 ** Contact: https://www.qt.io/licensing/
6 **
7 ** This file is part of the QtQml module of the Qt Toolkit.
8 **
9 ** $QT_BEGIN_LICENSE:LGPL$
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 Lesser General Public License Usage
19 ** Alternatively, this file may be used under the terms of the GNU Lesser
20 ** General Public License version 3 as published by the Free Software
21 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
22 ** packaging of this file. Please review the following information to
23 ** ensure the GNU Lesser General Public License version 3 requirements
24 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
25 **
26 ** GNU General Public License Usage
27 ** Alternatively, this file may be used under the terms of the GNU
28 ** General Public License version 2.0 or (at your option) the GNU General
29 ** Public license version 3 or any later version approved by the KDE Free
30 ** Qt Foundation. The licenses are as published by the Free Software
31 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
32 ** included in the packaging of this file. Please review the following
33 ** information to ensure the GNU General Public License requirements will
34 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
35 ** https://www.gnu.org/licenses/gpl-3.0.html.
36 **
37 ** $QT_END_LICENSE$
38 **
39 ****************************************************************************/
40 
41 #include "qqmlimport_p.h"
42 
43 #include <QtCore/qdebug.h>
44 #include <QtCore/qdir.h>
45 #include <QtQml/qqmlfile.h>
46 #include <QtCore/qfileinfo.h>
47 #include <QtCore/qpluginloader.h>
48 #include <QtCore/qlibraryinfo.h>
49 #include <QtCore/qreadwritelock.h>
50 #include <QtQml/qqmlextensioninterface.h>
51 #include <QtQml/qqmlextensionplugin.h>
52 #include <private/qqmlextensionplugin_p.h>
53 #include <private/qqmlglobal_p.h>
54 #include <private/qqmltypenamecache_p.h>
55 #include <private/qqmlengine_p.h>
56 #include <private/qfieldlist_p.h>
57 #include <private/qqmltypemodule_p.h>
58 #include <private/qqmltypeloaderqmldircontent_p.h>
59 #include <QtCore/qjsonobject.h>
60 #include <QtCore/qjsonarray.h>
61 #include <QtQml/private/qqmltype_p_p.h>
62 
63 #include <algorithm>
64 #include <functional>
65 
66 QT_BEGIN_NAMESPACE
67 
68 DEFINE_BOOL_CONFIG_OPTION(qmlImportTrace, QML_IMPORT_TRACE)
69 DEFINE_BOOL_CONFIG_OPTION(qmlCheckTypes, QML_CHECK_TYPES)
70 
71 static const QLatin1Char Dot('.');
72 static const QLatin1Char Slash('/');
73 static const QLatin1Char Backslash('\\');
74 static const QLatin1Char Colon(':');
75 static const QLatin1String Slash_qmldir("/qmldir");
76 static const QLatin1String String_qmldir("qmldir");
77 static const QString dotqml_string(QStringLiteral(".qml"));
78 static const QString dotuidotqml_string(QStringLiteral(".ui.qml"));
79 static bool designerSupportRequired = false;
80 
81 namespace {
82 
resolveLocalUrl(const QString & url,const QString & relative)83 QString resolveLocalUrl(const QString &url, const QString &relative)
84 {
85     if (relative.contains(Colon)) {
86         // contains a host name
87         return QUrl(url).resolved(QUrl(relative)).toString();
88     } else if (relative.isEmpty()) {
89         return url;
90     } else if (relative.at(0) == Slash || !url.contains(Slash)) {
91         return relative;
92     } else {
93         const QStringRef baseRef = url.leftRef(url.lastIndexOf(Slash) + 1);
94         if (relative == QLatin1String("."))
95             return baseRef.toString();
96 
97         QString base = baseRef + relative;
98 
99         // Remove any relative directory elements in the path
100         int length = base.length();
101         int index = 0;
102         while ((index = base.indexOf(QLatin1String("/."), index)) != -1) {
103             if ((length > (index + 2)) && (base.at(index + 2) == Dot) &&
104                 (length == (index + 3) || (base.at(index + 3) == Slash))) {
105                 // Either "/../" or "/..<END>"
106                 int previous = base.lastIndexOf(Slash, index - 1);
107                 if (previous == -1)
108                     break;
109 
110                 int removeLength = (index - previous) + 3;
111                 base.remove(previous + 1, removeLength);
112                 length -= removeLength;
113                 index = previous;
114             } else if ((length == (index + 2)) || (base.at(index + 2) == Slash)) {
115                 // Either "/./" or "/.<END>"
116                 base.remove(index, 2);
117                 length -= 2;
118             } else {
119                 ++index;
120             }
121         }
122 
123         return base;
124     }
125 }
126 
isPathAbsolute(const QString & path)127 bool isPathAbsolute(const QString &path)
128 {
129 #if defined(Q_OS_UNIX)
130     return (path.at(0) == Slash);
131 #else
132     QFileInfo fi(path);
133     return fi.isAbsolute();
134 #endif
135 }
136 
137 } // namespace
138 
139 struct RegisteredPlugin {
140     QString uri;
141     QPluginLoader* loader;
142 };
143 
144 struct StringRegisteredPluginMap : public QMap<QString, RegisteredPlugin> {
145     QMutex mutex;
146 
~StringRegisteredPluginMapStringRegisteredPluginMap147     ~StringRegisteredPluginMap()
148     {
149         QMutexLocker lock(&mutex);
150         for (const RegisteredPlugin &plugin : qAsConst(*this))
151             delete plugin.loader;
152     }
153 };
154 
155 Q_GLOBAL_STATIC(StringRegisteredPluginMap, qmlEnginePluginsWithRegisteredTypes); // stores the uri and the PluginLoaders
156 
qmlClearEnginePlugins()157 void qmlClearEnginePlugins()
158 {
159     StringRegisteredPluginMap *plugins = qmlEnginePluginsWithRegisteredTypes();
160     QMutexLocker lock(&plugins->mutex);
161 #if QT_CONFIG(library)
162     for (auto &plugin : qAsConst(*plugins)) {
163         QPluginLoader* loader = plugin.loader;
164         if (loader && !loader->unload())
165             qWarning("Unloading %s failed: %s", qPrintable(plugin.uri), qPrintable(loader->errorString()));
166         delete loader;
167     }
168 #endif
169     plugins->clear();
170 }
171 
172 typedef QPair<QStaticPlugin, QJsonArray> StaticPluginPair;
173 
174 /*!
175     \internal
176     \class QQmlImportInstance
177 
178     A QQmlImportType represents a single import of a document, held within a
179     namespace.
180 
181     \note The uri here may not necessarily be unique (e.g. for file imports).
182 
183     \note Version numbers may be -1 for file imports: this means that no
184     version was specified as part of the import. Type resolution will be
185     responsible for attempting to find the "best" possible version.
186 */
187 
188 /*!
189     \internal
190     \class QQmlImportNamespace
191 
192     A QQmlImportNamespace is a way of seperating imports into a local namespace.
193 
194     Within a QML document, there is at least one namespace (the
195     "unqualified set") where imports without a qualifier are placed, i.e:
196 
197         import QtQuick 2.6
198 
199     will have a single namespace (the unqualified set) containing a single import
200     for QtQuick 2.6. However, there may be others if an import statement gives
201     a qualifier, i.e the following will result in an additional new
202     QQmlImportNamespace in the qualified set:
203 
204         import MyFoo 1.0 as Foo
205 */
206 
207 class QQmlImportsPrivate
208 {
209 public:
210     QQmlImportsPrivate(QQmlTypeLoader *loader);
211     ~QQmlImportsPrivate();
212 
213     QQmlImportNamespace *importNamespace(const QString &prefix) const;
214 
215     bool addLibraryImport(const QString& uri, const QString &prefix,
216                           int vmaj, int vmin, const QString &qmldirIdentifier, const QString &qmldirUrl, bool incomplete,
217                           QQmlImportDatabase *database,
218                           QList<QQmlError> *errors);
219 
220     bool addFileImport(const QString &uri, const QString &prefix,
221                        int vmaj, int vmin,
222                        bool isImplicitImport, bool incomplete, QQmlImportDatabase *database,
223                        QList<QQmlError> *errors);
224 
225     bool updateQmldirContent(const QString &uri, const QString &prefix,
226                              const QString &qmldirIdentifier, const QString& qmldirUrl,
227                              QQmlImportDatabase *database,
228                              QList<QQmlError> *errors);
229 
230     bool resolveType(const QHashedStringRef &type, int *vmajor, int *vminor,
231                      QQmlType *type_return, QList<QQmlError> *errors,
232                      QQmlType::RegistrationType registrationType,
233                      bool *typeRecursionDetected = nullptr);
234 
235     QUrl baseUrl;
236     QString base;
237     int ref;
238 
239     // storage of data related to imports without a namespace
240     mutable QQmlImportNamespace unqualifiedset;
241 
242     QQmlImportNamespace *findQualifiedNamespace(const QHashedStringRef &) const;
243 
244     // storage of data related to imports with a namespace
245     mutable QFieldList<QQmlImportNamespace, &QQmlImportNamespace::nextNamespace> qualifiedSets;
246 
247     QQmlTypeLoader *typeLoader;
248 
249     static QQmlImports::LocalQmldirResult locateLocalQmldir(
250             const QString &uri, int vmaj, int vmin, QQmlImportDatabase *database,
251             QString *outQmldirFilePath, QString *outUrl);
252 
253     static bool validateQmldirVersion(const QQmlTypeLoaderQmldirContent &qmldir, const QString &uri, int vmaj, int vmin,
254                                       QList<QQmlError> *errors);
255 
256     bool importExtension(const QString &absoluteFilePath, const QString &uri,
257                          int vmaj, int vmin,
258                          QQmlImportDatabase *database,
259                          const QQmlTypeLoaderQmldirContent &qmldir,
260                          QList<QQmlError> *errors);
261 
262     bool getQmldirContent(const QString &qmldirIdentifier, const QString &uri,
263                           QQmlTypeLoaderQmldirContent *qmldir, QList<QQmlError> *errors);
264 
265     QString resolvedUri(const QString &dir_arg, QQmlImportDatabase *database);
266 
267     QQmlImportInstance *addImportToNamespace(QQmlImportNamespace *nameSpace,
268                                                       const QString &uri, const QString &url,
269                                                       int vmaj, int vmin, QV4::CompiledData::Import::ImportType type,
270                                                       QList<QQmlError> *errors, bool lowPrecedence = false);
271 
272     bool populatePluginPairVector(QVector<StaticPluginPair> &result, const QString &uri, const QStringList &versionUris,
273                                      const QString &qmldirPath, QList<QQmlError> *errors);
274 };
275 
276 /*!
277 \class QQmlImports
278 \brief The QQmlImports class encapsulates one QML document's import statements.
279 \internal
280 */
QQmlImports(const QQmlImports & copy)281 QQmlImports::QQmlImports(const QQmlImports &copy)
282 : d(copy.d)
283 {
284     ++d->ref;
285 }
286 
287 QQmlImports &
operator =(const QQmlImports & copy)288 QQmlImports::operator =(const QQmlImports &copy)
289 {
290     ++copy.d->ref;
291     if (--d->ref == 0)
292         delete d;
293     d = copy.d;
294     return *this;
295 }
296 
QQmlImports(QQmlTypeLoader * typeLoader)297 QQmlImports::QQmlImports(QQmlTypeLoader *typeLoader)
298     : d(new QQmlImportsPrivate(typeLoader))
299 {
300 }
301 
~QQmlImports()302 QQmlImports::~QQmlImports()
303 {
304     if (--d->ref == 0)
305         delete d;
306 }
307 
308 /*!
309   Sets the base URL to be used for all relative file imports added.
310 */
setBaseUrl(const QUrl & url,const QString & urlString)311 void QQmlImports::setBaseUrl(const QUrl& url, const QString &urlString)
312 {
313     d->baseUrl = url;
314 
315     if (urlString.isEmpty()) {
316         d->base = url.toString();
317     } else {
318         //Q_ASSERT(url.toString() == urlString);
319         d->base = urlString;
320     }
321 }
322 
323 /*!
324   Returns the base URL to be used for all relative file imports added.
325 */
baseUrl() const326 QUrl QQmlImports::baseUrl() const
327 {
328     return d->baseUrl;
329 }
330 
331 /*
332     \internal
333 
334     This method is responsible for populating data of all types visible in this
335     document's imports into the \a cache for resolution elsewhere (e.g. in JS,
336     or when loading additional types).
337 
338     \note This is for C++ types only. Composite types are handled separately,
339     as they do not have a QQmlTypeModule.
340 */
populateCache(QQmlTypeNameCache * cache) const341 void QQmlImports::populateCache(QQmlTypeNameCache *cache) const
342 {
343     const QQmlImportNamespace &set = d->unqualifiedset;
344 
345     for (int ii = set.imports.count() - 1; ii >= 0; --ii) {
346         const QQmlImportInstance *import = set.imports.at(ii);
347         QQmlTypeModule *module = QQmlMetaType::typeModule(import->uri, import->majversion);
348         if (module) {
349             cache->m_anonymousImports.append(QQmlTypeModuleVersion(module, import->minversion));
350         }
351     }
352 
353     for (QQmlImportNamespace *ns = d->qualifiedSets.first(); ns; ns = d->qualifiedSets.next(ns)) {
354 
355         const QQmlImportNamespace &set = *ns;
356 
357         // positioning is important; we must create the namespace even if there is no module.
358         QQmlImportRef &typeimport = cache->m_namedImports[set.prefix];
359         typeimport.m_qualifier = set.prefix;
360 
361         for (int ii = set.imports.count() - 1; ii >= 0; --ii) {
362             const QQmlImportInstance *import = set.imports.at(ii);
363             QQmlTypeModule *module = QQmlMetaType::typeModule(import->uri, import->majversion);
364             if (module) {
365                 QQmlImportRef &typeimport = cache->m_namedImports[set.prefix];
366                 typeimport.modules.append(QQmlTypeModuleVersion(module, import->minversion));
367             }
368         }
369     }
370 }
371 
372 // We need to exclude the entry for the current baseUrl. This can happen for example
373 // when handling qmldir files on the remote dir case and the current type is marked as
374 // singleton.
excludeBaseUrl(const QString & importUrl,const QString & fileName,const QString & baseUrl)375 bool excludeBaseUrl(const QString &importUrl, const QString &fileName, const QString &baseUrl)
376 {
377     if (importUrl.isEmpty())
378         return false;
379 
380     if (baseUrl.startsWith(importUrl))
381     {
382         if (fileName == baseUrl.midRef(importUrl.size()))
383             return false;
384     }
385 
386     return true;
387 }
388 
findCompositeSingletons(const QQmlImportNamespace & set,QList<QQmlImports::CompositeSingletonReference> & resultList,const QUrl & baseUrl)389 void findCompositeSingletons(const QQmlImportNamespace &set, QList<QQmlImports::CompositeSingletonReference> &resultList, const QUrl &baseUrl)
390 {
391     typedef QQmlDirComponents::const_iterator ConstIterator;
392 
393     for (int ii = set.imports.count() - 1; ii >= 0; --ii) {
394         const QQmlImportInstance *import = set.imports.at(ii);
395 
396         const QQmlDirComponents &components = import->qmlDirComponents;
397 
398         const int importMajorVersion = import->majversion;
399         const int importMinorVersion = import->minversion;
400         auto shouldSkipSingleton = [importMajorVersion, importMinorVersion](int singletonMajorVersion, int singletonMinorVersion) -> bool {
401             return importMajorVersion != -1 &&
402                     (singletonMajorVersion > importMajorVersion || (singletonMajorVersion == importMajorVersion && singletonMinorVersion > importMinorVersion));
403         };
404 
405         ConstIterator cend = components.constEnd();
406         for (ConstIterator cit = components.constBegin(); cit != cend; ++cit) {
407             if (cit->singleton && excludeBaseUrl(import->url, cit->fileName, baseUrl.toString())) {
408                 if (shouldSkipSingleton(cit->majorVersion, cit->minorVersion))
409                     continue;
410                 QQmlImports::CompositeSingletonReference ref;
411                 ref.typeName = cit->typeName;
412                 ref.prefix = set.prefix;
413                 ref.majorVersion = cit->majorVersion;
414                 ref.minorVersion = cit->minorVersion;
415                 resultList.append(ref);
416             }
417         }
418 
419         if (QQmlTypeModule *module = QQmlMetaType::typeModule(import->uri, import->majversion)) {
420             module->walkCompositeSingletons([&resultList, &set, &shouldSkipSingleton](const QQmlType &singleton) {
421                 if (shouldSkipSingleton(singleton.majorVersion(), singleton.minorVersion()))
422                     return;
423                 QQmlImports::CompositeSingletonReference ref;
424                 ref.typeName = singleton.elementName();
425                 ref.prefix = set.prefix;
426                 ref.majorVersion = singleton.majorVersion();
427                 ref.minorVersion = singleton.minorVersion();
428                 resultList.append(ref);
429             });
430         }
431     }
432 }
433 
434 /*
435     \internal
436 
437     Returns a list of all composite singletons present in this document's
438     imports.
439 
440     This information is used by QQmlTypeLoader to ensure that composite singletons
441     are marked as dependencies during type loading.
442 */
resolvedCompositeSingletons() const443 QList<QQmlImports::CompositeSingletonReference> QQmlImports::resolvedCompositeSingletons() const
444 {
445     QList<QQmlImports::CompositeSingletonReference> compositeSingletons;
446 
447     const QQmlImportNamespace &set = d->unqualifiedset;
448     findCompositeSingletons(set, compositeSingletons, baseUrl());
449 
450     for (QQmlImportNamespace *ns = d->qualifiedSets.first(); ns; ns = d->qualifiedSets.next(ns)) {
451         const QQmlImportNamespace &set = *ns;
452         findCompositeSingletons(set, compositeSingletons, baseUrl());
453     }
454 
455     std::stable_sort(compositeSingletons.begin(), compositeSingletons.end(),
456                      [](const QQmlImports::CompositeSingletonReference &lhs,
457                         const QQmlImports::CompositeSingletonReference &rhs) {
458         if (lhs.prefix != rhs.prefix)
459             return lhs.prefix < rhs.prefix;
460 
461         if (lhs.typeName != rhs.typeName)
462             return lhs.typeName < rhs.typeName;
463 
464         return lhs.majorVersion != rhs.majorVersion
465             ? lhs.majorVersion < rhs.majorVersion
466             : lhs.minorVersion < rhs.minorVersion;
467     });
468 
469     return compositeSingletons;
470 }
471 
472 /*
473     \internal
474 
475     Returns a list of scripts imported by this document. This is used by
476     QQmlTypeLoader to properly handle dependencies on imported scripts.
477 */
resolvedScripts() const478 QList<QQmlImports::ScriptReference> QQmlImports::resolvedScripts() const
479 {
480     QList<QQmlImports::ScriptReference> scripts;
481 
482     const QQmlImportNamespace &set = d->unqualifiedset;
483 
484     for (int ii = set.imports.count() - 1; ii >= 0; --ii) {
485         const QQmlImportInstance *import = set.imports.at(ii);
486 
487         for (const QQmlDirParser::Script &script : import->qmlDirScripts) {
488             ScriptReference ref;
489             ref.nameSpace = script.nameSpace;
490             ref.location = QUrl(import->url).resolved(QUrl(script.fileName));
491             scripts.append(ref);
492         }
493     }
494 
495     for (QQmlImportNamespace *ns = d->qualifiedSets.first(); ns; ns = d->qualifiedSets.next(ns)) {
496         const QQmlImportNamespace &set = *ns;
497 
498         for (int ii = set.imports.count() - 1; ii >= 0; --ii) {
499             const QQmlImportInstance *import = set.imports.at(ii);
500 
501             for (const QQmlDirParser::Script &script : import->qmlDirScripts) {
502                 ScriptReference ref;
503                 ref.nameSpace = script.nameSpace;
504                 ref.qualifier = set.prefix;
505                 ref.location = QUrl(import->url).resolved(QUrl(script.fileName));
506                 scripts.append(ref);
507             }
508         }
509     }
510 
511     return scripts;
512 }
513 
joinStringRefs(const QVector<QStringRef> & refs,const QChar & sep)514 static QString joinStringRefs(const QVector<QStringRef> &refs, const QChar &sep)
515 {
516     QString str;
517     for (auto it = refs.cbegin(); it != refs.cend(); ++it) {
518         if (it != refs.cbegin())
519             str += sep;
520         str += *it;
521     }
522     return str;
523 }
524 
525 /*!
526     Forms complete paths to a qmldir file, from a base URL, a module URI and version specification.
527 
528     For example, QtQml.Models 2.0:
529     - base/QtQml/Models.2.0/qmldir
530     - base/QtQml.2.0/Models/qmldir
531     - base/QtQml/Models.2/qmldir
532     - base/QtQml.2/Models/qmldir
533     - base/QtQml/Models/qmldir
534 */
completeQmldirPaths(const QString & uri,const QStringList & basePaths,int vmaj,int vmin)535 QStringList QQmlImports::completeQmldirPaths(const QString &uri, const QStringList &basePaths, int vmaj, int vmin)
536 {
537     const QVector<QStringRef> parts = uri.splitRef(Dot, Qt::SkipEmptyParts);
538 
539     QStringList qmlDirPathsPaths;
540     // fully & partially versioned parts + 1 unversioned for each base path
541     qmlDirPathsPaths.reserve(basePaths.count() * (2 * parts.count() + 1));
542 
543     for (int version = FullyVersioned; version <= Unversioned; ++version) {
544         const QString ver = versionString(vmaj, vmin, static_cast<QQmlImports::ImportVersion>(version));
545 
546         for (const QString &path : basePaths) {
547             QString dir = path;
548             if (!dir.endsWith(Slash) && !dir.endsWith(Backslash))
549                 dir += Slash;
550 
551             // append to the end
552             qmlDirPathsPaths += dir + joinStringRefs(parts, Slash) + ver + Slash_qmldir;
553 
554             if (version != Unversioned) {
555                 // insert in the middle
556                 for (int index = parts.count() - 2; index >= 0; --index) {
557                     qmlDirPathsPaths += dir + joinStringRefs(parts.mid(0, index + 1), Slash)
558                                             + ver + Slash
559                                             + joinStringRefs(parts.mid(index + 1), Slash) + Slash_qmldir;
560                 }
561             }
562         }
563     }
564 
565     return qmlDirPathsPaths;
566 }
567 
versionString(int vmaj,int vmin,ImportVersion version)568 QString QQmlImports::versionString(int vmaj, int vmin, ImportVersion version)
569 {
570     if (version == QQmlImports::FullyVersioned) {
571         // extension with fully encoded version number (eg. MyModule.3.2)
572         return QString::asprintf(".%d.%d", vmaj, vmin);
573     } else if (version == QQmlImports::PartiallyVersioned) {
574         // extension with encoded version major (eg. MyModule.3)
575         return QString::asprintf(".%d", vmaj);
576     } // else extension without version number (eg. MyModule)
577     return QString();
578 }
579 
580 /*!
581   \internal
582 
583   The given (namespace qualified) \a type is resolved to either
584   \list
585   \li a QQmlImportNamespace stored at \a ns_return, or
586   \li a QQmlType stored at \a type_return,
587   \endlist
588 
589   If any return pointer is 0, the corresponding search is not done.
590 
591   \sa addFileImport(), addLibraryImport
592 */
resolveType(const QHashedStringRef & type,QQmlType * type_return,int * vmaj,int * vmin,QQmlImportNamespace ** ns_return,QList<QQmlError> * errors,QQmlType::RegistrationType registrationType,bool * typeRecursionDetected) const593 bool QQmlImports::resolveType(const QHashedStringRef &type,
594                               QQmlType *type_return, int *vmaj, int *vmin,
595                               QQmlImportNamespace** ns_return, QList<QQmlError> *errors,
596                               QQmlType::RegistrationType registrationType,
597                               bool *typeRecursionDetected) const
598 {
599     QQmlImportNamespace* ns = d->findQualifiedNamespace(type);
600     if (ns) {
601         if (ns_return)
602             *ns_return = ns;
603         return true;
604     }
605     if (type_return) {
606         if (d->resolveType(type, vmaj, vmin, type_return, errors, registrationType,
607                            typeRecursionDetected)) {
608             if (qmlImportTrace()) {
609 #define RESOLVE_TYPE_DEBUG qDebug().nospace() << "QQmlImports(" << qPrintable(baseUrl().toString()) \
610                                               << ')' << "::resolveType: " << type.toString() << " => "
611 
612                 if (type_return && type_return->isValid()) {
613                     if (type_return->isCompositeSingleton())
614                         RESOLVE_TYPE_DEBUG << type_return->typeName() << ' ' << type_return->sourceUrl() << " TYPE/URL-SINGLETON";
615                     else if (type_return->isComposite())
616                         RESOLVE_TYPE_DEBUG << type_return->typeName() << ' ' << type_return->sourceUrl() << " TYPE/URL";
617                     else if (type_return->isInlineComponentType())
618                         RESOLVE_TYPE_DEBUG << type_return->typeName() << ' ' << type_return->sourceUrl() << " TYPE(INLINECOMPONENT)";
619                     else
620                         RESOLVE_TYPE_DEBUG << type_return->typeName() << " TYPE";
621                 }
622 #undef RESOLVE_TYPE_DEBUG
623             }
624             return true;
625         }
626     }
627     return false;
628 }
629 
setQmldirContent(const QString & resolvedUrl,const QQmlTypeLoaderQmldirContent & qmldir,QQmlImportNamespace * nameSpace,QList<QQmlError> * errors)630 bool QQmlImportInstance::setQmldirContent(const QString &resolvedUrl, const QQmlTypeLoaderQmldirContent &qmldir, QQmlImportNamespace *nameSpace, QList<QQmlError> *errors)
631 {
632     Q_ASSERT(resolvedUrl.endsWith(Slash));
633     url = resolvedUrl;
634     localDirectoryPath = QQmlFile::urlToLocalFileOrQrc(url);
635 
636     qmlDirComponents = qmldir.components();
637 
638     const QQmlDirScripts &scripts = qmldir.scripts();
639     if (!scripts.isEmpty()) {
640         // Verify that we haven't imported these scripts already
641         for (QList<QQmlImportInstance *>::const_iterator it = nameSpace->imports.constBegin();
642              it != nameSpace->imports.constEnd(); ++it) {
643             if ((*it != this) && ((*it)->uri == uri)) {
644                 QQmlError error;
645                 error.setDescription(QQmlImportDatabase::tr("\"%1\" is ambiguous. Found in %2 and in %3").arg(uri).arg(url).arg((*it)->url));
646                 errors->prepend(error);
647                 return false;
648             }
649         }
650 
651         qmlDirScripts = getVersionedScripts(scripts, majversion, minversion);
652     }
653 
654     return true;
655 }
656 
getVersionedScripts(const QQmlDirScripts & qmldirscripts,int vmaj,int vmin)657 QQmlDirScripts QQmlImportInstance::getVersionedScripts(const QQmlDirScripts &qmldirscripts, int vmaj, int vmin)
658 {
659     QMap<QString, QQmlDirParser::Script> versioned;
660 
661     for (QList<QQmlDirParser::Script>::const_iterator sit = qmldirscripts.constBegin();
662          sit != qmldirscripts.constEnd(); ++sit) {
663         // Only include scripts that match our requested version
664         if (((vmaj == -1) || (sit->majorVersion == vmaj)) &&
665             ((vmin == -1) || (sit->minorVersion <= vmin))) {
666             // Load the highest version that matches
667             QMap<QString, QQmlDirParser::Script>::iterator vit = versioned.find(sit->nameSpace);
668             if (vit == versioned.end() || (vit->minorVersion < sit->minorVersion)) {
669                 versioned.insert(sit->nameSpace, *sit);
670             }
671         }
672     }
673 
674     return versioned.values();
675 }
676 
677 /*!
678   \internal
679 
680   Searching \e only in the namespace \a ns (previously returned in a call to
681   resolveType(), \a type is found and returned to
682   a QQmlType stored at \a type_return. If the type is from a QML file, the returned
683   type will be a CompositeType.
684 
685   If the return pointer is 0, the corresponding search is not done.
686 */
resolveType(QQmlImportNamespace * ns,const QHashedStringRef & type,QQmlType * type_return,int * vmaj,int * vmin,QQmlType::RegistrationType registrationType) const687 bool QQmlImports::resolveType(QQmlImportNamespace *ns, const QHashedStringRef &type,
688                               QQmlType *type_return, int *vmaj, int *vmin,
689                               QQmlType::RegistrationType registrationType) const
690 {
691     return ns->resolveType(d->typeLoader, type, vmaj, vmin, type_return, nullptr, nullptr, registrationType);
692 }
693 
resolveType(QQmlTypeLoader * typeLoader,const QHashedStringRef & type,int * vmajor,int * vminor,QQmlType * type_return,QString * base,bool * typeRecursionDetected,QQmlType::RegistrationType registrationType,QQmlImport::RecursionRestriction recursionRestriction,QList<QQmlError> * errors) const694 bool QQmlImportInstance::resolveType(QQmlTypeLoader *typeLoader, const QHashedStringRef& type,
695                                      int *vmajor, int *vminor, QQmlType *type_return, QString *base,
696                                      bool *typeRecursionDetected,
697                                      QQmlType::RegistrationType registrationType,
698                                      QQmlImport::RecursionRestriction recursionRestriction,
699                                      QList<QQmlError> *errors) const
700 {
701     if (majversion >= 0 && minversion >= 0) {
702         QQmlType t = QQmlMetaType::qmlType(type, uri, majversion, minversion);
703         if (t.isValid()) {
704             if (vmajor)
705                 *vmajor = majversion;
706             if (vminor)
707                 *vminor = minversion;
708             if (type_return)
709                 *type_return = t;
710             return true;
711         }
712     }
713 
714     const QString typeStr = type.toString();
715     if (isInlineComponent) {
716         Q_ASSERT(type_return);
717         bool ret = uri == typeStr;
718         if (ret) {
719             Q_ASSERT(!type_return->isValid());
720             auto createICType = [&]() {
721                 auto typePriv = new QQmlTypePrivate {QQmlType::RegistrationType::InlineComponentType};
722                 bool ok = false;
723                 typePriv->extraData.id->objectId = QUrl(this->url).fragment().toInt(&ok);
724                 Q_ASSERT(ok);
725                 typePriv->extraData.id->url = QUrl(this->url);
726                 auto icType = QQmlType(typePriv);
727                 typePriv->release();
728                 return icType;
729             };
730             if (containingType.isValid()) {
731                 // we currently cannot reference a Singleton inside itself
732                 // in that case, containingType is still invalid
733                 if (int icID = containingType.lookupInlineComponentIdByName(typeStr) != -1) {
734                     *type_return = containingType.lookupInlineComponentById(icID);
735                 } else {
736                     auto icType = createICType();
737                     int placeholderId = containingType.generatePlaceHolderICId();
738                     const_cast<QQmlImportInstance*>(this)->containingType.associateInlineComponent(typeStr, placeholderId, CompositeMetaTypeIds {}, icType);
739                     *type_return = QQmlType(icType);
740                 }
741             } else  {
742                 *type_return = createICType();
743             }
744         }
745         return ret;
746     }
747     QQmlDirComponents::ConstIterator it = qmlDirComponents.find(typeStr), end = qmlDirComponents.end();
748     if (it != end) {
749         QString componentUrl;
750         bool isCompositeSingleton = false;
751         QQmlDirComponents::ConstIterator candidate = end;
752         for ( ; it != end && it.key() == typeStr; ++it) {
753             const QQmlDirParser::Component &c = *it;
754             switch (registrationType) {
755             case QQmlType::AnyRegistrationType:
756                 break;
757             case QQmlType::CompositeSingletonType:
758                 if (!c.singleton)
759                     continue;
760                 break;
761             default:
762                 if (c.singleton)
763                     continue;
764                 break;
765             }
766 
767             // importing version -1 means import ALL versions
768             if ((majversion == -1) ||
769                 (implicitlyImported && c.internal) || // allow the implicit import of internal types
770                 (c.majorVersion == majversion && c.minorVersion <= minversion)) {
771                 // Is this better than the previous candidate?
772                 if ((candidate == end) ||
773                     (c.majorVersion > candidate->majorVersion) ||
774                     ((c.majorVersion == candidate->majorVersion) && (c.minorVersion > candidate->minorVersion))) {
775                     if (base) {
776                         componentUrl = resolveLocalUrl(QString(url + c.typeName + dotqml_string), c.fileName);
777                         if (c.internal) {
778                             if (resolveLocalUrl(*base, c.fileName) != componentUrl)
779                                 continue; // failed attempt to access an internal type
780                         }
781 
782                         const bool recursion = *base == componentUrl;
783                         if (typeRecursionDetected)
784                             *typeRecursionDetected = recursion;
785 
786                         if (recursionRestriction == QQmlImport::PreventRecursion && recursion) {
787                             continue; // no recursion
788                         }
789                     }
790 
791                     // This is our best candidate so far
792                     candidate = it;
793                     isCompositeSingleton = c.singleton;
794                 }
795             }
796         }
797 
798         if (candidate != end) {
799             if (!base) // ensure we have a componentUrl
800                 componentUrl = resolveLocalUrl(QString(url + candidate->typeName + dotqml_string), candidate->fileName);
801             QQmlType returnType = QQmlMetaType::typeForUrl(componentUrl, type, isCompositeSingleton,
802                                                            nullptr, candidate->majorVersion,
803                                                            candidate->minorVersion);
804             if (vmajor)
805                 *vmajor = candidate->majorVersion;
806             if (vminor)
807                 *vminor = candidate->minorVersion;
808             if (type_return)
809                 *type_return = returnType;
810             return returnType.isValid();
811         }
812     } else if (!isLibrary && !localDirectoryPath.isEmpty()) {
813         QString qmlUrl;
814         bool exists = false;
815 
816         const QString urlsToTry[2] = {
817             typeStr + dotqml_string, // Type -> Type.qml
818             typeStr + dotuidotqml_string // Type -> Type.ui.qml
819         };
820         for (const QString &urlToTry : urlsToTry) {
821             exists = typeLoader->fileExists(localDirectoryPath, urlToTry);
822             if (exists) {
823 #if defined(Q_OS_MACOS) || defined(Q_OS_WIN)
824                 // don't let function.qml confuse the use of "new Function(...)" for example.
825                 if (!QQml_isFileCaseCorrect(localDirectoryPath + urlToTry)) {
826                     exists = false;
827                     if (errors) {
828                         QQmlError caseError;
829                         caseError.setDescription(QLatin1String("File name case mismatch"));
830                         errors->append(caseError);
831                     }
832                     break;
833                 }
834 #else
835                 Q_UNUSED(errors);
836 #endif
837                 qmlUrl = url + urlToTry;
838                 break;
839             }
840         }
841 
842         if (exists) {
843             const bool recursion = base && *base == qmlUrl;
844             if (typeRecursionDetected)
845                 *typeRecursionDetected = recursion;
846             if (recursionRestriction == QQmlImport::AllowRecursion || !recursion) {
847                 QQmlType returnType = QQmlMetaType::typeForUrl(
848                         qmlUrl, type, registrationType == QQmlType::CompositeSingletonType, errors);
849                 if (type_return)
850                     *type_return = returnType;
851                 return returnType.isValid();
852             }
853         }
854     }
855 
856     return false;
857 }
858 
resolveType(const QHashedStringRef & type,int * vmajor,int * vminor,QQmlType * type_return,QList<QQmlError> * errors,QQmlType::RegistrationType registrationType,bool * typeRecursionDetected)859 bool QQmlImportsPrivate::resolveType(const QHashedStringRef& type, int *vmajor, int *vminor,
860                                      QQmlType *type_return, QList<QQmlError> *errors,
861                                      QQmlType::RegistrationType registrationType,
862                                      bool *typeRecursionDetected)
863 {
864     const QVector<QHashedStringRef> splitName = type.split(Dot);
865     auto resolveTypeInNamespace = [&](QHashedStringRef unqualifiedtype, QQmlImportNamespace *nameSpace, QList<QQmlError> *errors) -> bool {
866         if (nameSpace->resolveType(typeLoader, unqualifiedtype, vmajor, vminor, type_return, &base, errors,
867                            registrationType, typeRecursionDetected))
868             return true;
869         if (nameSpace->imports.count() == 1 && !nameSpace->imports.at(0)->isLibrary && type_return && nameSpace != &unqualifiedset) {
870             // qualified, and only 1 url
871             *type_return = QQmlMetaType::typeForUrl(
872                     resolveLocalUrl(nameSpace->imports.at(0)->url,
873                                     unqualifiedtype.toString() + QLatin1String(".qml")),
874                     type, false, errors);
875             return type_return->isValid();
876         }
877         return false;
878     };
879     switch (splitName.size()) {
880     case 1: {
881         // must be a simple type
882         return resolveTypeInNamespace(type, &unqualifiedset, errors);
883     }
884     case 2: {
885         // either namespace + simple type OR simple type + inline component
886         QQmlImportNamespace *s = findQualifiedNamespace(splitName.at(0));
887         if (s) {
888             // namespace + simple type
889             return resolveTypeInNamespace(splitName.at(1), s, errors);
890         } else {
891             if (resolveTypeInNamespace(splitName.at(0), &unqualifiedset, nullptr)) {
892                 // either simple type + inline component
893                 auto const icName = splitName.at(1).toString();
894                 auto objectIndex = type_return->lookupInlineComponentIdByName(icName);
895                 if (objectIndex != -1) {
896                     *type_return = type_return->lookupInlineComponentById(objectIndex);
897                 } else {
898                     auto icTypePriv = new QQmlTypePrivate(QQmlType::RegistrationType::InlineComponentType);
899                     icTypePriv->setContainingType(type_return);
900                     icTypePriv->extraData.id->url = type_return->sourceUrl();
901                     int placeholderId = type_return->generatePlaceHolderICId();
902                     icTypePriv->extraData.id->url.setFragment(QString::number(placeholderId));
903                     auto icType = QQmlType(icTypePriv);
904                     icTypePriv->release();
905                     type_return->associateInlineComponent(icName, placeholderId, CompositeMetaTypeIds {}, icType);
906                     *type_return = icType;
907                 }
908                 Q_ASSERT(type_return->containingType().isValid());
909                 type_return->setPendingResolutionName(icName);
910                 return true;
911             } else {
912                 // or a failure
913                 if (errors) {
914                     QQmlError error;
915                     error.setDescription(QQmlImportDatabase::tr("- %1 is neither a type nor a namespace").arg(splitName.at(0).toString()));
916                     errors->prepend(error);
917                 }
918                 return false;
919             }
920         }
921     }
922     case 3: {
923         // must be namespace + simple type + inline component
924         QQmlImportNamespace *s = findQualifiedNamespace(splitName.at(0));
925         QQmlError error;
926         if (!s) {
927             error.setDescription(QQmlImportDatabase::tr("- %1 is not a namespace").arg(splitName.at(0).toString()));
928         } else {
929             if (resolveTypeInNamespace(splitName.at(1), s, nullptr)) {
930                 auto const icName = splitName.at(2).toString();
931                 auto objectIndex = type_return->lookupInlineComponentIdByName(icName);
932                 if (objectIndex != -1)
933                     *type_return = type_return->lookupInlineComponentById(objectIndex);
934                 else {
935                     auto icTypePriv = new QQmlTypePrivate(QQmlType::RegistrationType::InlineComponentType);
936                     icTypePriv->setContainingType(type_return);
937                     icTypePriv->extraData.id->url = type_return->sourceUrl();
938                     int placeholderId = type_return->generatePlaceHolderICId();
939                     icTypePriv->extraData.id->url.setFragment(QString::number(placeholderId));
940                     auto icType = QQmlType(icTypePriv);
941                     icTypePriv->release();
942                     type_return->associateInlineComponent(icName, placeholderId, CompositeMetaTypeIds {}, icType);
943                     *type_return = icType;
944                 }
945                 type_return->setPendingResolutionName(icName);
946                 return true;
947             } else {
948                 error.setDescription(QQmlImportDatabase::tr("- %1 is not a type").arg(splitName.at(1).toString()));
949             }
950         }
951         if (errors) {
952             errors->prepend(error);
953         }
954         return false;
955     }
956     default: {
957         // all other numbers suggest a user error
958         if (errors) {
959             QQmlError error;
960             error.setDescription(QQmlImportDatabase::tr("- nested namespaces not allowed"));
961             errors->prepend(error);
962         }
963         return false;
964     }
965     }
966     Q_UNREACHABLE();
967 }
968 
findImport(const QString & uri) const969 QQmlImportInstance *QQmlImportNamespace::findImport(const QString &uri) const
970 {
971     for (QQmlImportInstance *import : imports) {
972         if (import->uri == uri)
973             return import;
974     }
975     return nullptr;
976 }
977 
resolveType(QQmlTypeLoader * typeLoader,const QHashedStringRef & type,int * vmajor,int * vminor,QQmlType * type_return,QString * base,QList<QQmlError> * errors,QQmlType::RegistrationType registrationType,bool * typeRecursionDetected)978 bool QQmlImportNamespace::resolveType(QQmlTypeLoader *typeLoader, const QHashedStringRef &type,
979                                       int *vmajor, int *vminor, QQmlType *type_return,
980                                       QString *base, QList<QQmlError> *errors,
981                                       QQmlType::RegistrationType registrationType,
982                                       bool *typeRecursionDetected)
983 {
984     QQmlImport::RecursionRestriction recursionRestriction =
985             typeRecursionDetected ? QQmlImport::AllowRecursion : QQmlImport::PreventRecursion;
986 
987     bool localTypeRecursionDetected = false;
988     if (!typeRecursionDetected)
989         typeRecursionDetected = &localTypeRecursionDetected;
990 
991     if (needsSorting()) {
992         std::stable_partition(imports.begin(), imports.end(), [](QQmlImportInstance *import) {
993             return import->isInlineComponent;
994         });
995         setNeedsSorting(false);
996     }
997     for (int i=0; i<imports.count(); ++i) {
998         const QQmlImportInstance *import = imports.at(i);
999         if (import->resolveType(typeLoader, type, vmajor, vminor, type_return, base,
1000                                 typeRecursionDetected, registrationType, recursionRestriction, errors)) {
1001             if (qmlCheckTypes()) {
1002                 // check for type clashes
1003                 for (int j = i+1; j<imports.count(); ++j) {
1004                     const QQmlImportInstance *import2 = imports.at(j);
1005                     if (import2->resolveType(typeLoader, type, vmajor, vminor, nullptr, base,
1006                                              nullptr, registrationType)) {
1007                         if (errors) {
1008                             QString u1 = import->url;
1009                             QString u2 = import2->url;
1010                             if (base) {
1011                                 QStringRef b(base);
1012                                 int dot = b.lastIndexOf(Dot);
1013                                 if (dot >= 0) {
1014                                     b = b.left(dot+1);
1015                                     QStringRef l = b.left(dot);
1016                                     if (u1.startsWith(b))
1017                                         u1 = u1.mid(b.count());
1018                                     else if (u1 == l)
1019                                         u1 = QQmlImportDatabase::tr("local directory");
1020                                     if (u2.startsWith(b))
1021                                         u2 = u2.mid(b.count());
1022                                     else if (u2 == l)
1023                                         u2 = QQmlImportDatabase::tr("local directory");
1024                                 }
1025                             }
1026 
1027                             QQmlError error;
1028                             if (u1 != u2) {
1029                                 error.setDescription(QQmlImportDatabase::tr("is ambiguous. Found in %1 and in %2").arg(u1).arg(u2));
1030                             } else {
1031                                 error.setDescription(QQmlImportDatabase::tr("is ambiguous. Found in %1 in version %2.%3 and %4.%5")
1032                                                         .arg(u1)
1033                                                         .arg(import->majversion).arg(import->minversion)
1034                                                         .arg(import2->majversion).arg(import2->minversion));
1035                             }
1036                             errors->prepend(error);
1037                         }
1038                         return false;
1039                     }
1040                 }
1041             }
1042             return true;
1043         }
1044     }
1045     if (errors) {
1046         QQmlError error;
1047         if (*typeRecursionDetected)
1048             error.setDescription(QQmlImportDatabase::tr("is instantiated recursively"));
1049         else
1050             error.setDescription(QQmlImportDatabase::tr("is not a type"));
1051         errors->prepend(error);
1052     }
1053     return false;
1054 }
1055 
needsSorting() const1056 bool QQmlImportNamespace::needsSorting() const
1057 {
1058     return nextNamespace == this;
1059 }
1060 
setNeedsSorting(bool needsSorting)1061 void QQmlImportNamespace::setNeedsSorting(bool needsSorting)
1062 {
1063     Q_ASSERT(nextNamespace == this || nextNamespace == nullptr);
1064     nextNamespace = needsSorting ? this : nullptr;
1065 }
1066 
QQmlImportsPrivate(QQmlTypeLoader * loader)1067 QQmlImportsPrivate::QQmlImportsPrivate(QQmlTypeLoader *loader)
1068 : ref(1), typeLoader(loader) {
1069 }
1070 
~QQmlImportsPrivate()1071 QQmlImportsPrivate::~QQmlImportsPrivate()
1072 {
1073     while (QQmlImportNamespace *ns = qualifiedSets.takeFirst())
1074         delete ns;
1075 }
1076 
findQualifiedNamespace(const QHashedStringRef & prefix) const1077 QQmlImportNamespace *QQmlImportsPrivate::findQualifiedNamespace(const QHashedStringRef &prefix) const
1078 {
1079     for (QQmlImportNamespace *ns = qualifiedSets.first(); ns; ns = qualifiedSets.next(ns)) {
1080         if (prefix == ns->prefix)
1081             return ns;
1082     }
1083     return nullptr;
1084 }
1085 
1086 /*
1087     Returns the list of possible versioned URI combinations. For example, if \a uri is
1088     QtQml.Models, \a vmaj is 2, and \a vmin is 0, this method returns the following:
1089     [QtQml.Models.2.0, QtQml.2.0.Models, QtQml.Models.2, QtQml.2.Models, QtQml.Models]
1090  */
versionUriList(const QString & uri,int vmaj,int vmin)1091 static QStringList versionUriList(const QString &uri, int vmaj, int vmin)
1092 {
1093     QStringList result;
1094     for (int version = QQmlImports::FullyVersioned; version <= QQmlImports::Unversioned; ++version) {
1095         int index = uri.length();
1096         do {
1097             QString versionUri = uri;
1098             versionUri.insert(index, QQmlImports::versionString(vmaj, vmin, static_cast<QQmlImports::ImportVersion>(version)));
1099             result += versionUri;
1100 
1101             index = uri.lastIndexOf(Dot, index - 1);
1102         } while (index > 0 && version != QQmlImports::Unversioned);
1103     }
1104     return result;
1105 }
1106 
makePlugins()1107 static QVector<QStaticPlugin> makePlugins()
1108 {
1109     QVector<QStaticPlugin> plugins;
1110     // To avoid traversing all static plugins for all imports, we cut down
1111     // the list the first time called to only contain QML plugins:
1112     const auto staticPlugins = QPluginLoader::staticPlugins();
1113     for (const QStaticPlugin &plugin : staticPlugins) {
1114         const QString iid = plugin.metaData().value(QLatin1String("IID")).toString();
1115         if (iid == QLatin1String(QQmlEngineExtensionInterface_iid)
1116                 || iid == QLatin1String(QQmlExtensionInterface_iid)
1117                 || iid == QLatin1String(QQmlExtensionInterface_iid_old)) {
1118             plugins.append(plugin);
1119         }
1120     }
1121     return plugins;
1122 }
1123 
1124 /*
1125     Get all static plugins that are QML plugins and has a meta data URI that matches with one of
1126     \a versionUris, which is a list of all possible versioned URI combinations - see versionUriList()
1127     above.
1128  */
populatePluginPairVector(QVector<StaticPluginPair> & result,const QString & uri,const QStringList & versionUris,const QString & qmldirPath,QList<QQmlError> * errors)1129 bool QQmlImportsPrivate::populatePluginPairVector(QVector<StaticPluginPair> &result, const QString &uri, const QStringList &versionUris,
1130                                                       const QString &qmldirPath, QList<QQmlError> *errors)
1131 {
1132     static const QVector<QStaticPlugin> plugins = makePlugins();
1133     for (const QStaticPlugin &plugin : plugins) {
1134         // Since a module can list more than one plugin, we keep iterating even after we found a match.
1135         QObject *instance = plugin.instance();
1136         if (qobject_cast<QQmlEngineExtensionPlugin *>(instance)
1137                 || qobject_cast<QQmlExtensionPlugin *>(instance)) {
1138             const QJsonArray metaTagsUriList = plugin.metaData().value(QLatin1String("uri")).toArray();
1139             if (metaTagsUriList.isEmpty()) {
1140                 if (errors) {
1141                     QQmlError error;
1142                     error.setDescription(QQmlImportDatabase::tr("static plugin for module \"%1\" with name \"%2\" has no metadata URI")
1143                                          .arg(uri).arg(QString::fromUtf8(instance->metaObject()->className())));
1144                     error.setUrl(QUrl::fromLocalFile(qmldirPath));
1145                     errors->prepend(error);
1146                 }
1147                 return false;
1148             }
1149             // A plugin can be set up to handle multiple URIs, so go through the list:
1150             for (const QJsonValue &metaTagUri : metaTagsUriList) {
1151                 if (versionUris.contains(metaTagUri.toString())) {
1152                     result.append(qMakePair(plugin, metaTagsUriList));
1153                     break;
1154                 }
1155             }
1156         }
1157     }
1158     return true;
1159 }
1160 
1161 /*
1162 Import an extension defined by a qmldir file.
1163 
1164 \a qmldirFilePath is a raw file path.
1165 */
importExtension(const QString & qmldirFilePath,const QString & uri,int vmaj,int vmin,QQmlImportDatabase * database,const QQmlTypeLoaderQmldirContent & qmldir,QList<QQmlError> * errors)1166 bool QQmlImportsPrivate::importExtension(const QString &qmldirFilePath,
1167                                          const QString &uri,
1168                                          int vmaj, int vmin,
1169                                          QQmlImportDatabase *database,
1170                                          const QQmlTypeLoaderQmldirContent &qmldir,
1171                                          QList<QQmlError> *errors)
1172 {
1173     Q_ASSERT(qmldir.hasContent());
1174 
1175     if (qmlImportTrace())
1176         qDebug().nospace() << "QQmlImports(" << qPrintable(base) << ")::importExtension: "
1177                            << "loaded " << qmldirFilePath;
1178 
1179     if (designerSupportRequired && !qmldir.designerSupported()) {
1180         if (errors) {
1181             QQmlError error;
1182             error.setDescription(QQmlImportDatabase::tr("module does not support the designer \"%1\"").arg(qmldir.typeNamespace()));
1183             error.setUrl(QUrl::fromLocalFile(qmldirFilePath));
1184             errors->prepend(error);
1185         }
1186         return false;
1187     }
1188 
1189     int qmldirPluginCount = qmldir.plugins().count();
1190     if (qmldirPluginCount == 0)
1191         return true;
1192 
1193     if (database->qmlDirFilesForWhichPluginsHaveBeenLoaded.contains(qmldirFilePath)) {
1194         if ((vmaj >= 0 && vmin >= 0)
1195                 ? !QQmlMetaType::isModule(uri, vmaj, vmin)
1196                 : !QQmlMetaType::isAnyModule(uri)) {
1197             QQmlMetaType::qmlRegisterModuleTypes(uri, vmaj);
1198         }
1199     } else {
1200         // First search for listed qmldir plugins dynamically. If we cannot resolve them all, we continue
1201         // searching static plugins that has correct metadata uri. Note that since we only know the uri
1202         // for a static plugin, and not the filename, we cannot know which static plugin belongs to which
1203         // listed plugin inside qmldir. And for this reason, mixing dynamic and static plugins inside a
1204         // single module is not recommended.
1205 
1206         QString typeNamespace = qmldir.typeNamespace();
1207         QString qmldirPath = qmldirFilePath;
1208         int slash = qmldirPath.lastIndexOf(Slash);
1209         if (slash > 0)
1210             qmldirPath.truncate(slash);
1211 
1212         int dynamicPluginsFound = 0;
1213         int staticPluginsFound = 0;
1214 
1215 #if QT_CONFIG(library)
1216         const auto qmldirPlugins = qmldir.plugins();
1217         for (const QQmlDirParser::Plugin &plugin : qmldirPlugins) {
1218             QString resolvedFilePath = database->resolvePlugin(typeLoader, qmldirPath, plugin.path, plugin.name);
1219             if (!resolvedFilePath.isEmpty()) {
1220                 dynamicPluginsFound++;
1221                 if (!database->importDynamicPlugin(resolvedFilePath, uri, typeNamespace, vmaj, errors)) {
1222                     if (errors) {
1223                         // XXX TODO: should we leave the import plugin error alone?
1224                         // Here, we pop it off the top and coalesce it into this error's message.
1225                         // The reason is that the lower level may add url and line/column numbering information.
1226                         QQmlError error;
1227                         error.setDescription(
1228                                 QQmlImportDatabase::tr(
1229                                         "plugin cannot be loaded for module \"%1\": %2")
1230                                         .arg(uri, errors->takeFirst().description()));
1231                         error.setUrl(QUrl::fromLocalFile(qmldirFilePath));
1232                         errors->prepend(error);
1233                     }
1234                     return false;
1235                 }
1236             }
1237         }
1238 #endif // QT_CONFIG(library)
1239 
1240         if (dynamicPluginsFound < qmldirPluginCount) {
1241             // Check if the missing plugins can be resolved statically. We do this by looking at
1242             // the URIs embedded in a plugins meta data. Since those URIs can be anything from fully
1243             // versioned to unversioned, we need to compare with differnt version strings. If a module
1244             // has several plugins, they must all have the same version. Start by populating pluginPairs
1245             // with relevant plugins to cut the list short early on:
1246             const QStringList versionUris = versionUriList(uri, vmaj, vmin);
1247             QVector<StaticPluginPair> pluginPairs;
1248             if (!populatePluginPairVector(pluginPairs, uri, versionUris, qmldirFilePath, errors))
1249                 return false;
1250 
1251             const QString basePath = QFileInfo(qmldirPath).absoluteFilePath();
1252             for (const QString &versionUri : versionUris) {
1253                 for (const StaticPluginPair &pair : qAsConst(pluginPairs)) {
1254                     for (const QJsonValue &metaTagUri : pair.second) {
1255                         if (versionUri == metaTagUri.toString()) {
1256                             staticPluginsFound++;
1257                             QObject *instance = pair.first.instance();
1258                             if (!database->importStaticPlugin(instance, basePath, uri, typeNamespace, vmaj, errors)) {
1259                                 if (errors) {
1260                                     QQmlError poppedError = errors->takeFirst();
1261                                     QQmlError error;
1262                                     error.setDescription(QQmlImportDatabase::tr("static plugin for module \"%1\" with name \"%2\" cannot be loaded: %3")
1263                                                          .arg(uri).arg(QString::fromUtf8(instance->metaObject()->className())).arg(poppedError.description()));
1264                                     error.setUrl(QUrl::fromLocalFile(qmldirFilePath));
1265                                     errors->prepend(error);
1266                                 }
1267                                 return false;
1268                             }
1269                             break;
1270                         }
1271                     }
1272                 }
1273                 if (staticPluginsFound > 0)
1274                     break;
1275             }
1276         }
1277 
1278         if ((dynamicPluginsFound + staticPluginsFound) < qmldirPluginCount) {
1279             if (errors) {
1280                 QQmlError error;
1281                 if (qmldirPluginCount > 1 && staticPluginsFound > 0)
1282                     error.setDescription(QQmlImportDatabase::tr("could not resolve all plugins for module \"%1\"").arg(uri));
1283                 else
1284                     error.setDescription(QQmlImportDatabase::tr("module \"%1\" plugin \"%2\" not found").arg(uri).arg(qmldir.plugins()[dynamicPluginsFound].name));
1285                 error.setUrl(QUrl::fromLocalFile(qmldirFilePath));
1286                 errors->prepend(error);
1287             }
1288             return false;
1289         }
1290 
1291         database->qmlDirFilesForWhichPluginsHaveBeenLoaded.insert(qmldirFilePath);
1292     }
1293     return true;
1294 }
1295 
getQmldirContent(const QString & qmldirIdentifier,const QString & uri,QQmlTypeLoaderQmldirContent * qmldir,QList<QQmlError> * errors)1296 bool QQmlImportsPrivate::getQmldirContent(const QString &qmldirIdentifier, const QString &uri,
1297                                           QQmlTypeLoaderQmldirContent *qmldir, QList<QQmlError> *errors)
1298 {
1299     Q_ASSERT(errors);
1300     Q_ASSERT(qmldir);
1301 
1302     *qmldir = typeLoader->qmldirContent(qmldirIdentifier);
1303     if ((*qmldir).hasContent()) {
1304         // Ensure that parsing was successful
1305         if ((*qmldir).hasError()) {
1306             QUrl url = QUrl::fromLocalFile(qmldirIdentifier);
1307             const QList<QQmlError> qmldirErrors = (*qmldir).errors(uri);
1308             for (int i = 0; i < qmldirErrors.size(); ++i) {
1309                 QQmlError error = qmldirErrors.at(i);
1310                 error.setUrl(url);
1311                 errors->append(error);
1312             }
1313             return false;
1314         }
1315     }
1316 
1317     return true;
1318 }
1319 
resolvedUri(const QString & dir_arg,QQmlImportDatabase * database)1320 QString QQmlImportsPrivate::resolvedUri(const QString &dir_arg, QQmlImportDatabase *database)
1321 {
1322     QString dir = dir_arg;
1323     if (dir.endsWith(Slash) || dir.endsWith(Backslash))
1324         dir.chop(1);
1325 
1326     QStringList paths = database->fileImportPath;
1327     if (!paths.isEmpty())
1328         std::sort(paths.begin(), paths.end(), std::greater<QString>()); // Ensure subdirs preceed their parents.
1329 
1330     QString stableRelativePath = dir;
1331     for (const QString &path : qAsConst(paths)) {
1332         if (dir.startsWith(path)) {
1333             stableRelativePath = dir.mid(path.length()+1);
1334             break;
1335         }
1336     }
1337 
1338     stableRelativePath.replace(Backslash, Slash);
1339 
1340     // remove optional versioning in dot notation from uri
1341     int versionDot = stableRelativePath.lastIndexOf(Dot);
1342     if (versionDot >= 0) {
1343         int nextSlash = stableRelativePath.indexOf(Slash, versionDot);
1344         if (nextSlash >= 0)
1345             stableRelativePath.remove(versionDot, nextSlash - versionDot);
1346         else
1347             stableRelativePath = stableRelativePath.left(versionDot);
1348     }
1349 
1350     stableRelativePath.replace(Slash, Dot);
1351 
1352     return stableRelativePath;
1353 }
1354 
1355 /*
1356 Locates the qmldir file for \a uri version \a vmaj.vmin.  Returns true if found,
1357 and fills in outQmldirFilePath and outQmldirUrl appropriately.  Otherwise returns
1358 false.
1359 */
locateLocalQmldir(const QString & uri,int vmaj,int vmin,QQmlImportDatabase * database,QString * outQmldirFilePath,QString * outQmldirPathUrl)1360 QQmlImports::LocalQmldirResult QQmlImportsPrivate::locateLocalQmldir(
1361         const QString &uri, int vmaj, int vmin, QQmlImportDatabase *database,
1362         QString *outQmldirFilePath, QString *outQmldirPathUrl)
1363 {
1364     Q_ASSERT(vmaj >= 0 && vmin >= 0); // Versions are always specified for libraries
1365 
1366     // Check cache first
1367 
1368     QQmlImportDatabase::QmldirCache *cacheHead = nullptr;
1369     {
1370         QQmlImportDatabase::QmldirCache **cachePtr = database->qmldirCache.value(uri);
1371         if (cachePtr) {
1372             cacheHead = *cachePtr;
1373             QQmlImportDatabase::QmldirCache *cache = cacheHead;
1374             while (cache) {
1375                 if (cache->versionMajor == vmaj && cache->versionMinor == vmin) {
1376                     *outQmldirFilePath = cache->qmldirFilePath;
1377                     *outQmldirPathUrl = cache->qmldirPathUrl;
1378                     return cache->qmldirFilePath.isEmpty() ? QQmlImports::QmldirNotFound
1379                                                            : QQmlImports::QmldirFound;
1380                 }
1381                 cache = cache->next;
1382             }
1383         }
1384     }
1385 
1386     QQmlTypeLoader &typeLoader = QQmlEnginePrivate::get(database->engine)->typeLoader;
1387 
1388     // Interceptor might redirect remote files to local ones.
1389     QQmlAbstractUrlInterceptor *interceptor = typeLoader.engine()->urlInterceptor();
1390     QStringList localImportPaths = database->importPathList(
1391                 interceptor ? QQmlImportDatabase::LocalOrRemote : QQmlImportDatabase::Local);
1392 
1393     // Search local import paths for a matching version
1394     const QStringList qmlDirPaths = QQmlImports::completeQmldirPaths(
1395                 uri, localImportPaths, vmaj, vmin);
1396     bool pathTurnedRemote = false;
1397     for (QString qmldirPath : qmlDirPaths) {
1398         if (interceptor) {
1399             const QUrl intercepted = interceptor->intercept(
1400                         QQmlImports::urlFromLocalFileOrQrcOrUrl(qmldirPath),
1401                         QQmlAbstractUrlInterceptor::QmldirFile);
1402             qmldirPath = QQmlFile::urlToLocalFileOrQrc(intercepted);
1403             if (!pathTurnedRemote && qmldirPath.isEmpty() && !QQmlFile::isLocalFile(intercepted))
1404                 pathTurnedRemote = true;
1405         }
1406 
1407         QString absoluteFilePath = typeLoader.absoluteFilePath(qmldirPath);
1408         if (!absoluteFilePath.isEmpty()) {
1409             QString url;
1410             const QStringRef absolutePath = absoluteFilePath.leftRef(absoluteFilePath.lastIndexOf(Slash) + 1);
1411             if (absolutePath.at(0) == Colon)
1412                 url = QLatin1String("qrc") + absolutePath;
1413             else
1414                 url = QUrl::fromLocalFile(absolutePath.toString()).toString();
1415 
1416             QQmlImportDatabase::QmldirCache *cache = new QQmlImportDatabase::QmldirCache;
1417             cache->versionMajor = vmaj;
1418             cache->versionMinor = vmin;
1419             cache->qmldirFilePath = absoluteFilePath;
1420             cache->qmldirPathUrl = url;
1421             cache->next = cacheHead;
1422             database->qmldirCache.insert(uri, cache);
1423 
1424             *outQmldirFilePath = absoluteFilePath;
1425             *outQmldirPathUrl = url;
1426 
1427             return QQmlImports::QmldirFound;
1428         }
1429     }
1430 
1431     QQmlImportDatabase::QmldirCache *cache = new QQmlImportDatabase::QmldirCache;
1432     cache->versionMajor = vmaj;
1433     cache->versionMinor = vmin;
1434     cache->next = cacheHead;
1435     database->qmldirCache.insert(uri, cache);
1436 
1437     return pathTurnedRemote ? QQmlImports::QmldirInterceptedToRemote : QQmlImports::QmldirNotFound;
1438 }
1439 
validateQmldirVersion(const QQmlTypeLoaderQmldirContent & qmldir,const QString & uri,int vmaj,int vmin,QList<QQmlError> * errors)1440 bool QQmlImportsPrivate::validateQmldirVersion(const QQmlTypeLoaderQmldirContent &qmldir, const QString &uri, int vmaj, int vmin,
1441                                                QList<QQmlError> *errors)
1442 {
1443     int lowest_min = INT_MAX;
1444     int highest_min = INT_MIN;
1445 
1446     typedef QQmlDirComponents::const_iterator ConstIterator;
1447     const QQmlDirComponents &components = qmldir.components();
1448 
1449     ConstIterator cend = components.constEnd();
1450     for (ConstIterator cit = components.constBegin(); cit != cend; ++cit) {
1451         for (ConstIterator cit2 = components.constBegin(); cit2 != cit; ++cit2) {
1452             if ((cit2->typeName == cit->typeName) &&
1453                 (cit2->majorVersion == cit->majorVersion) &&
1454                 (cit2->minorVersion == cit->minorVersion)) {
1455                 // This entry clashes with a predecessor
1456                 QQmlError error;
1457                 error.setDescription(QQmlImportDatabase::tr("\"%1\" version %2.%3 is defined more than once in module \"%4\"")
1458                                      .arg(cit->typeName).arg(cit->majorVersion).arg(cit->minorVersion).arg(uri));
1459                 errors->prepend(error);
1460                 return false;
1461             }
1462         }
1463 
1464         if (cit->majorVersion == vmaj) {
1465             lowest_min = qMin(lowest_min, cit->minorVersion);
1466             highest_min = qMax(highest_min, cit->minorVersion);
1467         }
1468     }
1469 
1470     typedef QList<QQmlDirParser::Script>::const_iterator SConstIterator;
1471     const QQmlDirScripts &scripts = qmldir.scripts();
1472 
1473     SConstIterator send = scripts.constEnd();
1474     for (SConstIterator sit = scripts.constBegin(); sit != send; ++sit) {
1475         for (SConstIterator sit2 = scripts.constBegin(); sit2 != sit; ++sit2) {
1476             if ((sit2->nameSpace == sit->nameSpace) &&
1477                 (sit2->majorVersion == sit->majorVersion) &&
1478                 (sit2->minorVersion == sit->minorVersion)) {
1479                 // This entry clashes with a predecessor
1480                 QQmlError error;
1481                 error.setDescription(QQmlImportDatabase::tr("\"%1\" version %2.%3 is defined more than once in module \"%4\"")
1482                                      .arg(sit->nameSpace).arg(sit->majorVersion).arg(sit->minorVersion).arg(uri));
1483                 errors->prepend(error);
1484                 return false;
1485             }
1486         }
1487 
1488         if (sit->majorVersion == vmaj) {
1489             lowest_min = qMin(lowest_min, sit->minorVersion);
1490             highest_min = qMax(highest_min, sit->minorVersion);
1491         }
1492     }
1493 
1494     if (lowest_min > vmin || highest_min < vmin) {
1495         QQmlError error;
1496         error.setDescription(QQmlImportDatabase::tr("module \"%1\" version %2.%3 is not installed").arg(uri).arg(vmaj).arg(vmin));
1497         errors->prepend(error);
1498         return false;
1499     }
1500 
1501     return true;
1502 }
1503 
importNamespace(const QString & prefix) const1504 QQmlImportNamespace *QQmlImportsPrivate::importNamespace(const QString &prefix) const
1505 {
1506     QQmlImportNamespace *nameSpace = nullptr;
1507 
1508     if (prefix.isEmpty()) {
1509         nameSpace = &unqualifiedset;
1510     } else {
1511         nameSpace = findQualifiedNamespace(prefix);
1512 
1513         if (!nameSpace) {
1514             nameSpace = new QQmlImportNamespace;
1515             nameSpace->prefix = prefix;
1516             qualifiedSets.append(nameSpace);
1517         }
1518     }
1519 
1520     return nameSpace;
1521 }
1522 
addImportToNamespace(QQmlImportNamespace * nameSpace,const QString & uri,const QString & url,int vmaj,int vmin,QV4::CompiledData::Import::ImportType type,QList<QQmlError> * errors,bool lowPrecedence)1523 QQmlImportInstance *QQmlImportsPrivate::addImportToNamespace(QQmlImportNamespace *nameSpace,
1524                                                                       const QString &uri, const QString &url, int vmaj, int vmin,
1525                                                                       QV4::CompiledData::Import::ImportType type,
1526                                                                       QList<QQmlError> *errors, bool lowPrecedence)
1527 {
1528     Q_ASSERT(nameSpace);
1529     Q_ASSERT(errors);
1530     Q_UNUSED(errors);
1531     Q_ASSERT(url.isEmpty() || url.endsWith(Slash));
1532 
1533     QQmlImportInstance *import = new QQmlImportInstance;
1534     import->uri = uri;
1535     import->url = url;
1536     import->localDirectoryPath = QQmlFile::urlToLocalFileOrQrc(url);
1537     import->majversion = vmaj;
1538     import->minversion = vmin;
1539     import->isLibrary = (type == QV4::CompiledData::Import::ImportLibrary);
1540 
1541     if (lowPrecedence)
1542         nameSpace->imports.append(import);
1543     else
1544         nameSpace->imports.prepend(import);
1545 
1546     return import;
1547 }
1548 
addLibraryImport(const QString & uri,const QString & prefix,int vmaj,int vmin,const QString & qmldirIdentifier,const QString & qmldirUrl,bool incomplete,QQmlImportDatabase * database,QList<QQmlError> * errors)1549 bool QQmlImportsPrivate::addLibraryImport(const QString& uri, const QString &prefix,
1550                                           int vmaj, int vmin, const QString &qmldirIdentifier, const QString &qmldirUrl, bool incomplete,
1551                                           QQmlImportDatabase *database,
1552                                           QList<QQmlError> *errors)
1553 {
1554     Q_ASSERT(database);
1555     Q_ASSERT(errors);
1556 
1557     QQmlImportNamespace *nameSpace = importNamespace(prefix);
1558     Q_ASSERT(nameSpace);
1559 
1560     QQmlImportInstance *inserted = addImportToNamespace(nameSpace, uri, qmldirUrl, vmaj, vmin, QV4::CompiledData::Import::ImportLibrary, errors);
1561     Q_ASSERT(inserted);
1562 
1563     if (!incomplete) {
1564         QQmlTypeLoaderQmldirContent qmldir;
1565 
1566         if (!qmldirIdentifier.isEmpty()) {
1567             if (!getQmldirContent(qmldirIdentifier, uri, &qmldir, errors))
1568                 return false;
1569 
1570             if (qmldir.hasContent()) {
1571                 if (!importExtension(qmldir.pluginLocation(), uri, vmaj, vmin, database, qmldir, errors))
1572                     return false;
1573 
1574                 if (!inserted->setQmldirContent(qmldirUrl, qmldir, nameSpace, errors))
1575                     return false;
1576             }
1577         }
1578 
1579         // Ensure that we are actually providing something
1580         if ((vmaj < 0) || (vmin < 0) || !QQmlMetaType::isModule(uri, vmaj, vmin)) {
1581             if (inserted->qmlDirComponents.isEmpty() && inserted->qmlDirScripts.isEmpty()) {
1582                 QQmlError error;
1583                 if (QQmlMetaType::isAnyModule(uri))
1584                     error.setDescription(QQmlImportDatabase::tr("module \"%1\" version %2.%3 is not installed").arg(uri).arg(vmaj).arg(vmin));
1585                 else
1586                     error.setDescription(QQmlImportDatabase::tr("module \"%1\" is not installed").arg(uri));
1587                 errors->prepend(error);
1588                 return false;
1589             } else if ((vmaj >= 0) && (vmin >= 0) && qmldir.hasContent()) {
1590                 // Verify that the qmldir content is valid for this version
1591                 if (!validateQmldirVersion(qmldir, uri, vmaj, vmin, errors))
1592                     return false;
1593             }
1594         }
1595     }
1596 
1597     return true;
1598 }
1599 
addFileImport(const QString & uri,const QString & prefix,int vmaj,int vmin,bool isImplicitImport,bool incomplete,QQmlImportDatabase * database,QList<QQmlError> * errors)1600 bool QQmlImportsPrivate::addFileImport(const QString& uri, const QString &prefix,
1601                                        int vmaj, int vmin,
1602                                        bool isImplicitImport, bool incomplete, QQmlImportDatabase *database,
1603                                        QList<QQmlError> *errors)
1604 {
1605     Q_ASSERT(errors);
1606 
1607     QQmlImportNamespace *nameSpace = importNamespace(prefix);
1608     Q_ASSERT(nameSpace);
1609 
1610     // The uri for this import.  For library imports this is the same as uri
1611     // specified by the user, but it may be different in the case of file imports.
1612     QString importUri = uri;
1613     QString qmldirUrl = resolveLocalUrl(base, importUri + (importUri.endsWith(Slash)
1614                                                            ? String_qmldir
1615                                                            : Slash_qmldir));
1616     if (QQmlAbstractUrlInterceptor *interceptor = typeLoader->engine()->urlInterceptor()) {
1617         qmldirUrl = interceptor->intercept(QUrl(qmldirUrl),
1618                                            QQmlAbstractUrlInterceptor::QmldirFile).toString();
1619     }
1620     QString qmldirIdentifier;
1621 
1622     if (QQmlFile::isLocalFile(qmldirUrl)) {
1623 
1624         QString localFileOrQrc = QQmlFile::urlToLocalFileOrQrc(qmldirUrl);
1625         Q_ASSERT(!localFileOrQrc.isEmpty());
1626 
1627         const QString dir = localFileOrQrc.left(localFileOrQrc.lastIndexOf(Slash) + 1);
1628         if (!typeLoader->directoryExists(dir)) {
1629             if (!isImplicitImport) {
1630                 QQmlError error;
1631                 error.setDescription(QQmlImportDatabase::tr("\"%1\": no such directory").arg(uri));
1632                 error.setUrl(QUrl(qmldirUrl));
1633                 errors->prepend(error);
1634             }
1635             return false;
1636         }
1637 
1638         // Transforms the (possible relative) uri into our best guess relative to the
1639         // import paths.
1640         importUri = resolvedUri(dir, database);
1641         if (importUri.endsWith(Slash))
1642             importUri.chop(1);
1643 
1644         if (!typeLoader->absoluteFilePath(localFileOrQrc).isEmpty())
1645             qmldirIdentifier = localFileOrQrc;
1646 
1647     } else if (nameSpace->prefix.isEmpty() && !incomplete) {
1648 
1649         if (!isImplicitImport) {
1650             QQmlError error;
1651             error.setDescription(QQmlImportDatabase::tr("import \"%1\" has no qmldir and no namespace").arg(importUri));
1652             error.setUrl(QUrl(qmldirUrl));
1653             errors->prepend(error);
1654         }
1655 
1656         return false;
1657 
1658     }
1659 
1660     // The url for the path containing files for this import
1661     QString url = resolveLocalUrl(base, uri);
1662     if (!url.endsWith(Slash) && !url.endsWith(Backslash))
1663         url += Slash;
1664 
1665     // ### For enum support, we are now adding the implicit import always (and earlier). Bail early
1666     //     if the implicit import has already been explicitly added, otherwise we can run into issues
1667     //     with duplicate imports. However remember that we attempted to add this as implicit import, to
1668     //     allow for the loading of internal types.
1669     if (isImplicitImport) {
1670         for (QList<QQmlImportInstance *>::const_iterator it = nameSpace->imports.constBegin();
1671              it != nameSpace->imports.constEnd(); ++it) {
1672             if ((*it)->url == url) {
1673                 (*it)->implicitlyImported = true;
1674                 return true;
1675             }
1676         }
1677     }
1678 
1679     QQmlImportInstance *inserted = addImportToNamespace(nameSpace, importUri, url, vmaj, vmin, QV4::CompiledData::Import::ImportFile, errors, isImplicitImport);
1680     Q_ASSERT(inserted);
1681 
1682     if (!incomplete && !qmldirIdentifier.isEmpty()) {
1683         QQmlTypeLoaderQmldirContent qmldir;
1684         if (!getQmldirContent(qmldirIdentifier, importUri, &qmldir, errors))
1685             return false;
1686 
1687         if (qmldir.hasContent()) {
1688             if (!importExtension(qmldir.pluginLocation(), importUri, vmaj, vmin, database, qmldir, errors))
1689                 return false;
1690 
1691             if (!inserted->setQmldirContent(url, qmldir, nameSpace, errors))
1692                 return false;
1693         }
1694     }
1695 
1696     return true;
1697 }
1698 
updateQmldirContent(const QString & uri,const QString & prefix,const QString & qmldirIdentifier,const QString & qmldirUrl,QQmlImportDatabase * database,QList<QQmlError> * errors)1699 bool QQmlImportsPrivate::updateQmldirContent(const QString &uri, const QString &prefix,
1700                                              const QString &qmldirIdentifier, const QString& qmldirUrl,
1701                                              QQmlImportDatabase *database, QList<QQmlError> *errors)
1702 {
1703     QQmlImportNamespace *nameSpace = importNamespace(prefix);
1704     Q_ASSERT(nameSpace);
1705 
1706     if (QQmlImportInstance *import = nameSpace->findImport(uri)) {
1707         QQmlTypeLoaderQmldirContent qmldir;
1708         if (!getQmldirContent(qmldirIdentifier, uri, &qmldir, errors))
1709             return false;
1710 
1711         if (qmldir.hasContent()) {
1712             int vmaj = import->majversion;
1713             int vmin = import->minversion;
1714             if (!importExtension(qmldir.pluginLocation(), uri, vmaj, vmin, database, qmldir, errors))
1715                 return false;
1716 
1717             if (import->setQmldirContent(qmldirUrl, qmldir, nameSpace, errors)) {
1718                 if (import->qmlDirComponents.isEmpty() && import->qmlDirScripts.isEmpty()) {
1719                     // The implicit import qmldir can be empty, and plugins have no extra versions
1720                     if (uri != QLatin1String(".") && !QQmlMetaType::isModule(uri, vmaj, vmin)) {
1721                         QQmlError error;
1722                         if (QQmlMetaType::isAnyModule(uri))
1723                             error.setDescription(QQmlImportDatabase::tr("module \"%1\" version %2.%3 is not installed").arg(uri).arg(vmaj).arg(vmin));
1724                         else
1725                             error.setDescription(QQmlImportDatabase::tr("module \"%1\" is not installed").arg(uri));
1726                         errors->prepend(error);
1727                         return false;
1728                     }
1729                 } else if ((vmaj >= 0) && (vmin >= 0)) {
1730                     // Verify that the qmldir content is valid for this version
1731                     if (!validateQmldirVersion(qmldir, uri, vmaj, vmin, errors))
1732                         return false;
1733                 }
1734                 return true;
1735             }
1736         }
1737     }
1738 
1739     if (errors->isEmpty()) {
1740         QQmlError error;
1741         error.setDescription(QQmlTypeLoader::tr("Cannot update qmldir content for '%1'").arg(uri));
1742         errors->prepend(error);
1743     }
1744 
1745     return false;
1746 }
1747 
1748 /*!
1749   \internal
1750 
1751   Adds an implicit "." file import.  This is equivalent to calling addFileImport(), but error
1752   messages related to the path or qmldir file not existing are suppressed.
1753 
1754   Additionally, this will add the import with lowest instead of highest precedence.
1755 */
addImplicitImport(QQmlImportDatabase * importDb,QList<QQmlError> * errors)1756 bool QQmlImports::addImplicitImport(QQmlImportDatabase *importDb, QList<QQmlError> *errors)
1757 {
1758     Q_ASSERT(errors);
1759 
1760     if (qmlImportTrace())
1761         qDebug().nospace() << "QQmlImports(" << qPrintable(baseUrl().toString())
1762                            << ")::addImplicitImport";
1763 
1764     bool incomplete = !isLocal(baseUrl());
1765     return d->addFileImport(QLatin1String("."), QString(), -1, -1, true, incomplete, importDb, errors);
1766 }
1767 
1768 /*!
1769  \internal
1770  */
addInlineComponentImport(QQmlImportInstance * const importInstance,const QString & name,const QUrl importUrl,QQmlType containingType)1771 bool QQmlImports::addInlineComponentImport(QQmlImportInstance *const importInstance, const QString &name, const QUrl importUrl, QQmlType containingType)
1772 {
1773     importInstance->url = importUrl.toString();
1774     importInstance->uri = name;
1775     importInstance->isInlineComponent = true;
1776     importInstance->majversion = 0;
1777     importInstance->minversion = 0;
1778     importInstance->containingType = containingType;
1779     d->unqualifiedset.imports.push_back(importInstance);
1780     d->unqualifiedset.setNeedsSorting(true);
1781     return true;
1782 }
1783 
1784 /*!
1785   \internal
1786 
1787   Adds information to \a imports such that subsequent calls to resolveType()
1788   will resolve types qualified by \a prefix by considering types found at the given \a uri.
1789 
1790   The uri is either a directory (if importType is FileImport), or a URI resolved using paths
1791   added via addImportPath() (if importType is LibraryImport).
1792 
1793   The \a prefix may be empty, in which case the import location is considered for
1794   unqualified types.
1795 
1796   The base URL must already have been set with Import::setBaseUrl().
1797 
1798   Optionally, the url the import resolved to can be returned by providing the url parameter.
1799   Not all imports will result in an output url being generated, in which case the url will
1800   be set to an empty string.
1801 
1802   Returns true on success, and false on failure.  In case of failure, the errors array will
1803   filled appropriately.
1804 */
addFileImport(QQmlImportDatabase * importDb,const QString & uri,const QString & prefix,int vmaj,int vmin,bool incomplete,QList<QQmlError> * errors)1805 bool QQmlImports::addFileImport(QQmlImportDatabase *importDb,
1806                                 const QString& uri, const QString& prefix, int vmaj, int vmin,
1807                                 bool incomplete, QList<QQmlError> *errors)
1808 {
1809     Q_ASSERT(importDb);
1810     Q_ASSERT(errors);
1811 
1812     if (qmlImportTrace())
1813         qDebug().nospace() << "QQmlImports(" << qPrintable(baseUrl().toString()) << ')' << "::addFileImport: "
1814                            << uri << ' ' << vmaj << '.' << vmin << " as " << prefix;
1815 
1816     return d->addFileImport(uri, prefix, vmaj, vmin, false, incomplete, importDb, errors);
1817 }
1818 
addLibraryImport(QQmlImportDatabase * importDb,const QString & uri,const QString & prefix,int vmaj,int vmin,const QString & qmldirIdentifier,const QString & qmldirUrl,bool incomplete,QList<QQmlError> * errors)1819 bool QQmlImports::addLibraryImport(QQmlImportDatabase *importDb,
1820                                    const QString &uri, const QString &prefix, int vmaj, int vmin,
1821                                    const QString &qmldirIdentifier, const QString& qmldirUrl, bool incomplete, QList<QQmlError> *errors)
1822 {
1823     Q_ASSERT(importDb);
1824     Q_ASSERT(errors);
1825 
1826     if (qmlImportTrace())
1827         qDebug().nospace() << "QQmlImports(" << qPrintable(baseUrl().toString()) << ')' << "::addLibraryImport: "
1828                            << uri << ' ' << vmaj << '.' << vmin << " as " << prefix;
1829 
1830     return d->addLibraryImport(uri, prefix, vmaj, vmin, qmldirIdentifier, qmldirUrl, incomplete, importDb, errors);
1831 }
1832 
updateQmldirContent(QQmlImportDatabase * importDb,const QString & uri,const QString & prefix,const QString & qmldirIdentifier,const QString & qmldirUrl,QList<QQmlError> * errors)1833 bool QQmlImports::updateQmldirContent(QQmlImportDatabase *importDb,
1834                                       const QString &uri, const QString &prefix,
1835                                       const QString &qmldirIdentifier, const QString& qmldirUrl, QList<QQmlError> *errors)
1836 {
1837     Q_ASSERT(importDb);
1838     Q_ASSERT(errors);
1839 
1840     if (qmlImportTrace())
1841         qDebug().nospace() << "QQmlImports(" << qPrintable(baseUrl().toString()) << ')' << "::updateQmldirContent: "
1842                            << uri << " to " << qmldirUrl << " as " << prefix;
1843 
1844     return d->updateQmldirContent(uri, prefix, qmldirIdentifier, qmldirUrl, importDb, errors);
1845 }
1846 
locateLocalQmldir(QQmlImportDatabase * importDb,const QString & uri,int vmaj,int vmin,QString * qmldirFilePath,QString * url)1847 QQmlImports::LocalQmldirResult QQmlImports::locateLocalQmldir(
1848         QQmlImportDatabase *importDb, const QString &uri, int vmaj,  int vmin,
1849         QString *qmldirFilePath, QString *url)
1850 {
1851     return d->locateLocalQmldir(uri, vmaj, vmin, importDb, qmldirFilePath, url);
1852 }
1853 
isLocal(const QString & url)1854 bool QQmlImports::isLocal(const QString &url)
1855 {
1856     return !QQmlFile::urlToLocalFileOrQrc(url).isEmpty();
1857 }
1858 
isLocal(const QUrl & url)1859 bool QQmlImports::isLocal(const QUrl &url)
1860 {
1861     return !QQmlFile::urlToLocalFileOrQrc(url).isEmpty();
1862 }
1863 
urlFromLocalFileOrQrcOrUrl(const QString & file)1864 QUrl QQmlImports::urlFromLocalFileOrQrcOrUrl(const QString &file)
1865 {
1866     QUrl url(QLatin1String(file.at(0) == Colon ? "qrc" : "") + file);
1867 
1868     // We don't support single character schemes as those conflict with windows drive letters.
1869     if (url.scheme().length() < 2)
1870         return QUrl::fromLocalFile(file);
1871     return url;
1872 }
1873 
setDesignerSupportRequired(bool b)1874 void QQmlImports::setDesignerSupportRequired(bool b)
1875 {
1876     designerSupportRequired = b;
1877 }
1878 
1879 
1880 /*!
1881 \class QQmlImportDatabase
1882 \brief The QQmlImportDatabase class manages the QML imports for a QQmlEngine.
1883 \internal
1884 */
QQmlImportDatabase(QQmlEngine * e)1885 QQmlImportDatabase::QQmlImportDatabase(QQmlEngine *e)
1886 : engine(e)
1887 {
1888     filePluginPath << QLatin1String(".");
1889     // Search order is applicationDirPath(), qrc:/qt-project.org/imports, $QML2_IMPORT_PATH, QLibraryInfo::Qml2ImportsPath
1890 
1891     QString installImportsPath = QLibraryInfo::location(QLibraryInfo::Qml2ImportsPath);
1892     addImportPath(installImportsPath);
1893 
1894     // env import paths
1895     if (Q_UNLIKELY(!qEnvironmentVariableIsEmpty("QML2_IMPORT_PATH"))) {
1896         const QString envImportPath = qEnvironmentVariable("QML2_IMPORT_PATH");
1897 #if defined(Q_OS_WIN)
1898         QLatin1Char pathSep(';');
1899 #else
1900         QLatin1Char pathSep(':');
1901 #endif
1902         QStringList paths = envImportPath.split(pathSep, Qt::SkipEmptyParts);
1903         for (int ii = paths.count() - 1; ii >= 0; --ii)
1904             addImportPath(paths.at(ii));
1905     }
1906 
1907     addImportPath(QStringLiteral("qrc:/qt-project.org/imports"));
1908     addImportPath(QCoreApplication::applicationDirPath());
1909 #if defined(Q_OS_ANDROID)
1910     addImportPath(QStringLiteral("qrc:/android_rcc_bundle/qml"));
1911     if (Q_UNLIKELY(!qEnvironmentVariableIsEmpty("QT_BUNDLED_LIBS_PATH"))) {
1912         const QString envImportPath = qEnvironmentVariable("QT_BUNDLED_LIBS_PATH");
1913         QLatin1Char pathSep(':');
1914         QStringList paths = envImportPath.split(pathSep, Qt::SkipEmptyParts);
1915         for (int ii = paths.count() - 1; ii >= 0; --ii)
1916             addPluginPath(paths.at(ii));
1917     }
1918 #endif
1919 }
1920 
~QQmlImportDatabase()1921 QQmlImportDatabase::~QQmlImportDatabase()
1922 {
1923     clearDirCache();
1924 }
1925 
1926 /*!
1927   \internal
1928 
1929   Returns the result of the merge of \a baseName with \a path, \a suffixes, and \a prefix.
1930   The \a prefix must contain the dot.
1931 
1932   \a qmldirPath is the location of the qmldir file.
1933  */
resolvePlugin(QQmlTypeLoader * typeLoader,const QString & qmldirPath,const QString & qmldirPluginPath,const QString & baseName,const QStringList & suffixes,const QString & prefix)1934 QString QQmlImportDatabase::resolvePlugin(QQmlTypeLoader *typeLoader,
1935                                           const QString &qmldirPath,
1936                                           const QString &qmldirPluginPath,
1937                                           const QString &baseName, const QStringList &suffixes,
1938                                           const QString &prefix)
1939 {
1940     QStringList searchPaths = filePluginPath;
1941     bool qmldirPluginPathIsRelative = QDir::isRelativePath(qmldirPluginPath);
1942     if (!qmldirPluginPathIsRelative)
1943         searchPaths.prepend(qmldirPluginPath);
1944 
1945     for (const QString &pluginPath : qAsConst(searchPaths)) {
1946         QString resolvedPath;
1947         if (pluginPath == QLatin1String(".")) {
1948             if (qmldirPluginPathIsRelative && !qmldirPluginPath.isEmpty() && qmldirPluginPath != QLatin1String("."))
1949                 resolvedPath = QDir::cleanPath(qmldirPath + Slash + qmldirPluginPath);
1950             else
1951                 resolvedPath = qmldirPath;
1952         } else {
1953             if (QDir::isRelativePath(pluginPath))
1954                 resolvedPath = QDir::cleanPath(qmldirPath + Slash + pluginPath);
1955             else
1956                 resolvedPath = pluginPath;
1957         }
1958 
1959         // hack for resources, should probably go away
1960         if (resolvedPath.startsWith(Colon))
1961             resolvedPath = QCoreApplication::applicationDirPath();
1962 
1963         if (!resolvedPath.endsWith(Slash))
1964             resolvedPath += Slash;
1965 
1966 #if defined(Q_OS_ANDROID)
1967         if (qmldirPath.size() > 25 && qmldirPath.at(0) == QLatin1Char(':') && qmldirPath.at(1) == QLatin1Char('/') &&
1968            qmldirPath.startsWith(QStringLiteral(":/android_rcc_bundle/qml/"), Qt::CaseInsensitive)) {
1969             QString pluginName = qmldirPath.mid(21) + Slash + baseName;
1970             pluginName.replace(QLatin1Char('/'), QLatin1Char('_'));
1971             QString bundledPath = resolvedPath + QLatin1String("lib") + pluginName;
1972             for (const QString &suffix : suffixes) {
1973                 const QString absolutePath = typeLoader->absoluteFilePath(bundledPath + suffix);
1974                 if (!absolutePath.isEmpty())
1975                     return absolutePath;
1976             }
1977         }
1978 #endif
1979         resolvedPath += prefix + baseName;
1980         for (const QString &suffix : suffixes) {
1981             const QString absolutePath = typeLoader->absoluteFilePath(resolvedPath + suffix);
1982             if (!absolutePath.isEmpty())
1983                 return absolutePath;
1984         }
1985     }
1986 
1987     if (qmlImportTrace())
1988         qDebug() << "QQmlImportDatabase::resolvePlugin: Could not resolve plugin" << baseName
1989                  << "in" << qmldirPath;
1990 
1991     return QString();
1992 }
1993 
1994 /*!
1995   \internal
1996 
1997   Returns the result of the merge of \a baseName with \a dir and the platform suffix.
1998 
1999   \table
2000   \header \li Platform \li Valid suffixes
2001   \row \li Windows     \li \c .dll
2002   \row \li Unix/Linux  \li \c .so
2003   \row \li \macos    \li \c .dylib, \c .bundle, \c .so
2004   \endtable
2005 
2006   Version number on unix are ignored.
2007 */
resolvePlugin(QQmlTypeLoader * typeLoader,const QString & qmldirPath,const QString & qmldirPluginPath,const QString & baseName)2008 QString QQmlImportDatabase::resolvePlugin(QQmlTypeLoader *typeLoader,
2009                                                   const QString &qmldirPath, const QString &qmldirPluginPath,
2010                                                   const QString &baseName)
2011 {
2012 #if defined(Q_OS_WIN)
2013     static const QString prefix;
2014     static const QStringList suffixes = {
2015 # ifdef QT_DEBUG
2016         QLatin1String("d.dll"), // try a qmake-style debug build first
2017         QLatin1String(".dll")
2018 #else
2019         QLatin1String(".dll"),
2020         QLatin1String("d.dll") // try a qmake-style debug build after
2021 # endif
2022     };
2023 #elif defined(Q_OS_DARWIN)
2024     static const QString prefix = QLatin1String("lib");
2025     static const QStringList suffixes = {
2026 # ifdef QT_DEBUG
2027         QLatin1String("_debug.dylib"), // try a qmake-style debug build first
2028         QLatin1String(".dylib"),
2029 # else
2030         QLatin1String(".dylib"),
2031         QLatin1String("_debug.dylib"), // try a qmake-style debug build after
2032 # endif
2033         QLatin1String(".so"),
2034         QLatin1String(".bundle")
2035     };
2036 #else  // Unix
2037     static const QString prefix = QLatin1String("lib");
2038     static const QStringList suffixes = {
2039 # if defined(Q_OS_ANDROID)
2040         QStringLiteral(LIBS_SUFFIX),
2041 # endif
2042         QLatin1String(".so")
2043 
2044     };
2045 #endif
2046 
2047     return resolvePlugin(typeLoader, qmldirPath, qmldirPluginPath, baseName, suffixes, prefix);
2048 }
2049 
2050 /*!
2051     \internal
2052 */
pluginPathList() const2053 QStringList QQmlImportDatabase::pluginPathList() const
2054 {
2055     return filePluginPath;
2056 }
2057 
2058 /*!
2059     \internal
2060 */
setPluginPathList(const QStringList & paths)2061 void QQmlImportDatabase::setPluginPathList(const QStringList &paths)
2062 {
2063     if (qmlImportTrace())
2064         qDebug().nospace() << "QQmlImportDatabase::setPluginPathList: " << paths;
2065 
2066     filePluginPath = paths;
2067 }
2068 
2069 /*!
2070     \internal
2071 */
addPluginPath(const QString & path)2072 void QQmlImportDatabase::addPluginPath(const QString& path)
2073 {
2074     if (qmlImportTrace())
2075         qDebug().nospace() << "QQmlImportDatabase::addPluginPath: " << path;
2076 
2077     QUrl url = QUrl(path);
2078     if (url.isRelative() || url.scheme() == QLatin1String("file")
2079             || (url.scheme().length() == 1 && QFile::exists(path)) ) {  // windows path
2080         QDir dir = QDir(path);
2081         filePluginPath.prepend(dir.canonicalPath());
2082     } else {
2083         filePluginPath.prepend(path);
2084     }
2085 }
2086 
2087 /*!
2088     \internal
2089 */
addImportPath(const QString & path)2090 void QQmlImportDatabase::addImportPath(const QString& path)
2091 {
2092     if (qmlImportTrace())
2093         qDebug().nospace() << "QQmlImportDatabase::addImportPath: " << path;
2094 
2095     if (path.isEmpty())
2096         return;
2097 
2098     QUrl url = QUrl(path);
2099     QString cPath;
2100 
2101     if (url.scheme() == QLatin1String("file")) {
2102         cPath = QQmlFile::urlToLocalFileOrQrc(url);
2103     } else if (path.startsWith(QLatin1Char(':'))) {
2104         // qrc directory, e.g. :/foo
2105         // need to convert to a qrc url, e.g. qrc:/foo
2106         cPath = QLatin1String("qrc") + path;
2107         cPath.replace(Backslash, Slash);
2108     } else if (url.isRelative() ||
2109                (url.scheme().length() == 1 && QFile::exists(path)) ) {  // windows path
2110         QDir dir = QDir(path);
2111         cPath = dir.canonicalPath();
2112     } else {
2113         cPath = path;
2114         cPath.replace(Backslash, Slash);
2115     }
2116 
2117     if (!cPath.isEmpty()
2118         && !fileImportPath.contains(cPath))
2119         fileImportPath.prepend(cPath);
2120 }
2121 
2122 /*!
2123     \internal
2124 */
importPathList(PathType type) const2125 QStringList QQmlImportDatabase::importPathList(PathType type) const
2126 {
2127     if (type == LocalOrRemote)
2128         return fileImportPath;
2129 
2130     QStringList list;
2131     for (const QString &path : fileImportPath) {
2132         bool localPath = isPathAbsolute(path) || QQmlFile::isLocalFile(path);
2133         if (localPath == (type == Local))
2134             list.append(path);
2135     }
2136 
2137     return list;
2138 }
2139 
2140 /*!
2141     \internal
2142 */
setImportPathList(const QStringList & paths)2143 void QQmlImportDatabase::setImportPathList(const QStringList &paths)
2144 {
2145     if (qmlImportTrace())
2146         qDebug().nospace() << "QQmlImportDatabase::setImportPathList: " << paths;
2147 
2148     fileImportPath.clear();
2149     for (auto it = paths.crbegin(); it != paths.crend(); ++it)
2150         addImportPath(*it);
2151 
2152     // Our existing cached paths may have been invalidated
2153     clearDirCache();
2154 }
2155 
2156 /*!
2157     \internal
2158 */
registerPluginTypes(QObject * instance,const QString & basePath,const QString & uri,const QString & typeNamespace,int vmaj,QList<QQmlError> * errors)2159 static bool registerPluginTypes(QObject *instance, const QString &basePath, const QString &uri,
2160                                 const QString &typeNamespace, int vmaj, QList<QQmlError> *errors)
2161 {
2162     if (qmlImportTrace())
2163         qDebug().nospace() << "QQmlImportDatabase::registerPluginTypes: " << uri << " from " << basePath;
2164 
2165     if (!QQmlMetaType::registerPluginTypes(instance, basePath, uri, typeNamespace, vmaj, errors))
2166         return false;
2167 
2168     if (vmaj >= 0 && !typeNamespace.isEmpty() && !QQmlMetaType::protectModule(uri, vmaj)) {
2169         QQmlError error;
2170         error.setDescription(
2171                     QString::fromLatin1("Cannot protect module %1 %2 as it was never registered")
2172                     .arg(uri).arg(vmaj));
2173         errors->append(error);
2174         return false;
2175     }
2176 
2177     return true;
2178 }
2179 
2180 /*!
2181     \internal
2182 */
importStaticPlugin(QObject * instance,const QString & basePath,const QString & uri,const QString & typeNamespace,int vmaj,QList<QQmlError> * errors)2183 bool QQmlImportDatabase::importStaticPlugin(QObject *instance, const QString &basePath,
2184                                       const QString &uri, const QString &typeNamespace, int vmaj, QList<QQmlError> *errors)
2185 {
2186     // Dynamic plugins are differentiated by their filepath. For static plugins we
2187     // don't have that information so we use their address as key instead.
2188     const QString uniquePluginID = QString::asprintf("%p", instance);
2189     {
2190         StringRegisteredPluginMap *plugins = qmlEnginePluginsWithRegisteredTypes();
2191         QMutexLocker lock(&plugins->mutex);
2192 
2193         // Plugin types are global across all engines and should only be
2194         // registered once. But each engine still needs to be initialized.
2195         bool typesRegistered = plugins->contains(uniquePluginID);
2196 
2197         if (typesRegistered) {
2198             Q_ASSERT_X(plugins->value(uniquePluginID).uri == uri,
2199                        "QQmlImportDatabase::importStaticPlugin",
2200                        "Internal error: Static plugin imported previously with different uri");
2201         } else {
2202             RegisteredPlugin plugin;
2203             plugin.uri = uri;
2204             plugin.loader = nullptr;
2205             plugins->insert(uniquePluginID, plugin);
2206 
2207             if (!registerPluginTypes(instance, basePath, uri, typeNamespace, vmaj, errors))
2208                 return false;
2209         }
2210 
2211         // Release the lock on plugins early as we're done with the global part. Releasing the lock
2212         // also allows other QML loader threads to acquire the lock while this thread is blocking
2213         // in the initializeEngine call to the gui thread (which in turn may be busy waiting for
2214         // other QML loader threads and thus not process the initializeEngine call).
2215     }
2216 
2217     if (!initializedPlugins.contains(uniquePluginID))
2218         finalizePlugin(instance, uniquePluginID, uri);
2219 
2220     return true;
2221 }
2222 
2223 #if QT_CONFIG(library)
2224 /*!
2225     \internal
2226 */
importDynamicPlugin(const QString & filePath,const QString & uri,const QString & typeNamespace,int vmaj,QList<QQmlError> * errors)2227 bool QQmlImportDatabase::importDynamicPlugin(const QString &filePath, const QString &uri,
2228                                              const QString &typeNamespace, int vmaj, QList<QQmlError> *errors)
2229 {
2230     QFileInfo fileInfo(filePath);
2231     const QString absoluteFilePath = fileInfo.absoluteFilePath();
2232 
2233     QObject *instance = nullptr;
2234     bool engineInitialized = initializedPlugins.contains(absoluteFilePath);
2235     {
2236         StringRegisteredPluginMap *plugins = qmlEnginePluginsWithRegisteredTypes();
2237         QMutexLocker lock(&plugins->mutex);
2238         bool typesRegistered = plugins->contains(absoluteFilePath);
2239 
2240         if (typesRegistered) {
2241             Q_ASSERT_X(plugins->value(absoluteFilePath).uri == uri,
2242                        "QQmlImportDatabase::importDynamicPlugin",
2243                        "Internal error: Plugin imported previously with different uri");
2244         }
2245 
2246         if (!engineInitialized || !typesRegistered) {
2247             if (!QQml_isFileCaseCorrect(absoluteFilePath)) {
2248                 if (errors) {
2249                     QQmlError error;
2250                     error.setDescription(tr("File name case mismatch for \"%1\"").arg(absoluteFilePath));
2251                     errors->prepend(error);
2252                 }
2253                 return false;
2254             }
2255 
2256             QPluginLoader* loader = nullptr;
2257             if (!typesRegistered) {
2258                 loader = new QPluginLoader(absoluteFilePath);
2259 
2260                 if (!loader->load()) {
2261                     if (errors) {
2262                         QQmlError error;
2263                         error.setDescription(loader->errorString());
2264                         errors->prepend(error);
2265                     }
2266                     delete loader;
2267                     return false;
2268                 }
2269             } else {
2270                 loader = plugins->value(absoluteFilePath).loader;
2271             }
2272 
2273             instance = loader->instance();
2274 
2275             if (!typesRegistered) {
2276                 RegisteredPlugin plugin;
2277                 plugin.uri = uri;
2278                 plugin.loader = loader;
2279                 plugins->insert(absoluteFilePath, plugin);
2280 
2281                 // Continue with shared code path for dynamic and static plugins:
2282                 if (!registerPluginTypes(instance, fileInfo.absolutePath(), uri, typeNamespace, vmaj, errors))
2283                     return false;
2284             }
2285         }
2286 
2287     // Release the lock on plugins early as we're done with the global part. Releasing the lock
2288     // also allows other QML loader threads to acquire the lock while this thread is blocking
2289     // in the initializeEngine call to the gui thread (which in turn may be busy waiting for
2290     // other QML loader threads and thus not process the initializeEngine call).
2291     }
2292 
2293     if (!engineInitialized)
2294         finalizePlugin(instance, absoluteFilePath, uri);
2295 
2296     return true;
2297 }
2298 
removeDynamicPlugin(const QString & filePath)2299 bool QQmlImportDatabase::removeDynamicPlugin(const QString &filePath)
2300 {
2301     StringRegisteredPluginMap *plugins = qmlEnginePluginsWithRegisteredTypes();
2302     QMutexLocker lock(&plugins->mutex);
2303 
2304     auto it = plugins->find(QFileInfo(filePath).absoluteFilePath());
2305     if (it == plugins->end())
2306         return false;
2307 
2308     QPluginLoader *loader = it->loader;
2309     if (!loader)
2310         return false;
2311 
2312     if (!loader->unload()) {
2313         qWarning("Unloading %s failed: %s", qPrintable(it->uri),
2314                  qPrintable(loader->errorString()));
2315     }
2316 
2317     delete loader;
2318     plugins->erase(it);
2319     return true;
2320 }
2321 
dynamicPlugins() const2322 QStringList QQmlImportDatabase::dynamicPlugins() const
2323 {
2324     StringRegisteredPluginMap *plugins = qmlEnginePluginsWithRegisteredTypes();
2325     QMutexLocker lock(&plugins->mutex);
2326     QStringList results;
2327     for (auto it = plugins->constBegin(), end = plugins->constEnd(); it != end; ++it) {
2328         if (it->loader != nullptr)
2329             results.append(it.key());
2330     }
2331     return results;
2332 }
2333 #endif // QT_CONFIG(library)
2334 
clearDirCache()2335 void QQmlImportDatabase::clearDirCache()
2336 {
2337     QStringHash<QmldirCache *>::ConstIterator itr = qmldirCache.constBegin();
2338     while (itr != qmldirCache.constEnd()) {
2339         QmldirCache *cache = *itr;
2340         do {
2341             QmldirCache *nextCache = cache->next;
2342             delete cache;
2343             cache = nextCache;
2344         } while (cache);
2345 
2346         ++itr;
2347     }
2348     qmldirCache.clear();
2349 }
2350 
finalizePlugin(QObject * instance,const QString & path,const QString & uri)2351 void QQmlImportDatabase::finalizePlugin(QObject *instance, const QString &path, const QString &uri)
2352 {
2353     // The plugin's per-engine initialization does not need lock protection, as this function is
2354     // only called from the engine specific loader thread and importDynamicPlugin as well as
2355     // importStaticPlugin are the only places of access.
2356 
2357     initializedPlugins.insert(path);
2358     if (auto *extensionIface = qobject_cast<QQmlExtensionInterface *>(instance)) {
2359         QQmlEnginePrivate::get(engine)->typeLoader.initializeEngine(
2360                     extensionIface, uri.toUtf8().constData());
2361     } else if (auto *engineIface = qobject_cast<QQmlEngineExtensionInterface *>(instance)) {
2362         QQmlEnginePrivate::get(engine)->typeLoader.initializeEngine(
2363                     engineIface, uri.toUtf8().constData());
2364     }
2365 }
2366 
2367 QT_END_NAMESPACE
2368