/**************************************************************************** ** ** Copyright (C) 2019 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the tools applications of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qml/qqmlprivate.h" #include "qv4engine_p.h" #include "qv4executablecompilationunit_p.h" #include "qv4stackframe_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(QML_COMPILE_HASH) # 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[48 + 1] = QML_COMPILE_HASH; static_assert(sizeof(QV4::CompiledData::Unit::libraryVersionHash) >= QML_COMPILE_HASH_LENGTH + 1, "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 { ExecutableCompilationUnit::ExecutableCompilationUnit() = default; ExecutableCompilationUnit::ExecutableCompilationUnit( CompiledData::CompilationUnit &&compilationUnit) : CompiledData::CompilationUnit(std::move(compilationUnit)) {} ExecutableCompilationUnit::~ExecutableCompilationUnit() { unlink(); } QString ExecutableCompilationUnit::localCacheFilePath(const QUrl &url) { static const QByteArray envCachePath = qgetenv("QML_DISK_CACHE_PATH"); const QString localSourcePath = QQmlFile::urlToLocalFileOrQrc(url); const QString cacheFileSuffix = QFileInfo(localSourcePath + QLatin1Char('c')).completeSuffix(); QCryptographicHash fileNameHash(QCryptographicHash::Sha1); fileNameHash.addData(localSourcePath.toUtf8()); QString directory = envCachePath.isEmpty() ? QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + QLatin1String("/qmlcache/") : QString::fromLocal8Bit(envCachePath) + QLatin1String("/"); QDir::root().mkpath(directory); return directory + QString::fromUtf8(fileNameHash.result().toHex()) + QLatin1Char('.') + cacheFileSuffix; } static QString toString(QV4::ReturnedValue v) { Value val = Value::fromReturnedValue(v); QString result; if (val.isInt32()) result = QLatin1String("int "); else if (val.isDouble()) result = QLatin1String("double "); if (val.isEmpty()) result += QLatin1String("empty"); else result += val.toQStringNoThrow(); return result; } static void dumpConstantTable(const StaticValue *constants, uint count) { QDebug d = qDebug(); d.nospace() << Qt::right; for (uint i = 0; i < count; ++i) { d << qSetFieldWidth(8) << i << qSetFieldWidth(0) << ": " << toString(constants[i].asReturnedValue()).toUtf8().constData() << "\n"; } } QV4::Function *ExecutableCompilationUnit::linkToEngine(ExecutionEngine *engine) { this->engine = engine; engine->compilationUnits.insert(this); Q_ASSERT(!runtimeStrings); Q_ASSERT(data); const quint32 stringCount = totalStringCount(); runtimeStrings = (QV4::Heap::String **)malloc(stringCount * sizeof(QV4::Heap::String*)); // memset the strings to 0 in case a GC run happens while we're within the loop below memset(runtimeStrings, 0, stringCount * sizeof(QV4::Heap::String*)); for (uint i = 0; i < stringCount; ++i) runtimeStrings[i] = engine->newString(stringAt(i)); runtimeRegularExpressions = new QV4::Value[data->regexpTableSize]; // memset the regexps to 0 in case a GC run happens while we're within the loop below memset(runtimeRegularExpressions, 0, data->regexpTableSize * sizeof(QV4::Value)); for (uint i = 0; i < data->regexpTableSize; ++i) { const CompiledData::RegExp *re = data->regexpAt(i); uint f = re->flags; const CompiledData::RegExp::Flags flags = static_cast(f); runtimeRegularExpressions[i] = QV4::RegExp::create( engine, stringAt(re->stringIndex), flags); } if (data->lookupTableSize) { runtimeLookups = new QV4::Lookup[data->lookupTableSize]; memset(runtimeLookups, 0, data->lookupTableSize * sizeof(QV4::Lookup)); const CompiledData::Lookup *compiledLookups = data->lookupTable(); for (uint i = 0; i < data->lookupTableSize; ++i) { QV4::Lookup *l = runtimeLookups + i; CompiledData::Lookup::Type type = CompiledData::Lookup::Type(uint(compiledLookups[i].type_and_flags)); if (type == CompiledData::Lookup::Type_Getter) l->getter = QV4::Lookup::getterGeneric; else if (type == CompiledData::Lookup::Type_Setter) l->setter = QV4::Lookup::setterGeneric; else if (type == CompiledData::Lookup::Type_GlobalGetter) l->globalGetter = QV4::Lookup::globalGetterGeneric; else if (type == CompiledData::Lookup::Type_QmlContextPropertyGetter) l->qmlContextPropertyGetter = QQmlContextWrapper::resolveQmlContextPropertyLookupGetter; l->nameIndex = compiledLookups[i].nameIndex; } } if (data->jsClassTableSize) { runtimeClasses = (QV4::Heap::InternalClass **)malloc(data->jsClassTableSize * sizeof(QV4::Heap::InternalClass *)); // memset the regexps to 0 in case a GC run happens while we're within the loop below memset(runtimeClasses, 0, data->jsClassTableSize * sizeof(QV4::Heap::InternalClass *)); for (uint i = 0; i < data->jsClassTableSize; ++i) { int memberCount = 0; const CompiledData::JSClassMember *member = data->jsClassAt(i, &memberCount); runtimeClasses[i] = engine->internalClasses(QV4::ExecutionEngine::Class_Object); for (int j = 0; j < memberCount; ++j, ++member) runtimeClasses[i] = runtimeClasses[i]->addMember( engine->identifierTable->asPropertyKey( runtimeStrings[member->nameOffset]), member->isAccessor ? QV4::Attr_Accessor : QV4::Attr_Data); } } runtimeFunctions.resize(data->functionTableSize); static bool forceInterpreter = qEnvironmentVariableIsSet("QV4_FORCE_INTERPRETER"); const QQmlPrivate::AOTCompiledFunction *aotFunction = forceInterpreter ? nullptr : aotCompiledFunctions; auto advanceAotFunction = [&](int i) -> const QQmlPrivate::AOTCompiledFunction * { if (aotFunction) { if (aotFunction->functionPtr) { if (aotFunction->index == i) return aotFunction++; } else { aotFunction = nullptr; } } return nullptr; }; for (int i = 0 ;i < runtimeFunctions.size(); ++i) { const QV4::CompiledData::Function *compiledFunction = data->functionAt(i); runtimeFunctions[i] = QV4::Function::create(engine, this, compiledFunction, advanceAotFunction(i)); } Scope scope(engine); Scoped ic(scope); runtimeBlocks.resize(data->blockTableSize); for (int i = 0 ;i < runtimeBlocks.size(); ++i) { const QV4::CompiledData::Block *compiledBlock = data->blockAt(i); ic = engine->internalClasses(EngineBase::Class_CallContext); // first locals const quint32_le *localsIndices = compiledBlock->localsTable(); for (quint32 j = 0; j < compiledBlock->nLocals; ++j) ic = ic->addMember( engine->identifierTable->asPropertyKey(runtimeStrings[localsIndices[j]]), Attr_NotConfigurable); runtimeBlocks[i] = ic->d(); } static const bool showCode = qEnvironmentVariableIsSet("QV4_SHOW_BYTECODE"); if (showCode) { qDebug() << "=== Constant table"; dumpConstantTable(constants, data->constantTableSize); qDebug() << "=== String table"; for (uint i = 0, end = totalStringCount(); i < end; ++i) qDebug() << " " << i << ":" << runtimeStrings[i]->toQString(); qDebug() << "=== Closure table"; for (uint i = 0; i < data->functionTableSize; ++i) qDebug() << " " << i << ":" << runtimeFunctions[i]->name()->toQString(); qDebug() << "root function at index " << (data->indexOfRootFunction != -1 ? data->indexOfRootFunction : 0); } if (data->indexOfRootFunction != -1) return runtimeFunctions[data->indexOfRootFunction]; else return nullptr; } Heap::Object *ExecutableCompilationUnit::templateObjectAt(int index) const { Q_ASSERT(index < int(data->templateObjectTableSize)); if (!templateObjects.size()) templateObjects.resize(data->templateObjectTableSize); Heap::Object *o = templateObjects.at(index); if (o) return o; // create the template object Scope scope(engine); const CompiledData::TemplateObject *t = data->templateObjectAt(index); Scoped a(scope, engine->newArrayObject(t->size)); Scoped raw(scope, engine->newArrayObject(t->size)); ScopedValue s(scope); for (uint i = 0; i < t->size; ++i) { s = runtimeStrings[t->stringIndexAt(i)]; a->arraySet(i, s); s = runtimeStrings[t->rawStringIndexAt(i)]; raw->arraySet(i, s); } ObjectPrototype::method_freeze(engine->functionCtor(), nullptr, raw, 1); a->defineReadonlyProperty(QStringLiteral("raw"), raw); ObjectPrototype::method_freeze(engine->functionCtor(), nullptr, a, 1); templateObjects[index] = a->objectValue()->d(); return templateObjects.at(index); } void ExecutableCompilationUnit::unlink() { if (engine) nextCompilationUnit.remove(); if (isRegisteredWithEngine) { Q_ASSERT(data && propertyCaches.count() > 0 && propertyCaches.at(/*root object*/0)); if (qmlEngine) qmlEngine->unregisterInternalCompositeType(this); isRegisteredWithEngine = false; } propertyCaches.clear(); if (runtimeLookups) { for (uint i = 0; i < data->lookupTableSize; ++i) { QV4::Lookup &l = runtimeLookups[i]; if (l.getter == QV4::QObjectWrapper::lookupGetter || l.getter == QQmlTypeWrapper::lookupSingletonProperty) { if (QQmlPropertyCache *pc = l.qobjectLookup.propertyCache) pc->release(); } if (l.qmlContextPropertyGetter == QQmlContextWrapper::lookupScopeObjectProperty || l.qmlContextPropertyGetter == QQmlContextWrapper::lookupContextObjectProperty) { if (QQmlPropertyCache *pc = l.qobjectLookup.propertyCache) pc->release(); } } } dependentScripts.clear(); typeNameCache = nullptr; qDeleteAll(resolvedTypes); resolvedTypes.clear(); engine = nullptr; qmlEngine = nullptr; delete [] runtimeLookups; runtimeLookups = nullptr; for (QV4::Function *f : qAsConst(runtimeFunctions)) f->destroy(); runtimeFunctions.clear(); free(runtimeStrings); runtimeStrings = nullptr; delete [] runtimeRegularExpressions; runtimeRegularExpressions = nullptr; free(runtimeClasses); runtimeClasses = nullptr; } void ExecutableCompilationUnit::markObjects(QV4::MarkStack *markStack) { if (runtimeStrings) { for (uint i = 0, end = totalStringCount(); i < end; ++i) if (runtimeStrings[i]) runtimeStrings[i]->mark(markStack); } if (runtimeRegularExpressions) { for (uint i = 0; i < data->regexpTableSize; ++i) Value::fromStaticValue(runtimeRegularExpressions[i]).mark(markStack); } if (runtimeClasses) { for (uint i = 0; i < data->jsClassTableSize; ++i) if (runtimeClasses[i]) runtimeClasses[i]->mark(markStack); } for (QV4::Function *f : qAsConst(runtimeFunctions)) if (f && f->internalClass) f->internalClass->mark(markStack); for (QV4::Heap::InternalClass *c : qAsConst(runtimeBlocks)) if (c) c->mark(markStack); for (QV4::Heap::Object *o : qAsConst(templateObjects)) if (o) o->mark(markStack); if (runtimeLookups) { for (uint i = 0; i < data->lookupTableSize; ++i) runtimeLookups[i].markObjects(markStack); } if (auto mod = module()) mod->mark(markStack); } IdentifierHash ExecutableCompilationUnit::createNamedObjectsPerComponent(int componentObjectIndex) { IdentifierHash namedObjectCache(engine); const CompiledData::Object *component = objectAt(componentObjectIndex); const quint32_le *namedObjectIndexPtr = component->namedObjectsInComponentTable(); for (quint32 i = 0; i < component->nNamedObjectsInComponent; ++i, ++namedObjectIndexPtr) { const CompiledData::Object *namedObject = objectAt(*namedObjectIndexPtr); namedObjectCache.add(runtimeStrings[namedObject->idNameIndex], namedObject->id); } return *namedObjectsPerComponentCache.insert(componentObjectIndex, namedObjectCache); } void ExecutableCompilationUnit::finalizeCompositeType(QQmlEnginePrivate *qmlEngine, CompositeMetaTypeIds types) { this->qmlEngine = qmlEngine; // Add to type registry of composites if (propertyCaches.needsVMEMetaObject(/*root object*/0)) { // typeIds is only valid for types that have references to themselves. if (!types.isValid()) types = CompositeMetaTypeIds::fromCompositeName(rootPropertyCache()->className()); typeIds = types; qmlEngine->registerInternalCompositeType(this); } else { const QV4::CompiledData::Object *obj = objectAt(/*root object*/0); auto *typeRef = resolvedTypes.value(obj->inheritedTypeNameIndex); Q_ASSERT(typeRef); if (const auto compilationUnit = typeRef->compilationUnit()) { typeIds = compilationUnit->typeIds; } else { const auto type = typeRef->type(); typeIds = CompositeMetaTypeIds{ type.typeId(), type.qListTypeId() }; } } // Collect some data for instantiation later. using namespace icutils; std::vector allICs {}; for (int i=0; i != objectCount(); ++i) { const CompiledObject *obj = objectAt(i); for (auto it = obj->inlineComponentsBegin(); it != obj->inlineComponentsEnd(); ++it) { allICs.push_back(*it); } } std::vector nodes; nodes.resize(allICs.size()); std::iota(nodes.begin(), nodes.end(), 0); AdjacencyList adjacencyList; adjacencyList.resize(nodes.size()); fillAdjacencyListForInlineComponents(this, adjacencyList, nodes, allICs); bool hasCycle = false; auto nodesSorted = topoSort(nodes, adjacencyList, hasCycle); Q_ASSERT(!hasCycle); // would have already been discovered by qqmlpropertycachcecreator // We need to first iterate over all inline components, as the containing component might create instances of them // and in that case we need to add its object count for (auto nodeIt = nodesSorted.rbegin(); nodeIt != nodesSorted.rend(); ++nodeIt) { const auto &ic = allICs.at(nodeIt->index); int lastICRoot = ic.objectIndex; for (int i = ic.objectIndex; iflags & QV4::CompiledData::Object::IsInlineComponentRoot) || !(obj->flags & QV4::CompiledData::Object::InPartOfInlineComponent); if (leftCurrentInlineComponent) break; inlineComponentData[lastICRoot].totalBindingCount += obj->nBindings; if (auto *typeRef = resolvedTypes.value(obj->inheritedTypeNameIndex)) { const auto type = typeRef->type(); if (type.isValid() && type.parserStatusCast() != -1) ++inlineComponentData[lastICRoot].totalParserStatusCount; ++inlineComponentData[lastICRoot].totalObjectCount; if (const auto compilationUnit = typeRef->compilationUnit()) { // if the type is an inline component type, we have to extract the information from it // This requires that inline components are visited in the correct order auto icRoot = compilationUnit->icRoot; if (type.isInlineComponentType()) icRoot = type.inlineComponentId(); QScopedValueRollback rollback {compilationUnit->icRoot, icRoot}; inlineComponentData[lastICRoot].totalBindingCount += compilationUnit->totalBindingsCount(); inlineComponentData[lastICRoot].totalParserStatusCount += compilationUnit->totalParserStatusCount(); inlineComponentData[lastICRoot].totalObjectCount += compilationUnit->totalObjectCount(); } } } } int bindingCount = 0; int parserStatusCount = 0; int objectCount = 0; for (quint32 i = 0, count = this->objectCount(); i < count; ++i) { const QV4::CompiledData::Object *obj = objectAt(i); if (obj->flags & QV4::CompiledData::Object::InPartOfInlineComponent) { continue; } bindingCount += obj->nBindings; if (auto *typeRef = resolvedTypes.value(obj->inheritedTypeNameIndex)) { const auto type = typeRef->type(); if (type.isValid() && type.parserStatusCast() != -1) ++parserStatusCount; ++objectCount; if (const auto compilationUnit = typeRef->compilationUnit()) { auto icRoot = compilationUnit->icRoot; if (type.isInlineComponentType()) icRoot = type.inlineComponentId(); QScopedValueRollback rollback {compilationUnit->icRoot, icRoot}; bindingCount += compilationUnit->totalBindingsCount(); parserStatusCount += compilationUnit->totalParserStatusCount(); objectCount += compilationUnit->totalObjectCount(); } } } m_totalBindingsCount = bindingCount; m_totalParserStatusCount = parserStatusCount; m_totalObjectCount = objectCount; } int ExecutableCompilationUnit::totalBindingsCount() const { if (icRoot == -1) return m_totalBindingsCount; return inlineComponentData[icRoot].totalBindingCount; } int ExecutableCompilationUnit::totalObjectCount() const { if (icRoot == -1) return m_totalObjectCount; return inlineComponentData[icRoot].totalObjectCount; } int ExecutableCompilationUnit::totalParserStatusCount() const { if (icRoot == -1) return m_totalParserStatusCount; return inlineComponentData[icRoot].totalParserStatusCount; } bool ExecutableCompilationUnit::verifyChecksum(const CompiledData::DependentTypesHasher &dependencyHasher) const { if (!dependencyHasher) { for (size_t i = 0; i < sizeof(data->dependencyMD5Checksum); ++i) { if (data->dependencyMD5Checksum[i] != 0) return false; } return true; } const QByteArray checksum = dependencyHasher(); return checksum.size() == sizeof(data->dependencyMD5Checksum) && memcmp(data->dependencyMD5Checksum, checksum.constData(), sizeof(data->dependencyMD5Checksum)) == 0; } CompositeMetaTypeIds ExecutableCompilationUnit::typeIdsForComponent(int objectid) const { if (objectid == 0) return typeIds; return inlineComponentData[objectid].typeIds; } QStringList ExecutableCompilationUnit::moduleRequests() const { QStringList requests; requests.reserve(data->moduleRequestTableSize); for (uint i = 0; i < data->moduleRequestTableSize; ++i) requests << stringAt(data->moduleRequestTable()[i]); return requests; } Heap::Module *ExecutableCompilationUnit::instantiate(ExecutionEngine *engine) { if (isESModule() && module()) return module(); if (data->indexOfRootFunction < 0) return nullptr; if (!this->engine) linkToEngine(engine); Scope scope(engine); Scoped module(scope, engine->memoryManager->allocate(engine, this)); if (isESModule()) setModule(module->d()); for (const QString &request: moduleRequests()) { auto dependentModuleUnit = engine->loadModule(QUrl(request), this); if (engine->hasException) return nullptr; dependentModuleUnit->instantiate(engine); } ScopedString importName(scope); const uint importCount = data->importEntryTableSize; if (importCount > 0) { imports = new const StaticValue *[importCount]; memset(imports, 0, importCount * sizeof(StaticValue *)); } for (uint i = 0; i < importCount; ++i) { const CompiledData::ImportEntry &entry = data->importEntryTable()[i]; auto dependentModuleUnit = engine->loadModule(urlAt(entry.moduleRequest), this); importName = runtimeStrings[entry.importName]; const Value *valuePtr = dependentModuleUnit->resolveExport(importName); if (!valuePtr) { QString referenceErrorMessage = QStringLiteral("Unable to resolve import reference "); referenceErrorMessage += importName->toQString(); engine->throwReferenceError(referenceErrorMessage, fileName(), entry.location.line, entry.location.column); return nullptr; } imports[i] = valuePtr; } for (uint i = 0; i < data->indirectExportEntryTableSize; ++i) { const CompiledData::ExportEntry &entry = data->indirectExportEntryTable()[i]; auto dependentModuleUnit = engine->loadModule(urlAt(entry.moduleRequest), this); if (!dependentModuleUnit) return nullptr; ScopedString importName(scope, runtimeStrings[entry.importName]); if (!dependentModuleUnit->resolveExport(importName)) { QString referenceErrorMessage = QStringLiteral("Unable to resolve re-export reference "); referenceErrorMessage += importName->toQString(); engine->throwReferenceError(referenceErrorMessage, fileName(), entry.location.line, entry.location.column); return nullptr; } } return module->d(); } const Value *ExecutableCompilationUnit::resolveExportRecursively( QV4::String *exportName, QVector *resolveSet) { if (!module()) return nullptr; for (const auto &entry: *resolveSet) if (entry.module == this && entry.exportName->isEqualTo(exportName)) return nullptr; (*resolveSet) << ResolveSetEntry(this, exportName); if (exportName->toQString() == QLatin1String("*")) return &module()->self; Scope scope(engine); if (auto localExport = lookupNameInExportTable( data->localExportEntryTable(), data->localExportEntryTableSize, exportName)) { ScopedString localName(scope, runtimeStrings[localExport->localName]); uint index = module()->scope->internalClass->indexOfValueOrGetter(localName->toPropertyKey()); if (index == UINT_MAX) return nullptr; if (index >= module()->scope->locals.size) return &(imports[index - module()->scope->locals.size]->asValue()); return &module()->scope->locals[index]; } if (auto indirectExport = lookupNameInExportTable( data->indirectExportEntryTable(), data->indirectExportEntryTableSize, exportName)) { auto dependentModuleUnit = engine->loadModule(urlAt(indirectExport->moduleRequest), this); if (!dependentModuleUnit) return nullptr; ScopedString importName(scope, runtimeStrings[indirectExport->importName]); return dependentModuleUnit->resolveExportRecursively(importName, resolveSet); } if (exportName->toQString() == QLatin1String("default")) return nullptr; const Value *starResolution = nullptr; for (uint i = 0; i < data->starExportEntryTableSize; ++i) { const CompiledData::ExportEntry &entry = data->starExportEntryTable()[i]; auto dependentModuleUnit = engine->loadModule(urlAt(entry.moduleRequest), this); if (!dependentModuleUnit) return nullptr; const Value *resolution = dependentModuleUnit->resolveExportRecursively(exportName, resolveSet); // ### handle ambiguous if (resolution) { if (!starResolution) { starResolution = resolution; continue; } if (resolution != starResolution) return nullptr; } } return starResolution; } const CompiledData::ExportEntry *ExecutableCompilationUnit::lookupNameInExportTable( const CompiledData::ExportEntry *firstExportEntry, int tableSize, QV4::String *name) const { const CompiledData::ExportEntry *lastExportEntry = firstExportEntry + tableSize; auto matchingExport = std::lower_bound(firstExportEntry, lastExportEntry, name, [this](const CompiledData::ExportEntry &lhs, QV4::String *name) { return stringAt(lhs.exportName) < name->toQString(); }); if (matchingExport == lastExportEntry || stringAt(matchingExport->exportName) != name->toQString()) return nullptr; return matchingExport; } void ExecutableCompilationUnit::getExportedNamesRecursively( QStringList *names, QVector *exportNameSet, bool includeDefaultExport) const { if (exportNameSet->contains(this)) return; exportNameSet->append(this); const auto append = [names, includeDefaultExport](const QString &name) { if (!includeDefaultExport && name == QLatin1String("default")) return; names->append(name); }; for (uint i = 0; i < data->localExportEntryTableSize; ++i) { const CompiledData::ExportEntry &entry = data->localExportEntryTable()[i]; append(stringAt(entry.exportName)); } for (uint i = 0; i < data->indirectExportEntryTableSize; ++i) { const CompiledData::ExportEntry &entry = data->indirectExportEntryTable()[i]; append(stringAt(entry.exportName)); } for (uint i = 0; i < data->starExportEntryTableSize; ++i) { const CompiledData::ExportEntry &entry = data->starExportEntryTable()[i]; auto dependentModuleUnit = engine->loadModule(urlAt(entry.moduleRequest), this); if (!dependentModuleUnit) return; dependentModuleUnit->getExportedNamesRecursively(names, exportNameSet, /*includeDefaultExport*/false); } } void ExecutableCompilationUnit::evaluate() { QV4::Scope scope(engine); QV4::Scoped mod(scope, module()); mod->evaluate(); } void ExecutableCompilationUnit::evaluateModuleRequests() { for (const QString &request: moduleRequests()) { auto dependentModuleUnit = engine->loadModule(QUrl(request), this); if (engine->hasException) return; dependentModuleUnit->evaluate(); if (engine->hasException) return; } } bool ExecutableCompilationUnit::loadFromDisk(const QUrl &url, const QDateTime &sourceTimeStamp, QString *errorString) { if (!QQmlFile::isLocalFile(url)) { *errorString = QStringLiteral("File has to be a local file."); return false; } const QString sourcePath = QQmlFile::urlToLocalFileOrQrc(url); QScopedPointer cacheFile(new CompilationUnitMapper()); const QStringList cachePaths = { sourcePath + QLatin1Char('c'), localCacheFilePath(url) }; for (const QString &cachePath : cachePaths) { CompiledData::Unit *mappedUnit = cacheFile->get(cachePath, sourceTimeStamp, errorString); if (!mappedUnit) continue; const CompiledData::Unit * const oldDataPtr = (data && !(data->flags & QV4::CompiledData::Unit::StaticData)) ? data : nullptr; const CompiledData::Unit *oldData = data; auto dataPtrRevert = qScopeGuard([this, oldData](){ setUnitData(oldData); }); setUnitData(mappedUnit); if (data->sourceFileIndex != 0 && sourcePath != QQmlFile::urlToLocalFileOrQrc(stringAt(data->sourceFileIndex))) { *errorString = QStringLiteral("QML source file has moved to a different location."); continue; } dataPtrRevert.dismiss(); free(const_cast(oldDataPtr)); backingFile.reset(cacheFile.take()); return true; } return false; } bool ExecutableCompilationUnit::saveToDisk(const QUrl &unitUrl, QString *errorString) { if (data->sourceTimeStamp == 0) { *errorString = QStringLiteral("Missing time stamp for source file"); return false; } if (!QQmlFile::isLocalFile(unitUrl)) { *errorString = QStringLiteral("File has to be a local file."); return false; } return CompiledData::SaveableUnitPointer(unitData()).saveToDisk( [&unitUrl, errorString](const char *data, quint32 size) { return CompiledData::SaveableUnitPointer::writeDataToFile(localCacheFilePath(unitUrl), data, size, errorString); }); } bool ResolvedTypeReferenceMap::addToHash(QCryptographicHash *hash, QQmlEngine *engine) const { for (auto it = constBegin(), end = constEnd(); it != end; ++it) { if (!it.value()->addToHash(hash, engine)) return false; } return true; } QString ExecutableCompilationUnit::bindingValueAsString(const CompiledData::Binding *binding) const { using namespace CompiledData; #if QT_CONFIG(translation) switch (binding->type) { case Binding::Type_TranslationById: { const TranslationData &translation = data->translations()[binding->value.translationDataIndex]; QByteArray id = stringAt(translation.stringIndex).toUtf8(); return qtTrId(id.constData(), translation.number); } case Binding::Type_Translation: { const TranslationData &translation = data->translations()[binding->value.translationDataIndex]; // This code must match that in the qsTr() implementation const QString &path = fileName(); int lastSlash = path.lastIndexOf(QLatin1Char('/')); QStringView context = (lastSlash > -1) ? QStringView{path}.mid(lastSlash + 1, path.length() - lastSlash - 5) : QStringView(); QByteArray contextUtf8 = context.toUtf8(); QByteArray comment = stringAt(translation.commentIndex).toUtf8(); QByteArray text = stringAt(translation.stringIndex).toUtf8(); return QCoreApplication::translate(contextUtf8.constData(), text.constData(), comment.constData(), translation.number); } default: break; } #endif return CompilationUnit::bindingValueAsString(binding); } bool ExecutableCompilationUnit::verifyHeader( const CompiledData::Unit *unit, QDateTime expectedSourceTimeStamp, QString *errorString) { if (strncmp(unit->magic, CompiledData::magic_str, sizeof(unit->magic))) { *errorString = QStringLiteral("Magic bytes in the header do not match"); return false; } if (unit->version != quint32(QV4_DATA_STRUCTURE_VERSION)) { *errorString = QString::fromUtf8("V4 data structure version mismatch. Found %1 expected %2") .arg(unit->version, 0, 16).arg(QV4_DATA_STRUCTURE_VERSION, 0, 16); return false; } if (unit->qtVersion != quint32(QT_VERSION)) { *errorString = QString::fromUtf8("Qt version mismatch. Found %1 expected %2") .arg(unit->qtVersion, 0, 16).arg(QT_VERSION, 0, 16); return false; } if (unit->sourceTimeStamp) { // Files from the resource system do not have any time stamps, so fall back to the application // executable. if (!expectedSourceTimeStamp.isValid()) expectedSourceTimeStamp = QFileInfo(QCoreApplication::applicationFilePath()).lastModified(); if (expectedSourceTimeStamp.isValid() && expectedSourceTimeStamp.toMSecsSinceEpoch() != unit->sourceTimeStamp) { *errorString = QStringLiteral("QML source file has a different time stamp than cached file."); return false; } } #if defined(QML_COMPILE_HASH) if (qstrcmp(qml_compile_hash, unit->libraryVersionHash) != 0) { *errorString = QStringLiteral("QML library version mismatch. Expected compile hash does not match"); 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; } } // namespace QV4 QT_END_NAMESPACE