| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 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$ |
| ** |
| ****************************************************************************/ |
| |
| #include "qv4compilerscanfunctions_p.h" |
| |
| #include <QtCore/QCoreApplication> |
| #include <QtCore/QStringList> |
| #include <QtCore/QSet> |
| #include <QtCore/QBuffer> |
| #include <QtCore/QBitArray> |
| #include <QtCore/QStack> |
| #include <private/qqmljsast_p.h> |
| #include <private/qv4compilercontext_p.h> |
| #include <private/qv4codegen_p.h> |
| |
| QT_USE_NAMESPACE |
| using namespace QV4; |
| using namespace QV4::Compiler; |
| using namespace QQmlJS; |
| using namespace QQmlJS::AST; |
| |
| static CompiledData::Location location(const QQmlJS::AST::SourceLocation &astLocation) |
| { |
| CompiledData::Location target; |
| target.line = astLocation.startLine; |
| target.column = astLocation.startColumn; |
| return target; |
| } |
| |
| |
| ScanFunctions::ScanFunctions(Codegen *cg, const QString &sourceCode, ContextType defaultProgramType) |
| : QQmlJS::AST::Visitor(cg->recursionDepth()) |
| , _cg(cg) |
| , _sourceCode(sourceCode) |
| , _context(nullptr) |
| , _allowFuncDecls(true) |
| , defaultProgramType(defaultProgramType) |
| { |
| } |
| |
| void ScanFunctions::operator()(Node *node) |
| { |
| if (node) |
| node->accept(this); |
| |
| calcEscapingVariables(); |
| } |
| |
| void ScanFunctions::enterGlobalEnvironment(ContextType compilationMode) |
| { |
| enterEnvironment(astNodeForGlobalEnvironment, compilationMode, QStringLiteral("%GlobalCode")); |
| } |
| |
| void ScanFunctions::enterEnvironment(Node *node, ContextType compilationMode, const QString &name) |
| { |
| Context *c = _cg->_module->contextMap.value(node); |
| if (!c) |
| c = _cg->_module->newContext(node, _context, compilationMode); |
| if (!c->isStrict) |
| c->isStrict = _cg->_strictMode; |
| c->name = name; |
| _contextStack.append(c); |
| _context = c; |
| } |
| |
| void ScanFunctions::leaveEnvironment() |
| { |
| _contextStack.pop(); |
| _context = _contextStack.isEmpty() ? nullptr : _contextStack.top(); |
| } |
| |
| 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; |
| } |
| } |
| |
| break; |
| } |
| } |
| |
| void ScanFunctions::checkName(const QStringRef &name, const SourceLocation &loc) |
| { |
| if (_context->isStrict) { |
| if (name == QLatin1String("implements") |
| || name == QLatin1String("interface") |
| || name == QLatin1String("let") |
| || name == QLatin1String("package") |
| || name == QLatin1String("private") |
| || name == QLatin1String("protected") |
| || name == QLatin1String("public") |
| || name == QLatin1String("static") |
| || name == QLatin1String("yield")) { |
| _cg->throwSyntaxError(loc, QStringLiteral("Unexpected strict mode reserved word")); |
| } |
| } |
| } |
| |
| bool ScanFunctions::visit(Program *ast) |
| { |
| enterEnvironment(ast, defaultProgramType, QStringLiteral("%ProgramCode")); |
| checkDirectivePrologue(ast->statements); |
| return true; |
| } |
| |
| void ScanFunctions::endVisit(Program *) |
| { |
| leaveEnvironment(); |
| } |
| |
| bool ScanFunctions::visit(ESModule *ast) |
| { |
| enterEnvironment(ast, defaultProgramType, QStringLiteral("%ModuleCode")); |
| _context->isStrict = true; |
| return true; |
| } |
| |
| void ScanFunctions::endVisit(ESModule *) |
| { |
| leaveEnvironment(); |
| } |
| |
| bool ScanFunctions::visit(ExportDeclaration *declaration) |
| { |
| QString module; |
| if (declaration->fromClause) { |
| module = declaration->fromClause->moduleSpecifier.toString(); |
| if (!module.isEmpty()) |
| _context->moduleRequests << module; |
| } |
| |
| QString localNameForDefaultExport = QStringLiteral("*default*"); |
| |
| if (declaration->exportAll) { |
| Compiler::ExportEntry entry; |
| entry.moduleRequest = declaration->fromClause->moduleSpecifier.toString(); |
| entry.importName = QStringLiteral("*"); |
| entry.location = location(declaration->firstSourceLocation()); |
| _context->exportEntries << entry; |
| } else if (declaration->exportClause) { |
| for (ExportsList *it = declaration->exportClause->exportsList; it; it = it->next) { |
| ExportSpecifier *spec = it->exportSpecifier; |
| Compiler::ExportEntry entry; |
| if (module.isEmpty()) |
| entry.localName = spec->identifier.toString(); |
| else |
| entry.importName = spec->identifier.toString(); |
| |
| entry.moduleRequest = module; |
| entry.exportName = spec->exportedIdentifier.toString(); |
| entry.location = location(it->firstSourceLocation()); |
| |
| _context->exportEntries << entry; |
| } |
| } else if (auto *vstmt = AST::cast<AST::VariableStatement*>(declaration->variableStatementOrDeclaration)) { |
| BoundNames boundNames; |
| for (VariableDeclarationList *it = vstmt->declarations; it; it = it->next) { |
| if (!it->declaration) |
| continue; |
| it->declaration->boundNames(&boundNames); |
| } |
| for (const auto &name: boundNames) { |
| Compiler::ExportEntry entry; |
| entry.localName = name.id; |
| entry.exportName = name.id; |
| entry.location = location(vstmt->firstSourceLocation()); |
| _context->exportEntries << entry; |
| } |
| } else if (auto *classDecl = AST::cast<AST::ClassDeclaration*>(declaration->variableStatementOrDeclaration)) { |
| QString name = classDecl->name.toString(); |
| if (!name.isEmpty()) { |
| Compiler::ExportEntry entry; |
| entry.localName = name; |
| entry.exportName = name; |
| entry.location = location(classDecl->firstSourceLocation()); |
| _context->exportEntries << entry; |
| if (declaration->exportDefault) |
| localNameForDefaultExport = entry.localName; |
| } |
| } else if (auto *fdef = declaration->variableStatementOrDeclaration->asFunctionDefinition()) { |
| QString functionName; |
| |
| // Only function definitions for which we enter their name into the local environment |
| // can result in exports. Nested expressions such as (function foo() {}) are not accessible |
| // as locals and can only be exported as default exports (further down). |
| auto ast = declaration->variableStatementOrDeclaration; |
| if (AST::cast<AST::ExpressionStatement*>(ast) || AST::cast<AST::FunctionDeclaration*>(ast)) |
| functionName = fdef->name.toString(); |
| |
| if (!functionName.isEmpty()) { |
| Compiler::ExportEntry entry; |
| entry.localName = functionName; |
| entry.exportName = functionName; |
| entry.location = location(fdef->firstSourceLocation()); |
| _context->exportEntries << entry; |
| if (declaration->exportDefault) |
| localNameForDefaultExport = entry.localName; |
| } |
| } |
| |
| if (declaration->exportDefault) { |
| Compiler::ExportEntry entry; |
| entry.localName = localNameForDefaultExport; |
| _context->localNameForDefaultExport = localNameForDefaultExport; |
| entry.exportName = QStringLiteral("default"); |
| entry.location = location(declaration->firstSourceLocation()); |
| _context->exportEntries << entry; |
| } |
| |
| return true; // scan through potential assignment expression code, etc. |
| } |
| |
| bool ScanFunctions::visit(ImportDeclaration *declaration) |
| { |
| QString module; |
| if (declaration->fromClause) { |
| module = declaration->fromClause->moduleSpecifier.toString(); |
| if (!module.isEmpty()) |
| _context->moduleRequests << module; |
| } |
| |
| if (!declaration->moduleSpecifier.isEmpty()) |
| _context->moduleRequests << declaration->moduleSpecifier.toString(); |
| |
| if (ImportClause *import = declaration->importClause) { |
| if (!import->importedDefaultBinding.isEmpty()) { |
| Compiler::ImportEntry entry; |
| entry.moduleRequest = module; |
| entry.importName = QStringLiteral("default"); |
| entry.localName = import->importedDefaultBinding.toString(); |
| entry.location = location(declaration->firstSourceLocation()); |
| _context->importEntries << entry; |
| } |
| |
| if (import->nameSpaceImport) { |
| Compiler::ImportEntry entry; |
| entry.moduleRequest = module; |
| entry.importName = QStringLiteral("*"); |
| entry.localName = import->nameSpaceImport->importedBinding.toString(); |
| entry.location = location(declaration->firstSourceLocation()); |
| _context->importEntries << entry; |
| } |
| |
| if (import->namedImports) { |
| for (ImportsList *it = import->namedImports->importsList; it; it = it->next) { |
| Compiler::ImportEntry entry; |
| entry.moduleRequest = module; |
| entry.localName = it->importSpecifier->importedBinding.toString(); |
| if (!it->importSpecifier->identifier.isEmpty()) |
| entry.importName = it->importSpecifier->identifier.toString(); |
| else |
| entry.importName = entry.localName; |
| entry.location = location(declaration->firstSourceLocation()); |
| _context->importEntries << entry; |
| } |
| } |
| } |
| return false; |
| } |
| |
| bool ScanFunctions::visit(CallExpression *ast) |
| { |
| if (!_context->hasDirectEval) { |
| if (IdentifierExpression *id = cast<IdentifierExpression *>(ast->base)) { |
| if (id->name == QLatin1String("eval")) { |
| if (_context->usesArgumentsObject == Context::ArgumentsObjectUnknown) |
| _context->usesArgumentsObject = Context::ArgumentsObjectUsed; |
| _context->hasDirectEval = true; |
| } |
| } |
| } |
| return true; |
| } |
| |
| bool ScanFunctions::visit(PatternElement *ast) |
| { |
| if (!ast->isVariableDeclaration()) |
| return true; |
| |
| BoundNames names; |
| ast->boundNames(&names); |
| |
| QQmlJS::AST::SourceLocation lastInitializerLocation = ast->lastSourceLocation(); |
| if (_context->lastBlockInitializerLocation.isValid()) |
| lastInitializerLocation = _context->lastBlockInitializerLocation; |
| |
| for (const auto &name : qAsConst(names)) { |
| if (_context->isStrict && (name.id == QLatin1String("eval") || name.id == QLatin1String("arguments"))) |
| _cg->throwSyntaxError(ast->identifierToken, QStringLiteral("Variable name may not be eval or arguments in strict mode")); |
| checkName(QStringRef(&name.id), ast->identifierToken); |
| if (name.id == QLatin1String("arguments")) |
| _context->usesArgumentsObject = Context::ArgumentsObjectNotUsed; |
| if (ast->scope == VariableScope::Const && !ast->initializer && !ast->isForDeclaration && !ast->destructuringPattern()) { |
| _cg->throwSyntaxError(ast->identifierToken, QStringLiteral("Missing initializer in const declaration")); |
| return false; |
| } |
| if (!_context->addLocalVar(name.id, ast->initializer ? Context::VariableDefinition : Context::VariableDeclaration, ast->scope, |
| /*function*/nullptr, lastInitializerLocation)) { |
| _cg->throwSyntaxError(ast->identifierToken, QStringLiteral("Identifier %1 has already been declared").arg(name.id)); |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| bool ScanFunctions::visit(IdentifierExpression *ast) |
| { |
| checkName(ast->name, ast->identifierToken); |
| if (_context->usesArgumentsObject == Context::ArgumentsObjectUnknown && ast->name == QLatin1String("arguments")) |
| _context->usesArgumentsObject = Context::ArgumentsObjectUsed; |
| _context->addUsedVariable(ast->name.toString()); |
| return true; |
| } |
| |
| bool ScanFunctions::visit(ExpressionStatement *ast) |
| { |
| if (FunctionExpression* expr = AST::cast<AST::FunctionExpression*>(ast->expression)) { |
| if (!_allowFuncDecls) |
| _cg->throwSyntaxError(expr->functionToken, QStringLiteral("conditional function or closure declaration")); |
| |
| if (!enterFunction(expr, /*enterName*/ true)) |
| return false; |
| Node::accept(expr->formals, this); |
| Node::accept(expr->body, this); |
| leaveEnvironment(); |
| return false; |
| } else { |
| SourceLocation firstToken = ast->firstSourceLocation(); |
| if (_sourceCode.midRef(firstToken.offset, firstToken.length) == QLatin1String("function")) { |
| _cg->throwSyntaxError(firstToken, QStringLiteral("unexpected token")); |
| } |
| } |
| return true; |
| } |
| |
| bool ScanFunctions::visit(FunctionExpression *ast) |
| { |
| return enterFunction(ast, /*enterName*/ false); |
| } |
| |
| bool ScanFunctions::visit(ClassExpression *ast) |
| { |
| enterEnvironment(ast, ContextType::Block, QStringLiteral("%Class")); |
| _context->isStrict = true; |
| _context->hasNestedFunctions = true; |
| if (!ast->name.isEmpty()) |
| _context->addLocalVar(ast->name.toString(), Context::VariableDefinition, AST::VariableScope::Const); |
| return true; |
| } |
| |
| void ScanFunctions::endVisit(ClassExpression *) |
| { |
| leaveEnvironment(); |
| } |
| |
| bool ScanFunctions::visit(ClassDeclaration *ast) |
| { |
| if (!ast->name.isEmpty()) |
| _context->addLocalVar(ast->name.toString(), Context::VariableDeclaration, AST::VariableScope::Let); |
| |
| enterEnvironment(ast, ContextType::Block, QStringLiteral("%Class")); |
| _context->isStrict = true; |
| _context->hasNestedFunctions = true; |
| if (!ast->name.isEmpty()) |
| _context->addLocalVar(ast->name.toString(), Context::VariableDefinition, AST::VariableScope::Const); |
| return true; |
| } |
| |
| void ScanFunctions::endVisit(ClassDeclaration *) |
| { |
| leaveEnvironment(); |
| } |
| |
| bool ScanFunctions::visit(TemplateLiteral *ast) |
| { |
| while (ast) { |
| if (ast->expression) |
| Node::accept(ast->expression, this); |
| ast = ast->next; |
| } |
| return true; |
| |
| } |
| |
| bool ScanFunctions::visit(SuperLiteral *) |
| { |
| Context *c = _context; |
| bool needContext = false; |
| while (c && (c->contextType == ContextType::Block || c->isArrowFunction)) { |
| needContext |= c->isArrowFunction; |
| c = c->parent; |
| } |
| |
| c->requiresExecutionContext |= needContext; |
| |
| return false; |
| } |
| |
| bool ScanFunctions::visit(FieldMemberExpression *ast) |
| { |
| if (AST::IdentifierExpression *id = AST::cast<AST::IdentifierExpression *>(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::visit(ArrayPattern *ast) |
| { |
| for (PatternElementList *it = ast->elements; it; it = it->next) |
| Node::accept(it->element, this); |
| |
| return false; |
| } |
| |
| 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")); |
| return enterFunction(ast, ast->name.toString(), ast->formals, ast->body, enterName); |
| } |
| |
| void ScanFunctions::endVisit(FunctionExpression *) |
| { |
| leaveEnvironment(); |
| } |
| |
| bool ScanFunctions::visit(ObjectPattern *ast) |
| { |
| TemporaryBoolAssignment allowFuncDecls(_allowFuncDecls, true); |
| Node::accept(ast->properties, this); |
| return false; |
| } |
| |
| bool ScanFunctions::visit(PatternProperty *ast) |
| { |
| 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(PatternProperty *) |
| { |
| // ### |
| // if (ast->type == PatternProperty::Getter || ast->type == PatternProperty::Setter) |
| // leaveEnvironment(); |
| } |
| |
| bool ScanFunctions::visit(FunctionDeclaration *ast) |
| { |
| return enterFunction(ast, /*enterName*/ true); |
| } |
| |
| void ScanFunctions::endVisit(FunctionDeclaration *) |
| { |
| leaveEnvironment(); |
| } |
| |
| bool ScanFunctions::visit(DoWhileStatement *ast) { |
| { |
| TemporaryBoolAssignment allowFuncDecls(_allowFuncDecls, !_context->isStrict); |
| Node::accept(ast->statement, this); |
| } |
| Node::accept(ast->expression, this); |
| return false; |
| } |
| |
| bool ScanFunctions::visit(ForStatement *ast) { |
| enterEnvironment(ast, ContextType::Block, QStringLiteral("%For")); |
| Node::accept(ast->initialiser, this); |
| Node::accept(ast->declarations, this); |
| Node::accept(ast->condition, this); |
| Node::accept(ast->expression, this); |
| |
| TemporaryBoolAssignment allowFuncDecls(_allowFuncDecls, !_context->isStrict); |
| Node::accept(ast->statement, this); |
| |
| return false; |
| } |
| |
| void ScanFunctions::endVisit(ForStatement *) |
| { |
| leaveEnvironment(); |
| } |
| |
| bool ScanFunctions::visit(ForEachStatement *ast) { |
| enterEnvironment(ast, ContextType::Block, QStringLiteral("%Foreach")); |
| if (ast->expression) |
| _context->lastBlockInitializerLocation = ast->expression->lastSourceLocation(); |
| Node::accept(ast->lhs, this); |
| Node::accept(ast->expression, this); |
| |
| TemporaryBoolAssignment allowFuncDecls(_allowFuncDecls, !_context->isStrict); |
| Node::accept(ast->statement, this); |
| |
| return false; |
| } |
| |
| void ScanFunctions::endVisit(ForEachStatement *) |
| { |
| leaveEnvironment(); |
| } |
| |
| 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, QStringLiteral("%Block")); |
| Node::accept(ast->statements, this); |
| return false; |
| } |
| |
| void ScanFunctions::endVisit(Block *) |
| { |
| leaveEnvironment(); |
| } |
| |
| bool ScanFunctions::visit(CaseBlock *ast) |
| { |
| enterEnvironment(ast, ContextType::Block, QStringLiteral("%CaseBlock")); |
| return true; |
| } |
| |
| void ScanFunctions::endVisit(CaseBlock *) |
| { |
| leaveEnvironment(); |
| } |
| |
| bool ScanFunctions::visit(Catch *ast) |
| { |
| TemporaryBoolAssignment allowFuncDecls(_allowFuncDecls, _context->isStrict ? false : _allowFuncDecls); |
| enterEnvironment(ast, ContextType::Block, QStringLiteral("%CatchBlock")); |
| _context->isCatchBlock = true; |
| QString caughtVar = ast->patternElement->bindingIdentifier.toString(); |
| 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; |
| } |
| |
| void ScanFunctions::endVisit(Catch *) |
| { |
| leaveEnvironment(); |
| } |
| |
| bool ScanFunctions::visit(WithStatement *ast) |
| { |
| Node::accept(ast->expression, this); |
| |
| TemporaryBoolAssignment allowFuncDecls(_allowFuncDecls, _context->isStrict ? false : _allowFuncDecls); |
| enterEnvironment(ast, ContextType::Block, QStringLiteral("%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::endVisit(WithStatement *) |
| { |
| leaveEnvironment(); |
| } |
| |
| bool ScanFunctions::enterFunction(Node *ast, const QString &name, FormalParameterList *formals, StatementList *body, bool enterName) |
| { |
| Context *outerContext = _context; |
| enterEnvironment(ast, ContextType::Function, name); |
| |
| 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 (enterName) { |
| if (!outerContext->addLocalVar(name, Context::FunctionDefinition, VariableScope::Var, expr)) { |
| _cg->throwSyntaxError(ast->firstSourceLocation(), QStringLiteral("Identifier %1 has already been declared").arg(name)); |
| return false; |
| } |
| outerContext->addLocalVar(name, Context::FunctionDefinition, VariableScope::Var, expr); |
| } |
| if (name == QLatin1String("arguments")) |
| outerContext->usesArgumentsObject = Context::ArgumentsObjectNotUsed; |
| } |
| |
| _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 (expr->typeAnnotation) |
| _context->returnType = expr->typeAnnotation->type->toString(); |
| } |
| |
| |
| if (!enterName && (!name.isEmpty() && (!formals || !formals->containsName(name)))) |
| _context->addLocalVar(name, Context::ThisFunctionName, VariableScope::Var); |
| _context->formals = formals; |
| |
| if (body && !_context->isStrict) |
| checkDirectivePrologue(body); |
| |
| bool isSimpleParameterList = formals && formals->isSimpleParameterList(); |
| |
| _context->arguments = formals ? formals->formals() : BoundNames(); |
| |
| const BoundNames boundNames = formals ? formals->boundNames() : BoundNames(); |
| for (int i = 0; i < boundNames.size(); ++i) { |
| const QString &arg = boundNames.at(i).id; |
| 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(formals->firstSourceLocation(), QStringLiteral("'%1' cannot be used as parameter name in strict mode").arg(arg)); |
| return false; |
| } |
| } |
| if (!_context->arguments.contains(arg)) |
| _context->addLocalVar(arg, Context::VariableDefinition, VariableScope::Var); |
| } |
| |
| return true; |
| } |
| |
| void ScanFunctions::calcEscapingVariables() |
| { |
| Module *m = _cg->_module; |
| |
| for (Context *inner : qAsConst(m->contextMap)) { |
| if (inner->usesArgumentsObject != Context::ArgumentsObjectUsed) |
| continue; |
| if (inner->contextType != ContextType::Block && !inner->isArrowFunction) |
| continue; |
| Context *c = inner->parent; |
| while (c && (c->contextType == ContextType::Block || c->isArrowFunction)) |
| c = c->parent; |
| if (c) |
| 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 *c : qAsConst(m->contextMap)) { |
| if (c->contextType != ContextType::ESModule) |
| continue; |
| for (const auto &entry: c->exportEntries) { |
| auto mIt = c->members.constFind(entry.localName); |
| if (mIt != c->members.constEnd()) |
| mIt->canEscape = true; |
| } |
| break; |
| } |
| |
| 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.constFind(var); |
| if (it != c->members.constEnd()) { |
| if (c->parent || it->isLexicallyScoped()) { |
| it->canEscape = true; |
| c->requiresExecutionContext = true; |
| } else if (c->contextType == ContextType::ESModule) { |
| // Module instantiation provides a context, but vars used from inner |
| // scopes need to be stored in its locals[]. |
| it->canEscape = true; |
| } |
| break; |
| } |
| if (c->findArgument(var) != -1) { |
| c->argumentsCanEscape = true; |
| c->requiresExecutionContext = true; |
| break; |
| } |
| c = c->parent; |
| } |
| } |
| if (inner->hasDirectEval) { |
| inner->hasDirectEval = false; |
| inner->innerFunctionAccessesNewTarget = true; |
| if (!inner->isStrict) { |
| Context *c = inner; |
| while (c->contextType == ContextType::Block) { |
| c = c->parent; |
| } |
| Q_ASSERT(c); |
| c->hasDirectEval = true; |
| c->innerFunctionAccessesThis = true; |
| } |
| Context *c = inner; |
| while (c) { |
| c->allVarsEscape = true; |
| c = c->parent; |
| } |
| } |
| if (inner->usesThis) { |
| inner->usesThis = false; |
| bool innerFunctionAccessesThis = false; |
| Context *c = inner; |
| while (c->contextType == ContextType::Block || c->isArrowFunction) { |
| innerFunctionAccessesThis |= c->isArrowFunction; |
| c = c->parent; |
| } |
| Q_ASSERT(c); |
| if (!inner->isStrict) |
| c->usesThis = true; |
| c->innerFunctionAccessesThis |= innerFunctionAccessesThis; |
| } |
| } |
| for (Context *c : qAsConst(m->contextMap)) { |
| if (c->innerFunctionAccessesThis) { |
| // add an escaping 'this' variable |
| c->addLocalVar(QStringLiteral("this"), Context::VariableDefinition, VariableScope::Let); |
| c->requiresExecutionContext = true; |
| auto mIt = c->members.constFind(QStringLiteral("this")); |
| Q_ASSERT(mIt != c->members.constEnd()); |
| mIt->canEscape = true; |
| } |
| if (c->innerFunctionAccessesNewTarget) { |
| // add an escaping 'new.target' variable |
| c->addLocalVar(QStringLiteral("new.target"), Context::VariableDefinition, VariableScope::Let); |
| c->requiresExecutionContext = true; |
| auto mIt = c->members.constFind(QStringLiteral("new.target")); |
| Q_ASSERT(mIt != c->members.constEnd()); |
| mIt->canEscape = true; |
| } |
| if (c->allVarsEscape && c->contextType == ContextType::Block && c->members.isEmpty()) |
| c->allVarsEscape = false; |
| if (c->contextType == ContextType::Global || c->contextType == ContextType::ScriptImportedByQML || (!c->isStrict && c->contextType == ContextType::Eval) || m->debugMode) |
| c->allVarsEscape = true; |
| if (c->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 mIt = c->members.constFind(c->caughtVariable); |
| Q_ASSERT(mIt != c->members.constEnd()); |
| mIt->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 (c->allVarsEscape) { |
| for (const auto &m : qAsConst(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 << c->name << "requiresExecutionContext" << c->requiresExecutionContext << "isStrict" << c->isStrict; |
| qDebug() << " isArrowFunction" << c->isArrowFunction << "innerFunctionAccessesThis" << c->innerFunctionAccessesThis; |
| 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().index << it.value().canEscape << "isLexicallyScoped:" << it.value().isLexicallyScoped(); |
| } |
| } |
| } |
| } |
| |
| void ScanFunctions::throwRecursionDepthError() |
| { |
| _cg->throwRecursionDepthError(); |
| } |