diff options
-rw-r--r-- | src/qml/compiler/qv4codegen.cpp | 103 | ||||
-rw-r--r-- | src/qml/compiler/qv4codegen_p.h | 2 | ||||
-rw-r--r-- | src/qml/compiler/qv4compilercontrolflow_p.h | 27 | ||||
-rw-r--r-- | tests/auto/qml/ecmascripttests/TestExpectations | 19 | ||||
-rw-r--r-- | tests/auto/qml/qqmlecmascript/data/tryStatement.3.qml | 2 | ||||
-rw-r--r-- | tests/auto/qml/qqmlecmascript/data/tryStatement.4.qml | 2 | ||||
-rw-r--r-- | tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp | 4 |
7 files changed, 125 insertions, 34 deletions
diff --git a/src/qml/compiler/qv4codegen.cpp b/src/qml/compiler/qv4codegen.cpp index 9337f6c625..2aa1cef451 100644 --- a/src/qml/compiler/qv4codegen.cpp +++ b/src/qml/compiler/qv4codegen.cpp @@ -352,21 +352,96 @@ void Codegen::program(Program *ast) } } +enum class CompletionState { + Empty, + EmptyAbrupt, + NonEmpty +}; + +static CompletionState completionState(StatementList *list) +{ + for (StatementList *it = list; it; it = it->next) { + if (it->statement->kind == Statement::Kind_BreakStatement || + it->statement->kind == Statement::Kind_ContinueStatement) + return CompletionState::EmptyAbrupt; + if (it->statement->kind == Statement::Kind_EmptyStatement || + it->statement->kind == Statement::Kind_VariableDeclaration || + it->statement->kind == Statement::Kind_FunctionDeclaration) + continue; + if (it->statement->kind == Statement::Kind_Block) { + CompletionState subState = completionState(static_cast<Block *>(it->statement)->statements); + if (subState != CompletionState::Empty) + return subState; + continue; + } + return CompletionState::NonEmpty; + } + return CompletionState::Empty; +} + +static Node *completionStatement(StatementList *list) +{ + Node *completionStatement = nullptr; + for (StatementList *it = list; it; it = it->next) { + if (it->statement->kind == Statement::Kind_BreakStatement || + it->statement->kind == Statement::Kind_ContinueStatement) + return completionStatement; + if (it->statement->kind == Statement::Kind_ThrowStatement || + it->statement->kind == Statement::Kind_ReturnStatement) + return it->statement; + if (it->statement->kind == Statement::Kind_EmptyStatement || + it->statement->kind == Statement::Kind_VariableStatement || + it->statement->kind == Statement::Kind_FunctionDeclaration) + continue; + if (it->statement->kind == Statement::Kind_Block) { + CompletionState state = completionState(static_cast<Block *>(it->statement)->statements); + switch (state) { + case CompletionState::Empty: + continue; + case CompletionState::EmptyAbrupt: + return it->statement; + case CompletionState::NonEmpty: + break; + } + } + completionStatement = it->statement; + } + return completionStatement; +} + void Codegen::statementList(StatementList *ast) { + if (!ast) + return; + bool _requiresReturnValue = requiresReturnValue; - requiresReturnValue = false; + // ### the next line is pessimizing a bit too much, as there are many cases, where the complietion from the break + // statement will not be used, but it's at least spec compliant + if (!controlFlow || !controlFlow->hasLoop()) + requiresReturnValue = false; + + Node *needsCompletion = nullptr; + + if (_requiresReturnValue && !requiresReturnValue) + needsCompletion = completionStatement(ast); + + if (requiresReturnValue && !needsCompletion && !insideSwitch) { + // break or continue is the first real statement, set the return value to undefined + Reference::fromConst(this, Encode::undefined()).storeOnStack(_returnAddress); + } + + bool _insideSwitch = insideSwitch; + insideSwitch = false; + for (StatementList *it = ast; it; it = it->next) { - if (!it->next || - it->next->statement->kind == Statement::Kind_BreakStatement || - it->next->statement->kind == Statement::Kind_ContinueStatement || - it->next->statement->kind == Statement::Kind_ReturnStatement) - requiresReturnValue = _requiresReturnValue; + if (it->statement == needsCompletion) + requiresReturnValue = true; if (Statement *s = it->statement->statementCast()) statement(s); else statement(static_cast<ExpressionNode *>(it->statement)); - requiresReturnValue = false; + if (it->statement == needsCompletion) + requiresReturnValue = false; if (it->statement->kind == Statement::Kind_ThrowStatement || it->statement->kind == Statement::Kind_BreakStatement || it->statement->kind == Statement::Kind_ContinueStatement || @@ -375,6 +450,7 @@ void Codegen::statementList(StatementList *ast) break; } requiresReturnValue = _requiresReturnValue; + insideSwitch = _insideSwitch; } void Codegen::variableDeclaration(PatternElement *ast) @@ -2379,6 +2455,9 @@ int Codegen::defineFunction(const QString &name, AST::Node *ast, BytecodeGenerator::Label *savedReturnLabel = _returnLabel; _returnLabel = nullptr; + bool savedFunctionEndsWithReturn = functionEndsWithReturn; + functionEndsWithReturn = endsWithReturn(_module, body); + // reserve the js stack frame (Context & js Function & accumulator) bytecodeGenerator->newRegisterArray(sizeof(CallData)/sizeof(Value) - 1 + _context->arguments.size()); @@ -2442,7 +2521,7 @@ int Codegen::defineFunction(const QString &name, AST::Node *ast, bytecodeGenerator->setLocation(ast->lastSourceLocation()); _context->emitBlockFooter(this); - if (_returnLabel || hasError || !endsWithReturn(_module, body)) { + if (_returnLabel || hasError || !functionEndsWithReturn) { if (_returnLabel) _returnLabel->link(); @@ -2463,7 +2542,7 @@ int Codegen::defineFunction(const QString &name, AST::Node *ast, static const bool showCode = qEnvironmentVariableIsSet("QV4_SHOW_BYTECODE"); if (showCode) { qDebug() << "=== Bytecode for" << _context->name << "strict mode" << _context->isStrict - << "register count" << _context->registerCountInFunction; + << "register count" << _context->registerCountInFunction << "implicit return" << requiresReturnValue; QV4::Moth::dumpBytecode(_context->code, _context->locals.size(), _context->arguments.size(), _context->line, _context->lineNumberMapping); qDebug(); @@ -2476,6 +2555,7 @@ int Codegen::defineFunction(const QString &name, AST::Node *ast, delete _returnLabel; _returnLabel = savedReturnLabel; controlFlow = savedControlFlow; + functionEndsWithReturn = savedFunctionEndsWithReturn; _functionContext = savedFunctionContext; return leaveContext(); @@ -2842,6 +2922,9 @@ bool Codegen::visit(SwitchStatement *ast) if (hasError) return true; + if (requiresReturnValue) + Reference::fromConst(this, Encode::undefined()).storeOnStack(_returnAddress); + RegisterScope scope(this); if (ast->block) { @@ -2889,6 +2972,7 @@ bool Codegen::visit(SwitchStatement *ast) ControlFlowLoop flow(this, &switchEnd); + insideSwitch = true; for (CaseClauses *it = ast->block->clauses; it; it = it->next) { CaseClause *clause = it->clause; blockMap[clause].link(); @@ -2909,6 +2993,7 @@ bool Codegen::visit(SwitchStatement *ast) statementList(clause->statements); } + insideSwitch = false; switchEnd.link(); diff --git a/src/qml/compiler/qv4codegen_p.h b/src/qml/compiler/qv4codegen_p.h index 697da71480..39309f78ce 100644 --- a/src/qml/compiler/qv4codegen_p.h +++ b/src/qml/compiler/qv4codegen_p.h @@ -684,7 +684,9 @@ protected: bool _strictMode; bool useFastLookups = true; bool requiresReturnValue = false; + bool insideSwitch = false; bool inFormalParameterList = false; + bool functionEndsWithReturn = false; ControlFlow *controlFlow = nullptr; bool _fileNameIsUrl; diff --git a/src/qml/compiler/qv4compilercontrolflow_p.h b/src/qml/compiler/qv4compilercontrolflow_p.h index 9ab581f015..415a112ec5 100644 --- a/src/qml/compiler/qv4compilercontrolflow_p.h +++ b/src/qml/compiler/qv4compilercontrolflow_p.h @@ -119,6 +119,16 @@ struct ControlFlow { virtual QString label() const { return QString(); } + bool hasLoop() const { + const ControlFlow *flow = this; + while (flow) { + if (flow->type == Loop) + return true; + flow = flow->parent; + } + return false; + } + protected: virtual BytecodeGenerator::Label getUnwindTarget(UnwindType, const QString & = QString()) { return BytecodeGenerator::Label(); @@ -355,7 +365,6 @@ struct ControlFlowFinally : public ControlFlowUnwind { AST::Finally *finally; bool insideFinally = false; - int exceptionTemp = -1; ControlFlowFinally(Codegen *cg, AST::Finally *finally) : ControlFlowUnwind(cg, Finally), finally(finally) @@ -380,9 +389,17 @@ struct ControlFlowFinally : public ControlFlowUnwind Codegen::RegisterScope scope(cg); insideFinally = true; - exceptionTemp = generator()->newRegister(); + int exceptionTemp = generator()->newRegister(); Instruction::GetException instr; generator()->addInstruction(instr); + int returnValueTemp = -1; + if (cg->requiresReturnValue) { + returnValueTemp = generator()->newRegister(); + Instruction::MoveReg move; + move.srcReg = cg->_returnAddress; + move.destReg = returnValueTemp; + generator()->addInstruction(move); + } Reference::fromStackSlot(cg, exceptionTemp).storeConsumeAccumulator(); generator()->setUnwindHandler(parentUnwindHandler()); @@ -391,6 +408,12 @@ struct ControlFlowFinally : public ControlFlowUnwind Reference::fromStackSlot(cg, exceptionTemp).loadInAccumulator(); + if (cg->requiresReturnValue) { + Instruction::MoveReg move; + move.srcReg = returnValueTemp; + move.destReg = cg->_returnAddress; + generator()->addInstruction(move); + } Instruction::SetException setException; generator()->addInstruction(setException); diff --git a/tests/auto/qml/ecmascripttests/TestExpectations b/tests/auto/qml/ecmascripttests/TestExpectations index 7ab2dcd9aa..34ac22de66 100644 --- a/tests/auto/qml/ecmascripttests/TestExpectations +++ b/tests/auto/qml/ecmascripttests/TestExpectations @@ -4954,7 +4954,6 @@ language/statements/class/syntax/early-errors/class-body-constructor-empty-missi language/statements/const/block-local-closure-get-before-initialization.js fails language/statements/const/block-local-use-before-initialization-in-declaration-statement.js fails language/statements/const/block-local-use-before-initialization-in-prior-statement.js fails -language/statements/const/cptn-value.js fails language/statements/const/dstr-ary-ptrn-elem-id-init-fn-name-arrow.js fails language/statements/const/dstr-ary-ptrn-elem-id-init-fn-name-class.js fails language/statements/const/dstr-ary-ptrn-elem-id-init-fn-name-cover.js fails @@ -4976,7 +4975,6 @@ language/statements/const/syntax/const-invalid-assignment-next-expression-for.js language/statements/const/syntax/const-invalid-assignment-statement-body-for-in.js fails language/statements/const/syntax/const-invalid-assignment-statement-body-for-of.js fails language/statements/do-while/tco-body.js strictFails -language/statements/empty/cptn-value.js fails language/statements/for-in/head-const-bound-names-fordecl-tdz.js fails language/statements/for-in/head-const-fresh-binding-per-iteration.js fails language/statements/for-in/head-let-bound-names-fordecl-tdz.js fails @@ -5201,8 +5199,6 @@ language/statements/for-of/yield-star-from-catch.js fails language/statements/for-of/yield-star-from-finally.js fails language/statements/for-of/yield-star-from-try.js fails language/statements/for-of/yield-star.js fails -language/statements/for/S12.6.3_A9.1.js fails -language/statements/for/S12.6.3_A9.js fails language/statements/for/dstr-const-ary-ptrn-elem-id-init-fn-name-arrow.js fails language/statements/for/dstr-const-ary-ptrn-elem-id-init-fn-name-class.js fails language/statements/for/dstr-const-ary-ptrn-elem-id-init-fn-name-cover.js fails @@ -5243,7 +5239,6 @@ language/statements/function/13.2-30-s.js fails language/statements/function/S13_A15_T4.js sloppyFails language/statements/function/arguments-with-arguments-fn.js sloppyFails language/statements/function/arguments-with-arguments-lex.js sloppyFails -language/statements/function/cptn-decl.js fails language/statements/function/dflt-params-ref-later.js fails language/statements/function/dflt-params-ref-self.js fails language/statements/function/dflt-params-trailing-comma.js fails @@ -5278,7 +5273,6 @@ language/statements/function/scope-param-rest-elem-var-open.js sloppyFails language/statements/function/scope-paramsbody-var-open.js fails language/statements/generators/arguments-with-arguments-fn.js sloppyFails language/statements/generators/arguments-with-arguments-lex.js sloppyFails -language/statements/generators/cptn-decl.js fails language/statements/generators/default-proto.js fails language/statements/generators/dflt-params-ref-later.js fails language/statements/generators/dflt-params-ref-self.js fails @@ -5319,7 +5313,6 @@ language/statements/generators/yield-identifier-non-strict.js sloppyFails language/statements/generators/yield-spread-arr-multiple.js fails language/statements/generators/yield-spread-arr-single.js fails language/statements/generators/yield-star-before-newline.js fails -language/statements/if/cptn-no-else-true-abrupt-empty.js fails language/statements/if/tco-else-body.js strictFails language/statements/if/tco-if-body.js strictFails language/statements/labeled/tco.js strictFails @@ -5327,7 +5320,6 @@ language/statements/let/block-local-closure-get-before-initialization.js fails language/statements/let/block-local-closure-set-before-initialization.js fails language/statements/let/block-local-use-before-initialization-in-declaration-statement.js fails language/statements/let/block-local-use-before-initialization-in-prior-statement.js fails -language/statements/let/cptn-value.js fails language/statements/let/dstr-ary-ptrn-elem-id-init-fn-name-arrow.js fails language/statements/let/dstr-ary-ptrn-elem-id-init-fn-name-class.js fails language/statements/let/dstr-ary-ptrn-elem-id-init-fn-name-cover.js fails @@ -5356,15 +5348,6 @@ language/statements/switch/tco-case-body.js strictFails language/statements/switch/tco-dftl-body.js strictFails language/statements/throw/S12.13_A2_T6.js strictFails language/statements/try/S12.14_A18_T6.js strictFails -language/statements/try/cptn-catch-empty-break.js fails -language/statements/try/cptn-catch-empty-continue.js fails -language/statements/try/cptn-catch-finally-empty-break.js fails -language/statements/try/cptn-catch-finally-empty-continue.js fails -language/statements/try/cptn-finally-empty-break.js fails -language/statements/try/cptn-finally-empty-continue.js fails -language/statements/try/cptn-finally-from-catch.js fails -language/statements/try/cptn-finally-skip-catch.js fails -language/statements/try/cptn-finally-wo-catch.js fails language/statements/try/dstr-ary-ptrn-elem-id-init-fn-name-arrow.js fails language/statements/try/dstr-ary-ptrn-elem-id-init-fn-name-class.js fails language/statements/try/dstr-ary-ptrn-elem-id-init-fn-name-cover.js fails @@ -5380,7 +5363,6 @@ language/statements/try/tco-catch-finally.js strictFails language/statements/try/tco-catch.js strictFails language/statements/try/tco-finally.js strictFails language/statements/variable/binding-resolution.js sloppyFails -language/statements/variable/cptn-value.js fails language/statements/variable/dstr-ary-ptrn-elem-id-init-fn-name-arrow.js fails language/statements/variable/dstr-ary-ptrn-elem-id-init-fn-name-class.js fails language/statements/variable/dstr-ary-ptrn-elem-id-init-fn-name-cover.js fails @@ -5394,7 +5376,6 @@ language/statements/variable/dstr-obj-ptrn-id-init-fn-name-gen.js fails language/statements/variable/fn-name-class.js fails language/statements/while/tco-body.js strictFails language/statements/with/binding-blocked-by-unscopables.js sloppyFails -language/statements/with/cptn-abrupt-empty.js sloppyFails language/statements/with/has-property-err.js sloppyFails language/statements/with/unscopables-inc-dec.js sloppyFails language/types/reference/get-value-prop-base-primitive-realm.js fails diff --git a/tests/auto/qml/qqmlecmascript/data/tryStatement.3.qml b/tests/auto/qml/qqmlecmascript/data/tryStatement.3.qml index 04b39f73d5..7f5a22a459 100644 --- a/tests/auto/qml/qqmlecmascript/data/tryStatement.3.qml +++ b/tests/auto/qml/qqmlecmascript/data/tryStatement.3.qml @@ -8,6 +8,6 @@ MyQmlObject { return 321 } - value: try { var p = go() } catch(e) { var p = defaultValue } finally { p == 123 } + qjsvalue: try { var p = go() } catch(e) { var p = defaultValue } finally { p == 123 } } diff --git a/tests/auto/qml/qqmlecmascript/data/tryStatement.4.qml b/tests/auto/qml/qqmlecmascript/data/tryStatement.4.qml index 231aaf0683..39d4f74e97 100644 --- a/tests/auto/qml/qqmlecmascript/data/tryStatement.4.qml +++ b/tests/auto/qml/qqmlecmascript/data/tryStatement.4.qml @@ -7,6 +7,6 @@ MyQmlObject { return 321 } - value: try { var p = go() } catch(e) { var p = defaultValue } finally { p == 321 } + qjsvalue: try { var p = go() } catch(e) { var p = defaultValue } finally { p == 321 } } diff --git a/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp b/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp index 5959ecc19f..acdee3a808 100644 --- a/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp +++ b/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp @@ -7371,7 +7371,7 @@ void tst_qqmlecmascript::tryStatement() MyQmlObject *object = qobject_cast<MyQmlObject *>(component.create()); QVERIFY(object != nullptr); - QCOMPARE(object->value(), 1); + QVERIFY(object->qjsvalue().isUndefined()); } { @@ -7379,7 +7379,7 @@ void tst_qqmlecmascript::tryStatement() MyQmlObject *object = qobject_cast<MyQmlObject *>(component.create()); QVERIFY(object != nullptr); - QCOMPARE(object->value(), 1); + QVERIFY(object->qjsvalue().isUndefined()); } } |