Revision control
++ // Copyright (C) 2019 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++ // Qt-Security score:significant
++
++ #include "qml/qqmlprivate.h"
++ #include "qv4engine_p.h"
++ #include "qv4executablecompilationunit_p.h"
++
++ #include <private/qv4engine_p.h>
++ #include <private/qv4regexp_p.h>
++ #include <private/qv4lookup_p.h>
++ #include <private/qv4qmlcontext_p.h>
++ #include <private/qv4identifiertable_p.h>
++ #include <private/qv4objectproto_p.h>
++ #include <private/qqmlengine_p.h>
++ #include <private/qv4qobjectwrapper_p.h>
++ #include <private/qqmlvaluetypewrapper_p.h>
++ #include <private/qqmlscriptdata_p.h>
++ #include <private/qv4module_p.h>
++ #include <private/qv4compilationunitmapper_p.h>
++ #include <private/qqmltypewrapper_p.h>
++ #include <private/qv4resolvedtypereference_p.h>
++ #include <private/qv4objectiterator_p.h>
++
++ #include <QtQml/qqmlpropertymap.h>
++
++ #include <QtCore/qfileinfo.h>
++ #include <QtCore/qcryptographichash.h>
++
++ QT_BEGIN_NAMESPACE
++
++ DEFINE_BOOL_CONFIG_OPTION(qv4FailOnInvalidAot, QV4_FAIL_ON_INVALID_AOT)
++ DEFINE_BOOL_CONFIG_OPTION(qv4SkipAotValidation, QV4_SKIP_AOT_VALIDATION)
++
++ namespace QV4 {
++
++ ExecutableCompilationUnit::ExecutableCompilationUnit() = default;
++
++ ExecutableCompilationUnit::ExecutableCompilationUnit(
++ QQmlRefPointer<CompiledData::CompilationUnit> &&compilationUnit)
++ : m_compilationUnit(std::move(compilationUnit))
++ {
++ constants = m_compilationUnit->constants;
++ }
++
++ ExecutableCompilationUnit::~ExecutableCompilationUnit()
++ {
++ if (engine)
++ clear();
++ }
++
++ 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";
++ }
++ }
++
++ void ExecutableCompilationUnit::populate()
++ {
++ /* In general, we should use QV4::Scope whenever we allocate heap objects, and employ write barriers
++ for member variables pointing to heap objects. However, ExecutableCompilationUnit is special, as it
++ is always part of the root set. So instead of using scoped allocations and write barriers, we use a
++ slightly different approach: We temporarily block the gc from running. Afterwards, at the end of the
++ function we check whether the gc was already running, and mark the ExecutableCompilationUnit. This
++ ensures that all the newly allocated objects of the compilation unit will be marked in turn.
++ If the gc was not running, we don't have to do anything, because everything will be marked when the
++ gc starts marking the root set at the start of a run.
++ */
++ const CompiledData::Unit *data = m_compilationUnit->data;
++ GCCriticalSection<ExecutableCompilationUnit> criticalSection(engine, this);
++
++ Q_ASSERT(!runtimeStrings);
++ Q_ASSERT(engine);
++ Q_ASSERT(data);
++ const quint32 stringCount = totalStringCount();
++ runtimeStrings = (QV4::Heap::String **)calloc(stringCount, sizeof(QV4::Heap::String*));
++ for (uint i = 0; i < stringCount; ++i)
++ runtimeStrings[i] = engine->newString(stringAt(i));
++
++ runtimeRegularExpressions
++ = new QV4::Value[data->regexpTableSize];
++ 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<CompiledData::RegExp::Flags>(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()));
++ if (type == CompiledData::Lookup::Type_Getter)
++ l->call = Lookup::Call::GetterGeneric;
++ else if (type == CompiledData::Lookup::Type_Setter)
++ l->call = Lookup::Call::SetterGeneric;
++ else if (type == CompiledData::Lookup::Type_GlobalGetter)
++ l->call = Lookup::Call::GlobalGetterGeneric;
++ else if (type == CompiledData::Lookup::Type_QmlContextPropertyGetter)
++ l->call = Lookup::Call::ContextGetterGeneric;
++ l->forCall = compiledLookups[i].mode() == CompiledData::Lookup::Mode_ForCall;
++ l->nameIndex = compiledLookups[i].nameIndex();
++ }
++ }
++
++ if (data->jsClassTableSize) {
++ runtimeClasses
++ = (QV4::Heap::InternalClass **)calloc(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);
++ }
++ }
++
++ const auto validateCULookupSignatures = [&]() -> bool {
++ if (qv4SkipAotValidation())
++ return true;
++ if (!m_compilationUnit->aotCompiledFunctions)
++ return true; // Non-existent code can't be invalid
++
++ Q_ASSERT(data->version >= 0x4d); // Otherwise validateLookupSignatures is uninitialized
++ const CompiledData::LookupValidationFn v = m_compilationUnit->validateLookupSignatures;
++ bool valid = v && v(engine->qmlEngine(), m_compilationUnit.data());
++
++ if (!valid && qv4FailOnInvalidAot()) {
++ // The compile time and run time AOT lookup signatures don't match. A rebuild is needed.
++ qFatal("AOT lookup signatures validation failed for file %s. A rebuild is needed. %s",
++ fileName().toUtf8().constData(), "[QV4_FAIL_ON_INVALID_AOT]");
++ }
++ return valid;
++ };
++
++ runtimeFunctions.resize(data->functionTableSize);
++ static const bool staticIgnoreAotCompiledFunctions
++ = qEnvironmentVariableIsSet("QV4_FORCE_INTERPRETER")
++ || !(engine->diskCacheOptions() & ExecutionEngine::DiskCache::AotNative);
++
++ bool ignoreAotCompiledFunctions = staticIgnoreAotCompiledFunctions
++ || !validateCULookupSignatures();
++
++ const QQmlPrivate::AOTCompiledFunction *aotFunction
++ = ignoreAotCompiledFunctions ? nullptr : m_compilationUnit->aotCompiledFunctions;
++
++ auto advanceAotFunction = [&](int i) -> const QQmlPrivate::AOTCompiledFunction * {
++ if (aotFunction) {
++ if (aotFunction->functionPtr) {
++ if (aotFunction->functionIndex == 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<InternalClass> 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);
++ }
++ }
++
++ Heap::Object *ExecutableCompilationUnit::templateObjectAt(int index) const
++ {
++ const CompiledData::Unit *data = m_compilationUnit->data;
++ Q_ASSERT(data);
++ Q_ASSERT(engine);
++
++ 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<ArrayObject> a(scope, engine->newArrayObject(t->size));
++ Scoped<ArrayObject> 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::clear()
++ {
++ delete [] imports;
++ imports = nullptr;
++
++ if (runtimeLookups) {
++ const uint lookupTableSize = unitData()->lookupTableSize;
++ for (uint i = 0; i < lookupTableSize; ++i)
++ runtimeLookups[i].releasePropertyCache();
++ }
++
++ delete [] runtimeLookups;
++ runtimeLookups = nullptr;
++
++ for (QV4::Function *f : std::as_const(runtimeFunctions))
++ f->destroy();
++ runtimeFunctions.clear();
++
++ free(runtimeStrings);
++ runtimeStrings = nullptr;
++ delete [] runtimeRegularExpressions;
++ runtimeRegularExpressions = nullptr;
++ free(runtimeClasses);
++ runtimeClasses = nullptr;
++
++ static QQmlRefPointer<CompiledData::CompilationUnit> nullUnit
++ = QQml::makeRefPointer<CompiledData::CompilationUnit>();
++ m_compilationUnit = nullUnit;
++ }
++
++ void ExecutableCompilationUnit::markObjects(QV4::MarkStack *markStack) const
++ {
++ const CompiledData::Unit *data = m_compilationUnit->data;
++
++ 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 : std::as_const(runtimeFunctions))
++ if (f && f->internalClass)
++ f->internalClass->mark(markStack);
++ for (QV4::Heap::InternalClass *c : std::as_const(runtimeBlocks))
++ if (c)
++ c->mark(markStack);
++
++ for (QV4::Heap::Object *o : std::as_const(templateObjects))
++ if (o)
++ o->mark(markStack);
++
++ if (runtimeLookups) {
++ for (uint i = 0; i < data->lookupTableSize; ++i)
++ runtimeLookups[i].markObjects(markStack);
++ }
++
++ if (Heap::Base *v = m_valueOrModule.heapObject())
++ v->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->objectId());
++ }
++ Q_ASSERT(namedObjectCache.isValid());
++ return *namedObjectsPerComponentCache.insert(componentObjectIndex, namedObjectCache);
++ }
++
++ QQmlRefPointer<ExecutableCompilationUnit> ExecutableCompilationUnit::create(
++ QQmlRefPointer<CompiledData::CompilationUnit> &&compilationUnit, ExecutionEngine *engine)
++ {
++ auto result = QQmlRefPointer<ExecutableCompilationUnit>(
++ new ExecutableCompilationUnit(std::move(compilationUnit)),
++ QQmlRefPointer<ExecutableCompilationUnit>::Adopt);
++ result->engine = engine;
++ return result;
++ }
++
++ Heap::Module *ExecutableCompilationUnit::instantiate()
++ {
++ const CompiledData::Unit *data = m_compilationUnit->data;
++
++ if (isESModule() && module())
++ return module();
++
++ if (data->indexOfRootFunction < 0)
++ return nullptr;
++
++ Q_ASSERT(engine);
++ if (!runtimeStrings)
++ populate();
++
++ Scope scope(engine);
++ Scoped<Module> module(scope, engine->memoryManager->allocate<Module>(engine, this));
++
++ if (isESModule())
++ setModule(module->d());
++
++ for (uint i = 0, end = data->moduleRequestTableSize; i < end; ++i) {
++ const auto dependentModuleUnit = dependentModule(urlAt(data->moduleRequestTable()[i]));
++ if (engine->hasException)
++ return nullptr;
++ if (dependentModuleUnit)
++ dependentModuleUnit->instantiate();
++ }
++
++ 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];
++ importName = runtimeStrings[entry.importName];
++
++ if (const auto module = dependentModule(urlAt(entry.moduleRequest))) {
++ const Value *valuePtr = module->resolveExport(importName);
++ if (!valuePtr) {
++ QString referenceErrorMessage = QStringLiteral("Unable to resolve import reference ");
++ referenceErrorMessage += importName->toQString();
++ QV4::ScopedValue compiledValue(scope, module->value());
++ engine->throwReferenceError(
++ referenceErrorMessage, fileName(),
++ entry.location.line(), entry.location.column());
++ return nullptr;
++ }
++ imports[i] = valuePtr;
++ }
++ }
++
++ const auto throwReferenceError = [&](const CompiledData::ExportEntry &entry, const QString &importName) {
++ QString referenceErrorMessage = QStringLiteral("Unable to resolve re-export reference ");
++ referenceErrorMessage += importName;
++ engine->throwReferenceError(
++ referenceErrorMessage, fileName(),
++ entry.location.line(), entry.location.column());
++ };
++
++ for (uint i = 0; i < data->indirectExportEntryTableSize; ++i) {
++ const CompiledData::ExportEntry &entry = data->indirectExportEntryTable()[i];
++ if (auto module = dependentModule(urlAt(entry.moduleRequest))) {
++ ScopedString importName(scope, runtimeStrings[entry.importName]);
++ if (!module->resolveExport(importName)) {
++ throwReferenceError(entry, importName->toQString());
++ return nullptr;
++ }
++ }
++ }
++
++ return module->d();
++ }
++
++ const Value *ExecutableCompilationUnit::resolveExportRecursively(
++ QV4::String *exportName, QList<ResolveSetEntry> *resolveSet) const
++ {
++ if (!module())
++ return nullptr;
++
++ for (const auto &entry: std::as_const(*resolveSet)) {
++ if (entry.module == this && entry.exportName->isEqualTo(exportName))
++ return nullptr;
++ }
++
++ (*resolveSet) << ResolveSetEntry(this, exportName);
++
++ if (exportName->toQString() == QLatin1String("*"))
++ return &module()->self;
++
++ const CompiledData::Unit *data = m_compilationUnit->data;
++
++ Q_ASSERT(data);
++ Q_ASSERT(engine);
++
++ 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<Value>());
++ return &module()->scope->locals[index];
++ }
++
++ if (auto indirectExport = lookupNameInExportTable(
++ data->indirectExportEntryTable(), data->indirectExportEntryTableSize, exportName)) {
++ if (auto module = dependentModule(urlAt(indirectExport->moduleRequest))) {
++ ScopedString importName(scope, runtimeStrings[indirectExport->importName]);
++ return module->resolveExportRecursively(importName, resolveSet);
++ }
++ return nullptr;
++ }
++
++ 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];
++ const Value *resolution = nullptr;
++ if (auto module = dependentModule(urlAt(entry.moduleRequest)))
++ resolution = module->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, QList<const ExecutableCompilationUnit*> *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);
++ };
++
++ const CompiledData::Unit *data = m_compilationUnit->data;
++
++ Q_ASSERT(data);
++ Q_ASSERT(engine);
++
++ 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];
++ if (auto module = dependentModule(urlAt(entry.moduleRequest))) {
++ module->getExportedNamesRecursively(
++ names, exportNameSet, /*includeDefaultExport*/false);
++ }
++ }
++ }
++
++ void ExecutableCompilationUnit::evaluate()
++ {
++ Q_ASSERT(engine);
++
++ QV4::Scope scope(engine);
++ QV4::Scoped<Module> mod(scope, module());
++ mod->evaluate();
++ }
++
++ void ExecutableCompilationUnit::evaluateModuleRequests()
++ {
++ Q_ASSERT(engine);
++
++ const CompiledData::Unit *data = unitData();
++ for (uint i = 0, end = data->moduleRequestTableSize; i < end; ++i) {
++ auto module = dependentModule(urlAt(data->moduleRequestTable()[i]));
++
++ if (engine->hasException)
++ return;
++
++ Q_ASSERT(module);
++ module->evaluate();
++ if (engine->hasException)
++ return;
++ }
++ }
++
++ QString ExecutableCompilationUnit::bindingValueAsString(const CompiledData::Binding *binding) const
++ {
++ #if QT_CONFIG(translation)
++ using namespace CompiledData;
++ bool byId = false;
++ switch (binding->type()) {
++ case Binding::Type_TranslationById:
++ byId = true;
++ Q_FALLTHROUGH();
++ case Binding::Type_Translation: {
++ return translateFrom({ binding->value.translationDataIndex, byId });
++ }
++ default:
++ break;
++ }
++ #endif
++ return m_compilationUnit->bindingValueAsString(binding);
++ }
++
++ QString ExecutableCompilationUnit::translateFrom(TranslationDataIndex index) const
++ {
++ #if !QT_CONFIG(translation)
++ return QString();
++ #else
++ const CompiledData::TranslationData &translation = unitData()->translations()[index.index];
++
++ if (index.byId) {
++ QByteArray id = stringAt(translation.stringIndex).toUtf8();
++ return qtTrId(id.constData(), translation.number);
++ }
++
++ const auto fileContext = [this]() {
++ // 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.size() - lastSlash - 5)
++ : QStringView();
++ return context.toUtf8();
++ };
++
++ const bool hasContext
++ = translation.contextIndex != QV4::CompiledData::TranslationData::NoContextIndex;
++ QByteArray context;
++ if (hasContext) {
++ context = stringAt(translation.contextIndex).toUtf8();
++ } else {
++ auto pragmaTranslationContext = unitData()->translationContextIndex();
++ context = stringAt(*pragmaTranslationContext).toUtf8();
++ context = context.isEmpty() ? fileContext() : context;
++ }
++
++ QByteArray comment = stringAt(translation.commentIndex).toUtf8();
++ QByteArray text = stringAt(translation.stringIndex).toUtf8();
++ return QCoreApplication::translate(context, text, comment, translation.number);
++ #endif
++ }
++
++ Heap::Module *ExecutableCompilationUnit::module() const
++ {
++ if (const Module *m = m_valueOrModule.as<QV4::Module>())
++ return m->d();
++ return nullptr;
++ }
++
++ void ExecutableCompilationUnit::setModule(Heap::Module *module)
++ {
++ // We don't necessarily hold any other references to ES modules. So, if the GC
++ // is running right now, we need to mark it. Otherwise it might collect it even
++ // though it's still reachable via the engine's list of compilation units.
++ QV4::WriteBarrier::markCustom(engine, [module](QV4::MarkStack *stack) {
++ if constexpr (QV4::WriteBarrier::isInsertionBarrier) {
++ module->mark(stack);
++ }
++ });
++ m_valueOrModule = module;
++ }
++
++ } // namespace QV4
++
++ QT_END_NAMESPACE