diff options
Diffstat (limited to 'src/qml/compiler/qv4codegen.cpp')
-rw-r--r-- | src/qml/compiler/qv4codegen.cpp | 1406 |
1 files changed, 904 insertions, 502 deletions
diff --git a/src/qml/compiler/qv4codegen.cpp b/src/qml/compiler/qv4codegen.cpp index d2d913408a..25831eab73 100644 --- a/src/qml/compiler/qv4codegen.cpp +++ b/src/qml/compiler/qv4codegen.cpp @@ -1,75 +1,53 @@ -/**************************************************************************** -** -** Copyright (C) 2017 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// 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 #include "qv4codegen_p.h" -#include "qv4util_p.h" #include <QtCore/QCoreApplication> #include <QtCore/QStringList> #include <QtCore/QStack> +#include <QtCore/qurl.h> +#include <QtCore/qloggingcategory.h> #include <QScopeGuard> #include <private/qqmljsast_p.h> -#include <private/qv4string_p.h> -#include <private/qv4value_p.h> +#include <private/qqmljslexer_p.h> +#include <private/qqmljsparser_p.h> +#include <private/qv4staticvalue_p.h> #include <private/qv4compilercontext_p.h> #include <private/qv4compilercontrolflow_p.h> #include <private/qv4bytecodegenerator_p.h> #include <private/qv4compilerscanfunctions_p.h> - -#ifndef V4_BOOTSTRAP -# include <qqmlerror.h> -#endif +#include <private/qv4stringtoarrayindex_p.h> +#include <private/qqmljsdiagnosticmessage_p.h> #include <cmath> -#include <iostream> - -static const bool disable_lookups = false; #ifdef CONST #undef CONST #endif -QT_USE_NAMESPACE +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +Q_LOGGING_CATEGORY(lcQmlUsedBeforeDeclared, "qt.qml.usedbeforedeclared"); +Q_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 << "."; +} + static inline void setJumpOutLocation(QV4::Moth::BytecodeGenerator *bytecodeGenerator, const Statement *body, const SourceLocation &fallback) { @@ -89,15 +67,39 @@ static inline void setJumpOutLocation(QV4::Moth::BytecodeGenerator *bytecodeGene } } -Codegen::Codegen(QV4::Compiler::JSUnitGenerator *jsUnitGenerator, bool strict) - : _module(nullptr) - , _returnAddress(-1) - , _context(nullptr) - , _labelledStatement(nullptr) - , jsUnitGenerator(jsUnitGenerator) - , _strictMode(strict) - , _fileNameIsUrl(false) - , hasError(false) +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(); @@ -190,7 +192,7 @@ void Codegen::generateFromProgram(const QString &fileName, ScanFunctions scan(this, sourceCode, contextType); scan(node); - if (hasError) + if (hasError()) return; defineFunction(QStringLiteral("%entry"), node, nullptr, node->statements); @@ -214,7 +216,7 @@ void Codegen::generateFromModule(const QString &fileName, ScanFunctions scan(this, sourceCode, ContextType::ESModule); scan(node); - if (hasError) + if (hasError()) return; { @@ -264,17 +266,30 @@ Context *Codegen::enterBlock(Node *node) Codegen::Reference Codegen::unop(UnaryOperation op, const Reference &expr) { - if (hasError) + if (hasError()) return exprResult(); if (expr.isConstant()) { - auto v = Value::fromReturnedValue(expr.constant); + auto v = StaticValue::fromReturnedValue(expr.constant); if (v.isNumber()) { switch (op) { case Not: return Reference::fromConst(this, Encode(!v.toBoolean())); case UMinus: - return Reference::fromConst(this, Runtime::method_uMinus(v)); + // 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: @@ -288,13 +303,13 @@ Codegen::Reference Codegen::unop(UnaryOperation op, const Reference &expr) switch (op) { case UMinus: { expr.loadInAccumulator(); - Instruction::UMinus uminus; + Instruction::UMinus uminus = {}; bytecodeGenerator->addInstruction(uminus); return Reference::fromAccumulator(this); } case UPlus: { expr.loadInAccumulator(); - Instruction::UPlus uplus; + Instruction::UPlus uplus = {}; bytecodeGenerator->addInstruction(uplus); return Reference::fromAccumulator(this); } @@ -314,10 +329,10 @@ Codegen::Reference Codegen::unop(UnaryOperation op, const Reference &expr) if (!exprAccept(nx) || requiresReturnValue) { Reference e = expr.asLValue(); e.loadInAccumulator(); - Instruction::UPlus uplus; + Instruction::UPlus uplus = {}; bytecodeGenerator->addInstruction(uplus); Reference originalValue = Reference::fromStackSlot(this).storeRetainAccumulator(); - Instruction::Increment inc; + Instruction::Increment inc = {}; bytecodeGenerator->addInstruction(inc); e.storeConsumeAccumulator(); return originalValue; @@ -329,7 +344,7 @@ Codegen::Reference Codegen::unop(UnaryOperation op, const Reference &expr) case PreIncrement: { Reference e = expr.asLValue(); e.loadInAccumulator(); - Instruction::Increment inc; + Instruction::Increment inc = {}; bytecodeGenerator->addInstruction(inc); if (exprAccept(nx)) return e.storeConsumeAccumulator(); @@ -340,10 +355,10 @@ Codegen::Reference Codegen::unop(UnaryOperation op, const Reference &expr) if (!exprAccept(nx) || requiresReturnValue) { Reference e = expr.asLValue(); e.loadInAccumulator(); - Instruction::UPlus uplus; + Instruction::UPlus uplus = {}; bytecodeGenerator->addInstruction(uplus); Reference originalValue = Reference::fromStackSlot(this).storeRetainAccumulator(); - Instruction::Decrement dec; + Instruction::Decrement dec = {}; bytecodeGenerator->addInstruction(dec); e.storeConsumeAccumulator(); return originalValue; @@ -355,7 +370,7 @@ Codegen::Reference Codegen::unop(UnaryOperation op, const Reference &expr) case PreDecrement: { Reference e = expr.asLValue(); e.loadInAccumulator(); - Instruction::Decrement dec; + Instruction::Decrement dec = {}; bytecodeGenerator->addInstruction(dec); if (exprAccept(nx)) return e.storeConsumeAccumulator(); @@ -378,6 +393,7 @@ void Codegen::statement(Statement *ast) { RegisterScope scope(this); + bytecodeGenerator->incrementStatement(); bytecodeGenerator->setLocation(ast->firstSourceLocation()); VolatileMemoryLocations vLocs = scanVolatileMemoryLocations(ast); @@ -393,6 +409,7 @@ void Codegen::statement(ExpressionNode *ast) } else { RegisterScope scope(this); + bytecodeGenerator->incrementStatement(); pushExpr(Result(nx)); VolatileMemoryLocations vLocs = scanVolatileMemoryLocations(ast); qSwap(_volatileMemoryLocations, vLocs); @@ -402,7 +419,7 @@ void Codegen::statement(ExpressionNode *ast) qSwap(_volatileMemoryLocations, vLocs); Reference result = popResult(); - if (hasError) + if (hasError()) return; if (result.loadTriggersSideEffect()) result.loadInAccumulator(); // triggers side effects @@ -412,7 +429,7 @@ void Codegen::statement(ExpressionNode *ast) void Codegen::condition(ExpressionNode *ast, const BytecodeGenerator::Label *iftrue, const BytecodeGenerator::Label *iffalse, bool trueBlockFollowsCondition) { - if (hasError) + if (hasError()) return; if (!ast) @@ -422,7 +439,7 @@ void Codegen::condition(ExpressionNode *ast, const BytecodeGenerator::Label *ift accept(ast); Result r = popExpr(); - if (hasError) + if (hasError()) return; if (r.format() == ex) { @@ -576,7 +593,7 @@ Codegen::Reference Codegen::targetForPatternElement(AST::PatternElement *p) if (!p->bindingTarget || p->destructuringPattern()) return Codegen::Reference::fromStackSlot(this); Reference lhs = expression(p->bindingTarget); - if (hasError) + if (hasError()) return lhs; if (!lhs.isLValue()) { throwReferenceError(p->bindingTarget->firstSourceLocation(), QStringLiteral("Binding target is not a reference.")); @@ -594,14 +611,16 @@ void Codegen::initializeAndDestructureBindingElement(AST::PatternElement *e, con Reference varToStore = targetForPatternElement(e); if (isDefinition) varToStore.isReferenceToConst = false; - if (hasError) + if (hasError()) return; + accept(e->typeAnnotation); + if (e->initializer) { if (!baseRef.isValid()) { // assignment Reference expr = expression(e->initializer); - if (hasError) + if (hasError()) return; expr.loadInAccumulator(); varToStore.storeConsumeAccumulator(); @@ -609,7 +628,7 @@ void Codegen::initializeAndDestructureBindingElement(AST::PatternElement *e, con baseRef.loadInAccumulator(); BytecodeGenerator::Jump jump = bytecodeGenerator->jumpNotUndefined(); Reference expr = expression(e->initializer); - if (hasError) { + if (hasError()) { jump.link(); return; } @@ -620,7 +639,7 @@ void Codegen::initializeAndDestructureBindingElement(AST::PatternElement *e, con baseRef.loadInAccumulator(); BytecodeGenerator::Jump jump = bytecodeGenerator->jumpNotUndefined(); Reference expr = expression(e->initializer); - if (hasError) { + if (hasError()) { jump.link(); return; } @@ -657,7 +676,7 @@ Codegen::Reference Codegen::referenceForPropertyName(const Codegen::Reference &o Reference property; if (cname) { Reference computedName = expression(cname->expression); - if (hasError) + if (hasError()) return Reference(); computedName = computedName.storeOnStack(); property = Reference::fromSubscript(object, computedName).asLValue(); @@ -680,10 +699,10 @@ void Codegen::destructurePropertyList(const Codegen::Reference &object, PatternP PatternProperty *p = it->property; RegisterScope scope(this); Reference property = referenceForPropertyName(object, p->name); - if (hasError) + if (hasError()) return; initializeAndDestructureBindingElement(p, property, isDefinition); - if (hasError) + if (hasError()) return; } } @@ -693,9 +712,8 @@ void Codegen::destructureElementList(const Codegen::Reference &array, PatternEle RegisterScope scope(this); Reference iterator = Reference::fromStackSlot(this); - Reference iteratorValue = Reference::fromStackSlot(this); - Reference iteratorDone = Reference::fromStackSlot(this); - Reference::storeConstOnStack(this, Encode(false), iteratorDone.stackSlot()); + QVarLengthArray<Reference> iteratorValues; + Reference ignored; array.loadInAccumulator(); Instruction::GetIterator iteratorObjInstr; @@ -703,45 +721,76 @@ void Codegen::destructureElementList(const Codegen::Reference &array, PatternEle 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(); + { - auto cleanup = [this, iterator, iteratorDone]() { + ControlFlowUnwindCleanup flow(this, [&]() { + BytecodeGenerator::Label skipClose = bytecodeGenerator->newLabel(); + needsClose.loadInAccumulator(); + bytecodeGenerator->jumpFalse().link(skipClose); iterator.loadInAccumulator(); Instruction::IteratorClose close; - close.done = iteratorDone.stackSlot(); bytecodeGenerator->addInstruction(close); - }; - - ControlFlowUnwindCleanup flow(this, cleanup); + skipClose.link(); + }); + auto it = iteratorValues.constBegin(); 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; - next.value = iteratorValue.stackSlot(); - next.done = iteratorDone.stackSlot(); - bytecodeGenerator->addInstruction(next); - } if (!e) continue; - RegisterScope scope(this); - iterator.loadInAccumulator(); - if (e->type == PatternElement::RestElement) { - Reference::fromConst(this, Encode(true)).storeOnStack(iteratorDone.stackSlot()); + 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); + initializeAndDestructureBindingElement( + e, Reference::fromAccumulator(this), isDefinition); } else { - Instruction::IteratorNext next; - next.value = iteratorValue.stackSlot(); - next.done = iteratorDone.stackSlot(); - bytecodeGenerator->addInstruction(next); - initializeAndDestructureBindingElement(e, iteratorValue, isDefinition); - if (hasError) - return; + Q_ASSERT(it != iteratorValues.constEnd()); + initializeAndDestructureBindingElement(e, *it++, isDefinition); } + + if (hasError()) + return; } } } @@ -760,86 +809,72 @@ void Codegen::destructurePattern(Pattern *p, const Reference &rhs) bool Codegen::visit(ArgumentList *) { - Q_UNREACHABLE(); - return false; + Q_UNREACHABLE_RETURN(false); } bool Codegen::visit(CaseBlock *) { - Q_UNREACHABLE(); - return false; + Q_UNREACHABLE_RETURN(false); } bool Codegen::visit(CaseClause *) { - Q_UNREACHABLE(); - return false; + Q_UNREACHABLE_RETURN(false); } bool Codegen::visit(CaseClauses *) { - Q_UNREACHABLE(); - return false; + Q_UNREACHABLE_RETURN(false); } bool Codegen::visit(Catch *) { - Q_UNREACHABLE(); - return false; + Q_UNREACHABLE_RETURN(false); } bool Codegen::visit(DefaultClause *) { - Q_UNREACHABLE(); - return false; + Q_UNREACHABLE_RETURN(false); } bool Codegen::visit(Elision *) { - Q_UNREACHABLE(); - return false; + Q_UNREACHABLE_RETURN(false); } bool Codegen::visit(Finally *) { - Q_UNREACHABLE(); - return false; + Q_UNREACHABLE_RETURN(false); } bool Codegen::visit(FormalParameterList *) { - Q_UNREACHABLE(); - return false; + Q_UNREACHABLE_RETURN(false); } bool Codegen::visit(Program *) { - Q_UNREACHABLE(); - return false; + Q_UNREACHABLE_RETURN(false); } bool Codegen::visit(PatternElement *) { - Q_UNREACHABLE(); - return false; + Q_UNREACHABLE_RETURN(false); } bool Codegen::visit(PatternElementList *) { - Q_UNREACHABLE(); - return false; + Q_UNREACHABLE_RETURN(false); } bool Codegen::visit(PatternProperty *) { - Q_UNREACHABLE(); - return false; + Q_UNREACHABLE_RETURN(false); } bool Codegen::visit(PatternPropertyList *) { - Q_UNREACHABLE(); - return false; + Q_UNREACHABLE_RETURN(false); } bool Codegen::visit(ExportDeclaration *ast) @@ -872,70 +907,70 @@ bool Codegen::visit(ExportDeclaration *ast) return false; } -bool Codegen::visit(StatementList *) +bool Codegen::visit(TypeAnnotation *ast) { - Q_UNREACHABLE(); + 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; + Q_UNREACHABLE_RETURN(false); } bool Codegen::visit(UiImport *) { - Q_UNREACHABLE(); - return false; + Q_UNREACHABLE_RETURN(false); } bool Codegen::visit(UiHeaderItemList *) { - Q_UNREACHABLE(); - return false; + Q_UNREACHABLE_RETURN(false); +} + +bool Codegen::visit(UiPragmaValueList *) +{ + Q_UNREACHABLE_RETURN(false); } bool Codegen::visit(UiPragma *) { - Q_UNREACHABLE(); - return false; + Q_UNREACHABLE_RETURN(false); } bool Codegen::visit(UiObjectInitializer *) { - Q_UNREACHABLE(); - return false; + Q_UNREACHABLE_RETURN(false); } bool Codegen::visit(UiObjectMemberList *) { - Q_UNREACHABLE(); - return false; + Q_UNREACHABLE_RETURN(false); } bool Codegen::visit(UiParameterList *) { - Q_UNREACHABLE(); - return false; + Q_UNREACHABLE_RETURN(false); } bool Codegen::visit(UiProgram *) { - Q_UNREACHABLE(); - return false; + Q_UNREACHABLE_RETURN(false); } bool Codegen::visit(UiQualifiedId *) { - Q_UNREACHABLE(); - return false; + Q_UNREACHABLE_RETURN(false); } bool Codegen::visit(VariableDeclarationList *) { - Q_UNREACHABLE(); - return false; + Q_UNREACHABLE_RETURN(false); } bool Codegen::visit(ClassExpression *ast) @@ -1003,11 +1038,11 @@ bool Codegen::visit(ClassExpression *ast) if (ast->heritage) { bytecodeGenerator->setLocation(ast->heritage->firstSourceLocation()); Reference r = expression(ast->heritage); - if (hasError) + if (hasError()) return false; r.storeOnStack(heritage.stackSlot()); } else { - Reference::fromConst(this, Value::emptyValue().asReturnedValue()).loadInAccumulator(); + Reference::fromConst(this, StaticValue::emptyValue().asReturnedValue()).loadInAccumulator(); heritage.storeConsumeAccumulator(); } @@ -1022,7 +1057,7 @@ bool Codegen::visit(ClassExpression *ast) RegisterScope scope(this); bytecodeGenerator->setLocation(cname->firstSourceLocation()); Reference computedName = expression(cname->expression); - if (hasError) + if (hasError()) return false; computedName.storeOnStack(member->isStatic ? currentStaticName++ : currentNonStaticName++); } @@ -1055,7 +1090,7 @@ bool Codegen::visit(ClassDeclaration *ast) bool Codegen::visit(Expression *ast) { - if (hasError) + if (hasError()) return false; TailCallBlocker blockTailCalls(this); @@ -1068,7 +1103,7 @@ bool Codegen::visit(Expression *ast) bool Codegen::visit(ArrayPattern *ast) { - if (hasError) + if (hasError()) return false; TailCallBlocker blockTailCalls(this); @@ -1085,12 +1120,12 @@ bool Codegen::visit(ArrayPattern *ast) if (args == -1) args = temp; if (!arg) { - auto c = Reference::fromConst(this, Value::emptyValue().asReturnedValue()); + auto c = Reference::fromConst(this, StaticValue::emptyValue().asReturnedValue()); (void) c.storeOnStack(temp); } else { RegisterScope scope(this); Reference r = expression(arg); - if (hasError) + if (hasError()) return; (void) r.storeOnStack(temp); } @@ -1108,7 +1143,7 @@ bool Codegen::visit(ArrayPattern *ast) continue; push(e->initializer); - if (hasError) + if (hasError()) return false; } @@ -1139,14 +1174,15 @@ bool Codegen::visit(ArrayPattern *ast) slot.storeConsumeAccumulator(); index.loadInAccumulator(); - Instruction::Increment inc; + Instruction::Increment inc = {}; bytecodeGenerator->addInstruction(inc); index.storeConsumeAccumulator(); }; while (it) { for (Elision *elision = it->elision; elision; elision = elision->next) { - Reference::fromConst(this, Value::emptyValue().asReturnedValue()).loadInAccumulator(); + Reference::fromConst( + this, StaticValue::emptyValue().asReturnedValue()).loadInAccumulator(); pushAccumulator(); } @@ -1160,7 +1196,6 @@ bool Codegen::visit(ArrayPattern *ast) RegisterScope scope(this); Reference iterator = Reference::fromStackSlot(this); - Reference iteratorDone = Reference::fromConst(this, Encode(false)).storeOnStack(); Reference lhsValue = Reference::fromStackSlot(this); // There should be a temporal block, so that variables declared in lhs shadow outside vars. @@ -1168,7 +1203,7 @@ bool Codegen::visit(ArrayPattern *ast) { RegisterScope innerScope(this); Reference expr = expression(it->element->initializer); - if (hasError) + if (hasError()) return false; expr.loadInAccumulator(); @@ -1180,34 +1215,35 @@ bool Codegen::visit(ArrayPattern *ast) BytecodeGenerator::Label in = bytecodeGenerator->newLabel(); BytecodeGenerator::Label end = bytecodeGenerator->newLabel(); + BytecodeGenerator::Label done = bytecodeGenerator->newLabel(); { - auto cleanup = [this, iterator, iteratorDone]() { + auto cleanup = [this, iterator, done]() { iterator.loadInAccumulator(); Instruction::IteratorClose close; - close.done = iteratorDone.stackSlot(); bytecodeGenerator->addInstruction(close); + done.link(); }; - ControlFlowLoop flow(this, &end, &in, cleanup); + ControlFlowLoop flow(this, &end, &in, std::move(cleanup)); in.link(); + bytecodeGenerator->addLoopStart(in); iterator.loadInAccumulator(); Instruction::IteratorNext next; next.value = lhsValue.stackSlot(); - next.done = iteratorDone.stackSlot(); - bytecodeGenerator->addInstruction(next); - bytecodeGenerator->addJumpInstruction(Instruction::JumpTrue()).link(end); + 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) + if (hasError()) return false; expr.loadInAccumulator(); @@ -1225,36 +1261,61 @@ bool Codegen::visit(ArrayPattern *ast) bool Codegen::visit(ArrayMemberExpression *ast) { - if (hasError) + if (hasError()) return false; + const bool isTailOfChain = traverseOptionalChain(ast); + TailCallBlocker blockTailCalls(this); Reference base = expression(ast->base); - if (hasError) + + auto writeSkip = [&]() { + base.loadInAccumulator(); + bytecodeGenerator->addInstruction(Instruction::CmpEqNull()); + auto jumpToUndefined = bytecodeGenerator->jumpTrue(); + m_optionalChainsStates.top().jumpsToPatch.emplace_back(std::move(jumpToUndefined)); + }; + + if (hasError()) return false; if (base.isSuper()) { Reference index = expression(ast->expression).storeOnStack(); - setExprResult(Reference::fromSuperProperty(index)); + optionalChainFinalizer(Reference::fromSuperProperty(index), isTailOfChain); return false; } base = base.storeOnStack(); - if (hasError) + if (hasError()) return false; if (AST::StringLiteral *str = AST::cast<AST::StringLiteral *>(ast->expression)) { QString s = str->value.toString(); - uint arrayIndex = QV4::String::toArrayIndex(s); + uint arrayIndex = stringToArrayIndex(s); if (arrayIndex == UINT_MAX) { - setExprResult(Reference::fromMember(base, str->value.toString())); + 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)); - setExprResult(Reference::fromSubscript(base, index)); + optionalChainFinalizer(Reference::fromSubscript(base, index), isTailOfChain); return false; } + + + if (ast->isOptional) + writeSkip(); + Reference index = expression(ast->expression); - if (hasError) + + if (hasError()) return false; - setExprResult(Reference::fromSubscript(base, index)); + + optionalChainFinalizer(Reference::fromSubscript(base, index), isTailOfChain); return false; } @@ -1279,7 +1340,7 @@ static QSOperator::Op baseOp(int op) bool Codegen::visit(BinaryExpression *ast) { - if (hasError) + if (hasError()) return false; TailCallBlocker blockTailCalls(this); @@ -1297,7 +1358,7 @@ bool Codegen::visit(BinaryExpression *ast) auto endif = bytecodeGenerator->newLabel(); Reference left = expression(ast->left); - if (hasError) + if (hasError()) return false; left.loadInAccumulator(); @@ -1307,7 +1368,7 @@ bool Codegen::visit(BinaryExpression *ast) blockTailCalls.unblock(); Reference right = expression(ast->right); - if (hasError) + if (hasError()) return false; right.loadInAccumulator(); @@ -1328,7 +1389,7 @@ bool Codegen::visit(BinaryExpression *ast) auto endif = bytecodeGenerator->newLabel(); Reference left = expression(ast->left); - if (hasError) + if (hasError()) return false; left.loadInAccumulator(); @@ -1338,7 +1399,7 @@ bool Codegen::visit(BinaryExpression *ast) blockTailCalls.unblock(); Reference right = expression(ast->right); - if (hasError) + if (hasError()) return false; right.loadInAccumulator(); @@ -1347,11 +1408,39 @@ bool Codegen::visit(BinaryExpression *ast) 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); + right.loadInAccumulator(); + jump_endif.link(); + setExprResult(Reference::fromAccumulator(this)); + + return false; } else if (ast->op == QSOperator::Assign) { if (AST::Pattern *p = ast->left->patternCast()) { RegisterScope scope(this); Reference right = expression(ast->right); - if (hasError) + if (hasError()) return false; right = right.storeOnStack(); destructurePattern(p, right); @@ -1362,7 +1451,7 @@ bool Codegen::visit(BinaryExpression *ast) return false; } Reference left = expression(ast->left); - if (hasError) + if (hasError()) return false; if (!left.isLValue()) { @@ -1374,7 +1463,7 @@ bool Codegen::visit(BinaryExpression *ast) return false; blockTailCalls.unblock(); Reference r = expression(ast->right); - if (hasError) + if (hasError()) return false; r.loadInAccumulator(); if (exprAccept(nx)) @@ -1385,7 +1474,7 @@ bool Codegen::visit(BinaryExpression *ast) } Reference left = expression(ast->left); - if (hasError) + if (hasError()) return false; switch (ast->op) { @@ -1419,10 +1508,10 @@ bool Codegen::visit(BinaryExpression *ast) Reference tempLeft = left.storeOnStack(); Reference right = expression(ast->right); - if (hasError) + if (hasError()) return false; - binopHelper(baseOp(ast->op), tempLeft, right).loadInAccumulator(); + binopHelper(ast, baseOp(ast->op), tempLeft, right).loadInAccumulator(); setExprResult(left.storeRetainAccumulator()); break; @@ -1433,14 +1522,15 @@ bool Codegen::visit(BinaryExpression *ast) case QSOperator::BitXor: if (left.isConstant()) { Reference right = expression(ast->right); - if (hasError) + if (hasError()) return false; - setExprResult(binopHelper(static_cast<QSOperator::Op>(ast->op), right, left)); + setExprResult(binopHelper(ast, static_cast<QSOperator::Op>(ast->op), right, left)); break; } - // intentional fall-through! + Q_FALLTHROUGH(); case QSOperator::In: case QSOperator::InstanceOf: + case QSOperator::As: case QSOperator::Equal: case QSOperator::NotEqual: case QSOperator::Ge: @@ -1466,24 +1556,25 @@ bool Codegen::visit(BinaryExpression *ast) left = left.storeOnStack(); // force any loads of the lhs, so the rhs won't clobber it right = expression(ast->right); } - if (hasError) + if (hasError()) return false; - setExprResult(binopHelper(static_cast<QSOperator::Op>(ast->op), left, right)); + setExprResult(binopHelper(ast, static_cast<QSOperator::Op>(ast->op), left, right)); break; } - } // switch return false; } -Codegen::Reference Codegen::binopHelper(QSOperator::Op oper, Reference &left, Reference &right) +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: { - //### Todo: when we add type hints, we can generate an Increment when both the lhs is a number and the rhs == 1 left = left.storeOnStack(); right.loadInAccumulator(); Instruction::Add add; @@ -1494,7 +1585,8 @@ Codegen::Reference Codegen::binopHelper(QSOperator::Op oper, Reference &left, Re case QSOperator::Sub: { if (right.isConstant() && right.constant == Encode(int(1))) { left.loadInAccumulator(); - bytecodeGenerator->addInstruction(Instruction::Decrement()); + Instruction::Decrement dec = {}; + bytecodeGenerator->addInstruction(dec); } else { left = left.storeOnStack(); right.loadInAccumulator(); @@ -1538,9 +1630,9 @@ Codegen::Reference Codegen::binopHelper(QSOperator::Op oper, Reference &left, Re } case QSOperator::BitAnd: if (right.isConstant()) { - int rightAsInt = Value::fromReturnedValue(right.constant).toInt32(); + int rightAsInt = StaticValue::fromReturnedValue(right.constant).toInt32(); if (left.isConstant()) { - int result = Value::fromReturnedValue(left.constant).toInt32() & rightAsInt; + int result = StaticValue::fromReturnedValue(left.constant).toInt32() & rightAsInt; return Reference::fromConst(this, Encode(result)); } left.loadInAccumulator(); @@ -1556,9 +1648,9 @@ Codegen::Reference Codegen::binopHelper(QSOperator::Op oper, Reference &left, Re break; case QSOperator::BitOr: if (right.isConstant()) { - int rightAsInt = Value::fromReturnedValue(right.constant).toInt32(); + int rightAsInt = StaticValue::fromReturnedValue(right.constant).toInt32(); if (left.isConstant()) { - int result = Value::fromReturnedValue(left.constant).toInt32() | rightAsInt; + int result = StaticValue::fromReturnedValue(left.constant).toInt32() | rightAsInt; return Reference::fromConst(this, Encode(result)); } left.loadInAccumulator(); @@ -1574,9 +1666,9 @@ Codegen::Reference Codegen::binopHelper(QSOperator::Op oper, Reference &left, Re break; case QSOperator::BitXor: if (right.isConstant()) { - int rightAsInt = Value::fromReturnedValue(right.constant).toInt32(); + int rightAsInt = StaticValue::fromReturnedValue(right.constant).toInt32(); if (left.isConstant()) { - int result = Value::fromReturnedValue(left.constant).toInt32() ^ rightAsInt; + int result = StaticValue::fromReturnedValue(left.constant).toInt32() ^ rightAsInt; return Reference::fromConst(this, Encode(result)); } left.loadInAccumulator(); @@ -1594,7 +1686,7 @@ Codegen::Reference Codegen::binopHelper(QSOperator::Op oper, Reference &left, Re if (right.isConstant()) { left.loadInAccumulator(); Instruction::UShrConst ushr; - ushr.rhs = Value::fromReturnedValue(right.constant).toInt32() & 0x1f; + ushr.rhs = StaticValue::fromReturnedValue(right.constant).toInt32() & 0x1f; bytecodeGenerator->addInstruction(ushr); } else { right.loadInAccumulator(); @@ -1607,7 +1699,7 @@ Codegen::Reference Codegen::binopHelper(QSOperator::Op oper, Reference &left, Re if (right.isConstant()) { left.loadInAccumulator(); Instruction::ShrConst shr; - shr.rhs = Value::fromReturnedValue(right.constant).toInt32() & 0x1f; + shr.rhs = StaticValue::fromReturnedValue(right.constant).toInt32() & 0x1f; bytecodeGenerator->addInstruction(shr); } else { right.loadInAccumulator(); @@ -1620,7 +1712,7 @@ Codegen::Reference Codegen::binopHelper(QSOperator::Op oper, Reference &left, Re if (right.isConstant()) { left.loadInAccumulator(); Instruction::ShlConst shl; - shl.rhs = Value::fromReturnedValue(right.constant).toInt32() & 0x1f; + shl.rhs = StaticValue::fromReturnedValue(right.constant).toInt32() & 0x1f; bytecodeGenerator->addInstruction(shl); } else { right.loadInAccumulator(); @@ -1637,6 +1729,14 @@ Codegen::Reference Codegen::binopHelper(QSOperator::Op oper, Reference &left, Re 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(); @@ -1748,7 +1848,7 @@ Codegen::Reference Codegen::jumpBinop(QSOperator::Op oper, Reference &left, Refe qSwap(left, right); // null==a -> a==null if (right.isConstant()) { - Value c = Value::fromReturnedValue(right.constant); + StaticValue c = StaticValue::fromReturnedValue(right.constant); if (c.isNull() || c.isUndefined()) { left.loadInAccumulator(); if (oper == QSOperator::Equal) { @@ -1848,23 +1948,40 @@ Codegen::Reference Codegen::jumpBinop(QSOperator::Op oper, Reference &left, Refe 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) + if (hasError()) return false; + const bool isTailOfChain = traverseOptionalChain(ast); + RegisterScope scope(this); TailCallBlocker blockTailCalls(this); - Reference base = expression(ast->base); + Reference expr = expression(ast->base); + Reference base = expr; - if (hasError) + if (hasError()) return false; switch (base.type) { case Reference::Member: - case Reference::Subscript: base = base.asLValue(); break; + case Reference::Subscript: + base.element = loadSubscriptForCall(base).storeOnStack().stackSlot(); + base.subscriptLoadedForCall = true; + break; case Reference::Name: break; case Reference::Super: @@ -1877,11 +1994,25 @@ bool Codegen::visit(CallExpression *ast) 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) + if (hasError()) return false; blockTailCalls.unblock(); @@ -1891,78 +2022,108 @@ bool Codegen::visit(CallExpression *ast) baseObject.storeOnStack(thisObject); baseObject = Reference::fromStackSlot(this, thisObject); } - if (!base.isStackSlot()) { - base.storeOnStack(functionObject); - base = Reference::fromStackSlot(this, functionObject); - } + + 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 = base.stackSlot(); + call.func = func; call.thisObject = baseObject.stackSlot(); call.argc = calldata.argc; call.argv = calldata.argv; bytecodeGenerator->addInstruction(call); } else { Instruction::TailCall call; - call.func = base.stackSlot(); + call.func = func; call.thisObject = baseObject.stackSlot(); call.argc = calldata.argc; call.argv = calldata.argv; bytecodeGenerator->addInstruction(call); } - setExprResult(Reference::fromAccumulator(this)); + optionalChainFinalizer(Reference::fromAccumulator(this), isTailOfChain); return false; - } - handleCall(base, calldata, functionObject, thisObject); + handleCall(base, calldata, functionObject, thisObject, ast->isOptional); + optionalChainFinalizer(Reference::fromAccumulator(this), isTailOfChain); return false; } -void Codegen::handleCall(Reference &base, Arguments calldata, int slotForFunction, int slotForThisObject) +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) { - if (!disable_lookups && useFastLookups) { + if (base.type == Reference::Member || base.hasSavedCallBaseSlot) { + if (useFastLookups) { Instruction::CallPropertyLookup call; - call.base = base.propertyBase.stackSlot(); - call.lookupIndex = registerGetterLookup(base.propertyNameIndex); + 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; - call.base = base.propertyBase.stackSlot(); - call.name = base.propertyNameIndex; + 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::CallElement call; - call.base = base.elementBase; - call.index = base.elementSubscript.stackSlot(); + 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")) { + if (base.name == QStringLiteral("eval") && !optional) { Instruction::CallPossiblyDirectEval call; call.argc = calldata.argc; call.argv = calldata.argv; bytecodeGenerator->addInstruction(call); - } else if (!disable_lookups && useFastLookups && base.global) { + } else if (useFastLookups && base.global) { if (base.qmlGlobal) { Instruction::CallQmlContextPropertyLookup call; - call.index = registerQmlContextPropertyGetterLookup(base.nameAsIndex()); + 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()); + call.index = registerGlobalGetterLookup( + base.nameAsIndex(), JSUnitGenerator::LookupForCall); call.argc = calldata.argc; call.argv = calldata.argv; bytecodeGenerator->addInstruction(call); @@ -1998,8 +2159,6 @@ void Codegen::handleCall(Reference &base, Arguments calldata, int slotForFunctio call.argv = calldata.argv; bytecodeGenerator->addInstruction(call); } - - setExprResult(Reference::fromAccumulator(this)); } Codegen::Arguments Codegen::pushArgs(ArgumentList *args) @@ -2022,17 +2181,21 @@ Codegen::Arguments Codegen::pushArgs(ArgumentList *args) argc = 0; for (ArgumentList *it = args; it; it = it->next) { if (it->isSpreadElement) { - Reference::fromConst(this, Value::emptyValue().asReturnedValue()).storeOnStack(calldata + argc); + Reference::fromConst( + this, + StaticValue::emptyValue().asReturnedValue()).storeOnStack(calldata + argc); ++argc; } RegisterScope scope(this); Reference e = expression(it->expression); - if (hasError) + if (hasError()) break; if (!argc && !it->next && !hasSpread) { // avoid copy for functions taking a single argument - if (e.isStackSlot()) + if (e.isStackSlot()) { + e.tdzCheck(); return { 1, e.stackSlot(), hasSpread }; + } } (void) e.storeOnStack(calldata + argc); ++argc; @@ -2056,7 +2219,7 @@ Codegen::Arguments Codegen::pushTemplateArgs(TemplateLiteral *args) for (TemplateLiteral *it = args; it && it->expression; it = it->next) { RegisterScope scope(this); Reference e = expression(it->expression); - if (hasError) + if (hasError()) break; (void) e.storeOnStack(calldata + argc); ++argc; @@ -2067,7 +2230,7 @@ Codegen::Arguments Codegen::pushTemplateArgs(TemplateLiteral *args) bool Codegen::visit(ConditionalExpression *ast) { - if (hasError) + if (hasError()) return false; RegisterScope scope(this); @@ -2081,14 +2244,14 @@ bool Codegen::visit(ConditionalExpression *ast) iftrue.link(); Reference ok = expression(ast->ok); - if (hasError) + if (hasError()) return false; ok.loadInAccumulator(); BytecodeGenerator::Jump jump_endif = bytecodeGenerator->jump(); iffalse.link(); Reference ko = expression(ast->ko); - if (hasError) { + if (hasError()) { jump_endif.link(); // dummy link, to prevent assert in Jump destructor from triggering return false; } @@ -2102,15 +2265,21 @@ bool Codegen::visit(ConditionalExpression *ast) bool Codegen::visit(DeleteExpression *ast) { - if (hasError) + if (hasError()) return false; + const bool isTailOfChain = traverseOptionalChain(ast); + RegisterScope scope(this); TailCallBlocker blockTailCalls(this); Reference expr = expression(ast->expression); - if (hasError) + 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. @@ -2118,7 +2287,7 @@ bool Codegen::visit(DeleteExpression *ast) case Reference::StackSlot: if (!expr.stackSlotIsLocalOrArgument) break; - // fall through + Q_FALLTHROUGH(); case Reference::ScopedLocal: // Trying to delete a function argument might throw. if (_context->isStrict) { @@ -2141,6 +2310,14 @@ bool Codegen::visit(DeleteExpression *ast) 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); @@ -2150,17 +2327,29 @@ bool Codegen::visit(DeleteExpression *ast) del.base = expr.propertyBase.stackSlot(); del.index = index.stackSlot(); bytecodeGenerator->addInstruction(del); - setExprResult(Reference::fromAccumulator(this)); + 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); - setExprResult(Reference::fromAccumulator(this)); + auto ref = Reference::fromAccumulator(this); + + optionalChainFinalizer(ref, isTailOfChain, true); return false; } default: @@ -2171,9 +2360,13 @@ bool Codegen::visit(DeleteExpression *ast) return false; } +void Codegen::endVisit(DeleteExpression *ast) { + m_seenOptionalChainNodes.remove(ast); +} + bool Codegen::visit(FalseLiteral *) { - if (hasError) + if (hasError()) return false; setExprResult(Reference::fromConst(this, QV4::Encode(false))); @@ -2182,19 +2375,122 @@ bool Codegen::visit(FalseLiteral *) bool Codegen::visit(SuperLiteral *) { - if (hasError) + 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) + 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 @@ -2204,33 +2500,45 @@ bool Codegen::visit(FieldMemberExpression *ast) Reference r = referenceForName(QStringLiteral("new.target"), false); r.isReadonly = true; setExprResult(r); + return false; } - Reference r = Reference::fromStackSlot(this, CallData::NewTarget); - setExprResult(r); + auto ref = Reference::fromStackSlot(this, CallData::NewTarget); + optionalChainFinalizer(ref, isTailOfChain); return false; } } Reference base = expression(ast->base); - if (hasError) + + 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(); - setExprResult(Reference::fromSuperProperty(property)); + + optionalChainFinalizer(Reference::fromSuperProperty(property), isTailOfChain); return false; } - setExprResult(Reference::fromMember(base, ast->name.toString())); + + 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) + if (hasError()) return false; RegisterScope scope(this); @@ -2239,15 +2547,18 @@ bool Codegen::visit(TaggedTemplate *ast) bool Codegen::handleTaggedTemplate(Reference base, TaggedTemplate *ast) { - if (hasError) + if (hasError()) return false; int functionObject = -1, thisObject = -1; switch (base.type) { case Reference::Member: - case Reference::Subscript: base = base.asLValue(); break; + case Reference::Subscript: + base.element = loadSubscriptForCall(base).storeOnStack().stackSlot(); + base.subscriptLoadedForCall = true; + break; case Reference::Name: break; case Reference::SuperProperty: @@ -2263,13 +2574,14 @@ bool Codegen::handleTaggedTemplate(Reference base, TaggedTemplate *ast) int templateObjectTemp = Reference::fromAccumulator(this).storeOnStack().stackSlot(); Q_UNUSED(templateObjectTemp); auto calldata = pushTemplateArgs(ast->templateLiteral); - if (hasError) + 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; } @@ -2292,7 +2604,7 @@ void Codegen::createTemplateObject(TemplateLiteral *t) bool Codegen::visit(FunctionExpression *ast) { - if (hasError) + if (hasError()) return false; TailCallBlocker blockTailCalls(this); @@ -2300,7 +2612,7 @@ bool Codegen::visit(FunctionExpression *ast) RegisterScope scope(this); int function = defineFunction(ast->name.toString(), ast, ast->formals, ast->body); - if (hasError) + if (hasError()) return false; loadClosure(function); setExprResult(Reference::fromAccumulator(this)); @@ -2310,12 +2622,32 @@ bool Codegen::visit(FunctionExpression *ast) 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); + _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: @@ -2332,12 +2664,15 @@ Codegen::Reference Codegen::referenceForName(const QString &name, bool isLhs, co 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 && m_globalNames.contains(name)) r.global = true; return r; @@ -2356,7 +2691,7 @@ void Codegen::loadClosure(int closureId) bool Codegen::visit(IdentifierExpression *ast) { - if (hasError) + if (hasError()) return false; setExprResult(referenceForName(ast->name.toString(), false, ast->firstSourceLocation())); @@ -2365,7 +2700,7 @@ bool Codegen::visit(IdentifierExpression *ast) bool Codegen::visit(NestedExpression *ast) { - if (hasError) + if (hasError()) return false; accept(ast->expression); @@ -2384,7 +2719,7 @@ void Codegen::handleConstruct(const Reference &base, ArgumentList *arguments) } auto calldata = pushArgs(arguments); - if (hasError) + if (hasError()) return; if (base.isSuper()) @@ -2414,14 +2749,14 @@ void Codegen::handleConstruct(const Reference &base, ArgumentList *arguments) bool Codegen::visit(NewExpression *ast) { - if (hasError) + if (hasError()) return false; RegisterScope scope(this); TailCallBlocker blockTailCalls(this); Reference base = expression(ast->expression); - if (hasError) + if (hasError()) return false; if (base.isSuper()) { throwSyntaxError(ast->expression->firstSourceLocation(), QStringLiteral("Cannot use new with super.")); @@ -2434,14 +2769,14 @@ bool Codegen::visit(NewExpression *ast) bool Codegen::visit(NewMemberExpression *ast) { - if (hasError) + if (hasError()) return false; RegisterScope scope(this); TailCallBlocker blockTailCalls(this); Reference base = expression(ast->base); - if (hasError) + if (hasError()) return false; if (base.isSuper()) { throwSyntaxError(ast->base->firstSourceLocation(), QStringLiteral("Cannot use new with super.")); @@ -2454,7 +2789,7 @@ bool Codegen::visit(NewMemberExpression *ast) bool Codegen::visit(NotExpression *ast) { - if (hasError) + if (hasError()) return false; TailCallBlocker blockTailCalls(this); @@ -2464,7 +2799,7 @@ bool Codegen::visit(NotExpression *ast) bool Codegen::visit(NullExpression *) { - if (hasError) + if (hasError()) return false; if (exprAccept(cx)) @@ -2477,7 +2812,7 @@ bool Codegen::visit(NullExpression *) bool Codegen::visit(NumericLiteral *ast) { - if (hasError) + if (hasError()) return false; setExprResult(Reference::fromConst(this, QV4::Encode::smallestNumber(ast->value))); @@ -2486,14 +2821,11 @@ bool Codegen::visit(NumericLiteral *ast) bool Codegen::visit(ObjectPattern *ast) { - if (hasError) + if (hasError()) return false; TailCallBlocker blockTailCalls(this); - QVector<QPair<Reference, ObjectPropertyValue>> computedProperties; - QMap<QString, ObjectPropertyValue> valueMap; - RegisterScope scope(this); QStringList members; @@ -2515,7 +2847,7 @@ bool Codegen::visit(ObjectPattern *ast) if (cname || p->type != PatternProperty::Literal) break; QString name = p->name->asString(); - uint arrayIndex = QV4::String::toArrayIndex(name); + uint arrayIndex = stringToArrayIndex(name); if (arrayIndex != UINT_MAX) break; if (members.contains(name)) @@ -2525,7 +2857,7 @@ bool Codegen::visit(ObjectPattern *ast) { RegisterScope innerScope(this); Reference value = expression(p->initializer, name); - if (hasError) + if (hasError()) return false; value.loadInAccumulator(); } @@ -2552,7 +2884,7 @@ bool Codegen::visit(ObjectPattern *ast) if (cname) { RegisterScope innerScope(this); Reference name = expression(cname->expression); - if (hasError) + if (hasError()) return false; name.loadInAccumulator(); } else { @@ -2577,12 +2909,12 @@ bool Codegen::visit(ObjectPattern *ast) FunctionExpression *f = p->initializer->asFunctionDefinition(); Q_ASSERT(f); int function = defineFunction(f->name.toString(), f, f->formals, f->body); - if (hasError) + if (hasError()) return false; Reference::fromConst(this, Encode(function)).loadInAccumulator(); } else { Reference value = expression(p->initializer); - if (hasError) + if (hasError()) return false; value.loadInAccumulator(); } @@ -2601,11 +2933,11 @@ bool Codegen::visit(ObjectPattern *ast) bool Codegen::visit(PostDecrementExpression *ast) { - if (hasError) + if (hasError()) return false; Reference expr = expression(ast->base); - if (hasError) + if (hasError()) return false; if (!expr.isLValue()) { throwReferenceError(ast->base->lastSourceLocation(), QStringLiteral("Invalid left-hand side expression in postfix operation")); @@ -2621,11 +2953,11 @@ bool Codegen::visit(PostDecrementExpression *ast) bool Codegen::visit(PostIncrementExpression *ast) { - if (hasError) + if (hasError()) return false; Reference expr = expression(ast->base); - if (hasError) + if (hasError()) return false; if (!expr.isLValue()) { throwReferenceError(ast->base->lastSourceLocation(), QStringLiteral("Invalid left-hand side expression in postfix operation")); @@ -2639,11 +2971,11 @@ bool Codegen::visit(PostIncrementExpression *ast) } bool Codegen::visit(PreDecrementExpression *ast) -{ if (hasError) +{ if (hasError()) return false; Reference expr = expression(ast->expression); - if (hasError) + if (hasError()) return false; if (!expr.isLValue()) { throwReferenceError(ast->expression->lastSourceLocation(), QStringLiteral("Prefix ++ operator applied to value that is not a reference.")); @@ -2658,11 +2990,11 @@ bool Codegen::visit(PreDecrementExpression *ast) bool Codegen::visit(PreIncrementExpression *ast) { - if (hasError) + if (hasError()) return false; Reference expr = expression(ast->expression); - if (hasError) + if (hasError()) return false; if (!expr.isLValue()) { throwReferenceError(ast->expression->lastSourceLocation(), QStringLiteral("Prefix ++ operator applied to value that is not a reference.")); @@ -2677,7 +3009,7 @@ bool Codegen::visit(PreIncrementExpression *ast) bool Codegen::visit(RegExpLiteral *ast) { - if (hasError) + if (hasError()) return false; auto r = Reference::fromStackSlot(this); @@ -2693,7 +3025,7 @@ bool Codegen::visit(RegExpLiteral *ast) bool Codegen::visit(StringLiteral *ast) { - if (hasError) + if (hasError()) return false; auto r = Reference::fromAccumulator(this); @@ -2708,7 +3040,7 @@ bool Codegen::visit(StringLiteral *ast) bool Codegen::visit(TemplateLiteral *ast) { - if (hasError) + if (hasError()) return false; TailCallBlocker blockTailCalls(this); @@ -2725,7 +3057,7 @@ bool Codegen::visit(TemplateLiteral *ast) bytecodeGenerator->addInstruction(store); Reference expr = expression(ast->expression); - if (hasError) + if (hasError()) return false; if (ast->next) { @@ -2755,22 +3087,27 @@ bool Codegen::visit(TemplateLiteral *ast) bool Codegen::visit(ThisExpression *) { - if (hasError) + if (hasError()) return false; - if (_context->isArrowFunction) { - Reference r = referenceForName(QStringLiteral("this"), false); - r.isReadonly = true; - setExprResult(r); - 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) + if (hasError()) return false; TailCallBlocker blockTailCalls(this); @@ -2780,7 +3117,7 @@ bool Codegen::visit(TildeExpression *ast) bool Codegen::visit(TrueLiteral *) { - if (hasError) + if (hasError()) return false; setExprResult(Reference::fromConst(this, QV4::Encode(true))); @@ -2789,14 +3126,14 @@ bool Codegen::visit(TrueLiteral *) bool Codegen::visit(TypeOfExpression *ast) { - if (hasError) + if (hasError()) return false; RegisterScope scope(this); TailCallBlocker blockTailCalls(this); Reference expr = expression(ast->expression); - if (hasError) + if (hasError()) return false; if (expr.type == Reference::Name) { @@ -2816,7 +3153,7 @@ bool Codegen::visit(TypeOfExpression *ast) bool Codegen::visit(UnaryMinusExpression *ast) { - if (hasError) + if (hasError()) return false; TailCallBlocker blockTailCalls(this); @@ -2826,7 +3163,7 @@ bool Codegen::visit(UnaryMinusExpression *ast) bool Codegen::visit(UnaryPlusExpression *ast) { - if (hasError) + if (hasError()) return false; TailCallBlocker blockTailCalls(this); @@ -2836,7 +3173,7 @@ bool Codegen::visit(UnaryPlusExpression *ast) bool Codegen::visit(VoidExpression *ast) { - if (hasError) + if (hasError()) return false; RegisterScope scope(this); @@ -2849,7 +3186,7 @@ bool Codegen::visit(VoidExpression *ast) bool Codegen::visit(FunctionDeclaration * ast) { - if (hasError) + if (hasError()) return false; // no need to block tail calls: the function body isn't visited here. @@ -2868,10 +3205,21 @@ bool Codegen::visit(YieldExpression *ast) 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) + if (hasError()) return false; Reference acc = Reference::fromAccumulator(this); @@ -2902,15 +3250,15 @@ bool Codegen::visit(YieldExpression *ast) Instruction::IteratorNextForYieldStar next; next.object = lhsValue.stackSlot(); next.iterator = iterator.stackSlot(); - bytecodeGenerator->addInstruction(next); - - BytecodeGenerator::Jump done = bytecodeGenerator->jumpTrue(); + BytecodeGenerator::Jump done = bytecodeGenerator->addJumpInstruction(next); bytecodeGenerator->jumpNotUndefined().link(loop); + lhsValue.loadInAccumulator(); emitReturn(acc); done.link(); + bytecodeGenerator->checkException(); lhsValue.loadInAccumulator(); setExprResult(acc); @@ -2956,8 +3304,7 @@ static bool endsWithReturn(Module *module, Node *node) return false; } -int Codegen::defineFunction(const QString &name, AST::Node *ast, - AST::FormalParameterList *formals, +int Codegen::defineFunction(const QString &name, AST::Node *ast, AST::FormalParameterList *formals, AST::StatementList *body) { enterContext(ast); @@ -2968,7 +3315,7 @@ int Codegen::defineFunction(const QString &name, AST::Node *ast, _context->name = name.isEmpty() ? currentExpr().result().name : name; _module->functions.append(_context); - _context->functionIndex = _module->functions.count() - 1; + _context->functionIndex = _module->functions.size() - 1; Context *savedFunctionContext = _functionContext; _functionContext = _context; @@ -2977,7 +3324,7 @@ int Codegen::defineFunction(const QString &name, AST::Node *ast, if (_context->contextType == ContextType::Global || _context->contextType == ContextType::ScriptImportedByQML) { _module->blocks.append(_context); - _context->blockIndex = _module->blocks.count() - 1; + _context->blockIndex = _module->blocks.size() - 1; } if (_module->debugMode) // allow the debugger to see overwritten arguments _context->argumentsCanEscape = true; @@ -2988,9 +3335,10 @@ int Codegen::defineFunction(const QString &name, AST::Node *ast, // 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 && body->statement && cast<ExpressionStatement *>(body->statement) && cast<FunctionExpression *>(cast<ExpressionStatement *>(body->statement)->expression); + _context->returnsClosure = body && cast<ExpressionStatement *>(body->statement) + && cast<FunctionExpression *>(cast<ExpressionStatement *>(body->statement)->expression); - BytecodeGenerator bytecode(_context->line, _module->debugMode); + BytecodeGenerator bytecode(_context->line, _module->debugMode, storeSourceLocations); BytecodeGenerator *savedBytecodeGenerator; savedBytecodeGenerator = bytecodeGenerator; bytecodeGenerator = &bytecode; @@ -3002,7 +3350,8 @@ int Codegen::defineFunction(const QString &name, AST::Node *ast, functionEndsWithReturn = endsWithReturn(_module, body); // reserve the js stack frame (Context & js Function & accumulator) - bytecodeGenerator->newRegisterArray(sizeof(CallData)/sizeof(Value) - 1 + _context->arguments.size()); + bytecodeGenerator->newRegisterArray( + sizeof(CallData) / sizeof(StaticValue) - 1 + _context->arguments.size()); bool _inFormalParameterList = false; qSwap(_inFormalParameterList, inFormalParameterList); @@ -3016,7 +3365,7 @@ int Codegen::defineFunction(const QString &name, AST::Node *ast, // register the lexical scope for global code if (!_context->parent && _context->requiresExecutionContext) { _module->blocks.append(_context); - _context->blockIndex = _module->blocks.count() - 1; + _context->blockIndex = _module->blocks.size() - 1; } TailCallBlocker maybeBlockTailCalls(this, _context->canHaveTailCalls()); @@ -3048,7 +3397,7 @@ int Codegen::defineFunction(const QString &name, AST::Node *ast, } else { if (e->bindingTarget || e->initializer) { initializeAndDestructureBindingElement(e, arg); - if (hasError) + if (hasError()) break; } } @@ -3064,7 +3413,7 @@ int Codegen::defineFunction(const QString &name, AST::Node *ast, statementList(body); - if (!hasError) { + if (!hasError()) { bytecodeGenerator->setLocation(ast->lastSourceLocation()); _context->emitBlockFooter(this); @@ -3090,8 +3439,9 @@ int Codegen::defineFunction(const QString &name, AST::Node *ast, if (showCode) { qDebug() << "=== Bytecode for" << _context->name << "strict mode" << _context->isStrict << "register count" << _context->registerCountInFunction << "implicit return" << requiresReturnValue; - QV4::Moth::dumpBytecode(_context->code, _context->locals.size(), _context->arguments.size(), - _context->line, _context->lineNumberMapping); + qDebug().noquote() << QV4::Moth::dumpBytecode( + _context->code, _context->locals.size(), _context->arguments.size(), + _context->line, _context->lineAndStatementNumberMapping); qDebug(); } } @@ -3111,7 +3461,7 @@ int Codegen::defineFunction(const QString &name, AST::Node *ast, bool Codegen::visit(Block *ast) { - if (hasError) + if (hasError()) return false; RegisterScope scope(this); @@ -3123,7 +3473,7 @@ bool Codegen::visit(Block *ast) bool Codegen::visit(BreakStatement *ast) { - if (hasError) + if (hasError()) return false; // no need to block tail calls here: children aren't visited @@ -3148,7 +3498,7 @@ bool Codegen::visit(BreakStatement *ast) bool Codegen::visit(ContinueStatement *ast) { - if (hasError) + if (hasError()) return false; // no need to block tail calls here: children aren't visited @@ -3181,28 +3531,37 @@ bool Codegen::visit(DebuggerStatement *) bool Codegen::visit(DoWhileStatement *ast) { - if (hasError) + if (hasError()) return false; RegisterScope scope(this); - BytecodeGenerator::Label body = bytecodeGenerator->label(); + 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(); - - TailCallBlocker blockTailCalls(this); - if (!AST::cast<FalseLiteral *>(ast->expression)) { - if (AST::cast<TrueLiteral *>(ast->expression)) - bytecodeGenerator->jump().link(body); - else - condition(ast->expression, &body, &end, false); + 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(); @@ -3217,7 +3576,7 @@ bool Codegen::visit(EmptyStatement *) bool Codegen::visit(ExpressionStatement *ast) { - if (hasError) + if (hasError()) return false; RegisterScope scope(this); @@ -3225,7 +3584,7 @@ bool Codegen::visit(ExpressionStatement *ast) if (requiresReturnValue) { Reference e = expression(ast->expression); - if (hasError) + if (hasError()) return false; (void) e.storeOnStack(_returnAddress); } else { @@ -3236,14 +3595,13 @@ bool Codegen::visit(ExpressionStatement *ast) bool Codegen::visit(ForEachStatement *ast) { - if (hasError) + if (hasError()) return false; RegisterScope scope(this); TailCallBlocker blockTailCalls(this); Reference iterator = Reference::fromStackSlot(this); - Reference iteratorDone = Reference::fromConst(this, Encode(false)).storeOnStack(); Reference lhsValue = Reference::fromStackSlot(this); // There should be a temporal block, so that variables declared in lhs shadow outside vars. @@ -3252,7 +3610,7 @@ bool Codegen::visit(ForEachStatement *ast) RegisterScope innerScope(this); ControlFlowBlock controlFlow(this, ast); Reference expr = expression(ast->expression); - if (hasError) + if (hasError()) return false; expr.loadInAccumulator(); @@ -3264,20 +3622,28 @@ bool Codegen::visit(ForEachStatement *ast) BytecodeGenerator::Label in = bytecodeGenerator->newLabel(); BytecodeGenerator::Label end = bytecodeGenerator->newLabel(); + BytecodeGenerator::Label done; { - auto cleanup = [ast, iterator, iteratorDone, this]() { - if (ast->type == ForEachType::Of) { + std::function<void()> cleanup; + if (ast->type == ForEachType::Of) { + done = bytecodeGenerator->newLabel(); + cleanup = [iterator, this, done]() { iterator.loadInAccumulator(); Instruction::IteratorClose close; - close.done = iteratorDone.stackSlot(); bytecodeGenerator->addInstruction(close); - } - }; - ControlFlowLoop flow(this, &end, &in, cleanup); - bytecodeGenerator->jump().link(in); - - BytecodeGenerator::Label body = bytecodeGenerator->label(); + 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 { @@ -3290,7 +3656,7 @@ bool Codegen::visit(ForEachStatement *ast) destructurePattern(p, lhsValue); } else { Reference lhs = expression(e); - if (hasError) + if (hasError()) goto error; if (!lhs.isLValue()) { throwReferenceError(e->firstSourceLocation(), QStringLiteral("Invalid left-hand side expression for 'in' expression")); @@ -3302,26 +3668,23 @@ bool Codegen::visit(ForEachStatement *ast) } } else if (PatternElement *p = AST::cast<PatternElement *>(ast->lhs)) { initializeAndDestructureBindingElement(p, lhsValue, /*isDefinition =*/ true); - if (hasError) + 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: - in.link(); - iterator.loadInAccumulator(); - Instruction::IteratorNext next; - next.value = lhsValue.stackSlot(); - next.done = iteratorDone.stackSlot(); - bytecodeGenerator->addInstruction(next); - bytecodeGenerator->addJumpInstruction(Instruction::JumpFalse()).link(body); 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. } @@ -3331,7 +3694,7 @@ bool Codegen::visit(ForEachStatement *ast) bool Codegen::visit(ForStatement *ast) { - if (hasError) + if (hasError()) return false; RegisterScope scope(this); @@ -3350,7 +3713,7 @@ bool Codegen::visit(ForStatement *ast) BytecodeGenerator::Label end = bytecodeGenerator->newLabel(); ControlFlowLoop flow(this, &end, &step); - + bytecodeGenerator->addLoopStart(cond); condition(ast->condition, &body, &end, true); body.link(); @@ -3365,6 +3728,7 @@ bool Codegen::visit(ForStatement *ast) bytecodeGenerator->addInstruction(clone); } statement(ast->expression); + bytecodeGenerator->checkException(); bytecodeGenerator->jump().link(cond); end.link(); @@ -3374,7 +3738,7 @@ bool Codegen::visit(ForStatement *ast) bool Codegen::visit(IfStatement *ast) { - if (hasError) + if (hasError()) return false; RegisterScope scope(this); @@ -3406,7 +3770,7 @@ bool Codegen::visit(IfStatement *ast) bool Codegen::visit(LabelledStatement *ast) { - if (hasError) + if (hasError()) return false; RegisterScope scope(this); @@ -3454,7 +3818,7 @@ void Codegen::emitReturn(const Reference &expr) bool Codegen::visit(ReturnStatement *ast) { - if (hasError) + if (hasError()) return false; if (_functionContext->contextType != ContextType::Function && _functionContext->contextType != ContextType::Binding) { @@ -3464,7 +3828,7 @@ bool Codegen::visit(ReturnStatement *ast) Reference expr; if (ast->expression) { expr = expression(ast->expression); - if (hasError) + if (hasError()) return false; } else { expr = Reference::fromConst(this, Encode::undefined()); @@ -3477,7 +3841,7 @@ bool Codegen::visit(ReturnStatement *ast) bool Codegen::visit(SwitchStatement *ast) { - if (hasError) + if (hasError()) return false; if (requiresReturnValue) @@ -3490,7 +3854,7 @@ bool Codegen::visit(SwitchStatement *ast) BytecodeGenerator::Label switchEnd = bytecodeGenerator->newLabel(); Reference lhs = expression(ast->expression); - if (hasError) + if (hasError()) return false; lhs = lhs.storeOnStack(); @@ -3509,7 +3873,7 @@ bool Codegen::visit(SwitchStatement *ast) for (CaseClauses *it = ast->block->clauses; it; it = it->next) { CaseClause *clause = it->clause; Reference rhs = expression(clause->expression); - if (hasError) + if (hasError()) return false; rhs.loadInAccumulator(); bytecodeGenerator->jumpStrictEqual(lhs.stackSlot(), blockMap.value(clause)); @@ -3518,7 +3882,7 @@ bool Codegen::visit(SwitchStatement *ast) for (CaseClauses *it = ast->block->moreClauses; it; it = it->next) { CaseClause *clause = it->clause; Reference rhs = expression(clause->expression); - if (hasError) + if (hasError()) return false; rhs.loadInAccumulator(); bytecodeGenerator->jumpStrictEqual(lhs.stackSlot(), blockMap.value(clause)); @@ -3564,14 +3928,14 @@ bool Codegen::visit(SwitchStatement *ast) bool Codegen::visit(ThrowStatement *ast) { - if (hasError) + if (hasError()) return false; RegisterScope scope(this); TailCallBlocker blockTailCalls(this); Reference expr = expression(ast->expression); - if (hasError) + if (hasError()) return false; expr.loadInAccumulator(); @@ -3595,7 +3959,8 @@ void Codegen::handleTryCatch(TryStatement *ast) void Codegen::handleTryFinally(TryStatement *ast) { RegisterScope scope(this); - ControlFlowFinally finally(this, ast->finallyExpression); + 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) { @@ -3608,7 +3973,7 @@ void Codegen::handleTryFinally(TryStatement *ast) bool Codegen::visit(TryStatement *ast) { - if (hasError) + if (hasError()) return false; RegisterScope scope(this); @@ -3624,7 +3989,7 @@ bool Codegen::visit(TryStatement *ast) bool Codegen::visit(VariableStatement *ast) { - if (hasError) + if (hasError()) return false; variableDeclarationList(ast->declarations); @@ -3633,23 +3998,26 @@ bool Codegen::visit(VariableStatement *ast) bool Codegen::visit(WhileStatement *ast) { - if (hasError) + if (hasError()) return false; if (AST::cast<FalseLiteral *>(ast->expression)) return false; RegisterScope scope(this); - TailCallBlocker blockTailCalls(this); BytecodeGenerator::Label start = bytecodeGenerator->newLabel(); BytecodeGenerator::Label end = bytecodeGenerator->newLabel(); BytecodeGenerator::Label cond = bytecodeGenerator->label(); ControlFlowLoop flow(this, &end, &cond); + bytecodeGenerator->addLoopStart(cond); - if (!AST::cast<TrueLiteral *>(ast->expression)) + bytecodeGenerator->checkException(); + + if (!AST::cast<TrueLiteral *>(ast->expression)) { + TailCallBlocker blockTailCalls(this); condition(ast->expression, &start, &end, true); - blockTailCalls.unblock(); + } start.link(); statement(ast->statement); @@ -3662,14 +4030,14 @@ bool Codegen::visit(WhileStatement *ast) bool Codegen::visit(WithStatement *ast) { - if (hasError) + if (hasError()) return false; RegisterScope scope(this); TailCallBlocker blockTailCalls(this); Reference src = expression(ast->expression); - if (hasError) + 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(); @@ -3739,52 +4107,80 @@ bool Codegen::throwSyntaxErrorOnEvalOrArgumentsInStrictMode(const Reference &r, return isArgOrEval; } -void Codegen::throwSyntaxError(const SourceLocation &loc, const QString &detail) +void Codegen::throwError(ErrorType errorType, const SourceLocation &loc, const QString &detail) { - if (hasError) + if (hasError()) return; - hasError = true; - QQmlJS::DiagnosticMessage error; - error.message = detail; - error.loc = loc; - _errors << error; + _errorType = errorType; + _error.message = detail; + _error.loc = loc; } -void Codegen::throwReferenceError(const SourceLocation &loc, const QString &detail) +void Codegen::throwSyntaxError(const SourceLocation &loc, const QString &detail) { - if (hasError) - return; - - hasError = true; - QQmlJS::DiagnosticMessage error; - error.message = detail; - error.loc = loc; - _errors << error; + throwError(SyntaxError, loc, detail); } -QList<QQmlJS::DiagnosticMessage> Codegen::errors() const +void Codegen::throwReferenceError(const SourceLocation &loc, const QString &detail) { - return _errors; + throwError(ReferenceError, loc, detail); } -QQmlRefPointer<CompiledData::CompilationUnit> Codegen::generateCompilationUnit(bool generateUnitData) +QQmlJS::DiagnosticMessage Codegen::error() const { - CompiledData::Unit *unitData = nullptr; - if (generateUnitData) - unitData = jsUnitGenerator->generateUnit(); - CompiledData::CompilationUnit *compilationUnit = new CompiledData::CompilationUnit(unitData); + return _error; +} - QQmlRefPointer<CompiledData::CompilationUnit> unit; - unit.adopt(compilationUnit); - return unit; +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<CompiledData::CompilationUnit> Codegen::createUnitForLoading() +QQmlRefPointer<QV4::CompiledData::CompilationUnit> Codegen::compileModule( + bool debugMode, const QString &url, const QString &sourceCode, + const QDateTime &sourceTimeStamp, QList<QQmlJS::DiagnosticMessage> *diagnostics) { - QQmlRefPointer<CompiledData::CompilationUnit> result; - result.adopt(new CompiledData::CompilationUnit); - return result; + 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(debugMode); + compilerModule.unitFlags |= CompiledData::Unit::IsESModule; + compilerModule.sourceTimeStamp = sourceTimeStamp; + JSUnitGenerator jsGenerator(&compilerModule); + Codegen cg(&jsGenerator, /*strictMode*/true); + cg.generateFromModule(url, url, sourceCode, moduleNode, &compilerModule); + if (cg.hasError()) { + if (diagnostics) + *diagnostics << cg.error(); + return QQmlRefPointer<CompiledData::CompilationUnit>(); + } + + return cg.generateCompilationUnit(); } class Codegen::VolatileMemoryLocationScanner: protected QQmlJS::AST::Visitor @@ -3868,14 +4264,14 @@ public: } private: - void collectIdentifiers(QVector<QStringView> &ids, AST::Node *node) { + void collectIdentifiers(QList<QStringView> &ids, AST::Node *node) { class Collector: public QQmlJS::AST::Visitor { private: - QVector<QStringView> &ids; + QList<QStringView> &ids; VolatileMemoryLocationScanner *parent; public: - Collector(QVector<QStringView> &ids, VolatileMemoryLocationScanner *parent) : + Collector(QList<QStringView> &ids, VolatileMemoryLocationScanner *parent) : QQmlJS::AST::Visitor(parent->recursionDepth()), ids(ids), parent(parent) {} @@ -3900,34 +4296,11 @@ Codegen::VolatileMemoryLocations Codegen::scanVolatileMemoryLocations(AST::Node return scanner.scan(ast); } - -#ifndef V4_BOOTSTRAP - -QList<QQmlError> Codegen::qmlErrors() const +QUrl Codegen::url() const { - QList<QQmlError> qmlErrors; - - // Short circuit to avoid costly (de)heap allocation of QUrl if there are no errors. - if (_errors.size() == 0) - return qmlErrors; - - qmlErrors.reserve(_errors.size()); - - QUrl url(_fileNameIsUrl ? QUrl(_module->fileName) : QUrl::fromLocalFile(_module->fileName)); - for (const QQmlJS::DiagnosticMessage &msg: qAsConst(_errors)) { - QQmlError e; - e.setUrl(url); - e.setLine(msg.loc.startLine); - e.setColumn(msg.loc.startColumn); - e.setDescription(msg.message); - qmlErrors << e; - } - - return qmlErrors; + return QUrl(_fileNameIsUrl ? QUrl(_module->fileName) : QUrl::fromLocalFile(_module->fileName)); } -#endif // V4_BOOTSTRAP - bool Codegen::RValue::operator==(const RValue &other) const { switch (type) { @@ -3993,7 +4366,9 @@ bool Codegen::Reference::operator==(const Codegen::Reference &other) const case Member: return propertyBase == other.propertyBase && propertyNameIndex == other.propertyNameIndex; case Subscript: - return elementBase == other.elementBase && elementSubscript == other.elementSubscript; + return elementBase == other.elementBase && other.subscriptLoadedForCall + ? (subscriptLoadedForCall && element == other.element) + : (!subscriptLoadedForCall && elementSubscript == other.elementSubscript); case Import: return index == other.index; case Const: @@ -4026,7 +4401,7 @@ Codegen::Reference Codegen::Reference::asLValue() const case Accumulator: Q_UNREACHABLE(); case Super: - codegen->throwSyntaxError(AST::SourceLocation(), QStringLiteral("Super lvalues not implemented.")); + codegen->throwSyntaxError(SourceLocation(), QStringLiteral("Super lvalues not implemented.")); return *this; case Member: if (!propertyBase.isStackSlot()) { @@ -4110,6 +4485,28 @@ Codegen::Reference Codegen::Reference::doStoreOnStack(int slotIndex) const 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); +} + Codegen::Reference Codegen::Reference::storeRetainAccumulator() const { if (storeWipesAccumulator()) { @@ -4146,24 +4543,21 @@ bool Codegen::Reference::storeWipesAccumulator() const void Codegen::Reference::storeAccumulator() const { + if (throwsReferenceError) { + codegen->generateThrowException(QStringLiteral("ReferenceError"), + name + QStringLiteral(" is not defined")); + return; + } + if (isReferenceToConst) { // throw a type error - RegisterScope scope(codegen); - Reference r = codegen->referenceForName(QStringLiteral("TypeError"), false); - r = r.storeOnStack(); - Instruction::Construct construct; - construct.func = r.stackSlot(); - construct.argc = 0; - construct.argv = 0; - codegen->bytecodeGenerator->addInstruction(construct); - Instruction::ThrowException throwException; - codegen->bytecodeGenerator->addInstruction(throwException); + codegen->generateThrowException(QStringLiteral("TypeError")); return; } + switch (type) { case Super: - Q_UNREACHABLE(); - return; + Q_UNREACHABLE_RETURN(); case SuperProperty: Instruction::StoreSuperProperty store; store.property = property.stackSlot(); @@ -4201,7 +4595,7 @@ void Codegen::Reference::storeAccumulator() const } } return; case Member: - if (!disable_lookups && codegen->useFastLookups) { + if (codegen->useFastLookups) { Instruction::SetLookup store; store.base = propertyBase.stackSlot(); store.index = codegen->registerSetterLookup(propertyNameIndex); @@ -4231,30 +4625,13 @@ void Codegen::Reference::storeAccumulator() const void Codegen::Reference::loadInAccumulator() const { - auto tdzCheck = [this](bool requiresCheck){ - if (!requiresCheck) - return; - Instruction::DeadTemporalZoneCheck check; - check.name = codegen->registerString(name); - codegen->bytecodeGenerator->addInstruction(check); - }; - auto tdzCheckStackSlot = [this, tdzCheck](Moth::StackSlot slot, bool requiresCheck){ - if (!requiresCheck) - return; - Instruction::LoadReg load; - load.reg = slot; - codegen->bytecodeGenerator->addInstruction(load); - tdzCheck(true); - }; - switch (type) { case Accumulator: return; case Super: - Q_UNREACHABLE(); - return; + Q_UNREACHABLE_RETURN(); case SuperProperty: - tdzCheckStackSlot(property, subscriptRequiresTDZCheck); + tdzCheckStackSlot(property, subscriptRequiresTDZCheck, false); Instruction::LoadSuperProperty load; load.property = property.stackSlot(); codegen->bytecodeGenerator->addInstruction(load); @@ -4275,10 +4652,10 @@ QT_WARNING_DISABLE_GCC("-Wmaybe-uninitialized") // the loads below are empty str Instruction::LoadUndefined load; codegen->bytecodeGenerator->addInstruction(load); } else { - Value p = Value::fromReturnedValue(constant); + StaticValue p = StaticValue::fromReturnedValue(constant); if (p.isNumber()) { double d = p.asDouble(); - int i = static_cast<int>(d); + int i = QJSNumberCoercion::toInteger(d); if (d == i && (d != 0 || !std::signbit(d))) { if (!i) { Instruction::LoadZero load; @@ -4286,7 +4663,7 @@ QT_WARNING_DISABLE_GCC("-Wmaybe-uninitialized") // the loads below are empty str return; } Instruction::LoadInt load; - load.value = Value::fromReturnedValue(constant).toInt32(); + load.value = StaticValue::fromReturnedValue(constant).toInt32(); codegen->bytecodeGenerator->addInstruction(load); return; } @@ -4301,7 +4678,7 @@ QT_WARNING_POP Instruction::LoadReg load; load.reg = stackSlot(); codegen->bytecodeGenerator->addInstruction(load); - tdzCheck(requiresTDZCheck); + tdzCheck(requiresTDZCheck, throwsReferenceError); } return; case ScopedLocal: { if (!scope) { @@ -4314,7 +4691,7 @@ QT_WARNING_POP load.scope = scope; codegen->bytecodeGenerator->addInstruction(load); } - tdzCheck(requiresTDZCheck); + tdzCheck(requiresTDZCheck, throwsReferenceError); return; } case Name: @@ -4332,14 +4709,20 @@ QT_WARNING_POP return; } } - if (!disable_lookups && global) { + + if (sourceLocation.isValid()) + codegen->bytecodeGenerator->setLocation(sourceLocation); + + if (global) { if (qmlGlobal) { Instruction::LoadQmlContextPropertyLookup load; - load.index = codegen->registerQmlContextPropertyGetterLookup(nameAsIndex()); + load.index = codegen->registerQmlContextPropertyGetterLookup( + nameAsIndex(), JSUnitGenerator::LookupForStorage); codegen->bytecodeGenerator->addInstruction(load); } else { Instruction::LoadGlobalLookup load; - load.index = codegen->registerGlobalGetterLookup(nameAsIndex()); + load.index = codegen->registerGlobalGetterLookup( + nameAsIndex(), JSUnitGenerator::LookupForStorage); codegen->bytecodeGenerator->addInstruction(load); } } else { @@ -4350,27 +4733,44 @@ QT_WARNING_POP return; case Member: propertyBase.loadInAccumulator(); - tdzCheck(requiresTDZCheck); - if (!disable_lookups && codegen->useFastLookups) { - Instruction::GetLookup load; - load.index = codegen->registerGetterLookup(propertyNameIndex); - codegen->bytecodeGenerator->addInstruction(load); + 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 { - Instruction::LoadProperty load; - load.name = propertyNameIndex; - codegen->bytecodeGenerator->addInstruction(load); + 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); + tdzCheck(requiresTDZCheck, throwsReferenceError); } return; case Subscript: { - tdzCheckStackSlot(elementBase, requiresTDZCheck); + tdzCheckStackSlot(elementBase, requiresTDZCheck, throwsReferenceError); elementSubscript.loadInAccumulator(); - tdzCheck(subscriptRequiresTDZCheck); + tdzCheck(subscriptRequiresTDZCheck, false); Instruction::LoadElement load; load.base = elementBase; codegen->bytecodeGenerator->addInstruction(load); @@ -4380,3 +4780,5 @@ QT_WARNING_POP } Q_UNREACHABLE(); } + +QT_END_NAMESPACE |