diff options
-rw-r--r-- | src/qml/compiler/qqmlirbuilder.cpp | 2 | ||||
-rw-r--r-- | src/qml/compiler/qqmltypecompiler_p.h | 3 | ||||
-rw-r--r-- | src/qml/compiler/qv4compileddata.cpp | 30 | ||||
-rw-r--r-- | src/qml/compiler/qv4compileddata_p.h | 8 | ||||
-rw-r--r-- | src/qml/compiler/qv4compiler.cpp | 6 | ||||
-rw-r--r-- | src/qml/qml/qqmlpropertycache.cpp | 6 | ||||
-rw-r--r-- | src/qml/qml/qqmltypeloader.cpp | 1 | ||||
-rw-r--r-- | tests/auto/qml/qmldiskcache/tst_qmldiskcache.cpp | 100 |
8 files changed, 149 insertions, 7 deletions
diff --git a/src/qml/compiler/qqmlirbuilder.cpp b/src/qml/compiler/qqmlirbuilder.cpp index 31b964897f..eb83962630 100644 --- a/src/qml/compiler/qqmlirbuilder.cpp +++ b/src/qml/compiler/qqmlirbuilder.cpp @@ -1531,6 +1531,8 @@ QV4::CompiledData::Unit *QmlUnitGenerator::generate(Document &output, QQmlEngine output.jsGenerator.stringTable.serialize(qmlUnit); + qmlUnit->generateChecksum(); + return qmlUnit; } diff --git a/src/qml/compiler/qqmltypecompiler_p.h b/src/qml/compiler/qqmltypecompiler_p.h index 6ad6ad8557..de6abb4ced 100644 --- a/src/qml/compiler/qqmltypecompiler_p.h +++ b/src/qml/compiler/qqmltypecompiler_p.h @@ -285,7 +285,8 @@ protected: // indices of the objects that are actually Component {} QVector<quint32> componentRoots; - QHash<int, int> _idToObjectIndex; + // Deliberate choice of map over hash here to ensure stable generated output. + QMap<int, int> _idToObjectIndex; QVector<int> _objectsWithAliases; QV4::CompiledData::ResolvedTypeReferenceMap *resolvedTypes; diff --git a/src/qml/compiler/qv4compileddata.cpp b/src/qml/compiler/qv4compileddata.cpp index 35f61b4f69..0ab09f66ec 100644 --- a/src/qml/compiler/qv4compileddata.cpp +++ b/src/qml/compiler/qv4compileddata.cpp @@ -571,6 +571,17 @@ QQmlPropertyCache *ResolvedTypeReference::createPropertyCache(QQmlEngine *engine } } +bool ResolvedTypeReference::addToHash(QCryptographicHash *hash, QQmlEngine *engine) +{ + if (type) { + bool ok = false; + hash->addData(createPropertyCache(engine)->checksum(&ok)); + return ok; + } + hash->addData(compilationUnit->data->md5Checksum, sizeof(compilationUnit->data->md5Checksum)); + return true; +} + template <typename T> bool qtTypeInherits(const QMetaObject *mo) { while (mo) { @@ -596,15 +607,26 @@ void ResolvedTypeReference::doDynamicTypeCheck() bool ResolvedTypeReferenceMap::addToHash(QCryptographicHash *hash, QQmlEngine *engine) const { for (auto it = constBegin(), end = constEnd(); it != end; ++it) { - QQmlPropertyCache *pc = it.value()->createPropertyCache(engine); - bool ok = false; - hash->addData(pc->checksum(&ok)); - if (!ok) + if (!it.value()->addToHash(hash, engine)) return false; } return true; } +void Unit::generateChecksum() +{ + QCryptographicHash hash(QCryptographicHash::Md5); + + const int checksummableDataOffset = qOffsetOf(QV4::CompiledData::Unit, md5Checksum) + sizeof(md5Checksum); + + const char *dataPtr = reinterpret_cast<const char *>(this) + checksummableDataOffset; + hash.addData(dataPtr, unitSize - checksummableDataOffset); + + QByteArray checksum = hash.result(); + Q_ASSERT(checksum.size() == sizeof(md5Checksum)); + memcpy(md5Checksum, checksum.constData(), sizeof(md5Checksum)); +} + #endif } diff --git a/src/qml/compiler/qv4compileddata_p.h b/src/qml/compiler/qv4compileddata_p.h index fe18024070..8f68131ced 100644 --- a/src/qml/compiler/qv4compileddata_p.h +++ b/src/qml/compiler/qv4compileddata_p.h @@ -71,7 +71,7 @@ QT_BEGIN_NAMESPACE // Bump this whenever the compiler data structures change in an incompatible way. -#define QV4_DATA_STRUCTURE_VERSION 0x05 +#define QV4_DATA_STRUCTURE_VERSION 0x06 class QIODevice; class QQmlPropertyCache; @@ -612,6 +612,9 @@ struct Unit LEUInt32 unitSize; // Size of the Unit and any depending data. // END DO NOT CHANGE THESE FIELDS EVER + char md5Checksum[16]; // checksum of all bytes following this field. + void generateChecksum(); + LEUInt32 architectureIndex; // string index to QSysInfo::buildAbi() LEUInt32 codeGeneratorIndex; char dependencyMD5Checksum[16]; @@ -727,7 +730,7 @@ struct TypeReference bool errorWhenNotFound: 1; }; -// map from name index to location of first use +// Map from name index to location of first use. struct TypeReferenceMap : QHash<int, TypeReference> { TypeReference &add(int nameIndex, const Location &loc) { @@ -791,6 +794,7 @@ struct ResolvedTypeReference QQmlPropertyCache *propertyCache() const; QQmlPropertyCache *createPropertyCache(QQmlEngine *); + bool addToHash(QCryptographicHash *hash, QQmlEngine *engine); void doDynamicTypeCheck(); }; diff --git a/src/qml/compiler/qv4compiler.cpp b/src/qml/compiler/qv4compiler.cpp index 43347d246a..e1ea3a9b88 100644 --- a/src/qml/compiler/qv4compiler.cpp +++ b/src/qml/compiler/qv4compiler.cpp @@ -44,6 +44,7 @@ #include <private/qv4value_p.h> #include <private/qv4alloca_p.h> #include <wtf/MathExtras.h> +#include <QCryptographicHash> QV4::Compiler::StringTableGenerator::StringTableGenerator() { @@ -227,6 +228,7 @@ QV4::CompiledData::Unit *QV4::Compiler::JSUnitGenerator::generateUnit(GeneratorO { QV4::CompiledData::Unit tempHeader = generateHeader(option, functionOffsets, &jsClassDataOffset); dataPtr = reinterpret_cast<char *>(malloc(tempHeader.unitSize)); + memset(dataPtr, 0, tempHeader.unitSize); memcpy(&unit, &dataPtr, sizeof(CompiledData::Unit*)); memcpy(unit, &tempHeader, sizeof(tempHeader)); } @@ -270,6 +272,8 @@ QV4::CompiledData::Unit *QV4::Compiler::JSUnitGenerator::generateUnit(GeneratorO if (option == GenerateWithStringTable) stringTable.serialize(unit); + unit->generateChecksum(); + return unit; } @@ -363,11 +367,13 @@ void QV4::Compiler::JSUnitGenerator::writeFunction(char *f, QV4::IR::Function *i QV4::CompiledData::Unit QV4::Compiler::JSUnitGenerator::generateHeader(QV4::Compiler::JSUnitGenerator::GeneratorOption option, QJsonPrivate::q_littleendian<quint32> *functionOffsets, uint *jsClassDataOffset) { CompiledData::Unit unit; + memset(&unit, 0, sizeof(unit)); memcpy(unit.magic, CompiledData::magic_str, sizeof(unit.magic)); unit.flags = QV4::CompiledData::Unit::IsJavascript; unit.flags |= irModule->unitFlags; unit.version = QV4_DATA_STRUCTURE_VERSION; unit.qtVersion = QT_VERSION; + memset(unit.md5Checksum, 0, sizeof(unit.md5Checksum)); unit.architectureIndex = registerString(QSysInfo::buildAbi()); unit.codeGeneratorIndex = registerString(codeGeneratorName); memset(unit.dependencyMD5Checksum, 0, sizeof(unit.dependencyMD5Checksum)); diff --git a/src/qml/qml/qqmlpropertycache.cpp b/src/qml/qml/qqmlpropertycache.cpp index f75ab9e25b..2610a807b5 100644 --- a/src/qml/qml/qqmlpropertycache.cpp +++ b/src/qml/qml/qqmlpropertycache.cpp @@ -1429,6 +1429,12 @@ QByteArray QQmlPropertyCache::checksum(bool *ok) return _checksum; } + // Generate a checksum on the meta-object data only on C++ types. + if (!_metaObject || _ownMetaObject) { + *ok = false; + return _checksum; + } + QCryptographicHash hash(QCryptographicHash::Md5); if (_parent) { diff --git a/src/qml/qml/qqmltypeloader.cpp b/src/qml/qml/qqmltypeloader.cpp index 333ac430d3..09b9dcf452 100644 --- a/src/qml/qml/qqmltypeloader.cpp +++ b/src/qml/qml/qqmltypeloader.cpp @@ -2230,6 +2230,7 @@ void QQmlTypeData::done() // verify if any dependencies changed if we're using a cache if (m_document.isNull() && !m_compiledData->verifyChecksum(engine, resolvedTypeCache)) { + qCDebug(DBG_DISK_CACHE) << "Checksum mismatch for cached version of" << m_compiledData->url().toString(); if (!loadFromSource()) return; m_backupSourceCode.clear(); diff --git a/tests/auto/qml/qmldiskcache/tst_qmldiskcache.cpp b/tests/auto/qml/qmldiskcache/tst_qmldiskcache.cpp index 92b38c6ad8..e2c0055ea1 100644 --- a/tests/auto/qml/qmldiskcache/tst_qmldiskcache.cpp +++ b/tests/auto/qml/qmldiskcache/tst_qmldiskcache.cpp @@ -56,6 +56,7 @@ private slots: void fileSelectors(); void localAliases(); void cacheResources(); + void stableOrderOfDependentCompositeTypes(); }; // A wrapper around QQmlComponent to ensure the temporary reference counts @@ -566,6 +567,105 @@ void tst_qmldiskcache::cacheResources() } } +void tst_qmldiskcache::stableOrderOfDependentCompositeTypes() +{ + QQmlEngine engine; + + QTemporaryDir tempDir; + QVERIFY(tempDir.isValid()); + + { + const QString depFilePath = tempDir.path() + "/FirstDependentType.qml"; + QFile f(depFilePath); + QVERIFY2(f.open(QIODevice::WriteOnly), qPrintable(f.errorString())); + f.write(QByteArrayLiteral("import QtQml 2.0\nQtObject { property int value: 42 }")); + } + + { + const QString depFilePath = tempDir.path() + "/SecondDependentType.qml"; + QFile f(depFilePath); + QVERIFY2(f.open(QIODevice::WriteOnly), qPrintable(f.errorString())); + f.write(QByteArrayLiteral("import QtQml 2.0\nQtObject { property int value: 100 }")); + } + + const QString testFilePath = tempDir.path() + "/main.qml"; + { + QFile f(testFilePath); + QVERIFY2(f.open(QIODevice::WriteOnly), qPrintable(f.errorString())); + f.write(QByteArrayLiteral("import QtQml 2.0\nQtObject {\n" + " property QtObject dep1: FirstDependentType{}\n" + " property QtObject dep2: SecondDependentType{}\n" + " property int value: dep1.value + dep2.value\n" + "}")); + } + + QByteArray firstDependentTypeClassName; + QByteArray secondDependentTypeClassName; + + { + CleanlyLoadingComponent component(&engine, QUrl::fromLocalFile(testFilePath)); + QScopedPointer<QObject> obj(component.create()); + QVERIFY(!obj.isNull()); + QCOMPARE(obj->property("value").toInt(), 142); + + firstDependentTypeClassName = qvariant_cast<QObject *>(obj->property("dep1"))->metaObject()->className(); + secondDependentTypeClassName = qvariant_cast<QObject *>(obj->property("dep2"))->metaObject()->className(); + } + + QVERIFY(firstDependentTypeClassName != secondDependentTypeClassName); + QVERIFY2(firstDependentTypeClassName.contains("QMLTYPE"), firstDependentTypeClassName.constData()); + QVERIFY2(secondDependentTypeClassName.contains("QMLTYPE"), secondDependentTypeClassName.constData()); + + const QString testFileCachePath = testFilePath + QLatin1Char('c'); + QVERIFY(QFile::exists(testFileCachePath)); + QDateTime initialCacheTimeStamp = QFileInfo(testFileCachePath).lastModified(); + + engine.clearComponentCache(); + waitForFileSystem(); + + // Creating the test component a second time should load it from the cache (same time stamp), + // despite the class names of the dependent composite types differing. + { + CleanlyLoadingComponent component(&engine, QUrl::fromLocalFile(testFilePath)); + QScopedPointer<QObject> obj(component.create()); + QVERIFY(!obj.isNull()); + QCOMPARE(obj->property("value").toInt(), 142); + + QVERIFY(qvariant_cast<QObject *>(obj->property("dep1"))->metaObject()->className() != firstDependentTypeClassName); + QVERIFY(qvariant_cast<QObject *>(obj->property("dep2"))->metaObject()->className() != secondDependentTypeClassName); + } + + { + QVERIFY(QFile::exists(testFileCachePath)); + QDateTime newCacheTimeStamp = QFileInfo(testFileCachePath).lastModified(); + QCOMPARE(newCacheTimeStamp, initialCacheTimeStamp); + } + + // Now change the first dependent QML type and see if we correctly re-generate the + // caches. + engine.clearComponentCache(); + waitForFileSystem(); + { + const QString depFilePath = tempDir.path() + "/FirstDependentType.qml"; + QFile f(depFilePath); + QVERIFY2(f.open(QIODevice::WriteOnly), qPrintable(f.errorString())); + f.write(QByteArrayLiteral("import QtQml 2.0\nQtObject { property int value: 40 }")); + } + + { + CleanlyLoadingComponent component(&engine, QUrl::fromLocalFile(testFilePath)); + QScopedPointer<QObject> obj(component.create()); + QVERIFY(!obj.isNull()); + QCOMPARE(obj->property("value").toInt(), 140); + } + + { + QVERIFY(QFile::exists(testFileCachePath)); + QDateTime newCacheTimeStamp = QFileInfo(testFileCachePath).lastModified(); + QVERIFY2(newCacheTimeStamp > initialCacheTimeStamp, qPrintable(newCacheTimeStamp.toString())); + } +} + QTEST_MAIN(tst_qmldiskcache) #include "tst_qmldiskcache.moc" |