diff options
-rw-r--r-- | src/qml/compiler/qv4compileddata.cpp | 19 | ||||
-rw-r--r-- | src/qml/compiler/qv4compileddata_p.h | 3 | ||||
-rw-r--r-- | src/qml/qml/qqmltypeloader.cpp | 98 | ||||
-rw-r--r-- | src/qml/qml/qqmltypeloader_p.h | 10 | ||||
-rw-r--r-- | tests/auto/qml/qmldiskcache/tst_qmldiskcache.cpp | 122 |
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" |