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

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

source code of qtdeclarative/src/qml/common/qv4compileddata.cpp