Revision control
++ // Copyright (C) 2017 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 "qv4codegen_p.h"
++
++ #include <private/qqmljsast_p.h>
++ #include <private/qqmljsdiagnosticmessage_p.h>
++ #include <private/qqmljslexer_p.h>
++ #include <private/qqmljsparser_p.h>
++ #include <private/qv4bytecodegenerator_p.h>
++ #include <private/qv4compilercontext_p.h>
++ #include <private/qv4compilercontrolflow_p.h>
++ #include <private/qv4compilerscanfunctions_p.h>
++ #include <private/qv4object_p.h>
++ #include <private/qv4objectiterator_p.h>
++ #include <private/qv4staticvalue_p.h>
++ #include <private/qv4stringtoarrayindex_p.h>
++
++ #include <QtCore/qcoreapplication.h>
++ #include <QtCore/qloggingcategory.h>
++ #include <QtCore/qscopeguard.h>
++ #include <QtCore/qstack.h>
++ #include <QtCore/qstringlist.h>
++ #include <QtCore/qurl.h>
++
++ #include <cmath>
++
++ #ifdef CONST
++ #undef CONST
++ #endif
++
++ QT_BEGIN_NAMESPACE
++
++ using namespace Qt::StringLiterals;
++
++ Q_STATIC_LOGGING_CATEGORY(lcQmlUsedBeforeDeclared, "qt.qml.usedbeforedeclared");
++ Q_STATIC_LOGGING_CATEGORY(lcQmlInjectedParameter, "qt.qml.injectedparameter");
++
++ using namespace QV4;
++ using namespace QV4::Compiler;
++ using namespace QQmlJS;
++ using namespace QQmlJS::AST;
++
++ void CodegenWarningInterface::reportVarUsedBeforeDeclaration(
++ const QString &name, const QString &fileName, QQmlJS::SourceLocation declarationLocation,
++ QQmlJS::SourceLocation accessLocation)
++ {
++ qCWarning(lcQmlUsedBeforeDeclared).nospace().noquote()
++ << fileName << ":" << accessLocation.startLine << ":" << accessLocation.startColumn
++ << " Variable \"" << name << "\" is used before its declaration at "
++ << declarationLocation.startLine << ":" << declarationLocation.startColumn << ".";
++ }
++
++ void CodegenWarningInterface::reportFunctionUsedBeforeDeclaration(const QString &, const QString &,
++ QQmlJS::SourceLocation,
++ QQmlJS::SourceLocation)
++ {
++ // we don't report this by default, only when using qmllint.
++ }
++
++ static inline void setJumpOutLocation(QV4::Moth::BytecodeGenerator *bytecodeGenerator,
++ const Statement *body, const SourceLocation &fallback)
++ {
++ switch (body->kind) {
++ // Statements where we might never execute the last line.
++ // Use the fallback.
++ case Statement::Kind_ConditionalExpression:
++ case Statement::Kind_ForEachStatement:
++ case Statement::Kind_ForStatement:
++ case Statement::Kind_IfStatement:
++ case Statement::Kind_WhileStatement:
++ bytecodeGenerator->setLocation(fallback);
++ break;
++ default:
++ bytecodeGenerator->setLocation(body->lastSourceLocation());
++ break;
++ }
++ }
++
++ void Codegen::generateThrowException(const QString &type, const QString &text)
++ {
++ RegisterScope scope(this);
++ Instruction::Construct construct;
++ if (text.isEmpty()) {
++ construct.argc = 0;
++ construct.argv = 0;
++ } else {
++ construct.argc = 1;
++ Instruction::LoadRuntimeString load;
++ load.stringId = registerString(text);
++ bytecodeGenerator->addInstruction(load);
++ construct.argv = Reference::fromAccumulator(this).storeOnStack().stackSlot();
++ }
++ Reference r = referenceForName(type, false);
++ r = r.storeOnStack();
++ construct.func = r.stackSlot();
++ bytecodeGenerator->addInstruction(construct);
++ Instruction::ThrowException throwException;
++ bytecodeGenerator->addInstruction(throwException);
++ }
++
++ Codegen::Codegen(QV4::Compiler::JSUnitGenerator *jsUnitGenerator, bool strict,
++ CodegenWarningInterface *iface, bool storeSourceLocations)
++ : _module(nullptr),
++ _returnAddress(-1),
++ _context(nullptr),
++ _labelledStatement(nullptr),
++ jsUnitGenerator(jsUnitGenerator),
++ _strictMode(strict),
++ storeSourceLocations(storeSourceLocations),
++ _fileNameIsUrl(false),
++ _interface(iface)
++ {
++ jsUnitGenerator->codeGeneratorName = QStringLiteral("moth");
++ pushExpr();
++ }
++
++ static constexpr const QLatin1StringView s_globalNames[] = {
++ QLatin1StringView("Array"),
++ QLatin1StringView("ArrayBuffer"),
++ QLatin1StringView("Atomics"),
++ QLatin1StringView("Boolean"),
++ QLatin1StringView("DOMException"),
++ QLatin1StringView("DataView"),
++ QLatin1StringView("Date"),
++ QLatin1StringView("Error"),
++ QLatin1StringView("EvalError"),
++ QLatin1StringView("Float32Array"),
++ QLatin1StringView("Float64Array"),
++ QLatin1StringView("Function"),
++ QLatin1StringView("Infinity"),
++ QLatin1StringView("Int16Array"),
++ QLatin1StringView("Int32Array"),
++ QLatin1StringView("Int8Array"),
++ QLatin1StringView("JSON"),
++ QLatin1StringView("Map"),
++ QLatin1StringView("Math"),
++ QLatin1StringView("NaN"),
++ QLatin1StringView("Number"),
++ QLatin1StringView("Object"),
++ QLatin1StringView("Promise"),
++ QLatin1StringView("Proxy"),
++ QLatin1StringView("QT_TRANSLATE_NOOP"),
++ QLatin1StringView("QT_TRID_NOOP"),
++ QLatin1StringView("QT_TR_NOOP"),
++ QLatin1StringView("Qt"),
++ QLatin1StringView("RangeError"),
++ QLatin1StringView("ReferenceError"),
++ QLatin1StringView("Reflect"),
++ QLatin1StringView("RegExp"),
++ QLatin1StringView("SQLException"),
++ QLatin1StringView("Set"),
++ QLatin1StringView("SharedArrayBuffer"),
++ QLatin1StringView("String"),
++ QLatin1StringView("Symbol"),
++ QLatin1StringView("SyntaxError"),
++ QLatin1StringView("TypeError"),
++ QLatin1StringView("URIError"),
++ QLatin1StringView("URL"),
++ QLatin1StringView("URLSearchParams"),
++ QLatin1StringView("Uint16Array"),
++ QLatin1StringView("Uint32Array"),
++ QLatin1StringView("Uint8Array"),
++ QLatin1StringView("Uint8ClampedArray"),
++ QLatin1StringView("WeakMap"),
++ QLatin1StringView("WeakSet"),
++ QLatin1StringView("XMLHttpRequest"),
++ QLatin1StringView("console"),
++ QLatin1StringView("decodeURI"),
++ QLatin1StringView("decodeURIComponent"),
++ QLatin1StringView("encodeURI"),
++ QLatin1StringView("encodeURIComponent"),
++ QLatin1StringView("escape"),
++ QLatin1StringView("eval"),
++ QLatin1StringView("gc"),
++ QLatin1StringView("isFinite"),
++ QLatin1StringView("isNaN"),
++ QLatin1StringView("parseFloat"),
++ QLatin1StringView("parseInt"),
++ QLatin1StringView("print"),
++ QLatin1StringView("qsTr"),
++ QLatin1StringView("qsTrId"),
++ QLatin1StringView("qsTranslate"),
++ QLatin1StringView("undefined"),
++ QLatin1StringView("unescape"),
++ };
++
++ bool Codegen::isNameGlobal(QAnyStringView name)
++ {
++ return std::binary_search(std::begin(s_globalNames), std::end(s_globalNames), name);
++ }
++
++ void Codegen::forEachGlobalName(qxp::function_ref<void (QLatin1StringView)> &&handler)
++ {
++ for (QLatin1StringView name : s_globalNames)
++ handler(name);
++ }
++
++ void Codegen::generateFromProgram(
++ const QString &sourceCode, Program *node, Module *module, ContextType contextType)
++ {
++ Q_ASSERT(node);
++
++ _module = module;
++ _context = nullptr;
++
++ ScanFunctions scan(this, sourceCode, contextType);
++ scan(node);
++
++ if (hasError())
++ return;
++
++ defineFunction(QStringLiteral("%entry"), node, nullptr, node->statements);
++ }
++
++ void Codegen::generateFromModule(const QString &sourceCode, ESModule *node, Module *module)
++ {
++ Q_ASSERT(node);
++
++ _module = module;
++ _context = nullptr;
++
++ ScanFunctions scan(this, sourceCode, ContextType::ESModule);
++ scan(node);
++
++ if (hasError())
++ return;
++
++ {
++ Compiler::Context *moduleContext = _module->contextMap.value(node);
++ for (const auto &entry: std::as_const(moduleContext->exportEntries)) {
++ if (entry.moduleRequest.isEmpty()) {
++ // ### check against imported bound names
++ _module->localExportEntries << entry;
++ } else if (entry.importName == QLatin1Char('*')) {
++ _module->starExportEntries << entry;
++ } else {
++ _module->indirectExportEntries << entry;
++ }
++ }
++ _module->importEntries = moduleContext->importEntries;
++
++ _module->moduleRequests = std::move(moduleContext->moduleRequests);
++ _module->moduleRequests.removeDuplicates();
++ }
++
++ std::sort(_module->localExportEntries.begin(), _module->localExportEntries.end(), ExportEntry::lessThan);
++ std::sort(_module->starExportEntries.begin(), _module->starExportEntries.end(), ExportEntry::lessThan);
++ std::sort(_module->indirectExportEntries.begin(), _module->indirectExportEntries.end(), ExportEntry::lessThan);
++
++ defineFunction(QStringLiteral("%entry"), node, nullptr, node->body);
++ }
++
++ void Codegen::generateFromModule(const Value &value, Module *module)
++ {
++ _module = module;
++ _context = nullptr;
++
++ _module->newContext(nullptr, nullptr, ContextType::ESModule);
++ enterContext(nullptr);
++
++ _context->name = QStringLiteral("%entry");
++ _module->functions.append(_context);
++ _context->functionIndex = _module->functions.size() - 1;
++
++ ExportEntry entry;
++ entry.localName = entry.exportName = QLatin1String("default");
++ _module->localExportEntries << entry;
++
++ if (Object *o = value.objectValue()) {
++ QV4::Scope scope(o);
++ QV4::ObjectIterator it(scope, o, QV4::ObjectIterator::EnumerableOnly);
++ QV4::PropertyAttributes attrs;
++ QV4::ScopedPropertyKey name(scope);
++ while (true) {
++ name = it.next(nullptr, &attrs);
++ if (!name->isValid())
++ break;
++
++ ExportEntry entry;
++ entry.localName = entry.exportName = name->toQString();
++ _module->localExportEntries << entry;
++ }
++ }
++
++ std::sort(_module->localExportEntries.begin(), _module->localExportEntries.end(),
++ ExportEntry::lessThan);
++
++ for (auto it = _module->localExportEntries.cbegin(), end = _module->localExportEntries.cend();
++ it != end; ++it) {
++ Context::Member member;
++ member.index = _context->locals.size();
++ _context->locals.append(it->exportName);
++ _context->members.insert(it->exportName, member);
++ }
++
++ leaveContext();
++ }
++
++ void Codegen::enterContext(Node *node)
++ {
++ _context = _module->contextMap.value(node);
++ Q_ASSERT(_context);
++ }
++
++ int Codegen::leaveContext()
++ {
++ Q_ASSERT(_context);
++ int functionIndex = _context->functionIndex;
++ _context = _context->parent;
++ return functionIndex;
++ }
++
++ Context *Codegen::enterBlock(Node *node)
++ {
++ enterContext(node);
++ return _context;
++ }
++
++ Codegen::Reference Codegen::unop(UnaryOperation op, const Reference &expr)
++ {
++ if (hasError())
++ return exprResult();
++
++ if (expr.isConstant()) {
++ auto v = StaticValue::fromReturnedValue(expr.constant);
++ if (v.isNumber()) {
++ switch (op) {
++ case Not:
++ return Reference::fromConst(this, Encode(!v.toBoolean()));
++ case UMinus:
++ // This duplicates some of the logic from Runtime::UMinus::call()
++ ReturnedValue r;
++ if (v.isInteger()) {
++ int intVal = v.integerValue();
++ if (intVal && intVal != std::numeric_limits<int>::min())
++ r = QV4::Encode(-intVal);
++ else
++ r = QV4::Encode(-double(intVal));
++ } else if (v.isDouble()) {
++ r = QV4::Encode(-v.doubleValue());
++ } else {
++ r = QV4::Encode(-v.int_32());
++ }
++ return Reference::fromConst(this, r);
++ case UPlus:
++ return expr;
++ case Compl:
++ return Reference::fromConst(this, Encode((int)~v.toInt32()));
++ default:
++ break;
++ }
++ }
++ }
++
++ switch (op) {
++ case UMinus: {
++ expr.loadInAccumulator();
++ Instruction::UMinus uminus = {};
++ bytecodeGenerator->addInstruction(uminus);
++ return Reference::fromAccumulator(this);
++ }
++ case UPlus: {
++ expr.loadInAccumulator();
++ Instruction::UPlus uplus = {};
++ bytecodeGenerator->addInstruction(uplus);
++ return Reference::fromAccumulator(this);
++ }
++ case Not: {
++ expr.loadInAccumulator();
++ Instruction::UNot unot;
++ bytecodeGenerator->addInstruction(unot);
++ return Reference::fromAccumulator(this);
++ }
++ case Compl: {
++ expr.loadInAccumulator();
++ Instruction::UCompl ucompl;
++ bytecodeGenerator->addInstruction(ucompl);
++ return Reference::fromAccumulator(this);
++ }
++ case PostIncrement:
++ if (!exprAccept(nx) || requiresReturnValue) {
++ Reference e = expr.asLValue();
++ e.loadInAccumulator();
++ Instruction::UPlus uplus = {};
++ bytecodeGenerator->addInstruction(uplus);
++ Reference originalValue = Reference::fromStackSlot(this).storeRetainAccumulator();
++ Instruction::Increment inc = {};
++ bytecodeGenerator->addInstruction(inc);
++ e.storeConsumeAccumulator();
++ return originalValue;
++ } else {
++ // intentionally fall-through: the result is never used, so it's equivalent to
++ // "expr += 1", which is what a pre-increment does as well.
++ Q_FALLTHROUGH();
++ }
++ case PreIncrement: {
++ Reference e = expr.asLValue();
++ e.loadInAccumulator();
++ e.isReadonly = true;
++ Instruction::Increment inc = {};
++ bytecodeGenerator->addInstruction(inc);
++ if (exprAccept(nx))
++ return e.storeConsumeAccumulator();
++ else
++ return e.storeRetainAccumulator();
++ }
++ case PostDecrement:
++ if (!exprAccept(nx) || requiresReturnValue) {
++ Reference e = expr.asLValue();
++ e.loadInAccumulator();
++ Instruction::UPlus uplus = {};
++ bytecodeGenerator->addInstruction(uplus);
++ Reference originalValue = Reference::fromStackSlot(this).storeRetainAccumulator();
++ Instruction::Decrement dec = {};
++ bytecodeGenerator->addInstruction(dec);
++ e.storeConsumeAccumulator();
++ return originalValue;
++ } else {
++ // intentionally fall-through: the result is never used, so it's equivalent to
++ // "expr -= 1", which is what a pre-decrement does as well.
++ Q_FALLTHROUGH();
++ }
++ case PreDecrement: {
++ Reference e = expr.asLValue();
++ e.loadInAccumulator();
++ e.isReadonly = true;
++ Instruction::Decrement dec = {};
++ bytecodeGenerator->addInstruction(dec);
++ if (exprAccept(nx))
++ return e.storeConsumeAccumulator();
++ else
++ return e.storeRetainAccumulator();
++ }
++ }
++
++ Q_UNREACHABLE();
++ }
++
++ void Codegen::addCJump()
++ {
++ const Result &expression = currentExpr();
++ bytecodeGenerator->addCJumpInstruction(expression.trueBlockFollowsCondition(),
++ expression.iftrue(), expression.iffalse());
++ }
++
++ void Codegen::statement(Statement *ast)
++ {
++ RegisterScope scope(this);
++
++ bytecodeGenerator->incrementStatement();
++ bytecodeGenerator->setLocation(ast->firstSourceLocation());
++
++ VolatileMemoryLocations vLocs = scanVolatileMemoryLocations(ast);
++ qSwap(_volatileMemoryLocations, vLocs);
++ accept(ast);
++ qSwap(_volatileMemoryLocations, vLocs);
++ }
++
++ void Codegen::statement(ExpressionNode *ast)
++ {
++ if (! ast) {
++ return;
++ } else {
++ RegisterScope scope(this);
++
++ bytecodeGenerator->incrementStatement();
++ pushExpr(Result(nx));
++ VolatileMemoryLocations vLocs = scanVolatileMemoryLocations(ast);
++ qSwap(_volatileMemoryLocations, vLocs);
++
++ accept(ast);
++
++ qSwap(_volatileMemoryLocations, vLocs);
++ Reference result = popResult();
++
++ if (hasError())
++ return;
++ if (result.loadTriggersSideEffect())
++ result.loadInAccumulator(); // triggers side effects
++ }
++ }
++
++ void Codegen::condition(ExpressionNode *ast, const BytecodeGenerator::Label *iftrue,
++ const BytecodeGenerator::Label *iffalse, bool trueBlockFollowsCondition)
++ {
++ if (hasError())
++ return;
++
++ if (!ast)
++ return;
++
++ pushExpr(Result(iftrue, iffalse, trueBlockFollowsCondition));
++ accept(ast);
++ Result r = popExpr();
++
++ if (hasError())
++ return;
++
++ if (r.format() == ex) {
++ Q_ASSERT(iftrue == r.iftrue());
++ Q_ASSERT(iffalse == r.iffalse());
++ Q_ASSERT(r.result().isValid());
++ bytecodeGenerator->setLocation(ast->firstSourceLocation());
++ r.result().loadInAccumulator();
++ if (r.trueBlockFollowsCondition())
++ bytecodeGenerator->jumpFalse().link(*r.iffalse());
++ else
++ bytecodeGenerator->jumpTrue().link(*r.iftrue());
++ }
++ }
++
++ void Codegen::program(Program *ast)
++ {
++ if (ast) {
++ statementList(ast->statements);
++ }
++ }
++
++ enum class CompletionState {
++ Empty,
++ EmptyAbrupt,
++ NonEmpty
++ };
++
++ static CompletionState completionState(StatementList *list)
++ {
++ for (StatementList *it = list; it; it = it->next) {
++ if (it->statement->kind == Statement::Kind_BreakStatement ||
++ it->statement->kind == Statement::Kind_ContinueStatement)
++ return CompletionState::EmptyAbrupt;
++ if (it->statement->kind == Statement::Kind_EmptyStatement ||
++ it->statement->kind == Statement::Kind_VariableDeclaration ||
++ it->statement->kind == Statement::Kind_FunctionDeclaration)
++ continue;
++ if (it->statement->kind == Statement::Kind_Block) {
++ CompletionState subState = completionState(static_cast<Block *>(it->statement)->statements);
++ if (subState != CompletionState::Empty)
++ return subState;
++ continue;
++ }
++ return CompletionState::NonEmpty;
++ }
++ return CompletionState::Empty;
++ }
++
++ static Node *completionStatement(StatementList *list)
++ {
++ Node *completionStatement = nullptr;
++ for (StatementList *it = list; it; it = it->next) {
++ if (it->statement->kind == Statement::Kind_BreakStatement ||
++ it->statement->kind == Statement::Kind_ContinueStatement)
++ return completionStatement;
++ if (it->statement->kind == Statement::Kind_ThrowStatement ||
++ it->statement->kind == Statement::Kind_ReturnStatement)
++ return it->statement;
++ if (it->statement->kind == Statement::Kind_EmptyStatement ||
++ it->statement->kind == Statement::Kind_VariableStatement ||
++ it->statement->kind == Statement::Kind_FunctionDeclaration)
++ continue;
++ if (it->statement->kind == Statement::Kind_Block) {
++ CompletionState state = completionState(static_cast<Block *>(it->statement)->statements);
++ switch (state) {
++ case CompletionState::Empty:
++ continue;
++ case CompletionState::EmptyAbrupt:
++ return it->statement;
++ case CompletionState::NonEmpty:
++ break;
++ }
++ }
++ completionStatement = it->statement;
++ }
++ return completionStatement;
++ }
++
++ void Codegen::statementList(StatementList *ast)
++ {
++ if (!ast)
++ return;
++
++ bool _requiresReturnValue = requiresReturnValue;
++ // ### the next line is pessimizing a bit too much, as there are many cases, where the complietion from the break
++ // statement will not be used, but it's at least spec compliant
++ if (!controlFlow || !controlFlow->hasLoop())
++ requiresReturnValue = false;
++
++ Node *needsCompletion = nullptr;
++
++ if (_requiresReturnValue && !requiresReturnValue)
++ needsCompletion = completionStatement(ast);
++
++ if (requiresReturnValue && !needsCompletion && !insideSwitch) {
++ // break or continue is the first real statement, set the return value to undefined
++ Reference::fromConst(this, Encode::undefined()).storeOnStack(_returnAddress);
++ }
++
++ bool _insideSwitch = insideSwitch;
++ insideSwitch = false;
++
++ for (StatementList *it = ast; it; it = it->next) {
++ if (it->statement == needsCompletion)
++ requiresReturnValue = true;
++ if (Statement *s = it->statement->statementCast())
++ statement(s);
++ else
++ statement(static_cast<ExpressionNode *>(it->statement));
++ if (it->statement == needsCompletion)
++ requiresReturnValue = false;
++ if (it->statement->kind == Statement::Kind_ThrowStatement
++ || it->statement->kind == Statement::Kind_BreakStatement
++ || it->statement->kind == Statement::Kind_ContinueStatement
++ || it->statement->kind == Statement::Kind_ReturnStatement) {
++
++ if (Visitor *visitor = _interface->unreachableVisitor())
++ Node::accept(it->next, visitor);
++ break;
++ }
++ }
++ requiresReturnValue = _requiresReturnValue;
++ insideSwitch = _insideSwitch;
++ }
++
++ void Codegen::variableDeclaration(PatternElement *ast)
++ {
++ TailCallBlocker blockTailCalls(this);
++ RegisterScope scope(this);
++
++ if (!ast->initializer) {
++ if (ast->isLexicallyScoped()) {
++ Reference::fromConst(this, Encode::undefined()).loadInAccumulator();
++ Reference varToStore = targetForPatternElement(ast);
++ varToStore.storeConsumeAccumulator();
++ }
++ return;
++ }
++ initializeAndDestructureBindingElement(ast, Reference(), /*isDefinition*/ true);
++ }
++
++ void Codegen::variableDeclarationList(VariableDeclarationList *ast)
++ {
++ for (VariableDeclarationList *it = ast; it; it = it->next) {
++ variableDeclaration(it->declaration);
++ }
++ }
++
++ Codegen::Reference Codegen::targetForPatternElement(AST::PatternElement *p)
++ {
++ if (!p->bindingIdentifier.isNull())
++ return referenceForName(p->bindingIdentifier.toString(), true, p->firstSourceLocation());
++ if (!p->bindingTarget || p->destructuringPattern())
++ return Codegen::Reference::fromStackSlot(this);
++ Reference lhs = expression(p->bindingTarget);
++ if (hasError())
++ return lhs;
++ if (!lhs.isLValue()) {
++ throwReferenceError(p->bindingTarget->firstSourceLocation(), QStringLiteral("Binding target is not a reference."));
++ return lhs;
++ }
++ lhs = lhs.asLValue();
++ return lhs;
++ }
++
++ // Returns a reference to the raw value passed positionally for the argIndex'th
++ // formal parameter, independent of whatever the parameter's own name currently
++ // resolves to. This is needed when a parameter is promoted to a real member for
++ // TDZ tracking. See Context::promoteFormalParameterForTDZ.
++ Codegen::Reference Codegen::referenceForFormalParameter(int argIndex)
++ {
++ if (_context->argumentsCanEscape)
++ return Reference::fromScopedLocal(this, argIndex + _context->locals.size(), 0);
++ const int index = argIndex + int(sizeof(CallData) / sizeof(StaticValue)) - 1;
++ return Reference::fromStackSlot(this, index, true);
++ }
++
++ void Codegen::initializeAndDestructureBindingElement(AST::PatternElement *e, const Reference &base, bool isDefinition)
++ {
++ Q_ASSERT(e->type == AST::PatternElement::Binding || e->type == AST::PatternElement::RestElement);
++ RegisterScope scope(this);
++ Reference baseRef = (base.isAccumulator()) ? base.storeOnStack() : base;
++ Reference varToStore = targetForPatternElement(e);
++ if (isDefinition)
++ varToStore.isReferenceToConst = false;
++ if (hasError())
++ return;
++
++ if (e->typeAnnotation) {
++ throwSyntaxError(e->firstSourceLocation(),
++ QLatin1String("Type annotations on default parameters are not supported."));
++ }
++
++ const auto loadValueForStore = [&](const Reference &value) {
++ if (isDefinition || !varToStore.requiresTDZCheck) {
++ value.loadInAccumulator();
++ return;
++ }
++ Reference stashed = value.storeOnStack();
++ varToStore.checkTDZBeforeAssignment();
++ stashed.loadInAccumulator();
++ };
++
++ if (e->initializer) {
++ if (!baseRef.isValid()) {
++ // assignment
++ Reference expr = expression(e->initializer);
++ if (hasError())
++ return;
++ loadValueForStore(expr);
++ varToStore.storeConsumeAccumulator();
++ } else if (baseRef == varToStore) {
++ baseRef.loadInAccumulator();
++ BytecodeGenerator::Jump jump = bytecodeGenerator->jumpNotUndefined();
++ Reference expr = expression(e->initializer);
++ if (hasError()) {
++ jump.link();
++ return;
++ }
++ loadValueForStore(expr);
++ varToStore.storeConsumeAccumulator();
++ jump.link();
++ } else {
++ baseRef.loadInAccumulator();
++ BytecodeGenerator::Jump jump = bytecodeGenerator->jumpNotUndefined();
++ Reference expr = expression(e->initializer);
++ if (hasError()) {
++ jump.link();
++ return;
++ }
++ expr.loadInAccumulator();
++ jump.link();
++ loadValueForStore(Reference::fromAccumulator(this));
++ varToStore.storeConsumeAccumulator();
++ }
++ } else if (baseRef != varToStore && baseRef.isValid()) {
++ loadValueForStore(baseRef);
++ varToStore.storeConsumeAccumulator();
++ }
++ Pattern *p = e->destructuringPattern();
++ if (!p)
++ return;
++
++ if (!varToStore.isStackSlot())
++ varToStore = varToStore.storeOnStack();
++ if (PatternElementList *l = e->elementList()) {
++ destructureElementList(varToStore, l, isDefinition);
++ } else if (PatternPropertyList *p = e->propertyList()) {
++ destructurePropertyList(varToStore, p, isDefinition);
++ } else if (e->bindingTarget) {
++ // empty binding pattern. For spec compatibility, try to coerce the argument to an object
++ varToStore.loadInAccumulator();
++ Instruction::ToObject toObject;
++ bytecodeGenerator->addInstruction(toObject);
++ return;
++ }
++ }
++
++ Codegen::Reference Codegen::referenceForPropertyName(const Codegen::Reference &object, AST::PropertyName *name)
++ {
++ AST::ComputedPropertyName *cname = AST::cast<AST::ComputedPropertyName *>(name);
++ Reference property;
++ if (cname) {
++ Reference computedName = expression(cname->expression);
++ if (hasError())
++ return Reference();
++ computedName = computedName.storeOnStack();
++ property = Reference::fromSubscript(object, computedName).asLValue();
++ } else {
++ QString propertyName = name->asString();
++ property = Reference::fromMember(object, propertyName);
++ }
++ return property;
++ }
++
++ void Codegen::destructurePropertyList(const Codegen::Reference &object, PatternPropertyList *bindingList, bool isDefinition)
++ {
++ RegisterScope scope(this);
++
++ object.loadInAccumulator();
++ Instruction::ThrowOnNullOrUndefined t;
++ bytecodeGenerator->addInstruction(t);
++
++ for (PatternPropertyList *it = bindingList; it; it = it->next) {
++ PatternProperty *p = it->property;
++ RegisterScope scope(this);
++ Reference property = referenceForPropertyName(object, p->name);
++ if (hasError())
++ return;
++ initializeAndDestructureBindingElement(p, property, isDefinition);
++ if (hasError())
++ return;
++ }
++ }
++
++ void Codegen::destructureElementList(const Codegen::Reference &array, PatternElementList *bindingList, bool isDefinition)
++ {
++ RegisterScope scope(this);
++
++ Reference iterator = Reference::fromStackSlot(this);
++ QVarLengthArray<Reference, 32> iteratorValues;
++ Reference ignored;
++
++ array.loadInAccumulator();
++ Instruction::GetIterator iteratorObjInstr;
++ iteratorObjInstr.iterator = static_cast<int>(AST::ForEachType::Of);
++ bytecodeGenerator->addInstruction(iteratorObjInstr);
++ iterator.storeConsumeAccumulator();
++
++ BytecodeGenerator::Label done = bytecodeGenerator->newLabel();
++ Reference needsClose = Reference::storeConstOnStack(this, Encode(false));
++
++ for (PatternElementList *p = bindingList; p; p = p->next) {
++ PatternElement *e = p->element;
++ for (Elision *elision = p->elision; elision; elision = elision->next) {
++ iterator.loadInAccumulator();
++ Instruction::IteratorNext next;
++ if (!ignored.isValid())
++ ignored = Reference::fromStackSlot(this);
++ next.value = ignored.stackSlot();
++ bytecodeGenerator->addJumpInstruction(next).link(done);
++ }
++
++ if (!e)
++ continue;
++
++ if (e->type != PatternElement::RestElement) {
++ iterator.loadInAccumulator();
++ Instruction::IteratorNext next;
++ iteratorValues.push_back(Reference::fromStackSlot(this));
++ next.value = iteratorValues.back().stackSlot();
++ bytecodeGenerator->addJumpInstruction(next).link(done);
++ }
++ }
++
++ // If we've iterated through all the patterns without exhausing the iterator, it needs
++ // to be closed. But we don't close it here because:
++ // a, closing might throw an exception and we want to assign the values before we handle that
++ // b, there might be a rest element that could still continue iterating
++ Reference::fromConst(this, Encode(true)).storeOnStack(needsClose.stackSlot());
++
++ done.link();
++ bytecodeGenerator->checkException();
++
++ {
++ ControlFlowUnwindCleanup flow(this, [&]() {
++ BytecodeGenerator::Label skipClose = bytecodeGenerator->newLabel();
++ needsClose.loadInAccumulator();
++ bytecodeGenerator->jumpFalse().link(skipClose);
++ iterator.loadInAccumulator();
++ Instruction::IteratorClose close;
++ bytecodeGenerator->addInstruction(close);
++ skipClose.link();
++ });
++
++ auto it = iteratorValues.constBegin();
++ for (PatternElementList *p = bindingList; p; p = p->next) {
++ PatternElement *e = p->element;
++
++ if (!e)
++ continue;
++
++ if (e->type == PatternElement::RestElement) {
++ Q_ASSERT(it == iteratorValues.constEnd());
++
++ // The rest element is guaranteed to exhaust the iterator
++ Reference::fromConst(this, Encode(false)).storeOnStack(needsClose.stackSlot());
++
++ iterator.loadInAccumulator();
++ bytecodeGenerator->addInstruction(Instruction::DestructureRestElement());
++ initializeAndDestructureBindingElement(
++ e, Reference::fromAccumulator(this), isDefinition);
++ } else {
++ Q_ASSERT(it != iteratorValues.constEnd());
++ initializeAndDestructureBindingElement(e, *it++, isDefinition);
++ }
++
++ if (hasError())
++ return;
++ }
++ }
++ }
++
++ void Codegen::destructurePattern(Pattern *p, const Reference &rhs)
++ {
++ RegisterScope scope(this);
++ if (auto *o = AST::cast<ObjectPattern *>(p))
++ destructurePropertyList(rhs, o->properties);
++ else if (auto *a = AST::cast<ArrayPattern *>(p))
++ destructureElementList(rhs, a->elements);
++ else
++ Q_UNREACHABLE();
++ }
++
++
++ bool Codegen::visit(ArgumentList *)
++ {
++ Q_UNREACHABLE_RETURN(false);
++ }
++
++ bool Codegen::visit(CaseBlock *)
++ {
++ Q_UNREACHABLE_RETURN(false);
++ }
++
++ bool Codegen::visit(CaseClause *)
++ {
++ Q_UNREACHABLE_RETURN(false);
++ }
++
++ bool Codegen::visit(CaseClauses *)
++ {
++ Q_UNREACHABLE_RETURN(false);
++ }
++
++ bool Codegen::visit(Catch *)
++ {
++ Q_UNREACHABLE_RETURN(false);
++ }
++
++ bool Codegen::visit(DefaultClause *)
++ {
++ Q_UNREACHABLE_RETURN(false);
++ }
++
++ bool Codegen::visit(Elision *)
++ {
++ Q_UNREACHABLE_RETURN(false);
++ }
++
++ bool Codegen::visit(Finally *)
++ {
++ Q_UNREACHABLE_RETURN(false);
++ }
++
++ bool Codegen::visit(FormalParameterList *)
++ {
++ Q_UNREACHABLE_RETURN(false);
++ }
++
++ bool Codegen::visit(Program *)
++ {
++ Q_UNREACHABLE_RETURN(false);
++ }
++
++ bool Codegen::visit(PatternElement *)
++ {
++ Q_UNREACHABLE_RETURN(false);
++ }
++
++ bool Codegen::visit(PatternElementList *)
++ {
++ Q_UNREACHABLE_RETURN(false);
++ }
++
++ bool Codegen::visit(PatternProperty *)
++ {
++ Q_UNREACHABLE_RETURN(false);
++ }
++
++ bool Codegen::visit(PatternPropertyList *)
++ {
++ Q_UNREACHABLE_RETURN(false);
++ }
++
++ bool Codegen::visit(ExportDeclaration *ast)
++ {
++ if (!ast->exportDefault)
++ return true;
++
++ TailCallBlocker blockTailCalls(this);
++ Reference exportedValue;
++
++ if (auto *fdecl = AST::cast<FunctionDeclaration*>(ast->variableStatementOrDeclaration)) {
++ pushExpr();
++ visit(static_cast<FunctionExpression*>(fdecl));
++ exportedValue = popResult();
++ } else if (auto *classDecl = AST::cast<ClassDeclaration*>(ast->variableStatementOrDeclaration)) {
++ pushExpr();
++ visit(static_cast<ClassExpression*>(classDecl));
++ exportedValue = popResult();
++ } else if (ExpressionNode *expr = ast->variableStatementOrDeclaration->expressionCast()) {
++ exportedValue = expression(expr);
++ if (hasError())
++ return false;
++ }
++
++ exportedValue.loadInAccumulator();
++
++ const int defaultExportIndex = _context->locals.indexOf(_context->localNameForDefaultExport);
++ Q_ASSERT(defaultExportIndex != -1);
++ Reference defaultExportSlot = Reference::fromScopedLocal(this, defaultExportIndex, /*scope*/0);
++ defaultExportSlot.storeConsumeAccumulator();
++
++ return false;
++ }
++
++ bool Codegen::visit(TypeAnnotation *ast)
++ {
++ throwSyntaxError(ast->firstSourceLocation(), QLatin1String("Type annotations are not supported (yet)."));
++ return false;
++ }
++
++ bool Codegen::visit(StatementList *)
++ {
++ Q_UNREACHABLE_RETURN(false);
++ }
++
++ bool Codegen::visit(UiArrayMemberList *)
++ {
++ Q_UNREACHABLE_RETURN(false);
++ }
++
++ bool Codegen::visit(UiImport *)
++ {
++ Q_UNREACHABLE_RETURN(false);
++ }
++
++ bool Codegen::visit(UiHeaderItemList *)
++ {
++ Q_UNREACHABLE_RETURN(false);
++ }
++
++ bool Codegen::visit(UiPragmaValueList *)
++ {
++ Q_UNREACHABLE_RETURN(false);
++ }
++
++ bool Codegen::visit(UiPragma *)
++ {
++ Q_UNREACHABLE_RETURN(false);
++ }
++
++ bool Codegen::visit(UiObjectInitializer *)
++ {
++ Q_UNREACHABLE_RETURN(false);
++ }
++
++ bool Codegen::visit(UiObjectMemberList *)
++ {
++ Q_UNREACHABLE_RETURN(false);
++ }
++
++ bool Codegen::visit(UiParameterList *)
++ {
++ Q_UNREACHABLE_RETURN(false);
++ }
++
++ bool Codegen::visit(UiProgram *)
++ {
++ Q_UNREACHABLE_RETURN(false);
++ }
++
++ bool Codegen::visit(UiQualifiedId *)
++ {
++ Q_UNREACHABLE_RETURN(false);
++ }
++
++ bool Codegen::visit(VariableDeclarationList *)
++ {
++ Q_UNREACHABLE_RETURN(false);
++ }
++
++ bool Codegen::visit(ClassExpression *ast)
++ {
++ TailCallBlocker blockTailCalls(this);
++
++ Compiler::Class jsClass;
++ jsClass.nameIndex = registerString(ast->name.toString());
++
++ ClassElementList *constructor = nullptr;
++ int nComputedNames = 0;
++ int nStaticComputedNames = 0;
++
++ RegisterScope scope(this);
++ ControlFlowBlock controlFlow(this, ast);
++
++ for (auto *member = ast->elements; member; member = member->next) {
++ PatternProperty *p = member->property;
++ FunctionExpression *f = p->initializer->asFunctionDefinition();
++ Q_ASSERT(f);
++ AST::ComputedPropertyName *cname = AST::cast<ComputedPropertyName *>(p->name);
++ if (cname) {
++ ++nComputedNames;
++ if (member->isStatic)
++ ++nStaticComputedNames;
++ }
++ QString name = p->name->asString();
++ uint nameIndex = cname ? UINT_MAX : registerString(name);
++ Compiler::Class::Method::Type type = Compiler::Class::Method::Regular;
++ if (p->type == PatternProperty::Getter)
++ type = Compiler::Class::Method::Getter;
++ else if (p->type == PatternProperty::Setter)
++ type = Compiler::Class::Method::Setter;
++ Compiler::Class::Method m{ nameIndex, type, static_cast<uint>(defineFunction(name, f, f->formals, f->body)) };
++
++ if (member->isStatic) {
++ if (name == QStringLiteral("prototype")) {
++ throwSyntaxError(ast->firstSourceLocation(), QLatin1String("Cannot declare a static method named 'prototype'."));
++ return false;
++ }
++ jsClass.staticMethods << m;
++ } else {
++ if (name == QStringLiteral("constructor")) {
++ if (constructor) {
++ throwSyntaxError(ast->firstSourceLocation(), QLatin1String("Cannot declare a multiple constructors in a class."));
++ return false;
++ }
++ if (m.type != Compiler::Class::Method::Regular) {
++ throwSyntaxError(ast->firstSourceLocation(), QLatin1String("Cannot declare a getter or setter named 'constructor'."));
++ return false;
++ }
++ constructor = member;
++ jsClass.constructorIndex = m.functionIndex;
++ continue;
++ }
++
++ jsClass.methods << m;
++ }
++ }
++
++ int classIndex = _module->classes.size();
++ _module->classes.append(jsClass);
++
++ Reference heritage = Reference::fromStackSlot(this);
++ if (ast->heritage) {
++ bytecodeGenerator->setLocation(ast->heritage->firstSourceLocation());
++ Reference r = expression(ast->heritage);
++ if (hasError())
++ return false;
++ r.storeOnStack(heritage.stackSlot());
++ } else {
++ Reference::fromConst(this, StaticValue::emptyValue().asReturnedValue()).loadInAccumulator();
++ heritage.storeConsumeAccumulator();
++ }
++
++ int computedNames = nComputedNames ? bytecodeGenerator->newRegisterArray(nComputedNames) : 0;
++ int currentStaticName = computedNames;
++ int currentNonStaticName = computedNames + nStaticComputedNames;
++
++ for (auto *member = ast->elements; member; member = member->next) {
++ AST::ComputedPropertyName *cname = AST::cast<AST::ComputedPropertyName *>(member->property->name);
++ if (!cname)
++ continue;
++ RegisterScope scope(this);
++ bytecodeGenerator->setLocation(cname->firstSourceLocation());
++ Reference computedName = expression(cname->expression);
++ if (hasError())
++ return false;
++ computedName.storeOnStack(member->isStatic ? currentStaticName++ : currentNonStaticName++);
++ }
++
++ Instruction::CreateClass createClass;
++ createClass.classIndex = classIndex;
++ createClass.heritage = heritage.stackSlot();
++ createClass.computedNames = computedNames;
++
++ bytecodeGenerator->addInstruction(createClass);
++
++ if (!ast->name.isEmpty()) {
++ Reference ctor = referenceForName(ast->name.toString(), true);
++ ctor.isReferenceToConst = false; // this is the definition
++ (void) ctor.storeRetainAccumulator();
++ }
++
++ setExprResult(Reference::fromAccumulator(this));
++ return false;
++ }
++
++ bool Codegen::visit(ClassDeclaration *ast)
++ {
++ TailCallBlocker blockTailCalls(this);
++ Reference outerVar = referenceForName(ast->name.toString(), true);
++ visit(static_cast<ClassExpression *>(ast));
++ (void) outerVar.storeRetainAccumulator();
++ return false;
++ }
++
++ bool Codegen::visit(CommaExpression *ast)
++ {
++ if (hasError())
++ return false;
++
++ TailCallBlocker blockTailCalls(this);
++ statement(ast->left);
++ blockTailCalls.unblock();
++ clearExprResultName(); // The name only holds for the left part
++ accept(ast->right);
++ return false;
++ }
++
++ bool Codegen::visit(ArrayPattern *ast)
++ {
++ if (hasError())
++ return false;
++
++ TailCallBlocker blockTailCalls(this);
++
++ PatternElementList *it = ast->elements;
++
++ int argc = 0;
++ {
++ RegisterScope scope(this);
++
++ int args = -1;
++ auto push = [this, &argc, &args](AST::ExpressionNode *arg) {
++ int temp = bytecodeGenerator->newRegister();
++ if (args == -1)
++ args = temp;
++ if (!arg) {
++ auto c = Reference::fromConst(this, StaticValue::emptyValue().asReturnedValue());
++ (void) c.storeOnStack(temp);
++ } else {
++ RegisterScope scope(this);
++ Reference r = expression(arg);
++ if (hasError())
++ return;
++ (void) r.storeOnStack(temp);
++ }
++ ++argc;
++ };
++
++ for (; it; it = it->next) {
++ PatternElement *e = it->element;
++ if (e && e->type == PatternElement::SpreadElement)
++ break;
++ for (Elision *elision = it->elision; elision; elision = elision->next)
++ push(nullptr);
++
++ if (!e)
++ continue;
++
++ push(e->initializer);
++ if (hasError())
++ return false;
++ }
++
++ if (args == -1) {
++ Q_ASSERT(argc == 0);
++ args = 0;
++ }
++
++ Instruction::DefineArray call;
++ call.argc = argc;
++ call.args = Moth::StackSlot::createRegister(args);
++ bytecodeGenerator->addInstruction(call);
++ }
++
++ if (!it) {
++ setExprResult(Reference::fromAccumulator(this));
++ return false;
++ }
++ Q_ASSERT(it->element && it->element->type == PatternElement::SpreadElement);
++
++ RegisterScope scope(this);
++ Reference array = Reference::fromStackSlot(this);
++ array.storeConsumeAccumulator();
++ Reference index = Reference::storeConstOnStack(this, Encode(argc));
++
++ auto pushAccumulator = [&]() {
++ Reference slot = Reference::fromSubscript(array, index);
++ slot.storeConsumeAccumulator();
++
++ index.loadInAccumulator();
++ Instruction::Increment inc = {};
++ bytecodeGenerator->addInstruction(inc);
++ index.storeConsumeAccumulator();
++ };
++
++ while (it) {
++ for (Elision *elision = it->elision; elision; elision = elision->next) {
++ Reference::fromConst(
++ this, StaticValue::emptyValue().asReturnedValue()).loadInAccumulator();
++ pushAccumulator();
++ }
++
++ if (!it->element) {
++ it = it->next;
++ continue;
++ }
++
++ // handle spread element
++ if (it->element->type == PatternElement::SpreadElement) {
++ RegisterScope scope(this);
++
++ Reference iterator = Reference::fromStackSlot(this);
++ Reference lhsValue = Reference::fromStackSlot(this);
++
++ // There should be a temporal block, so that variables declared in lhs shadow outside vars.
++ // This block should define a temporal dead zone for those variables, which is not yet implemented.
++ {
++ RegisterScope innerScope(this);
++ Reference expr = expression(it->element->initializer);
++ if (hasError())
++ return false;
++
++ expr.loadInAccumulator();
++ Instruction::GetIterator iteratorObjInstr;
++ iteratorObjInstr.iterator = static_cast<int>(AST::ForEachType::Of);
++ bytecodeGenerator->addInstruction(iteratorObjInstr);
++ iterator.storeConsumeAccumulator();
++ }
++
++ BytecodeGenerator::Label in = bytecodeGenerator->newLabel();
++ BytecodeGenerator::Label end = bytecodeGenerator->newLabel();
++ BytecodeGenerator::Label done = bytecodeGenerator->newLabel();
++
++ {
++ auto cleanup = [this, iterator, done]() {
++ iterator.loadInAccumulator();
++ Instruction::IteratorClose close;
++ bytecodeGenerator->addInstruction(close);
++ done.link();
++ };
++ ControlFlowLoop flow(this, &end, &in, std::move(cleanup));
++
++ in.link();
++ bytecodeGenerator->addLoopStart(in);
++ iterator.loadInAccumulator();
++ Instruction::IteratorNext next;
++ next.value = lhsValue.stackSlot();
++ bytecodeGenerator->addJumpInstruction(next).link(done);
++
++ lhsValue.loadInAccumulator();
++ pushAccumulator();
++
++ bytecodeGenerator->checkException();
++ bytecodeGenerator->jump().link(in);
++ end.link();
++ }
++ } else {
++ RegisterScope innerScope(this);
++ Reference expr = expression(it->element->initializer);
++ if (hasError())
++ return false;
++
++ expr.loadInAccumulator();
++ pushAccumulator();
++ }
++
++ it = it->next;
++ }
++
++ array.loadInAccumulator();
++ setExprResult(Reference::fromAccumulator(this));
++
++ return false;
++ }
++
++ bool Codegen::visit(ArrayMemberExpression *ast)
++ {
++ if (hasError())
++ return false;
++
++ const bool isTailOfChain = traverseOptionalChain(ast);
++
++ TailCallBlocker blockTailCalls(this);
++ Reference base = expression(ast->base);
++ if (hasError())
++ return false;
++
++ auto writeSkip = [&]() {
++ base.loadInAccumulator();
++ bytecodeGenerator->addInstruction(Instruction::CmpEqNull());
++ auto jumpToUndefined = bytecodeGenerator->jumpTrue();
++ m_optionalChainsStates->top().jumpsToPatch.emplace_back(std::move(jumpToUndefined));
++ };
++
++ if (base.isSuper()) {
++ auto e = expression(ast->expression);
++ if (hasError())
++ return false;
++ Reference index = e.storeOnStack();
++ optionalChainFinalizer(Reference::fromSuperProperty(index), isTailOfChain);
++ return false;
++ }
++ base = base.storeOnStack();
++ if (hasError())
++ return false;
++ if (AST::StringLiteral *str = AST::cast<AST::StringLiteral *>(ast->expression)) {
++ QString s = str->value.toString();
++ uint arrayIndex = stringToArrayIndex(s);
++ if (arrayIndex == UINT_MAX) {
++ auto ref = Reference::fromMember(base, s, ast->expression->firstSourceLocation(),
++ ast->isOptional,
++ &m_optionalChainsStates->top().jumpsToPatch);
++ setExprResult(ref);
++ optionalChainFinalizer(ref, isTailOfChain);
++ return false;
++ }
++
++ if (ast->isOptional)
++ writeSkip();
++
++ Reference index = Reference::fromConst(this, QV4::Encode(arrayIndex));
++ optionalChainFinalizer(Reference::fromSubscript(base, index), isTailOfChain);
++ return false;
++ }
++
++
++ if (ast->isOptional)
++ writeSkip();
++
++ Reference index = expression(ast->expression);
++
++ if (hasError())
++ return false;
++
++ optionalChainFinalizer(Reference::fromSubscript(base, index), isTailOfChain);
++ return false;
++ }
++
++ static QSOperator::Op baseOp(int op)
++ {
++ switch ((QSOperator::Op) op) {
++ case QSOperator::InplaceAnd: return QSOperator::BitAnd;
++ case QSOperator::InplaceSub: return QSOperator::Sub;
++ case QSOperator::InplaceDiv: return QSOperator::Div;
++ case QSOperator::InplaceAdd: return QSOperator::Add;
++ case QSOperator::InplaceLeftShift: return QSOperator::LShift;
++ case QSOperator::InplaceMod: return QSOperator::Mod;
++ case QSOperator::InplaceExp: return QSOperator::Exp;
++ case QSOperator::InplaceMul: return QSOperator::Mul;
++ case QSOperator::InplaceOr: return QSOperator::BitOr;
++ case QSOperator::InplaceRightShift: return QSOperator::RShift;
++ case QSOperator::InplaceURightShift: return QSOperator::URShift;
++ case QSOperator::InplaceXor: return QSOperator::BitXor;
++ default: return QSOperator::Invalid;
++ }
++ }
++
++ bool Codegen::visit(BinaryExpression *ast)
++ {
++ if (hasError())
++ return false;
++
++ TailCallBlocker blockTailCalls(this);
++
++ if (ast->op == QSOperator::And) {
++ if (exprAccept(cx)) {
++ auto iftrue = bytecodeGenerator->newLabel();
++ condition(ast->left, &iftrue, currentExpr().iffalse(), true);
++ iftrue.link();
++ blockTailCalls.unblock();
++ const Result &expr = currentExpr();
++ condition(ast->right, expr.iftrue(), expr.iffalse(), expr.trueBlockFollowsCondition());
++ } else {
++ auto iftrue = bytecodeGenerator->newLabel();
++ auto endif = bytecodeGenerator->newLabel();
++
++ Reference left = expression(ast->left);
++ if (hasError())
++ return false;
++ left.loadInAccumulator();
++
++ bytecodeGenerator->setLocation(ast->operatorToken);
++ bytecodeGenerator->jumpFalse().link(endif);
++ iftrue.link();
++
++ blockTailCalls.unblock();
++ Reference right = expression(ast->right);
++ if (hasError())
++ return false;
++ right.loadInAccumulator();
++
++ endif.link();
++
++ setExprResult(Reference::fromAccumulator(this));
++ }
++ return false;
++ } else if (ast->op == QSOperator::Or) {
++ if (exprAccept(cx)) {
++ auto iffalse = bytecodeGenerator->newLabel();
++ condition(ast->left, currentExpr().iftrue(), &iffalse, false);
++ iffalse.link();
++ const Result &expr = currentExpr();
++ condition(ast->right, expr.iftrue(), expr.iffalse(), expr.trueBlockFollowsCondition());
++ } else {
++ auto iffalse = bytecodeGenerator->newLabel();
++ auto endif = bytecodeGenerator->newLabel();
++
++ Reference left = expression(ast->left);
++ if (hasError())
++ return false;
++ left.loadInAccumulator();
++
++ bytecodeGenerator->setLocation(ast->operatorToken);
++ bytecodeGenerator->jumpTrue().link(endif);
++ iffalse.link();
++
++ blockTailCalls.unblock();
++ Reference right = expression(ast->right);
++ if (hasError())
++ return false;
++ right.loadInAccumulator();
++
++ endif.link();
++
++ setExprResult(Reference::fromAccumulator(this));
++ }
++ return false;
++ } else if (ast->op == QSOperator::Coalesce) {
++
++ Reference left = expression(ast->left);
++ if (hasError())
++ return false;
++
++ BytecodeGenerator::Label iftrue = bytecodeGenerator->newLabel();
++
++ Instruction::CmpEqNull cmp;
++
++ left = left.storeOnStack();
++ left.loadInAccumulator();
++ bytecodeGenerator->addInstruction(cmp);
++
++ bytecodeGenerator->jumpTrue().link(iftrue);
++
++ blockTailCalls.unblock();
++
++ left.loadInAccumulator();
++ BytecodeGenerator::Jump jump_endif = bytecodeGenerator->jump();
++ iftrue.link();
++
++ Reference right = expression(ast->right);
++ if (hasError()) {
++ jump_endif.link();
++ return false;
++ }
++
++ right.loadInAccumulator();
++ jump_endif.link();
++ setExprResult(Reference::fromAccumulator(this));
++
++ return false;
++ } else if (ast->op == QSOperator::Assign) {
++ bytecodeGenerator->setLocation(ast->left->firstSourceLocation());
++ if (AST::Pattern *p = ast->left->patternCast()) {
++ RegisterScope scope(this);
++ Reference right = expression(ast->right);
++ if (hasError())
++ return false;
++ right = right.storeOnStack();
++ destructurePattern(p, right);
++ if (!exprAccept(nx)) {
++ right.loadInAccumulator();
++ setExprResult(Reference::fromAccumulator(this));
++ }
++ return false;
++ }
++ Reference left = expression(ast->left);
++ if (hasError())
++ return false;
++
++ if (!left.isLValue()) {
++ throwReferenceError(ast->operatorToken, QStringLiteral("left-hand side of assignment operator is not an lvalue"));
++ return false;
++ }
++ left = left.asLValue();
++ if (throwSyntaxErrorOnEvalOrArgumentsInStrictMode(left, ast->left->lastSourceLocation()))
++ return false;
++ blockTailCalls.unblock();
++ Reference r = expression(ast->right);
++ if (hasError())
++ return false;
++ if (left.requiresTDZCheck) {
++ r = r.storeOnStack();
++ left.checkTDZBeforeAssignment();
++ }
++ r.loadInAccumulator();
++ // The location saved above may have been overwritten when evaluating the rhs expression
++ bytecodeGenerator->setLocation(ast->left->firstSourceLocation());
++ if (exprAccept(nx))
++ setExprResult(left.storeConsumeAccumulator());
++ else
++ setExprResult(left.storeRetainAccumulator());
++ return false;
++ }
++
++ Reference left = expression(ast->left);
++ if (hasError())
++ return false;
++
++ switch (ast->op) {
++ case QSOperator::Or:
++ case QSOperator::And:
++ case QSOperator::Assign:
++ Q_UNREACHABLE(); // handled separately above
++ break;
++
++ case QSOperator::InplaceAnd:
++ case QSOperator::InplaceSub:
++ case QSOperator::InplaceDiv:
++ case QSOperator::InplaceAdd:
++ case QSOperator::InplaceLeftShift:
++ case QSOperator::InplaceMod:
++ case QSOperator::InplaceExp:
++ case QSOperator::InplaceMul:
++ case QSOperator::InplaceOr:
++ case QSOperator::InplaceRightShift:
++ case QSOperator::InplaceURightShift:
++ case QSOperator::InplaceXor: {
++ if (throwSyntaxErrorOnEvalOrArgumentsInStrictMode(left, ast->left->lastSourceLocation()))
++ return false;
++
++ if (!left.isLValue()) {
++ throwSyntaxError(ast->operatorToken, QStringLiteral("left-hand side of inplace operator is not an lvalue"));
++ return false;
++ }
++ left = left.asLValue();
++
++ Reference tempLeft = left.storeOnStack();
++ Reference right = expression(ast->right);
++
++ if (hasError())
++ return false;
++
++ binopHelper(ast, baseOp(ast->op), tempLeft, right).loadInAccumulator();
++ setExprResult(left.storeRetainAccumulator());
++
++ break;
++ }
++
++ case QSOperator::BitAnd:
++ case QSOperator::BitOr:
++ case QSOperator::BitXor:
++ if (left.isConstant()) {
++ Reference right = expression(ast->right);
++ if (hasError())
++ return false;
++ setExprResult(binopHelper(ast, static_cast<QSOperator::Op>(ast->op), right, left));
++ break;
++ }
++ Q_FALLTHROUGH();
++ case QSOperator::In:
++ case QSOperator::InstanceOf:
++ case QSOperator::As:
++ case QSOperator::Equal:
++ case QSOperator::NotEqual:
++ case QSOperator::Ge:
++ case QSOperator::Gt:
++ case QSOperator::Le:
++ case QSOperator::Lt:
++ case QSOperator::StrictEqual:
++ case QSOperator::StrictNotEqual:
++ case QSOperator::Add:
++ case QSOperator::Div:
++ case QSOperator::Exp:
++ case QSOperator::Mod:
++ case QSOperator::Mul:
++ case QSOperator::Sub:
++ case QSOperator::LShift:
++ case QSOperator::RShift:
++ case QSOperator::URShift: {
++ Reference right;
++ if (AST::NumericLiteral *rhs = AST::cast<AST::NumericLiteral *>(ast->right)) {
++ visit(rhs);
++ right = exprResult();
++ } else {
++ left = left.storeOnStack(); // force any loads of the lhs, so the rhs won't clobber it
++ right = expression(ast->right);
++ }
++ if (hasError())
++ return false;
++
++ setExprResult(binopHelper(ast, static_cast<QSOperator::Op>(ast->op), left, right));
++
++ break;
++ }
++ } // switch
++
++ return false;
++ }
++
++ Codegen::Reference Codegen::binopHelper(BinaryExpression *ast, QSOperator::Op oper, Reference &left,
++ Reference &right)
++ {
++ auto loc = combine(ast->left->firstSourceLocation(), ast->right->lastSourceLocation());
++ bytecodeGenerator->setLocation(loc);
++ switch (oper) {
++ case QSOperator::Add: {
++ left = left.storeOnStack();
++ right.loadInAccumulator();
++ Instruction::Add add;
++ add.lhs = left.stackSlot();
++ bytecodeGenerator->addInstruction(add);
++ break;
++ }
++ case QSOperator::Sub: {
++ if (right.isConstant() && right.constant == Encode(int(1))) {
++ left.loadInAccumulator();
++ Instruction::Decrement dec = {};
++ bytecodeGenerator->addInstruction(dec);
++ } else {
++ left = left.storeOnStack();
++ right.loadInAccumulator();
++ Instruction::Sub sub;
++ sub.lhs = left.stackSlot();
++ bytecodeGenerator->addInstruction(sub);
++ }
++ break;
++ }
++ case QSOperator::Exp: {
++ left = left.storeOnStack();
++ right.loadInAccumulator();
++ Instruction::Exp exp;
++ exp.lhs = left.stackSlot();
++ bytecodeGenerator->addInstruction(exp);
++ break;
++ }
++ case QSOperator::Mul: {
++ left = left.storeOnStack();
++ right.loadInAccumulator();
++ Instruction::Mul mul;
++ mul.lhs = left.stackSlot();
++ bytecodeGenerator->addInstruction(mul);
++ break;
++ }
++ case QSOperator::Div: {
++ left = left.storeOnStack();
++ right.loadInAccumulator();
++ Instruction::Div div;
++ div.lhs = left.stackSlot();
++ bytecodeGenerator->addInstruction(div);
++ break;
++ }
++ case QSOperator::Mod: {
++ left = left.storeOnStack();
++ right.loadInAccumulator();
++ Instruction::Mod mod;
++ mod.lhs = left.stackSlot();
++ bytecodeGenerator->addInstruction(mod);
++ break;
++ }
++ case QSOperator::BitAnd:
++ if (right.isConstant()) {
++ int rightAsInt = StaticValue::fromReturnedValue(right.constant).toInt32();
++ if (left.isConstant()) {
++ int result = StaticValue::fromReturnedValue(left.constant).toInt32() & rightAsInt;
++ return Reference::fromConst(this, Encode(result));
++ }
++ left.loadInAccumulator();
++ Instruction::BitAndConst bitAnd;
++ bitAnd.rhs = rightAsInt;
++ bytecodeGenerator->addInstruction(bitAnd);
++ } else {
++ right.loadInAccumulator();
++ Instruction::BitAnd bitAnd;
++ bitAnd.lhs = left.stackSlot();
++ bytecodeGenerator->addInstruction(bitAnd);
++ }
++ break;
++ case QSOperator::BitOr:
++ if (right.isConstant()) {
++ int rightAsInt = StaticValue::fromReturnedValue(right.constant).toInt32();
++ if (left.isConstant()) {
++ int result = StaticValue::fromReturnedValue(left.constant).toInt32() | rightAsInt;
++ return Reference::fromConst(this, Encode(result));
++ }
++ left.loadInAccumulator();
++ Instruction::BitOrConst bitOr;
++ bitOr.rhs = rightAsInt;
++ bytecodeGenerator->addInstruction(bitOr);
++ } else {
++ right.loadInAccumulator();
++ Instruction::BitOr bitOr;
++ bitOr.lhs = left.stackSlot();
++ bytecodeGenerator->addInstruction(bitOr);
++ }
++ break;
++ case QSOperator::BitXor:
++ if (right.isConstant()) {
++ int rightAsInt = StaticValue::fromReturnedValue(right.constant).toInt32();
++ if (left.isConstant()) {
++ int result = StaticValue::fromReturnedValue(left.constant).toInt32() ^ rightAsInt;
++ return Reference::fromConst(this, Encode(result));
++ }
++ left.loadInAccumulator();
++ Instruction::BitXorConst bitXor;
++ bitXor.rhs = rightAsInt;
++ bytecodeGenerator->addInstruction(bitXor);
++ } else {
++ right.loadInAccumulator();
++ Instruction::BitXor bitXor;
++ bitXor.lhs = left.stackSlot();
++ bytecodeGenerator->addInstruction(bitXor);
++ }
++ break;
++ case QSOperator::URShift:
++ if (right.isConstant()) {
++ left.loadInAccumulator();
++ Instruction::UShrConst ushr;
++ ushr.rhs = StaticValue::fromReturnedValue(right.constant).toInt32() & 0x1f;
++ bytecodeGenerator->addInstruction(ushr);
++ } else {
++ right.loadInAccumulator();
++ Instruction::UShr ushr;
++ ushr.lhs = left.stackSlot();
++ bytecodeGenerator->addInstruction(ushr);
++ }
++ break;
++ case QSOperator::RShift:
++ if (right.isConstant()) {
++ left.loadInAccumulator();
++ Instruction::ShrConst shr;
++ shr.rhs = StaticValue::fromReturnedValue(right.constant).toInt32() & 0x1f;
++ bytecodeGenerator->addInstruction(shr);
++ } else {
++ right.loadInAccumulator();
++ Instruction::Shr shr;
++ shr.lhs = left.stackSlot();
++ bytecodeGenerator->addInstruction(shr);
++ }
++ break;
++ case QSOperator::LShift:
++ if (right.isConstant()) {
++ left.loadInAccumulator();
++ Instruction::ShlConst shl;
++ shl.rhs = StaticValue::fromReturnedValue(right.constant).toInt32() & 0x1f;
++ bytecodeGenerator->addInstruction(shl);
++ } else {
++ right.loadInAccumulator();
++ Instruction::Shl shl;
++ shl.lhs = left.stackSlot();
++ bytecodeGenerator->addInstruction(shl);
++ }
++ break;
++ case QSOperator::InstanceOf: {
++ Instruction::CmpInstanceOf binop;
++ left = left.storeOnStack();
++ right.loadInAccumulator();
++ binop.lhs = left.stackSlot();
++ bytecodeGenerator->addInstruction(binop);
++ break;
++ }
++ case QSOperator::As: {
++ Instruction::As as;
++ left = left.storeOnStack();
++ right.loadInAccumulator();
++ as.lhs = left.stackSlot();
++ bytecodeGenerator->addInstruction(as);
++ break;
++ }
++ case QSOperator::In: {
++ Instruction::CmpIn binop;
++ left = left.storeOnStack();
++ right.loadInAccumulator();
++ binop.lhs = left.stackSlot();
++ bytecodeGenerator->addInstruction(binop);
++ break;
++ }
++ case QSOperator::StrictEqual: {
++ if (exprAccept(cx))
++ return jumpBinop(oper, left, right);
++
++ Instruction::CmpStrictEqual cmp;
++ left = left.storeOnStack();
++ right.loadInAccumulator();
++ cmp.lhs = left.stackSlot();
++ bytecodeGenerator->addInstruction(cmp);
++ break;
++ }
++ case QSOperator::StrictNotEqual: {
++ if (exprAccept(cx))
++ return jumpBinop(oper, left, right);
++
++ Instruction::CmpStrictNotEqual cmp;
++ left = left.storeOnStack();
++ right.loadInAccumulator();
++ cmp.lhs = left.stackSlot();
++ bytecodeGenerator->addInstruction(cmp);
++ break;
++ }
++ case QSOperator::Equal: {
++ if (exprAccept(cx))
++ return jumpBinop(oper, left, right);
++
++ Instruction::CmpEq cmp;
++ left = left.storeOnStack();
++ right.loadInAccumulator();
++ cmp.lhs = left.stackSlot();
++ bytecodeGenerator->addInstruction(cmp);
++ break;
++ }
++ case QSOperator::NotEqual: {
++ if (exprAccept(cx))
++ return jumpBinop(oper, left, right);
++
++ Instruction::CmpNe cmp;
++ left = left.storeOnStack();
++ right.loadInAccumulator();
++ cmp.lhs = left.stackSlot();
++ bytecodeGenerator->addInstruction(cmp);
++ break;
++ }
++ case QSOperator::Gt: {
++ if (exprAccept(cx))
++ return jumpBinop(oper, left, right);
++
++ Instruction::CmpGt cmp;
++ left = left.storeOnStack();
++ right.loadInAccumulator();
++ cmp.lhs = left.stackSlot();
++ bytecodeGenerator->addInstruction(cmp);
++ break;
++ }
++ case QSOperator::Ge: {
++ if (exprAccept(cx))
++ return jumpBinop(oper, left, right);
++
++ Instruction::CmpGe cmp;
++ left = left.storeOnStack();
++ right.loadInAccumulator();
++ cmp.lhs = left.stackSlot();
++ bytecodeGenerator->addInstruction(cmp);
++ break;
++ }
++ case QSOperator::Lt: {
++ if (exprAccept(cx))
++ return jumpBinop(oper, left, right);
++
++ Instruction::CmpLt cmp;
++ left = left.storeOnStack();
++ right.loadInAccumulator();
++ cmp.lhs = left.stackSlot();
++ bytecodeGenerator->addInstruction(cmp);
++ break;
++ }
++ case QSOperator::Le:
++ if (exprAccept(cx))
++ return jumpBinop(oper, left, right);
++
++ Instruction::CmpLe cmp;
++ left = left.storeOnStack();
++ right.loadInAccumulator();
++ cmp.lhs = left.stackSlot();
++ bytecodeGenerator->addInstruction(cmp);
++ break;
++ default:
++ Q_UNREACHABLE();
++ }
++
++ return Reference::fromAccumulator(this);
++ }
++
++ Codegen::Reference Codegen::jumpBinop(QSOperator::Op oper, Reference &left, Reference &right)
++ {
++ // See if we can generate specialized comparison instructions:
++ if (oper == QSOperator::Equal || oper == QSOperator::NotEqual) {
++ // Because == and != are reflexive, we can do the following:
++ if (left.isConstant() && !right.isConstant())
++ qSwap(left, right); // null==a -> a==null
++
++ if (right.isConstant()) {
++ StaticValue c = StaticValue::fromReturnedValue(right.constant);
++ if (c.isNull() || c.isUndefined()) {
++ left.loadInAccumulator();
++ if (oper == QSOperator::Equal) {
++ Instruction::CmpEqNull cmp;
++ bytecodeGenerator->addInstruction(cmp);
++ addCJump();
++ return Reference();
++ } else if (oper == QSOperator::NotEqual) {
++ Instruction::CmpNeNull cmp;
++ bytecodeGenerator->addInstruction(cmp);
++ addCJump();
++ return Reference();
++ }
++ } else if (c.isInt32()) {
++ left.loadInAccumulator();
++ if (oper == QSOperator::Equal) {
++ Instruction::CmpEqInt cmp;
++ cmp.lhs = c.int_32();
++ bytecodeGenerator->addInstruction(cmp);
++ addCJump();
++ return Reference();
++ } else if (oper == QSOperator::NotEqual) {
++ Instruction::CmpNeInt cmp;
++ cmp.lhs = c.int_32();
++ bytecodeGenerator->addInstruction(cmp);
++ addCJump();
++ return Reference();
++ }
++
++ }
++ }
++ }
++
++ left = left.storeOnStack();
++ right.loadInAccumulator();
++
++ switch (oper) {
++ case QSOperator::StrictEqual: {
++ Instruction::CmpStrictEqual cmp;
++ cmp.lhs = left.stackSlot();
++ bytecodeGenerator->addInstruction(cmp);
++ addCJump();
++ break;
++ }
++ case QSOperator::StrictNotEqual: {
++ Instruction::CmpStrictNotEqual cmp;
++ cmp.lhs = left.stackSlot();
++ bytecodeGenerator->addInstruction(cmp);
++ addCJump();
++ break;
++ }
++ case QSOperator::Equal: {
++ Instruction::CmpEq cmp;
++ cmp.lhs = left.stackSlot();
++ bytecodeGenerator->addInstruction(cmp);
++ addCJump();
++ break;
++ }
++ case QSOperator::NotEqual: {
++ Instruction::CmpNe cmp;
++ cmp.lhs = left.stackSlot();
++ bytecodeGenerator->addInstruction(cmp);
++ addCJump();
++ break;
++ }
++ case QSOperator::Gt: {
++ Instruction::CmpGt cmp;
++ cmp.lhs = left.stackSlot();
++ bytecodeGenerator->addInstruction(cmp);
++ addCJump();
++ break;
++ }
++ case QSOperator::Ge: {
++ Instruction::CmpGe cmp;
++ cmp.lhs = left.stackSlot();
++ bytecodeGenerator->addInstruction(cmp);
++ addCJump();
++ break;
++ }
++ case QSOperator::Lt: {
++ Instruction::CmpLt cmp;
++ cmp.lhs = left.stackSlot();
++ bytecodeGenerator->addInstruction(cmp);
++ addCJump();
++ break;
++ }
++ case QSOperator::Le: {
++ Instruction::CmpLe cmp;
++ cmp.lhs = left.stackSlot();
++ bytecodeGenerator->addInstruction(cmp);
++ addCJump();
++ break;
++ }
++ default:
++ Q_UNREACHABLE();
++ }
++ return Reference();
++ }
++
++ Codegen::Reference Codegen::loadSubscriptForCall(const Codegen::Reference &base)
++ {
++ // Retrieve the function to be called before generating the arguments.
++ // Generating the arguments might change the array.
++ base.elementSubscript.loadInAccumulator();
++ Codegen::Instruction::LoadElement load;
++ load.base = base.elementBase;
++ bytecodeGenerator->addInstruction(load);
++ return Reference::fromAccumulator(this);
++ }
++
++ bool Codegen::visit(CallExpression *ast)
++ {
++ if (hasError())
++ return false;
++
++ const bool isTailOfChain = traverseOptionalChain(ast);
++
++ RegisterScope scope(this);
++ TailCallBlocker blockTailCalls(this);
++
++ Reference expr = expression(ast->base);
++ Reference base = expr;
++
++ if (hasError())
++ return false;
++ switch (base.type) {
++ case Reference::Member:
++ base = base.asLValue();
++ break;
++ case Reference::Subscript:
++ base.element = loadSubscriptForCall(base).storeOnStack().stackSlot();
++ base.subscriptLoadedForCall = true;
++ break;
++ case Reference::Name:
++ break;
++ case Reference::Super:
++ handleConstruct(base, ast->arguments);
++ return false;
++ case Reference::SuperProperty:
++ break;
++ default:
++ base = base.storeOnStack();
++ break;
++ }
++
++ if (expr.hasSavedCallBaseSlot) {
++ // Hack to preserve `this` context in optional chain calls. See optionalChainFinalizer().
++ base.hasSavedCallBaseSlot = true;
++ base.savedCallBaseSlot = expr.savedCallBaseSlot;
++ base.savedCallPropertyNameIndex = expr.savedCallPropertyNameIndex;
++ }
++
++ int thisObject = bytecodeGenerator->newRegister();
++ int functionObject = bytecodeGenerator->newRegister();
++
++ if (ast->isOptional || m_optionalChainsStates->top().actuallyHasOptionals) {
++ base.loadInAccumulator();
++ bytecodeGenerator->addInstruction(Instruction::CmpEqNull());
++ auto jumpToUndefined = bytecodeGenerator->jumpTrue();
++ m_optionalChainsStates->top().jumpsToPatch.emplace_back(std::move(jumpToUndefined));
++ }
++
++ auto calldata = pushArgs(ast->arguments);
++ if (hasError())
++ return false;
++
++ blockTailCalls.unblock();
++ if (calldata.hasSpread || _tailCallsAreAllowed) {
++ Reference baseObject = base.baseObject();
++ if (!baseObject.isStackSlot()) {
++ baseObject.storeOnStack(thisObject);
++ baseObject = Reference::fromStackSlot(this, thisObject);
++ }
++
++ const int func = [&]() {
++ if (base.type == Reference::Subscript)
++ return base.element;
++
++ if (!base.isStackSlot()) {
++ base.storeOnStack(functionObject);
++ base = Reference::fromStackSlot(this, functionObject);
++ }
++
++ return base.stackSlot();
++ }();
++
++ if (calldata.hasSpread) {
++ Instruction::CallWithSpread call;
++ call.func = func;
++ call.thisObject = baseObject.stackSlot();
++ call.argc = calldata.argc;
++ call.argv = calldata.argv;
++ bytecodeGenerator->addInstruction(call);
++ } else {
++ Instruction::TailCall call;
++ call.func = func;
++ call.thisObject = baseObject.stackSlot();
++ call.argc = calldata.argc;
++ call.argv = calldata.argv;
++ bytecodeGenerator->addInstruction(call);
++ }
++
++ optionalChainFinalizer(Reference::fromAccumulator(this), isTailOfChain);
++ return false;
++ }
++
++ handleCall(base, calldata, functionObject, thisObject, ast->isOptional);
++ optionalChainFinalizer(Reference::fromAccumulator(this), isTailOfChain);
++ return false;
++ }
++
++ void Codegen::endVisit(CallExpression *ast)
++ {
++ m_seenOptionalChainNodes.remove(ast);
++ }
++
++ void Codegen::handleCall(Reference &base, Arguments calldata, int slotForFunction, int slotForThisObject, bool optional)
++ {
++ if (base.sourceLocation.isValid())
++ bytecodeGenerator->setLocation(base.sourceLocation);
++
++ //### Do we really need all these call instructions? can's we load the callee in a temp?
++ if (base.type == Reference::Member || base.hasSavedCallBaseSlot) {
++ if (useFastLookups) {
++ Instruction::CallPropertyLookup call;
++ if (base.hasSavedCallBaseSlot) {
++ call.base = base.savedCallBaseSlot;
++ call.lookupIndex = registerGetterLookup(
++ base.savedCallPropertyNameIndex, JSUnitGenerator::LookupForCall);
++ } else {
++ call.base = base.propertyBase.stackSlot();
++ call.lookupIndex = registerGetterLookup(
++ base.propertyNameIndex, JSUnitGenerator::LookupForCall);
++ }
++ call.argc = calldata.argc;
++ call.argv = calldata.argv;
++ bytecodeGenerator->addInstruction(call);
++ } else {
++ Instruction::CallProperty call;
++ if (base.hasSavedCallBaseSlot) {
++ call.base = base.savedCallBaseSlot;
++ call.name = base.savedCallPropertyNameIndex;
++ } else {
++ call.base = base.propertyBase.stackSlot();
++ call.name = base.propertyNameIndex;
++ }
++ call.argc = calldata.argc;
++ call.argv = calldata.argv;
++ bytecodeGenerator->addInstruction(call);
++ }
++ } else if (base.type == Reference::Subscript) {
++ Instruction::CallWithReceiver call;
++ call.thisObject = base.elementBase.stackSlot();
++ call.name = base.element;
++ call.argc = calldata.argc;
++ call.argv = calldata.argv;
++ bytecodeGenerator->addInstruction(call);
++ } else if (base.type == Reference::Name) {
++ if (base.name == QStringLiteral("eval") && !optional) {
++ Instruction::CallPossiblyDirectEval call;
++ call.argc = calldata.argc;
++ call.argv = calldata.argv;
++ bytecodeGenerator->addInstruction(call);
++ } else if (useFastLookups && base.global) {
++ if (base.qmlGlobal) {
++ Instruction::CallQmlContextPropertyLookup call;
++ call.index = registerQmlContextPropertyGetterLookup(
++ base.nameAsIndex(), JSUnitGenerator::LookupForCall);
++ call.argc = calldata.argc;
++ call.argv = calldata.argv;
++ bytecodeGenerator->addInstruction(call);
++ } else {
++ Instruction::CallGlobalLookup call;
++ call.index = registerGlobalGetterLookup(
++ base.nameAsIndex(), JSUnitGenerator::LookupForCall);
++ call.argc = calldata.argc;
++ call.argv = calldata.argv;
++ bytecodeGenerator->addInstruction(call);
++ }
++ } else {
++ Instruction::CallName call;
++ call.name = base.nameAsIndex();
++ call.argc = calldata.argc;
++ call.argv = calldata.argv;
++ bytecodeGenerator->addInstruction(call);
++ }
++ } else if (base.type == Reference::SuperProperty) {
++ Reference receiver = base.baseObject();
++ if (!base.isStackSlot()) {
++ base.storeOnStack(slotForFunction);
++ base = Reference::fromStackSlot(this, slotForFunction);
++ }
++ if (!receiver.isStackSlot()) {
++ receiver.storeOnStack(slotForThisObject);
++ receiver = Reference::fromStackSlot(this, slotForThisObject);
++ }
++ Instruction::CallWithReceiver call;
++ call.name = base.stackSlot();
++ call.thisObject = receiver.stackSlot();
++ call.argc = calldata.argc;
++ call.argv = calldata.argv;
++ bytecodeGenerator->addInstruction(call);
++ } else {
++ Q_ASSERT(base.isStackSlot());
++ Instruction::CallValue call;
++ call.name = base.stackSlot();
++ call.argc = calldata.argc;
++ call.argv = calldata.argv;
++ bytecodeGenerator->addInstruction(call);
++ }
++ }
++
++ Codegen::Arguments Codegen::pushArgs(ArgumentList *args)
++ {
++ bool hasSpread = false;
++ int argc = 0;
++ for (ArgumentList *it = args; it; it = it->next) {
++ if (it->spreadToken.isValid()) {
++ hasSpread = true;
++ ++argc;
++ }
++ ++argc;
++ }
++
++ if (!argc)
++ return { 0, 0, false };
++
++ int calldata = bytecodeGenerator->newRegisterArray(argc);
++
++ argc = 0;
++ for (ArgumentList *it = args; it; it = it->next) {
++ if (it->spreadToken.isValid()) {
++ Reference::fromConst(
++ this,
++ StaticValue::emptyValue().asReturnedValue()).storeOnStack(calldata + argc);
++ ++argc;
++ }
++ RegisterScope scope(this);
++ Reference e = expression(it->expression);
++ if (hasError())
++ break;
++ if (!argc && !it->next && !hasSpread) {
++ // avoid copy for functions taking a single argument
++ if (e.isStackSlot()) {
++ e.tdzCheck();
++ return { 1, e.stackSlot(), hasSpread };
++ }
++ }
++ (void) e.storeOnStack(calldata + argc);
++ ++argc;
++ }
++
++ return { argc, calldata, hasSpread };
++ }
++
++ Codegen::Arguments Codegen::pushTemplateArgs(TemplateLiteral *args)
++ {
++ int argc = 0;
++ for (TemplateLiteral *it = args; it; it = it->next)
++ ++argc;
++
++ if (!argc)
++ return { 0, 0, false };
++
++ int calldata = bytecodeGenerator->newRegisterArray(argc);
++
++ argc = 0;
++ for (TemplateLiteral *it = args; it && it->expression; it = it->next) {
++ RegisterScope scope(this);
++ Reference e = expression(it->expression);
++ if (hasError())
++ break;
++ (void) e.storeOnStack(calldata + argc);
++ ++argc;
++ }
++
++ return { argc, calldata, false };
++ }
++
++ bool Codegen::visit(ConditionalExpression *ast)
++ {
++ if (hasError())
++ return false;
++
++ RegisterScope scope(this);
++ TailCallBlocker blockTailCalls(this);
++
++ BytecodeGenerator::Label iftrue = bytecodeGenerator->newLabel();
++ BytecodeGenerator::Label iffalse = bytecodeGenerator->newLabel();
++ condition(ast->expression, &iftrue, &iffalse, true);
++
++ blockTailCalls.unblock();
++
++ iftrue.link();
++ Reference ok = expression(ast->ok);
++ if (hasError())
++ return false;
++ ok.loadInAccumulator();
++ BytecodeGenerator::Jump jump_endif = bytecodeGenerator->jump();
++
++ iffalse.link();
++ Reference ko = expression(ast->ko);
++ if (hasError()) {
++ jump_endif.link(); // dummy link, to prevent assert in Jump destructor from triggering
++ return false;
++ }
++ ko.loadInAccumulator();
++
++ jump_endif.link();
++ setExprResult(Reference::fromAccumulator(this));
++
++ return false;
++ }
++
++ bool Codegen::visit(DeleteExpression *ast)
++ {
++ if (hasError())
++ return false;
++
++ const bool isTailOfChain = traverseOptionalChain(ast);
++
++ RegisterScope scope(this);
++ TailCallBlocker blockTailCalls(this);
++ Reference expr = expression(ast->expression);
++ if (hasError())
++ return false;
++
++ const bool chainActuallyHasOptionals = m_optionalChainsStates->top().actuallyHasOptionals;
++ if (chainActuallyHasOptionals)
++ Q_ASSERT(expr.type == Reference::Member || expr.type == Reference::Subscript);
++
++ switch (expr.type) {
++ case Reference::SuperProperty:
++ // ### this should throw a reference error at runtime.
++ return false;
++ case Reference::StackSlot:
++ if (!expr.stackSlotIsLocalOrArgument)
++ break;
++ Q_FALLTHROUGH();
++ case Reference::ScopedLocal:
++ // Trying to delete a function argument might throw.
++ if (_context->isStrict) {
++ throwSyntaxError(ast->deleteToken, QStringLiteral("Delete of an unqualified identifier in strict mode."));
++ return false;
++ }
++ setExprResult(Reference::fromConst(this, QV4::Encode(false)));
++ return false;
++ case Reference::Name: {
++ if (_context->isStrict) {
++ throwSyntaxError(ast->deleteToken, QStringLiteral("Delete of an unqualified identifier in strict mode."));
++ return false;
++ }
++ Instruction::DeleteName del;
++ del.name = expr.nameAsIndex();
++ bytecodeGenerator->addInstruction(del);
++ setExprResult(Reference::fromAccumulator(this));
++ return false;
++ }
++ case Reference::Member: {
++ //### maybe add a variant where the base can be in the accumulator?
++ expr = expr.asLValue();
++
++ if (chainActuallyHasOptionals) {
++ expr.loadInAccumulator();
++ bytecodeGenerator->addInstruction(Instruction::CmpEqNull());
++ auto jumpToUndefined = bytecodeGenerator->jumpTrue();
++ m_optionalChainsStates->top().jumpsToPatch.emplace_back(std::move(jumpToUndefined));
++ }
++
++ Instruction::LoadRuntimeString instr;
++ instr.stringId = expr.propertyNameIndex;
++ bytecodeGenerator->addInstruction(instr);
++ Reference index = Reference::fromStackSlot(this);
++ index.storeConsumeAccumulator();
++ Instruction::DeleteProperty del;
++ del.base = expr.propertyBase.stackSlot();
++ del.index = index.stackSlot();
++ bytecodeGenerator->addInstruction(del);
++ auto ref = Reference::fromAccumulator(this);
++
++ optionalChainFinalizer(ref, isTailOfChain, true);
++ return false;
++ }
++ case Reference::Subscript: {
++ //### maybe add a variant where the index can be in the accumulator?
++ expr = expr.asLValue();
++
++ if (chainActuallyHasOptionals) {
++ expr.loadInAccumulator();
++ bytecodeGenerator->addInstruction(Instruction::CmpEqNull());
++ auto jumpToUndefined = bytecodeGenerator->jumpTrue();
++ m_optionalChainsStates->top().jumpsToPatch.emplace_back(std::move(jumpToUndefined));
++ }
++
++ Instruction::DeleteProperty del;
++ del.base = expr.elementBase;
++ del.index = expr.elementSubscript.stackSlot();
++ bytecodeGenerator->addInstruction(del);
++ auto ref = Reference::fromAccumulator(this);
++
++ optionalChainFinalizer(ref, isTailOfChain, true);
++ return false;
++ }
++ default:
++ break;
++ }
++ // [[11.4.1]] Return true if it's not a reference
++ setExprResult(Reference::fromConst(this, QV4::Encode(true)));
++ return false;
++ }
++
++ void Codegen::endVisit(DeleteExpression *ast) {
++ m_seenOptionalChainNodes.remove(ast);
++ }
++
++ bool Codegen::visit(FalseLiteral *)
++ {
++ if (hasError())
++ return false;
++
++ setExprResult(Reference::fromConst(this, QV4::Encode(false)));
++ return false;
++ }
++
++ bool Codegen::visit(SuperLiteral *)
++ {
++ if (hasError())
++ return false;
++
++ setExprResult(Reference::fromSuper(this));
++ return false;
++ }
++
++ bool Codegen::traverseOptionalChain(Node *node)
++ {
++ if (m_seenOptionalChainNodes.contains(node))
++ return false;
++
++ const auto isOptionalChainableNode = [](const Node *node) {
++ return node->kind == Node::Kind_FieldMemberExpression ||
++ node->kind == Node::Kind_CallExpression ||
++ node->kind == Node::Kind_ArrayMemberExpression ||
++ node->kind == Node::Kind_DeleteExpression;
++ };
++ m_optionalChainsStates->emplace();
++ while (isOptionalChainableNode(node)) {
++ m_seenOptionalChainNodes.insert(node);
++
++ switch (node->kind) {
++ case Node::Kind_FieldMemberExpression: {
++ auto *fme = AST::cast<FieldMemberExpression *>(node);
++ m_optionalChainsStates->top().actuallyHasOptionals |= fme->isOptional;
++ node = fme->base;
++ break;
++ }
++ case Node::Kind_CallExpression: {
++ auto *ce = AST::cast<CallExpression *>(node);
++ m_optionalChainsStates->top().actuallyHasOptionals |= ce->isOptional;
++ node = ce->base;
++ break;
++ }
++ case Node::Kind_ArrayMemberExpression: {
++ auto *ame = AST::cast<ArrayMemberExpression *>(node);
++ m_optionalChainsStates->top().actuallyHasOptionals |= ame->isOptional;
++ node = ame->base;
++ break;
++ }
++ case Node::Kind_DeleteExpression:
++ node = AST::cast<DeleteExpression *>(node)->expression;
++ break;
++ default:
++ Q_UNREACHABLE();
++ }
++ }
++
++ return true;
++ }
++
++ void Codegen::optionalChainFinalizer(const Reference &expressionResult, bool tailOfChain,
++ bool isDeleteExpression)
++ {
++ auto &chainState = m_optionalChainsStates->top();
++ if (!tailOfChain) {
++ setExprResult(expressionResult);
++ return;
++ } else if (!chainState.actuallyHasOptionals) {
++ setExprResult(expressionResult);
++ m_optionalChainsStates->pop();
++ return;
++ }
++
++ auto savedBaseSlot = -1;
++ if (expressionResult.type == Reference::Member)
++ savedBaseSlot = expressionResult.propertyBase.storeOnStack().stackSlot();
++ expressionResult.loadInAccumulator();
++
++ std::optional<Moth::BytecodeGenerator::Jump> jumpToDone;
++ if (!isDeleteExpression) // Delete expressions always return true, avoid the extra jump
++ jumpToDone.emplace(bytecodeGenerator->jump());
++
++ for (auto &jump : chainState.jumpsToPatch)
++ jump.link();
++
++ if (isDeleteExpression)
++ bytecodeGenerator->addInstruction(Instruction::LoadTrue());
++ else
++ bytecodeGenerator->addInstruction(Instruction::LoadUndefined());
++
++ if (jumpToDone.has_value())
++ jumpToDone.value().link();
++
++ auto ref = Reference::fromAccumulator(this);
++ if (expressionResult.type == Reference::Member) {
++ /* Because the whole optional chain is handled at once with a chain finalizer instead of
++ * instruction by instruction, the result of the chain (either undefined or the result of
++ * the optional operation) is stored in the accumulator. This works fine except for one
++ * edge case where the `this` context is required in a call
++ * (see tst_ecmascripttests: language/expressions/optional-chaining/optional-call-preserves-this.js).
++ *
++ * In order to preserve the `this` context in the call, the call base and the property name
++ * index need to be available as with a Member reference. However, since the result must be
++ * in the accumulator the resulting reference is of type Accumulator. Therefore, the call
++ * base and the property name index are `glued` to an accumulator reference to make it work
++ * when deciding which call instruction to use later on.
++ */
++ ref.hasSavedCallBaseSlot = true;
++ ref.savedCallBaseSlot = savedBaseSlot;
++ ref.savedCallPropertyNameIndex = expressionResult.propertyNameIndex;
++ }
++ setExprResult(ref);
++ m_optionalChainsStates->pop();
++ }
++
++ bool Codegen::visit(FieldMemberExpression *ast)
++ {
++ if (hasError())
++ return false;
++
++ const bool isTailOfChain = traverseOptionalChain(ast);
++
++ TailCallBlocker blockTailCalls(this);
++
++ if (AST::IdentifierExpression *id = AST::cast<AST::IdentifierExpression *>(ast->base)) {
++ if (id->name == QLatin1String("new")) {
++ // new.target
++ Q_ASSERT(ast->name == QLatin1String("target"));
++
++ if (_context->isArrowFunction || _context->contextType == ContextType::Eval) {
++ Reference r = referenceForName(QStringLiteral("new.target"), false);
++ r.isReadonly = true;
++ setExprResult(r);
++
++ return false;
++ }
++
++ auto ref = Reference::fromStackSlot(this, CallData::NewTarget);
++ optionalChainFinalizer(ref, isTailOfChain);
++ return false;
++ }
++ }
++
++ Reference base = expression(ast->base);
++
++ if (hasError())
++ return false;
++ if (base.isSuper()) {
++ Instruction::LoadRuntimeString load;
++ load.stringId = registerString(ast->name.toString());
++ bytecodeGenerator->addInstruction(load);
++ Reference property = Reference::fromAccumulator(this).storeOnStack();
++
++ optionalChainFinalizer(Reference::fromSuperProperty(property), isTailOfChain);
++ return false;
++ }
++
++ auto ref = Reference::fromMember(base, ast->name.toString(), ast->lastSourceLocation(),
++ ast->isOptional, &m_optionalChainsStates->top().jumpsToPatch);
++
++ optionalChainFinalizer(ref, isTailOfChain);
++ return false;
++ }
++
++ void Codegen::endVisit(FieldMemberExpression *ast)
++ {
++ m_seenOptionalChainNodes.remove(ast);
++ }
++
++ bool Codegen::visit(TaggedTemplate *ast)
++ {
++ if (hasError())
++ return false;
++
++ RegisterScope scope(this);
++ auto base = expression(ast->base);
++ if (hasError())
++ return false;
++ return handleTaggedTemplate(std::move(base), ast);
++ }
++
++ bool Codegen::handleTaggedTemplate(Reference base, TaggedTemplate *ast)
++ {
++ if (hasError())
++ return false;
++
++ int functionObject = -1, thisObject = -1;
++ switch (base.type) {
++ case Reference::Member:
++ base = base.asLValue();
++ break;
++ case Reference::Subscript:
++ base.element = loadSubscriptForCall(base).storeOnStack().stackSlot();
++ base.subscriptLoadedForCall = true;
++ break;
++ case Reference::Name:
++ break;
++ case Reference::SuperProperty:
++ thisObject = bytecodeGenerator->newRegister();
++ functionObject = bytecodeGenerator->newRegister();
++ break;
++ default:
++ base = base.storeOnStack();
++ break;
++ }
++
++ createTemplateObject(ast->templateLiteral);
++ int templateObjectTemp = Reference::fromAccumulator(this).storeOnStack().stackSlot();
++ Q_UNUSED(templateObjectTemp);
++ auto calldata = pushTemplateArgs(ast->templateLiteral);
++ if (hasError())
++ return false;
++ ++calldata.argc;
++ Q_ASSERT(calldata.argv == templateObjectTemp + 1);
++ --calldata.argv;
++
++ handleCall(base, calldata, functionObject, thisObject);
++ setExprResult(Reference::fromAccumulator(this));
++ return false;
++ }
++
++ void Codegen::createTemplateObject(TemplateLiteral *t)
++ {
++ TemplateObject obj;
++
++ for (TemplateLiteral *it = t; it; it = it->next) {
++ obj.strings.append(registerString(it->value.toString()));
++ obj.rawStrings.append(registerString(it->rawValue.toString()));
++ }
++
++ int index = _module->templateObjects.size();
++ _module->templateObjects.append(obj);
++
++ Instruction::GetTemplateObject getTemplateObject;
++ getTemplateObject.index = index;
++ bytecodeGenerator->addInstruction(getTemplateObject);
++ }
++
++ bool Codegen::visit(FunctionExpression *ast)
++ {
++ if (hasError())
++ return false;
++
++ TailCallBlocker blockTailCalls(this);
++
++ RegisterScope scope(this);
++
++ int function = defineFunction(ast->name.toString(), ast, ast->formals, ast->body);
++ if (hasError())
++ return false;
++ loadClosure(function);
++ setExprResult(Reference::fromAccumulator(this));
++ return false;
++ }
++
++ Codegen::Reference Codegen::referenceForName(const QString &name, bool isLhs, const SourceLocation &accessLocation)
++ {
++ Context::ResolvedName resolved = _context->resolveName(name, accessLocation);
++ bool throwsReferenceError = false;
++
++ if (resolved.type == Context::ResolvedName::Local || resolved.type == Context::ResolvedName::Stack
++ || resolved.type == Context::ResolvedName::Import) {
++ if (resolved.isArgOrEval && isLhs)
++ // ### add correct source location
++ throwSyntaxError(SourceLocation(), QStringLiteral("Variable name may not be eval or arguments in strict mode"));
++
++ if (resolved.declarationLocation.isValid() && accessLocation.isValid()
++ && resolved.declarationLocation.begin() > accessLocation.end()) {
++ Q_ASSERT(_interface);
++ if (resolved.memberType == Context::FunctionDefinition) {
++ _interface->reportFunctionUsedBeforeDeclaration(
++ name, url().toLocalFile(), resolved.declarationLocation, accessLocation);
++ } else {
++ _interface->reportVarUsedBeforeDeclaration(
++ name, url().toLocalFile(), resolved.declarationLocation, accessLocation);
++ }
++ if (resolved.type == Context::ResolvedName::Stack && resolved.requiresTDZCheck)
++ throwsReferenceError = true;
++ }
++
++ if (resolved.isInjected && accessLocation.isValid()) {
++ qCWarning(lcQmlInjectedParameter).nospace().noquote()
++ << url().toString() << ":" << accessLocation.startLine
++ << ":" << accessLocation.startColumn << " Parameter \"" << name
++ << "\" is not declared."
++ << " Injection of parameters into signal handlers is deprecated."
++ << " Use JavaScript functions with formal parameters instead.";
++ }
++
++ Reference r;
++ switch (resolved.type) {
++ case Context::ResolvedName::Local:
++ r = Reference::fromScopedLocal(this, resolved.index, resolved.scope); break;
++ case Context::ResolvedName::Stack:
++ r = Reference::fromStackSlot(this, resolved.index, true /*isLocal*/); break;
++ case Context::ResolvedName::Import:
++ r = Reference::fromImport(this, resolved.index); break;
++ default: Q_UNREACHABLE();
++ }
++ if (r.isStackSlot() && _volatileMemoryLocations.isVolatile(name))
++ r.isVolatile = true;
++ r.isArgOrEval = resolved.isArgOrEval;
++ r.isReferenceToConst = resolved.isConst;
++ r.requiresTDZCheck = resolved.requiresTDZCheck;
++ r.name = name; // used to show correct name at run-time when TDZ check fails.
++ r.sourceLocation = accessLocation;
++ r.throwsReferenceError = throwsReferenceError;
++ return r;
++ }
++
++ Reference r = Reference::fromName(this, name);
++ r.global = useFastLookups && (resolved.type == Context::ResolvedName::Global || resolved.type == Context::ResolvedName::QmlGlobal);
++ r.qmlGlobal = resolved.type == Context::ResolvedName::QmlGlobal;
++ r.sourceLocation = accessLocation;
++ if (!r.global && !r.qmlGlobal
++ && _context->contextType == ContextType::ScriptImportedByQML
++ && Codegen::isNameGlobal(name)) {
++ // the global object is frozen, so we know that members of it are
++ // pointing to the global object. This is important so that references
++ // to Math etc. do not go through the expensive path in the context wrapper
++ // that tries to see whether we have a matching type
++ r.global = true;
++ }
++ return r;
++ }
++
++ void Codegen::loadClosure(int closureId)
++ {
++ if (closureId >= 0) {
++ Instruction::LoadClosure load;
++ load.value = closureId;
++ bytecodeGenerator->addInstruction(load);
++ } else {
++ Reference::fromConst(this, Encode::undefined()).loadInAccumulator();
++ }
++ }
++
++ bool Codegen::visit(IdentifierExpression *ast)
++ {
++ if (hasError())
++ return false;
++
++ setExprResult(referenceForName(ast->name.toString(), false, ast->firstSourceLocation()));
++ return false;
++ }
++
++ bool Codegen::visit(NestedExpression *ast)
++ {
++ if (hasError())
++ return false;
++
++ accept(ast->expression);
++ return false;
++ }
++
++ void Codegen::handleConstruct(const Reference &base, ArgumentList *arguments)
++ {
++ Reference constructor;
++ if (base.isSuper()) {
++ Instruction::LoadSuperConstructor super;
++ bytecodeGenerator->addInstruction(super);
++ constructor = Reference::fromAccumulator(this).storeOnStack();
++ } else {
++ constructor = base.storeOnStack();
++ }
++
++ auto calldata = pushArgs(arguments);
++ if (hasError())
++ return;
++
++ if (base.isSuper())
++ Reference::fromStackSlot(this, CallData::NewTarget).loadInAccumulator();
++ else
++ constructor.loadInAccumulator();
++
++ if (calldata.hasSpread) {
++ Instruction::ConstructWithSpread create;
++ create.func = constructor.stackSlot();
++ create.argc = calldata.argc;
++ create.argv = calldata.argv;
++ bytecodeGenerator->addInstruction(create);
++ } else {
++ Instruction::Construct create;
++ create.func = constructor.stackSlot();
++ create.argc = calldata.argc;
++ create.argv = calldata.argv;
++ bytecodeGenerator->addInstruction(create);
++ }
++ if (base.isSuper())
++ // set the result up as the thisObject
++ Reference::fromAccumulator(this).storeOnStack(CallData::This);
++
++ setExprResult(Reference::fromAccumulator(this));
++ }
++
++ bool Codegen::visit(NewExpression *ast)
++ {
++ if (hasError())
++ return false;
++
++ RegisterScope scope(this);
++ TailCallBlocker blockTailCalls(this);
++
++ Reference base = expression(ast->expression);
++ if (hasError())
++ return false;
++ if (base.isSuper()) {
++ throwSyntaxError(ast->expression->firstSourceLocation(), QStringLiteral("Cannot use new with super."));
++ return false;
++ }
++
++ handleConstruct(base, nullptr);
++ return false;
++ }
++
++ bool Codegen::visit(NewMemberExpression *ast)
++ {
++ if (hasError())
++ return false;
++
++ RegisterScope scope(this);
++ TailCallBlocker blockTailCalls(this);
++
++ Reference base = expression(ast->base);
++ if (hasError())
++ return false;
++ if (base.isSuper()) {
++ throwSyntaxError(ast->base->firstSourceLocation(), QStringLiteral("Cannot use new with super."));
++ return false;
++ }
++
++ handleConstruct(base, ast->arguments);
++ return false;
++ }
++
++ bool Codegen::visit(NotExpression *ast)
++ {
++ if (hasError())
++ return false;
++
++ TailCallBlocker blockTailCalls(this);
++ auto e = expression(ast->expression);
++ if (hasError())
++ return false;
++ setExprResult(unop(Not, e));
++ return false;
++ }
++
++ bool Codegen::visit(NullExpression *)
++ {
++ if (hasError())
++ return false;
++
++ if (exprAccept(cx))
++ bytecodeGenerator->jump().link(*currentExpr().iffalse());
++ else
++ setExprResult(Reference::fromConst(this, Encode::null()));
++
++ return false;
++ }
++
++ bool Codegen::visit(NumericLiteral *ast)
++ {
++ if (hasError())
++ return false;
++
++ setExprResult(Reference::fromConst(this, QV4::Encode::smallestNumber(ast->value)));
++ return false;
++ }
++
++ bool Codegen::visit(ObjectPattern *ast)
++ {
++ if (hasError())
++ return false;
++
++ TailCallBlocker blockTailCalls(this);
++
++ RegisterScope scope(this);
++
++ QStringList members;
++
++ int argc = 0;
++ int args = 0;
++ auto push = [this, &args, &argc](const Reference &arg) {
++ int temp = bytecodeGenerator->newRegister();
++ if (argc == 0)
++ args = temp;
++ (void) arg.storeOnStack(temp);
++ ++argc;
++ };
++
++ PatternPropertyList *it = ast->properties;
++ for (; it; it = it->next) {
++ PatternProperty *p = it->property;
++ AST::ComputedPropertyName *cname = AST::cast<AST::ComputedPropertyName *>(p->name);
++ if (cname || p->type != PatternProperty::Literal)
++ break;
++ QString name = p->name->asString();
++ uint arrayIndex = stringToArrayIndex(name);
++ if (arrayIndex != UINT_MAX)
++ break;
++ if (members.contains(name))
++ break;
++ members.append(name);
++
++ {
++ RegisterScope innerScope(this);
++ Reference value = expression(p->initializer, name);
++ if (hasError())
++ return false;
++ value.loadInAccumulator();
++ }
++ push(Reference::fromAccumulator(this));
++ }
++
++ int classId = jsUnitGenerator->registerJSClass(members);
++
++ // handle complex property setters
++ for (; it; it = it->next) {
++ PatternProperty *p = it->property;
++ AST::ComputedPropertyName *cname = AST::cast<AST::ComputedPropertyName *>(p->name);
++ ObjectLiteralArgument argType = ObjectLiteralArgument::Value;
++ if (p->type == PatternProperty::Method)
++ argType = ObjectLiteralArgument::Method;
++ else if (p->type == PatternProperty::Getter)
++ argType = ObjectLiteralArgument::Getter;
++ else if (p->type == PatternProperty::Setter)
++ argType = ObjectLiteralArgument::Setter;
++
++ Reference::fromConst(this, Encode(int(argType))).loadInAccumulator();
++ push(Reference::fromAccumulator(this));
++
++ if (cname) {
++ RegisterScope innerScope(this);
++ Reference name = expression(cname->expression);
++ if (hasError())
++ return false;
++ name.loadInAccumulator();
++ } else {
++ QString name = p->name->asString();
++ #if 0
++ uint arrayIndex = QV4::String::toArrayIndex(name);
++ if (arrayIndex != UINT_MAX) {
++ Reference::fromConst(this, Encode(arrayIndex)).loadInAccumulator();
++ } else
++ #endif
++ {
++ Instruction::LoadRuntimeString instr;
++ instr.stringId = registerString(name);
++ bytecodeGenerator->addInstruction(instr);
++ }
++ }
++ push(Reference::fromAccumulator(this));
++ {
++ RegisterScope innerScope(this);
++ if (p->type != PatternProperty::Literal) {
++ // need to get the closure id for the method
++ FunctionExpression *f = p->initializer->asFunctionDefinition();
++ Q_ASSERT(f);
++ int function = defineFunction(f->name.toString(), f, f->formals, f->body);
++ if (hasError())
++ return false;
++ Reference::fromConst(this, Encode(function)).loadInAccumulator();
++ } else {
++ Reference value = expression(p->initializer);
++ if (hasError())
++ return false;
++ value.loadInAccumulator();
++ }
++ }
++ push(Reference::fromAccumulator(this));
++ }
++
++ Instruction::DefineObjectLiteral call;
++ call.internalClassId = classId;
++ call.argc = argc;
++ call.args = Moth::StackSlot::createRegister(args);
++ bytecodeGenerator->addInstruction(call);
++ setExprResult(Reference::fromAccumulator(this));
++ return false;
++ }
++
++ bool Codegen::visit(PostDecrementExpression *ast)
++ {
++ if (hasError())
++ return false;
++
++ Reference expr = expression(ast->base);
++ if (hasError())
++ return false;
++ if (!expr.isLValue()) {
++ throwReferenceError(ast->base->lastSourceLocation(), QStringLiteral("Invalid left-hand side expression in postfix operation"));
++ return false;
++ }
++ if (throwSyntaxErrorOnEvalOrArgumentsInStrictMode(expr, ast->decrementToken))
++ return false;
++
++ setExprResult(unop(PostDecrement, expr));
++
++ return false;
++ }
++
++ bool Codegen::visit(PostIncrementExpression *ast)
++ {
++ if (hasError())
++ return false;
++
++ Reference expr = expression(ast->base);
++ if (hasError())
++ return false;
++ if (!expr.isLValue()) {
++ throwReferenceError(ast->base->lastSourceLocation(), QStringLiteral("Invalid left-hand side expression in postfix operation"));
++ return false;
++ }
++ if (throwSyntaxErrorOnEvalOrArgumentsInStrictMode(expr, ast->incrementToken))
++ return false;
++
++ setExprResult(unop(PostIncrement, expr));
++ return false;
++ }
++
++ bool Codegen::visit(PreDecrementExpression *ast)
++ { if (hasError())
++ return false;
++
++ Reference expr = expression(ast->expression);
++ if (hasError())
++ return false;
++ if (!expr.isLValue()) {
++ throwReferenceError(ast->expression->lastSourceLocation(), QStringLiteral("Prefix -- operator applied to value that is not a reference."));
++ return false;
++ }
++
++ if (throwSyntaxErrorOnEvalOrArgumentsInStrictMode(expr, ast->decrementToken))
++ return false;
++ setExprResult(unop(PreDecrement, expr));
++ return false;
++ }
++
++ bool Codegen::visit(PreIncrementExpression *ast)
++ {
++ if (hasError())
++ return false;
++
++ Reference expr = expression(ast->expression);
++ if (hasError())
++ return false;
++ if (!expr.isLValue()) {
++ throwReferenceError(ast->expression->lastSourceLocation(), QStringLiteral("Prefix ++ operator applied to value that is not a reference."));
++ return false;
++ }
++
++ if (throwSyntaxErrorOnEvalOrArgumentsInStrictMode(expr, ast->incrementToken))
++ return false;
++ setExprResult(unop(PreIncrement, expr));
++ return false;
++ }
++
++ bool Codegen::visit(RegExpLiteral *ast)
++ {
++ if (hasError())
++ return false;
++
++ auto r = Reference::fromStackSlot(this);
++ r.isReadonly = true;
++ setExprResult(r);
++
++ Instruction::MoveRegExp instr;
++ instr.regExpId = jsUnitGenerator->registerRegExp(ast);
++ instr.destReg = r.stackSlot();
++ bytecodeGenerator->addInstruction(instr);
++ return false;
++ }
++
++ bool Codegen::visit(StringLiteral *ast)
++ {
++ if (hasError())
++ return false;
++
++ auto r = Reference::fromAccumulator(this);
++ r.isReadonly = true;
++ setExprResult(r);
++
++ Instruction::LoadRuntimeString instr;
++ instr.stringId = registerString(ast->value.toString());
++ bytecodeGenerator->addInstruction(instr);
++ return false;
++ }
++
++ bool Codegen::visit(TemplateLiteral *ast)
++ {
++ if (hasError())
++ return false;
++
++ TailCallBlocker blockTailCalls(this);
++
++ Instruction::LoadRuntimeString instr;
++ instr.stringId = registerString(ast->value.toString());
++ bytecodeGenerator->addInstruction(instr);
++
++ if (ast->expression) {
++ RegisterScope scope(this);
++ int temp = bytecodeGenerator->newRegister();
++ Instruction::StoreReg store;
++ store.reg = temp;
++ bytecodeGenerator->addInstruction(store);
++
++ Reference expr = expression(ast->expression);
++ if (hasError())
++ return false;
++
++ if (ast->next) {
++ int temp2 = bytecodeGenerator->newRegister();
++ expr.storeOnStack(temp2);
++ visit(ast->next);
++
++ Instruction::Add instr;
++ instr.lhs = temp2;
++ bytecodeGenerator->addInstruction(instr);
++ } else {
++ expr.loadInAccumulator();
++ }
++
++ Instruction::Add instr;
++ instr.lhs = temp;
++ bytecodeGenerator->addInstruction(instr);
++ }
++
++ auto r = Reference::fromAccumulator(this);
++ r.isReadonly = true;
++
++ setExprResult(r);
++ return false;
++
++ }
++
++ bool Codegen::visit(ThisExpression *)
++ {
++ if (hasError())
++ return false;
++
++ for (Context *parentContext = _context; parentContext; parentContext = parentContext->parent) {
++ if (parentContext->isArrowFunction) {
++ Reference r = referenceForName(QStringLiteral("this"), false);
++ r.isReadonly = true;
++ setExprResult(r);
++ return false;
++ }
++ if (parentContext->contextType != ContextType::Block)
++ break;
++ }
++
++ setExprResult(Reference::fromThis(this));
++ return false;
++ }
++
++ bool Codegen::visit(TildeExpression *ast)
++ {
++ if (hasError())
++ return false;
++
++ TailCallBlocker blockTailCalls(this);
++ auto e = expression(ast->expression);
++ if (hasError())
++ return false;
++
++ setExprResult(unop(Compl, e));
++ return false;
++ }
++
++ bool Codegen::visit(TrueLiteral *)
++ {
++ if (hasError())
++ return false;
++
++ setExprResult(Reference::fromConst(this, QV4::Encode(true)));
++ return false;
++ }
++
++ bool Codegen::visit(TypeOfExpression *ast)
++ {
++ if (hasError())
++ return false;
++
++ RegisterScope scope(this);
++ TailCallBlocker blockTailCalls(this);
++
++ Reference expr = expression(ast->expression);
++ if (hasError())
++ return false;
++
++ if (expr.type == Reference::Name) {
++ // special handling as typeof doesn't throw here
++ Instruction::TypeofName instr;
++ instr.name = expr.nameAsIndex();
++ bytecodeGenerator->addInstruction(instr);
++ } else {
++ expr.loadInAccumulator();
++ Instruction::TypeofValue instr;
++ bytecodeGenerator->addInstruction(instr);
++ }
++ setExprResult(Reference::fromAccumulator(this));
++
++ return false;
++ }
++
++ bool Codegen::visit(UnaryMinusExpression *ast)
++ {
++ if (hasError())
++ return false;
++
++ TailCallBlocker blockTailCalls(this);
++ auto e = expression(ast->expression);
++ if (hasError())
++ return false;
++
++ setExprResult(unop(UMinus, e));
++ return false;
++ }
++
++ bool Codegen::visit(UnaryPlusExpression *ast)
++ {
++ if (hasError())
++ return false;
++
++ TailCallBlocker blockTailCalls(this);
++ auto e = expression(ast->expression);
++ if (hasError())
++ return false;
++
++ setExprResult(unop(UPlus, e));
++ return false;
++ }
++
++ bool Codegen::visit(VoidExpression *ast)
++ {
++ if (hasError())
++ return false;
++
++ RegisterScope scope(this);
++ TailCallBlocker blockTailCalls(this);
++
++ statement(ast->expression);
++ setExprResult(Reference::fromConst(this, Encode::undefined()));
++ return false;
++ }
++
++ bool Codegen::visit(FunctionDeclaration * ast)
++ {
++ if (hasError())
++ return false;
++
++ // no need to block tail calls: the function body isn't visited here.
++ RegisterScope scope(this);
++
++ if (_functionContext->contextType == ContextType::Binding)
++ referenceForName(ast->name.toString(), true).loadInAccumulator();
++ exprAccept(nx);
++ return false;
++ }
++
++ bool Codegen::visit(YieldExpression *ast)
++ {
++ if (inFormalParameterList) {
++ throwSyntaxError(ast->firstSourceLocation(), QLatin1String("yield is not allowed inside parameter lists"));
++ return false;
++ }
++
++ auto innerMostCurentFunctionContext = _context;
++ while (innerMostCurentFunctionContext && innerMostCurentFunctionContext->contextType != ContextType::Function)
++ innerMostCurentFunctionContext = innerMostCurentFunctionContext->parent;
++
++ Q_ASSERT(innerMostCurentFunctionContext); // yield outside function would have been rejected by parser
++
++ if (!innerMostCurentFunctionContext->isGenerator) {
++ throwSyntaxError(ast->firstSourceLocation(), u"Yield is only valid in generator functions"_s);
++ return false;
++ }
++
++ RegisterScope scope(this);
++ TailCallBlocker blockTailCalls(this);
++ Reference expr = ast->expression ? expression(ast->expression) : Reference::fromConst(this, Encode::undefined());
++ if (hasError())
++ return false;
++
++ Reference acc = Reference::fromAccumulator(this);
++
++ if (ast->isYieldStar) {
++ Reference iterator = Reference::fromStackSlot(this);
++ Reference lhsValue = Reference::fromConst(this, Encode::undefined()).storeOnStack();
++
++ expr.loadInAccumulator();
++ Instruction::GetIterator getIterator;
++ getIterator.iterator = static_cast<int>(AST::ForEachType::Of);
++ bytecodeGenerator->addInstruction(getIterator);
++ iterator.storeConsumeAccumulator();
++ Instruction::LoadUndefined load;
++ bytecodeGenerator->addInstruction(load);
++
++ BytecodeGenerator::Label in = bytecodeGenerator->newLabel();
++ bytecodeGenerator->jump().link(in);
++
++ BytecodeGenerator::Label loop = bytecodeGenerator->label();
++
++ lhsValue.loadInAccumulator();
++ Instruction::YieldStar yield;
++ bytecodeGenerator->addInstruction(yield);
++
++ in.link();
++
++ Instruction::IteratorNextForYieldStar next;
++ next.object = lhsValue.stackSlot();
++ next.iterator = iterator.stackSlot();
++ BytecodeGenerator::Jump done = bytecodeGenerator->addJumpInstruction(next);
++ bytecodeGenerator->jumpNotUndefined().link(loop);
++
++ lhsValue.loadInAccumulator();
++ emitReturn(acc);
++
++
++ done.link();
++ bytecodeGenerator->checkException();
++
++ lhsValue.loadInAccumulator();
++ setExprResult(acc);
++ return false;
++ }
++
++ expr.loadInAccumulator();
++ Instruction::Yield yield;
++ bytecodeGenerator->addInstruction(yield);
++ Instruction::Resume resume;
++ BytecodeGenerator::Jump jump = bytecodeGenerator->addJumpInstruction(resume);
++ emitReturn(acc);
++ jump.link();
++ setExprResult(acc);
++ return false;
++ }
++
++ static bool endsWithReturn(Module *module, Node *node)
++ {
++ if (!node)
++ return false;
++ if (AST::cast<ReturnStatement *>(node))
++ return true;
++ if (AST::cast<ThrowStatement *>(node))
++ return true;
++ if (Program *p = AST::cast<Program *>(node))
++ return endsWithReturn(module, p->statements);
++ if (StatementList *sl = AST::cast<StatementList *>(node)) {
++ while (sl->next)
++ sl = sl->next;
++ return endsWithReturn(module, sl->statement);
++ }
++ if (Block *b = AST::cast<Block *>(node)) {
++ Context *blockContext = module->contextMap.value(node);
++ if (blockContext->requiresExecutionContext)
++ // we need to emit a return statement here, because of the
++ // unwind handler
++ return false;
++ return endsWithReturn(module, b->statements);
++ }
++ if (IfStatement *is = AST::cast<IfStatement *>(node))
++ return is->ko && endsWithReturn(module, is->ok) && endsWithReturn(module, is->ko);
++ return false;
++ }
++
++ int Codegen::defineFunction(const QString &name, AST::Node *ast, AST::FormalParameterList *formals,
++ AST::StatementList *body)
++ {
++ enterContext(ast);
++
++ if (_context->functionIndex >= 0)
++ // already defined
++ return leaveContext();
++
++ _context->name = name.isEmpty() ? currentExpr().result().name : name;
++ _module->functions.append(_context);
++ _context->functionIndex = _module->functions.size() - 1;
++
++ Context *savedFunctionContext = _functionContext;
++ _functionContext = _context;
++ ControlFlow *savedControlFlow = controlFlow;
++ controlFlow = nullptr;
++
++ if (_context->contextType == ContextType::Global || _context->contextType == ContextType::ScriptImportedByQML) {
++ _module->blocks.append(_context);
++ _context->blockIndex = _module->blocks.size() - 1;
++ }
++ if (_module->debugMode) // allow the debugger to see overwritten arguments
++ _context->argumentsCanEscape = true;
++
++ // When a user writes the following QML signal binding:
++ // onSignal: function() { doSomethingUsefull }
++ // we will generate a binding function that just returns the closure. However, that's not useful
++ // at all, because if the onSignal is a signal handler, the user is actually making it explicit
++ // that the binding is a function, so we should execute that. However, we don't know that during
++ // AOT compilation, so mark the surrounding function as only-returning-a-closure.
++ _context->returnsClosure = body && cast<ExpressionStatement *>(body->statement)
++ && cast<FunctionExpression *>(cast<ExpressionStatement *>(body->statement)->expression);
++
++ BytecodeGenerator bytecode(_context->line, _module->debugMode, storeSourceLocations);
++ BytecodeGenerator *savedBytecodeGenerator;
++ savedBytecodeGenerator = bytecodeGenerator;
++ bytecodeGenerator = &bytecode;
++ bytecodeGenerator->setLocation(ast->firstSourceLocation());
++ BytecodeGenerator::Label *savedReturnLabel = _returnLabel;
++ _returnLabel = nullptr;
++
++ OptionalChainStates optionalChainStates;
++ OptionalChainStates *savedOptionalChainStates = m_optionalChainsStates;
++ m_optionalChainsStates = &optionalChainStates;
++
++ bool savedFunctionEndsWithReturn = functionEndsWithReturn;
++ functionEndsWithReturn = endsWithReturn(_module, body);
++
++ // reserve the js stack frame (Context & js Function & accumulator)
++ bytecodeGenerator->newRegisterArray(
++ sizeof(CallData) / sizeof(StaticValue) - 1 + _context->arguments.size());
++
++ bool _inFormalParameterList = false;
++ qSwap(_inFormalParameterList, inFormalParameterList);
++
++ int returnAddress = -1;
++ bool _requiresReturnValue = _context->requiresImplicitReturnValue();
++ qSwap(requiresReturnValue, _requiresReturnValue);
++ returnAddress = bytecodeGenerator->newRegister();
++ qSwap(_returnAddress, returnAddress);
++
++ // register the lexical scope for global code
++ if (!_context->parent && _context->requiresExecutionContext) {
++ _module->blocks.append(_context);
++ _context->blockIndex = _module->blocks.size() - 1;
++ }
++
++ TailCallBlocker maybeBlockTailCalls(this, _context->canHaveTailCalls());
++
++ RegisterScope registerScope(this);
++ _context->emitBlockHeader(this);
++
++ {
++ QScopedValueRollback<bool> inFormals(inFormalParameterList, true);
++ TailCallBlocker blockTailCalls(this); // we're not in the FunctionBody or ConciseBody yet
++
++ int argc = 0;
++ while (formals) {
++ PatternElement *e = formals->element;
++ if (!e) {
++ if (!formals->next)
++ // trailing comma
++ break;
++ Q_UNREACHABLE();
++ }
++
++ if (e->type == PatternElement::RestElement) {
++ Q_ASSERT(!formals->next);
++ Reference arg = referenceForName(e->bindingIdentifier.toString(), true);
++ Instruction::CreateRestParameter rest;
++ rest.argIndex = argc;
++ bytecodeGenerator->addInstruction(rest);
++ arg.storeConsumeAccumulator();
++ } else {
++ // Copy the value of TDZ-promoted parameters into distinct slots to enable TDZ check
++ const auto memberIt = _context->members.constFind(e->bindingIdentifier.toString());
++ const bool promoted = memberIt != _context->members.constEnd()
++ && memberIt->isFormalParameterTDZPromotion;
++ if (e->bindingTarget || e->initializer || promoted) {
++ Reference arg = referenceForFormalParameter(argc);
++ initializeAndDestructureBindingElement(e, arg, /*isDefinition=*/true);
++ if (hasError())
++ break;
++ }
++ }
++ formals = formals->next;
++ ++argc;
++ }
++ }
++
++ if (_context->isGenerator) {
++ Instruction::Yield yield;
++ bytecodeGenerator->addInstruction(yield);
++ }
++
++ statementList(body);
++
++ if (hasError()) {
++ bytecodeGenerator->setError(true);
++ } else {
++ bytecodeGenerator->setLocation(ast->lastSourceLocation());
++ _context->emitBlockFooter(this);
++
++ if (_returnLabel || !functionEndsWithReturn) {
++ if (_returnLabel)
++ _returnLabel->link();
++
++ if (_returnLabel || requiresReturnValue) {
++ Instruction::LoadReg load;
++ load.reg = Moth::StackSlot::createRegister(_returnAddress);
++ bytecodeGenerator->addInstruction(load);
++ } else {
++ Reference::fromConst(this, Encode::undefined()).loadInAccumulator();
++ }
++
++ bytecodeGenerator->addInstruction(Instruction::Ret());
++ }
++
++ Q_ASSERT(_context == _functionContext);
++ bytecodeGenerator->finalize(_context);
++ _context->registerCountInFunction = bytecodeGenerator->registerCount();
++ static const bool showCode = qEnvironmentVariableIsSet("QV4_SHOW_BYTECODE");
++ if (showCode) {
++ qDebug() << "=== Bytecode for" << _context->name << "strict mode" << _context->isStrict
++ << "register count" << _context->registerCountInFunction << "implicit return" << requiresReturnValue;
++ qDebug().noquote() << QV4::Moth::dumpBytecode(
++ _context->code, _context->locals.size(), _context->arguments.size(),
++ _context->line, _context->lineAndStatementNumberMapping);
++ qDebug();
++ }
++ }
++
++ qSwap(_returnAddress, returnAddress);
++ qSwap(requiresReturnValue, _requiresReturnValue);
++ qSwap(_inFormalParameterList, inFormalParameterList);
++ m_optionalChainsStates = savedOptionalChainStates;
++ bytecodeGenerator = savedBytecodeGenerator;
++ delete _returnLabel;
++ _returnLabel = savedReturnLabel;
++ controlFlow = savedControlFlow;
++ functionEndsWithReturn = savedFunctionEndsWithReturn;
++ _functionContext = savedFunctionContext;
++
++ return leaveContext();
++ }
++
++ bool Codegen::visit(Block *ast)
++ {
++ if (hasError())
++ return false;
++
++ RegisterScope scope(this);
++
++ ControlFlowBlock controlFlow(this, ast);
++ statementList(ast->statements);
++ return false;
++ }
++
++ bool Codegen::visit(BreakStatement *ast)
++ {
++ if (hasError())
++ return false;
++
++ // no need to block tail calls here: children aren't visited
++ if (!controlFlow) {
++ throwSyntaxError(ast->lastSourceLocation(), QStringLiteral("Break outside of loop"));
++ return false;
++ }
++
++ ControlFlow::UnwindTarget target = controlFlow->unwindTarget(ControlFlow::Break, ast->label.toString());
++ if (!target.linkLabel.isValid()) {
++ if (ast->label.isEmpty())
++ throwSyntaxError(ast->lastSourceLocation(), QStringLiteral("Break outside of loop"));
++ else
++ throwSyntaxError(ast->lastSourceLocation(), QStringLiteral("Undefined label '%1'").arg(ast->label.toString()));
++ return false;
++ }
++
++ bytecodeGenerator->unwindToLabel(target.unwindLevel, target.linkLabel);
++
++ return false;
++ }
++
++ bool Codegen::visit(ContinueStatement *ast)
++ {
++ if (hasError())
++ return false;
++
++ // no need to block tail calls here: children aren't visited
++ RegisterScope scope(this);
++
++ if (!controlFlow) {
++ throwSyntaxError(ast->lastSourceLocation(), QStringLiteral("Continue outside of loop"));
++ return false;
++ }
++
++ ControlFlow::UnwindTarget target = controlFlow->unwindTarget(ControlFlow::Continue, ast->label.toString());
++ if (!target.linkLabel.isValid()) {
++ if (ast->label.isEmpty())
++ throwSyntaxError(ast->lastSourceLocation(), QStringLiteral("Undefined label '%1'").arg(ast->label.toString()));
++ else
++ throwSyntaxError(ast->lastSourceLocation(), QStringLiteral("continue outside of loop"));
++ return false;
++ }
++
++ bytecodeGenerator->unwindToLabel(target.unwindLevel, target.linkLabel);
++
++ return false;
++ }
++
++ bool Codegen::visit(DebuggerStatement *)
++ {
++ Q_UNIMPLEMENTED();
++ return false;
++ }
++
++ bool Codegen::visit(DoWhileStatement *ast)
++ {
++ if (hasError())
++ return false;
++
++ RegisterScope scope(this);
++
++ BytecodeGenerator::Label body = bytecodeGenerator->newLabel();
++ BytecodeGenerator::Label cond = bytecodeGenerator->newLabel();
++ BytecodeGenerator::Label end = bytecodeGenerator->newLabel();
++
++ ControlFlowLoop flow(this, &end, &cond);
++
++ // special case that is not a loop:
++ // do {...} while (false)
++ if (!AST::cast<FalseLiteral *>(ast->expression))
++ bytecodeGenerator->addLoopStart(body);
++
++ body.link();
++ statement(ast->statement);
++ setJumpOutLocation(bytecodeGenerator, ast->statement, ast->semicolonToken);
++
++ cond.link();
++ if (AST::cast<TrueLiteral *>(ast->expression)) {
++ // do {} while (true) -> just jump back to the loop body, no need to generate a condition
++ bytecodeGenerator->checkException();
++ bytecodeGenerator->jump().link(body);
++ } else if (AST::cast<FalseLiteral *>(ast->expression)) {
++ // do {} while (false) -> fall through, no need to generate a condition
++ } else {
++ TailCallBlocker blockTailCalls(this);
++ bytecodeGenerator->checkException();
++ condition(ast->expression, &body, &end, false);
++ }
++
++ end.link();
++
++ return false;
++ }
++
++ bool Codegen::visit(EmptyStatement *)
++ {
++ return false;
++ }
++
++ bool Codegen::visit(ExpressionStatement *ast)
++ {
++ if (hasError())
++ return false;
++
++ RegisterScope scope(this);
++ TailCallBlocker blockTailCalls(this);
++
++ if (requiresReturnValue) {
++ Reference e = expression(ast->expression);
++ if (hasError())
++ return false;
++ (void) e.storeOnStack(_returnAddress);
++ } else {
++ statement(ast->expression);
++ }
++ return false;
++ }
++
++ bool Codegen::visit(ForEachStatement *ast)
++ {
++ if (hasError())
++ return false;
++
++ RegisterScope scope(this);
++ TailCallBlocker blockTailCalls(this);
++
++ Reference iterator = Reference::fromStackSlot(this);
++ Reference lhsValue = Reference::fromStackSlot(this);
++
++ // There should be a temporal block, so that variables declared in lhs shadow outside vars.
++ // This block should define a temporal dead zone for those variables.
++ {
++ RegisterScope innerScope(this);
++ ControlFlowBlock controlFlow(this, ast);
++ Reference expr = expression(ast->expression);
++ if (hasError())
++ return false;
++
++ expr.loadInAccumulator();
++ Instruction::GetIterator iteratorObjInstr;
++ iteratorObjInstr.iterator = static_cast<int>(ast->type);
++ bytecodeGenerator->addInstruction(iteratorObjInstr);
++ iterator.storeConsumeAccumulator();
++ }
++
++ BytecodeGenerator::Label in = bytecodeGenerator->newLabel();
++ BytecodeGenerator::Label end = bytecodeGenerator->newLabel();
++ BytecodeGenerator::Label done;
++
++ {
++ std::function<void()> cleanup;
++ if (ast->type == ForEachType::Of) {
++ done = bytecodeGenerator->newLabel();
++ cleanup = [iterator, this, done]() {
++ iterator.loadInAccumulator();
++ Instruction::IteratorClose close;
++ bytecodeGenerator->addInstruction(close);
++ done.link();
++ };
++ } else {
++ done = end;
++ }
++ ControlFlowLoop flow(this, &end, &in, std::move(cleanup));
++ bytecodeGenerator->addLoopStart(in);
++ in.link();
++ iterator.loadInAccumulator();
++ Instruction::IteratorNext next;
++ next.value = lhsValue.stackSlot();
++ bytecodeGenerator->addJumpInstruction(next).link(done);
++
++ // each iteration gets it's own context, as per spec
++ {
++ RegisterScope innerScope(this);
++ ControlFlowBlock controlFlow(this, ast);
++
++ if (ExpressionNode *e = ast->lhs->expressionCast()) {
++ if (AST::Pattern *p = e->patternCast()) {
++ RegisterScope scope(this);
++ destructurePattern(p, lhsValue);
++ } else {
++ Reference lhs = expression(e);
++ if (hasError())
++ goto error;
++ if (!lhs.isLValue()) {
++ throwReferenceError(e->firstSourceLocation(), QStringLiteral("Invalid left-hand side expression for 'in' expression"));
++ goto error;
++ }
++ lhs = lhs.asLValue();
++ lhsValue.loadInAccumulator();
++ lhs.storeConsumeAccumulator();
++ }
++ } else if (PatternElement *p = AST::cast<PatternElement *>(ast->lhs)) {
++ initializeAndDestructureBindingElement(p, lhsValue, /*isDefinition =*/ true);
++ if (hasError())
++ goto error;
++ } else {
++ Q_UNREACHABLE();
++ }
++
++ blockTailCalls.unblock();
++ statement(ast->statement);
++ setJumpOutLocation(bytecodeGenerator, ast->statement, ast->forToken);
++ }
++
++ bytecodeGenerator->checkException();
++ bytecodeGenerator->jump().link(in);
++
++ error:
++ end.link();
++
++ // all execution paths need to end up here (normal loop exit, break, and exceptions) in
++ // order to reset the unwind handler, and to close the iterator in calse of an for-of loop.
++ }
++
++ return false;
++ }
++
++ bool Codegen::visit(ForStatement *ast)
++ {
++ if (hasError())
++ return false;
++
++ RegisterScope scope(this);
++ TailCallBlocker blockTailCalls(this);
++
++ ControlFlowBlock controlFlow(this, ast);
++
++ if (ast->initialiser)
++ statement(ast->initialiser);
++ else if (ast->declarations)
++ variableDeclarationList(ast->declarations);
++
++ BytecodeGenerator::Label cond = bytecodeGenerator->label();
++ BytecodeGenerator::Label body = bytecodeGenerator->newLabel();
++ BytecodeGenerator::Label step = bytecodeGenerator->newLabel();
++ BytecodeGenerator::Label end = bytecodeGenerator->newLabel();
++
++ ControlFlowLoop flow(this, &end, &step);
++ bytecodeGenerator->addLoopStart(cond);
++ condition(ast->condition, &body, &end, true);
++
++ body.link();
++ blockTailCalls.unblock();
++ statement(ast->statement);
++ blockTailCalls.reblock();
++ setJumpOutLocation(bytecodeGenerator, ast->statement, ast->forToken);
++
++ step.link();
++ if (_context->requiresExecutionContext) {
++ Instruction::CloneBlockContext clone;
++ bytecodeGenerator->addInstruction(clone);
++ }
++ statement(ast->expression);
++ bytecodeGenerator->checkException();
++ bytecodeGenerator->jump().link(cond);
++
++ end.link();
++
++ return false;
++ }
++
++ bool Codegen::visit(IfStatement *ast)
++ {
++ if (hasError())
++ return false;
++
++ RegisterScope scope(this);
++ TailCallBlocker blockTailCalls(this);
++
++ BytecodeGenerator::Label trueLabel = bytecodeGenerator->newLabel();
++ BytecodeGenerator::Label falseLabel = bytecodeGenerator->newLabel();
++ condition(ast->expression, &trueLabel, &falseLabel, true);
++ blockTailCalls.unblock();
++
++ trueLabel.link();
++ statement(ast->ok);
++ if (ast->ko) {
++ if (endsWithReturn(_module, ast)) {
++ falseLabel.link();
++ statement(ast->ko);
++ } else {
++ BytecodeGenerator::Jump jump_endif = bytecodeGenerator->jump();
++ falseLabel.link();
++ statement(ast->ko);
++ jump_endif.link();
++ }
++ } else {
++ falseLabel.link();
++ }
++
++ return false;
++ }
++
++ bool Codegen::visit(LabelledStatement *ast)
++ {
++ if (hasError())
++ return false;
++
++ RegisterScope scope(this);
++
++ // check that no outer loop contains the label
++ ControlFlow *l = controlFlow;
++ while (l) {
++ if (l->label() == ast->label) {
++ QString error = QString(QStringLiteral("Label '%1' has already been declared")).arg(ast->label.toString());
++ throwSyntaxError(ast->firstSourceLocation(), error);
++ return false;
++ }
++ l = l->parent;
++ }
++ _labelledStatement = ast;
++
++ if (AST::cast<AST::SwitchStatement *>(ast->statement) ||
++ AST::cast<AST::WhileStatement *>(ast->statement) ||
++ AST::cast<AST::DoWhileStatement *>(ast->statement) ||
++ AST::cast<AST::ForStatement *>(ast->statement) ||
++ AST::cast<AST::ForEachStatement *>(ast->statement)) {
++ statement(ast->statement); // labelledStatement will be associated with the ast->statement's loop.
++ } else {
++ BytecodeGenerator::Label breakLabel = bytecodeGenerator->newLabel();
++ ControlFlowLoop flow(this, &breakLabel);
++ statement(ast->statement);
++ breakLabel.link();
++ }
++
++ return false;
++ }
++
++ void Codegen::emitReturn(const Reference &expr)
++ {
++ ControlFlow::UnwindTarget target = controlFlow ? controlFlow->unwindTarget(ControlFlow::Return) : ControlFlow::UnwindTarget();
++ if (target.linkLabel.isValid() && target.unwindLevel) {
++ Q_ASSERT(_returnAddress >= 0);
++ (void) expr.storeOnStack(_returnAddress);
++ bytecodeGenerator->unwindToLabel(target.unwindLevel, target.linkLabel);
++ } else {
++ expr.loadInAccumulator();
++ bytecodeGenerator->addInstruction(Instruction::Ret());
++ }
++ }
++
++ bool Codegen::visit(ReturnStatement *ast)
++ {
++ if (hasError())
++ return false;
++
++ if (_functionContext->contextType != ContextType::Function && _functionContext->contextType != ContextType::Binding) {
++ throwSyntaxError(ast->returnToken, QStringLiteral("Return statement outside of function"));
++ return false;
++ }
++ Reference expr;
++ if (ast->expression) {
++ expr = expression(ast->expression);
++ if (hasError())
++ return false;
++ } else {
++ expr = Reference::fromConst(this, Encode::undefined());
++ }
++
++ emitReturn(expr);
++
++ return false;
++ }
++
++ bool Codegen::visit(SwitchStatement *ast)
++ {
++ if (hasError())
++ return false;
++
++ if (requiresReturnValue)
++ Reference::fromConst(this, Encode::undefined()).storeOnStack(_returnAddress);
++
++ RegisterScope scope(this);
++ TailCallBlocker blockTailCalls(this);
++
++ if (ast->block) {
++ BytecodeGenerator::Label switchEnd = bytecodeGenerator->newLabel();
++
++ Reference lhs = expression(ast->expression);
++ if (hasError())
++ return false;
++ lhs = lhs.storeOnStack();
++
++ ControlFlowBlock controlFlow(this, ast->block);
++
++ // set up labels for all clauses
++ QHash<Node *, BytecodeGenerator::Label> blockMap;
++ for (CaseClauses *it = ast->block->clauses; it; it = it->next)
++ blockMap[it->clause] = bytecodeGenerator->newLabel();
++ if (ast->block->defaultClause)
++ blockMap[ast->block->defaultClause] = bytecodeGenerator->newLabel();
++ for (CaseClauses *it = ast->block->moreClauses; it; it = it->next)
++ blockMap[it->clause] = bytecodeGenerator->newLabel();
++
++ // do the switch conditions
++ for (CaseClauses *it = ast->block->clauses; it; it = it->next) {
++ CaseClause *clause = it->clause;
++ Reference rhs = expression(clause->expression);
++ if (hasError())
++ return false;
++ rhs.loadInAccumulator();
++ bytecodeGenerator->jumpStrictEqual(lhs.stackSlot(), blockMap.value(clause));
++ }
++
++ for (CaseClauses *it = ast->block->moreClauses; it; it = it->next) {
++ CaseClause *clause = it->clause;
++ Reference rhs = expression(clause->expression);
++ if (hasError())
++ return false;
++ rhs.loadInAccumulator();
++ bytecodeGenerator->jumpStrictEqual(lhs.stackSlot(), blockMap.value(clause));
++ }
++
++ if (DefaultClause *defaultClause = ast->block->defaultClause)
++ bytecodeGenerator->jump().link(blockMap.value(defaultClause));
++ else
++ bytecodeGenerator->jump().link(switchEnd);
++
++ ControlFlowLoop flow(this, &switchEnd);
++
++ insideSwitch = true;
++ blockTailCalls.unblock();
++ for (CaseClauses *it = ast->block->clauses; it; it = it->next) {
++ CaseClause *clause = it->clause;
++ blockMap[clause].link();
++
++ statementList(clause->statements);
++ }
++
++ if (ast->block->defaultClause) {
++ DefaultClause *clause = ast->block->defaultClause;
++ blockMap[clause].link();
++
++ statementList(clause->statements);
++ }
++
++ for (CaseClauses *it = ast->block->moreClauses; it; it = it->next) {
++ CaseClause *clause = it->clause;
++ blockMap[clause].link();
++
++ statementList(clause->statements);
++ }
++ insideSwitch = false;
++
++ switchEnd.link();
++
++ }
++
++ return false;
++ }
++
++ bool Codegen::visit(ThrowStatement *ast)
++ {
++ if (hasError())
++ return false;
++
++ RegisterScope scope(this);
++ TailCallBlocker blockTailCalls(this);
++
++ Reference expr = expression(ast->expression);
++ if (hasError())
++ return false;
++
++ expr.loadInAccumulator();
++ Instruction::ThrowException instr;
++ bytecodeGenerator->addInstruction(instr);
++ return false;
++ }
++
++ void Codegen::handleTryCatch(TryStatement *ast)
++ {
++ Q_ASSERT(ast);
++ RegisterScope scope(this);
++ {
++ ControlFlowCatch catchFlow(this, ast->catchExpression);
++ RegisterScope scope(this);
++ TailCallBlocker blockTailCalls(this); // IMPORTANT: destruction will unblock tail calls before catch is generated
++ statement(ast->statement);
++ }
++ }
++
++ void Codegen::handleTryFinally(TryStatement *ast)
++ {
++ RegisterScope scope(this);
++ const bool hasCatchBlock = ast->catchExpression;
++ ControlFlowFinally finally(this, ast->finallyExpression, hasCatchBlock);
++ TailCallBlocker blockTailCalls(this); // IMPORTANT: destruction will unblock tail calls before finally is generated
++
++ if (ast->catchExpression) {
++ handleTryCatch(ast);
++ } else {
++ RegisterScope scope(this);
++ statement(ast->statement);
++ }
++ }
++
++ bool Codegen::visit(TryStatement *ast)
++ {
++ if (hasError())
++ return false;
++
++ RegisterScope scope(this);
++
++ if (ast->finallyExpression && ast->finallyExpression->statement) {
++ handleTryFinally(ast);
++ } else {
++ handleTryCatch(ast);
++ }
++
++ return false;
++ }
++
++ bool Codegen::visit(VariableStatement *ast)
++ {
++ if (hasError())
++ return false;
++
++ variableDeclarationList(ast->declarations);
++ return false;
++ }
++
++ bool Codegen::visit(WhileStatement *ast)
++ {
++ if (hasError())
++ return false;
++
++ if (AST::cast<FalseLiteral *>(ast->expression))
++ return false;
++
++ RegisterScope scope(this);
++
++ BytecodeGenerator::Label start = bytecodeGenerator->newLabel();
++ BytecodeGenerator::Label end = bytecodeGenerator->newLabel();
++ BytecodeGenerator::Label cond = bytecodeGenerator->label();
++ ControlFlowLoop flow(this, &end, &cond);
++ bytecodeGenerator->addLoopStart(cond);
++
++ bytecodeGenerator->checkException();
++
++ if (!AST::cast<TrueLiteral *>(ast->expression)) {
++ TailCallBlocker blockTailCalls(this);
++ condition(ast->expression, &start, &end, true);
++ }
++
++ start.link();
++ statement(ast->statement);
++ setJumpOutLocation(bytecodeGenerator, ast->statement, ast->whileToken);
++ bytecodeGenerator->jump().link(cond);
++
++ end.link();
++ return false;
++ }
++
++ bool Codegen::visit(WithStatement *ast)
++ {
++ if (hasError())
++ return false;
++
++ RegisterScope scope(this);
++ TailCallBlocker blockTailCalls(this);
++
++ Reference src = expression(ast->expression);
++ if (hasError())
++ return false;
++ src = src.storeOnStack(); // trigger load before we setup the exception handler, so exceptions here go to the right place
++ src.loadInAccumulator();
++
++ enterContext(ast);
++ {
++ blockTailCalls.unblock();
++ ControlFlowWith flow(this);
++ statement(ast->statement);
++ }
++ leaveContext();
++
++ return false;
++ }
++
++ bool Codegen::visit(UiArrayBinding *)
++ {
++ Q_UNIMPLEMENTED();
++ return false;
++ }
++
++ bool Codegen::visit(UiObjectBinding *)
++ {
++ Q_UNIMPLEMENTED();
++ return false;
++ }
++
++ bool Codegen::visit(UiObjectDefinition *)
++ {
++ Q_UNIMPLEMENTED();
++ return false;
++ }
++
++ bool Codegen::visit(UiPublicMember *)
++ {
++ Q_UNIMPLEMENTED();
++ return false;
++ }
++
++ bool Codegen::visit(UiScriptBinding *)
++ {
++ Q_UNIMPLEMENTED();
++ return false;
++ }
++
++ bool Codegen::visit(UiSourceElement *)
++ {
++ Q_UNIMPLEMENTED();
++ return false;
++ }
++
++ bool Codegen::throwSyntaxErrorOnEvalOrArgumentsInStrictMode(const Reference &r, const SourceLocation& loc)
++ {
++ if (!_context->isStrict)
++ return false;
++ bool isArgOrEval = false;
++ if (r.type == Reference::Name) {
++ QString str = jsUnitGenerator->stringForIndex(r.nameAsIndex());
++ if (str == QLatin1String("eval") || str == QLatin1String("arguments")) {
++ isArgOrEval = true;
++ }
++ } else if (r.type == Reference::ScopedLocal || r.isRegister()) {
++ isArgOrEval = r.isArgOrEval;
++ }
++ if (isArgOrEval)
++ throwSyntaxError(loc, QStringLiteral("Variable name may not be eval or arguments in strict mode"));
++ return isArgOrEval;
++ }
++
++ void Codegen::throwError(ErrorType errorType, const SourceLocation &loc, const QString &detail)
++ {
++ if (hasError())
++ return;
++
++ _errorType = errorType;
++ _error.message = detail;
++ _error.loc = loc;
++ }
++
++ void Codegen::throwSyntaxError(const SourceLocation &loc, const QString &detail)
++ {
++ throwError(SyntaxError, loc, detail);
++ }
++
++ void Codegen::throwReferenceError(const SourceLocation &loc, const QString &detail)
++ {
++ throwError(ReferenceError, loc, detail);
++ }
++
++ QQmlJS::DiagnosticMessage Codegen::error() const
++ {
++ return _error;
++ }
++
++ QQmlRefPointer<QV4::CompiledData::CompilationUnit> Codegen::generateCompilationUnit(
++ bool generateUnitData)
++ {
++ return QQmlRefPointer<QV4::CompiledData::CompilationUnit>(
++ new QV4::CompiledData::CompilationUnit(
++ generateUnitData ? jsUnitGenerator->generateUnit() : nullptr),
++ QQmlRefPointer<QV4::CompiledData::CompilationUnit>::Adopt);
++ }
++
++ QQmlRefPointer<QV4::CompiledData::CompilationUnit> Codegen::compileModule(
++ bool debugMode, const QString &url, const QString &sourceCode,
++ const QDateTime &sourceTimeStamp, QList<QQmlJS::DiagnosticMessage> *diagnostics)
++ {
++ QQmlJS::Engine ee;
++ QQmlJS::Lexer lexer(&ee);
++ lexer.setCode(sourceCode, /*line*/1, /*qml mode*/false);
++ QQmlJS::Parser parser(&ee);
++
++ const bool parsed = parser.parseModule();
++
++ if (diagnostics)
++ *diagnostics = parser.diagnosticMessages();
++
++ if (!parsed)
++ return QQmlRefPointer<CompiledData::CompilationUnit>();
++
++ QQmlJS::AST::ESModule *moduleNode = QQmlJS::AST::cast<QQmlJS::AST::ESModule*>(parser.rootNode());
++ if (!moduleNode) {
++ // if parsing was successful, and we have no module, then
++ // the file was empty.
++ if (diagnostics)
++ diagnostics->clear();
++ return nullptr;
++ }
++
++ using namespace QV4::Compiler;
++ Compiler::Module compilerModule(url, url, debugMode);
++ compilerModule.unitFlags |= CompiledData::Unit::IsESModule;
++ compilerModule.sourceTimeStamp = sourceTimeStamp;
++ JSUnitGenerator jsGenerator(&compilerModule);
++ Codegen cg(&jsGenerator, /*strictMode*/true);
++ cg.generateFromModule(sourceCode, moduleNode, &compilerModule);
++ if (cg.hasError()) {
++ if (diagnostics)
++ *diagnostics << cg.error();
++ return QQmlRefPointer<CompiledData::CompilationUnit>();
++ }
++
++ return cg.generateCompilationUnit();
++ }
++
++ const QV4::CompiledData::Unit *Codegen::generateNativeModuleUnitData(
++ bool debugMode, const QString &url, const Value &value)
++ {
++ using namespace QV4::Compiler;
++ Compiler::Module compilerModule(url, url, debugMode);
++ compilerModule.unitFlags |= CompiledData::Unit::IsESModule;
++ JSUnitGenerator jsGenerator(&compilerModule);
++ Codegen cg(&jsGenerator, /*strictMode*/true);
++ cg.generateFromModule(value, &compilerModule);
++ Q_ASSERT(!cg.hasError());
++ return jsGenerator.generateUnit();
++ }
++
++ class Codegen::VolatileMemoryLocationScanner: protected QQmlJS::AST::Visitor
++ {
++ VolatileMemoryLocations locs;
++ Codegen *parent;
++
++ public:
++ VolatileMemoryLocationScanner(Codegen *parent) :
++ QQmlJS::AST::Visitor(parent->recursionDepth()),
++ parent(parent)
++ {}
++
++ Codegen::VolatileMemoryLocations scan(AST::Node *s)
++ {
++ s->accept(this);
++ return locs;
++ }
++
++ bool visit(ArrayMemberExpression *) override
++ {
++ locs.setAllVolatile();
++ return false;
++ }
++
++ bool visit(FieldMemberExpression *) override
++ {
++ locs.setAllVolatile();
++ return false;
++ }
++
++ bool visit(PostIncrementExpression *e) override
++ {
++ collectIdentifiers(locs.specificLocations, e->base);
++ return false;
++ }
++
++ bool visit(PostDecrementExpression *e) override
++ {
++ collectIdentifiers(locs.specificLocations, e->base);
++ return false;
++ }
++
++ bool visit(PreIncrementExpression *e) override
++ {
++ collectIdentifiers(locs.specificLocations, e->expression);
++ return false;
++ }
++
++ bool visit(PreDecrementExpression *e) override
++ {
++ collectIdentifiers(locs.specificLocations, e->expression);
++ return false;
++ }
++
++ bool visit(BinaryExpression *e) override
++ {
++ switch (e->op) {
++ case QSOperator::InplaceAnd:
++ case QSOperator::InplaceSub:
++ case QSOperator::InplaceDiv:
++ case QSOperator::InplaceAdd:
++ case QSOperator::InplaceLeftShift:
++ case QSOperator::InplaceMod:
++ case QSOperator::InplaceMul:
++ case QSOperator::InplaceOr:
++ case QSOperator::InplaceRightShift:
++ case QSOperator::InplaceURightShift:
++ case QSOperator::InplaceXor:
++ collectIdentifiers(locs.specificLocations, e);
++ return false;
++
++ default:
++ return true;
++ }
++ }
++
++ void throwRecursionDepthError() override
++ {
++ parent->throwRecursionDepthError();
++ }
++
++ private:
++ void collectIdentifiers(QList<QStringView> &ids, AST::Node *node) {
++ class Collector: public QQmlJS::AST::Visitor {
++ private:
++ QList<QStringView> &ids;
++ VolatileMemoryLocationScanner *parent;
++
++ public:
++ Collector(QList<QStringView> &ids, VolatileMemoryLocationScanner *parent) :
++ QQmlJS::AST::Visitor(parent->recursionDepth()), ids(ids), parent(parent)
++ {}
++
++ bool visit(IdentifierExpression *ie) final {
++ ids.append(ie->name);
++ return false;
++ }
++
++ void throwRecursionDepthError() final
++ {
++ parent->throwRecursionDepthError();
++ }
++ };
++ Collector collector(ids, this);
++ node->accept(&collector);
++ }
++ };
++
++ Codegen::VolatileMemoryLocations Codegen::scanVolatileMemoryLocations(AST::Node *ast)
++ {
++ VolatileMemoryLocationScanner scanner(this);
++ return scanner.scan(ast);
++ }
++
++ QUrl Codegen::url() const
++ {
++ return QUrl(_fileNameIsUrl ? QUrl(_module->fileName) : QUrl::fromLocalFile(_module->fileName));
++ }
++
++ bool Codegen::RValue::operator==(const RValue &other) const
++ {
++ switch (type) {
++ case Accumulator:
++ return other.isAccumulator();
++ case StackSlot:
++ return other.isStackSlot() && theStackSlot == other.theStackSlot;
++ case Const:
++ return other.isConst() && constant == other.constant;
++ default:
++ return false;
++ }
++ }
++
++ Codegen::RValue Codegen::RValue::storeOnStack() const
++ {
++ switch (type) {
++ case Accumulator:
++ return RValue::fromStackSlot(codegen, Reference::fromAccumulator(codegen).storeOnStack().stackSlot());
++ case StackSlot:
++ return *this;
++ case Const:
++ return RValue::fromStackSlot(codegen, Reference::storeConstOnStack(codegen, constant).stackSlot());
++ default:
++ Q_UNREACHABLE();
++ }
++ }
++
++ void Codegen::RValue::loadInAccumulator() const
++ {
++ switch (type) {
++ case Accumulator:
++ // nothing to do
++ return;
++ case StackSlot:
++ return Reference::fromStackSlot(codegen, theStackSlot).loadInAccumulator();
++ case Const:
++ return Reference::fromConst(codegen, constant).loadInAccumulator();
++ default:
++ Q_UNREACHABLE();
++ }
++
++ }
++
++ bool Codegen::Reference::operator==(const Codegen::Reference &other) const
++ {
++ if (type != other.type)
++ return false;
++ switch (type) {
++ case Invalid:
++ case Accumulator:
++ break;
++ case Super:
++ return true;
++ case SuperProperty:
++ return property == other.property;
++ case StackSlot:
++ return theStackSlot == other.theStackSlot;
++ case ScopedLocal:
++ return index == other.index && scope == other.scope;
++ case Name:
++ return nameAsIndex() == other.nameAsIndex();
++ case Member:
++ return propertyBase == other.propertyBase && propertyNameIndex == other.propertyNameIndex;
++ case Subscript:
++ return elementBase == other.elementBase && other.subscriptLoadedForCall
++ ? (subscriptLoadedForCall && element == other.element)
++ : (!subscriptLoadedForCall && elementSubscript == other.elementSubscript);
++ case Import:
++ return index == other.index;
++ case Const:
++ return constant == other.constant;
++ }
++ return true;
++ }
++
++ Codegen::RValue Codegen::Reference::asRValue() const
++ {
++ switch (type) {
++ case Invalid:
++ Q_UNREACHABLE();
++ case Accumulator:
++ return RValue::fromAccumulator(codegen);
++ case StackSlot:
++ return RValue::fromStackSlot(codegen, stackSlot());
++ case Const:
++ return RValue::fromConst(codegen, constant);
++ default:
++ loadInAccumulator();
++ return RValue::fromAccumulator(codegen);
++ }
++ }
++
++ Codegen::Reference Codegen::Reference::asLValue() const
++ {
++ switch (type) {
++ case Invalid:
++ case Accumulator:
++ Q_UNREACHABLE();
++ case Super:
++ codegen->throwSyntaxError(SourceLocation(), QStringLiteral("Super lvalues not implemented."));
++ return *this;
++ case Member:
++ if (!propertyBase.isStackSlot()) {
++ Reference r = *this;
++ r.propertyBase = propertyBase.storeOnStack();
++ return r;
++ }
++ return *this;
++ case Subscript:
++ if (!elementSubscript.isStackSlot()) {
++ Reference r = *this;
++ r.elementSubscript = elementSubscript.storeOnStack();
++ return r;
++ }
++ return *this;
++ default:
++ return *this;
++ }
++ }
++
++ Codegen::Reference Codegen::Reference::storeConsumeAccumulator() const
++ {
++ storeAccumulator(); // it doesn't matter what happens here, just do it.
++ return Reference();
++ }
++
++ Codegen::Reference Codegen::Reference::baseObject() const
++ {
++ if (type == Reference::Member) {
++ RValue rval = propertyBase;
++ if (!rval.isValid())
++ return Reference::fromConst(codegen, Encode::undefined());
++ if (rval.isAccumulator())
++ return Reference::fromAccumulator(codegen);
++ if (rval.isStackSlot())
++ return Reference::fromStackSlot(codegen, rval.stackSlot());
++ if (rval.isConst())
++ return Reference::fromConst(codegen, rval.constantValue());
++ Q_UNREACHABLE();
++ } else if (type == Reference::Subscript) {
++ return Reference::fromStackSlot(codegen, elementBase.stackSlot());
++ } else if (type == Reference::SuperProperty) {
++ return Reference::fromStackSlot(codegen, CallData::This);
++ } else {
++ return Reference::fromConst(codegen, Encode::undefined());
++ }
++ }
++
++ Codegen::Reference Codegen::Reference::storeOnStack() const
++ { return doStoreOnStack(-1); }
++
++ void Codegen::Reference::storeOnStack(int slotIndex) const
++ { doStoreOnStack(slotIndex); }
++
++ Codegen::Reference Codegen::Reference::doStoreOnStack(int slotIndex) const
++ {
++ Q_ASSERT(isValid());
++
++ if (isStackSlot() && slotIndex == -1 && !(stackSlotIsLocalOrArgument && isVolatile) && !requiresTDZCheck)
++ return *this;
++
++ if (isStackSlot() && !requiresTDZCheck) { // temp-to-temp move
++ Reference dest = Reference::fromStackSlot(codegen, slotIndex);
++ Instruction::MoveReg move;
++ move.srcReg = stackSlot();
++ move.destReg = dest.stackSlot();
++ codegen->bytecodeGenerator->addInstruction(move);
++ return dest;
++ }
++
++ Reference slot = Reference::fromStackSlot(codegen, slotIndex);
++ if (isConstant()) {
++ Instruction::MoveConst move;
++ move.constIndex = codegen->registerConstant(constant);
++ move.destTemp = slot.stackSlot();
++ codegen->bytecodeGenerator->addInstruction(move);
++ } else {
++ loadInAccumulator();
++ slot.storeConsumeAccumulator();
++ }
++ return slot;
++ }
++
++ void Codegen::Reference::tdzCheck(bool requiresCheck, bool throwsReferenceError) const {
++ if (throwsReferenceError) {
++ codegen->generateThrowException(QStringLiteral("ReferenceError"),
++ name + QStringLiteral(" is not defined"));
++ return;
++ }
++ if (!requiresCheck)
++ return;
++ Instruction::DeadTemporalZoneCheck check;
++ check.name = codegen->registerString(name);
++ codegen->bytecodeGenerator->addInstruction(check);
++ }
++
++ void Codegen::Reference::tdzCheckStackSlot(Moth::StackSlot slot, bool requiresCheck, bool throwsReferenceError) const {
++ if (!requiresCheck)
++ return;
++ Instruction::LoadReg load;
++ load.reg = slot;
++ codegen->bytecodeGenerator->addInstruction(load);
++ tdzCheck(true, throwsReferenceError);
++ }
++
++ // Verifies, at run-time, that this reference is not currently in its temporal dead zone.
++ // Only call this for references being *assigned to*, never for the store that performs a
++ // binding's own initialization (that store is what ends the TDZ). Clobbers the accumulator.
++ void Codegen::Reference::checkTDZBeforeAssignment() const
++ {
++ if (!requiresTDZCheck)
++ return;
++
++ if (type != StackSlot && type != ScopedLocal)
++ return;
++
++ loadInAccumulator();
++ }
++
++ Codegen::Reference Codegen::Reference::storeRetainAccumulator() const
++ {
++ if (storeWipesAccumulator()) {
++ // a store will
++ auto tmp = Reference::fromStackSlot(codegen);
++ tmp.storeAccumulator(); // this is safe, and won't destory the accumulator
++ tmp.isReadonly = isReadonly;
++ storeAccumulator();
++ return tmp;
++ } else {
++ // ok, this is safe, just do the store.
++ storeAccumulator();
++ return *this;
++ }
++ }
++
++ bool Codegen::Reference::storeWipesAccumulator() const
++ {
++ switch (type) {
++ default:
++ case Invalid:
++ case Const:
++ case Accumulator:
++ Q_UNREACHABLE();
++ return false;
++ case StackSlot:
++ case ScopedLocal:
++ return false;
++ case Name:
++ case Member:
++ case Subscript:
++ return true;
++ }
++ }
++
++ void Codegen::Reference::storeAccumulator() const
++ {
++ if (throwsReferenceError) {
++ codegen->generateThrowException(QStringLiteral("ReferenceError"),
++ name + QStringLiteral(" is not defined"));
++ return;
++ }
++
++ if (isReferenceToConst) {
++ // throw a type error
++ codegen->generateThrowException(QStringLiteral("TypeError"));
++ return;
++ }
++
++ switch (type) {
++ case Super:
++ Q_UNREACHABLE_RETURN();
++ case SuperProperty:
++ Instruction::StoreSuperProperty store;
++ store.property = property.stackSlot();
++ codegen->bytecodeGenerator->addInstruction(store);
++ return;
++ case StackSlot: {
++ Instruction::StoreReg store;
++ store.reg = theStackSlot;
++ codegen->bytecodeGenerator->addInstruction(store);
++ return;
++ }
++ case ScopedLocal: {
++ if (scope == 0) {
++ Instruction::StoreLocal store;
++ store.index = index;
++ codegen->bytecodeGenerator->addInstruction(store);
++ } else {
++ Instruction::StoreScopedLocal store;
++ store.index = index;
++ store.scope = scope;
++ codegen->bytecodeGenerator->addInstruction(store);
++ }
++ return;
++ }
++ case Name: {
++ Context *c = codegen->currentContext();
++ if (c->isStrict) {
++ Instruction::StoreNameStrict store;
++ store.name = nameAsIndex();
++ codegen->bytecodeGenerator->addInstruction(store);
++ } else {
++ Instruction::StoreNameSloppy store;
++ store.name = nameAsIndex();
++ codegen->bytecodeGenerator->addInstruction(store);
++ }
++ } return;
++ case Member:
++ if (codegen->useFastLookups) {
++ Instruction::SetLookup store;
++ store.base = propertyBase.stackSlot();
++ store.index = codegen->registerSetterLookup(propertyNameIndex);
++ codegen->bytecodeGenerator->addInstruction(store);
++ } else {
++ Instruction::StoreProperty store;
++ store.base = propertyBase.stackSlot();
++ store.name = propertyNameIndex;
++ codegen->bytecodeGenerator->addInstruction(store);
++ }
++ return;
++ case Subscript: {
++ Instruction::StoreElement store;
++ store.base = elementBase;
++ store.index = elementSubscript.stackSlot();
++ codegen->bytecodeGenerator->addInstruction(store);
++ } return;
++ case Invalid:
++ case Accumulator:
++ case Const:
++ case Import:
++ break;
++ }
++
++ Q_UNREACHABLE();
++ }
++
++ void Codegen::Reference::loadInAccumulator() const
++ {
++ switch (type) {
++ case Accumulator:
++ return;
++ case Super:
++ Q_UNREACHABLE_RETURN();
++ case SuperProperty:
++ tdzCheckStackSlot(property, subscriptRequiresTDZCheck, false);
++ Instruction::LoadSuperProperty load;
++ load.property = property.stackSlot();
++ codegen->bytecodeGenerator->addInstruction(load);
++ return;
++ case Const: {
++ QT_WARNING_PUSH
++ QT_WARNING_DISABLE_GCC("-Wmaybe-uninitialized") // the loads below are empty structs.
++ if (constant == Encode::null()) {
++ Instruction::LoadNull load;
++ codegen->bytecodeGenerator->addInstruction(load);
++ } else if (constant == Encode(true)) {
++ Instruction::LoadTrue load;
++ codegen->bytecodeGenerator->addInstruction(load);
++ } else if (constant == Encode(false)) {
++ Instruction::LoadFalse load;
++ codegen->bytecodeGenerator->addInstruction(load);
++ } else if (constant == Encode::undefined()) {
++ Instruction::LoadUndefined load;
++ codegen->bytecodeGenerator->addInstruction(load);
++ } else {
++ StaticValue p = StaticValue::fromReturnedValue(constant);
++ if (p.isNumber()) {
++ double d = p.asDouble();
++ int i = QJSNumberCoercion::toInteger(d);
++ if (d == i && (d != 0 || !std::signbit(d))) {
++ if (!i) {
++ Instruction::LoadZero load;
++ codegen->bytecodeGenerator->addInstruction(load);
++ return;
++ }
++ Instruction::LoadInt load;
++ load.value = StaticValue::fromReturnedValue(constant).toInt32();
++ codegen->bytecodeGenerator->addInstruction(load);
++ return;
++ }
++ }
++ Instruction::LoadConst load;
++ load.index = codegen->registerConstant(constant);
++ codegen->bytecodeGenerator->addInstruction(load);
++ }
++ QT_WARNING_POP
++ } return;
++ case StackSlot: {
++ Instruction::LoadReg load;
++ load.reg = stackSlot();
++ codegen->bytecodeGenerator->addInstruction(load);
++ tdzCheck(requiresTDZCheck, throwsReferenceError);
++ } return;
++ case ScopedLocal: {
++ if (!scope) {
++ Instruction::LoadLocal load;
++ load.index = index;
++ codegen->bytecodeGenerator->addInstruction(load);
++ } else {
++ Instruction::LoadScopedLocal load;
++ load.index = index;
++ load.scope = scope;
++ codegen->bytecodeGenerator->addInstruction(load);
++ }
++ tdzCheck(requiresTDZCheck, throwsReferenceError);
++ return;
++ }
++ case Name:
++ if (global) {
++ // these value properties of the global object are immutable, we we can directly convert them
++ // to their numeric value here
++ if (name == QStringLiteral("undefined")) {
++ Reference::fromConst(codegen, Encode::undefined()).loadInAccumulator();
++ return;
++ } else if (name == QStringLiteral("Infinity")) {
++ Reference::fromConst(codegen, Encode(qInf())).loadInAccumulator();
++ return;
++ } else if (name == QStringLiteral("Nan")) {
++ Reference::fromConst(codegen, Encode(qQNaN())).loadInAccumulator();
++ return;
++ }
++ }
++
++ if (sourceLocation.isValid())
++ codegen->bytecodeGenerator->setLocation(sourceLocation);
++
++ if (global) {
++ if (qmlGlobal) {
++ Instruction::LoadQmlContextPropertyLookup load;
++ load.index = codegen->registerQmlContextPropertyGetterLookup(
++ nameAsIndex(), JSUnitGenerator::LookupForStorage);
++ codegen->bytecodeGenerator->addInstruction(load);
++ } else {
++ Instruction::LoadGlobalLookup load;
++ load.index = codegen->registerGlobalGetterLookup(
++ nameAsIndex(), JSUnitGenerator::LookupForStorage);
++ codegen->bytecodeGenerator->addInstruction(load);
++ }
++ } else {
++ Instruction::LoadName load;
++ load.name = nameAsIndex();
++ codegen->bytecodeGenerator->addInstruction(load);
++ }
++ return;
++ case Member:
++ propertyBase.loadInAccumulator();
++ tdzCheck(requiresTDZCheck, throwsReferenceError);
++
++ if (sourceLocation.isValid())
++ codegen->bytecodeGenerator->setLocation(sourceLocation);
++
++ if (codegen->useFastLookups) {
++ if (optionalChainJumpsToPatch && isOptional) {
++ auto jump = codegen->bytecodeGenerator->jumpOptionalLookup(
++ codegen->registerGetterLookup(
++ propertyNameIndex, JSUnitGenerator::LookupForStorage));
++ optionalChainJumpsToPatch->emplace_back(std::move(jump));
++ } else {
++ Instruction::GetLookup load;
++ load.index = codegen->registerGetterLookup(
++ propertyNameIndex, JSUnitGenerator::LookupForStorage);
++ codegen->bytecodeGenerator->addInstruction(load);
++ }
++ } else {
++ if (optionalChainJumpsToPatch && isOptional) {
++ auto jump = codegen->bytecodeGenerator->jumpOptionalProperty(propertyNameIndex);
++ optionalChainJumpsToPatch->emplace_back(std::move(jump));
++ } else {
++ Instruction::LoadProperty load;
++ load.name = propertyNameIndex;
++ codegen->bytecodeGenerator->addInstruction(load);
++ }
++ }
++ return;
++ case Import: {
++ Instruction::LoadImport load;
++ load.index = index;
++ codegen->bytecodeGenerator->addInstruction(load);
++ tdzCheck(requiresTDZCheck, throwsReferenceError);
++ } return;
++ case Subscript: {
++ tdzCheckStackSlot(elementBase, requiresTDZCheck, throwsReferenceError);
++ elementSubscript.loadInAccumulator();
++ tdzCheck(subscriptRequiresTDZCheck, false);
++ Instruction::LoadElement load;
++ load.base = elementBase;
++ codegen->bytecodeGenerator->addInstruction(load);
++ } return;
++ case Invalid:
++ break;
++ }
++ Q_UNREACHABLE();
++ }
++
++ QT_END_NAMESPACE