From ee3d935a8b45576a237c74fd453fb0810f30f574 Mon Sep 17 00:00:00 2001 From: Lars Knoll Date: Tue, 4 Sep 2018 14:49:49 +0200 Subject: Fix new.target access from eval() Change-Id: I1855eb303225d1784b019f8eebab0ad8bf2cdf5e Reviewed-by: Simon Hausmann --- src/qml/compiler/qv4codegen.cpp | 9 +++-- src/qml/compiler/qv4compilercontext.cpp | 7 ++++ src/qml/compiler/qv4compilercontext_p.h | 1 + src/qml/compiler/qv4compilerscanfunctions.cpp | 45 ++++++++++++++++++++++--- src/qml/compiler/qv4compilerscanfunctions_p.h | 1 + tests/auto/qml/ecmascripttests/TestExpectations | 3 -- 6 files changed, 56 insertions(+), 10 deletions(-) diff --git a/src/qml/compiler/qv4codegen.cpp b/src/qml/compiler/qv4codegen.cpp index d61f79f0fc..cbe935c5cd 100644 --- a/src/qml/compiler/qv4codegen.cpp +++ b/src/qml/compiler/qv4codegen.cpp @@ -2143,10 +2143,15 @@ bool Codegen::visit(FieldMemberExpression *ast) if (AST::IdentifierExpression *id = AST::cast(ast->base)) { if (id->name == QLatin1String("new")) { // new.target - if (ast->name != QLatin1String("target")) { - throwSyntaxError(ast->identifierToken, QLatin1String("Expected 'target' after 'new.'.")); + Q_ASSERT(ast->name == QLatin1String("target")); + + if (_context->isArrowFunction || _context->contextType == ContextType::Eval) { + Reference r = referenceForName(QStringLiteral("new.target"), false); + r.isReadonly = true; + _expr.setResult(r); return false; } + Reference r = Reference::fromStackSlot(this, CallData::NewTarget); _expr.setResult(r); return false; diff --git a/src/qml/compiler/qv4compilercontext.cpp b/src/qml/compiler/qv4compilercontext.cpp index 1b4c084ab4..551467f95c 100644 --- a/src/qml/compiler/qv4compilercontext.cpp +++ b/src/qml/compiler/qv4compilercontext.cpp @@ -248,6 +248,13 @@ void Context::emitBlockHeader(Codegen *codegen) Codegen::Reference r = codegen->referenceForName(QStringLiteral("this"), true); r.storeConsumeAccumulator(); } + if (innerFunctionAccessesNewTarget) { + Instruction::LoadReg load; + load.reg = CallData::NewTarget; + bytecodeGenerator->addInstruction(load); + Codegen::Reference r = codegen->referenceForName(QStringLiteral("new.target"), true); + r.storeConsumeAccumulator(); + } if (contextType == ContextType::Global || (contextType == ContextType::Eval && !isStrict)) { // variables in global code are properties of the global context object, not locals as with other functions. diff --git a/src/qml/compiler/qv4compilercontext_p.h b/src/qml/compiler/qv4compilercontext_p.h index 8713b0a188..51b0595d0e 100644 --- a/src/qml/compiler/qv4compilercontext_p.h +++ b/src/qml/compiler/qv4compilercontext_p.h @@ -202,6 +202,7 @@ struct Context { bool isGenerator = false; bool usesThis = false; bool innerFunctionAccessesThis = false; + bool innerFunctionAccessesNewTarget = false; bool hasTry = false; bool returnsClosure = false; mutable bool argumentsCanEscape = false; diff --git a/src/qml/compiler/qv4compilerscanfunctions.cpp b/src/qml/compiler/qv4compilerscanfunctions.cpp index dafb1c360d..4e18af2227 100644 --- a/src/qml/compiler/qv4compilerscanfunctions.cpp +++ b/src/qml/compiler/qv4compilerscanfunctions.cpp @@ -417,14 +417,42 @@ bool ScanFunctions::visit(TemplateLiteral *ast) bool ScanFunctions::visit(SuperLiteral *) { Context *c = _context; - while (c && (c->contextType != ContextType::Function || c->isArrowFunction)) + bool needContext = false; + while (c && (c->contextType == ContextType::Block || c->isArrowFunction)) { + needContext |= c->isArrowFunction; c = c->parent; + } - if (c) - c->requiresExecutionContext = true; + c->requiresExecutionContext |= needContext; return false; } + +bool ScanFunctions::visit(FieldMemberExpression *ast) +{ + if (AST::IdentifierExpression *id = AST::cast(ast->base)) { + if (id->name == QLatin1String("new")) { + // new.target + if (ast->name != QLatin1String("target")) { + _cg->throwSyntaxError(ast->identifierToken, QLatin1String("Expected 'target' after 'new.'.")); + return false; + } + Context *c = _context; + bool needContext = false; + while (c->contextType == ContextType::Block || c->isArrowFunction) { + needContext |= c->isArrowFunction; + c = c->parent; + } + c->requiresExecutionContext |= needContext; + c->innerFunctionAccessesNewTarget |= needContext; + + return false; + } + } + + return true; +} + bool ScanFunctions::enterFunction(FunctionExpression *ast, bool enterName) { if (_context->isStrict && (ast->name == QLatin1String("eval") || ast->name == QLatin1String("arguments"))) @@ -735,6 +763,7 @@ void ScanFunctions::calcEscapingVariables() } if (inner->hasDirectEval) { inner->hasDirectEval = false; + inner->innerFunctionAccessesNewTarget = true; if (!inner->isStrict) { Context *c = inner; while (c->contextType == ContextType::Block) { @@ -742,8 +771,7 @@ void ScanFunctions::calcEscapingVariables() } Q_ASSERT(c); c->hasDirectEval = true; - if (!c->isStrict) - c->innerFunctionAccessesThis = true; + c->innerFunctionAccessesThis = true; } Context *c = inner; while (c) { @@ -773,6 +801,13 @@ void ScanFunctions::calcEscapingVariables() auto m = c->members.find(QStringLiteral("this")); m->canEscape = true; } + if (c->innerFunctionAccessesNewTarget) { + // add an escaping 'new.target' variable + c->addLocalVar(QStringLiteral("new.target"), Context::VariableDefinition, VariableScope::Let); + c->requiresExecutionContext = true; + auto m = c->members.find(QStringLiteral("new.target")); + m->canEscape = true; + } if (c->allVarsEscape && c->contextType == ContextType::Block && c->members.isEmpty()) c->allVarsEscape = false; if (c->contextType == ContextType::Global || (!c->isStrict && c->contextType == ContextType::Eval) || m->debugMode) diff --git a/src/qml/compiler/qv4compilerscanfunctions_p.h b/src/qml/compiler/qv4compilerscanfunctions_p.h index 2e016b25b1..bb07540ec9 100644 --- a/src/qml/compiler/qv4compilerscanfunctions_p.h +++ b/src/qml/compiler/qv4compilerscanfunctions_p.h @@ -116,6 +116,7 @@ protected: bool visit(AST::FunctionExpression *ast) override; bool visit(AST::TemplateLiteral *ast) override; bool visit(AST::SuperLiteral *) override; + bool visit(AST::FieldMemberExpression *) override; bool enterFunction(AST::FunctionExpression *ast, bool enterName); diff --git a/tests/auto/qml/ecmascripttests/TestExpectations b/tests/auto/qml/ecmascripttests/TestExpectations index fe2e9835e1..f0bde2132b 100644 --- a/tests/auto/qml/ecmascripttests/TestExpectations +++ b/tests/auto/qml/ecmascripttests/TestExpectations @@ -578,7 +578,6 @@ language/computed-property-names/class/static/method-string.js fails language/computed-property-names/class/static/method-symbol.js fails language/eval-code/direct/new.target.js fails language/eval-code/direct/new.target-arrow.js fails -language/eval-code/direct/new.target-fn.js fails language/eval-code/direct/non-definable-function-with-function.js sloppyFails language/eval-code/direct/non-definable-function-with-variable.js sloppyFails language/eval-code/direct/non-definable-global-function.js sloppyFails @@ -617,8 +616,6 @@ language/eval-code/indirect/var-env-var-init-global-new.js strictFails language/eval-code/indirect/var-env-var-non-strict.js strictFails language/expressions/arrow-function/dflt-params-ref-later.js fails language/expressions/arrow-function/dflt-params-ref-self.js fails -language/expressions/arrow-function/lexical-new.target-closure-returned.js fails -language/expressions/arrow-function/lexical-new.target.js fails language/expressions/arrow-function/lexical-super-call-from-within-constructor.js fails language/expressions/arrow-function/lexical-supercall-from-immediately-invoked-arrow.js fails language/expressions/arrow-function/scope-body-lex-distinct.js sloppyFails -- cgit v1.2.3