QtQml: Drop checks for compile hash and Qt version from the CUs

The data structure version is supposed to encode any incompatible
changes to our compilation unit format. Checking the compile hash and Qt
version in addition is redundant and excessively restrictive.

[ChangeLog][QtQml] You can now use QML code compiled with Qt Quick
Compiler across Qt versions as long as the compilation unit format
hasn't changed between those versions. You cannot rely on the
compilation unit format to stay unchanged under any specific
circumstances. However, we won't change it unnecessarily.

Change-Id: I8c407b505ac7fa952f53fa25bb6d4e7caf0fba0c
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
This commit is contained in:
Ulf Hermann 2025-03-10 08:34:45 +01:00
parent 0675e10429
commit 390c78b394
7 changed files with 5 additions and 118 deletions

1
.gitignore vendored
View File

@ -351,7 +351,6 @@ tests/auto/*/*/*/*.moc
src/qml/RegExpJitTables.h
src/qml/udis86_itab.c
src/qml/udis86_itab.h
src/qml/qml_compile_hash_p.h
# Generated HLSL bytecode headers
*.hlslh

View File

@ -1,59 +1,6 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
# Create a header containing a hash that describes this library. For a
# released version of Qt, we'll use the .tag file that is updated by git
# archive with the tree hash. For unreleased versions, we'll do
# "git show -s --format=format:%T" to get the tree hash. If none of this
# works, we use CMake to hash all the files in the src/qml/ directory.
# Skip recreation of the hash when doing a developer build.
function(qt_declarative_write_tag_header target_name)
set(out_file "${CMAKE_CURRENT_BINARY_DIR}/qml_compile_hash_p.h")
if(FEATURE_developer_build AND EXISTS "${out_file}")
target_sources(${target_name} PRIVATE "${out_file}")
set_source_files_properties("${out_file}" PROPERTIES GENERATED TRUE)
return()
endif()
set(tag_file "${CMAKE_CURRENT_SOURCE_DIR}/../../.tag")
set(tag_contents "")
if(EXISTS "${tag_file}")
file(READ "${tag_file}" tag_contents)
string(STRIP "${tag_contents}" tag_contents)
endif()
find_program(git_path git)
if(tag_contents AND NOT tag_contents STREQUAL "$Format:%T$")
set(QML_COMPILE_HASH "${tag_contents}")
elseif(git_path AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/../../.git")
execute_process(
COMMAND ${git_path} show -s --format=format:%T HEAD
OUTPUT_VARIABLE QML_COMPILE_HASH
OUTPUT_STRIP_TRAILING_WHITESPACE
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}")
endif()
string(LENGTH "${QML_COMPILE_HASH}" QML_COMPILE_HASH_LENGTH)
if(QML_COMPILE_HASH_LENGTH EQUAL 0)
set(sources_hash "")
file(GLOB_RECURSE qtqml_source_files "${CMAKE_CURRENT_SOURCE_DIR}/*")
foreach(file IN LISTS qtqml_source_files)
file(SHA1 ${file} file_hash)
string(APPEND sources_hash ${file_hash})
endforeach()
string(SHA1 QML_COMPILE_HASH "${sources_hash}")
endif()
string(LENGTH "${QML_COMPILE_HASH}" QML_COMPILE_HASH_LENGTH)
if(QML_COMPILE_HASH_LENGTH GREATER 0)
configure_file("qml_compile_hash_p.h.in" "${out_file}")
else()
message(FATAL_ERROR "QML compile hash is empty! "
"You need either a valid git repository or a non-empty .tag file.")
endif()
target_sources(${target_name} PRIVATE "${out_file}")
set_source_files_properties("${out_file}" PROPERTIES GENERATED TRUE)
endfunction()
# Generate a header file containing a regular expression jit table.
function(qt_declarative_generate_reg_exp_jit_tables consuming_target)
set(generate_dir "${CMAKE_CURRENT_BINARY_DIR}/.generated")

View File

@ -521,7 +521,6 @@ set_source_files_properties(compat/removed_api.cpp
# ""
#)
qt_declarative_write_tag_header(Qml)
set(_qt_qlalr_flags "--no-debug" "--qt")
qt_process_qlalr(Qml "${CMAKE_CURRENT_SOURCE_DIR}/parser/qqmljs.g" "${_qt_qlalr_flags}")
qt_declarative_generate_reg_exp_jit_tables(Qml)

View File

@ -4,7 +4,6 @@
#include "qv4compileddata_p.h"
#include <private/inlinecomponentutils_p.h>
#include <private/qml_compile_hash_p.h>
#include <private/qqmlscriptdata_p.h>
#include <private/qqmltypenamecache_p.h>
#include <private/qv4resolvedtypereference_p.h>
@ -16,21 +15,6 @@
#include <QtCore/qstandardpaths.h>
#include <QtCore/qxpfunctional.h>
static_assert(QV4::CompiledData::QmlCompileHashSpace > QML_COMPILE_HASH_LENGTH);
#if defined(QML_COMPILE_HASH) && defined(QML_COMPILE_HASH_LENGTH) && QML_COMPILE_HASH_LENGTH > 0
# ifdef Q_OS_LINUX
// Place on a separate section on Linux so it's easier to check from outside
// what the hash version is.
__attribute__((section(".qml_compile_hash")))
# endif
const char qml_compile_hash[QV4::CompiledData::QmlCompileHashSpace] = QML_COMPILE_HASH;
static_assert(sizeof(QV4::CompiledData::Unit::libraryVersionHash) > QML_COMPILE_HASH_LENGTH,
"Compile hash length exceeds reserved size in data structure. Please adjust and bump the format version");
#else
# error "QML_COMPILE_HASH must be defined for the build of QtDeclarative to ensure version checking for cache files"
#endif
QT_BEGIN_NAMESPACE
namespace QV4 {
@ -50,12 +34,6 @@ bool Unit::verifyHeader(QDateTime expectedSourceTimeStamp, QString *errorString)
return false;
}
if (qtVersion != quint32(QT_VERSION)) {
*errorString = QString::fromUtf8("Qt version mismatch. Found %1 expected %2")
.arg(qtVersion, 0, 16).arg(QT_VERSION, 0, 16);
return false;
}
if (sourceTimeStamp) {
// Files from the resource system do not have any time stamps, so fall back to the application
// executable.
@ -69,20 +47,6 @@ bool Unit::verifyHeader(QDateTime expectedSourceTimeStamp, QString *errorString)
}
}
#if defined(QML_COMPILE_HASH) && defined(QML_COMPILE_HASH_LENGTH) && QML_COMPILE_HASH_LENGTH > 0
if (qstrncmp(qml_compile_hash, libraryVersionHash, QML_COMPILE_HASH_LENGTH) != 0) {
*errorString = QStringLiteral("QML compile hashes don't match. Found %1 expected %2")
.arg(QString::fromLatin1(
QByteArray(libraryVersionHash, QML_COMPILE_HASH_LENGTH)
.toPercentEncoding()),
QString::fromLatin1(
QByteArray(qml_compile_hash, QML_COMPILE_HASH_LENGTH)
.toPercentEncoding()));
return false;
}
#else
#error "QML_COMPILE_HASH must be defined for the build of QtDeclarative to ensure version checking for cache files"
#endif
return true;
}

View File

@ -51,7 +51,7 @@ QT_BEGIN_NAMESPACE
// Also change the comment behind the number to describe the latest change. This has the added
// benefit that if another patch changes the version too, it will result in a merge conflict, and
// not get removed silently.
#define QV4_DATA_STRUCTURE_VERSION 0x44 // added a flag to mark properties as final from QML
#define QV4_DATA_STRUCTURE_VERSION 0x45 // Removed Qt version and compile hash checks
class QIODevice;
class QQmlTypeNameCache;
@ -1203,7 +1203,6 @@ struct QmlUnit
};
static_assert(sizeof(QmlUnit) == 16, "QmlUnit structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target");
enum { QmlCompileHashSpace = 48 };
static const char magic_str[] = "qv4cdata";
struct Unit
@ -1211,12 +1210,11 @@ struct Unit
// DO NOT CHANGE THESE FIELDS EVER
char magic[8];
quint32_le version;
quint32_le qtVersion;
qint64_le sourceTimeStamp;
quint32_le unitSize; // Size of the Unit and any depending data.
// END DO NOT CHANGE THESE FIELDS EVER
char libraryVersionHash[QmlCompileHashSpace];
quint32_le reserved; // For predictable alignment and size. Used to be Qt version.
qint64_le sourceTimeStamp;
quint32_le unitSize; // Size of the Unit and any depending data.
char md5Checksum[16]; // checksum of all bytes following this field.
char dependencyMD5Checksum[16];
@ -1387,7 +1385,7 @@ struct Unit
bool verifyHeader(QDateTime expectedSourceTimeStamp, QString *errorString) const;
};
static_assert(sizeof(Unit) == 248, "Unit structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target");
static_assert(sizeof(Unit) == 200, "Unit structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target");
struct TypeReference
{

View File

@ -9,7 +9,6 @@
#include <private/qv4alloca_p.h>
#include <private/qqmljslexer_p.h>
#include <private/qqmljsast_p.h>
#include <private/qml_compile_hash_p.h>
#include <private/qqmlirbuilder_p.h>
#include <QCryptographicHash>
#include <QtEndian>
@ -632,8 +631,6 @@ QV4::CompiledData::Unit QV4::Compiler::JSUnitGenerator::generateHeader(QV4::Comp
unit.flags = QV4::CompiledData::Unit::IsJavascript;
unit.flags |= module->unitFlags;
unit.version = QV4_DATA_STRUCTURE_VERSION;
unit.qtVersion = QT_VERSION;
qstrcpy(unit.libraryVersionHash, QML_COMPILE_HASH);
memset(unit.md5Checksum, 0, sizeof(unit.md5Checksum));
memset(unit.dependencyMD5Checksum, 0, sizeof(unit.dependencyMD5Checksum));

View File

@ -281,11 +281,8 @@ void tst_qmldiskcache::loadLocalAsFallback()
QV4::CompiledData::Unit unit = {};
memcpy(unit.magic, QV4::CompiledData::magic_str, sizeof(unit.magic));
unit.version = QV4_DATA_STRUCTURE_VERSION;
unit.qtVersion = QT_VERSION;
unit.sourceTimeStamp = testCompiler.mappedFile.fileTime(QFile::FileModificationTime).toMSecsSinceEpoch();
unit.unitSize = ~0U; // make the size a silly number
// write something to the library hash that should cause it not to be loaded
memset(unit.libraryVersionHash, 'z', sizeof(unit.libraryVersionHash));
memset(unit.md5Checksum, 0, sizeof(unit.md5Checksum));
// leave the other fields unset, since they don't matter
@ -424,20 +421,6 @@ void tst_qmldiskcache::basicVersionChecks()
QVERIFY2(testCompiler.verify(), qPrintable(testCompiler.lastErrorString));
}
{
testCompiler.clearCache();
QVERIFY2(testCompiler.compile(contents), qPrintable(testCompiler.lastErrorString));
const QString qtVersionFile = QStringLiteral("qtversion.qml");
QVERIFY(testCompiler.tweakHeader([](QV4::CompiledData::Unit *header) {
header->qtVersion = 0;
}, qtVersionFile));
QVERIFY(!testCompiler.verify(qtVersionFile));
QCOMPARE(testCompiler.lastErrorString, QString::fromUtf8("Qt version mismatch. Found 0 expected %1").arg(QT_VERSION, 0, 16));
testCompiler.clearCache(qtVersionFile);
}
{
testCompiler.clearCache();
QVERIFY2(testCompiler.compile(contents), qPrintable(testCompiler.lastErrorString));