f31522130f
Build system (5 gaps hardened): - COOKBOOK_OFFLINE defaults to true (fork-mode) - normalize_patch handles diff -ruN format - New 'repo validate-patches' command (25/25 relibc patches) - 14 patched Qt/Wayland/display recipes added to protected list - relibc archive regenerated with current patch chain Boot fixes (fixable): - Full ISO EFI partition: 16 MiB → 1 MiB (matches mini, BIOS hardcoded 2 MiB offset) - D-Bus system bus: absolute /usr/bin/dbus-daemon path (was skipped) - redbear-sessiond: absolute /usr/bin/redbear-sessiond path (was skipped) - daemon framework: silenced spurious INIT_NOTIFY warnings for oneshot_async services (P0-daemon-silence-init-notify.patch) - udev-shim: demoted INIT_NOTIFY warning to INFO (expected for oneshot_async) - relibc: comprehensive named semaphores (sem_open/close/unlink) replacing upstream todo!() stubs - greeterd: Wayland socket timeout 15s → 30s (compositor DRM wait) - greeter-ui: built and linked (header guard unification, sem_compat stubs removed) - mc: un-ignored in both configs, fixed glib/libiconv/pcre2 transitive deps - greeter config: removed stale keymapd dependency from display/greeter services - prefix toolchain: relibc headers synced, _RELIBC_STDLIB_H guard unified Unfixable (diagnosed, upstream): - i2c-hidd: abort on no-I2C-hardware (QEMU) — process::exit → relibc abort - kded6/greeter-ui: page fault 0x8 — Qt library null deref - Thread panics fd != -1 — Rust std library on Redox - DHCP timeout / eth0 MAC — QEMU user-mode networking - hwrngd/thermald — no hardware RNG/thermal in VM - live preload allocation — BIOS memory fragmentation, continues on demand
954 lines
34 KiB
C++
954 lines
34 KiB
C++
// Copyright (C) 2016 The Qt Company Ltd.
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
|
|
// Qt-Security score:significant
|
|
|
|
#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::SourceLocation &astLocation)
|
|
{
|
|
return CompiledData::Location(astLocation.startLine, astLocation.startColumn);
|
|
}
|
|
|
|
|
|
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)
|
|
{
|
|
Q_ASSERT(_context);
|
|
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;
|
|
QStringView str = QStringView{_sourceCode}.mid(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(QStringView name, const QQmlJS::SourceLocation &loc)
|
|
{
|
|
Q_ASSERT(_context);
|
|
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"));
|
|
Q_ASSERT(_context);
|
|
_context->isStrict = true;
|
|
return true;
|
|
}
|
|
|
|
void ScanFunctions::endVisit(ESModule *)
|
|
{
|
|
leaveEnvironment();
|
|
}
|
|
|
|
bool ScanFunctions::visit(ExportDeclaration *declaration)
|
|
{
|
|
Q_ASSERT(_context);
|
|
QString module;
|
|
if (declaration->fromClause) {
|
|
module = declaration->fromClause->moduleSpecifier.toString();
|
|
if (!module.isEmpty())
|
|
_context->moduleRequests << module;
|
|
}
|
|
|
|
QString localNameForDefaultExport = QStringLiteral("*default*");
|
|
|
|
if (declaration->exportsAll()) {
|
|
Q_ASSERT_X(declaration->fromClause, "ScanFunctions",
|
|
"ExportDeclaration with exportAll always have a fromClause");
|
|
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)
|
|
{
|
|
Q_ASSERT(_context);
|
|
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)
|
|
{
|
|
Q_ASSERT(_context);
|
|
if (!_context->hasDirectEval) {
|
|
if (IdentifierExpression *id = cast<IdentifierExpression *>(ast->base)) {
|
|
if (id->name == QLatin1String("eval")) {
|
|
if (_context->usesArgumentsObject == Context::UsesArgumentsObject::Unknown)
|
|
_context->usesArgumentsObject = Context::UsesArgumentsObject::Used;
|
|
_context->hasDirectEval = true;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool ScanFunctions::visit(PatternElement *ast)
|
|
{
|
|
Q_ASSERT(_context);
|
|
if (!ast->isVariableDeclaration())
|
|
return true;
|
|
|
|
BoundNames names;
|
|
ast->boundNames(&names);
|
|
|
|
QQmlJS::SourceLocation declarationLocation = ast->firstSourceLocation();
|
|
if (_context->lastBlockInitializerLocation.isValid()) {
|
|
declarationLocation.length = _context->lastBlockInitializerLocation.end()
|
|
- declarationLocation.offset;
|
|
} else {
|
|
declarationLocation.length = ast->lastSourceLocation().end() - declarationLocation.offset;
|
|
}
|
|
|
|
for (const auto &name : std::as_const(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(QStringView(name.id), ast->identifierToken);
|
|
if (name.id == QLatin1String("arguments"))
|
|
_context->usesArgumentsObject = Context::UsesArgumentsObject::NotUsed;
|
|
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, declarationLocation)) {
|
|
_cg->throwSyntaxError(ast->identifierToken, QStringLiteral("Identifier %1 has already been declared").arg(name.id));
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool ScanFunctions::visit(IdentifierExpression *ast)
|
|
{
|
|
Q_ASSERT(_context);
|
|
checkName(ast->name, ast->identifierToken);
|
|
if (_context->usesArgumentsObject == Context::UsesArgumentsObject::Unknown && ast->name == QLatin1String("arguments"))
|
|
_context->usesArgumentsObject = Context::UsesArgumentsObject::Used;
|
|
_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, expr->identifierToken.length > 0
|
|
? FunctionNameContext::Inner
|
|
: FunctionNameContext::None)) {
|
|
return false;
|
|
}
|
|
Node::accept(expr->formals, this);
|
|
Node::accept(expr->body, this);
|
|
leaveEnvironment();
|
|
return false;
|
|
} else {
|
|
SourceLocation firstToken = ast->firstSourceLocation();
|
|
if (QStringView{_sourceCode}.mid(firstToken.offset, firstToken.length) == QLatin1String("function")) {
|
|
_cg->throwSyntaxError(firstToken, QStringLiteral("unexpected token"));
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool ScanFunctions::visit(FunctionExpression *ast)
|
|
{
|
|
return enterFunction(ast, ast->identifierToken.length > 0
|
|
? FunctionNameContext::Inner
|
|
: FunctionNameContext::None);
|
|
}
|
|
|
|
bool ScanFunctions::visit(ClassExpression *ast)
|
|
{
|
|
enterEnvironment(ast, ContextType::Block, QStringLiteral("%Class"));
|
|
Q_ASSERT(_context);
|
|
_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)
|
|
{
|
|
Q_ASSERT(_context);
|
|
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 *)
|
|
{
|
|
Q_ASSERT(_context);
|
|
Context *c = _context;
|
|
bool needContext = false;
|
|
while (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;
|
|
}
|
|
Q_ASSERT(_context);
|
|
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, FunctionNameContext nameContext)
|
|
{
|
|
Q_ASSERT(_context);
|
|
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, nameContext);
|
|
}
|
|
|
|
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, FunctionNameContext::Outer);
|
|
}
|
|
|
|
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) {
|
|
Q_ASSERT(_context);
|
|
_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 *)
|
|
{
|
|
Q_ASSERT(_context);
|
|
_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)
|
|
{
|
|
Q_ASSERT(_context);
|
|
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)
|
|
{
|
|
Q_ASSERT(_context);
|
|
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,
|
|
FunctionNameContext nameContext)
|
|
{
|
|
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 (nameContext == FunctionNameContext::Outer) {
|
|
if (!outerContext->addLocalVar(name, Context::FunctionDefinition, VariableScope::Var,
|
|
expr, expr->identifierToken)) {
|
|
_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::UsesArgumentsObject::NotUsed;
|
|
}
|
|
|
|
Q_ASSERT(_context);
|
|
_context->name = name;
|
|
if (formals && formals->containsName(QStringLiteral("arguments")))
|
|
_context->usesArgumentsObject = Context::UsesArgumentsObject::NotUsed;
|
|
if (expr) {
|
|
if (expr->isArrowFunction)
|
|
_context->isArrowFunction = true;
|
|
else if (expr->isGenerator)
|
|
_context->isGenerator = true;
|
|
|
|
if (expr->typeAnnotation)
|
|
_context->returnType = expr->typeAnnotation->type;
|
|
}
|
|
|
|
|
|
if (nameContext == FunctionNameContext::Inner
|
|
&& (!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 auto &arg = boundNames.at(i);
|
|
if (_context->isStrict || !isSimpleParameterList) {
|
|
bool duplicate = (boundNames.indexOf(arg.id, i + 1) != -1);
|
|
if (duplicate) {
|
|
_cg->throwSyntaxError(formals->firstSourceLocation(), QStringLiteral("Duplicate parameter name '%1' is not allowed.").arg(arg.id));
|
|
return false;
|
|
}
|
|
}
|
|
if (_context->isStrict) {
|
|
if (arg.id == QLatin1String("eval") || arg.id == QLatin1String("arguments")) {
|
|
_cg->throwSyntaxError(formals->firstSourceLocation(), QStringLiteral("'%1' cannot be used as parameter name in strict mode").arg(arg.id));
|
|
return false;
|
|
}
|
|
}
|
|
if (!_context->arguments.contains(arg.id)) {
|
|
_context->addLocalVar(arg.id, Context::VariableDefinition, VariableScope::Var, nullptr,
|
|
QQmlJS::SourceLocation(), arg.isInjected());
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
enum class Iteration { Continue, Break };
|
|
|
|
struct ContextCounter
|
|
{
|
|
Q_DISABLE_COPY_MOVE(ContextCounter)
|
|
#ifdef QT_NO_DEBUG
|
|
ContextCounter(Module *) {}
|
|
void operator++() {}
|
|
void dismiss() {}
|
|
#else
|
|
ContextCounter(Module *m) : module(m) {}
|
|
~ContextCounter() { Q_ASSERT(numContexts == module->contextMap.size()); }
|
|
|
|
void operator++() { numContexts++; }
|
|
void dismiss() { numContexts = module->contextMap.size(); }
|
|
|
|
private:
|
|
Module *module = nullptr;
|
|
qsizetype numContexts = 0;
|
|
#endif
|
|
};
|
|
|
|
|
|
template<typename Action>
|
|
void forEachContext(Module *m, Action &&action)
|
|
{
|
|
ContextCounter counter(m);
|
|
|
|
if (!m->rootContext)
|
|
return;
|
|
|
|
QVarLengthArray<Context *> stack {m->rootContext};
|
|
do {
|
|
Context *current = stack.back();
|
|
stack.pop_back();
|
|
|
|
for (Context *child : std::as_const(current->nestedContexts))
|
|
stack.push_back(child);
|
|
|
|
if (action(current) == Iteration::Break) {
|
|
counter.dismiss();
|
|
return;
|
|
}
|
|
|
|
++counter;
|
|
} while (!stack.empty());
|
|
}
|
|
|
|
void ScanFunctions::calcEscapingVariables()
|
|
{
|
|
Module *m = _cg->_module;
|
|
|
|
forEachContext(m, [](Context *inner) {
|
|
if (inner->usesArgumentsObject != Context::UsesArgumentsObject::Used)
|
|
return Iteration::Continue;
|
|
if (inner->contextType != ContextType::Block && !inner->isArrowFunction)
|
|
return Iteration::Continue;
|
|
Context *c = inner->parent;
|
|
while (c && (c->contextType == ContextType::Block || c->isArrowFunction))
|
|
c = c->parent;
|
|
if (c)
|
|
c->usesArgumentsObject = Context::UsesArgumentsObject::Used;
|
|
inner->usesArgumentsObject = Context::UsesArgumentsObject::NotUsed;
|
|
return Iteration::Continue;
|
|
});
|
|
|
|
forEachContext(m, [](Context *inner) {
|
|
if (!inner->parent || inner->usesArgumentsObject == Context::UsesArgumentsObject::Unknown)
|
|
inner->usesArgumentsObject = Context::UsesArgumentsObject::NotUsed;
|
|
if (inner->usesArgumentsObject == Context::UsesArgumentsObject::Used) {
|
|
QString arguments = QStringLiteral("arguments");
|
|
inner->addLocalVar(arguments, Context::VariableDeclaration, AST::VariableScope::Var);
|
|
if (!inner->isStrict) {
|
|
inner->argumentsCanEscape = true;
|
|
inner->requiresExecutionContext = true;
|
|
}
|
|
}
|
|
return Iteration::Continue;
|
|
});
|
|
|
|
forEachContext(m, [](Context *c) {
|
|
if (c->contextType != ContextType::ESModule)
|
|
return Iteration::Continue;
|
|
for (const auto &entry: std::as_const(c->exportEntries)) {
|
|
auto mIt = c->members.constFind(entry.localName);
|
|
if (mIt != c->members.constEnd())
|
|
mIt->canEscape = true;
|
|
}
|
|
return Iteration::Break;
|
|
});
|
|
|
|
forEachContext(m, [](Context *inner) {
|
|
for (const QString &var : std::as_const(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->hasArgument(var)) {
|
|
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;
|
|
}
|
|
|
|
return Iteration::Continue;
|
|
});
|
|
|
|
forEachContext(m, [m](Context *c) {
|
|
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 : std::as_const(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.size() > 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 : std::as_const(c->members))
|
|
m.canEscape = true;
|
|
}
|
|
|
|
return Iteration::Continue;
|
|
});
|
|
|
|
static const bool showEscapingVars = qEnvironmentVariableIsSet("QV4_SHOW_ESCAPING_VARS");
|
|
if (showEscapingVars) {
|
|
qDebug() << "==== escaping variables ====";
|
|
for (Context *c : std::as_const(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();
|
|
}
|