aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/qml/compiler/qv4compileddata.cpp19
-rw-r--r--src/qml/compiler/qv4compileddata_p.h3
-rw-r--r--src/qml/qml/qqmltypeloader.cpp98
-rw-r--r--src/qml/qml/qqmltypeloader_p.h10
-rw-r--r--tests/auto/qml/qmldiskcache/tst_qmldiskcache.cpp122
5 files changed, 207 insertions, 45 deletions
diff --git a/src/qml/compiler/qv4compileddata.cpp b/src/qml/compiler/qv4compileddata.cpp
index 5948992f67..a4fffedbcd 100644
--- a/src/qml/compiler/qv4compileddata.cpp
+++ b/src/qml/compiler/qv4compileddata.cpp
@@ -301,6 +301,25 @@ void CompilationUnit::finalize(QQmlEnginePrivate *engine)
totalObjectCount = objectCount;
}
+bool CompilationUnit::verifyChecksum(QQmlEngine *engine,
+ const ResolvedTypeReferenceMap &dependentTypes) const
+{
+ if (dependentTypes.isEmpty()) {
+ for (size_t i = 0; i < sizeof(data->dependencyMD5Checksum); ++i) {
+ if (data->dependencyMD5Checksum[i] != 0)
+ return false;
+ }
+ return true;
+ }
+ QCryptographicHash hash(QCryptographicHash::Md5);
+ if (!dependentTypes.addToHash(&hash, engine))
+ return false;
+ QByteArray checksum = hash.result();
+ Q_ASSERT(checksum.size() == sizeof(data->dependencyMD5Checksum));
+ return memcmp(data->dependencyMD5Checksum, checksum.constData(),
+ sizeof(data->dependencyMD5Checksum)) == 0;
+}
+
bool CompilationUnit::saveToDisk(QString *errorString)
{
errorString->clear();
diff --git a/src/qml/compiler/qv4compileddata_p.h b/src/qml/compiler/qv4compileddata_p.h
index 1f253e02fd..c79f4cf007 100644
--- a/src/qml/compiler/qv4compileddata_p.h
+++ b/src/qml/compiler/qv4compileddata_p.h
@@ -865,6 +865,9 @@ struct Q_QML_PRIVATE_EXPORT CompilationUnit : public QQmlRefCount
QVector<QQmlScriptData *> dependentScripts;
ResolvedTypeReferenceMap resolvedTypes;
+ bool verifyChecksum(QQmlEngine *engine,
+ const ResolvedTypeReferenceMap &dependentTypes) const;
+
int metaTypeId;
int listMetaTypeId;
bool isRegisteredWithEngine;
diff --git a/src/qml/qml/qqmltypeloader.cpp b/src/qml/qml/qqmltypeloader.cpp
index 5bc9c8ac25..538e630e98 100644
--- a/src/qml/qml/qqmltypeloader.cpp
+++ b/src/qml/qml/qqmltypeloader.cpp
@@ -2136,21 +2136,16 @@ bool QQmlTypeData::tryLoadFromDiskCache()
return true;
}
-void QQmlTypeData::rebuildTypeAndPropertyCaches()
+void QQmlTypeData::createTypeAndPropertyCaches(const QQmlRefPointer<QQmlTypeNameCache> &importCache,
+ const QV4::CompiledData::ResolvedTypeReferenceMap &resolvedTypeCache)
{
Q_ASSERT(m_compiledData);
+ m_compiledData->importCache = importCache;
+ m_compiledData->resolvedTypes = resolvedTypeCache;
QQmlEnginePrivate * const engine = QQmlEnginePrivate::get(typeLoader()->engine());
{
- QQmlCompileError error = buildTypeResolutionCaches(&m_compiledData->importCache, &m_compiledData->resolvedTypes);
- if (error.isSet()) {
- setError(error);
- return;
- }
- }
-
- {
QQmlPropertyCacheCreator<QV4::CompiledData::CompilationUnit> propertyCacheCreator(&m_compiledData->propertyCaches, engine, m_compiledData, &m_importCache);
QQmlCompileError error = propertyCacheCreator.buildMetaObjects();
if (error.isSet()) {
@@ -2231,21 +2226,41 @@ void QQmlTypeData::done()
}
}
+ QQmlRefPointer<QQmlTypeNameCache> importCache;
+ QV4::CompiledData::ResolvedTypeReferenceMap resolvedTypeCache;
+ {
+ QQmlCompileError error = buildTypeResolutionCaches(&importCache, &resolvedTypeCache);
+ if (error.isSet()) {
+ setError(error);
+ return;
+ }
+ }
+
+ QQmlEngine *const engine = typeLoader()->engine();
+
+ // verify if any dependencies changed if we're using a cache
+ if (m_document.isNull() && !m_compiledData->verifyChecksum(engine, resolvedTypeCache)) {
+ if (!loadFromSource())
+ return;
+ m_backupSourceCode.clear();
+ m_compiledData = nullptr;
+ }
+
if (!m_document.isNull()) {
// Compile component
- compile();
+ compile(importCache, resolvedTypeCache);
} else {
- rebuildTypeAndPropertyCaches();
+ createTypeAndPropertyCaches(importCache, resolvedTypeCache);
}
if (isError())
return;
{
- QQmlEnginePrivate * const engine = QQmlEnginePrivate::get(typeLoader()->engine());
+ QQmlEnginePrivate *const enginePrivate = QQmlEnginePrivate::get(engine);
{
// Sanity check property bindings
- QQmlPropertyValidator validator(engine, m_importCache, m_compiledData);
+ QQmlPropertyValidator validator(enginePrivate, m_importCache, m_compiledData);
QVector<QQmlCompileError> errors = validator.validate();
if (!errors.isEmpty()) {
setError(errors);
@@ -2253,7 +2268,7 @@ void QQmlTypeData::done()
}
}
- m_compiledData->finalize(engine);
+ m_compiledData->finalize(enginePrivate);
}
{
@@ -2336,19 +2351,43 @@ bool QQmlTypeData::loadImplicitImport()
void QQmlTypeData::dataReceived(const Data &data)
{
+ QString error;
+ m_backupSourceCode = data.readAll(&error, &m_sourceTimeStamp);
+ // if we failed to read the source code, process it _after_ we've tried
+ // to use the disk cache, in order to support scenarios where the source
+ // was removed deliberately.
+
if (tryLoadFromDiskCache())
return;
- qint64 sourceTimeStamp;
- QString error;
- QString code = QString::fromUtf8(data.readAll(&error, &sourceTimeStamp));
+ if (isError())
+ return;
+
if (!error.isEmpty()) {
setError(error);
return;
}
+
+ if (!loadFromSource())
+ return;
+
+ continueLoadFromIR();
+}
+
+void QQmlTypeData::initializeFromCachedUnit(const QQmlPrivate::CachedQmlUnit *unit)
+{
QQmlEngine *qmlEngine = typeLoader()->engine();
m_document.reset(new QmlIR::Document(QV8Engine::getV4(qmlEngine)->debugger() != 0));
- m_document->jsModule.sourceTimeStamp = sourceTimeStamp;
+ unit->loadIR(m_document.data(), unit);
+ continueLoadFromIR();
+}
+
+bool QQmlTypeData::loadFromSource()
+{
+ QString code = QString::fromUtf8(m_backupSourceCode);
+ QQmlEngine *qmlEngine = typeLoader()->engine();
+ m_document.reset(new QmlIR::Document(QV8Engine::getV4(qmlEngine)->debugger() != 0));
+ m_document->jsModule.sourceTimeStamp = m_sourceTimeStamp;
QmlIR::IRBuilder compiler(QV8Engine::get(qmlEngine)->illegalNames());
if (!compiler.generateFromQml(code, finalUrlString(), m_document.data())) {
QList<QQmlError> errors;
@@ -2362,18 +2401,9 @@ void QQmlTypeData::dataReceived(const Data &data)
errors << e;
}
setError(errors);
- return;
+ return false;
}
-
- continueLoadFromIR();
-}
-
-void QQmlTypeData::initializeFromCachedUnit(const QQmlPrivate::CachedQmlUnit *unit)
-{
- QQmlEngine *qmlEngine = typeLoader()->engine();
- m_document.reset(new QmlIR::Document(QV8Engine::getV4(qmlEngine)->debugger() != 0));
- unit->loadIR(m_document.data(), unit);
- continueLoadFromIR();
+ return true;
}
void QQmlTypeData::continueLoadFromIR()
@@ -2468,18 +2498,10 @@ QString QQmlTypeData::stringAt(int index) const
return m_document->jsGenerator.stringTable.stringForIndex(index);
}
-void QQmlTypeData::compile()
+void QQmlTypeData::compile(const QQmlRefPointer<QQmlTypeNameCache> &importCache, const QV4::CompiledData::ResolvedTypeReferenceMap &resolvedTypeCache)
{
Q_ASSERT(m_compiledData.isNull());
- QQmlRefPointer<QQmlTypeNameCache> importCache;
- QV4::CompiledData::ResolvedTypeReferenceMap resolvedTypeCache;
- QQmlCompileError error = buildTypeResolutionCaches(&importCache, &resolvedTypeCache);
- if (error.isSet()) {
- setError(error);
- return;
- }
-
QQmlTypeCompiler compiler(QQmlEnginePrivate::get(typeLoader()->engine()), this, m_document.data(), importCache, resolvedTypeCache);
m_compiledData = compiler.compile();
if (!m_compiledData) {
diff --git a/src/qml/qml/qqmltypeloader_p.h b/src/qml/qml/qqmltypeloader_p.h
index 5f754df1fc..cfbaa2e92b 100644
--- a/src/qml/qml/qqmltypeloader_p.h
+++ b/src/qml/qml/qqmltypeloader_p.h
@@ -442,18 +442,24 @@ protected:
private:
bool tryLoadFromDiskCache();
+ bool loadFromSource();
void continueLoadFromIR();
void resolveTypes();
QQmlCompileError buildTypeResolutionCaches(
QQmlRefPointer<QQmlTypeNameCache> *importCache,
QV4::CompiledData::ResolvedTypeReferenceMap *resolvedTypeCache
) const;
- void compile();
- void rebuildTypeAndPropertyCaches();
+ void compile(const QQmlRefPointer<QQmlTypeNameCache> &importCache,
+ const QV4::CompiledData::ResolvedTypeReferenceMap &resolvedTypeCache);
+ void createTypeAndPropertyCaches(const QQmlRefPointer<QQmlTypeNameCache> &importCache,
+ const QV4::CompiledData::ResolvedTypeReferenceMap &resolvedTypeCache);
bool resolveType(const QString &typeName, int &majorVersion, int &minorVersion, TypeReference &ref);
virtual void scriptImported(QQmlScriptBlob *blob, const QV4::CompiledData::Location &location, const QString &qualifier, const QString &nameSpace);
+
+ qint64 m_sourceTimeStamp = 0;
+ QByteArray m_backupSourceCode; // used when cache verification fails.
QScopedPointer<QmlIR::Document> m_document;
QV4::CompiledData::TypeReferenceMap m_typeReferences;
diff --git a/tests/auto/qml/qmldiskcache/tst_qmldiskcache.cpp b/tests/auto/qml/qmldiskcache/tst_qmldiskcache.cpp
index 1fe63bb99a..a4e10205bd 100644
--- a/tests/auto/qml/qmldiskcache/tst_qmldiskcache.cpp
+++ b/tests/auto/qml/qmldiskcache/tst_qmldiskcache.cpp
@@ -48,8 +48,40 @@ private slots:
void regenerateAfterChange();
void registerImportForImplicitComponent();
void basicVersionChecks();
+ void recompileAfterChange();
};
+// A wrapper around QQmlComponent to ensure the temporary reference counts
+// on the type data as a result of the main thread <> loader thread communication
+// are dropped. Regular Synchronous loading will leave us with an event posted
+// to the gui thread and an extra refcount that will only be dropped after the
+// event delivery. A plain sendPostedEvents() however is insufficient because
+// we can't be sure that the event is posted after the constructor finished.
+class CleanlyLoadingComponent : public QQmlComponent
+{
+public:
+ CleanlyLoadingComponent(QQmlEngine *engine, const QUrl &url)
+ : QQmlComponent(engine, url, QQmlComponent::Asynchronous)
+ { waitForLoad(); }
+ CleanlyLoadingComponent(QQmlEngine *engine, const QString &fileName)
+ : QQmlComponent(engine, fileName, QQmlComponent::Asynchronous)
+ { waitForLoad(); }
+
+ void waitForLoad()
+ {
+ QTRY_VERIFY(status() == QQmlComponent::Ready || status() == QQmlComponent::Error);
+ }
+};
+
+static void waitForFileSystem()
+{
+ // On macOS with HFS+ the precision of file times is measured in seconds, so to ensure that
+ // the newly written file has a modification date newer than an existing cache file, we must
+ // wait.
+ // Similar effects of lacking precision have been observed on some Linux systems.
+ QThread::sleep(1);
+}
+
struct TestCompiler
{
TestCompiler(QQmlEngine *engine)
@@ -67,10 +99,8 @@ struct TestCompiler
closeMapping();
engine->clearComponentCache();
- // Qt API limits the precision of QFileInfo::modificationTime() to seconds, so to ensure that
- // the newly written file has a modification date newer than an existing cache file, we must
- // wait.
- QThread::sleep(1);
+ waitForFileSystem();
+
{
QFile f(testFilePath);
if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
@@ -83,7 +113,7 @@ struct TestCompiler
}
}
- QQmlComponent component(engine, testFilePath);
+ CleanlyLoadingComponent component(engine, testFilePath);
if (!component.isReady()) {
lastErrorString = component.errorString();
return false;
@@ -321,6 +351,88 @@ void tst_qmldiskcache::basicVersionChecks()
}
}
+class TypeVersion1 : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(int value READ value WRITE setValue NOTIFY valueChanged);
+public:
+
+
+ int m_value = 0;
+ int value() const { return m_value; }
+ void setValue(int v) { m_value = v; emit valueChanged(); }
+
+signals:
+ void valueChanged();
+};
+
+// Same as TypeVersion1 except the property type changed!
+class TypeVersion2 : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QString value READ value WRITE setValue NOTIFY valueChanged);
+public:
+
+
+ QString m_value;
+ QString value() const { return m_value; }
+ void setValue(QString v) { m_value = v; emit valueChanged(); }
+
+signals:
+ void valueChanged();
+};
+
+void tst_qmldiskcache::recompileAfterChange()
+{
+ QQmlEngine engine;
+
+ TestCompiler testCompiler(&engine);
+ QVERIFY(testCompiler.tempDir.isValid());
+
+ const QByteArray contents = QByteArrayLiteral("import TypeTest 1.0\n"
+ "TypeThatWillChange {\n"
+ "}");
+
+ qmlRegisterType<TypeVersion1>("TypeTest", 1, 0, "TypeThatWillChange");
+
+ {
+ testCompiler.clearCache();
+ QVERIFY2(testCompiler.compile(contents), qPrintable(testCompiler.lastErrorString));
+ QVERIFY2(testCompiler.verify(), qPrintable(testCompiler.lastErrorString));
+ }
+
+ QDateTime initialCacheTimeStamp = QFileInfo(testCompiler.cacheFilePath).lastModified();
+
+ {
+ CleanlyLoadingComponent component(&engine, testCompiler.testFilePath);
+ QScopedPointer<TypeVersion1> obj(qobject_cast<TypeVersion1*>(component.create()));
+ QVERIFY(!obj.isNull());
+ QCOMPARE(QFileInfo(testCompiler.cacheFilePath).lastModified(), initialCacheTimeStamp);
+ }
+
+ engine.clearComponentCache();
+
+ {
+ CleanlyLoadingComponent component(&engine, testCompiler.testFilePath);
+ QScopedPointer<TypeVersion1> obj(qobject_cast<TypeVersion1*>(component.create()));
+ QVERIFY(!obj.isNull());
+ QCOMPARE(QFileInfo(testCompiler.cacheFilePath).lastModified(), initialCacheTimeStamp);
+ }
+
+ engine.clearComponentCache();
+ qmlClearTypeRegistrations();
+ qmlRegisterType<TypeVersion2>("TypeTest", 1, 0, "TypeThatWillChange");
+
+ waitForFileSystem();
+
+ {
+ CleanlyLoadingComponent component(&engine, testCompiler.testFilePath);
+ QScopedPointer<TypeVersion2> obj(qobject_cast<TypeVersion2*>(component.create()));
+ QVERIFY(!obj.isNull());
+ QVERIFY(QFileInfo(testCompiler.cacheFilePath).lastModified() > initialCacheTimeStamp);
+ }
+}
+
QTEST_MAIN(tst_qmldiskcache)
#include "tst_qmldiskcache.moc"