fix: comprehensive boot warnings and exceptions — fixable silenced, unfixable diagnosed

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
This commit is contained in:
2026-05-05 20:20:37 +01:00
parent a5f97b6632
commit f31522130f
81834 changed files with 11051982 additions and 108 deletions
@@ -0,0 +1,953 @@
// 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();
}