aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/qml/compiler/qv4codegen.cpp103
-rw-r--r--src/qml/compiler/qv4codegen_p.h2
-rw-r--r--src/qml/compiler/qv4compilercontrolflow_p.h27
-rw-r--r--tests/auto/qml/ecmascripttests/TestExpectations19
-rw-r--r--tests/auto/qml/qqmlecmascript/data/tryStatement.3.qml2
-rw-r--r--tests/auto/qml/qqmlecmascript/data/tryStatement.4.qml2
-rw-r--r--tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp4
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());
}
}