diff options
Diffstat (limited to 'src/qml/compiler/qv4codegen.cpp')
-rw-r--r-- | src/qml/compiler/qv4codegen.cpp | 901 |
1 files changed, 630 insertions, 271 deletions
diff --git a/src/qml/compiler/qv4codegen.cpp b/src/qml/compiler/qv4codegen.cpp index c43ea64e2e..25831eab73 100644 --- a/src/qml/compiler/qv4codegen.cpp +++ b/src/qml/compiler/qv4codegen.cpp @@ -1,49 +1,13 @@ -/**************************************************************************** -** -** 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/qqmljslexer_p.h> @@ -57,20 +21,33 @@ #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) { @@ -90,14 +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) +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(); @@ -391,6 +393,7 @@ void Codegen::statement(Statement *ast) { RegisterScope scope(this); + bytecodeGenerator->incrementStatement(); bytecodeGenerator->setLocation(ast->firstSourceLocation()); VolatileMemoryLocations vLocs = scanVolatileMemoryLocations(ast); @@ -406,6 +409,7 @@ void Codegen::statement(ExpressionNode *ast) } else { RegisterScope scope(this); + bytecodeGenerator->incrementStatement(); pushExpr(Result(nx)); VolatileMemoryLocations vLocs = scanVolatileMemoryLocations(ast); qSwap(_volatileMemoryLocations, vLocs); @@ -708,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; @@ -718,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; } } } @@ -775,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) @@ -895,68 +915,62 @@ bool Codegen::visit(TypeAnnotation *ast) bool Codegen::visit(StatementList *) { - Q_UNREACHABLE(); - return false; + 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) @@ -1182,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. @@ -1202,24 +1215,23 @@ 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(); @@ -1252,13 +1264,23 @@ bool Codegen::visit(ArrayMemberExpression *ast) if (hasError()) return false; + const bool isTailOfChain = traverseOptionalChain(ast); + TailCallBlocker blockTailCalls(this); Reference base = expression(ast->base); + + 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(); @@ -1268,17 +1290,32 @@ bool Codegen::visit(ArrayMemberExpression *ast) QString s = str->value.toString(); 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()) return false; - setExprResult(Reference::fromSubscript(base, index)); + + optionalChainFinalizer(Reference::fromSubscript(base, index), isTailOfChain); return false; } @@ -1371,6 +1408,34 @@ 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); @@ -1446,7 +1511,7 @@ bool Codegen::visit(BinaryExpression *ast) if (hasError()) return false; - binopHelper(baseOp(ast->op), tempLeft, right).loadInAccumulator(); + binopHelper(ast, baseOp(ast->op), tempLeft, right).loadInAccumulator(); setExprResult(left.storeRetainAccumulator()); break; @@ -1459,12 +1524,13 @@ bool Codegen::visit(BinaryExpression *ast) Reference right = expression(ast->right); 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: @@ -1493,18 +1559,20 @@ bool Codegen::visit(BinaryExpression *ast) 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: { left = left.storeOnStack(); @@ -1661,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(); @@ -1872,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()) 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()) 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: @@ -1901,9 +1994,23 @@ 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()) return false; @@ -1915,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); @@ -2022,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) @@ -2057,8 +2192,10 @@ Codegen::Arguments Codegen::pushArgs(ArgumentList *args) 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; @@ -2131,12 +2268,18 @@ bool Codegen::visit(DeleteExpression *ast) if (hasError()) return false; + const bool isTailOfChain = traverseOptionalChain(ast); + RegisterScope scope(this); TailCallBlocker blockTailCalls(this); Reference expr = expression(ast->expression); if (hasError()) return false; + const bool chainActuallyHasOptionals = m_optionalChainsStates.top().actuallyHasOptionals; + if (chainActuallyHasOptionals) + Q_ASSERT(expr.type == Reference::Member || expr.type == Reference::Subscript); + switch (expr.type) { case Reference::SuperProperty: // ### this should throw a reference error at runtime. @@ -2144,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) { @@ -2167,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); @@ -2176,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: @@ -2197,6 +2360,10 @@ bool Codegen::visit(DeleteExpression *ast) return false; } +void Codegen::endVisit(DeleteExpression *ast) { + m_seenOptionalChainNodes.remove(ast); +} + bool Codegen::visit(FalseLiteral *) { if (hasError()) @@ -2215,12 +2382,115 @@ bool Codegen::visit(SuperLiteral *) return false; } +bool Codegen::traverseOptionalChain(Node *node) +{ + if (m_seenOptionalChainNodes.contains(node)) + return false; + + const auto isOptionalChainableNode = [](const Node *node) { + return node->kind == Node::Kind_FieldMemberExpression || + node->kind == Node::Kind_CallExpression || + node->kind == Node::Kind_ArrayMemberExpression || + node->kind == Node::Kind_DeleteExpression; + }; + m_optionalChainsStates.emplace(); + while (isOptionalChainableNode(node)) { + m_seenOptionalChainNodes.insert(node); + + switch (node->kind) { + case Node::Kind_FieldMemberExpression: { + auto *fme = AST::cast<FieldMemberExpression *>(node); + m_optionalChainsStates.top().actuallyHasOptionals |= fme->isOptional; + node = fme->base; + break; + } + case Node::Kind_CallExpression: { + auto *ce = AST::cast<CallExpression *>(node); + m_optionalChainsStates.top().actuallyHasOptionals |= ce->isOptional; + node = ce->base; + break; + } + case Node::Kind_ArrayMemberExpression: { + auto *ame = AST::cast<ArrayMemberExpression *>(node); + m_optionalChainsStates.top().actuallyHasOptionals |= ame->isOptional; + node = ame->base; + break; + } + case Node::Kind_DeleteExpression: + node = AST::cast<DeleteExpression *>(node)->expression; + break; + default: + Q_UNREACHABLE(); + } + } + + return true; +} + +void Codegen::optionalChainFinalizer(const Reference &expressionResult, bool tailOfChain, + bool isDeleteExpression) +{ + auto &chainState = m_optionalChainsStates.top(); + if (!tailOfChain) { + setExprResult(expressionResult); + return; + } else if (!chainState.actuallyHasOptionals) { + setExprResult(expressionResult); + m_optionalChainsStates.pop(); + return; + } + + auto savedBaseSlot = -1; + if (expressionResult.type == Reference::Member) + savedBaseSlot = expressionResult.propertyBase.storeOnStack().stackSlot(); + expressionResult.loadInAccumulator(); + + std::optional<Moth::BytecodeGenerator::Jump> jumpToDone; + if (!isDeleteExpression) // Delete expressions always return true, avoid the extra jump + jumpToDone.emplace(bytecodeGenerator->jump()); + + for (auto &jump : chainState.jumpsToPatch) + jump.link(); + + if (isDeleteExpression) + bytecodeGenerator->addInstruction(Instruction::LoadTrue()); + else + bytecodeGenerator->addInstruction(Instruction::LoadUndefined()); + + if (jumpToDone.has_value()) + jumpToDone.value().link(); + + auto ref = Reference::fromAccumulator(this); + if (expressionResult.type == Reference::Member) { + /* Because the whole optional chain is handled at once with a chain finalizer instead of + * instruction by instruction, the result of the chain (either undefined or the result of + * the optional operation) is stored in the accumulator. This works fine except for one + * edge case where the `this` context is required in a call + * (see tst_ecmascripttests: language/expressions/optional-chaining/optional-call-preserves-this.js). + * + * In order to preserve the `this` context in the call, the call base and the property name + * index need to be available as with a Member reference. However, since the result must be + * in the accumulator the resulting reference is of type Accumulator. Therefore, the call + * base and the property name index are `glued` to an accumulator reference to make it work + * when deciding which call instruction to use later on. + */ + ref.hasSavedCallBaseSlot = true; + ref.savedCallBaseSlot = savedBaseSlot; + ref.savedCallPropertyNameIndex = expressionResult.propertyNameIndex; + } + setExprResult(ref); + m_optionalChainsStates.pop(); +} + bool Codegen::visit(FieldMemberExpression *ast) { if (hasError()) return false; + const bool isTailOfChain = traverseOptionalChain(ast); + TailCallBlocker blockTailCalls(this); + if (AST::IdentifierExpression *id = AST::cast<AST::IdentifierExpression *>(ast->base)) { if (id->name == QLatin1String("new")) { // new.target @@ -2230,16 +2500,18 @@ 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()) return false; if (base.isSuper()) { @@ -2247,13 +2519,23 @@ bool Codegen::visit(FieldMemberExpression *ast) 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()) @@ -2271,9 +2553,12 @@ bool Codegen::handleTaggedTemplate(Reference base, TaggedTemplate *ast) 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: @@ -2296,6 +2581,7 @@ bool Codegen::handleTaggedTemplate(Reference base, TaggedTemplate *ast) --calldata.argv; handleCall(base, calldata, functionObject, thisObject); + setExprResult(Reference::fromAccumulator(this)); return false; } @@ -2336,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: @@ -2358,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; @@ -2781,12 +3090,17 @@ bool Codegen::visit(ThisExpression *) 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; } @@ -2891,6 +3205,17 @@ 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()); @@ -2925,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); @@ -2979,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); @@ -2991,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; @@ -3000,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; @@ -3011,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; @@ -3040,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()); @@ -3114,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(); } } @@ -3276,7 +3602,6 @@ bool Codegen::visit(ForEachStatement *ast) 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. @@ -3297,25 +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); + 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(); - next.done = iteratorDone.stackSlot(); - bytecodeGenerator->addInstruction(next); - bytecodeGenerator->addJumpInstruction(Instruction::JumpTrue()).link(end); + bytecodeGenerator->addJumpInstruction(next).link(done); // each iteration gets it's own context, as per spec { @@ -3631,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) { @@ -3785,8 +4114,7 @@ void Codegen::throwError(ErrorType errorType, const SourceLocation &loc, const Q _errorType = errorType; _error.message = detail; - _error.line = loc.startLine; - _error.column = loc.startColumn; + _error.loc = loc; } void Codegen::throwSyntaxError(const SourceLocation &loc, const QString &detail) @@ -3804,14 +4132,16 @@ QQmlJS::DiagnosticMessage Codegen::error() const return _error; } -QV4::CompiledData::CompilationUnit Codegen::generateCompilationUnit( +QQmlRefPointer<QV4::CompiledData::CompilationUnit> Codegen::generateCompilationUnit( bool generateUnitData) { - return QV4::CompiledData::CompilationUnit( - generateUnitData ? jsUnitGenerator->generateUnit() : nullptr); + return QQmlRefPointer<QV4::CompiledData::CompilationUnit>( + new QV4::CompiledData::CompilationUnit( + generateUnitData ? jsUnitGenerator->generateUnit() : nullptr), + QQmlRefPointer<QV4::CompiledData::CompilationUnit>::Adopt); } -CompiledData::CompilationUnit Codegen::compileModule( +QQmlRefPointer<QV4::CompiledData::CompilationUnit> Codegen::compileModule( bool debugMode, const QString &url, const QString &sourceCode, const QDateTime &sourceTimeStamp, QList<QQmlJS::DiagnosticMessage> *diagnostics) { @@ -3826,7 +4156,7 @@ CompiledData::CompilationUnit Codegen::compileModule( *diagnostics = parser.diagnosticMessages(); if (!parsed) - return CompiledData::CompilationUnit(); + return QQmlRefPointer<CompiledData::CompilationUnit>(); QQmlJS::AST::ESModule *moduleNode = QQmlJS::AST::cast<QQmlJS::AST::ESModule*>(parser.rootNode()); if (!moduleNode) { @@ -3847,7 +4177,7 @@ CompiledData::CompilationUnit Codegen::compileModule( if (cg.hasError()) { if (diagnostics) *diagnostics << cg.error(); - return CompiledData::CompilationUnit(); + return QQmlRefPointer<CompiledData::CompilationUnit>(); } return cg.generateCompilationUnit(); @@ -3934,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) {} @@ -4036,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: @@ -4069,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()) { @@ -4153,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()) { @@ -4189,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(); @@ -4244,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); @@ -4274,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); @@ -4321,7 +4655,7 @@ QT_WARNING_DISABLE_GCC("-Wmaybe-uninitialized") // the loads below are empty str 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; @@ -4344,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) { @@ -4357,7 +4691,7 @@ QT_WARNING_POP load.scope = scope; codegen->bytecodeGenerator->addInstruction(load); } - tdzCheck(requiresTDZCheck); + tdzCheck(requiresTDZCheck, throwsReferenceError); return; } case Name: @@ -4375,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 { @@ -4393,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); @@ -4423,3 +4780,5 @@ QT_WARNING_POP } Q_UNREACHABLE(); } + +QT_END_NAMESPACE |