1 | // Copyright (C) 2019 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 "qml/qqmlprivate.h" |
5 | #include "qv4engine_p.h" |
6 | #include "qv4executablecompilationunit_p.h" |
7 | |
8 | #include <private/qv4engine_p.h> |
9 | #include <private/qv4regexp_p.h> |
10 | #include <private/qv4lookup_p.h> |
11 | #include <private/qv4qmlcontext_p.h> |
12 | #include <private/qv4identifiertable_p.h> |
13 | #include <private/qv4objectproto_p.h> |
14 | #include <private/qqmlengine_p.h> |
15 | #include <private/qv4qobjectwrapper_p.h> |
16 | #include <private/qqmlvaluetypewrapper_p.h> |
17 | #include <private/qqmlscriptdata_p.h> |
18 | #include <private/qv4module_p.h> |
19 | #include <private/qv4compilationunitmapper_p.h> |
20 | #include <private/qml_compile_hash_p.h> |
21 | #include <private/qqmltypewrapper_p.h> |
22 | #include <private/inlinecomponentutils_p.h> |
23 | #include <private/qv4resolvedtypereference_p.h> |
24 | #include <private/qv4objectiterator_p.h> |
25 | |
26 | #include <QtQml/qqmlfile.h> |
27 | #include <QtQml/qqmlpropertymap.h> |
28 | |
29 | #include <QtCore/qdir.h> |
30 | #include <QtCore/qstandardpaths.h> |
31 | #include <QtCore/qfileinfo.h> |
32 | #include <QtCore/qscopeguard.h> |
33 | #include <QtCore/qcryptographichash.h> |
34 | #include <QtCore/QScopedValueRollback> |
35 | |
36 | static_assert(QV4::CompiledData::QmlCompileHashSpace > QML_COMPILE_HASH_LENGTH); |
37 | |
38 | #if defined(QML_COMPILE_HASH) && defined(QML_COMPILE_HASH_LENGTH) && QML_COMPILE_HASH_LENGTH > 0 |
39 | # ifdef Q_OS_LINUX |
40 | // Place on a separate section on Linux so it's easier to check from outside |
41 | // what the hash version is. |
42 | __attribute__((section(".qml_compile_hash" ))) |
43 | # endif |
44 | const char qml_compile_hash[QV4::CompiledData::QmlCompileHashSpace] = QML_COMPILE_HASH; |
45 | static_assert(sizeof(QV4::CompiledData::Unit::libraryVersionHash) > QML_COMPILE_HASH_LENGTH, |
46 | "Compile hash length exceeds reserved size in data structure. Please adjust and bump the format version" ); |
47 | #else |
48 | # error "QML_COMPILE_HASH must be defined for the build of QtDeclarative to ensure version checking for cache files" |
49 | #endif |
50 | |
51 | QT_BEGIN_NAMESPACE |
52 | |
53 | namespace QV4 { |
54 | |
55 | ExecutableCompilationUnit::ExecutableCompilationUnit() = default; |
56 | |
57 | ExecutableCompilationUnit::ExecutableCompilationUnit( |
58 | CompiledData::CompilationUnit &&compilationUnit) |
59 | : CompiledData::CompilationUnit(std::move(compilationUnit)) |
60 | {} |
61 | |
62 | ExecutableCompilationUnit::~ExecutableCompilationUnit() |
63 | { |
64 | unlink(); |
65 | } |
66 | |
67 | QString ExecutableCompilationUnit::localCacheFilePath(const QUrl &url) |
68 | { |
69 | static const QByteArray envCachePath = qgetenv(varName: "QML_DISK_CACHE_PATH" ); |
70 | |
71 | const QString localSourcePath = QQmlFile::urlToLocalFileOrQrc(url); |
72 | const QString cacheFileSuffix = QFileInfo(localSourcePath + QLatin1Char('c')).completeSuffix(); |
73 | QCryptographicHash fileNameHash(QCryptographicHash::Sha1); |
74 | fileNameHash.addData(data: localSourcePath.toUtf8()); |
75 | QString directory = envCachePath.isEmpty() |
76 | ? QStandardPaths::writableLocation(type: QStandardPaths::CacheLocation) + QLatin1String("/qmlcache/" ) |
77 | : QString::fromLocal8Bit(ba: envCachePath) + QLatin1String("/" ); |
78 | QDir::root().mkpath(dirPath: directory); |
79 | return directory + QString::fromUtf8(ba: fileNameHash.result().toHex()) + QLatin1Char('.') + cacheFileSuffix; |
80 | } |
81 | |
82 | static QString toString(QV4::ReturnedValue v) |
83 | { |
84 | Value val = Value::fromReturnedValue(val: v); |
85 | QString result; |
86 | if (val.isInt32()) |
87 | result = QLatin1String("int " ); |
88 | else if (val.isDouble()) |
89 | result = QLatin1String("double " ); |
90 | if (val.isEmpty()) |
91 | result += QLatin1String("empty" ); |
92 | else |
93 | result += val.toQStringNoThrow(); |
94 | return result; |
95 | } |
96 | |
97 | static void dumpConstantTable(const StaticValue *constants, uint count) |
98 | { |
99 | QDebug d = qDebug(); |
100 | d.nospace() << Qt::right; |
101 | for (uint i = 0; i < count; ++i) { |
102 | d << qSetFieldWidth(width: 8) << i << qSetFieldWidth(width: 0) << ": " |
103 | << toString(v: constants[i].asReturnedValue()).toUtf8().constData() << "\n" ; |
104 | } |
105 | } |
106 | |
107 | QV4::Function *ExecutableCompilationUnit::linkToEngine(ExecutionEngine *engine) |
108 | { |
109 | this->engine = engine; |
110 | engine->compilationUnits.insert(n: this); |
111 | |
112 | Q_ASSERT(!runtimeStrings); |
113 | Q_ASSERT(data); |
114 | const quint32 stringCount = totalStringCount(); |
115 | // strings need to be 0 in case a GC run happens while we're within the loop below |
116 | runtimeStrings = (QV4::Heap::String **)calloc(nmemb: stringCount, size: sizeof(QV4::Heap::String*)); |
117 | for (uint i = 0; i < stringCount; ++i) |
118 | runtimeStrings[i] = engine->newString(s: stringAt(index: i)); |
119 | |
120 | // zero-initialize regexps in case a GC run happens while we're within the loop below |
121 | runtimeRegularExpressions |
122 | = new QV4::Value[data->regexpTableSize] {}; |
123 | for (uint i = 0; i < data->regexpTableSize; ++i) { |
124 | const CompiledData::RegExp *re = data->regexpAt(index: i); |
125 | uint f = re->flags(); |
126 | const CompiledData::RegExp::Flags flags = static_cast<CompiledData::RegExp::Flags>(f); |
127 | runtimeRegularExpressions[i] = QV4::RegExp::create( |
128 | engine, pattern: stringAt(index: re->stringIndex()), flags); |
129 | } |
130 | |
131 | if (data->lookupTableSize) { |
132 | runtimeLookups = new QV4::Lookup[data->lookupTableSize]; |
133 | memset(s: runtimeLookups, c: 0, n: data->lookupTableSize * sizeof(QV4::Lookup)); |
134 | const CompiledData::Lookup *compiledLookups = data->lookupTable(); |
135 | for (uint i = 0; i < data->lookupTableSize; ++i) { |
136 | QV4::Lookup *l = runtimeLookups + i; |
137 | |
138 | CompiledData::Lookup::Type type |
139 | = CompiledData::Lookup::Type(uint(compiledLookups[i].type())); |
140 | if (type == CompiledData::Lookup::Type_Getter) |
141 | l->getter = QV4::Lookup::getterGeneric; |
142 | else if (type == CompiledData::Lookup::Type_Setter) |
143 | l->setter = QV4::Lookup::setterGeneric; |
144 | else if (type == CompiledData::Lookup::Type_GlobalGetter) |
145 | l->globalGetter = QV4::Lookup::globalGetterGeneric; |
146 | else if (type == CompiledData::Lookup::Type_QmlContextPropertyGetter) |
147 | l->qmlContextPropertyGetter = QQmlContextWrapper::resolveQmlContextPropertyLookupGetter; |
148 | l->forCall = compiledLookups[i].mode() == CompiledData::Lookup::Mode_ForCall; |
149 | l->nameIndex = compiledLookups[i].nameIndex(); |
150 | } |
151 | } |
152 | |
153 | if (data->jsClassTableSize) { |
154 | // zero the regexps with calloc in case a GC run happens while we're within the loop below |
155 | runtimeClasses |
156 | = (QV4::Heap::InternalClass **)calloc(nmemb: data->jsClassTableSize, |
157 | size: sizeof(QV4::Heap::InternalClass *)); |
158 | |
159 | for (uint i = 0; i < data->jsClassTableSize; ++i) { |
160 | int memberCount = 0; |
161 | const CompiledData::JSClassMember *member |
162 | = data->jsClassAt(idx: i, nMembers: &memberCount); |
163 | runtimeClasses[i] |
164 | = engine->internalClasses(icType: QV4::ExecutionEngine::Class_Object); |
165 | for (int j = 0; j < memberCount; ++j, ++member) |
166 | runtimeClasses[i] |
167 | = runtimeClasses[i]->addMember( |
168 | identifier: engine->identifierTable->asPropertyKey( |
169 | str: runtimeStrings[member->nameOffset()]), |
170 | data: member->isAccessor() |
171 | ? QV4::Attr_Accessor |
172 | : QV4::Attr_Data); |
173 | } |
174 | } |
175 | |
176 | runtimeFunctions.resize(size: data->functionTableSize); |
177 | static bool ignoreAotCompiledFunctions |
178 | = qEnvironmentVariableIsSet(varName: "QV4_FORCE_INTERPRETER" ) |
179 | || !(engine->diskCacheOptions() & ExecutionEngine::DiskCache::AotNative); |
180 | |
181 | const QQmlPrivate::AOTCompiledFunction *aotFunction |
182 | = ignoreAotCompiledFunctions ? nullptr : aotCompiledFunctions; |
183 | |
184 | auto advanceAotFunction = [&](int i) -> const QQmlPrivate::AOTCompiledFunction * { |
185 | if (aotFunction) { |
186 | if (aotFunction->functionPtr) { |
187 | if (aotFunction->extraData == i) |
188 | return aotFunction++; |
189 | } else { |
190 | aotFunction = nullptr; |
191 | } |
192 | } |
193 | return nullptr; |
194 | }; |
195 | |
196 | for (int i = 0 ;i < runtimeFunctions.size(); ++i) { |
197 | const QV4::CompiledData::Function *compiledFunction = data->functionAt(idx: i); |
198 | runtimeFunctions[i] = QV4::Function::create(engine, unit: this, function: compiledFunction, |
199 | aotFunction: advanceAotFunction(i)); |
200 | } |
201 | |
202 | Scope scope(engine); |
203 | Scoped<InternalClass> ic(scope); |
204 | |
205 | runtimeBlocks.resize(size: data->blockTableSize); |
206 | for (int i = 0 ;i < runtimeBlocks.size(); ++i) { |
207 | const QV4::CompiledData::Block *compiledBlock = data->blockAt(idx: i); |
208 | ic = engine->internalClasses(icType: EngineBase::Class_CallContext); |
209 | |
210 | // first locals |
211 | const quint32_le *localsIndices = compiledBlock->localsTable(); |
212 | for (quint32 j = 0; j < compiledBlock->nLocals; ++j) |
213 | ic = ic->addMember( |
214 | identifier: engine->identifierTable->asPropertyKey(str: runtimeStrings[localsIndices[j]]), |
215 | data: Attr_NotConfigurable); |
216 | runtimeBlocks[i] = ic->d(); |
217 | } |
218 | |
219 | static const bool showCode = qEnvironmentVariableIsSet(varName: "QV4_SHOW_BYTECODE" ); |
220 | if (showCode) { |
221 | qDebug() << "=== Constant table" ; |
222 | dumpConstantTable(constants, count: data->constantTableSize); |
223 | qDebug() << "=== String table" ; |
224 | for (uint i = 0, end = totalStringCount(); i < end; ++i) |
225 | qDebug() << " " << i << ":" << runtimeStrings[i]->toQString(); |
226 | qDebug() << "=== Closure table" ; |
227 | for (uint i = 0; i < data->functionTableSize; ++i) |
228 | qDebug() << " " << i << ":" << runtimeFunctions[i]->name()->toQString(); |
229 | qDebug() << "root function at index " |
230 | << (data->indexOfRootFunction != -1 |
231 | ? data->indexOfRootFunction : 0); |
232 | } |
233 | |
234 | if (data->indexOfRootFunction != -1) |
235 | return runtimeFunctions[data->indexOfRootFunction]; |
236 | else |
237 | return nullptr; |
238 | } |
239 | |
240 | Heap::Object *ExecutableCompilationUnit::templateObjectAt(int index) const |
241 | { |
242 | Q_ASSERT(index < int(data->templateObjectTableSize)); |
243 | if (!templateObjects.size()) |
244 | templateObjects.resize(size: data->templateObjectTableSize); |
245 | Heap::Object *o = templateObjects.at(i: index); |
246 | if (o) |
247 | return o; |
248 | |
249 | // create the template object |
250 | Scope scope(engine); |
251 | const CompiledData::TemplateObject *t = data->templateObjectAt(idx: index); |
252 | Scoped<ArrayObject> a(scope, engine->newArrayObject(count: t->size)); |
253 | Scoped<ArrayObject> raw(scope, engine->newArrayObject(count: t->size)); |
254 | ScopedValue s(scope); |
255 | for (uint i = 0; i < t->size; ++i) { |
256 | s = runtimeStrings[t->stringIndexAt(i)]; |
257 | a->arraySet(index: i, value: s); |
258 | s = runtimeStrings[t->rawStringIndexAt(i)]; |
259 | raw->arraySet(index: i, value: s); |
260 | } |
261 | |
262 | ObjectPrototype::method_freeze(engine->functionCtor(), thisObject: nullptr, argv: raw, argc: 1); |
263 | a->defineReadonlyProperty(QStringLiteral("raw" ), value: raw); |
264 | ObjectPrototype::method_freeze(engine->functionCtor(), thisObject: nullptr, argv: a, argc: 1); |
265 | |
266 | templateObjects[index] = a->objectValue()->d(); |
267 | return templateObjects.at(i: index); |
268 | } |
269 | |
270 | void ExecutableCompilationUnit::unlink() |
271 | { |
272 | if (engine) |
273 | nextCompilationUnit.remove(); |
274 | |
275 | if (isRegistered) { |
276 | Q_ASSERT(data && propertyCaches.count() > 0 && propertyCaches.at(/*root object*/0)); |
277 | QQmlMetaType::unregisterInternalCompositeType(compilationUnit: this); |
278 | } |
279 | |
280 | propertyCaches.clear(); |
281 | |
282 | if (runtimeLookups) { |
283 | for (uint i = 0; i < data->lookupTableSize; ++i) |
284 | runtimeLookups[i].releasePropertyCache(); |
285 | } |
286 | |
287 | dependentScripts.clear(); |
288 | |
289 | typeNameCache.reset(); |
290 | |
291 | qDeleteAll(c: resolvedTypes); |
292 | resolvedTypes.clear(); |
293 | |
294 | engine = nullptr; |
295 | |
296 | delete [] runtimeLookups; |
297 | runtimeLookups = nullptr; |
298 | |
299 | for (QV4::Function *f : std::as_const(t&: runtimeFunctions)) |
300 | f->destroy(); |
301 | runtimeFunctions.clear(); |
302 | |
303 | free(ptr: runtimeStrings); |
304 | runtimeStrings = nullptr; |
305 | delete [] runtimeRegularExpressions; |
306 | runtimeRegularExpressions = nullptr; |
307 | free(ptr: runtimeClasses); |
308 | runtimeClasses = nullptr; |
309 | } |
310 | |
311 | void ExecutableCompilationUnit::markObjects(QV4::MarkStack *markStack) |
312 | { |
313 | if (runtimeStrings) { |
314 | for (uint i = 0, end = totalStringCount(); i < end; ++i) |
315 | if (runtimeStrings[i]) |
316 | runtimeStrings[i]->mark(markStack); |
317 | } |
318 | if (runtimeRegularExpressions) { |
319 | for (uint i = 0; i < data->regexpTableSize; ++i) |
320 | Value::fromStaticValue(staticValue: runtimeRegularExpressions[i]).mark(markStack); |
321 | } |
322 | if (runtimeClasses) { |
323 | for (uint i = 0; i < data->jsClassTableSize; ++i) |
324 | if (runtimeClasses[i]) |
325 | runtimeClasses[i]->mark(markStack); |
326 | } |
327 | for (QV4::Function *f : std::as_const(t&: runtimeFunctions)) |
328 | if (f && f->internalClass) |
329 | f->internalClass->mark(markStack); |
330 | for (QV4::Heap::InternalClass *c : std::as_const(t&: runtimeBlocks)) |
331 | if (c) |
332 | c->mark(markStack); |
333 | |
334 | for (QV4::Heap::Object *o : std::as_const(t&: templateObjects)) |
335 | if (o) |
336 | o->mark(markStack); |
337 | |
338 | if (runtimeLookups) { |
339 | for (uint i = 0; i < data->lookupTableSize; ++i) |
340 | runtimeLookups[i].markObjects(stack: markStack); |
341 | } |
342 | |
343 | if (auto mod = module()) |
344 | mod->mark(markStack); |
345 | } |
346 | |
347 | IdentifierHash ExecutableCompilationUnit::createNamedObjectsPerComponent(int componentObjectIndex) |
348 | { |
349 | IdentifierHash namedObjectCache(engine); |
350 | const CompiledData::Object *component = objectAt(index: componentObjectIndex); |
351 | const quint32_le *namedObjectIndexPtr = component->namedObjectsInComponentTable(); |
352 | for (quint32 i = 0; i < component->nNamedObjectsInComponent; ++i, ++namedObjectIndexPtr) { |
353 | const CompiledData::Object *namedObject = objectAt(index: *namedObjectIndexPtr); |
354 | namedObjectCache.add(str: runtimeStrings[namedObject->idNameIndex], value: namedObject->objectId()); |
355 | } |
356 | Q_ASSERT(!namedObjectCache.isEmpty()); |
357 | return *namedObjectsPerComponentCache.insert(key: componentObjectIndex, value: namedObjectCache); |
358 | } |
359 | |
360 | template<typename F> |
361 | void processInlinComponentType( |
362 | const QQmlType &type, const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit, |
363 | F &&populateIcData) |
364 | { |
365 | if (type.isInlineComponentType()) { |
366 | QString icRootName; |
367 | if (compilationUnit->icRootName) { |
368 | icRootName = type.elementName(); |
369 | std::swap(a&: *compilationUnit->icRootName, b&: icRootName); |
370 | } else { |
371 | compilationUnit->icRootName = std::make_unique<QString>(args: type.elementName()); |
372 | } |
373 | |
374 | populateIcData(); |
375 | |
376 | if (icRootName.isEmpty()) |
377 | compilationUnit->icRootName.reset(); |
378 | else |
379 | std::swap(a&: *compilationUnit->icRootName, b&: icRootName); |
380 | } else { |
381 | populateIcData(); |
382 | } |
383 | } |
384 | |
385 | void ExecutableCompilationUnit::finalizeCompositeType(CompositeMetaTypeIds types) |
386 | { |
387 | // Add to type registry of composites |
388 | if (propertyCaches.needsVMEMetaObject(/*root object*/index: 0)) { |
389 | // typeIds is only valid for types that have references to themselves. |
390 | if (!types.isValid()) |
391 | types = CompositeMetaTypeIds::fromCompositeName(name: rootPropertyCache()->className()); |
392 | typeIds = types; |
393 | QQmlMetaType::registerInternalCompositeType(compilationUnit: this); |
394 | |
395 | } else { |
396 | const QV4::CompiledData::Object *obj = objectAt(/*root object*/index: 0); |
397 | auto *typeRef = resolvedTypes.value(key: obj->inheritedTypeNameIndex); |
398 | Q_ASSERT(typeRef); |
399 | if (const auto compilationUnit = typeRef->compilationUnit()) { |
400 | typeIds = compilationUnit->typeIds; |
401 | } else { |
402 | const auto type = typeRef->type(); |
403 | typeIds = CompositeMetaTypeIds{ type.typeId(), type.qListTypeId() }; |
404 | } |
405 | } |
406 | |
407 | // Collect some data for instantiation later. |
408 | using namespace icutils; |
409 | std::vector<QV4::CompiledData::InlineComponent> allICs {}; |
410 | for (int i=0; i != objectCount(); ++i) { |
411 | const CompiledObject *obj = objectAt(index: i); |
412 | for (auto it = obj->inlineComponentsBegin(); it != obj->inlineComponentsEnd(); ++it) { |
413 | allICs.push_back(x: *it); |
414 | } |
415 | } |
416 | NodeList nodes; |
417 | nodes.resize(new_size: allICs.size()); |
418 | std::iota(first: nodes.begin(), last: nodes.end(), value: 0); |
419 | AdjacencyList adjacencyList; |
420 | adjacencyList.resize(new_size: nodes.size()); |
421 | fillAdjacencyListForInlineComponents(objectContainer: this, adjacencyList, nodes, allICs); |
422 | bool hasCycle = false; |
423 | auto nodesSorted = topoSort(nodes, adjacencyList, hasCycle); |
424 | Q_ASSERT(!hasCycle); // would have already been discovered by qqmlpropertycachcecreator |
425 | |
426 | // We need to first iterate over all inline components, as the containing component might create instances of them |
427 | // and in that case we need to add its object count |
428 | for (auto nodeIt = nodesSorted.rbegin(); nodeIt != nodesSorted.rend(); ++nodeIt) { |
429 | const auto &ic = allICs.at(n: nodeIt->index()); |
430 | const int lastICRoot = ic.objectIndex; |
431 | for (int i = ic.objectIndex; i<objectCount(); ++i) { |
432 | const QV4::CompiledData::Object *obj = objectAt(index: i); |
433 | bool leftCurrentInlineComponent |
434 | = (i != lastICRoot |
435 | && obj->hasFlag(flag: QV4::CompiledData::Object::IsInlineComponentRoot)) |
436 | || !obj->hasFlag(flag: QV4::CompiledData::Object::IsPartOfInlineComponent); |
437 | if (leftCurrentInlineComponent) |
438 | break; |
439 | const QString lastICRootName = stringAt(index: ic.nameIndex); |
440 | inlineComponentData[lastICRootName].totalBindingCount += obj->nBindings; |
441 | |
442 | if (auto *typeRef = resolvedTypes.value(key: obj->inheritedTypeNameIndex)) { |
443 | const auto type = typeRef->type(); |
444 | if (type.isValid() && type.parserStatusCast() != -1) |
445 | ++inlineComponentData[lastICRootName].totalParserStatusCount; |
446 | |
447 | ++inlineComponentData[lastICRootName].totalObjectCount; |
448 | if (const auto compilationUnit = typeRef->compilationUnit()) { |
449 | // if the type is an inline component type, we have to extract the information from it |
450 | // This requires that inline components are visited in the correct order |
451 | processInlinComponentType(type, compilationUnit, populateIcData: [&]() { |
452 | auto &icData = inlineComponentData[lastICRootName]; |
453 | icData.totalBindingCount += compilationUnit->totalBindingsCount(); |
454 | icData.totalParserStatusCount += compilationUnit->totalParserStatusCount(); |
455 | icData.totalObjectCount += compilationUnit->totalObjectCount(); |
456 | }); |
457 | } |
458 | } |
459 | } |
460 | } |
461 | int bindingCount = 0; |
462 | int parserStatusCount = 0; |
463 | int objectCount = 0; |
464 | for (quint32 i = 0, count = this->objectCount(); i < count; ++i) { |
465 | const QV4::CompiledData::Object *obj = objectAt(index: i); |
466 | if (obj->hasFlag(flag: QV4::CompiledData::Object::IsPartOfInlineComponent)) |
467 | continue; |
468 | |
469 | bindingCount += obj->nBindings; |
470 | if (auto *typeRef = resolvedTypes.value(key: obj->inheritedTypeNameIndex)) { |
471 | const auto type = typeRef->type(); |
472 | if (type.isValid() && type.parserStatusCast() != -1) |
473 | ++parserStatusCount; |
474 | ++objectCount; |
475 | if (const auto compilationUnit = typeRef->compilationUnit()) { |
476 | processInlinComponentType(type, compilationUnit, populateIcData: [&](){ |
477 | bindingCount += compilationUnit->totalBindingsCount(); |
478 | parserStatusCount += compilationUnit->totalParserStatusCount(); |
479 | objectCount += compilationUnit->totalObjectCount(); |
480 | }); |
481 | } |
482 | } |
483 | } |
484 | |
485 | m_totalBindingsCount = bindingCount; |
486 | m_totalParserStatusCount = parserStatusCount; |
487 | m_totalObjectCount = objectCount; |
488 | } |
489 | |
490 | int ExecutableCompilationUnit::totalBindingsCount() const { |
491 | if (!icRootName) |
492 | return m_totalBindingsCount; |
493 | return inlineComponentData[*icRootName].totalBindingCount; |
494 | } |
495 | |
496 | int ExecutableCompilationUnit::totalObjectCount() const { |
497 | if (!icRootName) |
498 | return m_totalObjectCount; |
499 | return inlineComponentData[*icRootName].totalObjectCount; |
500 | } |
501 | |
502 | int ExecutableCompilationUnit::totalParserStatusCount() const { |
503 | if (!icRootName) |
504 | return m_totalParserStatusCount; |
505 | return inlineComponentData[*icRootName].totalParserStatusCount; |
506 | } |
507 | |
508 | bool ExecutableCompilationUnit::verifyChecksum(const CompiledData::DependentTypesHasher &dependencyHasher) const |
509 | { |
510 | if (!dependencyHasher) { |
511 | for (size_t i = 0; i < sizeof(data->dependencyMD5Checksum); ++i) { |
512 | if (data->dependencyMD5Checksum[i] != 0) |
513 | return false; |
514 | } |
515 | return true; |
516 | } |
517 | const QByteArray checksum = dependencyHasher(); |
518 | return checksum.size() == sizeof(data->dependencyMD5Checksum) |
519 | && memcmp(s1: data->dependencyMD5Checksum, s2: checksum.constData(), |
520 | n: sizeof(data->dependencyMD5Checksum)) == 0; |
521 | } |
522 | |
523 | CompositeMetaTypeIds ExecutableCompilationUnit::typeIdsForComponent( |
524 | const QString &inlineComponentName) const |
525 | { |
526 | if (inlineComponentName.isEmpty()) |
527 | return typeIds; |
528 | return inlineComponentData[inlineComponentName].typeIds; |
529 | } |
530 | |
531 | QStringList ExecutableCompilationUnit::moduleRequests() const |
532 | { |
533 | QStringList requests; |
534 | requests.reserve(asize: data->moduleRequestTableSize); |
535 | for (uint i = 0; i < data->moduleRequestTableSize; ++i) |
536 | requests << stringAt(index: data->moduleRequestTable()[i]); |
537 | return requests; |
538 | } |
539 | |
540 | Heap::Module *ExecutableCompilationUnit::instantiate(ExecutionEngine *engine) |
541 | { |
542 | if (isESModule() && module()) |
543 | return module(); |
544 | |
545 | if (data->indexOfRootFunction < 0) |
546 | return nullptr; |
547 | |
548 | if (!this->engine) |
549 | linkToEngine(engine); |
550 | |
551 | Scope scope(engine); |
552 | Scoped<Module> module(scope, engine->memoryManager->allocate<Module>(args&: engine, args: this)); |
553 | |
554 | if (isESModule()) |
555 | setModule(module->d()); |
556 | |
557 | for (const QString &request: moduleRequests()) { |
558 | const QUrl url(request); |
559 | const auto dependentModuleUnit = engine->loadModule(url: url, referrer: this); |
560 | if (engine->hasException) |
561 | return nullptr; |
562 | if (dependentModuleUnit.compiled) |
563 | dependentModuleUnit.compiled->instantiate(engine); |
564 | } |
565 | |
566 | ScopedString importName(scope); |
567 | |
568 | const uint importCount = data->importEntryTableSize; |
569 | if (importCount > 0) { |
570 | imports = new const StaticValue *[importCount]; |
571 | memset(s: imports, c: 0, n: importCount * sizeof(StaticValue *)); |
572 | } |
573 | for (uint i = 0; i < importCount; ++i) { |
574 | const CompiledData::ImportEntry &entry = data->importEntryTable()[i]; |
575 | QUrl url = urlAt(index: entry.moduleRequest); |
576 | importName = runtimeStrings[entry.importName]; |
577 | |
578 | const auto module = engine->loadModule(url: url, referrer: this); |
579 | if (module.compiled) { |
580 | const Value *valuePtr = module.compiled->resolveExport(exportName: importName); |
581 | if (!valuePtr) { |
582 | QString referenceErrorMessage = QStringLiteral("Unable to resolve import reference " ); |
583 | referenceErrorMessage += importName->toQString(); |
584 | engine->throwReferenceError( |
585 | value: referenceErrorMessage, fileName: fileName(), |
586 | lineNumber: entry.location.line(), column: entry.location.column()); |
587 | return nullptr; |
588 | } |
589 | imports[i] = valuePtr; |
590 | } else if (Value *value = module.native) { |
591 | const QString name = importName->toQString(); |
592 | if (value->isNullOrUndefined()) { |
593 | QString errorMessage = name; |
594 | errorMessage += QStringLiteral(" from " ); |
595 | errorMessage += url.toString(); |
596 | errorMessage += QStringLiteral(" is null" ); |
597 | engine->throwError(message: errorMessage); |
598 | return nullptr; |
599 | } |
600 | |
601 | if (name == QStringLiteral("default" )) { |
602 | imports[i] = value; |
603 | } else { |
604 | url.setFragment(fragment: name); |
605 | const auto fragment = engine->moduleForUrl(url: url, referrer: this); |
606 | if (fragment.native) { |
607 | imports[i] = fragment.native; |
608 | } else { |
609 | Scope scope(this->engine); |
610 | ScopedObject o(scope, value); |
611 | if (!o) { |
612 | QString referenceErrorMessage = QStringLiteral("Unable to resolve import reference " ); |
613 | referenceErrorMessage += name; |
614 | referenceErrorMessage += QStringLiteral(" because " ); |
615 | referenceErrorMessage += url.toString(options: QUrl::RemoveFragment); |
616 | referenceErrorMessage += QStringLiteral(" is not an object" ); |
617 | engine->throwReferenceError( |
618 | value: referenceErrorMessage, fileName: fileName(), |
619 | lineNumber: entry.location.line(), column: entry.location.column()); |
620 | return nullptr; |
621 | } |
622 | |
623 | const ScopedPropertyKey key(scope, scope.engine->identifierTable->asPropertyKey(s: name)); |
624 | const ScopedValue result(scope, o->get(id: key)); |
625 | imports[i] = engine->registerNativeModule(url, module: result); |
626 | } |
627 | } |
628 | } |
629 | } |
630 | |
631 | const auto throwReferenceError = [&](const CompiledData::ExportEntry &entry, const QString &importName) { |
632 | QString referenceErrorMessage = QStringLiteral("Unable to resolve re-export reference " ); |
633 | referenceErrorMessage += importName; |
634 | engine->throwReferenceError( |
635 | value: referenceErrorMessage, fileName: fileName(), |
636 | lineNumber: entry.location.line(), column: entry.location.column()); |
637 | }; |
638 | |
639 | for (uint i = 0; i < data->indirectExportEntryTableSize; ++i) { |
640 | const CompiledData::ExportEntry &entry = data->indirectExportEntryTable()[i]; |
641 | auto dependentModule = engine->loadModule(url: urlAt(index: entry.moduleRequest), referrer: this); |
642 | ScopedString importName(scope, runtimeStrings[entry.importName]); |
643 | if (const auto dependentModuleUnit = dependentModule.compiled) { |
644 | if (!dependentModuleUnit->resolveExport(exportName: importName)) { |
645 | throwReferenceError(entry, importName->toQString()); |
646 | return nullptr; |
647 | } |
648 | } else if (const auto native = dependentModule.native) { |
649 | ScopedObject o(scope, native); |
650 | const ScopedPropertyKey key(scope, scope.engine->identifierTable->asPropertyKey(str: importName)); |
651 | const ScopedValue result(scope, o->get(id: key)); |
652 | if (result->isUndefined()) { |
653 | throwReferenceError(entry, importName->toQString()); |
654 | return nullptr; |
655 | } |
656 | } |
657 | } |
658 | |
659 | return module->d(); |
660 | } |
661 | |
662 | const Value *ExecutableCompilationUnit::resolveExportRecursively( |
663 | QV4::String *exportName, QVector<ResolveSetEntry> *resolveSet) |
664 | { |
665 | if (!module()) |
666 | return nullptr; |
667 | |
668 | for (const auto &entry: *resolveSet) |
669 | if (entry.module == this && entry.exportName->isEqualTo(other: exportName)) |
670 | return nullptr; |
671 | |
672 | (*resolveSet) << ResolveSetEntry(this, exportName); |
673 | |
674 | if (exportName->toQString() == QLatin1String("*" )) |
675 | return &module()->self; |
676 | |
677 | Scope scope(engine); |
678 | |
679 | if (auto localExport = lookupNameInExportTable( |
680 | firstExportEntry: data->localExportEntryTable(), tableSize: data->localExportEntryTableSize, name: exportName)) { |
681 | ScopedString localName(scope, runtimeStrings[localExport->localName]); |
682 | uint index = module()->scope->internalClass->indexOfValueOrGetter(id: localName->toPropertyKey()); |
683 | if (index == UINT_MAX) |
684 | return nullptr; |
685 | if (index >= module()->scope->locals.size) |
686 | return &(imports[index - module()->scope->locals.size]->asValue<Value>()); |
687 | return &module()->scope->locals[index]; |
688 | } |
689 | |
690 | if (auto indirectExport = lookupNameInExportTable( |
691 | firstExportEntry: data->indirectExportEntryTable(), tableSize: data->indirectExportEntryTableSize, name: exportName)) { |
692 | QUrl request = urlAt(index: indirectExport->moduleRequest); |
693 | auto dependentModule = engine->loadModule(url: request, referrer: this); |
694 | ScopedString importName(scope, runtimeStrings[indirectExport->importName]); |
695 | if (dependentModule.compiled) { |
696 | return dependentModule.compiled->resolveExportRecursively(exportName: importName, resolveSet); |
697 | } else if (dependentModule.native) { |
698 | if (exportName->toQString() == QLatin1String("*" )) |
699 | return dependentModule.native; |
700 | if (exportName->toQString() == QLatin1String("default" )) |
701 | return nullptr; |
702 | |
703 | request.setFragment(fragment: importName->toQString()); |
704 | const auto fragment = engine->moduleForUrl(url: request); |
705 | if (fragment.native) |
706 | return fragment.native; |
707 | |
708 | ScopedObject o(scope, dependentModule.native); |
709 | if (o) |
710 | return engine->registerNativeModule(url: request, module: o->get(name: importName)); |
711 | |
712 | return nullptr; |
713 | } else { |
714 | return nullptr; |
715 | } |
716 | } |
717 | |
718 | if (exportName->toQString() == QLatin1String("default" )) |
719 | return nullptr; |
720 | |
721 | const Value *starResolution = nullptr; |
722 | |
723 | for (uint i = 0; i < data->starExportEntryTableSize; ++i) { |
724 | const CompiledData::ExportEntry &entry = data->starExportEntryTable()[i]; |
725 | QUrl request = urlAt(index: entry.moduleRequest); |
726 | auto dependentModule = engine->loadModule(url: request, referrer: this); |
727 | const Value *resolution = nullptr; |
728 | if (dependentModule.compiled) { |
729 | resolution = dependentModule.compiled->resolveExportRecursively( |
730 | exportName, resolveSet); |
731 | } else if (dependentModule.native) { |
732 | if (exportName->toQString() == QLatin1String("*" )) { |
733 | resolution = dependentModule.native; |
734 | } else if (exportName->toQString() != QLatin1String("default" )) { |
735 | request.setFragment(fragment: exportName->toQString()); |
736 | const auto fragment = engine->moduleForUrl(url: request); |
737 | if (fragment.native) { |
738 | resolution = fragment.native; |
739 | } else { |
740 | ScopedObject o(scope, dependentModule.native); |
741 | if (o) |
742 | resolution = engine->registerNativeModule(url: request, module: o->get(name: exportName)); |
743 | } |
744 | } |
745 | } |
746 | |
747 | // ### handle ambiguous |
748 | if (resolution) { |
749 | if (!starResolution) { |
750 | starResolution = resolution; |
751 | continue; |
752 | } |
753 | if (resolution != starResolution) |
754 | return nullptr; |
755 | } |
756 | } |
757 | |
758 | return starResolution; |
759 | } |
760 | |
761 | const CompiledData::ExportEntry *ExecutableCompilationUnit::lookupNameInExportTable( |
762 | const CompiledData::ExportEntry *firstExportEntry, int tableSize, QV4::String *name) const |
763 | { |
764 | const CompiledData::ExportEntry *lastExportEntry = firstExportEntry + tableSize; |
765 | auto matchingExport = std::lower_bound(first: firstExportEntry, last: lastExportEntry, val: name, comp: [this](const CompiledData::ExportEntry &lhs, QV4::String *name) { |
766 | return stringAt(index: lhs.exportName) < name->toQString(); |
767 | }); |
768 | if (matchingExport == lastExportEntry || stringAt(index: matchingExport->exportName) != name->toQString()) |
769 | return nullptr; |
770 | return matchingExport; |
771 | } |
772 | |
773 | void ExecutableCompilationUnit::getExportedNamesRecursively( |
774 | QStringList *names, QVector<const ExecutableCompilationUnit*> *exportNameSet, |
775 | bool includeDefaultExport) const |
776 | { |
777 | if (exportNameSet->contains(t: this)) |
778 | return; |
779 | exportNameSet->append(t: this); |
780 | |
781 | const auto append = [names, includeDefaultExport](const QString &name) { |
782 | if (!includeDefaultExport && name == QLatin1String("default" )) |
783 | return; |
784 | names->append(t: name); |
785 | }; |
786 | |
787 | for (uint i = 0; i < data->localExportEntryTableSize; ++i) { |
788 | const CompiledData::ExportEntry &entry = data->localExportEntryTable()[i]; |
789 | append(stringAt(index: entry.exportName)); |
790 | } |
791 | |
792 | for (uint i = 0; i < data->indirectExportEntryTableSize; ++i) { |
793 | const CompiledData::ExportEntry &entry = data->indirectExportEntryTable()[i]; |
794 | append(stringAt(index: entry.exportName)); |
795 | } |
796 | |
797 | for (uint i = 0; i < data->starExportEntryTableSize; ++i) { |
798 | const CompiledData::ExportEntry &entry = data->starExportEntryTable()[i]; |
799 | auto dependentModule = engine->loadModule(url: urlAt(index: entry.moduleRequest), referrer: this); |
800 | if (dependentModule.compiled) { |
801 | dependentModule.compiled->getExportedNamesRecursively( |
802 | names, exportNameSet, /*includeDefaultExport*/false); |
803 | } else if (dependentModule.native) { |
804 | Scope scope(engine); |
805 | ScopedObject o(scope, dependentModule.native); |
806 | ObjectIterator iterator(scope, o, ObjectIterator::EnumerableOnly); |
807 | while (true) { |
808 | ScopedValue val(scope, iterator.nextPropertyNameAsString()); |
809 | if (val->isNull()) |
810 | break; |
811 | append(val->toQString()); |
812 | } |
813 | } |
814 | } |
815 | } |
816 | |
817 | void ExecutableCompilationUnit::evaluate() |
818 | { |
819 | QV4::Scope scope(engine); |
820 | QV4::Scoped<Module> mod(scope, module()); |
821 | mod->evaluate(); |
822 | } |
823 | |
824 | void ExecutableCompilationUnit::evaluateModuleRequests() |
825 | { |
826 | for (const QString &request: moduleRequests()) { |
827 | auto dependentModule = engine->loadModule(url: QUrl(request), referrer: this); |
828 | if (dependentModule.native) |
829 | continue; |
830 | |
831 | if (engine->hasException) |
832 | return; |
833 | |
834 | Q_ASSERT(dependentModule.compiled); |
835 | dependentModule.compiled->evaluate(); |
836 | if (engine->hasException) |
837 | return; |
838 | } |
839 | } |
840 | |
841 | bool ExecutableCompilationUnit::loadFromDisk(const QUrl &url, const QDateTime &sourceTimeStamp, QString *errorString) |
842 | { |
843 | if (!QQmlFile::isLocalFile(url)) { |
844 | *errorString = QStringLiteral("File has to be a local file." ); |
845 | return false; |
846 | } |
847 | |
848 | const QString sourcePath = QQmlFile::urlToLocalFileOrQrc(url); |
849 | auto cacheFile = std::make_unique<CompilationUnitMapper>(); |
850 | |
851 | const QStringList cachePaths = { sourcePath + QLatin1Char('c'), localCacheFilePath(url) }; |
852 | for (const QString &cachePath : cachePaths) { |
853 | CompiledData::Unit *mappedUnit = cacheFile->get(cacheFilePath: cachePath, sourceTimeStamp, errorString); |
854 | if (!mappedUnit) |
855 | continue; |
856 | |
857 | const CompiledData::Unit * const oldDataPtr |
858 | = (data && !(data->flags & QV4::CompiledData::Unit::StaticData)) ? data |
859 | : nullptr; |
860 | const CompiledData::Unit *oldData = data; |
861 | auto dataPtrRevert = qScopeGuard(f: [this, oldData](){ |
862 | setUnitData(unitData: oldData); |
863 | }); |
864 | setUnitData(unitData: mappedUnit); |
865 | |
866 | if (data->sourceFileIndex != 0) { |
867 | if (data->sourceFileIndex >= data->stringTableSize + dynamicStrings.size()) { |
868 | *errorString = QStringLiteral("QML source file index is invalid." ); |
869 | continue; |
870 | } |
871 | if (sourcePath != QQmlFile::urlToLocalFileOrQrc(stringAt(index: data->sourceFileIndex))) { |
872 | *errorString = QStringLiteral("QML source file has moved to a different location." ); |
873 | continue; |
874 | } |
875 | } |
876 | |
877 | dataPtrRevert.dismiss(); |
878 | free(ptr: const_cast<CompiledData::Unit*>(oldDataPtr)); |
879 | backingFile = std::move(cacheFile); |
880 | return true; |
881 | } |
882 | |
883 | return false; |
884 | } |
885 | |
886 | bool ExecutableCompilationUnit::saveToDisk(const QUrl &unitUrl, QString *errorString) |
887 | { |
888 | if (data->sourceTimeStamp == 0) { |
889 | *errorString = QStringLiteral("Missing time stamp for source file" ); |
890 | return false; |
891 | } |
892 | |
893 | if (!QQmlFile::isLocalFile(url: unitUrl)) { |
894 | *errorString = QStringLiteral("File has to be a local file." ); |
895 | return false; |
896 | } |
897 | |
898 | return CompiledData::SaveableUnitPointer(unitData()).saveToDisk<char>( |
899 | writer: [&unitUrl, errorString](const char *data, quint32 size) { |
900 | const QString cachePath = localCacheFilePath(url: unitUrl); |
901 | if (CompiledData::SaveableUnitPointer::writeDataToFile( |
902 | outputFileName: cachePath, data, size, errorString)) { |
903 | CompilationUnitMapper::invalidate(cacheFilePath: cachePath); |
904 | return true; |
905 | } |
906 | |
907 | return false; |
908 | }); |
909 | } |
910 | |
911 | /*! |
912 | \internal |
913 | This function creates a temporary key vector and sorts it to guarantuee a stable |
914 | hash. This is used to calculate a check-sum on dependent meta-objects. |
915 | */ |
916 | bool ResolvedTypeReferenceMap::addToHash( |
917 | QCryptographicHash *hash, QHash<quintptr, QByteArray> *checksums) const |
918 | { |
919 | std::vector<int> keys (size()); |
920 | int i = 0; |
921 | for (auto it = constBegin(), end = constEnd(); it != end; ++it) { |
922 | keys[i] = it.key(); |
923 | ++i; |
924 | } |
925 | std::sort(first: keys.begin(), last: keys.end()); |
926 | for (int key: keys) { |
927 | if (!this->operator[](key)->addToHash(hash, checksums)) |
928 | return false; |
929 | } |
930 | |
931 | return true; |
932 | } |
933 | |
934 | QString ExecutableCompilationUnit::bindingValueAsString(const CompiledData::Binding *binding) const |
935 | { |
936 | #if QT_CONFIG(translation) |
937 | using namespace CompiledData; |
938 | bool byId = false; |
939 | switch (binding->type()) { |
940 | case Binding::Type_TranslationById: |
941 | byId = true; |
942 | Q_FALLTHROUGH(); |
943 | case Binding::Type_Translation: { |
944 | return translateFrom(index: { .index: binding->value.translationDataIndex, .byId: byId }); |
945 | } |
946 | default: |
947 | break; |
948 | } |
949 | #endif |
950 | return CompilationUnit::bindingValueAsString(binding); |
951 | } |
952 | |
953 | QString ExecutableCompilationUnit::translateFrom(TranslationDataIndex index) const |
954 | { |
955 | #if !QT_CONFIG(translation) |
956 | return QString(); |
957 | #else |
958 | const CompiledData::TranslationData &translation = data->translations()[index.index]; |
959 | |
960 | if (index.byId) { |
961 | QByteArray id = stringAt(index: translation.stringIndex).toUtf8(); |
962 | return qtTrId(id: id.constData(), n: translation.number); |
963 | } |
964 | |
965 | const auto fileContext = [this]() { |
966 | // This code must match that in the qsTr() implementation |
967 | const QString &path = fileName(); |
968 | int lastSlash = path.lastIndexOf(c: QLatin1Char('/')); |
969 | |
970 | QStringView context = (lastSlash > -1) |
971 | ? QStringView{ path }.mid(pos: lastSlash + 1, n: path.size() - lastSlash - 5) |
972 | : QStringView(); |
973 | return context.toUtf8(); |
974 | }; |
975 | |
976 | QByteArray context = stringAt(index: translation.contextIndex).toUtf8(); |
977 | QByteArray = stringAt(index: translation.commentIndex).toUtf8(); |
978 | QByteArray text = stringAt(index: translation.stringIndex).toUtf8(); |
979 | return QCoreApplication::translate( |
980 | context: context.isEmpty() ? fileContext() : context, key: text, disambiguation: comment, n: translation.number); |
981 | #endif |
982 | } |
983 | |
984 | bool ExecutableCompilationUnit::( |
985 | const CompiledData::Unit *unit, QDateTime expectedSourceTimeStamp, QString *errorString) |
986 | { |
987 | if (strncmp(s1: unit->magic, s2: CompiledData::magic_str, n: sizeof(unit->magic))) { |
988 | *errorString = QStringLiteral("Magic bytes in the header do not match" ); |
989 | return false; |
990 | } |
991 | |
992 | if (unit->version != quint32(QV4_DATA_STRUCTURE_VERSION)) { |
993 | *errorString = QString::fromUtf8(utf8: "V4 data structure version mismatch. Found %1 expected %2" ) |
994 | .arg(a: unit->version, fieldWidth: 0, base: 16).arg(QV4_DATA_STRUCTURE_VERSION, fieldWidth: 0, base: 16); |
995 | return false; |
996 | } |
997 | |
998 | if (unit->qtVersion != quint32(QT_VERSION)) { |
999 | *errorString = QString::fromUtf8(utf8: "Qt version mismatch. Found %1 expected %2" ) |
1000 | .arg(a: unit->qtVersion, fieldWidth: 0, base: 16).arg(QT_VERSION, fieldWidth: 0, base: 16); |
1001 | return false; |
1002 | } |
1003 | |
1004 | if (unit->sourceTimeStamp) { |
1005 | // Files from the resource system do not have any time stamps, so fall back to the application |
1006 | // executable. |
1007 | if (!expectedSourceTimeStamp.isValid()) |
1008 | expectedSourceTimeStamp = QFileInfo(QCoreApplication::applicationFilePath()).lastModified(); |
1009 | |
1010 | if (expectedSourceTimeStamp.isValid() |
1011 | && expectedSourceTimeStamp.toMSecsSinceEpoch() != unit->sourceTimeStamp) { |
1012 | *errorString = QStringLiteral("QML source file has a different time stamp than cached file." ); |
1013 | return false; |
1014 | } |
1015 | } |
1016 | |
1017 | #if defined(QML_COMPILE_HASH) && defined(QML_COMPILE_HASH_LENGTH) && QML_COMPILE_HASH_LENGTH > 0 |
1018 | if (qstrncmp(str1: qml_compile_hash, str2: unit->libraryVersionHash, QML_COMPILE_HASH_LENGTH) != 0) { |
1019 | *errorString = QStringLiteral("QML compile hashes don't match. Found %1 expected %2" ) |
1020 | .arg(args: QString::fromLatin1( |
1021 | ba: QByteArray(unit->libraryVersionHash, QML_COMPILE_HASH_LENGTH) |
1022 | .toPercentEncoding()), |
1023 | args: QString::fromLatin1( |
1024 | ba: QByteArray(qml_compile_hash, QML_COMPILE_HASH_LENGTH) |
1025 | .toPercentEncoding())); |
1026 | return false; |
1027 | } |
1028 | #else |
1029 | #error "QML_COMPILE_HASH must be defined for the build of QtDeclarative to ensure version checking for cache files" |
1030 | #endif |
1031 | return true; |
1032 | } |
1033 | |
1034 | } // namespace QV4 |
1035 | |
1036 | QT_END_NAMESPACE |
1037 | |