diff options
Diffstat (limited to 'src/qml/compiler/qv4compilerscanfunctions.cpp')
-rw-r--r-- | src/qml/compiler/qv4compilerscanfunctions.cpp | 409 |
1 files changed, 268 insertions, 141 deletions
diff --git a/src/qml/compiler/qv4compilerscanfunctions.cpp b/src/qml/compiler/qv4compilerscanfunctions.cpp index 84ee452332..eeb051046b 100644 --- a/src/qml/compiler/qv4compilerscanfunctions.cpp +++ b/src/qml/compiler/qv4compilerscanfunctions.cpp @@ -56,12 +56,12 @@ using namespace QV4; using namespace QV4::Compiler; using namespace QQmlJS::AST; -ScanFunctions::ScanFunctions(Codegen *cg, const QString &sourceCode, CompilationMode defaultProgramMode) +ScanFunctions::ScanFunctions(Codegen *cg, const QString &sourceCode, ContextType defaultProgramType) : _cg(cg) , _sourceCode(sourceCode) , _context(nullptr) , _allowFuncDecls(true) - , defaultProgramMode(defaultProgramMode) + , defaultProgramType(defaultProgramType) { } @@ -73,12 +73,12 @@ void ScanFunctions::operator()(Node *node) calcEscapingVariables(); } -void ScanFunctions::enterGlobalEnvironment(CompilationMode compilationMode) +void ScanFunctions::enterGlobalEnvironment(ContextType compilationMode) { enterEnvironment(astNodeForGlobalEnvironment, compilationMode); } -void ScanFunctions::enterEnvironment(Node *node, CompilationMode compilationMode) +void ScanFunctions::enterEnvironment(Node *node, ContextType compilationMode) { Context *c = _cg->_module->contextMap.value(node); if (!c) @@ -95,25 +95,23 @@ void ScanFunctions::leaveEnvironment() _context = _contextStack.isEmpty() ? 0 : _contextStack.top(); } -void ScanFunctions::checkDirectivePrologue(SourceElements *ast) -{ - for (SourceElements *it = ast; it; it = it->next) { - if (StatementSourceElement *stmt = cast<StatementSourceElement *>(it->element)) { - if (ExpressionStatement *expr = cast<ExpressionStatement *>(stmt->statement)) { - if (StringLiteral *strLit = cast<StringLiteral *>(expr->expression)) { - // Use the source code, because the StringLiteral's - // value might have escape sequences in it, which is not - // allowed. - if (strLit->literalToken.length < 2) - continue; - QStringRef str = _sourceCode.midRef(strLit->literalToken.offset + 1, strLit->literalToken.length - 2); - if (str == QLatin1String("use strict")) { - _context->isStrict = true; - } else { - // TODO: give a warning. - } +void ScanFunctions::checkDirectivePrologue(StatementList *ast) +{ + for (StatementList *it = ast; it; it = it->next) { + if (ExpressionStatement *expr = cast<ExpressionStatement *>(it->statement)) { + if (StringLiteral *strLit = cast<StringLiteral *>(expr->expression)) { + // Use the source code, because the StringLiteral's + // value might have escape sequences in it, which is not + // allowed. + if (strLit->literalToken.length < 2) continue; + QStringRef str = _sourceCode.midRef(strLit->literalToken.offset + 1, strLit->literalToken.length - 2); + if (str == QLatin1String("use strict")) { + _context->isStrict = true; + } else { + // TODO: give a warning. } + continue; } } @@ -138,20 +136,10 @@ void ScanFunctions::checkName(const QStringRef &name, const SourceLocation &loc) } } -bool ScanFunctions::formalsContainName(AST::FormalParameterList *parameters, const QString &name) -{ - while (parameters) { - if (parameters->name == name) - return true; - parameters = parameters->next; - } - return false; -} - bool ScanFunctions::visit(Program *ast) { - enterEnvironment(ast, defaultProgramMode); - checkDirectivePrologue(ast->elements); + enterEnvironment(ast, defaultProgramType); + checkDirectivePrologue(ast->statements); return true; } @@ -162,7 +150,7 @@ void ScanFunctions::endVisit(Program *) bool ScanFunctions::visit(CallExpression *ast) { - if (! _context->hasDirectEval) { + if (!_context->hasDirectEval) { if (IdentifierExpression *id = cast<IdentifierExpression *>(ast->base)) { if (id->name == QLatin1String("eval")) { if (_context->usesArgumentsObject == Context::ArgumentsObjectUnknown) @@ -187,10 +175,10 @@ bool ScanFunctions::visit(NewMemberExpression *ast) return true; } -bool ScanFunctions::visit(ArrayLiteral *ast) +bool ScanFunctions::visit(ArrayPattern *ast) { int index = 0; - for (ElementList *it = ast->elements; it; it = it->next) { + for (PatternElementList *it = ast->elements; it; it = it->next) { for (Elision *elision = it->elision; elision; elision = elision->next) ++index; ++index; @@ -203,21 +191,28 @@ bool ScanFunctions::visit(ArrayLiteral *ast) return true; } -bool ScanFunctions::visit(VariableDeclaration *ast) +bool ScanFunctions::visit(PatternElement *ast) { - if (_context->isStrict && (ast->name == QLatin1String("eval") || ast->name == QLatin1String("arguments"))) - _cg->throwSyntaxError(ast->identifierToken, QStringLiteral("Variable name may not be eval or arguments in strict mode")); - checkName(ast->name, ast->identifierToken); - if (ast->name == QLatin1String("arguments")) - _context->usesArgumentsObject = Context::ArgumentsObjectNotUsed; - if (ast->scope == AST::VariableDeclaration::VariableScope::ReadOnlyBlockScope && !ast->expression) { - _cg->throwSyntaxError(ast->identifierToken, QStringLiteral("Missing initializer in const declaration")); - return false; - } - QString name = ast->name.toString(); - if (!_context->addLocalVar(ast->name.toString(), ast->expression ? Context::VariableDefinition : Context::VariableDeclaration, ast->scope)) { - _cg->throwSyntaxError(ast->identifierToken, QStringLiteral("Identifier %1 has already been declared").arg(name)); - return false; + if (!ast->isVariableDeclaration()) + return true; + + QStringList names; + ast->boundNames(&names); + + for (const QString &name : qAsConst(names)) { + if (_context->isStrict && (name == QLatin1String("eval") || name == QLatin1String("arguments"))) + _cg->throwSyntaxError(ast->identifierToken, QStringLiteral("Variable name may not be eval or arguments in strict mode")); + checkName(QStringRef(&name), ast->identifierToken); + if (name == QLatin1String("arguments")) + _context->usesArgumentsObject = Context::ArgumentsObjectNotUsed; + if (ast->scope == VariableScope::Const && !ast->initializer) { + _cg->throwSyntaxError(ast->identifierToken, QStringLiteral("Missing initializer in const declaration")); + return false; + } + if (!_context->addLocalVar(name, ast->initializer ? Context::VariableDefinition : Context::VariableDeclaration, ast->scope)) { + _cg->throwSyntaxError(ast->identifierToken, QStringLiteral("Identifier %1 has already been declared").arg(name)); + return false; + } } return true; } @@ -237,7 +232,8 @@ bool ScanFunctions::visit(ExpressionStatement *ast) if (!_allowFuncDecls) _cg->throwSyntaxError(expr->functionToken, QStringLiteral("conditional function or closure declaration")); - enterFunction(expr, /*enterName*/ true); + if (!enterFunction(expr, /*enterName*/ true)) + return false; Node::accept(expr->formals, this); Node::accept(expr->body, this); leaveEnvironment(); @@ -253,15 +249,25 @@ bool ScanFunctions::visit(ExpressionStatement *ast) bool ScanFunctions::visit(FunctionExpression *ast) { - enterFunction(ast, /*enterName*/ false); + return enterFunction(ast, /*enterName*/ false); +} + +bool ScanFunctions::visit(TemplateLiteral *ast) +{ + while (ast) { + if (ast->expression) + Node::accept(ast->expression, this); + ast = ast->next; + } return true; + } -void ScanFunctions::enterFunction(FunctionExpression *ast, bool enterName) +bool ScanFunctions::enterFunction(FunctionExpression *ast, bool enterName) { if (_context->isStrict && (ast->name == QLatin1String("eval") || ast->name == QLatin1String("arguments"))) _cg->throwSyntaxError(ast->identifierToken, QStringLiteral("Function name may not be eval or arguments in strict mode")); - enterFunction(ast, ast->name.toString(), ast->formals, ast->body, enterName ? ast : nullptr); + return enterFunction(ast, ast->name.toString(), ast->formals, ast->body, enterName); } void ScanFunctions::endVisit(FunctionExpression *) @@ -269,40 +275,34 @@ void ScanFunctions::endVisit(FunctionExpression *) leaveEnvironment(); } -bool ScanFunctions::visit(ObjectLiteral *ast) +bool ScanFunctions::visit(ObjectPattern *ast) { - int argc = 0; - for (PropertyAssignmentList *it = ast->properties; it; it = it->next) { - QString key = it->assignment->name->asString(); - if (QV4::String::toArrayIndex(key) != UINT_MAX) - ++argc; - ++argc; - if (AST::cast<AST::PropertyGetterSetter *>(it->assignment)) - ++argc; - } - _context->maxNumberOfArguments = qMax(_context->maxNumberOfArguments, argc); - TemporaryBoolAssignment allowFuncDecls(_allowFuncDecls, true); Node::accept(ast->properties, this); return false; } -bool ScanFunctions::visit(PropertyGetterSetter *ast) +bool ScanFunctions::visit(PatternProperty *ast) { - TemporaryBoolAssignment allowFuncDecls(_allowFuncDecls, true); - enterFunction(ast, QString(), ast->formals, ast->functionBody, /*FunctionExpression*/nullptr); + Q_UNUSED(ast); + // ### Shouldn't be required anymore +// if (ast->type == PatternProperty::Getter || ast->type == PatternProperty::Setter) { +// TemporaryBoolAssignment allowFuncDecls(_allowFuncDecls, true); +// return enterFunction(ast, QString(), ast->formals, ast->functionBody, /*enterName */ false); +// } return true; } -void ScanFunctions::endVisit(PropertyGetterSetter *) +void ScanFunctions::endVisit(PatternProperty *) { - leaveEnvironment(); + // ### +// if (ast->type == PatternProperty::Getter || ast->type == PatternProperty::Setter) +// leaveEnvironment(); } bool ScanFunctions::visit(FunctionDeclaration *ast) { - enterFunction(ast, /*enterName*/ true); - return true; + return enterFunction(ast, /*enterName*/ true); } void ScanFunctions::endVisit(FunctionDeclaration *) @@ -310,24 +310,6 @@ void ScanFunctions::endVisit(FunctionDeclaration *) leaveEnvironment(); } -bool ScanFunctions::visit(TryStatement *) -{ - // ### should limit to catch(), as try{} finally{} should be ok without - _context->hasTry = true; - return true; -} - -bool ScanFunctions::visit(WithStatement *ast) -{ - if (_context->isStrict) { - _cg->throwSyntaxError(ast->withToken, QStringLiteral("'with' statement is not allowed in strict mode")); - return false; - } - - _context->hasWith = true; - return true; -} - bool ScanFunctions::visit(DoWhileStatement *ast) { { TemporaryBoolAssignment allowFuncDecls(_allowFuncDecls, !_context->isStrict); @@ -338,7 +320,10 @@ bool ScanFunctions::visit(DoWhileStatement *ast) { } bool ScanFunctions::visit(ForStatement *ast) { + enterEnvironment(ast, ContextType::Block); + _context->name = QLatin1String("For"); Node::accept(ast->initialiser, this); + Node::accept(ast->declarations, this); Node::accept(ast->condition, this); Node::accept(ast->expression, this); @@ -348,9 +333,15 @@ bool ScanFunctions::visit(ForStatement *ast) { return false; } -bool ScanFunctions::visit(LocalForStatement *ast) { - Node::accept(ast->declarations, this); - Node::accept(ast->condition, this); +void ScanFunctions::endVisit(ForStatement *) +{ + leaveEnvironment(); +} + +bool ScanFunctions::visit(ForEachStatement *ast) { + enterEnvironment(ast, ContextType::Block); + _context->name = QLatin1String("Foreach"); + Node::accept(ast->lhs, this); Node::accept(ast->expression, this); TemporaryBoolAssignment allowFuncDecls(_allowFuncDecls, !_context->isStrict); @@ -359,90 +350,158 @@ bool ScanFunctions::visit(LocalForStatement *ast) { return false; } -bool ScanFunctions::visit(ForEachStatement *ast) { - Node::accept(ast->initialiser, this); - Node::accept(ast->expression, this); +void ScanFunctions::endVisit(ForEachStatement *) +{ + leaveEnvironment(); +} - TemporaryBoolAssignment allowFuncDecls(_allowFuncDecls, !_context->isStrict); - Node::accept(ast->statement, this); +bool ScanFunctions::visit(ThisExpression *) +{ + _context->usesThis = true; + return false; +} +bool ScanFunctions::visit(Block *ast) +{ + TemporaryBoolAssignment allowFuncDecls(_allowFuncDecls, _context->isStrict ? false : _allowFuncDecls); + enterEnvironment(ast, ContextType::Block); + _context->name = QLatin1String("Block"); + Node::accept(ast->statements, this); return false; } -bool ScanFunctions::visit(LocalForEachStatement *ast) { - Node::accept(ast->declaration, this); - Node::accept(ast->expression, this); +void ScanFunctions::endVisit(Block *) +{ + leaveEnvironment(); +} - TemporaryBoolAssignment allowFuncDecls(_allowFuncDecls, !_context->isStrict); - Node::accept(ast->statement, this); +bool ScanFunctions::visit(CaseBlock *ast) +{ + enterEnvironment(ast, ContextType::Block); + _context->name = QLatin1String("CaseBlock"); + return true; +} - return false; +void ScanFunctions::endVisit(CaseBlock *) +{ + leaveEnvironment(); } -bool ScanFunctions::visit(ThisExpression *) +bool ScanFunctions::visit(Catch *ast) { - _context->usesThis = true; + TemporaryBoolAssignment allowFuncDecls(_allowFuncDecls, _context->isStrict ? false : _allowFuncDecls); + enterEnvironment(ast, ContextType::Block); + _context->name = QLatin1String("CatchBlock"); + _context->isCatchBlock = true; + QString caughtVar = ast->patternElement->bindingIdentifier; + if (caughtVar.isEmpty()) + caughtVar = QStringLiteral("@caught"); + _context->addLocalVar(caughtVar, Context::MemberType::VariableDefinition, VariableScope::Let); + + _context->caughtVariable = caughtVar; + if (_context->isStrict && + (caughtVar == QLatin1String("eval") || caughtVar == QLatin1String("arguments"))) { + _cg->throwSyntaxError(ast->identifierToken, QStringLiteral("Catch variable name may not be eval or arguments in strict mode")); + return false; + } + Node::accept(ast->patternElement, this); + // skip the block statement + Node::accept(ast->statement->statements, this); return false; } -bool ScanFunctions::visit(Block *ast) { +void ScanFunctions::endVisit(Catch *) +{ + leaveEnvironment(); +} + +bool ScanFunctions::visit(WithStatement *ast) +{ + Node::accept(ast->expression, this); + TemporaryBoolAssignment allowFuncDecls(_allowFuncDecls, _context->isStrict ? false : _allowFuncDecls); - Node::accept(ast->statements, this); + enterEnvironment(ast, ContextType::Block); + _context->name = QLatin1String("WithBlock"); + _context->isWithBlock = true; + + if (_context->isStrict) { + _cg->throwSyntaxError(ast->withToken, QStringLiteral("'with' statement is not allowed in strict mode")); + return false; + } + Node::accept(ast->statement, this); + return false; } -void ScanFunctions::enterFunction(Node *ast, const QString &name, FormalParameterList *formals, FunctionBody *body, FunctionExpression *expr) +void ScanFunctions::endVisit(WithStatement *) +{ + leaveEnvironment(); +} + +bool ScanFunctions::enterFunction(Node *ast, const QString &name, FormalParameterList *formals, StatementList *body, bool enterName) { Context *outerContext = _context; - enterEnvironment(ast, FunctionCode); + enterEnvironment(ast, ContextType::Function); + FunctionExpression *expr = AST::cast<FunctionExpression *>(ast); + if (!expr) + expr = AST::cast<FunctionDeclaration *>(ast); if (outerContext) { outerContext->hasNestedFunctions = true; // The identifier of a function expression cannot be referenced from the enclosing environment. - if (expr) { - if (!outerContext->addLocalVar(name, Context::FunctionDefinition, AST::VariableDeclaration::FunctionScope, expr)) { + if (enterName) { + if (!outerContext->addLocalVar(name, Context::FunctionDefinition, VariableScope::Var, expr)) { _cg->throwSyntaxError(ast->firstSourceLocation(), QStringLiteral("Identifier %1 has already been declared").arg(name)); - return; + return false; } + outerContext->addLocalVar(name, Context::FunctionDefinition, VariableScope::Var, expr); } if (name == QLatin1String("arguments")) outerContext->usesArgumentsObject = Context::ArgumentsObjectNotUsed; } - if (formalsContainName(formals, QStringLiteral("arguments"))) + _context->name = name; + if (formals && formals->containsName(QStringLiteral("arguments"))) _context->usesArgumentsObject = Context::ArgumentsObjectNotUsed; + if (expr) { + if (expr->isArrowFunction) + _context->isArrowFunction = true; + else if (expr->isGenerator) + _context->isGenerator = true; + } - if (!name.isEmpty() && !formalsContainName(formals, name)) - _context->addLocalVar(name, Context::ThisFunctionName, QQmlJS::AST::VariableDeclaration::FunctionScope); + if (!name.isEmpty() && (!formals || !formals->containsName(name))) + _context->addLocalVar(name, Context::ThisFunctionName, VariableScope::Var); _context->formals = formals; if (body && !_context->isStrict) - checkDirectivePrologue(body->elements); - - for (FormalParameterList *it = formals; it; it = it->next) { - QString arg = it->name.toString(); - int duplicateIndex = _context->arguments.indexOf(arg); - if (duplicateIndex != -1) { - if (_context->isStrict) { - _cg->throwSyntaxError(it->identifierToken, QStringLiteral("Duplicate parameter name '%1' is not allowed in strict mode").arg(arg)); - return; - } else { - // change the name of the earlier argument to enforce the specified lookup semantics - QString modified = arg; - while (_context->arguments.contains(modified)) - modified += QString(0xfffe); - _context->arguments[duplicateIndex] = modified; + checkDirectivePrologue(body); + + bool isSimpleParameterList = formals && formals->isSimpleParameterList(); + + _context->arguments = formals ? formals->formals() : QStringList(); + + const QStringList boundNames = formals ? formals->boundNames() : QStringList(); + for (int i = 0; i < boundNames.size(); ++i) { + const QString &arg = boundNames.at(i); + if (_context->isStrict || !isSimpleParameterList) { + bool duplicate = (boundNames.indexOf(arg, i + 1) != -1); + if (duplicate) { + _cg->throwSyntaxError(formals->firstSourceLocation(), QStringLiteral("Duplicate parameter name '%1' is not allowed.").arg(arg)); + return false; } } if (_context->isStrict) { if (arg == QLatin1String("eval") || arg == QLatin1String("arguments")) { - _cg->throwSyntaxError(it->identifierToken, QStringLiteral("'%1' cannot be used as parameter name in strict mode").arg(arg)); - return; + _cg->throwSyntaxError(formals->firstSourceLocation(), QStringLiteral("'%1' cannot be used as parameter name in strict mode").arg(arg)); + return false; } } - _context->arguments += arg; + if (!_context->arguments.contains(arg)) + _context->addLocalVar(arg, Context::VariableDefinition, VariableScope::Var); } + return true; } void ScanFunctions::calcEscapingVariables() @@ -450,18 +509,49 @@ void ScanFunctions::calcEscapingVariables() Module *m = _cg->_module; for (Context *inner : qAsConst(m->contextMap)) { + if (inner->contextType == ContextType::Block && inner->usesArgumentsObject == Context::ArgumentsObjectUsed) { + Context *c = inner->parent; + while (c->contextType == ContextType::Block) + c = c->parent; + c->usesArgumentsObject = Context::ArgumentsObjectUsed; + inner->usesArgumentsObject = Context::ArgumentsObjectNotUsed; + } + } + for (Context *inner : qAsConst(m->contextMap)) { + if (!inner->parent || inner->usesArgumentsObject == Context::ArgumentsObjectUnknown) + inner->usesArgumentsObject = Context::ArgumentsObjectNotUsed; + if (inner->usesArgumentsObject == Context::ArgumentsObjectUsed) { + QString arguments = QStringLiteral("arguments"); + inner->addLocalVar(arguments, Context::VariableDeclaration, AST::VariableScope::Var); + if (!inner->isStrict) { + inner->argumentsCanEscape = true; + inner->requiresExecutionContext = true; + } + } + } + + for (Context *inner : qAsConst(m->contextMap)) { for (const QString &var : qAsConst(inner->usedVariables)) { Context *c = inner; while (c) { + Context *current = c; + c = c->parent; + if (current->isWithBlock || current->contextType != ContextType::Block) + break; + } + Q_ASSERT(c != inner); + while (c) { Context::MemberMap::const_iterator it = c->members.find(var); if (it != c->members.end()) { - if (c != inner) + if (c->parent || it->isLexicallyScoped()) { it->canEscape = true; + c->requiresExecutionContext = true; + } break; } if (c->findArgument(var) != -1) { - if (c != inner) - c->argumentsCanEscape = true; + c->argumentsCanEscape = true; + c->requiresExecutionContext = true; break; } c = c->parent; @@ -473,15 +563,52 @@ void ScanFunctions::calcEscapingVariables() c = c->parent; } } + for (Context *c : qAsConst(m->contextMap)) { + bool allVarsEscape = c->hasDirectEval; + if (allVarsEscape && c->contextType == ContextType::Block && c->members.isEmpty()) + allVarsEscape = false; + if (!c->parent || m->debugMode) + allVarsEscape = true; + if (allVarsEscape) { + if (c->parent) { + c->requiresExecutionContext = true; + c->argumentsCanEscape = true; + } else { + for (const auto &m : qAsConst(c->members)) { + if (m.isLexicallyScoped()) { + c->requiresExecutionContext = true; + break; + } + } + } + } + if (c->contextType == ContextType::Block && c->isCatchBlock) { + c->requiresExecutionContext = true; + auto m = c->members.find(c->caughtVariable); + m->canEscape = true; + } + const QLatin1String exprForOn("expression for on"); + if (c->contextType == ContextType::Binding && c->name.length() > exprForOn.size() && + c->name.startsWith(exprForOn) && c->name.at(exprForOn.size()).isUpper()) + // we don't really need this for bindings, but we do for signal handlers, and in this case, + // we don't know if the code is a signal handler or not. + c->requiresExecutionContext = true; + if (allVarsEscape) { + for (auto &m : c->members) + m.canEscape = true; + } + } static const bool showEscapingVars = qEnvironmentVariableIsSet("QV4_SHOW_ESCAPING_VARS"); if (showEscapingVars) { qDebug() << "==== escaping variables ===="; for (Context *c : qAsConst(m->contextMap)) { - qDebug() << "Context" << c->name << ":"; - qDebug() << " Arguments escape" << c->argumentsCanEscape; + qDebug() << "Context" << c << c->name << "requiresExecutionContext" << c->requiresExecutionContext; + qDebug() << " parent:" << c->parent; + if (c->argumentsCanEscape) + qDebug() << " Arguments escape"; for (auto it = c->members.constBegin(); it != c->members.constEnd(); ++it) { - qDebug() << " " << it.key() << it.value().canEscape; + qDebug() << " " << it.key() << it.value().canEscape << "isLexicallyScoped:" << it.value().isLexicallyScoped(); } } } |