Blob Blame History Raw
From 98358715930739ca8de172d88c5ce6941c275ff3 Mon Sep 17 00:00:00 2001
From: Simon Hausmann <simon.hausmann@qt.io>
Date: Tue, 12 Sep 2017 15:13:33 +0200
Subject: [PATCH 111/153] Fix qml cache invalidation when changing dependent
 C++ registered QML singletons

When a qml file uses a qml singleton, we need to reliably detect when
the singleton changes and re-generate the cache of the qml file using
it. This is a scenario covered and fixed by commit
5b94de09cc738837d1539e28b3c0dccd17c18d29, with the exception that
currently QML singletons registered via qmlRegisterSingleton were not
added to the list of dependent singletons for a qml file. We can fix
this by extending findCompositeSingletons() to also cover the singletons
that do not originate from a qmldir file.

[ChangeLog][Qt][Qml] Fixed bug where sometimes changes to a qml
singleton would not propagate to the users or cause crashes.

Task-number: QTBUG-62243
Change-Id: I16c3d9ba65fd82e898a29b946c341907751135a9
Reviewed-by: Lars Knoll <lars.knoll@qt.io>
---
 src/qml/qml/qqmlimport.cpp                       | 11 +++++
 src/qml/qml/qqmlmetatype.cpp                     | 12 +++++
 src/qml/qml/qqmlmetatype_p.h                     |  2 +
 tests/auto/qml/qmldiskcache/tst_qmldiskcache.cpp | 61 ++++++++++++++++++++++++
 4 files changed, 86 insertions(+)

diff --git a/src/qml/qml/qqmlimport.cpp b/src/qml/qml/qqmlimport.cpp
index 0bd731747..ccd287e1b 100644
--- a/src/qml/qml/qqmlimport.cpp
+++ b/src/qml/qml/qqmlimport.cpp
@@ -471,6 +471,17 @@ void findCompositeSingletons(const QQmlImportNamespace &set, QList<QQmlImports::
                 resultList.append(ref);
             }
         }
+
+        if (QQmlTypeModule *module = QQmlMetaType::typeModule(import->uri, import->majversion)) {
+            module->walkCompositeSingletons([&resultList, &set](const QQmlType &singleton) {
+                QQmlImports::CompositeSingletonReference ref;
+                ref.typeName = singleton.elementName();
+                ref.prefix = set.prefix;
+                ref.majorVersion = singleton.majorVersion();
+                ref.minorVersion = singleton.minorVersion();
+                resultList.append(ref);
+            });
+        }
     }
 }
 
diff --git a/src/qml/qml/qqmlmetatype.cpp b/src/qml/qml/qqmlmetatype.cpp
index 5bbc250d5..8f5d11a96 100644
--- a/src/qml/qml/qqmlmetatype.cpp
+++ b/src/qml/qml/qqmlmetatype.cpp
@@ -1258,6 +1258,18 @@ QQmlType QQmlTypeModule::type(const QV4::String *name, int minor) const
     return QQmlType();
 }
 
+void QQmlTypeModule::walkCompositeSingletons(const std::function<void(const QQmlType &)> &callback) const
+{
+    QMutexLocker lock(metaTypeDataLock());
+    for (auto typeCandidates = d->typeHash.begin(), end = d->typeHash.end();
+         typeCandidates != end; ++typeCandidates) {
+        for (auto type: typeCandidates.value()) {
+            if (type->regType == QQmlType::CompositeSingletonType)
+                callback(QQmlType(type));
+        }
+    }
+}
+
 QQmlTypeModuleVersion::QQmlTypeModuleVersion()
 : m_module(0), m_minor(0)
 {
diff --git a/src/qml/qml/qqmlmetatype_p.h b/src/qml/qml/qqmlmetatype_p.h
index ac2133ba3..9a7736ffc 100644
--- a/src/qml/qml/qqmlmetatype_p.h
+++ b/src/qml/qml/qqmlmetatype_p.h
@@ -298,6 +298,8 @@ public:
     QQmlType type(const QHashedStringRef &, int) const;
     QQmlType type(const QV4::String *, int) const;
 
+    void walkCompositeSingletons(const std::function<void(const QQmlType &)> &callback) const;
+
     QQmlTypeModulePrivate *priv() { return d; }
 private:
     //Used by register functions and creates the QQmlTypeModule for them
diff --git a/tests/auto/qml/qmldiskcache/tst_qmldiskcache.cpp b/tests/auto/qml/qmldiskcache/tst_qmldiskcache.cpp
index 6ab84774f..e75e51ed2 100644
--- a/tests/auto/qml/qmldiskcache/tst_qmldiskcache.cpp
+++ b/tests/auto/qml/qmldiskcache/tst_qmldiskcache.cpp
@@ -59,6 +59,7 @@ private slots:
     void cacheResources();
     void stableOrderOfDependentCompositeTypes();
     void singletonDependency();
+    void cppRegisteredSingletonDependency();
 };
 
 // A wrapper around QQmlComponent to ensure the temporary reference counts
@@ -790,6 +791,66 @@ void tst_qmldiskcache::singletonDependency()
     }
 }
 
+void tst_qmldiskcache::cppRegisteredSingletonDependency()
+{
+    qmlClearTypeRegistrations();
+    QScopedPointer<QQmlEngine> engine(new QQmlEngine);
+
+    QTemporaryDir tempDir;
+    QVERIFY(tempDir.isValid());
+
+    const auto writeTempFile = [&tempDir](const QString &fileName, const char *contents) {
+        QFile f(tempDir.path() + '/' + fileName);
+        const bool ok = f.open(QIODevice::WriteOnly | QIODevice::Truncate);
+        Q_ASSERT(ok);
+        f.write(contents);
+        return f.fileName();
+    };
+
+    writeTempFile("MySingleton.qml", "import QtQml 2.0\npragma Singleton\nQtObject { property int value: 42 }");
+
+    qmlRegisterSingletonType(QUrl::fromLocalFile(tempDir.path() + QLatin1String("/MySingleton.qml")), "CppRegisteredSingletonDependency", 1, 0, "Singly");
+
+    const QString testFilePath = writeTempFile("main.qml", "import QtQml 2.0\nimport CppRegisteredSingletonDependency 1.0\nQtObject {\n"
+                                                           "    function getValue() { return Singly.value; }\n"
+                                                           "}");
+
+    {
+        CleanlyLoadingComponent component(engine.data(), QUrl::fromLocalFile(testFilePath));
+        QScopedPointer<QObject> obj(component.create());
+        QVERIFY(!obj.isNull());
+        QVariant value;
+        QVERIFY(QMetaObject::invokeMethod(obj.data(), "getValue", Q_RETURN_ARG(QVariant, value)));
+        QCOMPARE(value.toInt(), 42);
+    }
+
+    const QString testFileCachePath = testFilePath + QLatin1Char('c');
+    QVERIFY(QFile::exists(testFileCachePath));
+    QDateTime initialCacheTimeStamp = QFileInfo(testFileCachePath).lastModified();
+
+    engine.reset(new QQmlEngine);
+    waitForFileSystem();
+
+    writeTempFile("MySingleton.qml", "import QtQml 2.0\npragma Singleton\nQtObject { property int value: 100 }");
+    waitForFileSystem();
+
+    {
+        CleanlyLoadingComponent component(engine.data(), QUrl::fromLocalFile(testFilePath));
+        QScopedPointer<QObject> obj(component.create());
+        QVERIFY(!obj.isNull());
+
+        {
+            QVERIFY(QFile::exists(testFileCachePath));
+            QDateTime newCacheTimeStamp = QFileInfo(testFileCachePath).lastModified();
+            QVERIFY2(newCacheTimeStamp > initialCacheTimeStamp, qPrintable(newCacheTimeStamp.toString()));
+        }
+
+        QVariant value;
+        QVERIFY(QMetaObject::invokeMethod(obj.data(), "getValue", Q_RETURN_ARG(QVariant, value)));
+        QCOMPARE(value.toInt(), 100);
+    }
+}
+
 QTEST_MAIN(tst_qmldiskcache)
 
 #include "tst_qmldiskcache.moc"
-- 
2.14.3