Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qv4compileddata.cpp
Go to the documentation of this file.
1// Copyright (C) 2024 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
5
6#include <private/inlinecomponentutils_p.h>
7#include <private/qml_compile_hash_p.h>
8#include <private/qqmlscriptdata_p.h>
9#include <private/qqmltypenamecache_p.h>
10#include <private/qv4resolvedtypereference_p.h>
11
12#include <QtQml/qqmlfile.h>
13
14#include <QtCore/qdir.h>
15#include <QtCore/qscopeguard.h>
16#include <QtCore/qstandardpaths.h>
17#include <QtCore/qxpfunctional.h>
18
19static_assert(QV4::CompiledData::QmlCompileHashSpace > QML_COMPILE_HASH_LENGTH);
20
21#if defined(QML_COMPILE_HASH) && defined(QML_COMPILE_HASH_LENGTH) && QML_COMPILE_HASH_LENGTH > 0
22# ifdef Q_OS_LINUX
23// Place on a separate section on Linux so it's easier to check from outside
24// what the hash version is.
25__attribute__((section(".qml_compile_hash")))
26# endif
27const char qml_compile_hash[QV4::CompiledData::QmlCompileHashSpace] = QML_COMPILE_HASH;
28static_assert(sizeof(QV4::CompiledData::Unit::libraryVersionHash) > QML_COMPILE_HASH_LENGTH,
29 "Compile hash length exceeds reserved size in data structure. Please adjust and bump the format version");
30#else
31# error "QML_COMPILE_HASH must be defined for the build of QtDeclarative to ensure version checking for cache files"
32#endif
33
34QT_BEGIN_NAMESPACE
35
36namespace QV4 {
37namespace CompiledData {
38
39
40bool Unit::verifyHeader(QDateTime expectedSourceTimeStamp, QString *errorString) const
41{
42 if (strncmp(magic, CompiledData::magic_str, sizeof(magic))) {
43 *errorString = QStringLiteral("Magic bytes in the header do not match");
44 return false;
45 }
46
47 if (version != quint32(QV4_DATA_STRUCTURE_VERSION)) {
48 *errorString = QString::fromUtf8("V4 data structure version mismatch. Found %1 expected %2")
49 .arg(version, 0, 16).arg(QV4_DATA_STRUCTURE_VERSION, 0, 16);
50 return false;
51 }
52
53 if (qtVersion != quint32(QT_VERSION)) {
54 *errorString = QString::fromUtf8("Qt version mismatch. Found %1 expected %2")
55 .arg(qtVersion, 0, 16).arg(QT_VERSION, 0, 16);
56 return false;
57 }
58
59 if (sourceTimeStamp) {
60 // Files from the resource system do not have any time stamps, so fall back to the application
61 // executable.
62 if (!expectedSourceTimeStamp.isValid())
63 expectedSourceTimeStamp = QFileInfo(QCoreApplication::applicationFilePath()).lastModified();
64
65 if (expectedSourceTimeStamp.isValid()
66 && expectedSourceTimeStamp.toMSecsSinceEpoch() != sourceTimeStamp) {
67 *errorString = QStringLiteral("QML source file has a different time stamp than cached file.");
68 return false;
69 }
70 }
71
72#if defined(QML_COMPILE_HASH) && defined(QML_COMPILE_HASH_LENGTH) && QML_COMPILE_HASH_LENGTH > 0
73 if (qstrncmp(qml_compile_hash, libraryVersionHash, QML_COMPILE_HASH_LENGTH) != 0) {
74 *errorString = QStringLiteral("QML compile hashes don't match. Found %1 expected %2")
75 .arg(QString::fromLatin1(
76 QByteArray(libraryVersionHash, QML_COMPILE_HASH_LENGTH)
77 .toPercentEncoding()),
78 QString::fromLatin1(
79 QByteArray(qml_compile_hash, QML_COMPILE_HASH_LENGTH)
80 .toPercentEncoding()));
81 return false;
82 }
83#else
84#error "QML_COMPILE_HASH must be defined for the build of QtDeclarative to ensure version checking for cache files"
85#endif
86 return true;
87}
88
89/*!
90 \internal
91 This function creates a temporary key vector and sorts it to guarantuee a stable
92 hash. This is used to calculate a check-sum on dependent meta-objects.
93 */
95 QCryptographicHash *hash, QHash<quintptr, QByteArray> *checksums) const
96{
97 std::vector<int> keys (size());
98 int i = 0;
99 for (auto it = constBegin(), end = constEnd(); it != end; ++it) {
100 keys[i] = it.key();
101 ++i;
102 }
103 std::sort(keys.begin(), keys.end());
104 for (int key: keys) {
105 if (!this->operator[](key)->addToHash(hash, checksums))
106 return false;
107 }
108
109 return true;
110}
111
112CompilationUnit::CompilationUnit(
113 const Unit *unitData, const QString &fileName, const QString &finalUrlString)
114{
115 setUnitData(unitData, nullptr, fileName, finalUrlString);
116}
117
119{
120 qDeleteAll(resolvedTypes);
121
122 if (data) {
123 if (data->qmlUnit() != qmlData)
124 free(const_cast<QmlUnit *>(qmlData));
125 qmlData = nullptr;
126
127 if (!(data->flags & QV4::CompiledData::Unit::StaticData))
128 free(const_cast<Unit *>(data));
129 }
130 data = nullptr;
131#if Q_BYTE_ORDER == Q_BIG_ENDIAN
132 delete [] constants;
133 constants = nullptr;
134#endif
135}
136
137QString CompilationUnit::localCacheFilePath(const QUrl &url)
138{
139 static const QByteArray envCachePath = qgetenv("QML_DISK_CACHE_PATH");
140
141 const QString localSourcePath = QQmlFile::urlToLocalFileOrQrc(url);
142 const QString cacheFileSuffix
143 = QFileInfo(localSourcePath + QLatin1Char('c')).completeSuffix();
144 QCryptographicHash fileNameHash(QCryptographicHash::Sha1);
145 fileNameHash.addData(localSourcePath.toUtf8());
146 QString directory = envCachePath.isEmpty()
147 ? QStandardPaths::writableLocation(QStandardPaths::CacheLocation)
148 + QLatin1String("/qmlcache/")
149 : QString::fromLocal8Bit(envCachePath) + QLatin1String("/");
150 QDir::root().mkpath(directory);
151 return directory + QString::fromUtf8(fileNameHash.result().toHex())
152 + QLatin1Char('.') + cacheFileSuffix;
153}
154
156 const QUrl &url, const QDateTime &sourceTimeStamp, QString *errorString)
157{
158 if (!QQmlFile::isLocalFile(url)) {
159 *errorString = QStringLiteral("File has to be a local file.");
160 return false;
161 }
162
163 const QString sourcePath = QQmlFile::urlToLocalFileOrQrc(url);
164 auto cacheFile = std::make_unique<CompilationUnitMapper>();
165
166 const QStringList cachePaths = { sourcePath + QLatin1Char('c'), localCacheFilePath(url) };
167 for (const QString &cachePath : cachePaths) {
168 Unit *mappedUnit = cacheFile->get(cachePath, sourceTimeStamp, errorString);
169 if (!mappedUnit)
170 continue;
171
172 const Unit *oldData = unitData();
173 const Unit * const oldDataPtr
174 = (oldData && !(oldData->flags & Unit::StaticData))
175 ? oldData
176 : nullptr;
177
178 auto dataPtrRevert = qScopeGuard([this, oldData](){
179 setUnitData(oldData);
180 });
181 setUnitData(mappedUnit);
182
183 if (mappedUnit->sourceFileIndex != 0) {
184 if (mappedUnit->sourceFileIndex >=
185 mappedUnit->stringTableSize + dynamicStrings.size()) {
186 *errorString = QStringLiteral("QML source file index is invalid.");
187 continue;
188 }
189 if (sourcePath !=
190 QQmlFile::urlToLocalFileOrQrc(stringAt(mappedUnit->sourceFileIndex))) {
191 *errorString = QStringLiteral("QML source file has moved to a different location.");
192 continue;
193 }
194 }
195
196 dataPtrRevert.dismiss();
197 free(const_cast<Unit*>(oldDataPtr));
198 backingFile = std::move(cacheFile);
199 return true;
200 }
201
202 return false;
203}
204
205bool CompilationUnit::saveToDisk(const QUrl &unitUrl, QString *errorString)
206{
207 if (unitData()->sourceTimeStamp == 0) {
208 *errorString = QStringLiteral("Missing time stamp for source file");
209 return false;
210 }
211
212 if (!QQmlFile::isLocalFile(unitUrl)) {
213 *errorString = QStringLiteral("File has to be a local file.");
214 return false;
215 }
216
217 return SaveableUnitPointer(unitData()).saveToDisk<char>(
218 [&unitUrl, errorString](const char *data, quint32 size) {
219 const QString cachePath = localCacheFilePath(unitUrl);
220 if (SaveableUnitPointer::writeDataToFile(
221 cachePath, data, size, errorString)) {
222 CompilationUnitMapper::invalidate(cachePath);
223 return true;
224 }
225
226 return false;
227 });
228}
229
230QStringList CompilationUnit::moduleRequests() const
231{
232 QStringList requests;
233 requests.reserve(data->moduleRequestTableSize);
234 for (uint i = 0; i < data->moduleRequestTableSize; ++i)
235 requests << stringAt(data->moduleRequestTable()[i]);
236 return requests;
237}
238
240{
241 for (ResolvedTypeReference *ref : std::as_const(resolvedTypes)) {
242 if (ref->type().typeId() == type)
243 return ref;
244 }
245 return nullptr;
246
247}
248
249int CompilationUnit::totalBindingsCount(const QString &inlineComponentRootName) const
250{
251 if (inlineComponentRootName.isEmpty())
253 return inlineComponentData[inlineComponentRootName].totalBindingCount;
254}
255
256int CompilationUnit::totalObjectCount(const QString &inlineComponentRootName) const
257{
258 if (inlineComponentRootName.isEmpty())
259 return m_totalObjectCount;
260 return inlineComponentData[inlineComponentRootName].totalObjectCount;
261}
262
263
265 const QQmlType &type,
266 qxp::function_ref<void(const QString&)> &&populateIcData)
267{
268 QString icRootName;
269 if (type.isInlineComponentType()) {
270 icRootName = type.elementName();
271 }
272 populateIcData(icRootName);
273}
274
276{
277 // Add to type registry of composites
278 if (propertyCaches.needsVMEMetaObject(/*root object*/0)) {
279 // qmlType is only valid for types that have references to themselves.
280 if (type.isValid()) {
281 qmlType = type;
282 } else {
283 qmlType = QQmlMetaType::findCompositeType(
284 url(), this, (unitData()->flags & CompiledData::Unit::IsSingleton)
285 ? QQmlMetaType::Singleton
286 : QQmlMetaType::NonSingleton);
287 }
288
289 QQmlMetaType::registerInternalCompositeType(this);
290 } else {
291 const QV4::CompiledData::Object *obj = objectAt(/*root object*/0);
292 auto *typeRef = resolvedTypes.value(obj->inheritedTypeNameIndex);
293 Q_ASSERT(typeRef);
294 if (const auto compilationUnit = typeRef->compilationUnit())
295 qmlType = compilationUnit->qmlType;
296 else
297 qmlType = typeRef->type();
298 }
299
300 // Collect some data for instantiation later.
301 using namespace icutils;
302 std::vector<QV4::CompiledData::InlineComponent> allICs {};
303 for (int i=0; i != objectCount(); ++i) {
304 const CompiledObject *obj = objectAt(i);
305 for (auto it = obj->inlineComponentsBegin(); it != obj->inlineComponentsEnd(); ++it) {
306 allICs.push_back(*it);
307 }
308 }
309 NodeList nodes;
310 nodes.resize(allICs.size());
311 std::iota(nodes.begin(), nodes.end(), 0);
312 AdjacencyList adjacencyList;
313 adjacencyList.resize(nodes.size());
314 fillAdjacencyListForInlineComponents(this, adjacencyList, nodes, allICs);
315 bool hasCycle = false;
316 auto nodesSorted = topoSort(nodes, adjacencyList, hasCycle);
317 Q_ASSERT(!hasCycle); // would have already been discovered by qqmlpropertycachcecreator
318
319 // We need to first iterate over all inline components,
320 // as the containing component might create instances of them
321 // and in that case we need to add its object count
322 for (auto nodeIt = nodesSorted.rbegin(); nodeIt != nodesSorted.rend(); ++nodeIt) {
323 const auto &ic = allICs.at(nodeIt->index());
324 const int lastICRoot = ic.objectIndex;
325 for (int i = ic.objectIndex; i<objectCount(); ++i) {
326 const QV4::CompiledData::Object *obj = objectAt(i);
327 bool leftCurrentInlineComponent
328 = (i != lastICRoot
331 if (leftCurrentInlineComponent)
332 break;
333 const QString lastICRootName = stringAt(ic.nameIndex);
334 inlineComponentData[lastICRootName].totalBindingCount
335 += obj->nBindings;
336
337 if (auto *typeRef = resolvedTypes.value(obj->inheritedTypeNameIndex)) {
338 const auto type = typeRef->type();
339 if (type.isValid() && type.parserStatusCast() != -1)
340 ++inlineComponentData[lastICRootName].totalParserStatusCount;
341
342 ++inlineComponentData[lastICRootName].totalObjectCount;
343 if (const auto compilationUnit = typeRef->compilationUnit()) {
344 // if the type is an inline component type, we have to extract the information
345 // from it.
346 // This requires that inline components are visited in the correct order.
347 processInlinComponentType(type, [&](const QString &currentlyVisitedICName) {
348 auto &icData = inlineComponentData[lastICRootName];
349 icData.totalBindingCount += compilationUnit->totalBindingsCount(currentlyVisitedICName);
350 icData.totalParserStatusCount += compilationUnit->totalParserStatusCount(currentlyVisitedICName);
351 icData.totalObjectCount += compilationUnit->totalObjectCount(currentlyVisitedICName);
352 });
353 }
354 }
355 }
356 }
357 int bindingCount = 0;
358 int parserStatusCount = 0;
359 int objectCount = 0;
360 for (quint32 i = 0, count = this->objectCount(); i < count; ++i) {
361 const QV4::CompiledData::Object *obj = objectAt(i);
362 if (obj->hasFlag(QV4::CompiledData::Object::IsPartOfInlineComponent))
363 continue;
364
365 bindingCount += obj->nBindings;
366 if (auto *typeRef = resolvedTypes.value(obj->inheritedTypeNameIndex)) {
367 const auto type = typeRef->type();
368 if (type.isValid() && type.parserStatusCast() != -1)
369 ++parserStatusCount;
370 ++objectCount;
371 if (const auto compilationUnit = typeRef->compilationUnit()) {
372 processInlinComponentType(type, [&](const QString &currentlyVisitedICName){
373 bindingCount += compilationUnit->totalBindingsCount(currentlyVisitedICName);
374 parserStatusCount += compilationUnit->totalParserStatusCount(currentlyVisitedICName);
375 objectCount += compilationUnit->totalObjectCount(currentlyVisitedICName);
376 });
377 }
378 }
379 }
380
381 m_totalBindingsCount = bindingCount;
382 m_totalParserStatusCount = parserStatusCount;
383 m_totalObjectCount = objectCount;
384}
385
386int CompilationUnit::totalParserStatusCount(const QString &inlineComponentRootName) const
387{
388 if (inlineComponentRootName.isEmpty())
390 return inlineComponentData[inlineComponentRootName].totalParserStatusCount;
391}
392
393bool CompilationUnit::verifyChecksum(const DependentTypesHasher &dependencyHasher) const
394{
395 if (!dependencyHasher) {
396 for (size_t i = 0; i < sizeof(data->dependencyMD5Checksum); ++i) {
397 if (data->dependencyMD5Checksum[i] != 0)
398 return false;
399 }
400 return true;
401 }
402 const QByteArray checksum = dependencyHasher();
403 return checksum.size() == sizeof(data->dependencyMD5Checksum)
404 && memcmp(data->dependencyMD5Checksum, checksum.constData(),
405 sizeof(data->dependencyMD5Checksum)) == 0;
406}
407
408QQmlType CompilationUnit::qmlTypeForComponent(const QString &inlineComponentName) const
409{
410 if (inlineComponentName.isEmpty())
411 return qmlType;
412 return inlineComponentData[inlineComponentName].qmlType;
413}
414
415} // namespace CompiledData
416} // namespace QV4
417
418QT_END_NAMESPACE
static void processInlinComponentType(const QQmlType &type, qxp::function_ref< void(const QString &)> &&populateIcData)
static const char magic_str[]
Definition qjsvalue.h:23
#define QV4_DATA_STRUCTURE_VERSION
void finalizeCompositeType(const QQmlType &type)
int totalParserStatusCount(const QString &inlineComponentRootName) const
Q_QML_EXPORT bool saveToDisk(const QUrl &unitUrl, QString *errorString)
ResolvedTypeReference * resolvedType(int id) const
int totalBindingsCount(const QString &inlineComponentRootName) const
ResolvedTypeReferenceMap resolvedTypes
int totalObjectCount(const QString &inlineComponentRootName) const
bool verifyChecksum(const CompiledData::DependentTypesHasher &dependencyHasher) const
QQmlType qmlTypeForComponent(const QString &inlineComponentName=QString()) const
const CompiledObject * objectAt(int index) const
Q_QML_EXPORT bool loadFromDisk(const QUrl &url, const QDateTime &sourceTimeStamp, QString *errorString)
bool hasFlag(Flag flag) const
InlineComponentIterator inlineComponentsEnd() const
InlineComponentIterator inlineComponentsBegin() const
bool addToHash(QCryptographicHash *hash, QHash< quintptr, QByteArray > *checksums) const
bool verifyHeader(QDateTime expectedSourceTimeStamp, QString *errorString) const