diff options
Diffstat (limited to 'src/qml/jsruntime/qv4executablecompilationunit.cpp')
-rw-r--r-- | src/qml/jsruntime/qv4executablecompilationunit.cpp | 710 |
1 files changed, 710 insertions, 0 deletions
diff --git a/src/qml/jsruntime/qv4executablecompilationunit.cpp b/src/qml/jsruntime/qv4executablecompilationunit.cpp new file mode 100644 index 0000000000..34d737cdae --- /dev/null +++ b/src/qml/jsruntime/qv4executablecompilationunit.cpp @@ -0,0 +1,710 @@ +// 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 + +#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 + +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 scopde 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->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->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); + } + } + + runtimeFunctions.resize(data->functionTableSize); + static bool ignoreAotCompiledFunctions + = qEnvironmentVariableIsSet("QV4_FORCE_INTERPRETER") + || !(engine->diskCacheOptions() & ExecutionEngine::DiskCache::AotNative); + + 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; +} + +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 (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->objectId()); + } + Q_ASSERT(!namedObjectCache.isEmpty()); + 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()); + + const QStringList moduleRequests = m_compilationUnit->moduleRequests(); + for (const QString &request: moduleRequests) { + const QUrl url(request); + const auto dependentModuleUnit = engine->loadModule(url, this); + if (engine->hasException) + return nullptr; + if (dependentModuleUnit.compiled) + dependentModuleUnit.compiled->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]; + QUrl url = urlAt(entry.moduleRequest); + importName = runtimeStrings[entry.importName]; + + const auto module = engine->loadModule(url, this); + if (module.compiled) { + const Value *valuePtr = module.compiled->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; + } else if (Value *value = module.native) { + const QString name = importName->toQString(); + if (value->isNullOrUndefined()) { + QString errorMessage = name; + errorMessage += QStringLiteral(" from "); + errorMessage += url.toString(); + errorMessage += QStringLiteral(" is null"); + engine->throwError(errorMessage); + return nullptr; + } + + if (name == QStringLiteral("default")) { + imports[i] = value; + } else { + url.setFragment(name); + const auto fragment = engine->moduleForUrl(url, this); + if (fragment.native) { + imports[i] = fragment.native; + } else { + Scope scope(this->engine); + ScopedObject o(scope, value); + if (!o) { + QString referenceErrorMessage = QStringLiteral("Unable to resolve import reference "); + referenceErrorMessage += name; + referenceErrorMessage += QStringLiteral(" because "); + referenceErrorMessage += url.toString(QUrl::RemoveFragment); + referenceErrorMessage += QStringLiteral(" is not an object"); + engine->throwReferenceError( + referenceErrorMessage, fileName(), + entry.location.line(), entry.location.column()); + return nullptr; + } + + const ScopedPropertyKey key(scope, scope.engine->identifierTable->asPropertyKey(name)); + const ScopedValue result(scope, o->get(key)); + imports[i] = engine->registerNativeModule(url, result); + } + } + } + } + + 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]; + auto dependentModule = engine->loadModule(urlAt(entry.moduleRequest), this); + ScopedString importName(scope, runtimeStrings[entry.importName]); + if (const auto dependentModuleUnit = dependentModule.compiled) { + if (!dependentModuleUnit->resolveExport(importName)) { + throwReferenceError(entry, importName->toQString()); + return nullptr; + } + } else if (const auto native = dependentModule.native) { + ScopedObject o(scope, native); + const ScopedPropertyKey key(scope, scope.engine->identifierTable->asPropertyKey(importName)); + const ScopedValue result(scope, o->get(key)); + if (result->isUndefined()) { + throwReferenceError(entry, importName->toQString()); + return nullptr; + } + } + } + + return module->d(); +} + +const Value *ExecutableCompilationUnit::resolveExportRecursively( + QV4::String *exportName, QVector<ResolveSetEntry> *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; + + 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)) { + QUrl request = urlAt(indirectExport->moduleRequest); + auto dependentModule = engine->loadModule(request, this); + ScopedString importName(scope, runtimeStrings[indirectExport->importName]); + if (dependentModule.compiled) { + return dependentModule.compiled->resolveExportRecursively(importName, resolveSet); + } else if (dependentModule.native) { + if (exportName->toQString() == QLatin1String("*")) + return dependentModule.native; + if (exportName->toQString() == QLatin1String("default")) + return nullptr; + + request.setFragment(importName->toQString()); + const auto fragment = engine->moduleForUrl(request); + if (fragment.native) + return fragment.native; + + ScopedObject o(scope, dependentModule.native); + if (o) + return engine->registerNativeModule(request, o->get(importName)); + + return nullptr; + } else { + 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]; + QUrl request = urlAt(entry.moduleRequest); + auto dependentModule = engine->loadModule(request, this); + const Value *resolution = nullptr; + if (dependentModule.compiled) { + resolution = dependentModule.compiled->resolveExportRecursively( + exportName, resolveSet); + } else if (dependentModule.native) { + if (exportName->toQString() == QLatin1String("*")) { + resolution = dependentModule.native; + } else if (exportName->toQString() != QLatin1String("default")) { + request.setFragment(exportName->toQString()); + const auto fragment = engine->moduleForUrl(request); + if (fragment.native) { + resolution = fragment.native; + } else { + ScopedObject o(scope, dependentModule.native); + if (o) + resolution = engine->registerNativeModule(request, o->get(exportName)); + } + } + } + + // ### 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<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]; + auto dependentModule = engine->loadModule(urlAt(entry.moduleRequest), this); + if (dependentModule.compiled) { + dependentModule.compiled->getExportedNamesRecursively( + names, exportNameSet, /*includeDefaultExport*/false); + } else if (dependentModule.native) { + Scope scope(engine); + ScopedObject o(scope, dependentModule.native); + ObjectIterator iterator(scope, o, ObjectIterator::EnumerableOnly); + while (true) { + ScopedValue val(scope, iterator.nextPropertyNameAsString()); + if (val->isNull()) + break; + append(val->toQString()); + } + } + } +} + +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 QStringList moduleRequests = m_compilationUnit->moduleRequests(); + for (const QString &request: moduleRequests) { + auto dependentModule = engine->loadModule(QUrl(request), this); + if (dependentModule.native) + continue; + + if (engine->hasException) + return; + + Q_ASSERT(dependentModule.compiled); + dependentModule.compiled->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 +} + +} // namespace QV4 + +QT_END_NAMESPACE |