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// Qt-Security score:critical data-parser
4
6
7#include <private/inlinecomponentutils_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
20
21namespace QV4 {
22namespace CompiledData {
23
24
25bool Unit::verifyHeader(QDateTime expectedSourceTimeStamp, QString *errorString) const
26{
27 if (strncmp(magic, CompiledData::magic_str, sizeof(magic))) {
28 *errorString = QStringLiteral("Magic bytes in the header do not match");
29 return false;
30 }
31
32 if (version != quint32(QV4_DATA_STRUCTURE_VERSION)) {
33 *errorString = QString::fromUtf8("V4 data structure version mismatch. Found %1 expected %2")
34 .arg(quint32(version), 0, 16).arg(QV4_DATA_STRUCTURE_VERSION, 0, 16);
35 return false;
36 }
37
38 switch (sourceTimeStamp) {
39 case 0:
40 // No validation necessary
41 return true;
42 case -1:
43 // Content-hash mode: the unit is validated against sourceChecksum rather than the source
44 // file's time stamp. The actual comparison is done in CompilationUnit::loadFromDisk, which
45 // has access to the source code.
46 return true;
47 default:
48 break;
49 }
50
51 // Files from the resource system do not have any time stamps, so fall back to the application
52 // executable.
53 if (!expectedSourceTimeStamp.isValid()) {
54 expectedSourceTimeStamp = QFileInfo(QCoreApplication::applicationFilePath()).lastModified();
55 if (!expectedSourceTimeStamp.isValid()) {
56 *errorString =
57 QStringLiteral("Failed to get valid timestamp from application executable");
58 return false;
59 }
60 }
61 if (expectedSourceTimeStamp.toMSecsSinceEpoch() != sourceTimeStamp) {
62 *errorString =
63 QStringLiteral("QML source file has a different time stamp than cached file.");
64 return false;
65 }
66
67 return true;
68}
69
70/*!
71 \internal
72 This function creates a temporary key vector and sorts it to guarantuee a stable
73 hash. This is used to calculate a check-sum on dependent meta-objects.
74 */
76 QCryptographicHash *hash, QHash<quintptr, QByteArray> *checksums) const
77{
78 std::vector<int> keys (size());
79 int i = 0;
80 for (auto it = constBegin(), end = constEnd(); it != end; ++it) {
81 keys[i] = it.key();
82 ++i;
83 }
84 std::sort(keys.begin(), keys.end());
85 for (int key: keys) {
86 if (!this->operator[](key)->addToHash(hash, checksums))
87 return false;
88 }
89
90 return true;
91}
92
93CompilationUnit::CompilationUnit(
94 const Unit *unitData, const QString &fileName, const QString &finalUrlString)
95{
96 setUnitData(unitData, nullptr, fileName, finalUrlString);
97}
98
100{
101 qDeleteAll(resolvedTypes);
102
103 if (data) {
104 if (data->qmlUnit() != qmlData)
105 free(const_cast<QmlUnit *>(qmlData));
106 qmlData = nullptr;
107
108 if (!(data->flags & QV4::CompiledData::Unit::StaticData))
109 free(const_cast<Unit *>(data));
110 }
111 data = nullptr;
112#if Q_BYTE_ORDER == Q_BIG_ENDIAN
113 delete [] constants;
114 constants = nullptr;
115#endif
116}
117
118QString CompilationUnit::localCacheFilePath(const QUrl &url)
119{
120 static const QByteArray envCachePath = qgetenv("QML_DISK_CACHE_PATH");
121
122 const QString localSourcePath = QQmlFile::urlToLocalFileOrQrc(url);
123 const QString cacheFileSuffix
124 = QFileInfo(localSourcePath + QLatin1Char('c')).completeSuffix();
125 QCryptographicHash fileNameHash(QCryptographicHash::Sha1);
126 fileNameHash.addData(localSourcePath.toUtf8());
127 QString directory = envCachePath.isEmpty()
128 ? QStandardPaths::writableLocation(QStandardPaths::CacheLocation)
129 + QLatin1String("/qmlcache/")
130 : QString::fromLocal8Bit(envCachePath) + QLatin1String("/");
131 QDir::root().mkpath(directory);
132 return directory + QString::fromUtf8(fileNameHash.result().toHex())
133 + QLatin1Char('.') + cacheFileSuffix;
134}
135
137 const QUrl &url, const QDateTime &sourceTimeStamp,
139{
140 if (!QQmlFile::isLocalFile(url)) {
141 *errorString = QStringLiteral("File has to be a local file.");
142 return false;
143 }
144
145 const QString sourcePath = QQmlFile::urlToLocalFileOrQrc(url);
146 auto cacheFile = std::make_unique<CompilationUnitMapper>();
147
148 const QStringList cachePaths = { sourcePath + QLatin1Char('c'), localCacheFilePath(url) };
149 for (const QString &cachePath : cachePaths) {
150 Unit *mappedUnit = cacheFile->get(cachePath, sourceTimeStamp, errorString);
151 if (!mappedUnit)
152 continue;
153
154 const Unit *oldData = unitData();
155 const Unit * const oldDataPtr
156 = (oldData && !(oldData->flags & Unit::StaticData))
157 ? oldData
158 : nullptr;
159
160 auto dataPtrRevert = qScopeGuard([this, oldData](){
161 setUnitData(oldData);
162 });
163 setUnitData(mappedUnit);
164
165 if (mappedUnit->sourceTimeStamp == -1) {
166 const QByteArray checksum = sourceChecksum();
167 if (checksum.size() != sizeof(mappedUnit->sourceChecksum)
168 || memcmp(mappedUnit->sourceChecksum, checksum.constData(), checksum.size()) != 0) {
169 *errorString = QStringLiteral(
170 "QML source file has a different content checksum than cached file.");
171 continue;
172 }
173 }
174
175 if (mappedUnit->sourceFileIndex != 0) {
176 if (mappedUnit->sourceFileIndex >=
177 mappedUnit->stringTableSize + dynamicStrings.size()) {
178 *errorString = QStringLiteral("QML source file index is invalid.");
179 continue;
180 }
181 if (sourcePath !=
182 QQmlFile::urlToLocalFileOrQrc(stringAt(mappedUnit->sourceFileIndex))) {
183 *errorString = QStringLiteral("QML source file has moved to a different location.");
184 continue;
185 }
186 }
187
188 dataPtrRevert.dismiss();
189 free(const_cast<Unit*>(oldDataPtr));
190 backingFile = std::move(cacheFile);
191 return true;
192 }
193
194 return false;
195}
196
198 const QUrl &unitUrl, qxp::function_ref<QByteArray() const> sourceChecksum,
199 QString *errorString) const
200{
201 if (!QQmlFile::isLocalFile(unitUrl)) {
202 *errorString = QStringLiteral("File has to be a local file.");
203 return false;
204 }
205
206 Unit *mutableUnit = const_cast<Unit *>(unitData());
207 const QByteArray checksum = sourceChecksum();
208 if (checksum.size() != sizeof(mutableUnit->sourceChecksum)) {
209 *errorString = QStringLiteral("Failed to compute source code checksum");
210 return false;
211 }
212
213 // Switch the unit to content-hash mode for the duration of the write. sourceTimeStamp and
214 // sourceChecksum live before md5Checksum and are therefore not covered by it, so we can patch
215 // them without recomputing the integrity checksum. Restore them afterwards because the
216 // in-memory unit is kept around and may still be used with its original time stamp.
217 const qint64 oldTimeStamp = mutableUnit->sourceTimeStamp;
218 char oldChecksum[sizeof(mutableUnit->sourceChecksum)];
219 memcpy(oldChecksum, mutableUnit->sourceChecksum, sizeof(oldChecksum));
220 mutableUnit->sourceTimeStamp = -1;
221 memcpy(mutableUnit->sourceChecksum, checksum.constData(), sizeof(mutableUnit->sourceChecksum));
222 const auto restore = qScopeGuard([&]() {
223 mutableUnit->sourceTimeStamp = oldTimeStamp;
224 memcpy(mutableUnit->sourceChecksum, oldChecksum, sizeof(oldChecksum));
225 });
226
227 return SaveableUnitPointer(unitData()).saveToDisk<char>(
228 [&unitUrl, errorString](const char *data, quint32 size) {
229 const QString cachePath = localCacheFilePath(unitUrl);
230 if (SaveableUnitPointer::writeDataToFile(
231 cachePath, data, size, errorString)) {
232 CompilationUnitMapper::invalidate(cachePath);
233 return true;
234 }
235
236 return false;
237 });
238}
239
240QStringList CompilationUnit::moduleRequests() const
241{
242 QStringList requests;
243 requests.reserve(data->moduleRequestTableSize);
244 for (uint i = 0; i < data->moduleRequestTableSize; ++i)
245 requests << stringAt(data->moduleRequestTable()[i]);
246 return requests;
247}
248
250{
251 for (ResolvedTypeReference *ref : std::as_const(resolvedTypes)) {
252 if (ref->type().typeId() == type)
253 return ref;
254 }
255 return nullptr;
256
257}
258
260{
261 // Add to type registry of composites
262 if (propertyCaches.needsVMEMetaObject(/*root object*/0)) {
263 // qmlType is only valid for types that have references to themselves.
264 if (type.isValid()) {
265 qmlType = type;
266 } else {
267 qmlType = QQmlMetaType::findCompositeType(
268 url(), this, (unitData()->flags & CompiledData::Unit::IsSingleton)
269 ? QQmlMetaType::Singleton
270 : QQmlMetaType::NonSingleton);
271 }
272
273 QQmlMetaType::registerInternalCompositeType(this);
274 } else {
275 const QV4::CompiledData::Object *obj = objectAt(/*root object*/0);
276 auto *typeRef = resolvedTypes.value(obj->inheritedTypeNameIndex);
277 Q_ASSERT(typeRef);
278 qmlType = typeRef->type();
279 }
280}
281
282bool CompilationUnit::verifyChecksum(const DependentTypesHasher &dependencyHasher) const
283{
284 if (!dependencyHasher) {
285 for (size_t i = 0; i < sizeof(data->dependencyMD5Checksum); ++i) {
286 if (data->dependencyMD5Checksum[i] != 0)
287 return false;
288 }
289 return true;
290 }
291 const QByteArray checksum = dependencyHasher();
292 return checksum.size() == sizeof(data->dependencyMD5Checksum)
293 && memcmp(data->dependencyMD5Checksum, checksum.constData(),
294 sizeof(data->dependencyMD5Checksum)) == 0;
295}
296
297QQmlType CompilationUnit::qmlTypeForComponent(const QString &inlineComponentName) const
298{
299 if (inlineComponentName.isEmpty())
300 return qmlType;
301 return inlineComponentData[inlineComponentName].qmlType;
302}
303
304} // namespace CompiledData
305} // namespace QV4
306
307QT_END_NAMESPACE
Combined button and popup list for selecting options.
static const char magic_str[]
Definition qjsvalue.h:24
#define QV4_DATA_STRUCTURE_VERSION
void finalizeCompositeType(const QQmlType &type)
ResolvedTypeReference * resolvedType(int id) const
ResolvedTypeReferenceMap resolvedTypes
bool verifyChecksum(const CompiledData::DependentTypesHasher &dependencyHasher) const
QQmlType qmlTypeForComponent(const QString &inlineComponentName=QString()) const
Q_QML_EXPORT bool saveToDisk(const QUrl &unitUrl, qxp::function_ref< QByteArray() const > sourceChecksum, QString *errorString) const
const CompiledObject * objectAt(int index) const
Q_QML_EXPORT bool loadFromDisk(const QUrl &url, const QDateTime &sourceTimeStamp, qxp::function_ref< QByteArray() const > sourceChecksum, QString *errorString)
bool addToHash(QCryptographicHash *hash, QHash< quintptr, QByteArray > *checksums) const
bool verifyHeader(QDateTime expectedSourceTimeStamp, QString *errorString) const