Revision control
++ // Copyright (C) 2016 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 "qqmlirbuilder_p.h"
++
++ #include <private/qv4staticvalue_p.h>
++ #include <private/qv4compileddata_p.h>
++ #include <private/qqmljsparser_p.h>
++ #include <private/qqmljslexer_p.h>
++ #include <private/qv4compilerscanfunctions_p.h>
++ #include <QCoreApplication>
++ #include <QCryptographicHash>
++ #include <QtCore/qtyperevision.h>
++
++ #include <cmath>
++ #include <iterator>
++
++ QT_USE_NAMESPACE
++
++ using namespace Qt::StringLiterals;
++
++ static const quint32 emptyStringIndex = 0;
++ using namespace QmlIR;
++ using namespace QQmlJS;
++
++ #define COMPILE_EXCEPTION(location, desc) \
++ { \
++ recordError(location, desc); \
++ if (!shouldContinue()) \
++ return false; \
++ }
++
++ void Object::simplifyRequiredProperties() {
++ // if a property of the current object was marked as required
++ // do not store that information in the ExtraData
++ // but rather mark the property as required
++ QSet<int> required;
++ for (auto it = this->requiredPropertyExtraDataBegin(); it != this->requiredPropertyExtraDataEnd(); ++it)
++ required.insert(it->nameIndex);
++ if (required.isEmpty())
++ return;
++ for (auto it = this->propertiesBegin(); it != this->propertiesEnd(); ++it) {
++ auto requiredIt = required.find(it->nameIndex());
++ if (requiredIt != required.end()) {
++ it->setIsRequired(true);
++ required.erase(requiredIt);
++ }
++ }
++ QmlIR::RequiredPropertyExtraData *prev = nullptr;
++ auto current = this->requiredPropertyExtraDatas->first;
++ while (current) {
++ if (required.contains(current->nameIndex))
++ prev = current;
++ else
++ requiredPropertyExtraDatas->unlink(prev, current);
++ current = current->next;
++ }
++ }
++
++ /*!
++ \internal
++
++ Reorders the alias data within the linked list of this object so that
++ aliases targeting other aliases on the same object come after their
++ targets. This ensures that dependencies are resolved and appended to
++ the property cache first, which is necessary because the runtime
++ assumes that the property cache position matches the alias table index.
++
++ Only considers same-object dependencies (where the alias's idIndex
++ resolves to idNameIndex). Cross-object dependencies are handled by
++ the multi-pass resolution in QQmlCOmponentAndAliasResolver.
++ */
++ void Object::sortAliasDependencies(const Document *doc, QList<QQmlJS::DiagnosticMessage> *errors)
++ {
++ using AliasArray = QVarLengthArray<Alias *, 8>;
++
++ AliasArray ordered;
++ ordered.reserve(aliasCount());
++
++ // if the default property is an alias, we need to later update the default property index
++ Alias *defaultPropertyAlias = nullptr;
++ qsizetype aliasCounter = 0;
++
++ // Collect aliases as nodes in a graph. Non-local ones are already ordered.
++ AliasArray nodes;
++ for (Alias *a = firstAlias(); a; ++aliasCounter, a = a->next) {
++ if (defaultPropertyIsAlias && aliasCounter == indexOfDefaultPropertyOrAlias) {
++ defaultPropertyAlias = a;
++ }
++ if (a->idIndex() == idNameIndex && idNameIndex != 0)
++ nodes.append(a);
++ else
++ ordered.append(a);
++ }
++
++ // Nothing to sort here.
++ if (nodes.isEmpty())
++ return;
++
++ // Collect dependencies as edges between nodes
++ QVarLengthArray<qsizetype, 8> edges(nodes.size(), -1);
++ for (qsizetype i = 0, end = nodes.size(); i < end; ++i) {
++ const QStringView propValue = doc->stringAt(nodes[i]->propertyNameIndex());
++ const int dotIdx = propValue.indexOf(QLatin1Char('.'));
++ const QStringView targetName = dotIdx != -1 ? propValue.left(dotIdx) : propValue;
++ if (targetName.isEmpty())
++ continue;
++
++ for (qsizetype j = 0; j < end; ++j) {
++ if (j != i && doc->stringAt(nodes[j]->nameIndex()) == targetName) {
++ edges[i] = j;
++ break;
++ }
++ }
++ }
++
++ // Simple DFS-based topological sort
++ for (qsizetype i = 0, end = nodes.size(); i < end; ++i) {
++ // Skip already inserted nodes
++ if (nodes[i] == nullptr)
++ continue;
++
++ // Follow the dependency chain to find the root.
++ QVarLengthArray<qsizetype, 8> chain;
++ for (qsizetype j = edges[i]; j != -1 && nodes[j]; j = edges[j]) {
++ if (!chain.contains(j)) {
++ chain.append(j);
++ continue;
++ }
++
++ const QV4::CompiledData::Location &location = nodes[j]->location();
++ QQmlJS::DiagnosticMessage error;
++ error.loc.startLine = location.line();
++ error.loc.startColumn = location.column();
++ error.loc.offset = QQmlJS::SourceLocation::offsetFrom(
++ doc->code, location.line(), location.column());
++ error.message = QCoreApplication::translate("QQmlParser", "Cyclic alias");
++ errors->append(std::move(error));
++ return;
++ }
++
++ // Emit in reverse (dependency first)
++ for (qsizetype k = chain.size() - 1; k >= 0; --k)
++ ordered.append(std::exchange(nodes[chain[k]], nullptr));
++ ordered.append(std::exchange(nodes[i], nullptr));
++ }
++
++ // Apply the sorted order to the alias list.
++ setFirstAlias(ordered[0]);
++
++ for (qsizetype i = 0, end = ordered.size() - 1; i < end; ++i)
++ ordered[i]->next = ordered[i + 1];
++ ordered.last()->next = nullptr;
++ if (defaultPropertyAlias) {
++ auto it = std::find(ordered.constBegin(), ordered.constEnd(), defaultPropertyAlias);
++ indexOfDefaultPropertyOrAlias = std::distance(ordered.constBegin(), it);
++ }
++ }
++
++ bool Parameter::initType(
++ QV4::CompiledData::ParameterType *paramType,
++ const QString &typeName, int typeNameIndex,
++ QV4::CompiledData::ParameterType::Flag listFlag)
++ {
++ auto builtinType = stringToBuiltinType(typeName);
++ if (builtinType == QV4::CompiledData::CommonType::Invalid) {
++ if (typeName.isEmpty()) {
++ paramType->set(listFlag, 0);
++ return false;
++ }
++ Q_ASSERT(quint32(typeNameIndex) < (1u << 31));
++ paramType->set(listFlag, typeNameIndex);
++ } else {
++ Q_ASSERT(quint32(builtinType) < (1u << 31));
++ paramType->set(listFlag | QV4::CompiledData::ParameterType::Common,
++ static_cast<quint32>(builtinType));
++ }
++ return true;
++ }
++
++ QV4::CompiledData::CommonType Parameter::stringToBuiltinType(const QString &typeName)
++ {
++ static const struct TypeNameToType {
++ const char *name;
++ size_t nameLength;
++ QV4::CompiledData::CommonType type;
++ } propTypeNameToTypes[] = {
++ { "void", strlen("void"), QV4::CompiledData::CommonType::Void },
++ { "int", strlen("int"), QV4::CompiledData::CommonType::Int },
++ { "bool", strlen("bool"), QV4::CompiledData::CommonType::Bool },
++ { "double", strlen("double"), QV4::CompiledData::CommonType::Real },
++ { "real", strlen("real"), QV4::CompiledData::CommonType::Real },
++ { "string", strlen("string"), QV4::CompiledData::CommonType::String },
++ { "url", strlen("url"), QV4::CompiledData::CommonType::Url },
++ { "date", strlen("date"), QV4::CompiledData::CommonType::DateTime },
++ { "regexp", strlen("regexp"), QV4::CompiledData::CommonType::RegExp },
++ { "rect", strlen("rect"), QV4::CompiledData::CommonType::Rect },
++ { "point", strlen("point"), QV4::CompiledData::CommonType::Point },
++ { "size", strlen("size"), QV4::CompiledData::CommonType::Size },
++ { "variant", strlen("variant"), QV4::CompiledData::CommonType::Var },
++ { "var", strlen("var"), QV4::CompiledData::CommonType::Var }
++ };
++ static const int propTypeNameToTypesCount = sizeof(propTypeNameToTypes) /
++ sizeof(propTypeNameToTypes[0]);
++
++ for (int typeIndex = 0; typeIndex < propTypeNameToTypesCount; ++typeIndex) {
++ const TypeNameToType *t = propTypeNameToTypes + typeIndex;
++ if (typeName == QLatin1String(t->name, static_cast<int>(t->nameLength))) {
++ return t->type;
++ }
++ }
++ return QV4::CompiledData::CommonType::Invalid;
++ }
++
++ void Object::init(QQmlJS::MemoryPool *pool, int typeNameIndex, int idIndex,
++ const QV4::CompiledData::Location &loc)
++ {
++ Q_ASSERT(loc.line() > 0 && loc.column() > 0);
++ inheritedTypeNameIndex = typeNameIndex;
++ location = loc;
++ idNameIndex = idIndex;
++ id = -1;
++ indexOfDefaultPropertyOrAlias = -1;
++ defaultPropertyIsAlias = false;
++ flags = QV4::CompiledData::Object::NoFlag;
++ properties = pool->New<PoolList<Property> >();
++ aliases = pool->New<PoolList<Alias> >();
++ qmlEnums = pool->New<PoolList<Enum>>();
++ qmlSignals = pool->New<PoolList<Signal> >();
++ bindings = pool->New<PoolList<Binding> >();
++ functions = pool->New<PoolList<Function> >();
++ functionsAndExpressions = pool->New<PoolList<CompiledFunctionOrExpression> >();
++ inlineComponents = pool->New<PoolList<InlineComponent>>();
++ requiredPropertyExtraDatas = pool->New<PoolList<RequiredPropertyExtraData>>();
++ declarationsOverride = nullptr;
++ }
++
++ QString IRBuilder::sanityCheckFunctionNames(Object *obj, QQmlJS::SourceLocation *errorLocation)
++ {
++ QSet<int> functionNames;
++ for (auto functionit = obj->functionsBegin(); functionit != obj->functionsEnd(); ++functionit) {
++ Function *f = functionit.ptr;
++ errorLocation->startLine = f->location.line();
++ errorLocation->startColumn = f->location.column();
++ if (f->isQmlFunction) {
++ if (functionNames.contains(f->nameIndex))
++ return tr("Duplicate method name");
++ functionNames.insert(f->nameIndex);
++ }
++
++ for (auto signalit = obj->signalsBegin(); signalit != obj->signalsEnd(); ++signalit) {
++ QmlIR::Signal *s = signalit.ptr;
++ if (s->nameIndex == f->nameIndex)
++ return tr("Duplicate method name");
++ }
++
++ const QString name = stringAt(f->nameIndex);
++ Q_ASSERT(!f->isQmlFunction || !name.isEmpty());
++ if (!name.isEmpty() && name.at(0).isUpper())
++ return tr("Method names cannot begin with an upper case letter");
++ if (QV4::Compiler::Codegen::isNameGlobal(name))
++ return tr("Illegal method name");
++ }
++ return QString(); // no error
++ }
++
++ QString Object::appendEnum(Enum *enumeration)
++ {
++ Object *target = declarationsOverride;
++ if (!target)
++ target = this;
++
++ for (Enum *e = qmlEnums->first; e; e = e->next) {
++ if (e->nameIndex == enumeration->nameIndex)
++ return tr("Duplicate scoped enum name");
++ }
++
++ target->qmlEnums->append(enumeration);
++ return QString(); // no error
++ }
++
++ QString Object::appendSignal(Signal *signal)
++ {
++ Object *target = declarationsOverride;
++ if (!target)
++ target = this;
++
++ for (Signal *s = qmlSignals->first; s; s = s->next) {
++ if (s->nameIndex == signal->nameIndex)
++ return tr("Duplicate signal name");
++ }
++
++ target->qmlSignals->append(signal);
++ return QString(); // no error
++ }
++
++ QString Object::appendProperty(Property *prop, const QString &propertyName, bool isDefaultProperty, const QQmlJS::SourceLocation &defaultToken, QQmlJS::SourceLocation *errorLocation)
++ {
++ Object *target = declarationsOverride;
++ if (!target)
++ target = this;
++
++ for (Property *p = target->properties->first; p; p = p->next)
++ if (p->nameIndex() == prop->nameIndex())
++ return tr("Duplicate property name");
++
++ for (Alias *a = target->aliases->first; a; a = a->next)
++ if (a->nameIndex() == prop->nameIndex())
++ return tr("Property duplicates alias name");
++
++ if (propertyName.constData()->isUpper())
++ return tr("Property names cannot begin with an upper case letter");
++
++ const int index = target->properties->append(prop);
++ if (isDefaultProperty) {
++ if (target->indexOfDefaultPropertyOrAlias != -1) {
++ *errorLocation = defaultToken;
++ return tr("Duplicate default property");
++ }
++ target->indexOfDefaultPropertyOrAlias = index;
++ }
++ return QString(); // no error
++ }
++
++ QString Object::appendAlias(Alias *alias, const QString &aliasName, bool isDefaultProperty, const QQmlJS::SourceLocation &defaultToken, QQmlJS::SourceLocation *errorLocation)
++ {
++ Object *target = declarationsOverride;
++ if (!target)
++ target = this;
++
++ const auto aliasWithSameName = std::find_if(target->aliases->begin(), target->aliases->end(), [&alias](const Alias &targetAlias){
++ return targetAlias.nameIndex() == alias->nameIndex();
++ });
++ if (aliasWithSameName != target->aliases->end())
++ return tr("Duplicate alias name");
++
++ const auto aliasSameAsProperty = std::find_if(target->properties->begin(), target->properties->end(), [&alias](const Property &targetProp){
++ return targetProp.nameIndex() == alias->nameIndex();
++ });
++
++ if (aliasSameAsProperty != target->properties->end())
++ return tr("Alias has same name as existing property");
++
++ if (aliasName.constData()->isUpper())
++ return tr("Alias names cannot begin with an upper case letter");
++
++ const int index = target->aliases->append(alias);
++
++ if (isDefaultProperty) {
++ if (target->indexOfDefaultPropertyOrAlias != -1) {
++ *errorLocation = defaultToken;
++ return tr("Duplicate default property");
++ }
++ target->indexOfDefaultPropertyOrAlias = index;
++ target->defaultPropertyIsAlias = true;
++ }
++
++ return QString(); // no error
++ }
++
++ void Object::appendFunction(QmlIR::Function *f)
++ {
++ // Unlike properties, a function definition inside a grouped property does not go into
++ // the surrounding object. It's been broken since the Qt 5 era, and the semantics
++ // seems super confusing, so it wouldn't make sense to support that.
++ Q_ASSERT(!declarationsOverride);
++ functions->append(f);
++ }
++
++ void Object::appendInlineComponent(InlineComponent *ic)
++ {
++ inlineComponents->append(ic);
++ }
++
++ void Object::appendRequiredPropertyExtraData(RequiredPropertyExtraData *extraData)
++ {
++ requiredPropertyExtraDatas->append(extraData);
++ }
++
++ QString Object::appendBinding(Binding *b, bool isListBinding)
++ {
++ const bool bindingToDefaultProperty = (b->propertyNameIndex == quint32(0));
++ if (!isListBinding
++ && !bindingToDefaultProperty
++ && b->type() != QV4::CompiledData::Binding::Type_GroupedProperty
++ && b->type() != QV4::CompiledData::Binding::Type_AttachedProperty
++ && !b->hasFlag(QV4::CompiledData::Binding::IsOnAssignment)) {
++ Binding *existing = findBinding(b->propertyNameIndex);
++ if (existing
++ && existing->type() != QV4::CompiledData::Binding::Type_GroupedProperty
++ && existing->type() != QV4::CompiledData::Binding::Type_AttachedProperty
++ && !existing->hasFlag(QV4::CompiledData::Binding::IsOnAssignment)) {
++ return tr("Property value set multiple times");
++ }
++ }
++ if (bindingToDefaultProperty)
++ bindings->append(b);
++ else
++ bindings->prepend(b);
++ return QString(); // no error
++ }
++
++ Binding *Object::findBinding(quint32 nameIndex) const
++ {
++ for (Binding *b = bindings->first; b; b = b->next)
++ if (b->propertyNameIndex == nameIndex)
++ return b;
++ return nullptr;
++ }
++
++ QString Object::bindingAsString(Document *doc, int scriptIndex) const
++ {
++ CompiledFunctionOrExpression *foe = functionsAndExpressions->slowAt(scriptIndex);
++ QQmlJS::AST::Node *node = foe->node;
++ if (QQmlJS::AST::ExpressionStatement *exprStmt = QQmlJS::AST::cast<QQmlJS::AST::ExpressionStatement *>(node))
++ node = exprStmt->expression;
++ QQmlJS::SourceLocation start = node->firstSourceLocation();
++ QQmlJS::SourceLocation end = node->lastSourceLocation();
++ return doc->code.mid(start.offset, end.offset + end.length - start.offset);
++ }
++
++ QStringList Signal::parameterStringList(const QV4::Compiler::StringTableGenerator *stringPool) const
++ {
++ QStringList result;
++ result.reserve(parameters->count);
++ for (Parameter *param = parameters->first; param; param = param->next)
++ result << stringPool->stringForIndex(param->nameIndex);
++ return result;
++ }
++
++ Document::Document(const QString &fileName, const QString &finalUrl, bool debugMode)
++ : jsModule(fileName, finalUrl, debugMode)
++ , program(nullptr)
++ , jsGenerator(&jsModule)
++ {
++ }
++
++ ScriptDirectivesCollector::ScriptDirectivesCollector(Document *doc)
++ : document(doc)
++ , engine(&doc->jsParserEngine)
++ , jsGenerator(&doc->jsGenerator)
++ {
++ }
++
++ void ScriptDirectivesCollector::pragmaLibrary()
++ {
++ document->jsModule.unitFlags |= QV4::CompiledData::Unit::IsSharedLibrary;
++ }
++
++ void ScriptDirectivesCollector::importFile(const QString &jsfile, const QString &module, int lineNumber, int column)
++ {
++ QV4::CompiledData::Import *import = engine->pool()->New<QV4::CompiledData::Import>();
++ import->type = QV4::CompiledData::Import::ImportScript;
++ import->uriIndex = jsGenerator->registerString(jsfile);
++ import->qualifierIndex = jsGenerator->registerString(module);
++ import->location.set(lineNumber, column);
++ document->imports << import;
++ }
++
++ void ScriptDirectivesCollector::importModule(const QString &uri, const QString &version, const QString &module, int lineNumber, int column)
++ {
++ QV4::CompiledData::Import *import = engine->pool()->New<QV4::CompiledData::Import>();
++ import->type = QV4::CompiledData::Import::ImportLibrary;
++ import->uriIndex = jsGenerator->registerString(uri);
++ import->version = IRBuilder::extractVersion(version);
++ import->qualifierIndex = jsGenerator->registerString(module);
++ import->location.set(lineNumber, column);
++ document->imports << import;
++ }
++
++ IRBuilder::IRBuilder(FailureMode failureMode)
++ : _object(nullptr)
++ , _propertyDeclaration(nullptr)
++ , pool(nullptr)
++ , jsGenerator(nullptr)
++ , m_failureMode(failureMode)
++ {
++ }
++
++ bool IRBuilder::generateFromQml(const QString &code, const QString &url, Document *output,
++ QV4::Compiler::CodegenWarningInterface *wInterface)
++ {
++ QQmlJS::AST::UiProgram *program = nullptr;
++ {
++ QQmlJS::Lexer lexer(&output->jsParserEngine);
++ lexer.setCode(code, /*line = */ 1);
++
++ QQmlJS::Parser parser(&output->jsParserEngine);
++
++ const bool parseResult = parser.parse();
++ const auto diagnosticMessages = parser.diagnosticMessages();
++ if (!parseResult || !diagnosticMessages.isEmpty()) {
++ // Extract errors from the parser
++ for (const QQmlJS::DiagnosticMessage &m : diagnosticMessages) {
++ if (m.isWarning()) {
++ wInterface->reportParserWarnings(url, m.loc, m.message);
++ continue;
++ }
++
++ errors << m;
++ }
++
++ if (!errors.isEmpty() || !parseResult)
++ return false;
++ }
++ program = parser.ast();
++ Q_ASSERT(program);
++ }
++
++ output->code = code;
++ output->program = program;
++
++ qSwap(_imports, output->imports);
++ qSwap(_pragmas, output->pragmas);
++ qSwap(_objects, output->objects);
++ this->pool = output->jsParserEngine.pool();
++ this->jsGenerator = &output->jsGenerator;
++
++ Q_ASSERT(registerString(QString()) == emptyStringIndex);
++
++ sourceCode = code;
++
++ accept(program->headers);
++
++ if (program->members->next) {
++ QQmlJS::SourceLocation loc = program->members->next->firstSourceLocation();
++ recordError(loc, QCoreApplication::translate("QQmlParser", "Unexpected object definition"));
++ if (!shouldContinue())
++ return false;
++ }
++
++ QQmlJS::AST::UiObjectDefinition *rootObject = QQmlJS::AST::cast<QQmlJS::AST::UiObjectDefinition*>(program->members->member);
++ Q_ASSERT(rootObject);
++ int rootObjectIndex = -1;
++ if (defineQMLObject(&rootObjectIndex, rootObject)) {
++ Q_ASSERT(rootObjectIndex == 0);
++ }
++
++ qSwap(_imports, output->imports);
++ qSwap(_pragmas, output->pragmas);
++ qSwap(_objects, output->objects);
++
++ for (Object *object: std::as_const(output->objects)) {
++ object->simplifyRequiredProperties();
++
++ // Reorder aliases so that same-object dependencies come before their
++ // dependents. This later ensures the property cache ordering matches
++ // the alias table, which the runtime relies on.
++ object->sortAliasDependencies(output, &errors);
++ }
++
++ return errors.isEmpty();
++ }
++
++ bool IRBuilder::visit(QQmlJS::AST::UiProgram *)
++ {
++ Q_ASSERT(!"should not happen");
++ return false;
++ }
++
++ bool IRBuilder::visit(QQmlJS::AST::UiObjectDefinition *node)
++ {
++ // The grammar can't distinguish between two different definitions here:
++ // Item { ... }
++ // versus
++ // font { ... }
++ // The former is a new binding with no property name and "Item" as type name,
++ // and the latter is a binding to the font property with no type name but
++ // only initializer.
++
++ QQmlJS::AST::UiQualifiedId *lastId = node->qualifiedTypeNameId;
++ while (lastId->next)
++ lastId = lastId->next;
++ bool isType = lastId->name.data()->isUpper();
++ if (isType) {
++ int idx = 0;
++ if (!defineQMLObject(&idx, node))
++ return false;
++ const QQmlJS::SourceLocation nameLocation = node->qualifiedTypeNameId->identifierToken;
++ appendBinding(nameLocation, nameLocation, emptyStringIndex, idx);
++ } else {
++ int idx = 0;
++ const QQmlJS::SourceLocation location = node->qualifiedTypeNameId->firstSourceLocation();
++ if (!defineQMLObject(
++ &idx, /*qualfied type name id*/nullptr,
++ { location.startLine, location.startColumn }, node->initializer,
++ /*declarations should go here*/_object)) {
++ return false;
++ }
++ appendBinding(node->qualifiedTypeNameId, idx);
++ }
++ return false;
++ }
++
++ bool IRBuilder::visit(QQmlJS::AST::UiInlineComponent *ast)
++ {
++ int idx = -1;
++ if (insideInlineComponent) {
++ recordError(ast->firstSourceLocation(), QLatin1String("Nested inline components are not supported"));
++ if (!shouldContinue())
++ return false;
++ }
++ if (inlineComponentsNames.contains(ast->name.toString())) {
++ recordError(ast->firstSourceLocation(), QLatin1String("Inline component names must be unique per file"));
++ if (!shouldContinue())
++ return false;
++ } else {
++ inlineComponentsNames.insert(ast->name.toString());
++ }
++ {
++ QScopedValueRollback<bool> rollBack {insideInlineComponent, true};
++ if (!defineQMLObject(&idx, ast->component))
++ return false;
++ }
++ Q_ASSERT(idx > 0);
++ Object* definedObject = _objects.at(idx);
++ definedObject->flags |= QV4::CompiledData::Object::IsInlineComponentRoot;
++ definedObject->flags |= QV4::CompiledData::Object::IsPartOfInlineComponent;
++ auto inlineComponent = New<InlineComponent>();
++ inlineComponent->nameIndex = registerString(ast->name.toString());
++ inlineComponent->objectIndex = idx;
++ auto location = ast->firstSourceLocation();
++ inlineComponent->location.set(location.startLine, location.startColumn);
++ _object->appendInlineComponent(inlineComponent);
++ return false;
++ }
++
++ bool IRBuilder::visit(QQmlJS::AST::UiObjectBinding *node)
++ {
++ int idx = 0;
++ const QQmlJS::SourceLocation location = node->qualifiedTypeNameId->firstSourceLocation();
++ if (!defineQMLObject(&idx, node->qualifiedTypeNameId,
++ { location.startLine, location.startColumn }, node->initializer)) {
++ return false;
++ }
++ appendBinding(node->qualifiedId, idx, node->hasOnToken);
++ return false;
++ }
++
++ bool IRBuilder::visit(QQmlJS::AST::UiScriptBinding *node)
++ {
++ appendBinding(node->qualifiedId, node->statement, node);
++ return false;
++ }
++
++ bool IRBuilder::visit(QQmlJS::AST::UiArrayBinding *node)
++ {
++ const QQmlJS::SourceLocation qualifiedNameLocation = node->qualifiedId->identifierToken;
++ Object *object = nullptr;
++ QQmlJS::AST::UiQualifiedId *name = node->qualifiedId;
++ if (!resolveQualifiedId(&name, &object))
++ return false;
++
++ qSwap(_object, object);
++
++ const int propertyNameIndex = registerString(name->name.toString());
++
++ if (bindingsTarget()->findBinding(propertyNameIndex) != nullptr) {
++ recordError(name->identifierToken, tr("Property value set multiple times"));
++ if (!shouldContinue())
++ return false;
++ }
++
++ QVarLengthArray<QQmlJS::AST::UiArrayMemberList *, 16> memberList;
++ QQmlJS::AST::UiArrayMemberList *member = node->members;
++ while (member) {
++ memberList.append(member);
++ member = member->next;
++ }
++ for (int i = memberList.size() - 1; i >= 0; --i) {
++ member = memberList.at(i);
++ QQmlJS::AST::UiObjectDefinition *def = QQmlJS::AST::cast<QQmlJS::AST::UiObjectDefinition*>(member->member);
++
++ int idx = 0;
++ if (!defineQMLObject(&idx, def))
++ return false;
++ appendBinding(qualifiedNameLocation, name->identifierToken, propertyNameIndex, idx, /*isListItem*/ true);
++ }
++
++ qSwap(_object, object);
++ return false;
++ }
++
++ void IRBuilder::accept(QQmlJS::AST::Node *node)
++ {
++ QQmlJS::AST::Node::accept(node, this);
++ }
++
++ bool IRBuilder::defineQMLObject(
++ int *objectIndex, QQmlJS::AST::UiQualifiedId *qualifiedTypeNameId,
++ const QV4::CompiledData::Location &location, QQmlJS::AST::UiObjectInitializer *initializer,
++ Object *declarationsOverride)
++ {
++ if (QQmlJS::AST::UiQualifiedId *lastName = qualifiedTypeNameId) {
++ while (lastName->next)
++ lastName = lastName->next;
++ if (!lastName->name.constData()->isUpper()) {
++ recordError(lastName->identifierToken, tr("Expected type name"));
++ if (!shouldContinue())
++ return false;
++ }
++ }
++
++ Object *obj = New<Object>();
++
++ _objects.append(obj);
++ *objectIndex = _objects.size() - 1;
++ qSwap(_object, obj);
++
++ _object->init(pool, registerString(asString(qualifiedTypeNameId)), emptyStringIndex, location);
++ _object->declarationsOverride = declarationsOverride;
++ if (insideInlineComponent) {
++ _object->flags |= QV4::CompiledData::Object::IsPartOfInlineComponent;
++ }
++
++ // A new object is also a boundary for property declarations.
++ Property *declaration = nullptr;
++ qSwap(_propertyDeclaration, declaration);
++
++ accept(initializer);
++
++ qSwap(_propertyDeclaration, declaration);
++
++ qSwap(_object, obj);
++
++ if (!errors.isEmpty() && !shouldContinue())
++ return false;
++
++ QQmlJS::SourceLocation loc;
++ QString error = sanityCheckFunctionNames(obj, &loc);
++ if (!error.isEmpty()) {
++ recordError(loc, error);
++ if (!shouldContinue())
++ return false;
++ }
++
++ return true;
++ }
++
++ bool IRBuilder::visit(QQmlJS::AST::UiImport *node)
++ {
++ QString uri;
++ QV4::CompiledData::Import *import = New<QV4::CompiledData::Import>();
++
++ if (!node->fileName.isNull()) {
++ uri = node->fileName.toString();
++
++ if (uri.endsWith(QLatin1String(".js")) || uri.endsWith(QLatin1String(".mjs"))) {
++ import->type = QV4::CompiledData::Import::ImportScript;
++ } else {
++ import->type = QV4::CompiledData::Import::ImportFile;
++ }
++ } else {
++ import->type = QV4::CompiledData::Import::ImportLibrary;
++ uri = asString(node->importUri);
++ }
++
++ import->qualifierIndex = emptyStringIndex;
++
++ // Qualifier
++ if (!node->importId.isNull()) {
++ QString qualifier = node->importId.toString();
++ if (!qualifier.at(0).isUpper()) {
++ recordError(node->importIdToken, QCoreApplication::translate("QQmlParser","Invalid import qualifier '%1': must start with an uppercase letter").arg(qualifier));
++ if (!shouldContinue())
++ return false;
++ }
++ if (qualifier == QLatin1String("Qt")) {
++ recordError(node->importIdToken, QCoreApplication::translate("QQmlParser","Reserved name \"Qt\" cannot be used as an qualifier"));
++ if (!shouldContinue())
++ return false;
++ }
++ import->qualifierIndex = registerString(qualifier);
++
++ // Check for script qualifier clashes
++ bool isScript = import->type == QV4::CompiledData::Import::ImportScript;
++ for (int ii = 0; ii < _imports.size(); ++ii) {
++ const QV4::CompiledData::Import *other = _imports.at(ii);
++ bool otherIsScript = other->type == QV4::CompiledData::Import::ImportScript;
++
++ if ((isScript || otherIsScript) && qualifier == jsGenerator->stringForIndex(other->qualifierIndex)) {
++ recordError(node->importIdToken, QCoreApplication::translate("QQmlParser","Script import qualifiers must be unique."));
++ if (!shouldContinue())
++ return false;
++ }
++ }
++
++ } else if (import->type == QV4::CompiledData::Import::ImportScript) {
++ recordError(node->fileNameToken, QCoreApplication::translate("QQmlParser","Script import requires a qualifier"));
++ if (!shouldContinue())
++ return false;
++ }
++
++ if (node->version) {
++ import->version = node->version->version;
++ } else {
++ // Otherwise initialize the major and minor version to invalid to signal "latest".
++ import->version = QTypeRevision();
++ }
++
++ import->location.set(node->importToken.startLine, node->importToken.startColumn);
++
++ import->uriIndex = registerString(uri);
++
++ _imports.append(import);
++
++ return false;
++ }
++
++
++ template<typename Argument>
++ struct PragmaParser
++ {
++ static bool run(IRBuilder *builder, QQmlJS::AST::UiPragma *node, Pragma *pragma)
++ {
++ Q_ASSERT(builder);
++ Q_ASSERT(node);
++ Q_ASSERT(pragma);
++
++ bool isValid = true;
++ if (!isUnique(builder)) {
++ builder->recordError(
++ node->pragmaToken, QCoreApplication::translate(
++ "QQmlParser", "Multiple %1 pragmas found").arg(name()));
++ if (!builder->shouldContinue())
++ return false;
++ isValid = false;
++ }
++
++ pragma->type = type();
++
++ if (QQmlJS::AST::UiPragmaValueList *bad = assign(pragma, node->values)) {
++ builder->recordError(
++ node->pragmaToken, QCoreApplication::translate(
++ "QQmlParser", "Unknown %1 '%2' in pragma").arg(name(), bad->value));
++ if (!builder->shouldContinue())
++ return false;
++ isValid = false;
++ }
++
++ return isValid;
++ }
++
++ private:
++ static constexpr Pragma::PragmaType type()
++ {
++ if constexpr (std::is_same_v<Argument, Pragma::ComponentBehaviorValue>) {
++ return Pragma::ComponentBehavior;
++ } else if constexpr (std::is_same_v<Argument, Pragma::ListPropertyAssignBehaviorValue>) {
++ return Pragma::ListPropertyAssignBehavior;
++ } else if constexpr (std::is_same_v<Argument, Pragma::FunctionSignatureBehaviorValue>) {
++ return Pragma::FunctionSignatureBehavior;
++ } else if constexpr (std::is_same_v<Argument, Pragma::NativeMethodBehaviorValue>) {
++ return Pragma::NativeMethodBehavior;
++ } else if constexpr (std::is_same_v<Argument, Pragma::ValueTypeBehaviorValue>) {
++ return Pragma::ValueTypeBehavior;
++ }
++
++ Q_UNREACHABLE_RETURN(Pragma::PragmaType(-1));
++ }
++
++ template<typename F>
++ static QQmlJS::AST::UiPragmaValueList *iterateValues(
++ QQmlJS::AST::UiPragmaValueList *input, F &&process)
++ {
++ for (QQmlJS::AST::UiPragmaValueList *i = input; i; i = i->next) {
++ if (!process(i->value))
++ return i;
++ }
++ return nullptr;
++ }
++
++ static QQmlJS::AST::UiPragmaValueList *assign(
++ Pragma *pragma, QQmlJS::AST::UiPragmaValueList *values)
++ {
++ // We could use QMetaEnum here to make the code more compact,
++ // but it's probably more expensive.
++
++ if constexpr (std::is_same_v<Argument, Pragma::ComponentBehaviorValue>) {
++ return iterateValues(values, [pragma](QStringView value) {
++ if (value == "Unbound"_L1) {
++ pragma->componentBehavior = Pragma::Unbound;
++ return true;
++ }
++ if (value == "Bound"_L1) {
++ pragma->componentBehavior = Pragma::Bound;
++ return true;
++ }
++ return false;
++ });
++ } else if constexpr (std::is_same_v<Argument, Pragma::ListPropertyAssignBehaviorValue>) {
++ return iterateValues(values, [pragma](QStringView value) {
++ if (value == "Append"_L1) {
++ pragma->listPropertyAssignBehavior = Pragma::Append;
++ return true;
++ }
++ if (value == "Replace"_L1) {
++ pragma->listPropertyAssignBehavior = Pragma::Replace;
++ return true;
++ }
++ if (value == "ReplaceIfNotDefault"_L1) {
++ pragma->listPropertyAssignBehavior = Pragma::ReplaceIfNotDefault;
++ return true;
++ }
++ return false;
++ });
++ } else if constexpr (std::is_same_v<Argument, Pragma::FunctionSignatureBehaviorValue>) {
++ return iterateValues(values, [pragma](QStringView value) {
++ if (value == "Ignored"_L1) {
++ pragma->functionSignatureBehavior = Pragma::Ignored;
++ return true;
++ }
++ if (value == "Enforced"_L1) {
++ pragma->functionSignatureBehavior = Pragma::Enforced;
++ return true;
++ }
++ return false;
++ });
++ } else if constexpr (std::is_same_v<Argument, Pragma::NativeMethodBehaviorValue>) {
++ return iterateValues(values, [pragma](QStringView value) {
++ if (value == "AcceptThisObject"_L1) {
++ pragma->nativeMethodBehavior = Pragma::AcceptThisObject;
++ return true;
++ }
++ if (value == "RejectThisObject"_L1) {
++ pragma->nativeMethodBehavior = Pragma::RejectThisObject;
++ return true;
++ }
++ return false;
++ });
++ } else if constexpr (std::is_same_v<Argument, Pragma::ValueTypeBehaviorValue>) {
++ pragma->valueTypeBehavior = Pragma::ValueTypeBehaviorValues().toInt();
++ return iterateValues(values, [pragma](QStringView value) {
++ const auto setFlag = [pragma](Pragma::ValueTypeBehaviorValue flag, bool value) {
++ pragma->valueTypeBehavior
++ = Pragma::ValueTypeBehaviorValues(pragma->valueTypeBehavior)
++ .setFlag(flag, value).toInt();
++ };
++
++ if (value == "Reference"_L1) {
++ setFlag(Pragma::Copy, false);
++ return true;
++ }
++ if (value == "Copy"_L1) {
++ setFlag(Pragma::Copy, true);
++ return true;
++ }
++
++ if (value == "Inaddressable"_L1) {
++ setFlag(Pragma::Addressable, false);
++ return true;
++ }
++ if (value == "Addressable"_L1) {
++ setFlag(Pragma::Addressable, true);
++ return true;
++ }
++
++ if (value == "Inassertable"_L1) {
++ setFlag(Pragma::Assertable, false);
++ return true;
++ }
++ if (value == "Assertable"_L1) {
++ setFlag(Pragma::Assertable, true);
++ return true;
++ }
++
++ return false;
++ });
++ }
++
++ Q_UNREACHABLE_RETURN(nullptr);
++ }
++
++ static bool isUnique(const IRBuilder *builder)
++ {
++ for (const Pragma *prev : builder->_pragmas) {
++ if (prev->type == type())
++ return false;
++ }
++ return true;
++ };
++
++ static QLatin1StringView name()
++ {
++ switch (type()) {
++ case Pragma::ListPropertyAssignBehavior:
++ return "list property assign behavior"_L1;
++ case Pragma::ComponentBehavior:
++ return "component behavior"_L1;
++ case Pragma::FunctionSignatureBehavior:
++ return "function signature behavior"_L1;
++ case Pragma::NativeMethodBehavior:
++ return "native method behavior"_L1;
++ case Pragma::ValueTypeBehavior:
++ return "value type behavior"_L1;
++ default:
++ break;
++ }
++ Q_UNREACHABLE_RETURN(QLatin1StringView());
++ }
++ };
++
++ bool IRBuilder::visit(QQmlJS::AST::UiPragma *node)
++ {
++ Pragma *pragma = New<Pragma>();
++ bool isValid = true;
++
++ if (!node->name.isNull()) {
++ if (node->name == "Singleton"_L1) {
++ pragma->type = Pragma::Singleton;
++ } else if (node->name == "Strict"_L1) {
++ pragma->type = Pragma::Strict;
++ } else if (node->name == "ComponentBehavior"_L1) {
++ if (!PragmaParser<Pragma::ComponentBehaviorValue>::run(this, node, pragma)) {
++ if (!shouldContinue())
++ return false;
++ isValid = false;
++ }
++ } else if (node->name == "ListPropertyAssignBehavior"_L1) {
++ if (!PragmaParser<Pragma::ListPropertyAssignBehaviorValue>::run(this, node, pragma)) {
++ if (!shouldContinue())
++ return false;
++ isValid = false;
++ }
++ } else if (node->name == "FunctionSignatureBehavior"_L1) {
++ if (!PragmaParser<Pragma::FunctionSignatureBehaviorValue>::run(this, node, pragma)) {
++ if (!shouldContinue())
++ return false;
++ isValid = false;
++ }
++ } else if (node->name == "NativeMethodBehavior"_L1) {
++ if (!PragmaParser<Pragma::NativeMethodBehaviorValue>::run(this, node, pragma)) {
++ if (!shouldContinue())
++ return false;
++ isValid = false;
++ }
++ } else if (node->name == "ValueTypeBehavior"_L1) {
++ if (!PragmaParser<Pragma::ValueTypeBehaviorValue>::run(this, node, pragma)) {
++ if (!shouldContinue())
++ return false;
++ isValid = false;
++ }
++ } else if (node->name == "Translator"_L1) {
++ pragma->type = Pragma::Translator;
++ pragma->translationContextIndex = registerString(node->values->value.toString());
++
++ } else {
++ recordError(node->pragmaToken, QCoreApplication::translate(
++ "QQmlParser", "Unknown pragma '%1'").arg(node->name));
++ if (!shouldContinue())
++ return false;
++ isValid = false;
++ }
++ } else {
++ recordError(node->pragmaToken, QCoreApplication::translate(
++ "QQmlParser", "Empty pragma found"));
++ if (!shouldContinue())
++ return false;
++ isValid = false;
++ }
++
++ if (isValid) {
++ pragma->location.set(node->pragmaToken.startLine, node->pragmaToken.startColumn);
++ _pragmas.append(pragma);
++ }
++
++ return false;
++ }
++
++ static QStringList astNodeToStringList(QQmlJS::AST::Node *node)
++ {
++ if (node->kind == QQmlJS::AST::Node::Kind_IdentifierExpression) {
++ QString name =
++ static_cast<QQmlJS::AST::IdentifierExpression *>(node)->name.toString();
++ return QStringList() << name;
++ } else if (node->kind == QQmlJS::AST::Node::Kind_FieldMemberExpression) {
++ QQmlJS::AST::FieldMemberExpression *expr = static_cast<QQmlJS::AST::FieldMemberExpression *>(node);
++
++ QStringList rv = astNodeToStringList(expr->base);
++ if (rv.isEmpty())
++ return rv;
++ rv.append(expr->name.toString());
++ return rv;
++ }
++ return QStringList();
++ }
++
++ bool IRBuilder::visit(QQmlJS::AST::UiEnumDeclaration *node)
++ {
++ Enum *enumeration = New<Enum>();
++ bool isValid = true;
++ QString enumName = node->name.toString();
++ enumeration->nameIndex = registerString(enumName);
++
++ if (enumName.at(0).isLower()) {
++ COMPILE_EXCEPTION(node->enumToken, tr("Scoped enum names must begin with an upper case letter"));
++ isValid = false;
++ }
++
++ enumeration->location.set(node->enumToken.startLine, node->enumToken.startColumn);
++
++ enumeration->enumValues = New<PoolList<EnumValue>>();
++
++ QQmlJS::AST::UiEnumMemberList *e = node->members;
++ while (e) {
++ EnumValue *enumValue = New<EnumValue>();
++ QString member = e->member.toString();
++ enumValue->nameIndex = registerString(member);
++ bool isValueValid = true;
++ if (member.at(0).isLower()) {
++ COMPILE_EXCEPTION(e->memberToken, tr("Enum names must begin with an upper case letter"));
++ isValueValid = false;
++ }
++
++ double part;
++ if (std::modf(e->value, &part) != 0.0) {
++ COMPILE_EXCEPTION(e->valueToken, tr("Enum value must be an integer"));
++ isValueValid = false;
++ }
++ if (e->value > std::numeric_limits<qint32>::max()
++ || e->value < std::numeric_limits<qint32>::min()) {
++ COMPILE_EXCEPTION(e->valueToken, tr("Enum value out of range"));
++ isValueValid = false;
++ }
++ if (isValueValid) {
++ enumValue->value = e->value;
++ enumeration->enumValues->append(enumValue);
++ } else {
++ isValid = false;
++ }
++
++ e = e->next;
++ }
++
++ QString error;
++ if (isValid)
++ error = _object->appendEnum(enumeration);
++ if (!error.isEmpty()) {
++ recordError(node->enumToken, error);
++ if (!shouldContinue())
++ return false;
++ }
++
++ return false;
++ }
++
++
++ bool IRBuilder::visit(QQmlJS::AST::UiPublicMember *node)
++ {
++ if (node->type == QQmlJS::AST::UiPublicMember::Signal) {
++ Signal *signal = New<Signal>();
++ bool isValid = true;
++ const QString signalName = node->name.toString();
++ signal->nameIndex = registerString(signalName);
++
++ QQmlJS::SourceLocation loc = node->typeToken;
++ signal->location.set(loc.startLine, loc.startColumn);
++
++ signal->parameters = New<PoolList<Parameter> >();
++
++ QQmlJS::AST::UiParameterList *p = node->parameters;
++ while (p) {
++ if (!p->type) {
++ recordError(node->typeToken, QCoreApplication::translate("QQmlParser","Expected parameter type"));
++ if (!shouldContinue())
++ return false;
++ isValid = false;
++ p = p->next;
++ continue;
++ }
++
++ Parameter *param = New<Parameter>();
++ param->nameIndex = registerString(p->name.toString());
++ if (!Parameter::initType(
++ ¶m->type, [this](const QString &str) { return registerString(str); },
++ p->type)) {
++ QString errStr = QCoreApplication::translate("QQmlParser","Invalid signal parameter type: ");
++ errStr.append(p->type->toString());
++ recordError(node->typeToken, errStr);
++ if (!shouldContinue())
++ return false;
++ isValid = false;
++ p = p->next;
++ continue;
++ }
++ signal->parameters->append(param);
++ p = p->next;
++ }
++
++ for (const QChar &ch : signalName) {
++ if (ch.isLower())
++ break;
++ if (ch.isUpper()) {
++ COMPILE_EXCEPTION(node->identifierToken,
++ tr("Signal names cannot begin with an upper case letter"));
++ isValid = false;
++ break;
++ }
++ }
++
++ if (QV4::Compiler::Codegen::isNameGlobal(signalName)) {
++ COMPILE_EXCEPTION(node->identifierToken, tr("Illegal signal name"));
++ isValid = false;
++ }
++
++ QString error;
++ if (isValid)
++ error = _object->appendSignal(signal);
++ if (!error.isEmpty()) {
++ recordError(node->identifierToken, error);
++ if (!shouldContinue())
++ return false;
++ }
++ } else {
++ QString memberType = asString(node->memberType);
++ if (memberType == QLatin1String("alias")) {
++ return appendAlias(node);
++ } else {
++ QStringView name = node->name;
++ bool isValid = true;
++
++ Property *property = New<Property>();
++ property->setIsReadOnly(node->isReadonly());
++ property->setIsRequired(node->isRequired());
++ property->setIsVirtual(node->isVirtual());
++ property->setIsOverride(node->isOverride());
++ property->setIsFinal(node->isFinal());
++
++ const QV4::CompiledData::CommonType builtinPropertyType
++ = Parameter::stringToBuiltinType(memberType);
++ if (builtinPropertyType != QV4::CompiledData::CommonType::Invalid)
++ property->setCommonType(builtinPropertyType);
++ else
++ property->setTypeNameIndex(registerString(memberType));
++
++ QStringView typeModifier = node->typeModifier;
++ if (typeModifier == QLatin1String("list")) {
++ property->setIsList(true);
++ } else if (!typeModifier.isEmpty()) {
++ recordError(node->typeModifierToken, QCoreApplication::translate("QQmlParser","Invalid property type modifier"));
++ if (!shouldContinue())
++ return false;
++ isValid = false;
++ }
++
++ const QString propName = name.toString();
++ property->setNameIndex(registerString(propName));
++
++ QQmlJS::SourceLocation loc = node->firstSourceLocation();
++ property->location.set(loc.startLine, loc.startColumn);
++
++ QQmlJS::SourceLocation errorLocation;
++ QString error;
++
++ if (QV4::Compiler::Codegen::isNameGlobal(propName))
++ error = tr("Illegal property name");
++ else if (isValid)
++ error = _object->appendProperty(property, propName, node->isDefaultMember(), node->defaultToken(), &errorLocation);
++
++ if (!error.isEmpty()) {
++ if (errorLocation.startLine == 0)
++ errorLocation = node->identifierToken;
++
++ recordError(errorLocation, error);
++ if (!shouldContinue())
++ return false;
++ isValid = false;
++ }
++
++ if (isValid) {
++ qSwap(_propertyDeclaration, property);
++ if (node->binding) {
++ // process QML-like initializers (e.g. property Object o: Object {})
++ QQmlJS::AST::Node::accept(node->binding, this);
++ } else if (node->statement) {
++ if (!isRedundantNullInitializerForPropertyDeclaration(_propertyDeclaration,
++ node->statement)) {
++ appendBinding(node->identifierToken, node->identifierToken,
++ _propertyDeclaration->nameIndex(), node->statement, node);
++ }
++ }
++ qSwap(_propertyDeclaration, property);
++ }
++ }
++ }
++
++ return false;
++ }
++
++ void IRBuilder::registerFunctionExpr(QQmlJS::AST::FunctionExpression *fexp, IsQmlFunction isQmlFunction)
++ {
++ CompiledFunctionOrExpression *foe = New<CompiledFunctionOrExpression>();
++ foe->node = fexp;
++ foe->parentNode = fexp;
++ foe->nameIndex = registerString(fexp->name.toString());
++ const int index = _object->functionsAndExpressions->append(foe);
++
++ Function *f = New<Function>();
++ QQmlJS::SourceLocation loc = fexp->identifierToken;
++ f->location.set(loc.startLine, loc.startColumn);
++ f->index = index;
++ f->nameIndex = registerString(fexp->name.toString());
++ f->isQmlFunction = isQmlFunction == IsQmlFunction::Yes;
++
++ const auto idGenerator = [this](const QString &str) { return registerString(str); };
++
++ Parameter::initType(
++ &f->returnType, idGenerator,
++ fexp->typeAnnotation ? fexp->typeAnnotation->type : nullptr);
++
++ const QQmlJS::AST::BoundNames formals = fexp->formals ? fexp->formals->formals()
++ : QQmlJS::AST::BoundNames();
++ int formalsCount = formals.size();
++ f->formals.allocate(pool, formalsCount);
++
++ int i = 0;
++ for (const auto &arg : formals) {
++ Parameter *functionParameter = &f->formals[i];
++ functionParameter->nameIndex = registerString(arg.id);
++ Parameter::initType(
++ &functionParameter->type, idGenerator,
++ arg.typeAnnotation.isNull() ? nullptr : arg.typeAnnotation->type);
++ ++i;
++ }
++
++ _object->appendFunction(f);
++ }
++
++ bool IRBuilder::visit(QQmlJS::AST::UiSourceElement *node)
++ {
++ if (QQmlJS::AST::FunctionExpression *funDecl = node->sourceElement->asFunctionDefinition()) {
++ if (_object->declarationsOverride) {
++ // See Object::appendFunction() for why.
++ recordError(node->firstSourceLocation(),
++ QCoreApplication::translate(
++ "QQmlParser", "Function declaration inside grouped property"));
++ if (!shouldContinue())
++ return false;
++ } else {
++ registerFunctionExpr(funDecl, IsQmlFunction::Yes);
++ }
++ } else {
++ recordError(node->firstSourceLocation(), QCoreApplication::translate("QQmlParser","JavaScript declaration outside Script element"));
++ }
++ return false;
++ }
++
++ bool IRBuilder::visit(AST::UiRequired *ast)
++ {
++ auto extraData = New<RequiredPropertyExtraData>();
++ extraData->nameIndex = registerString(ast->name.toString());
++ _object->appendRequiredPropertyExtraData(extraData);
++ return false;
++ }
++
++ QString IRBuilder::asString(QQmlJS::AST::UiQualifiedId *node)
++ {
++ QString s;
++
++ for (QQmlJS::AST::UiQualifiedId *it = node; it; it = it->next) {
++ s.append(it->name);
++
++ if (it->next)
++ s.append(QLatin1Char('.'));
++ }
++
++ return s;
++ }
++
++ QStringView IRBuilder::asStringRef(QQmlJS::AST::Node *node)
++ {
++ if (!node)
++ return QStringView();
++
++ return textRefAt(node->firstSourceLocation(), node->lastSourceLocation());
++ }
++
++ QTypeRevision IRBuilder::extractVersion(QStringView string)
++ {
++ if (string.isEmpty())
++ return QTypeRevision();
++
++ const int dot = string.indexOf(QLatin1Char('.'));
++ return (dot < 0)
++ ? QTypeRevision::fromMajorVersion(string.toInt())
++ : QTypeRevision::fromVersion(string.left(dot).toInt(), string.mid(dot + 1).toInt());
++ }
++
++ QStringView IRBuilder::textRefAt(const QQmlJS::SourceLocation &first, const QQmlJS::SourceLocation &last) const
++ {
++ return QStringView(sourceCode).mid(first.offset, last.offset + last.length - first.offset);
++ }
++
++ void IRBuilder::setBindingValue(QV4::CompiledData::Binding *binding, QQmlJS::AST::Statement *statement, QQmlJS::AST::Node *parentNode)
++ {
++ QQmlJS::SourceLocation loc = statement->firstSourceLocation();
++ binding->valueLocation.set(loc.startLine, loc.startColumn);
++ binding->setType(QV4::CompiledData::Binding::Type_Invalid);
++ if (_propertyDeclaration && _propertyDeclaration->isReadOnly())
++ binding->setFlag(QV4::CompiledData::Binding::InitializerForReadOnlyDeclaration);
++
++ QQmlJS::AST::ExpressionStatement *exprStmt = QQmlJS::AST::cast<QQmlJS::AST::ExpressionStatement *>(statement);
++ if (exprStmt) {
++ QQmlJS::AST::ExpressionNode * const expr = exprStmt->expression;
++ if (QQmlJS::AST::StringLiteral *lit = QQmlJS::AST::cast<QQmlJS::AST::StringLiteral *>(expr)) {
++ binding->setType(QV4::CompiledData::Binding::Type_String);
++ binding->stringIndex = registerString(lit->value.toString());
++ } else if (QQmlJS::AST::TemplateLiteral *templateLit = QQmlJS::AST::cast<QQmlJS::AST::TemplateLiteral *>(expr);
++ templateLit && templateLit->hasNoSubstitution) {
++ // A template literal without substitution is just a string.
++ // With substitution, it could however be an arbitrarily complex expression
++ binding->setType(QV4::CompiledData::Binding::Type_String);
++ binding->stringIndex = registerString(templateLit->value.toString());
++ } else if (expr->kind == QQmlJS::AST::Node::Kind_TrueLiteral) {
++ binding->setType(QV4::CompiledData::Binding::Type_Boolean);
++ binding->value.b = true;
++ } else if (expr->kind == QQmlJS::AST::Node::Kind_FalseLiteral) {
++ binding->setType(QV4::CompiledData::Binding::Type_Boolean);
++ binding->value.b = false;
++ } else if (QQmlJS::AST::NumericLiteral *lit = QQmlJS::AST::cast<QQmlJS::AST::NumericLiteral *>(expr)) {
++ binding->setType(QV4::CompiledData::Binding::Type_Number);
++ binding->value.constantValueIndex = jsGenerator->registerConstant(QV4::Encode(lit->value));
++ } else if (QQmlJS::AST::CallExpression *call = QQmlJS::AST::cast<QQmlJS::AST::CallExpression *>(expr)) {
++ if (QQmlJS::AST::IdentifierExpression *base = QQmlJS::AST::cast<QQmlJS::AST::IdentifierExpression *>(call->base)) {
++ tryGeneratingTranslationBinding(base->name, call->arguments, binding);
++ // If it wasn't a translation binding, a normal script binding will be generated
++ // below.
++ }
++ } else if (QQmlJS::AST::cast<QQmlJS::AST::FunctionExpression *>(expr)) {
++ binding->setFlag(QV4::CompiledData::Binding::IsFunctionExpression);
++ } else if (QQmlJS::AST::UnaryMinusExpression *unaryMinus = QQmlJS::AST::cast<QQmlJS::AST::UnaryMinusExpression *>(expr)) {
++ if (QQmlJS::AST::NumericLiteral *lit = QQmlJS::AST::cast<QQmlJS::AST::NumericLiteral *>(unaryMinus->expression)) {
++ binding->setType(QV4::CompiledData::Binding::Type_Number);
++ binding->value.constantValueIndex = jsGenerator->registerConstant(QV4::Encode(-lit->value));
++ }
++ } else if (QQmlJS::AST::cast<QQmlJS::AST::NullExpression *>(expr)) {
++ binding->setType(QV4::CompiledData::Binding::Type_Null);
++ binding->value.nullMarker = 0;
++ }
++ }
++
++ // Do binding instead
++ if (binding->type() == QV4::CompiledData::Binding::Type_Invalid) {
++ binding->setType(QV4::CompiledData::Binding::Type_Script);
++
++ CompiledFunctionOrExpression *expr = New<CompiledFunctionOrExpression>();
++ expr->node = statement;
++ expr->parentNode = parentNode;
++ expr->nameIndex = registerString(QLatin1String("expression for ")
++ + stringAt(binding->propertyNameIndex));
++ const int index = bindingsTarget()->functionsAndExpressions->append(expr);
++ binding->value.compiledScriptIndex = index;
++ // We don't need to store the binding script as string, except for script strings
++ // and types with custom parsers. Those will be added later in the compilation phase.
++ // Except that we cannot recover the string when cachegen runs; we need to therefore retain
++ // "undefined" and "[]" (for QQmlListModel). Any other "special" strings (for the various
++ // literals) are already handled above
++ QQmlJS::AST::Node *nodeForString = statement;
++ if (exprStmt)
++ nodeForString = exprStmt->expression;
++ const QStringView source = asStringRef(nodeForString);
++ if (source == u"undefined") {
++ binding->stringIndex = registerString(u"undefined"_s);
++ } else if (qualifiedEnumDot(source) != -1) {
++ binding->stringIndex = registerString(source.toString());
++ } else if (const auto *arrayPattern =
++ QQmlJS::AST::cast<QQmlJS::AST::ArrayPattern *>(nodeForString);
++ arrayPattern && !arrayPattern->elements) {
++ binding->stringIndex = registerString(u"[]"_s);
++ } else {
++ binding->stringIndex = emptyStringIndex;
++ }
++ }
++ }
++
++ void IRBuilder::tryGeneratingTranslationBinding(QStringView base, AST::ArgumentList *args, QV4::CompiledData::Binding *binding)
++ {
++ const auto registerString = [&](QStringView string) {
++ return jsGenerator->registerString(string.toString()) ;
++ };
++
++ const auto finalizeTranslationData = [&](
++ QV4::CompiledData::Binding::Type type,
++ QV4::CompiledData::TranslationData translationData) {
++ binding->setType(type);
++ if (type == QV4::CompiledData::Binding::Type_Translation
++ || type == QV4::CompiledData::Binding::Type_TranslationById) {
++ binding->value.translationDataIndex = jsGenerator->registerTranslation(translationData);
++ } else if (type == QV4::CompiledData::Binding::Type_String) {
++ binding->stringIndex = translationData.number;
++ }
++ };
++
++ tryGeneratingTranslationBindingBase(
++ base, args,
++ registerString, registerString, registerString, finalizeTranslationData);
++ }
++
++ void IRBuilder::appendBinding(QQmlJS::AST::UiQualifiedId *name, QQmlJS::AST::Statement *value, QQmlJS::AST::Node *parentNode)
++ {
++ const QQmlJS::SourceLocation qualifiedNameLocation = name->identifierToken;
++ Object *object = nullptr;
++ if (!resolveQualifiedId(&name, &object))
++ return;
++ if (_object == object && name->name == QLatin1String("id")) {
++ setId(name->identifierToken, value);
++ return;
++ }
++ qSwap(_object, object);
++ appendBinding(qualifiedNameLocation, name->identifierToken, registerString(name->name.toString()), value, parentNode);
++ qSwap(_object, object);
++ }
++
++ void IRBuilder::appendBinding(QQmlJS::AST::UiQualifiedId *name, int objectIndex, bool isOnAssignment)
++ {
++ const QQmlJS::SourceLocation qualifiedNameLocation = name->identifierToken;
++ Object *object = nullptr;
++ if (!resolveQualifiedId(&name, &object, isOnAssignment))
++ return;
++ qSwap(_object, object);
++ appendBinding(qualifiedNameLocation, name->identifierToken, registerString(name->name.toString()), objectIndex, /*isListItem*/false, isOnAssignment);
++ qSwap(_object, object);
++ }
++
++ void IRBuilder::appendBinding(const QQmlJS::SourceLocation &qualifiedNameLocation, const QQmlJS::SourceLocation &nameLocation, quint32 propertyNameIndex,
++ QQmlJS::AST::Statement *value, QQmlJS::AST::Node *parentNode)
++ {
++ Binding *binding = New<Binding>();
++ binding->propertyNameIndex = propertyNameIndex;
++ binding->offset = nameLocation.offset;
++ binding->location.set(nameLocation.startLine, nameLocation.startColumn);
++ binding->clearFlags();
++ setBindingValue(binding, value, parentNode);
++ QString error = bindingsTarget()->appendBinding(binding, /*isListBinding*/false);
++ if (!error.isEmpty()) {
++ recordError(qualifiedNameLocation, error);
++ }
++ }
++
++ void IRBuilder::appendBinding(const QQmlJS::SourceLocation &qualifiedNameLocation, const QQmlJS::SourceLocation &nameLocation, quint32 propertyNameIndex, int objectIndex, bool isListItem, bool isOnAssignment)
++ {
++ if (stringAt(propertyNameIndex) == QLatin1String("id")) {
++ recordError(nameLocation, tr("Invalid component id specification"));
++ if (!shouldContinue())
++ return;
++ } else {
++ Binding *binding = New<Binding>();
++ binding->propertyNameIndex = propertyNameIndex;
++ binding->offset = nameLocation.offset;
++ binding->location.set(nameLocation.startLine, nameLocation.startColumn);
++
++ const Object *obj = _objects.at(objectIndex);
++ binding->valueLocation = obj->location;
++
++ binding->clearFlags();
++
++ if (_propertyDeclaration && _propertyDeclaration->isReadOnly())
++ binding->setFlag(Binding::InitializerForReadOnlyDeclaration);
++
++ // No type name on the initializer means it must be a grouped property
++ if (_objects.at(objectIndex)->inheritedTypeNameIndex == emptyStringIndex)
++ binding->setType(Binding::Type_GroupedProperty);
++ else
++ binding->setType(Binding::Type_Object);
++
++ if (isOnAssignment)
++ binding->setFlag(Binding::IsOnAssignment);
++ if (isListItem)
++ binding->setFlag(Binding::IsListItem);
++
++ binding->value.objectIndex = objectIndex;
++ QString error = bindingsTarget()->appendBinding(binding, isListItem);
++ if (!error.isEmpty())
++ recordError(qualifiedNameLocation, error);
++ }
++ }
++
++ bool IRBuilder::appendAlias(QQmlJS::AST::UiPublicMember *node)
++ {
++ Alias *alias = New<Alias>();
++ bool isValid = true;
++ alias->setIsReadOnly(node->isReadonly());
++
++ const QString propName = node->name.toString();
++ alias->setNameIndex(registerString(propName));
++
++ QQmlJS::SourceLocation loc = node->firstSourceLocation();
++ alias->setLocation({loc.startLine, loc.startColumn});
++
++ alias->setPropertyNameIndex(emptyStringIndex);
++
++ if (!node->statement && !node->binding) {
++ COMPILE_EXCEPTION(loc, tr("No property alias location"));
++ isValid = false;
++ }
++
++ QQmlJS::SourceLocation rhsLoc;
++ if (node->binding)
++ rhsLoc = node->binding->firstSourceLocation();
++ else if (node->statement)
++ rhsLoc = node->statement->firstSourceLocation();
++ else
++ rhsLoc = node->semicolonToken;
++ alias->setReferenceLocation({rhsLoc.startLine, rhsLoc.startColumn});
++
++ QStringList aliasReference;
++
++ if (QQmlJS::AST::ExpressionStatement *stmt = QQmlJS::AST::cast<QQmlJS::AST::ExpressionStatement*>(node->statement)) {
++ aliasReference = astNodeToStringList(stmt->expression);
++ if (aliasReference.isEmpty()) {
++ if (isStatementNodeScript(node->statement)) {
++ COMPILE_EXCEPTION(rhsLoc, tr("Invalid alias reference. An alias reference must be specified as <id>, <id>.<property> or <id>.<value property>.<property>"));
++ } else {
++ COMPILE_EXCEPTION(rhsLoc, tr("Invalid alias location"));
++ }
++ isValid = false;
++ }
++ } else {
++ COMPILE_EXCEPTION(rhsLoc, tr("Invalid alias reference. An alias reference must be specified as <id>, <id>.<property> or <id>.<value property>.<property>"));
++ isValid = false;
++ }
++
++ if (isValid && (aliasReference.size() < 1 || aliasReference.size() > 3)) {
++ COMPILE_EXCEPTION(rhsLoc, tr("Invalid alias reference. An alias reference must be specified as <id>, <id>.<property> or <id>.<value property>.<property>"));
++ isValid = false;
++ }
++
++ if (isValid)
++ alias->setIdIndex(registerString(aliasReference.first()));
++
++ QString propertyValue = aliasReference.value(1);
++ if (aliasReference.size() == 3)
++ propertyValue += QLatin1Char('.') + aliasReference.at(2);
++ alias->setPropertyNameIndex(registerString(propertyValue));
++
++ QQmlJS::SourceLocation errorLocation;
++ QString error;
++
++ if (QV4::Compiler::Codegen::isNameGlobal(propName)) {
++ error = tr("Illegal property name");
++ } else if (isValid) {
++ error = _object->appendAlias(alias, propName, node->isDefaultMember(), node->defaultToken(),
++ &errorLocation);
++ }
++
++ if (!error.isEmpty()) {
++ if (errorLocation.startLine == 0)
++ errorLocation = node->identifierToken;
++
++ recordError(errorLocation, error);
++ if (!shouldContinue())
++ return false;
++ }
++
++ return false;
++ }
++
++ Object *IRBuilder::bindingsTarget() const
++ {
++ if (_propertyDeclaration && _object->declarationsOverride)
++ return _object->declarationsOverride;
++ return _object;
++ }
++
++ bool IRBuilder::setId(const QQmlJS::SourceLocation &idLocation, QQmlJS::AST::Statement *value)
++ {
++ QQmlJS::SourceLocation loc = value->firstSourceLocation();
++ QStringView str;
++
++ QQmlJS::AST::Node *node = value;
++ if (QQmlJS::AST::ExpressionStatement *stmt = QQmlJS::AST::cast<QQmlJS::AST::ExpressionStatement *>(node)) {
++ if (QQmlJS::AST::StringLiteral *lit = QQmlJS::AST::cast<QQmlJS::AST::StringLiteral *>(stmt->expression)) {
++ str = lit->value;
++ node = nullptr;
++ } else
++ node = stmt->expression;
++ }
++
++ if (node && str.isEmpty())
++ str = asStringRef(node);
++
++ bool isValid = !str.isEmpty();
++ if (!isValid) {
++ COMPILE_EXCEPTION(loc, tr( "Invalid empty ID"));
++ } else {
++ QChar ch = str.at(0);
++ if (ch.isLetter() && !ch.isLower()) {
++ COMPILE_EXCEPTION(loc, tr( "IDs cannot start with an uppercase letter"));
++ isValid = false;
++ }
++
++ QChar u(QLatin1Char('_'));
++ if (!ch.isLetter() && ch != u) {
++ COMPILE_EXCEPTION(loc, tr( "IDs must start with a letter or underscore"));
++ isValid = false;
++ }
++
++ for (int ii = 1; ii < str.size(); ++ii) {
++ ch = str.at(ii);
++ if (!ch.isLetterOrNumber() && ch != u) {
++ COMPILE_EXCEPTION(loc, tr( "IDs must contain only letters, numbers, and underscores"));
++ isValid = false;
++ break;
++ }
++ }
++
++ QString idQString(str.toString());
++ if (QV4::Compiler::Codegen::isNameGlobal(idQString)) {
++ COMPILE_EXCEPTION(loc, tr( "ID illegally masks global JavaScript property"));
++ isValid = false;
++ }
++
++ if (_object->idNameIndex != emptyStringIndex) {
++ COMPILE_EXCEPTION(idLocation, tr("Property value set multiple times"));
++ isValid = false;
++ }
++
++ if (isValid) {
++ _object->idNameIndex = registerString(idQString);
++ _object->locationOfIdProperty.set(idLocation.startLine, idLocation.startColumn);
++ }
++ }
++
++ return isValid;
++ }
++
++ bool IRBuilder::resolveQualifiedId(QQmlJS::AST::UiQualifiedId **nameToResolve, Object **object, bool onAssignment)
++ {
++ QQmlJS::AST::UiQualifiedId *qualifiedIdElement = *nameToResolve;
++
++ if (qualifiedIdElement->name == QLatin1String("id") && qualifiedIdElement->next)
++ COMPILE_EXCEPTION(qualifiedIdElement->identifierToken, tr( "Invalid use of id property"));
++
++ // If it's a namespace, prepend the qualifier and we'll resolve it later to the correct type.
++ QString currentName = qualifiedIdElement->name.toString();
++ if (qualifiedIdElement->next) {
++ for (const QV4::CompiledData::Import* import : std::as_const(_imports))
++ if (import->qualifierIndex != emptyStringIndex
++ && stringAt(import->qualifierIndex) == currentName) {
++ qualifiedIdElement = qualifiedIdElement->next;
++ currentName += QLatin1Char('.') + qualifiedIdElement->name;
++
++ if (!qualifiedIdElement->name.data()->isUpper())
++ COMPILE_EXCEPTION(qualifiedIdElement->firstSourceLocation(), tr("Expected type name"));
++
++ break;
++ }
++ }
++
++ *object = _object;
++ while (qualifiedIdElement->next) {
++ const quint32 propertyNameIndex = registerString(currentName);
++ const bool isAttachedProperty = qualifiedIdElement->name.data()->isUpper();
++
++ Binding *binding = (*object)->findBinding(propertyNameIndex);
++ if (binding) {
++ if (isAttachedProperty) {
++ if (!binding->isAttachedProperty())
++ binding = nullptr;
++ } else if (!binding->isGroupedProperty()) {
++ binding = nullptr;
++ }
++ }
++ if (!binding) {
++ binding = New<Binding>();
++ binding->propertyNameIndex = propertyNameIndex;
++ binding->offset = qualifiedIdElement->identifierToken.offset;
++ binding->location.set(qualifiedIdElement->identifierToken.startLine,
++ qualifiedIdElement->identifierToken.startColumn);
++ binding->valueLocation.set(qualifiedIdElement->next->identifierToken.startLine,
++ qualifiedIdElement->next->identifierToken.startColumn);
++ binding->clearFlags();
++
++ if (onAssignment)
++ binding->setFlag(QV4::CompiledData::Binding::IsOnAssignment);
++
++ if (isAttachedProperty)
++ binding->setType(QV4::CompiledData::Binding::Type_AttachedProperty);
++ else
++ binding->setType(QV4::CompiledData::Binding::Type_GroupedProperty);
++
++ int objIndex = 0;
++ if (!defineQMLObject(&objIndex, nullptr, binding->location, nullptr, nullptr))
++ return false;
++ binding->value.objectIndex = objIndex;
++
++ QString error = (*object)->appendBinding(binding, /*isListBinding*/false);
++ if (!error.isEmpty()) {
++ recordError(qualifiedIdElement->identifierToken, error);
++ if (!shouldContinue())
++ return false;
++ }
++ *object = _objects.at(objIndex);
++ } else {
++ Q_ASSERT(binding->isAttachedProperty() || binding->isGroupedProperty());
++ *object = _objects.at(binding->value.objectIndex);
++ }
++
++ qualifiedIdElement = qualifiedIdElement->next;
++ if (qualifiedIdElement)
++ currentName = qualifiedIdElement->name.toString();
++ }
++ *nameToResolve = qualifiedIdElement;
++ return true;
++ }
++
++ void IRBuilder::recordError(const QQmlJS::SourceLocation &location, const QString &description)
++ {
++ QQmlJS::DiagnosticMessage error;
++ error.loc = location;
++ error.message = description;
++ errors << error;
++ }
++
++ bool IRBuilder::isStatementNodeScript(QQmlJS::AST::Statement *statement)
++ {
++ if (QQmlJS::AST::ExpressionStatement *stmt = QQmlJS::AST::cast<QQmlJS::AST::ExpressionStatement *>(statement)) {
++ QQmlJS::AST::ExpressionNode *expr = stmt->expression;
++ if (QQmlJS::AST::cast<QQmlJS::AST::StringLiteral *>(expr))
++ return false;
++ else if (expr->kind == QQmlJS::AST::Node::Kind_TrueLiteral)
++ return false;
++ else if (expr->kind == QQmlJS::AST::Node::Kind_FalseLiteral)
++ return false;
++ else if (QQmlJS::AST::cast<QQmlJS::AST::NumericLiteral *>(expr))
++ return false;
++ else {
++
++ if (QQmlJS::AST::UnaryMinusExpression *unaryMinus = QQmlJS::AST::cast<QQmlJS::AST::UnaryMinusExpression *>(expr)) {
++ if (QQmlJS::AST::cast<QQmlJS::AST::NumericLiteral *>(unaryMinus->expression)) {
++ return false;
++ }
++ }
++ }
++ }
++
++ return true;
++ }
++
++ bool IRBuilder::isRedundantNullInitializerForPropertyDeclaration(Property *property, QQmlJS::AST::Statement *statement)
++ {
++ if (property->isCommonType() || property->isList())
++ return false;
++ QQmlJS::AST::ExpressionStatement *exprStmt = QQmlJS::AST::cast<QQmlJS::AST::ExpressionStatement *>(statement);
++ if (!exprStmt)
++ return false;
++ QQmlJS::AST::ExpressionNode * const expr = exprStmt->expression;
++ return QQmlJS::AST::cast<QQmlJS::AST::NullExpression *>(expr);
++ }
++
++ void QmlUnitGenerator::generate(Document &output, const QV4::CompiledData::DependentTypesHasher &dependencyHasher)
++ {
++ using namespace QV4::CompiledData;
++
++ output.jsGenerator.stringTable.registerString(output.jsModule.fileName);
++ output.jsGenerator.stringTable.registerString(output.jsModule.finalUrl);
++
++ Unit *jsUnit = nullptr;
++
++ if (!output.javaScriptCompilationUnit)
++ output.javaScriptCompilationUnit.adopt(new QV4::CompiledData::CompilationUnit);
++
++ // We may already have unit data if we're loading an ahead-of-time generated cache file.
++ if (output.javaScriptCompilationUnit->unitData()) {
++ jsUnit = const_cast<Unit *>(output.javaScriptCompilationUnit->unitData());
++ output.javaScriptCompilationUnit->dynamicStrings
++ = output.jsGenerator.stringTable.allStrings();
++ } else {
++ Unit *createdUnit;
++ jsUnit = createdUnit = output.jsGenerator.generateUnit();
++
++ // enable flag if we encountered pragma Singleton
++ for (Pragma *p : std::as_const(output.pragmas)) {
++ switch (p->type) {
++ case Pragma::Singleton:
++ createdUnit->flags |= Unit::IsSingleton;
++ break;
++ case Pragma::Strict:
++ createdUnit->flags |= Unit::IsStrict;
++ break;
++ case Pragma::ComponentBehavior:
++ // ### Qt7: Change the default to Bound by reverting the meaning of the flag.
++ switch (p->componentBehavior) {
++ case Pragma::Bound:
++ createdUnit->flags |= Unit::ComponentsBound;
++ break;
++ case Pragma::Unbound:
++ // this is the default
++ break;
++ }
++ break;
++ case Pragma::ListPropertyAssignBehavior:
++ switch (p->listPropertyAssignBehavior) {
++ case Pragma::Replace:
++ createdUnit->flags |= Unit::ListPropertyAssignReplace;
++ break;
++ case Pragma::ReplaceIfNotDefault:
++ createdUnit->flags |= Unit::ListPropertyAssignReplaceIfNotDefault;
++ break;
++ case Pragma::Append:
++ // this is the default
++ break;
++ }
++ break;
++ case Pragma::FunctionSignatureBehavior:
++ switch (p->functionSignatureBehavior) {
++ case Pragma::Enforced:
++ break;
++ case Pragma::Ignored:
++ createdUnit->flags |= Unit::FunctionSignaturesIgnored;
++ break;
++ }
++ break;
++ case Pragma::NativeMethodBehavior:
++ switch (p->nativeMethodBehavior) {
++ case Pragma::AcceptThisObject:
++ createdUnit->flags |= Unit::NativeMethodsAcceptThisObject;
++ break;
++ case Pragma::RejectThisObject:
++ // this is the default;
++ break;
++ }
++ break;
++ case Pragma::ValueTypeBehavior:
++ if (Pragma::ValueTypeBehaviorValues(p->valueTypeBehavior)
++ .testFlag(Pragma::Copy)) {
++ createdUnit->flags |= Unit::ValueTypesCopied;
++ }
++ if (Pragma::ValueTypeBehaviorValues(p->valueTypeBehavior)
++ .testFlag(Pragma::Addressable)) {
++ createdUnit->flags |= Unit::ValueTypesAddressable;
++ }
++ if (Pragma::ValueTypeBehaviorValues(p->valueTypeBehavior)
++ .testFlag(Pragma::Assertable)) {
++ createdUnit->flags |= Unit::ValueTypesAssertable;
++ }
++ break;
++ case Pragma::Translator:
++ if (createdUnit->translationTableSize)
++ if (quint32_le *index = createdUnit->translationContextIndex())
++ *index = p->translationContextIndex;
++ break;
++ }
++ }
++
++ if (dependencyHasher) {
++ const QByteArray checksum = dependencyHasher();
++ if (checksum.size() == sizeof(createdUnit->dependencyMD5Checksum)) {
++ memcpy(createdUnit->dependencyMD5Checksum, checksum.constData(),
++ sizeof(createdUnit->dependencyMD5Checksum));
++ }
++ }
++
++ createdUnit->sourceFileIndex = output.jsGenerator.stringTable.getStringId(output.jsModule.fileName);
++ createdUnit->finalUrlIndex = output.jsGenerator.stringTable.getStringId(output.jsModule.finalUrl);
++ }
++
++ // No more new strings after this point, we're calculating offsets.
++ output.jsGenerator.stringTable.freeze();
++
++ const uint importSize = uint(sizeof(QV4::CompiledData::Import)) * output.imports.size();
++ const uint objectOffsetTableSize = output.objects.size() * uint(sizeof(quint32));
++
++ QHash<const Object*, quint32> objectOffsets;
++
++ const unsigned int objectOffset = sizeof(QV4::CompiledData::QmlUnit) + importSize;
++ uint nextOffset = objectOffset + objectOffsetTableSize;
++ for (Object *o : std::as_const(output.objects)) {
++ objectOffsets.insert(o, nextOffset);
++ nextOffset += QV4::CompiledData::Object::calculateSizeExcludingSignalsAndEnums(o->functionCount(), o->propertyCount(), o->aliasCount(), o->enumCount(), o->signalCount(), o->bindingCount(), o->namedObjectsInComponent.size(), o->inlineComponentCount(), o->requiredPropertyExtraDataCount());
++
++ int signalTableSize = 0;
++ for (const Signal *s = o->firstSignal(); s; s = s->next)
++ signalTableSize += QV4::CompiledData::Signal::calculateSize(s->parameters->count);
++
++ nextOffset += signalTableSize;
++
++ int enumTableSize = 0;
++ for (const Enum *e = o->firstEnum(); e; e = e->next)
++ enumTableSize += QV4::CompiledData::Enum::calculateSize(e->enumValues->count);
++
++ nextOffset += enumTableSize;
++ }
++
++ const uint totalSize = nextOffset;
++ char *data = (char*)malloc(totalSize);
++ memset(data, 0, totalSize);
++ QV4::CompiledData::QmlUnit *qmlUnit = reinterpret_cast<QV4::CompiledData::QmlUnit *>(data);
++ qmlUnit->offsetToImports = sizeof(*qmlUnit);
++ qmlUnit->nImports = output.imports.size();
++ qmlUnit->offsetToObjects = objectOffset;
++ qmlUnit->nObjects = output.objects.size();
++
++ // write imports
++ char *importPtr = data + qmlUnit->offsetToImports;
++ for (const QV4::CompiledData::Import *imp : std::as_const(output.imports)) {
++ QV4::CompiledData::Import *importToWrite = reinterpret_cast<QV4::CompiledData::Import*>(importPtr);
++ *importToWrite = *imp;
++ importPtr += sizeof(QV4::CompiledData::Import);
++ }
++
++ // write objects
++ quint32_le *objectTable = reinterpret_cast<quint32_le*>(data + qmlUnit->offsetToObjects);
++ for (int i = 0; i < output.objects.size(); ++i) {
++ const Object *o = output.objects.at(i);
++ char * const objectPtr = data + objectOffsets.value(o);
++ *objectTable++ = objectOffsets.value(o);
++
++ QV4::CompiledData::Object *objectToWrite = reinterpret_cast<QV4::CompiledData::Object*>(objectPtr);
++ objectToWrite->inheritedTypeNameIndex = o->inheritedTypeNameIndex;
++ objectToWrite->indexOfDefaultPropertyOrAlias = o->indexOfDefaultPropertyOrAlias;
++ objectToWrite->setHasAliasAsDefaultProperty(o->defaultPropertyIsAlias);
++ objectToWrite->setFlags(QV4::CompiledData::Object::Flags(o->flags));
++ objectToWrite->idNameIndex = o->idNameIndex;
++ objectToWrite->setObjectId(o->id);
++ objectToWrite->location = o->location;
++ objectToWrite->locationOfIdProperty = o->locationOfIdProperty;
++
++ quint32 nextOffset = sizeof(QV4::CompiledData::Object);
++
++ objectToWrite->nFunctions = o->functionCount();
++ objectToWrite->offsetToFunctions = nextOffset;
++ nextOffset += objectToWrite->nFunctions * sizeof(quint32);
++
++ objectToWrite->nProperties = o->propertyCount();
++ objectToWrite->offsetToProperties = nextOffset;
++ nextOffset += objectToWrite->nProperties * sizeof(QV4::CompiledData::Property);
++
++ objectToWrite->nAliases = o->aliasCount();
++ objectToWrite->offsetToAliases = nextOffset;
++ nextOffset += objectToWrite->nAliases * sizeof(QV4::CompiledData::Alias);
++
++ objectToWrite->nEnums = o->enumCount();
++ objectToWrite->offsetToEnums = nextOffset;
++ nextOffset += objectToWrite->nEnums * sizeof(quint32);
++
++ objectToWrite->nSignals = o->signalCount();
++ objectToWrite->offsetToSignals = nextOffset;
++ nextOffset += objectToWrite->nSignals * sizeof(quint32);
++
++ objectToWrite->nBindings = o->bindingCount();
++ objectToWrite->offsetToBindings = nextOffset;
++ nextOffset += objectToWrite->nBindings * sizeof(QV4::CompiledData::Binding);
++
++ objectToWrite->nNamedObjectsInComponent = o->namedObjectsInComponent.size();
++ objectToWrite->offsetToNamedObjectsInComponent = nextOffset;
++ nextOffset += objectToWrite->nNamedObjectsInComponent * sizeof(quint32);
++
++ objectToWrite->nInlineComponents = o->inlineComponentCount();
++ objectToWrite->offsetToInlineComponents = nextOffset;
++ nextOffset += objectToWrite->nInlineComponents * sizeof (QV4::CompiledData::InlineComponent);
++
++ objectToWrite->nRequiredPropertyExtraData = o->requiredPropertyExtraDataCount();
++ objectToWrite->offsetToRequiredPropertyExtraData = nextOffset;
++ nextOffset += objectToWrite->nRequiredPropertyExtraData * sizeof(QV4::CompiledData::RequiredPropertyExtraData);
++
++ quint32_le *functionsTable = reinterpret_cast<quint32_le *>(objectPtr + objectToWrite->offsetToFunctions);
++ for (const Function *f = o->firstFunction(); f; f = f->next)
++ *functionsTable++ = o->runtimeFunctionIndices.at(f->index);
++
++ char *propertiesPtr = objectPtr + objectToWrite->offsetToProperties;
++ for (const Property *p = o->firstProperty(); p; p = p->next) {
++ QV4::CompiledData::Property *propertyToWrite = reinterpret_cast<QV4::CompiledData::Property*>(propertiesPtr);
++ *propertyToWrite = *p;
++ propertiesPtr += sizeof(QV4::CompiledData::Property);
++ }
++
++ char *aliasesPtr = objectPtr + objectToWrite->offsetToAliases;
++ for (const Alias *a = o->firstAlias(); a; a = a->next) {
++ QV4::CompiledData::Alias *aliasToWrite = reinterpret_cast<QV4::CompiledData::Alias*>(aliasesPtr);
++ *aliasToWrite = *a;
++ aliasesPtr += sizeof(QV4::CompiledData::Alias);
++ }
++
++ char *bindingPtr = objectPtr + objectToWrite->offsetToBindings;
++ bindingPtr = writeBindings(bindingPtr, o);
++ Q_ASSERT((bindingPtr - objectToWrite->offsetToBindings - objectPtr) / sizeof(QV4::CompiledData::Binding) == unsigned(o->bindingCount()));
++
++ quint32_le *signalOffsetTable = reinterpret_cast<quint32_le *>(objectPtr + objectToWrite->offsetToSignals);
++ quint32 signalTableSize = 0;
++ char *signalPtr = objectPtr + nextOffset;
++ for (const Signal *s = o->firstSignal(); s; s = s->next) {
++ *signalOffsetTable++ = signalPtr - objectPtr;
++ QV4::CompiledData::Signal *signalToWrite = reinterpret_cast<QV4::CompiledData::Signal*>(signalPtr);
++
++ signalToWrite->nameIndex = s->nameIndex;
++ signalToWrite->location = s->location;
++ signalToWrite->nParameters = s->parameters->count;
++
++ QV4::CompiledData::Parameter *parameterToWrite = reinterpret_cast<QV4::CompiledData::Parameter*>(signalPtr + sizeof(*signalToWrite));
++ for (Parameter *param = s->parameters->first; param; param = param->next, ++parameterToWrite)
++ *parameterToWrite = *param;
++
++ int size = QV4::CompiledData::Signal::calculateSize(s->parameters->count);
++ signalTableSize += size;
++ signalPtr += size;
++ }
++ nextOffset += signalTableSize;
++
++ quint32_le *enumOffsetTable = reinterpret_cast<quint32_le*>(objectPtr + objectToWrite->offsetToEnums);
++ char *enumPtr = objectPtr + nextOffset;
++ for (const Enum *e = o->firstEnum(); e; e = e->next) {
++ *enumOffsetTable++ = enumPtr - objectPtr;
++ QV4::CompiledData::Enum *enumToWrite = reinterpret_cast<QV4::CompiledData::Enum*>(enumPtr);
++
++ enumToWrite->nameIndex = e->nameIndex;
++ enumToWrite->location = e->location;
++ enumToWrite->nEnumValues = e->enumValues->count;
++
++ QV4::CompiledData::EnumValue *enumValueToWrite = reinterpret_cast<QV4::CompiledData::EnumValue*>(enumPtr + sizeof(*enumToWrite));
++ for (EnumValue *enumValue = e->enumValues->first; enumValue; enumValue = enumValue->next, ++enumValueToWrite)
++ *enumValueToWrite = *enumValue;
++
++ int size = QV4::CompiledData::Enum::calculateSize(e->enumValues->count);
++ enumPtr += size;
++ }
++
++ quint32_le *namedObjectInComponentPtr = reinterpret_cast<quint32_le *>(objectPtr + objectToWrite->offsetToNamedObjectsInComponent);
++ for (int i = 0; i < o->namedObjectsInComponent.size(); ++i) {
++ *namedObjectInComponentPtr++ = o->namedObjectsInComponent.at(i);
++ }
++
++ char *inlineComponentPtr = objectPtr + objectToWrite->offsetToInlineComponents;
++ for (auto it = o->inlineComponentsBegin(); it != o->inlineComponentsEnd(); ++it) {
++ const InlineComponent *ic = it.ptr;
++ QV4::CompiledData::InlineComponent *icToWrite = reinterpret_cast<QV4::CompiledData::InlineComponent*>(inlineComponentPtr);
++ *icToWrite = *ic;
++ inlineComponentPtr += sizeof(QV4::CompiledData::InlineComponent);
++ }
++
++ char *requiredPropertyExtraDataPtr = objectPtr + objectToWrite->offsetToRequiredPropertyExtraData;
++ for (auto it = o->requiredPropertyExtraDataBegin(); it != o->requiredPropertyExtraDataEnd(); ++it) {
++ const RequiredPropertyExtraData *extraData = it.ptr;
++ QV4::CompiledData::RequiredPropertyExtraData *extraDataToWrite = reinterpret_cast<QV4::CompiledData::RequiredPropertyExtraData*>(requiredPropertyExtraDataPtr);
++ *extraDataToWrite = *extraData;
++ requiredPropertyExtraDataPtr += sizeof(QV4::CompiledData::RequiredPropertyExtraData);
++ }
++ }
++
++ if (!output.javaScriptCompilationUnit->unitData()) {
++ // Combine the qml data into the general unit data.
++ jsUnit = static_cast<QV4::CompiledData::Unit *>(realloc(jsUnit, jsUnit->unitSize + totalSize));
++ jsUnit->offsetToQmlUnit = jsUnit->unitSize;
++ jsUnit->unitSize += totalSize;
++ memcpy(jsUnit->qmlUnit(), qmlUnit, totalSize);
++ free(qmlUnit);
++ QV4::Compiler::JSUnitGenerator::generateUnitChecksum(jsUnit);
++ qmlUnit = jsUnit->qmlUnit();
++ }
++
++ static const bool showStats = qEnvironmentVariableIsSet("QML_SHOW_UNIT_STATS");
++ if (showStats) {
++ qDebug() << "Generated QML unit that is" << totalSize << "bytes big contains:";
++ qDebug() << " " << jsUnit->functionTableSize << "functions";
++ qDebug() << " " << jsUnit->unitSize << "for JS unit";
++ qDebug() << " " << importSize << "for imports";
++ qDebug() << " " << nextOffset - objectOffset - objectOffsetTableSize << "for" << qmlUnit->nObjects << "objects";
++ quint32 totalBindingCount = 0;
++ for (quint32 i = 0; i < qmlUnit->nObjects; ++i)
++ totalBindingCount += qmlUnit->objectAt(i)->nBindings;
++ qDebug() << " " << totalBindingCount << "bindings";
++ quint32 totalCodeSize = 0;
++ for (quint32 i = 0; i < jsUnit->functionTableSize; ++i)
++ totalCodeSize += jsUnit->functionAt(i)->codeSize;
++ qDebug() << " " << totalCodeSize << "bytes total byte code";
++ qDebug() << " " << jsUnit->stringTableSize << "strings";
++ quint32 totalStringSize = 0;
++ for (quint32 i = 0; i < jsUnit->stringTableSize; ++i)
++ totalStringSize += QV4::CompiledData::String::calculateSize(jsUnit->stringAtInternal(i));
++ qDebug() << " " << totalStringSize << "bytes total strings";
++ }
++
++ output.javaScriptCompilationUnit->setUnitData(
++ jsUnit, qmlUnit, output.jsModule.fileName, output.jsModule.finalUrl);
++ }
++
++ char *QmlUnitGenerator::writeBindings(char *bindingPtr, const Object *o) const
++ {
++ for (const Binding *b = o->firstBinding(); b; b = b->next) {
++ QV4::CompiledData::Binding *bindingToWrite = reinterpret_cast<QV4::CompiledData::Binding*>(bindingPtr);
++ *bindingToWrite = *b;
++ if (b->type() == QV4::CompiledData::Binding::Type_Script)
++ bindingToWrite->value.compiledScriptIndex = o->runtimeFunctionIndices.at(b->value.compiledScriptIndex);
++ bindingPtr += sizeof(QV4::CompiledData::Binding);
++ }
++ return bindingPtr;
++ }
++
++ JSCodeGen::JSCodeGen(
++ Document *document, QV4::Compiler::CodegenWarningInterface *iface,
++ bool storeSourceLocations)
++ : QV4::Compiler::Codegen(&document->jsGenerator, /*strict mode*/ false, iface,
++ storeSourceLocations),
++ document(document)
++ {
++ _module = &document->jsModule;
++ _fileNameIsUrl = true;
++ }
++
++ QList<int> JSCodeGen::generateJSCodeForFunctionsAndBindings(
++ const QList<CompiledFunctionOrExpression> &functions)
++ {
++ auto qmlName = [&](const CompiledFunctionOrExpression &c) {
++ if (c.nameIndex != 0)
++ return document->stringAt(c.nameIndex);
++ else
++ return QStringLiteral("%qml-expression-entry");
++ };
++ QList<int> runtimeFunctionIndices(functions.size());
++
++ QV4::Compiler::ScanFunctions scan(this, document->code, QV4::Compiler::ContextType::Global);
++ scan.enterGlobalEnvironment(QV4::Compiler::ContextType::Binding);
++ for (const CompiledFunctionOrExpression &f : functions) {
++ Q_ASSERT(f.node != document->program);
++ Q_ASSERT(f.parentNode && f.parentNode != document->program);
++ auto function = f.node->asFunctionDefinition();
++
++ if (function) {
++ scan.enterQmlFunction(function);
++ } else {
++ Q_ASSERT(f.node != f.parentNode);
++ scan.enterEnvironment(f.parentNode, QV4::Compiler::ContextType::Binding, qmlName(f));
++ }
++
++ /* We do not want to visit the whole function, as we already called enterQmlFunction
++ However, there might be a function defined as a default argument of the function.
++ That needs to be considered, too, so we call handleTopLevelFunctionFormals to
++ deal with them.
++ */
++ scan.handleTopLevelFunctionFormals(function);
++ scan(function ? function->body : f.node);
++ scan.leaveEnvironment();
++ }
++ scan.leaveEnvironment();
++
++ if (hasError())
++ return QList<int>();
++
++ _context = nullptr;
++
++ for (int i = 0; i < functions.size(); ++i) {
++ const CompiledFunctionOrExpression &qmlFunction = functions.at(i);
++ QQmlJS::AST::Node *node = qmlFunction.node;
++ Q_ASSERT(node != document->program);
++
++ QQmlJS::AST::FunctionExpression *function = node->asFunctionDefinition();
++
++ QString name;
++ if (function)
++ name = function->name.toString();
++ else
++ name = qmlName(qmlFunction);
++
++ QQmlJS::AST::StatementList *body;
++ if (function) {
++ body = function->body;
++ } else {
++ // Synthesize source elements.
++ QQmlJS::MemoryPool *pool = document->jsParserEngine.pool();
++
++ QQmlJS::AST::Statement *stmt = node->statementCast();
++ if (!stmt) {
++ Q_ASSERT(node->expressionCast());
++ QQmlJS::AST::ExpressionNode *expr = node->expressionCast();
++ stmt = new (pool) QQmlJS::AST::ExpressionStatement(expr);
++ }
++ body = new (pool) QQmlJS::AST::StatementList(stmt);
++ body = body->finish();
++ }
++
++ int idx = defineFunction(name, function ? function : qmlFunction.parentNode,
++ function ? function->formals : nullptr, body);
++ runtimeFunctionIndices[i] = idx;
++ }
++
++ return runtimeFunctionIndices;
++ }
++
++ bool JSCodeGen::generateRuntimeFunctions(QmlIR::Object *object)
++ {
++ if (object->functionsAndExpressions->count == 0)
++ return true;
++
++ QList<QmlIR::CompiledFunctionOrExpression> functionsToCompile;
++ functionsToCompile.reserve(object->functionsAndExpressions->count);
++ for (QmlIR::CompiledFunctionOrExpression *foe = object->functionsAndExpressions->first; foe;
++ foe = foe->next) {
++ functionsToCompile << *foe;
++ }
++
++ const auto runtimeFunctionIndices = generateJSCodeForFunctionsAndBindings(functionsToCompile);
++ if (hasError())
++ return false;
++
++ object->runtimeFunctionIndices.allocate(document->jsParserEngine.pool(),
++ runtimeFunctionIndices);
++ return true;
++ }