Revision control

Copy as Markdown

// Copyright (C) 2017 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 "qv4codegen_p.h"
#include "qv4compilercontext_p.h"
#include "qv4bytecodegenerator_p.h"
#include <QtQml/private/qv4calldata_p.h>
QT_USE_NAMESPACE
using namespace QV4;
using namespace QV4::Compiler;
using namespace QQmlJS::AST;
using namespace QQmlJS;
QT_BEGIN_NAMESPACE
Context *Module::newContext(Node *node, Context *parent, ContextType contextType)
{
Q_ASSERT(!contextMap.contains(node));
Context *c = new Context(parent, contextType);
if (node) {
SourceLocation loc = node->firstSourceLocation();
c->line = loc.startLine;
c->column = loc.startColumn;
}
contextMap.insert(node, c);
if (!parent)
rootContext = c;
else {
parent->nestedContexts.append(c);
c->isStrict = parent->isStrict;
}
return c;
}
bool Context::Member::requiresTDZCheck(const SourceLocation &accessLocation, bool accessAcrossContextBoundaries) const
{
if (!isLexicallyScoped())
return false;
if (accessAcrossContextBoundaries)
return true;
if (!accessLocation.isValid() || !declarationLocation.isValid())
return true;
return accessLocation.begin() < declarationLocation.end();
}
bool Context::addLocalVar(
const QString &name, Context::MemberType type, VariableScope scope,
FunctionExpression *function, const QQmlJS::SourceLocation &declarationLocation,
bool isInjected)
{
// ### can this happen?
if (name.isEmpty())
return true;
if (type != FunctionDefinition) {
if (formals && formals->containsName(name))
return (scope == VariableScope::Var);
}
if (!isCatchBlock || name != caughtVariable) {
MemberMap::iterator it = members.find(name);
if (it != members.end()) {
if ((*it).type == ThisFunctionName) {
// The name of a named function expression is bound in an implicit
// scope of its own, distinct from the function's parameter, var and
// lexical scope. Any real declaration sharing that name therefore
// shadows it rather than conflicting with it, so let it replace the
// placeholder entry below instead of rejecting the declaration.
members.erase(it);
} else {
if (scope != VariableScope::Var || (*it).scope != VariableScope::Var)
return false;
if ((*it).type <= type) {
(*it).type = type;
(*it).function = function;
}
return true;
}
}
}
// hoist var declarations to the function level
if (contextType == ContextType::Block && (scope == VariableScope::Var && type != MemberType::FunctionDefinition))
return parent->addLocalVar(name, type, scope, function, declarationLocation);
Member m;
m.type = type;
m.function = function;
m.scope = scope;
m.declarationLocation = declarationLocation;
m.isInjected = isInjected;
members.insert(name, m);
return true;
}
// A formal parameter whose default value initializer references a
// parameter of the same formal parameter list (itself, or one that
// hasn't been bound yet) can't be resolved as a plain argument: reading
// it before its own initialization must throw, the same way it would
// for a "let" binding used before declaration. Give such a parameter a
// real lexically-scoped Member (bypassing addLocalVar()'s formal-name
// collision guard) so it goes through the normal TDZ machinery instead of
// the fast argument-register path.
// Used to detect self- or forward-references to another
// (possibly not-yet-bound) parameter of the same formal parameter list, e.g.
// "(x = x) => {}" or "(a = b, b) => {}".
void Context::promoteFormalParameterForTDZ(const QString &name, const QQmlJS::SourceLocation &loc)
{
if (members.contains(name))
return;
Member m;
m.type = VariableDefinition;
m.scope = VariableScope::Let;
m.declarationLocation = loc;
m.isFormalParameterTDZPromotion = true;
members.insert(name, m);
}
Context::ResolvedName Context::resolveName(const QString &name, const QQmlJS::SourceLocation &accessLocation)
{
int scope = 0;
Context *c = this;
ResolvedName result;
while (c) {
if (c->isWithBlock)
return result;
Context::Member m = c->findMember(name);
if (!c->parent && m.index < 0)
break;
if (m.type != Context::UndefinedMember) {
result.type = m.canEscape ? ResolvedName::Local : ResolvedName::Stack;
result.memberType = m.type;
result.scope = scope;
result.index = m.index;
result.isConst = (m.scope == VariableScope::Const);
result.requiresTDZCheck = m.requiresTDZCheck(accessLocation, c != this) || c->isCaseBlock();
if (c->isStrict && (name == QLatin1String("arguments") || name == QLatin1String("eval")))
result.isArgOrEval = true;
result.declarationLocation = m.declarationLocation;
result.isInjected = m.isInjected;
return result;
}
const int argIdx = c->findArgument(name, &result.isInjected);
if (argIdx != -1) {
if (c->argumentsCanEscape) {
result.index = argIdx + c->locals.size();
result.scope = scope;
result.type = ResolvedName::Local;
result.isConst = false;
return result;
} else {
result.index = argIdx + sizeof(CallData) / sizeof(StaticValue) - 1;
result.scope = 0;
result.type = ResolvedName::Stack;
result.isConst = false;
return result;
}
}
if (c->hasDirectEval) {
Q_ASSERT(!c->isStrict && c->contextType != ContextType::Block);
return result;
}
if (c->requiresExecutionContext)
++scope;
c = c->parent;
}
if (!c)
return result;
if (c->contextType == ContextType::ESModule) {
for (int i = 0; i < c->importEntries.size(); ++i) {
if (c->importEntries.at(i).localName == name) {
result.index = i;
result.type = ResolvedName::Import;
result.isConst = true;
// We don't know at compile time whether the imported value is let/const or not.
result.requiresTDZCheck = true;
return result;
}
}
}
// ### can we relax the restrictions here?
if (c->contextType == ContextType::Eval)
return result;
if (c->contextType == ContextType::Binding || c->contextType == ContextType::ScriptImportedByQML)
result.type = ResolvedName::QmlGlobal;
else
result.type = ResolvedName::Global;
return result;
}
void Context::emitBlockHeader(Codegen *codegen)
{
using Instruction = Moth::Instruction;
Moth::BytecodeGenerator *bytecodeGenerator = codegen->generator();
setupFunctionIndices(bytecodeGenerator);
if (requiresExecutionContext) {
if (blockIndex < 0) {
codegen->module()->blocks.append(this);
blockIndex = codegen->module()->blocks.size() - 1;
}
if (contextType == ContextType::Global) {
Instruction::PushScriptContext scriptContext;
scriptContext.index = blockIndex;
bytecodeGenerator->addInstruction(scriptContext);
} else if (contextType == ContextType::Block || (contextType == ContextType::Eval && !isStrict)) {
if (isCatchBlock) {
Instruction::PushCatchContext catchContext;
catchContext.index = blockIndex;
catchContext.name = codegen->registerString(caughtVariable);
bytecodeGenerator->addInstruction(catchContext);
} else {
Instruction::PushBlockContext blockContext;
blockContext.index = blockIndex;
bytecodeGenerator->addInstruction(blockContext);
}
} else if (contextType != ContextType::ESModule && contextType != ContextType::ScriptImportedByQML) {
Instruction::CreateCallContext createContext;
bytecodeGenerator->addInstruction(createContext);
}
}
if (contextType == ContextType::Block && sizeOfRegisterTemporalDeadZone > 0) {
Instruction::InitializeBlockDeadTemporalZone tdzInit;
tdzInit.firstReg = registerOffset + nRegisters - sizeOfRegisterTemporalDeadZone;
tdzInit.count = sizeOfRegisterTemporalDeadZone;
bytecodeGenerator->addInstruction(tdzInit);
}
if (usesThis) {
Q_ASSERT(!isStrict);
// make sure we convert this to an object
Instruction::ConvertThisToObject convert;
bytecodeGenerator->addInstruction(convert);
}
if (innerFunctionAccessesThis) {
Instruction::LoadReg load;
load.reg = CallData::This;
bytecodeGenerator->addInstruction(load);
Codegen::Reference r = codegen->referenceForName(QStringLiteral("this"), true);
r.storeConsumeAccumulator();
}
if (innerFunctionAccessesNewTarget) {
Instruction::LoadReg load;
load.reg = CallData::NewTarget;
bytecodeGenerator->addInstruction(load);
Codegen::Reference r = codegen->referenceForName(QStringLiteral("new.target"), true);
r.storeConsumeAccumulator();
}
if (contextType == ContextType::Global || contextType == ContextType::ScriptImportedByQML || (contextType == ContextType::Eval && !isStrict)) {
// variables in global code are properties of the global context object, not locals as with other functions.
for (Context::MemberMap::const_iterator it = members.constBegin(), cend = members.constEnd(); it != cend; ++it) {
if (it->isLexicallyScoped())
continue;
const QString &local = it.key();
Instruction::DeclareVar declareVar;
declareVar.isDeletable = (contextType == ContextType::Eval);
declareVar.varName = codegen->registerString(local);
bytecodeGenerator->addInstruction(declareVar);
}
}
if (contextType == ContextType::Function || contextType == ContextType::Binding || contextType == ContextType::ESModule) {
for (Context::MemberMap::iterator it = members.begin(), end = members.end(); it != end; ++it) {
if (it->canEscape && it->type == Context::ThisFunctionName) {
// move the function from the stack to the call context
Instruction::LoadReg load;
load.reg = CallData::Function;
bytecodeGenerator->addInstruction(load);
Instruction::StoreLocal store;
store.index = it->index;
bytecodeGenerator->addInstruction(store);
}
}
}
if (usesArgumentsObject == Context::UsesArgumentsObject::Used) {
Q_ASSERT(contextType != ContextType::Block);
if (isStrict || (formals && !formals->isSimpleParameterList())) {
Instruction::CreateUnmappedArgumentsObject setup;
bytecodeGenerator->addInstruction(setup);
} else {
Instruction::CreateMappedArgumentsObject setup;
bytecodeGenerator->addInstruction(setup);
}
codegen->referenceForName(QStringLiteral("arguments"), false).storeConsumeAccumulator();
}
for (const Context::Member &member : std::as_const(members)) {
if (member.function) {
const int function = codegen->defineFunction(member.function->name.toString(), member.function, member.function->formals, member.function->body);
codegen->loadClosure(function);
Codegen::Reference r = codegen->referenceForName(member.function->name.toString(), true);
r.storeConsumeAccumulator();
}
}
}
void Context::emitBlockFooter(Codegen *codegen)
{
using Instruction = Moth::Instruction;
Moth::BytecodeGenerator *bytecodeGenerator = codegen->generator();
if (!requiresExecutionContext)
return;
QT_WARNING_PUSH
QT_WARNING_DISABLE_GCC("-Wmaybe-uninitialized") // the loads below are empty structs.
if (contextType == ContextType::Global)
bytecodeGenerator->addInstruction(Instruction::PopScriptContext());
else if (contextType != ContextType::ESModule && contextType != ContextType::ScriptImportedByQML)
bytecodeGenerator->addInstruction(Instruction::PopContext());
QT_WARNING_POP
}
void Context::setupFunctionIndices(Moth::BytecodeGenerator *bytecodeGenerator)
{
if (registerOffset != -1) {
// already computed, check for consistency
Q_ASSERT(registerOffset == bytecodeGenerator->currentRegister());
bytecodeGenerator->newRegisterArray(nRegisters);
return;
}
Q_ASSERT(locals.size() == 0);
Q_ASSERT(nRegisters == 0);
registerOffset = bytecodeGenerator->currentRegister();
QList<Context::MemberMap::Iterator> localsInTDZ;
const auto registerLocal = [this, &localsInTDZ](Context::MemberMap::iterator member) {
if (member->isLexicallyScoped()) {
localsInTDZ << member;
} else {
member->index = locals.size();
locals.append(member.key());
}
};
QList<Context::MemberMap::Iterator> registersInTDZ;
const auto allocateRegister = [bytecodeGenerator, &registersInTDZ](Context::MemberMap::iterator member) {
if (member->isLexicallyScoped())
registersInTDZ << member;
else
member->index = bytecodeGenerator->newRegister();
};
switch (contextType) {
case ContextType::ESModule:
case ContextType::Block:
case ContextType::Function:
case ContextType::Binding: {
for (Context::MemberMap::iterator it = members.begin(), end = members.end(); it != end; ++it) {
if (it->canEscape) {
registerLocal(it);
} else {
if (it->type == Context::ThisFunctionName)
it->index = CallData::Function;
else
allocateRegister(it);
}
}
break;
}
case ContextType::Global:
case ContextType::ScriptImportedByQML:
case ContextType::Eval:
for (Context::MemberMap::iterator it = members.begin(), end = members.end(); it != end; ++it) {
if (!it->isLexicallyScoped() && (contextType == ContextType::Global || contextType == ContextType::ScriptImportedByQML || !isStrict))
continue;
if (it->canEscape)
registerLocal(it);
else
allocateRegister(it);
}
break;
}
sizeOfLocalTemporalDeadZone = localsInTDZ.size();
for (auto &member: std::as_const(localsInTDZ)) {
member->index = locals.size();
locals.append(member.key());
}
if (contextType == ContextType::ESModule && !localNameForDefaultExport.isEmpty()) {
if (!members.contains(localNameForDefaultExport)) {
// allocate a local slot for the default export, to be used in
// CodeGen::visit(ExportDeclaration*).
locals.append(localNameForDefaultExport);
++sizeOfLocalTemporalDeadZone;
}
}
sizeOfRegisterTemporalDeadZone = registersInTDZ.size();
firstTemporalDeadZoneRegister = bytecodeGenerator->currentRegister();
for (auto &member: std::as_const(registersInTDZ))
member->index = bytecodeGenerator->newRegister();
nRegisters = bytecodeGenerator->currentRegister() - registerOffset;
}
QT_END_NAMESPACE