2997 lines
92 KiB
C++
2997 lines
92 KiB
C++
/****************************************************************************
|
|
**
|
|
** 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 "qv4codegen_p.h"
|
|
#include "qv4util_p.h"
|
|
#include "qv4engine_p.h"
|
|
|
|
#include <QtCore/QCoreApplication>
|
|
#include <QtCore/QStringList>
|
|
#include <QtCore/QSet>
|
|
#include <QtCore/QBuffer>
|
|
#include <QtCore/QBitArray>
|
|
#include <QtCore/QLinkedList>
|
|
#include <QtCore/QStack>
|
|
#include <private/qqmljsast_p.h>
|
|
#include <private/qv4string_p.h>
|
|
#include <private/qv4value_p.h>
|
|
|
|
#ifndef V4_BOOTSTRAP
|
|
#include <qv4context_p.h>
|
|
#endif
|
|
|
|
#include <cmath>
|
|
#include <iostream>
|
|
|
|
#ifdef CONST
|
|
#undef CONST
|
|
#endif
|
|
|
|
using namespace QV4;
|
|
using namespace QQmlJS;
|
|
using namespace AST;
|
|
|
|
static inline void setLocation(IR::Stmt *s, const SourceLocation &loc)
|
|
{
|
|
if (s && loc.isValid())
|
|
s->location = loc;
|
|
}
|
|
|
|
static bool cjumpCanHandle(IR::AluOp op)
|
|
{
|
|
switch (op) {
|
|
case IR::OpIn:
|
|
case IR::OpInstanceof:
|
|
case IR::OpEqual:
|
|
case IR::OpNotEqual:
|
|
case IR::OpGe:
|
|
case IR::OpGt:
|
|
case IR::OpLe:
|
|
case IR::OpLt:
|
|
case IR::OpStrictEqual:
|
|
case IR::OpStrictNotEqual:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
Codegen::ScanFunctions::ScanFunctions(Codegen *cg, const QString &sourceCode, CompilationMode defaultProgramMode)
|
|
: _cg(cg)
|
|
, _sourceCode(sourceCode)
|
|
, _variableEnvironment(0)
|
|
, _allowFuncDecls(true)
|
|
, defaultProgramMode(defaultProgramMode)
|
|
{
|
|
}
|
|
|
|
void Codegen::ScanFunctions::operator()(Node *node)
|
|
{
|
|
if (node)
|
|
node->accept(this);
|
|
}
|
|
|
|
void Codegen::ScanFunctions::enterEnvironment(Node *node, CompilationMode compilationMode)
|
|
{
|
|
Environment *e = _cg->newEnvironment(node, _variableEnvironment, compilationMode);
|
|
if (!e->isStrict)
|
|
e->isStrict = _cg->_strictMode;
|
|
_envStack.append(e);
|
|
_variableEnvironment = e;
|
|
}
|
|
|
|
void Codegen::ScanFunctions::leaveEnvironment()
|
|
{
|
|
_envStack.pop();
|
|
_variableEnvironment = _envStack.isEmpty() ? 0 : _envStack.top();
|
|
}
|
|
|
|
void Codegen::ScanFunctions::checkDirectivePrologue(SourceElements *ast)
|
|
{
|
|
for (SourceElements *it = ast; it; it = it->next) {
|
|
if (StatementSourceElement *stmt = cast<StatementSourceElement *>(it->element)) {
|
|
if (ExpressionStatement *expr = cast<ExpressionStatement *>(stmt->statement)) {
|
|
if (StringLiteral *strLit = cast<StringLiteral *>(expr->expression)) {
|
|
// Use the source code, because the StringLiteral's
|
|
// value might have escape sequences in it, which is not
|
|
// allowed.
|
|
if (strLit->literalToken.length < 2)
|
|
continue;
|
|
QStringRef str = _sourceCode.midRef(strLit->literalToken.offset + 1, strLit->literalToken.length - 2);
|
|
if (str == QLatin1String("use strict")) {
|
|
_variableEnvironment->isStrict = true;
|
|
} else {
|
|
// TODO: give a warning.
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Codegen::ScanFunctions::checkName(const QStringRef &name, const SourceLocation &loc)
|
|
{
|
|
if (_variableEnvironment->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"));
|
|
}
|
|
}
|
|
}
|
|
void Codegen::ScanFunctions::checkForArguments(AST::FormalParameterList *parameters)
|
|
{
|
|
while (parameters) {
|
|
if (parameters->name == QLatin1String("arguments"))
|
|
_variableEnvironment->usesArgumentsObject = Environment::ArgumentsObjectNotUsed;
|
|
parameters = parameters->next;
|
|
}
|
|
}
|
|
|
|
bool Codegen::ScanFunctions::visit(Program *ast)
|
|
{
|
|
enterEnvironment(ast, defaultProgramMode);
|
|
checkDirectivePrologue(ast->elements);
|
|
return true;
|
|
}
|
|
|
|
void Codegen::ScanFunctions::endVisit(Program *)
|
|
{
|
|
leaveEnvironment();
|
|
}
|
|
|
|
bool Codegen::ScanFunctions::visit(CallExpression *ast)
|
|
{
|
|
if (! _variableEnvironment->hasDirectEval) {
|
|
if (IdentifierExpression *id = cast<IdentifierExpression *>(ast->base)) {
|
|
if (id->name == QLatin1String("eval")) {
|
|
if (_variableEnvironment->usesArgumentsObject == Environment::ArgumentsObjectUnknown)
|
|
_variableEnvironment->usesArgumentsObject = Environment::ArgumentsObjectUsed;
|
|
_variableEnvironment->hasDirectEval = true;
|
|
}
|
|
}
|
|
}
|
|
int argc = 0;
|
|
for (ArgumentList *it = ast->arguments; it; it = it->next)
|
|
++argc;
|
|
_variableEnvironment->maxNumberOfArguments = qMax(_variableEnvironment->maxNumberOfArguments, argc);
|
|
return true;
|
|
}
|
|
|
|
bool Codegen::ScanFunctions::visit(NewMemberExpression *ast)
|
|
{
|
|
int argc = 0;
|
|
for (ArgumentList *it = ast->arguments; it; it = it->next)
|
|
++argc;
|
|
_variableEnvironment->maxNumberOfArguments = qMax(_variableEnvironment->maxNumberOfArguments, argc);
|
|
return true;
|
|
}
|
|
|
|
bool Codegen::ScanFunctions::visit(ArrayLiteral *ast)
|
|
{
|
|
int index = 0;
|
|
for (ElementList *it = ast->elements; it; it = it->next) {
|
|
for (Elision *elision = it->elision; elision; elision = elision->next)
|
|
++index;
|
|
++index;
|
|
}
|
|
if (ast->elision) {
|
|
for (Elision *elision = ast->elision->next; elision; elision = elision->next)
|
|
++index;
|
|
}
|
|
_variableEnvironment->maxNumberOfArguments = qMax(_variableEnvironment->maxNumberOfArguments, index);
|
|
return true;
|
|
}
|
|
|
|
bool Codegen::ScanFunctions::visit(VariableDeclaration *ast)
|
|
{
|
|
if (_variableEnvironment->isStrict && (ast->name == QLatin1String("eval") || ast->name == QLatin1String("arguments")))
|
|
_cg->throwSyntaxError(ast->identifierToken, QStringLiteral("Variable name may not be eval or arguments in strict mode"));
|
|
checkName(ast->name, ast->identifierToken);
|
|
if (ast->name == QLatin1String("arguments"))
|
|
_variableEnvironment->usesArgumentsObject = Environment::ArgumentsObjectNotUsed;
|
|
if (ast->scope == AST::VariableDeclaration::VariableScope::ReadOnlyBlockScope && !ast->expression) {
|
|
_cg->throwSyntaxError(ast->identifierToken, QStringLiteral("Missing initializer in const declaration"));
|
|
return false;
|
|
}
|
|
QString name = ast->name.toString();
|
|
const Environment::Member *m = 0;
|
|
if (_variableEnvironment->memberInfo(name, &m)) {
|
|
if (m->isLexicallyScoped() || ast->isLexicallyScoped()) {
|
|
_cg->throwSyntaxError(ast->identifierToken, QStringLiteral("Identifier %1 has already been declared").arg(name));
|
|
return false;
|
|
}
|
|
}
|
|
_variableEnvironment->enter(ast->name.toString(), ast->expression ? Environment::VariableDefinition : Environment::VariableDeclaration, ast->scope);
|
|
return true;
|
|
}
|
|
|
|
bool Codegen::ScanFunctions::visit(IdentifierExpression *ast)
|
|
{
|
|
checkName(ast->name, ast->identifierToken);
|
|
if (_variableEnvironment->usesArgumentsObject == Environment::ArgumentsObjectUnknown && ast->name == QLatin1String("arguments"))
|
|
_variableEnvironment->usesArgumentsObject = Environment::ArgumentsObjectUsed;
|
|
return true;
|
|
}
|
|
|
|
bool Codegen::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"));
|
|
|
|
enterFunction(expr, /*enterName*/ true);
|
|
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 Codegen::ScanFunctions::visit(FunctionExpression *ast)
|
|
{
|
|
enterFunction(ast, /*enterName*/ false);
|
|
return true;
|
|
}
|
|
|
|
void Codegen::ScanFunctions::enterFunction(FunctionExpression *ast, bool enterName, bool isExpression)
|
|
{
|
|
if (_variableEnvironment->isStrict && (ast->name == QLatin1String("eval") || ast->name == QLatin1String("arguments")))
|
|
_cg->throwSyntaxError(ast->identifierToken, QStringLiteral("Function name may not be eval or arguments in strict mode"));
|
|
enterFunction(ast, ast->name.toString(), ast->formals, ast->body, enterName ? ast : 0, isExpression);
|
|
}
|
|
|
|
void Codegen::ScanFunctions::endVisit(FunctionExpression *)
|
|
{
|
|
leaveEnvironment();
|
|
}
|
|
|
|
bool Codegen::ScanFunctions::visit(ObjectLiteral *ast)
|
|
{
|
|
int argc = 0;
|
|
for (PropertyAssignmentList *it = ast->properties; it; it = it->next) {
|
|
QString key = it->assignment->name->asString();
|
|
if (QV4::String::toArrayIndex(key) != UINT_MAX)
|
|
++argc;
|
|
++argc;
|
|
if (AST::cast<AST::PropertyGetterSetter *>(it->assignment))
|
|
++argc;
|
|
}
|
|
_variableEnvironment->maxNumberOfArguments = qMax(_variableEnvironment->maxNumberOfArguments, argc);
|
|
|
|
TemporaryBoolAssignment allowFuncDecls(_allowFuncDecls, true);
|
|
Node::accept(ast->properties, this);
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::ScanFunctions::visit(PropertyGetterSetter *ast)
|
|
{
|
|
TemporaryBoolAssignment allowFuncDecls(_allowFuncDecls, true);
|
|
enterFunction(ast, QString(), ast->formals, ast->functionBody, /*FunctionExpression*/0, /*isExpression*/false);
|
|
return true;
|
|
}
|
|
|
|
void Codegen::ScanFunctions::endVisit(PropertyGetterSetter *)
|
|
{
|
|
leaveEnvironment();
|
|
}
|
|
|
|
bool Codegen::ScanFunctions::visit(FunctionDeclaration *ast)
|
|
{
|
|
enterFunction(ast, /*enterName*/ true, /*isExpression */false);
|
|
return true;
|
|
}
|
|
|
|
void Codegen::ScanFunctions::endVisit(FunctionDeclaration *)
|
|
{
|
|
leaveEnvironment();
|
|
}
|
|
|
|
bool Codegen::ScanFunctions::visit(WithStatement *ast)
|
|
{
|
|
if (_variableEnvironment->isStrict) {
|
|
_cg->throwSyntaxError(ast->withToken, QStringLiteral("'with' statement is not allowed in strict mode"));
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Codegen::ScanFunctions::visit(DoWhileStatement *ast) {
|
|
{
|
|
TemporaryBoolAssignment allowFuncDecls(_allowFuncDecls, !_variableEnvironment->isStrict);
|
|
Node::accept(ast->statement, this);
|
|
}
|
|
Node::accept(ast->expression, this);
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::ScanFunctions::visit(ForStatement *ast) {
|
|
Node::accept(ast->initialiser, this);
|
|
Node::accept(ast->condition, this);
|
|
Node::accept(ast->expression, this);
|
|
|
|
TemporaryBoolAssignment allowFuncDecls(_allowFuncDecls, !_variableEnvironment->isStrict);
|
|
Node::accept(ast->statement, this);
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::ScanFunctions::visit(LocalForStatement *ast) {
|
|
Node::accept(ast->declarations, this);
|
|
Node::accept(ast->condition, this);
|
|
Node::accept(ast->expression, this);
|
|
|
|
TemporaryBoolAssignment allowFuncDecls(_allowFuncDecls, !_variableEnvironment->isStrict);
|
|
Node::accept(ast->statement, this);
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::ScanFunctions::visit(ForEachStatement *ast) {
|
|
Node::accept(ast->initialiser, this);
|
|
Node::accept(ast->expression, this);
|
|
|
|
TemporaryBoolAssignment allowFuncDecls(_allowFuncDecls, !_variableEnvironment->isStrict);
|
|
Node::accept(ast->statement, this);
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::ScanFunctions::visit(LocalForEachStatement *ast) {
|
|
Node::accept(ast->declaration, this);
|
|
Node::accept(ast->expression, this);
|
|
|
|
TemporaryBoolAssignment allowFuncDecls(_allowFuncDecls, !_variableEnvironment->isStrict);
|
|
Node::accept(ast->statement, this);
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::ScanFunctions::visit(ThisExpression *)
|
|
{
|
|
_variableEnvironment->usesThis = true;
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::ScanFunctions::visit(Block *ast) {
|
|
TemporaryBoolAssignment allowFuncDecls(_allowFuncDecls, _variableEnvironment->isStrict ? false : _allowFuncDecls);
|
|
Node::accept(ast->statements, this);
|
|
return false;
|
|
}
|
|
|
|
void Codegen::ScanFunctions::enterFunction(Node *ast, const QString &name, FormalParameterList *formals, FunctionBody *body, FunctionExpression *expr, bool isExpression)
|
|
{
|
|
bool wasStrict = false;
|
|
if (_variableEnvironment) {
|
|
_variableEnvironment->hasNestedFunctions = true;
|
|
// The identifier of a function expression cannot be referenced from the enclosing environment.
|
|
if (expr)
|
|
_variableEnvironment->enter(name, Environment::FunctionDefinition, AST::VariableDeclaration::FunctionScope, expr);
|
|
if (name == QLatin1String("arguments"))
|
|
_variableEnvironment->usesArgumentsObject = Environment::ArgumentsObjectNotUsed;
|
|
wasStrict = _variableEnvironment->isStrict;
|
|
}
|
|
|
|
enterEnvironment(ast, FunctionCode);
|
|
checkForArguments(formals);
|
|
|
|
_variableEnvironment->isNamedFunctionExpression = isExpression && !name.isEmpty();
|
|
_variableEnvironment->formals = formals;
|
|
|
|
if (body)
|
|
checkDirectivePrologue(body->elements);
|
|
|
|
if (wasStrict || _variableEnvironment->isStrict) {
|
|
QStringList args;
|
|
for (FormalParameterList *it = formals; it; it = it->next) {
|
|
QString arg = it->name.toString();
|
|
if (args.contains(arg)) {
|
|
_cg->throwSyntaxError(it->identifierToken, QStringLiteral("Duplicate parameter name '%1' is not allowed in strict mode").arg(arg));
|
|
return;
|
|
}
|
|
if (arg == QLatin1String("eval") || arg == QLatin1String("arguments")) {
|
|
_cg->throwSyntaxError(it->identifierToken, QStringLiteral("'%1' cannot be used as parameter name in strict mode").arg(arg));
|
|
return;
|
|
}
|
|
args += arg;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
Codegen::Codegen(bool strict)
|
|
: _module(0)
|
|
, _function(0)
|
|
, _block(0)
|
|
, _exitBlock(0)
|
|
, _returnAddress(0)
|
|
, _variableEnvironment(0)
|
|
, _loop(0)
|
|
, _labelledStatement(0)
|
|
, _scopeAndFinally(0)
|
|
, _strictMode(strict)
|
|
, _fileNameIsUrl(false)
|
|
, hasError(false)
|
|
{
|
|
}
|
|
|
|
void Codegen::generateFromProgram(const QString &fileName,
|
|
const QString &sourceCode,
|
|
Program *node,
|
|
QV4::IR::Module *module,
|
|
CompilationMode mode,
|
|
const QStringList &inheritedLocals)
|
|
{
|
|
Q_ASSERT(node);
|
|
|
|
_module = module;
|
|
_variableEnvironment = 0;
|
|
|
|
_module->setFileName(fileName);
|
|
|
|
ScanFunctions scan(this, sourceCode, mode);
|
|
scan(node);
|
|
|
|
defineFunction(QStringLiteral("%entry"), node, 0, node->elements, inheritedLocals);
|
|
qDeleteAll(_envMap);
|
|
_envMap.clear();
|
|
}
|
|
|
|
void Codegen::generateFromFunctionExpression(const QString &fileName,
|
|
const QString &sourceCode,
|
|
AST::FunctionExpression *ast,
|
|
QV4::IR::Module *module)
|
|
{
|
|
_module = module;
|
|
_module->setFileName(fileName);
|
|
_variableEnvironment = 0;
|
|
|
|
ScanFunctions scan(this, sourceCode, GlobalCode);
|
|
// fake a global environment
|
|
scan.enterEnvironment(0, FunctionCode);
|
|
scan(ast);
|
|
scan.leaveEnvironment();
|
|
|
|
defineFunction(ast->name.toString(), ast, ast->formals, ast->body ? ast->body->elements : 0);
|
|
|
|
qDeleteAll(_envMap);
|
|
_envMap.clear();
|
|
}
|
|
|
|
|
|
void Codegen::enterEnvironment(Node *node)
|
|
{
|
|
_variableEnvironment = _envMap.value(node);
|
|
Q_ASSERT(_variableEnvironment);
|
|
}
|
|
|
|
void Codegen::leaveEnvironment()
|
|
{
|
|
Q_ASSERT(_variableEnvironment);
|
|
_variableEnvironment = _variableEnvironment->parent;
|
|
}
|
|
|
|
void Codegen::enterLoop(Statement *node, IR::BasicBlock *breakBlock, IR::BasicBlock *continueBlock)
|
|
{
|
|
_loop = new Loop(node, breakBlock, continueBlock, _loop);
|
|
_loop->labelledStatement = _labelledStatement; // consume the enclosing labelled statement
|
|
_loop->scopeAndFinally = _scopeAndFinally;
|
|
_labelledStatement = 0;
|
|
}
|
|
|
|
void Codegen::leaveLoop()
|
|
{
|
|
Loop *current = _loop;
|
|
_loop = _loop->parent;
|
|
delete current;
|
|
}
|
|
|
|
IR::Expr *Codegen::member(IR::Expr *base, const QString *name)
|
|
{
|
|
if (hasError)
|
|
return 0;
|
|
|
|
if (base->asTemp() || base->asArgLocal())
|
|
return _block->MEMBER(base, name);
|
|
else {
|
|
const unsigned t = _block->newTemp();
|
|
move(_block->TEMP(t), base);
|
|
return _block->MEMBER(_block->TEMP(t), name);
|
|
}
|
|
}
|
|
|
|
IR::Expr *Codegen::subscript(IR::Expr *base, IR::Expr *index)
|
|
{
|
|
if (hasError)
|
|
return 0;
|
|
|
|
if (! base->asTemp() && !base->asArgLocal()) {
|
|
const unsigned t = _block->newTemp();
|
|
move(_block->TEMP(t), base);
|
|
base = _block->TEMP(t);
|
|
}
|
|
|
|
if (! index->asTemp() && !index->asArgLocal() && !index->asConst()) {
|
|
const unsigned t = _block->newTemp();
|
|
move(_block->TEMP(t), index);
|
|
index = _block->TEMP(t);
|
|
}
|
|
|
|
Q_ASSERT(base->asTemp() || base->asArgLocal());
|
|
Q_ASSERT(index->asTemp() || index->asArgLocal() || index->asConst());
|
|
return _block->SUBSCRIPT(base, index);
|
|
}
|
|
|
|
IR::Expr *Codegen::argument(IR::Expr *expr)
|
|
{
|
|
if (expr && !expr->asTemp()) {
|
|
const unsigned t = _block->newTemp();
|
|
move(_block->TEMP(t), expr);
|
|
expr = _block->TEMP(t);
|
|
}
|
|
return expr;
|
|
}
|
|
|
|
// keeps references alive, converts other expressions to temps
|
|
IR::Expr *Codegen::reference(IR::Expr *expr)
|
|
{
|
|
if (hasError)
|
|
return 0;
|
|
|
|
if (expr && !expr->asTemp() && !expr->asArgLocal() && !expr->asName() && !expr->asMember() && !expr->asSubscript()) {
|
|
const unsigned t = _block->newTemp();
|
|
move(_block->TEMP(t), expr);
|
|
expr = _block->TEMP(t);
|
|
}
|
|
return expr;
|
|
}
|
|
|
|
IR::Expr *Codegen::unop(IR::AluOp op, IR::Expr *expr, const SourceLocation &loc)
|
|
{
|
|
if (hasError)
|
|
return 0;
|
|
|
|
Q_ASSERT(op != IR::OpIncrement);
|
|
Q_ASSERT(op != IR::OpDecrement);
|
|
|
|
if (IR::Const *c = expr->asConst()) {
|
|
if (c->type == IR::NumberType) {
|
|
switch (op) {
|
|
case IR::OpNot:
|
|
return _block->CONST(IR::BoolType, !c->value);
|
|
case IR::OpUMinus:
|
|
return _block->CONST(IR::NumberType, -c->value);
|
|
case IR::OpUPlus:
|
|
return expr;
|
|
case IR::OpCompl:
|
|
return _block->CONST(IR::NumberType, ~QV4::Primitive::toInt32(c->value));
|
|
case IR::OpIncrement:
|
|
return _block->CONST(IR::NumberType, c->value + 1);
|
|
case IR::OpDecrement:
|
|
return _block->CONST(IR::NumberType, c->value - 1);
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!expr->asTemp() && !expr->asArgLocal()) {
|
|
const unsigned t = _block->newTemp();
|
|
setLocation(move(_block->TEMP(t), expr), loc);
|
|
expr = _block->TEMP(t);
|
|
}
|
|
Q_ASSERT(expr->asTemp() || expr->asArgLocal());
|
|
return _block->UNOP(op, expr);
|
|
}
|
|
|
|
IR::Expr *Codegen::binop(IR::AluOp op, IR::Expr *left, IR::Expr *right, const AST::SourceLocation &loc)
|
|
{
|
|
if (hasError)
|
|
return 0;
|
|
|
|
if (IR::Const *c1 = left->asConst()) {
|
|
if (IR::Const *c2 = right->asConst()) {
|
|
if ((c1->type & IR::NumberType) && (c2->type & IR::NumberType)) {
|
|
switch (op) {
|
|
case IR::OpAdd: return _block->CONST(IR::NumberType, c1->value + c2->value);
|
|
case IR::OpAnd: return _block->CONST(IR::BoolType, c1->value ? c2->value : 0);
|
|
case IR::OpBitAnd: return _block->CONST(IR::NumberType, int(c1->value) & int(c2->value));
|
|
case IR::OpBitOr: return _block->CONST(IR::NumberType, int(c1->value) | int(c2->value));
|
|
case IR::OpBitXor: return _block->CONST(IR::NumberType, int(c1->value) ^ int(c2->value));
|
|
case IR::OpDiv: return _block->CONST(IR::NumberType, c1->value / c2->value);
|
|
case IR::OpEqual: return _block->CONST(IR::BoolType, c1->value == c2->value);
|
|
case IR::OpNotEqual: return _block->CONST(IR::BoolType, c1->value != c2->value);
|
|
case IR::OpStrictEqual: return _block->CONST(IR::BoolType, c1->value == c2->value);
|
|
case IR::OpStrictNotEqual: return _block->CONST(IR::BoolType, c1->value != c2->value);
|
|
case IR::OpGe: return _block->CONST(IR::BoolType, c1->value >= c2->value);
|
|
case IR::OpGt: return _block->CONST(IR::BoolType, c1->value > c2->value);
|
|
case IR::OpLe: return _block->CONST(IR::BoolType, c1->value <= c2->value);
|
|
case IR::OpLt: return _block->CONST(IR::BoolType, c1->value < c2->value);
|
|
case IR::OpLShift: return _block->CONST(IR::NumberType, QV4::Primitive::toInt32(c1->value) << (QV4::Primitive::toUInt32(c2->value) & 0x1f));
|
|
case IR::OpMod: return _block->CONST(IR::NumberType, std::fmod(c1->value, c2->value));
|
|
case IR::OpMul: return _block->CONST(IR::NumberType, c1->value * c2->value);
|
|
case IR::OpOr: return _block->CONST(IR::NumberType, c1->value ? c1->value : c2->value);
|
|
case IR::OpRShift: return _block->CONST(IR::NumberType, QV4::Primitive::toInt32(c1->value) >> (QV4::Primitive::toUInt32(c2->value) & 0x1f));
|
|
case IR::OpSub: return _block->CONST(IR::NumberType, c1->value - c2->value);
|
|
case IR::OpURShift: return _block->CONST(IR::NumberType,QV4::Primitive::toUInt32(c1->value) >> (QV4::Primitive::toUInt32(c2->value) & 0x1f));
|
|
|
|
case IR::OpInstanceof:
|
|
case IR::OpIn:
|
|
break;
|
|
|
|
case IR::OpIfTrue: // unary ops
|
|
case IR::OpNot:
|
|
case IR::OpUMinus:
|
|
case IR::OpUPlus:
|
|
case IR::OpCompl:
|
|
case IR::OpIncrement:
|
|
case IR::OpDecrement:
|
|
case IR::OpInvalid:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} else if (op == IR::OpAdd) {
|
|
if (IR::String *s1 = left->asString()) {
|
|
if (IR::String *s2 = right->asString()) {
|
|
return _block->STRING(_function->newString(*s1->value + *s2->value));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!left->asTemp() && !left->asArgLocal() && !left->asConst()) {
|
|
const unsigned t = _block->newTemp();
|
|
setLocation(move(_block->TEMP(t), left), loc);
|
|
left = _block->TEMP(t);
|
|
}
|
|
|
|
if (!right->asTemp() && !right->asArgLocal() && !right->asConst()) {
|
|
const unsigned t = _block->newTemp();
|
|
setLocation(move(_block->TEMP(t), right), loc);
|
|
right = _block->TEMP(t);
|
|
}
|
|
|
|
Q_ASSERT(left->asTemp() || left->asArgLocal() || left->asConst());
|
|
Q_ASSERT(right->asTemp() || right->asArgLocal() || right->asConst());
|
|
|
|
return _block->BINOP(op, left, right);
|
|
}
|
|
|
|
IR::Expr *Codegen::call(IR::Expr *base, IR::ExprList *args)
|
|
{
|
|
if (hasError)
|
|
return 0;
|
|
base = reference(base);
|
|
return _block->CALL(base, args);
|
|
}
|
|
|
|
IR::Stmt *Codegen::move(IR::Expr *target, IR::Expr *source, IR::AluOp op)
|
|
{
|
|
if (hasError)
|
|
return 0;
|
|
|
|
Q_ASSERT(target->isLValue());
|
|
|
|
if (op != IR::OpInvalid) {
|
|
return move(target, binop(op, target, source));
|
|
}
|
|
|
|
if (!source->asTemp() && !source->asConst() && !target->asTemp() && !source->asArgLocal() && !target->asArgLocal()) {
|
|
unsigned t = _block->newTemp();
|
|
_block->MOVE(_block->TEMP(t), source);
|
|
source = _block->TEMP(t);
|
|
}
|
|
if (source->asConst() && !target->asTemp() && !target->asArgLocal()) {
|
|
unsigned t = _block->newTemp();
|
|
_block->MOVE(_block->TEMP(t), source);
|
|
source = _block->TEMP(t);
|
|
}
|
|
|
|
return _block->MOVE(target, source);
|
|
}
|
|
|
|
IR::Stmt *Codegen::cjump(IR::Expr *cond, IR::BasicBlock *iftrue, IR::BasicBlock *iffalse)
|
|
{
|
|
if (hasError)
|
|
return 0;
|
|
|
|
if (! (cond->asTemp() || (cond->asBinop() && cjumpCanHandle(cond->asBinop()->op)) )) {
|
|
const unsigned t = _block->newTemp();
|
|
move(_block->TEMP(t), cond);
|
|
cond = _block->TEMP(t);
|
|
}
|
|
return _block->CJUMP(cond, iftrue, iffalse);
|
|
}
|
|
|
|
void Codegen::accept(Node *node)
|
|
{
|
|
if (hasError)
|
|
return;
|
|
|
|
if (node)
|
|
node->accept(this);
|
|
}
|
|
|
|
void Codegen::statement(Statement *ast)
|
|
{
|
|
_block->nextLocation = ast->firstSourceLocation();
|
|
accept(ast);
|
|
}
|
|
|
|
void Codegen::statement(ExpressionNode *ast)
|
|
{
|
|
if (! ast) {
|
|
return;
|
|
} else {
|
|
Result r(nx);
|
|
qSwap(_expr, r);
|
|
accept(ast);
|
|
if (hasError)
|
|
return;
|
|
qSwap(_expr, r);
|
|
if (r.format == ex) {
|
|
if (r->asCall()) {
|
|
_block->EXP(*r); // the nest nx representation for calls is EXP(CALL(c..))
|
|
} else if (r->asTemp() || r->asArgLocal()) {
|
|
// there is nothing to do
|
|
} else {
|
|
unsigned t = _block->newTemp();
|
|
move(_block->TEMP(t), *r);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Codegen::condition(ExpressionNode *ast, IR::BasicBlock *iftrue, IR::BasicBlock *iffalse)
|
|
{
|
|
if (ast) {
|
|
Result r(iftrue, iffalse);
|
|
qSwap(_expr, r);
|
|
accept(ast);
|
|
qSwap(_expr, r);
|
|
if (r.format == ex) {
|
|
setLocation(cjump(*r, r.iftrue, r.iffalse), ast->firstSourceLocation());
|
|
}
|
|
}
|
|
}
|
|
|
|
Codegen::Result Codegen::expression(ExpressionNode *ast)
|
|
{
|
|
Result r;
|
|
if (ast) {
|
|
qSwap(_expr, r);
|
|
accept(ast);
|
|
qSwap(_expr, r);
|
|
}
|
|
return r;
|
|
}
|
|
|
|
Codegen::Result Codegen::sourceElement(SourceElement *ast)
|
|
{
|
|
Result r(nx);
|
|
if (ast) {
|
|
qSwap(_expr, r);
|
|
accept(ast);
|
|
qSwap(_expr, r);
|
|
}
|
|
return r;
|
|
}
|
|
|
|
Codegen::UiMember Codegen::uiObjectMember(UiObjectMember *ast)
|
|
{
|
|
UiMember m;
|
|
if (ast) {
|
|
qSwap(_uiMember, m);
|
|
accept(ast);
|
|
qSwap(_uiMember, m);
|
|
}
|
|
return m;
|
|
}
|
|
|
|
void Codegen::functionBody(FunctionBody *ast)
|
|
{
|
|
if (ast)
|
|
sourceElements(ast->elements);
|
|
}
|
|
|
|
void Codegen::program(Program *ast)
|
|
{
|
|
if (ast) {
|
|
sourceElements(ast->elements);
|
|
}
|
|
}
|
|
|
|
void Codegen::sourceElements(SourceElements *ast)
|
|
{
|
|
for (SourceElements *it = ast; it; it = it->next) {
|
|
sourceElement(it->element);
|
|
if (hasError)
|
|
return;
|
|
}
|
|
}
|
|
|
|
void Codegen::variableDeclaration(VariableDeclaration *ast)
|
|
{
|
|
IR::Expr *initializer = 0;
|
|
if (!ast->expression)
|
|
return;
|
|
Result expr = expression(ast->expression);
|
|
if (hasError)
|
|
return;
|
|
|
|
Q_ASSERT(expr.code);
|
|
initializer = *expr;
|
|
|
|
IR::Expr *lhs = identifier(ast->name.toString(), ast->identifierToken.startLine,
|
|
ast->identifierToken.startColumn);
|
|
|
|
if (lhs->asArgLocal()) {
|
|
move(lhs, initializer);
|
|
} else {
|
|
int initialized = _block->newTemp();
|
|
move(_block->TEMP(initialized), initializer);
|
|
move(lhs, _block->TEMP(initialized));
|
|
}
|
|
}
|
|
|
|
void Codegen::variableDeclarationList(VariableDeclarationList *ast)
|
|
{
|
|
for (VariableDeclarationList *it = ast; it; it = it->next) {
|
|
variableDeclaration(it->declaration);
|
|
}
|
|
}
|
|
|
|
|
|
bool Codegen::visit(ArgumentList *)
|
|
{
|
|
Q_ASSERT(!"unreachable");
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(CaseBlock *)
|
|
{
|
|
Q_ASSERT(!"unreachable");
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(CaseClause *)
|
|
{
|
|
Q_ASSERT(!"unreachable");
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(CaseClauses *)
|
|
{
|
|
Q_ASSERT(!"unreachable");
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(Catch *)
|
|
{
|
|
Q_ASSERT(!"unreachable");
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(DefaultClause *)
|
|
{
|
|
Q_ASSERT(!"unreachable");
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(ElementList *)
|
|
{
|
|
Q_ASSERT(!"unreachable");
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(Elision *)
|
|
{
|
|
Q_ASSERT(!"unreachable");
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(Finally *)
|
|
{
|
|
Q_ASSERT(!"unreachable");
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(FormalParameterList *)
|
|
{
|
|
Q_ASSERT(!"unreachable");
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(FunctionBody *)
|
|
{
|
|
Q_ASSERT(!"unreachable");
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(Program *)
|
|
{
|
|
Q_ASSERT(!"unreachable");
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(PropertyAssignmentList *)
|
|
{
|
|
Q_ASSERT(!"unreachable");
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(PropertyNameAndValue *)
|
|
{
|
|
Q_ASSERT(!"unreachable");
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(PropertyGetterSetter *)
|
|
{
|
|
Q_ASSERT(!"unreachable");
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(SourceElements *)
|
|
{
|
|
Q_ASSERT(!"unreachable");
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(StatementList *)
|
|
{
|
|
Q_ASSERT(!"unreachable");
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(UiArrayMemberList *)
|
|
{
|
|
Q_ASSERT(!"unreachable");
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(UiImport *)
|
|
{
|
|
Q_ASSERT(!"unreachable");
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(UiHeaderItemList *)
|
|
{
|
|
Q_ASSERT(!"unreachable");
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(UiPragma *)
|
|
{
|
|
Q_ASSERT(!"unreachable");
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(UiObjectInitializer *)
|
|
{
|
|
Q_ASSERT(!"unreachable");
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(UiObjectMemberList *)
|
|
{
|
|
Q_ASSERT(!"unreachable");
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(UiParameterList *)
|
|
{
|
|
Q_ASSERT(!"unreachable");
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(UiProgram *)
|
|
{
|
|
Q_ASSERT(!"unreachable");
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(UiQualifiedId *)
|
|
{
|
|
Q_ASSERT(!"unreachable");
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(UiQualifiedPragmaId *)
|
|
{
|
|
Q_ASSERT(!"unreachable");
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(VariableDeclaration *)
|
|
{
|
|
Q_ASSERT(!"unreachable");
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(VariableDeclarationList *)
|
|
{
|
|
Q_ASSERT(!"unreachable");
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(Expression *ast)
|
|
{
|
|
if (hasError)
|
|
return false;
|
|
|
|
statement(ast->left);
|
|
accept(ast->right);
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(ArrayLiteral *ast)
|
|
{
|
|
if (hasError)
|
|
return false;
|
|
|
|
IR::ExprList *args = 0;
|
|
IR::ExprList *current = 0;
|
|
for (ElementList *it = ast->elements; it; it = it->next) {
|
|
for (Elision *elision = it->elision; elision; elision = elision->next) {
|
|
IR::ExprList *arg = _function->New<IR::ExprList>();
|
|
if (!current) {
|
|
args = arg;
|
|
} else {
|
|
current->next = arg;
|
|
}
|
|
current = arg;
|
|
current->expr = _block->CONST(IR::MissingType, 0);
|
|
}
|
|
Result expr = expression(it->expression);
|
|
if (hasError)
|
|
return false;
|
|
|
|
IR::ExprList *arg = _function->New<IR::ExprList>();
|
|
if (!current) {
|
|
args = arg;
|
|
} else {
|
|
current->next = arg;
|
|
}
|
|
current = arg;
|
|
|
|
IR::Expr *exp = *expr;
|
|
if (exp->asTemp() || expr->asArgLocal() || exp->asConst()) {
|
|
current->expr = exp;
|
|
} else {
|
|
unsigned value = _block->newTemp();
|
|
move(_block->TEMP(value), exp);
|
|
current->expr = _block->TEMP(value);
|
|
}
|
|
}
|
|
for (Elision *elision = ast->elision; elision; elision = elision->next) {
|
|
IR::ExprList *arg = _function->New<IR::ExprList>();
|
|
if (!current) {
|
|
args = arg;
|
|
} else {
|
|
current->next = arg;
|
|
}
|
|
current = arg;
|
|
current->expr = _block->CONST(IR::MissingType, 0);
|
|
}
|
|
|
|
const unsigned t = _block->newTemp();
|
|
move(_block->TEMP(t), _block->CALL(_block->NAME(IR::Name::builtin_define_array, 0, 0), args));
|
|
_expr.code = _block->TEMP(t);
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(ArrayMemberExpression *ast)
|
|
{
|
|
if (hasError)
|
|
return false;
|
|
|
|
Result base = expression(ast->base);
|
|
Result index = expression(ast->expression);
|
|
if (hasError)
|
|
return false;
|
|
_expr.code = subscript(*base, *index);
|
|
return false;
|
|
}
|
|
|
|
static IR::AluOp baseOp(int op)
|
|
{
|
|
switch ((QSOperator::Op) op) {
|
|
case QSOperator::InplaceAnd: return IR::OpBitAnd;
|
|
case QSOperator::InplaceSub: return IR::OpSub;
|
|
case QSOperator::InplaceDiv: return IR::OpDiv;
|
|
case QSOperator::InplaceAdd: return IR::OpAdd;
|
|
case QSOperator::InplaceLeftShift: return IR::OpLShift;
|
|
case QSOperator::InplaceMod: return IR::OpMod;
|
|
case QSOperator::InplaceMul: return IR::OpMul;
|
|
case QSOperator::InplaceOr: return IR::OpBitOr;
|
|
case QSOperator::InplaceRightShift: return IR::OpRShift;
|
|
case QSOperator::InplaceURightShift: return IR::OpURShift;
|
|
case QSOperator::InplaceXor: return IR::OpBitXor;
|
|
default: return IR::OpInvalid;
|
|
}
|
|
}
|
|
|
|
bool Codegen::visit(BinaryExpression *ast)
|
|
{
|
|
if (hasError)
|
|
return false;
|
|
|
|
if (ast->op == QSOperator::And) {
|
|
if (_expr.accept(cx)) {
|
|
IR::BasicBlock *iftrue = _function->newBasicBlock(exceptionHandler());
|
|
condition(ast->left, iftrue, _expr.iffalse);
|
|
_block = iftrue;
|
|
condition(ast->right, _expr.iftrue, _expr.iffalse);
|
|
} else {
|
|
IR::BasicBlock *iftrue = _function->newBasicBlock(exceptionHandler());
|
|
IR::BasicBlock *endif = _function->newBasicBlock(exceptionHandler());
|
|
|
|
const unsigned r = _block->newTemp();
|
|
|
|
Result lhs = expression(ast->left);
|
|
if (hasError)
|
|
return false;
|
|
move(_block->TEMP(r), *lhs);
|
|
setLocation(cjump(_block->TEMP(r), iftrue, endif), ast->operatorToken);
|
|
_block = iftrue;
|
|
Result rhs = expression(ast->right);
|
|
if (hasError)
|
|
return false;
|
|
move(_block->TEMP(r), *rhs);
|
|
_block->JUMP(endif);
|
|
|
|
_expr.code = _block->TEMP(r);
|
|
_block = endif;
|
|
}
|
|
return false;
|
|
} else if (ast->op == QSOperator::Or) {
|
|
if (_expr.accept(cx)) {
|
|
IR::BasicBlock *iffalse = _function->newBasicBlock(exceptionHandler());
|
|
condition(ast->left, _expr.iftrue, iffalse);
|
|
_block = iffalse;
|
|
condition(ast->right, _expr.iftrue, _expr.iffalse);
|
|
} else {
|
|
IR::BasicBlock *iffalse = _function->newBasicBlock(exceptionHandler());
|
|
IR::BasicBlock *endif = _function->newBasicBlock(exceptionHandler());
|
|
|
|
const unsigned r = _block->newTemp();
|
|
Result lhs = expression(ast->left);
|
|
if (hasError)
|
|
return false;
|
|
move(_block->TEMP(r), *lhs);
|
|
setLocation(cjump(_block->TEMP(r), endif, iffalse), ast->operatorToken);
|
|
_block = iffalse;
|
|
Result rhs = expression(ast->right);
|
|
if (hasError)
|
|
return false;
|
|
move(_block->TEMP(r), *rhs);
|
|
_block->JUMP(endif);
|
|
|
|
_block = endif;
|
|
_expr.code = _block->TEMP(r);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
IR::Expr* left = *expression(ast->left);
|
|
if (hasError)
|
|
return false;
|
|
|
|
switch (ast->op) {
|
|
case QSOperator::Or:
|
|
case QSOperator::And:
|
|
break;
|
|
|
|
case QSOperator::Assign: {
|
|
if (throwSyntaxErrorOnEvalOrArgumentsInStrictMode(left, ast->left->lastSourceLocation()))
|
|
return false;
|
|
Result right = expression(ast->right);
|
|
if (hasError)
|
|
return false;
|
|
if (!left->isLValue()) {
|
|
throwReferenceError(ast->operatorToken, QStringLiteral("left-hand side of assignment operator is not an lvalue"));
|
|
return false;
|
|
}
|
|
|
|
if (_expr.accept(nx)) {
|
|
move(left, *right);
|
|
} else {
|
|
const unsigned t = _block->newTemp();
|
|
move(_block->TEMP(t), *right);
|
|
move(left, _block->TEMP(t));
|
|
_expr.code = _block->TEMP(t);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case QSOperator::InplaceAnd:
|
|
case QSOperator::InplaceSub:
|
|
case QSOperator::InplaceDiv:
|
|
case QSOperator::InplaceAdd:
|
|
case QSOperator::InplaceLeftShift:
|
|
case QSOperator::InplaceMod:
|
|
case QSOperator::InplaceMul:
|
|
case QSOperator::InplaceOr:
|
|
case QSOperator::InplaceRightShift:
|
|
case QSOperator::InplaceURightShift:
|
|
case QSOperator::InplaceXor: {
|
|
if (throwSyntaxErrorOnEvalOrArgumentsInStrictMode(left, ast->left->lastSourceLocation()))
|
|
return false;
|
|
Result right = expression(ast->right);
|
|
if (hasError)
|
|
return false;
|
|
if (!left->isLValue()) {
|
|
throwSyntaxError(ast->operatorToken, QStringLiteral("left-hand side of inplace operator is not an lvalue"));
|
|
return false;
|
|
}
|
|
|
|
if (_expr.accept(nx)) {
|
|
move(left, *right, baseOp(ast->op));
|
|
} else {
|
|
const unsigned t = _block->newTemp();
|
|
move(_block->TEMP(t), *right);
|
|
move(left, _block->TEMP(t), baseOp(ast->op));
|
|
_expr.code = left;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case QSOperator::In:
|
|
case QSOperator::InstanceOf:
|
|
case QSOperator::Equal:
|
|
case QSOperator::NotEqual:
|
|
case QSOperator::Ge:
|
|
case QSOperator::Gt:
|
|
case QSOperator::Le:
|
|
case QSOperator::Lt:
|
|
case QSOperator::StrictEqual:
|
|
case QSOperator::StrictNotEqual: {
|
|
if (!left->asTemp() && !left->asArgLocal() && !left->asConst()) {
|
|
const unsigned t = _block->newTemp();
|
|
setLocation(move(_block->TEMP(t), left), ast->operatorToken);
|
|
left = _block->TEMP(t);
|
|
}
|
|
|
|
Result right = expression(ast->right);
|
|
if (hasError)
|
|
return false;
|
|
|
|
if (_expr.accept(cx)) {
|
|
setLocation(cjump(binop(IR::binaryOperator(ast->op), left, *right, ast->operatorToken), _expr.iftrue, _expr.iffalse), ast->operatorToken);
|
|
} else {
|
|
_expr.code = binop(IR::binaryOperator(ast->op), left, *right, ast->operatorToken);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case QSOperator::Add:
|
|
case QSOperator::BitAnd:
|
|
case QSOperator::BitOr:
|
|
case QSOperator::BitXor:
|
|
case QSOperator::Div:
|
|
case QSOperator::LShift:
|
|
case QSOperator::Mod:
|
|
case QSOperator::Mul:
|
|
case QSOperator::RShift:
|
|
case QSOperator::Sub:
|
|
case QSOperator::URShift: {
|
|
if (!left->asTemp() && !left->asArgLocal() && !left->asConst()) {
|
|
const unsigned t = _block->newTemp();
|
|
setLocation(move(_block->TEMP(t), left), ast->operatorToken);
|
|
left = _block->TEMP(t);
|
|
}
|
|
|
|
Result right = expression(ast->right);
|
|
if (hasError)
|
|
return false;
|
|
|
|
_expr.code = binop(IR::binaryOperator(ast->op), left, *right, ast->operatorToken);
|
|
break;
|
|
}
|
|
|
|
} // switch
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(CallExpression *ast)
|
|
{
|
|
if (hasError)
|
|
return false;
|
|
|
|
Result base = expression(ast->base);
|
|
IR::ExprList *args = 0, **args_it = &args;
|
|
for (ArgumentList *it = ast->arguments; it; it = it->next) {
|
|
Result arg = expression(it->expression);
|
|
if (hasError)
|
|
return false;
|
|
IR::Expr *actual = argument(*arg);
|
|
*args_it = _function->New<IR::ExprList>();
|
|
(*args_it)->init(actual);
|
|
args_it = &(*args_it)->next;
|
|
}
|
|
if (hasError)
|
|
return false;
|
|
_expr.code = call(*base, args);
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(ConditionalExpression *ast)
|
|
{
|
|
if (hasError)
|
|
return true;
|
|
|
|
IR::BasicBlock *iftrue = _function->newBasicBlock(exceptionHandler());
|
|
IR::BasicBlock *iffalse = _function->newBasicBlock(exceptionHandler());
|
|
IR::BasicBlock *endif = _function->newBasicBlock(exceptionHandler());
|
|
|
|
const unsigned t = _block->newTemp();
|
|
|
|
condition(ast->expression, iftrue, iffalse);
|
|
|
|
_block = iftrue;
|
|
Result ok = expression(ast->ok);
|
|
if (hasError)
|
|
return false;
|
|
move(_block->TEMP(t), *ok);
|
|
_block->JUMP(endif);
|
|
|
|
_block = iffalse;
|
|
Result ko = expression(ast->ko);
|
|
if (hasError)
|
|
return false;
|
|
move(_block->TEMP(t), *ko);
|
|
_block->JUMP(endif);
|
|
|
|
_block = endif;
|
|
|
|
_expr.code = _block->TEMP(t);
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(DeleteExpression *ast)
|
|
{
|
|
if (hasError)
|
|
return false;
|
|
|
|
IR::Expr* expr = *expression(ast->expression);
|
|
if (hasError)
|
|
return false;
|
|
// Temporaries cannot be deleted
|
|
IR::ArgLocal *al = expr->asArgLocal();
|
|
if (al && al->index < static_cast<unsigned>(_variableEnvironment->members.size())) {
|
|
// Trying to delete a function argument might throw.
|
|
if (_function->isStrict) {
|
|
throwSyntaxError(ast->deleteToken, QStringLiteral("Delete of an unqualified identifier in strict mode."));
|
|
return false;
|
|
}
|
|
_expr.code = _block->CONST(IR::BoolType, 0);
|
|
return false;
|
|
}
|
|
if (_function->isStrict && expr->asName()) {
|
|
throwSyntaxError(ast->deleteToken, QStringLiteral("Delete of an unqualified identifier in strict mode."));
|
|
return false;
|
|
}
|
|
|
|
// [[11.4.1]] Return true if it's not a reference
|
|
if (expr->asConst() || expr->asString()) {
|
|
_expr.code = _block->CONST(IR::BoolType, 1);
|
|
return false;
|
|
}
|
|
|
|
// Return values from calls are also not a reference, but we have to
|
|
// perform the call to allow for side effects.
|
|
if (expr->asCall()) {
|
|
_block->EXP(expr);
|
|
_expr.code = _block->CONST(IR::BoolType, 1);
|
|
return false;
|
|
}
|
|
if (expr->asTemp() ||
|
|
(expr->asArgLocal() &&
|
|
expr->asArgLocal()->index >= static_cast<unsigned>(_variableEnvironment->members.size()))) {
|
|
_expr.code = _block->CONST(IR::BoolType, 1);
|
|
return false;
|
|
}
|
|
|
|
IR::ExprList *args = _function->New<IR::ExprList>();
|
|
args->init(reference(expr));
|
|
_expr.code = call(_block->NAME(IR::Name::builtin_delete, ast->deleteToken.startLine, ast->deleteToken.startColumn), args);
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(FalseLiteral *)
|
|
{
|
|
if (hasError)
|
|
return false;
|
|
|
|
if (_expr.accept(cx)) {
|
|
_block->JUMP(_expr.iffalse);
|
|
} else {
|
|
_expr.code = _block->CONST(IR::BoolType, 0);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(FieldMemberExpression *ast)
|
|
{
|
|
if (hasError)
|
|
return false;
|
|
|
|
Result base = expression(ast->base);
|
|
if (!hasError)
|
|
_expr.code = member(*base, _function->newString(ast->name.toString()));
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(FunctionExpression *ast)
|
|
{
|
|
if (hasError)
|
|
return false;
|
|
|
|
int function = defineFunction(ast->name.toString(), ast, ast->formals, ast->body ? ast->body->elements : 0);
|
|
_expr.code = _block->CLOSURE(function);
|
|
return false;
|
|
}
|
|
|
|
IR::Expr *Codegen::identifier(const QString &name, int line, int col)
|
|
{
|
|
if (hasError)
|
|
return 0;
|
|
|
|
uint scope = 0;
|
|
Environment *e = _variableEnvironment;
|
|
IR::Function *f = _function;
|
|
|
|
while (f && e->parent) {
|
|
if (f->insideWithOrCatch || (f->isNamedExpression && QStringRef(f->name) == name))
|
|
return _block->NAME(name, line, col);
|
|
|
|
int index = e->findMember(name);
|
|
Q_ASSERT (index < e->members.size());
|
|
if (index != -1) {
|
|
IR::ArgLocal *al = _block->LOCAL(index, scope);
|
|
if (name == QLatin1String("arguments") || name == QLatin1String("eval"))
|
|
al->isArgumentsOrEval = true;
|
|
return al;
|
|
}
|
|
const int argIdx = f->indexOfArgument(QStringRef(&name));
|
|
if (argIdx != -1)
|
|
return _block->ARG(argIdx, scope);
|
|
|
|
if (!f->isStrict && f->hasDirectEval)
|
|
return _block->NAME(name, line, col);
|
|
|
|
++scope;
|
|
e = e->parent;
|
|
f = f->outer;
|
|
}
|
|
|
|
// This hook allows implementing QML lookup semantics
|
|
if (IR::Expr *fallback = fallbackNameLookup(name, line, col))
|
|
return fallback;
|
|
|
|
if (!e->parent && (!f || !f->insideWithOrCatch) && _variableEnvironment->compilationMode != EvalCode && e->compilationMode != QmlBinding)
|
|
return _block->GLOBALNAME(name, line, col);
|
|
|
|
// global context or with. Lookup by name
|
|
return _block->NAME(name, line, col);
|
|
|
|
}
|
|
|
|
IR::Expr *Codegen::fallbackNameLookup(const QString &name, int line, int col)
|
|
{
|
|
Q_UNUSED(name)
|
|
Q_UNUSED(line)
|
|
Q_UNUSED(col)
|
|
return 0;
|
|
}
|
|
|
|
bool Codegen::visit(IdentifierExpression *ast)
|
|
{
|
|
if (hasError)
|
|
return false;
|
|
|
|
_expr.code = identifier(ast->name.toString(), ast->identifierToken.startLine, ast->identifierToken.startColumn);
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(NestedExpression *ast)
|
|
{
|
|
if (hasError)
|
|
return false;
|
|
|
|
accept(ast->expression);
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(NewExpression *ast)
|
|
{
|
|
if (hasError)
|
|
return false;
|
|
|
|
Result base = expression(ast->expression);
|
|
if (hasError)
|
|
return false;
|
|
IR::Expr *expr = *base;
|
|
if (expr && !expr->asTemp() && !expr->asArgLocal() && !expr->asName() && !expr->asMember()) {
|
|
const unsigned t = _block->newTemp();
|
|
move(_block->TEMP(t), expr);
|
|
expr = _block->TEMP(t);
|
|
}
|
|
_expr.code = _block->NEW(expr, 0);
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(NewMemberExpression *ast)
|
|
{
|
|
if (hasError)
|
|
return false;
|
|
|
|
Result base = expression(ast->base);
|
|
if (hasError)
|
|
return false;
|
|
IR::Expr *expr = *base;
|
|
if (expr && !expr->asTemp() && !expr->asArgLocal() && !expr->asName() && !expr->asMember()) {
|
|
const unsigned t = _block->newTemp();
|
|
move(_block->TEMP(t), expr);
|
|
expr = _block->TEMP(t);
|
|
}
|
|
|
|
IR::ExprList *args = 0, **args_it = &args;
|
|
for (ArgumentList *it = ast->arguments; it; it = it->next) {
|
|
Result arg = expression(it->expression);
|
|
if (hasError)
|
|
return false;
|
|
IR::Expr *actual = argument(*arg);
|
|
*args_it = _function->New<IR::ExprList>();
|
|
(*args_it)->init(actual);
|
|
args_it = &(*args_it)->next;
|
|
}
|
|
const unsigned t = _block->newTemp();
|
|
move(_block->TEMP(t), _block->NEW(expr, args));
|
|
_expr.code = _block->TEMP(t);
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(NotExpression *ast)
|
|
{
|
|
if (hasError)
|
|
return false;
|
|
|
|
Result expr = expression(ast->expression);
|
|
if (hasError)
|
|
return false;
|
|
const unsigned r = _block->newTemp();
|
|
setLocation(move(_block->TEMP(r), unop(IR::OpNot, *expr, ast->notToken)), ast->notToken);
|
|
_expr.code = _block->TEMP(r);
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(NullExpression *)
|
|
{
|
|
if (hasError)
|
|
return false;
|
|
|
|
if (_expr.accept(cx)) _block->JUMP(_expr.iffalse);
|
|
else _expr.code = _block->CONST(IR::NullType, 0);
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(NumericLiteral *ast)
|
|
{
|
|
if (hasError)
|
|
return false;
|
|
|
|
if (_expr.accept(cx)) {
|
|
if (ast->value) _block->JUMP(_expr.iftrue);
|
|
else _block->JUMP(_expr.iffalse);
|
|
} else {
|
|
_expr.code = _block->CONST(IR::NumberType, ast->value);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
struct ObjectPropertyValue {
|
|
ObjectPropertyValue()
|
|
: value(0)
|
|
, getter(-1)
|
|
, setter(-1)
|
|
{}
|
|
|
|
IR::Expr *value;
|
|
int getter; // index in _module->functions or -1 if not set
|
|
int setter;
|
|
|
|
bool hasGetter() const { return getter >= 0; }
|
|
bool hasSetter() const { return setter >= 0; }
|
|
};
|
|
|
|
bool Codegen::visit(ObjectLiteral *ast)
|
|
{
|
|
if (hasError)
|
|
return false;
|
|
|
|
QMap<QString, ObjectPropertyValue> valueMap;
|
|
|
|
for (PropertyAssignmentList *it = ast->properties; it; it = it->next) {
|
|
QString name = it->assignment->name->asString();
|
|
if (PropertyNameAndValue *nv = AST::cast<AST::PropertyNameAndValue *>(it->assignment)) {
|
|
Result value = expression(nv->value);
|
|
if (hasError)
|
|
return false;
|
|
ObjectPropertyValue &v = valueMap[name];
|
|
if (v.hasGetter() || v.hasSetter() || (_function->isStrict && v.value)) {
|
|
throwSyntaxError(nv->lastSourceLocation(),
|
|
QStringLiteral("Illegal duplicate key '%1' in object literal").arg(name));
|
|
return false;
|
|
}
|
|
|
|
valueMap[name].value = *value;
|
|
} else if (PropertyGetterSetter *gs = AST::cast<AST::PropertyGetterSetter *>(it->assignment)) {
|
|
const int function = defineFunction(name, gs, gs->formals, gs->functionBody ? gs->functionBody->elements : 0);
|
|
ObjectPropertyValue &v = valueMap[name];
|
|
if (v.value ||
|
|
(gs->type == PropertyGetterSetter::Getter && v.hasGetter()) ||
|
|
(gs->type == PropertyGetterSetter::Setter && v.hasSetter())) {
|
|
throwSyntaxError(gs->lastSourceLocation(),
|
|
QStringLiteral("Illegal duplicate key '%1' in object literal").arg(name));
|
|
return false;
|
|
}
|
|
if (gs->type == PropertyGetterSetter::Getter)
|
|
v.getter = function;
|
|
else
|
|
v.setter = function;
|
|
} else {
|
|
Q_UNREACHABLE();
|
|
}
|
|
}
|
|
|
|
// The linked-list arguments to builtin_define_object_literal
|
|
// begin with a CONST counting the number of key/value pairs, followed by the
|
|
// key value pairs, followed by the array entries.
|
|
IR::ExprList *args = _function->New<IR::ExprList>();
|
|
|
|
IR::Const *entryCountParam = _function->New<IR::Const>();
|
|
entryCountParam->init(IR::SInt32Type, 0);
|
|
args->expr = entryCountParam;
|
|
args->next = 0;
|
|
|
|
IR::ExprList *keyValueEntries = 0;
|
|
IR::ExprList *currentKeyValueEntry = 0;
|
|
int keyValueEntryCount = 0;
|
|
IR::ExprList *arrayEntries = 0;
|
|
|
|
IR::ExprList *currentArrayEntry = 0;
|
|
|
|
for (QMap<QString, ObjectPropertyValue>::iterator it = valueMap.begin(); it != valueMap.end(); ) {
|
|
IR::ExprList **currentPtr = 0;
|
|
uint keyAsIndex = QV4::String::toArrayIndex(it.key());
|
|
if (keyAsIndex != UINT_MAX) {
|
|
if (!arrayEntries) {
|
|
arrayEntries = _function->New<IR::ExprList>();
|
|
currentArrayEntry = arrayEntries;
|
|
} else {
|
|
currentArrayEntry->next = _function->New<IR::ExprList>();
|
|
currentArrayEntry = currentArrayEntry->next;
|
|
}
|
|
currentPtr = ¤tArrayEntry;
|
|
IR::Const *idx = _function->New<IR::Const>();
|
|
idx->init(IR::UInt32Type, keyAsIndex);
|
|
(*currentPtr)->expr = idx;
|
|
} else {
|
|
if (!keyValueEntries) {
|
|
keyValueEntries = _function->New<IR::ExprList>();
|
|
currentKeyValueEntry = keyValueEntries;
|
|
} else {
|
|
currentKeyValueEntry->next = _function->New<IR::ExprList>();
|
|
currentKeyValueEntry = currentKeyValueEntry->next;
|
|
}
|
|
currentPtr = ¤tKeyValueEntry;
|
|
(*currentPtr)->expr = _block->NAME(it.key(), 0, 0);
|
|
keyValueEntryCount++;
|
|
}
|
|
|
|
IR::ExprList *¤t = *currentPtr;
|
|
if (it->value) {
|
|
current->next = _function->New<IR::ExprList>();
|
|
current = current->next;
|
|
current->expr = _block->CONST(IR::BoolType, true);
|
|
|
|
unsigned value = _block->newTemp();
|
|
move(_block->TEMP(value), it->value);
|
|
|
|
current->next = _function->New<IR::ExprList>();
|
|
current = current->next;
|
|
current->expr = _block->TEMP(value);
|
|
} else {
|
|
current->next = _function->New<IR::ExprList>();
|
|
current = current->next;
|
|
current->expr = _block->CONST(IR::BoolType, false);
|
|
|
|
unsigned getter = _block->newTemp();
|
|
unsigned setter = _block->newTemp();
|
|
move(_block->TEMP(getter), it->hasGetter() ? _block->CLOSURE(it->getter) : _block->CONST(IR::UndefinedType, 0));
|
|
move(_block->TEMP(setter), it->hasSetter() ? _block->CLOSURE(it->setter) : _block->CONST(IR::UndefinedType, 0));
|
|
|
|
current->next = _function->New<IR::ExprList>();
|
|
current = current->next;
|
|
current->expr = _block->TEMP(getter);
|
|
current->next = _function->New<IR::ExprList>();
|
|
current = current->next;
|
|
current->expr = _block->TEMP(setter);
|
|
}
|
|
|
|
it = valueMap.erase(it);
|
|
}
|
|
|
|
entryCountParam->value = keyValueEntryCount;
|
|
|
|
if (keyValueEntries)
|
|
args->next = keyValueEntries;
|
|
if (arrayEntries) {
|
|
if (currentKeyValueEntry)
|
|
currentKeyValueEntry->next = arrayEntries;
|
|
else
|
|
args->next = arrayEntries;
|
|
}
|
|
|
|
const unsigned t = _block->newTemp();
|
|
move(_block->TEMP(t), _block->CALL(_block->NAME(IR::Name::builtin_define_object_literal,
|
|
ast->firstSourceLocation().startLine, ast->firstSourceLocation().startColumn), args));
|
|
|
|
_expr.code = _block->TEMP(t);
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(PostDecrementExpression *ast)
|
|
{
|
|
if (hasError)
|
|
return false;
|
|
|
|
Result expr = expression(ast->base);
|
|
if (hasError)
|
|
return false;
|
|
if (!expr->isLValue()) {
|
|
throwReferenceError(ast->base->lastSourceLocation(), QStringLiteral("Invalid left-hand side expression in postfix operation"));
|
|
return false;
|
|
}
|
|
if (throwSyntaxErrorOnEvalOrArgumentsInStrictMode(*expr, ast->decrementToken))
|
|
return false;
|
|
|
|
const unsigned oldValue = _block->newTemp();
|
|
setLocation(move(_block->TEMP(oldValue), unop(IR::OpUPlus, *expr, ast->decrementToken)), ast->decrementToken);
|
|
|
|
const unsigned newValue = _block->newTemp();
|
|
setLocation(move(_block->TEMP(newValue), binop(IR::OpSub, _block->TEMP(oldValue), _block->CONST(IR::NumberType, 1), ast->decrementToken)), ast->decrementToken);
|
|
setLocation(move(*expr, _block->TEMP(newValue)), ast->decrementToken);
|
|
|
|
if (!_expr.accept(nx))
|
|
_expr.code = _block->TEMP(oldValue);
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(PostIncrementExpression *ast)
|
|
{
|
|
if (hasError)
|
|
return false;
|
|
|
|
Result expr = expression(ast->base);
|
|
if (hasError)
|
|
return false;
|
|
if (!expr->isLValue()) {
|
|
throwReferenceError(ast->base->lastSourceLocation(), QStringLiteral("Invalid left-hand side expression in postfix operation"));
|
|
return false;
|
|
}
|
|
if (throwSyntaxErrorOnEvalOrArgumentsInStrictMode(*expr, ast->incrementToken))
|
|
return false;
|
|
|
|
const unsigned oldValue = _block->newTemp();
|
|
setLocation(move(_block->TEMP(oldValue), unop(IR::OpUPlus, *expr, ast->incrementToken)), ast->incrementToken);
|
|
|
|
const unsigned newValue = _block->newTemp();
|
|
setLocation(move(_block->TEMP(newValue), binop(IR::OpAdd, _block->TEMP(oldValue), _block->CONST(IR::NumberType, 1), ast->incrementToken)), ast->incrementToken);
|
|
setLocation(move(*expr, _block->TEMP(newValue)), ast->incrementToken);
|
|
|
|
if (!_expr.accept(nx))
|
|
_expr.code = _block->TEMP(oldValue);
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(PreDecrementExpression *ast)
|
|
{
|
|
if (hasError)
|
|
return false;
|
|
|
|
Result expr = expression(ast->expression);
|
|
if (hasError)
|
|
return false;
|
|
if (!expr->isLValue()) {
|
|
throwReferenceError(ast->expression->lastSourceLocation(), QStringLiteral("Prefix ++ operator applied to value that is not a reference."));
|
|
return false;
|
|
}
|
|
|
|
if (throwSyntaxErrorOnEvalOrArgumentsInStrictMode(*expr, ast->decrementToken))
|
|
return false;
|
|
IR::Expr *op = binop(IR::OpSub, *expr, _block->CONST(IR::NumberType, 1), ast->decrementToken);
|
|
if (_expr.accept(nx)) {
|
|
setLocation(move(*expr, op), ast->decrementToken);
|
|
} else {
|
|
const unsigned t = _block->newTemp();
|
|
setLocation(move(_block->TEMP(t), op), ast->decrementToken);
|
|
setLocation(move(*expr, _block->TEMP(t)), ast->decrementToken);
|
|
_expr.code = _block->TEMP(t);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(PreIncrementExpression *ast)
|
|
{
|
|
if (hasError)
|
|
return false;
|
|
|
|
Result expr = expression(ast->expression);
|
|
if (hasError)
|
|
return false;
|
|
if (!expr->isLValue()) {
|
|
throwReferenceError(ast->expression->lastSourceLocation(), QStringLiteral("Prefix ++ operator applied to value that is not a reference."));
|
|
return false;
|
|
}
|
|
|
|
if (throwSyntaxErrorOnEvalOrArgumentsInStrictMode(*expr, ast->incrementToken))
|
|
return false;
|
|
IR::Expr *op = binop(IR::OpAdd, unop(IR::OpUPlus, *expr, ast->incrementToken), _block->CONST(IR::NumberType, 1), ast->incrementToken);
|
|
if (_expr.accept(nx)) {
|
|
setLocation(move(*expr, op), ast->incrementToken);
|
|
} else {
|
|
const unsigned t = _block->newTemp();
|
|
setLocation(move(_block->TEMP(t), op), ast->incrementToken);
|
|
setLocation(move(*expr, _block->TEMP(t)), ast->incrementToken);
|
|
_expr.code = _block->TEMP(t);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(RegExpLiteral *ast)
|
|
{
|
|
if (hasError)
|
|
return false;
|
|
|
|
_expr.code = _block->REGEXP(_function->newString(ast->pattern.toString()), ast->flags);
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(StringLiteral *ast)
|
|
{
|
|
if (hasError)
|
|
return false;
|
|
|
|
_expr.code = _block->STRING(_function->newString(ast->value.toString()));
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(ThisExpression *ast)
|
|
{
|
|
if (hasError)
|
|
return false;
|
|
|
|
_expr.code = _block->NAME(QStringLiteral("this"), ast->thisToken.startLine, ast->thisToken.startColumn);
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(TildeExpression *ast)
|
|
{
|
|
if (hasError)
|
|
return false;
|
|
|
|
Result expr = expression(ast->expression);
|
|
if (hasError)
|
|
return false;
|
|
const unsigned t = _block->newTemp();
|
|
setLocation(move(_block->TEMP(t), unop(IR::OpCompl, *expr, ast->tildeToken)), ast->tildeToken);
|
|
_expr.code = _block->TEMP(t);
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(TrueLiteral *)
|
|
{
|
|
if (hasError)
|
|
return false;
|
|
|
|
if (_expr.accept(cx)) {
|
|
_block->JUMP(_expr.iftrue);
|
|
} else {
|
|
_expr.code = _block->CONST(IR::BoolType, 1);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(TypeOfExpression *ast)
|
|
{
|
|
if (hasError)
|
|
return false;
|
|
|
|
Result expr = expression(ast->expression);
|
|
if (hasError)
|
|
return false;
|
|
IR::ExprList *args = _function->New<IR::ExprList>();
|
|
args->init(reference(*expr));
|
|
_expr.code = call(_block->NAME(IR::Name::builtin_typeof, ast->typeofToken.startLine, ast->typeofToken.startColumn), args);
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(UnaryMinusExpression *ast)
|
|
{
|
|
if (hasError)
|
|
return false;
|
|
|
|
Result expr = expression(ast->expression);
|
|
if (hasError)
|
|
return false;
|
|
const unsigned t = _block->newTemp();
|
|
setLocation(move(_block->TEMP(t), unop(IR::OpUMinus, *expr, ast->minusToken)), ast->minusToken);
|
|
_expr.code = _block->TEMP(t);
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(UnaryPlusExpression *ast)
|
|
{
|
|
if (hasError)
|
|
return false;
|
|
|
|
Result expr = expression(ast->expression);
|
|
if (hasError)
|
|
return false;
|
|
const unsigned t = _block->newTemp();
|
|
setLocation(move(_block->TEMP(t), unop(IR::OpUPlus, *expr, ast->plusToken)), ast->plusToken);
|
|
_expr.code = _block->TEMP(t);
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(VoidExpression *ast)
|
|
{
|
|
if (hasError)
|
|
return false;
|
|
|
|
statement(ast->expression);
|
|
_expr.code = _block->CONST(IR::UndefinedType, 0);
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(FunctionDeclaration * ast)
|
|
{
|
|
if (hasError)
|
|
return false;
|
|
|
|
if (_variableEnvironment->compilationMode == QmlBinding)
|
|
move(_block->TEMP(_returnAddress), _block->NAME(ast->name.toString(), 0, 0));
|
|
_expr.accept(nx);
|
|
return false;
|
|
}
|
|
|
|
int Codegen::defineFunction(const QString &name, AST::Node *ast,
|
|
AST::FormalParameterList *formals,
|
|
AST::SourceElements *body,
|
|
const QStringList &inheritedLocals)
|
|
{
|
|
Loop *loop = 0;
|
|
qSwap(_loop, loop);
|
|
QStack<IR::BasicBlock *> exceptionHandlers;
|
|
qSwap(_exceptionHandlers, exceptionHandlers);
|
|
|
|
ScopeAndFinally *scopeAndFinally = 0;
|
|
|
|
enterEnvironment(ast);
|
|
IR::Function *function = _module->newFunction(name, _function);
|
|
int functionIndex = _module->functions.count() - 1;
|
|
|
|
IR::BasicBlock *entryBlock = function->newBasicBlock(0);
|
|
IR::BasicBlock *exitBlock = function->newBasicBlock(0, IR::Function::DontInsertBlock);
|
|
function->hasDirectEval = _variableEnvironment->hasDirectEval || _variableEnvironment->compilationMode == EvalCode
|
|
|| _module->debugMode; // Conditional breakpoints are like eval in the function
|
|
function->usesArgumentsObject = _variableEnvironment->parent && (_variableEnvironment->usesArgumentsObject == Environment::ArgumentsObjectUsed);
|
|
function->usesThis = _variableEnvironment->usesThis;
|
|
function->maxNumberOfArguments = qMax(_variableEnvironment->maxNumberOfArguments, (int)QV4::Global::ReservedArgumentCount);
|
|
function->isStrict = _variableEnvironment->isStrict;
|
|
function->isNamedExpression = _variableEnvironment->isNamedFunctionExpression;
|
|
function->isQmlBinding = _variableEnvironment->compilationMode == QmlBinding;
|
|
|
|
AST::SourceLocation loc = ast->firstSourceLocation();
|
|
function->line = loc.startLine;
|
|
function->column = loc.startColumn;
|
|
|
|
if (function->usesArgumentsObject)
|
|
_variableEnvironment->enter(QStringLiteral("arguments"), Environment::VariableDeclaration, AST::VariableDeclaration::FunctionScope);
|
|
|
|
// variables in global code are properties of the global context object, not locals as with other functions.
|
|
if (_variableEnvironment->compilationMode == FunctionCode || _variableEnvironment->compilationMode == QmlBinding) {
|
|
unsigned t = 0;
|
|
for (Environment::MemberMap::iterator it = _variableEnvironment->members.begin(), end = _variableEnvironment->members.end(); it != end; ++it) {
|
|
const QString &local = it.key();
|
|
function->LOCAL(local);
|
|
(*it).index = t;
|
|
entryBlock->MOVE(entryBlock->LOCAL(t, 0), entryBlock->CONST(IR::UndefinedType, 0));
|
|
++t;
|
|
}
|
|
} else {
|
|
if (!_variableEnvironment->isStrict) {
|
|
for (const QString &inheritedLocal : qAsConst(inheritedLocals)) {
|
|
function->LOCAL(inheritedLocal);
|
|
unsigned tempIndex = entryBlock->newTemp();
|
|
Environment::Member member = { Environment::UndefinedMember,
|
|
static_cast<int>(tempIndex), 0,
|
|
AST::VariableDeclaration::VariableScope::FunctionScope };
|
|
_variableEnvironment->members.insert(inheritedLocal, member);
|
|
}
|
|
}
|
|
|
|
IR::ExprList *args = 0;
|
|
for (Environment::MemberMap::const_iterator it = _variableEnvironment->members.constBegin(), cend = _variableEnvironment->members.constEnd(); it != cend; ++it) {
|
|
const QString &local = it.key();
|
|
IR::ExprList *next = function->New<IR::ExprList>();
|
|
next->expr = entryBlock->NAME(local, 0, 0);
|
|
next->next = args;
|
|
args = next;
|
|
}
|
|
if (args) {
|
|
IR::ExprList *next = function->New<IR::ExprList>();
|
|
next->expr = entryBlock->CONST(IR::BoolType, false); // ### Investigate removal of bool deletable
|
|
next->next = args;
|
|
args = next;
|
|
|
|
entryBlock->EXP(entryBlock->CALL(entryBlock->NAME(IR::Name::builtin_declare_vars, 0, 0), args));
|
|
}
|
|
}
|
|
|
|
unsigned returnAddress = entryBlock->newTemp();
|
|
|
|
entryBlock->MOVE(entryBlock->TEMP(returnAddress), entryBlock->CONST(IR::UndefinedType, 0));
|
|
setLocation(exitBlock->RET(exitBlock->TEMP(returnAddress)), ast->lastSourceLocation());
|
|
|
|
qSwap(_function, function);
|
|
qSwap(_block, entryBlock);
|
|
qSwap(_exitBlock, exitBlock);
|
|
qSwap(_returnAddress, returnAddress);
|
|
qSwap(_scopeAndFinally, scopeAndFinally);
|
|
|
|
for (FormalParameterList *it = formals; it; it = it->next) {
|
|
_function->RECEIVE(it->name.toString());
|
|
}
|
|
|
|
for (const Environment::Member &member : qAsConst(_variableEnvironment->members)) {
|
|
if (member.function) {
|
|
const int function = defineFunction(member.function->name.toString(), member.function, member.function->formals,
|
|
member.function->body ? member.function->body->elements : 0);
|
|
if (! _variableEnvironment->parent) {
|
|
move(_block->NAME(member.function->name.toString(), member.function->identifierToken.startLine, member.function->identifierToken.startColumn),
|
|
_block->CLOSURE(function));
|
|
} else {
|
|
Q_ASSERT(member.index >= 0);
|
|
move(_block->LOCAL(member.index, 0), _block->CLOSURE(function));
|
|
}
|
|
}
|
|
}
|
|
if (_function->usesArgumentsObject) {
|
|
move(identifier(QStringLiteral("arguments"), ast->firstSourceLocation().startLine, ast->firstSourceLocation().startColumn),
|
|
_block->CALL(_block->NAME(IR::Name::builtin_setup_argument_object,
|
|
ast->firstSourceLocation().startLine, ast->firstSourceLocation().startColumn), 0));
|
|
}
|
|
if (_function->usesThis && !_function->isStrict) {
|
|
// make sure we convert this to an object
|
|
_block->EXP(_block->CALL(_block->NAME(IR::Name::builtin_convert_this_to_object,
|
|
ast->firstSourceLocation().startLine, ast->firstSourceLocation().startColumn), 0));
|
|
}
|
|
|
|
beginFunctionBodyHook();
|
|
|
|
sourceElements(body);
|
|
|
|
_function->addBasicBlock(_exitBlock);
|
|
|
|
_block->JUMP(_exitBlock);
|
|
|
|
qSwap(_function, function);
|
|
qSwap(_block, entryBlock);
|
|
qSwap(_exitBlock, exitBlock);
|
|
qSwap(_returnAddress, returnAddress);
|
|
qSwap(_scopeAndFinally, scopeAndFinally);
|
|
qSwap(_exceptionHandlers, exceptionHandlers);
|
|
qSwap(_loop, loop);
|
|
|
|
leaveEnvironment();
|
|
|
|
return functionIndex;
|
|
}
|
|
|
|
bool Codegen::visit(FunctionSourceElement *ast)
|
|
{
|
|
if (hasError)
|
|
return false;
|
|
|
|
statement(ast->declaration);
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(StatementSourceElement *ast)
|
|
{
|
|
if (hasError)
|
|
return false;
|
|
|
|
statement(ast->statement);
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(Block *ast)
|
|
{
|
|
if (hasError)
|
|
return false;
|
|
|
|
for (StatementList *it = ast->statements; it; it = it->next) {
|
|
statement(it->statement);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(BreakStatement *ast)
|
|
{
|
|
if (hasError)
|
|
return false;
|
|
|
|
if (!_loop) {
|
|
throwSyntaxError(ast->lastSourceLocation(), QStringLiteral("Break outside of loop"));
|
|
return false;
|
|
}
|
|
Loop *loop = 0;
|
|
if (ast->label.isEmpty())
|
|
loop = _loop;
|
|
else {
|
|
for (loop = _loop; loop; loop = loop->parent) {
|
|
if (loop->labelledStatement && loop->labelledStatement->label == ast->label)
|
|
break;
|
|
}
|
|
if (!loop) {
|
|
throwSyntaxError(ast->lastSourceLocation(), QStringLiteral("Undefined label '%1'").arg(ast->label.toString()));
|
|
return false;
|
|
}
|
|
}
|
|
unwindException(loop->scopeAndFinally);
|
|
_block->JUMP(loop->breakBlock);
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(ContinueStatement *ast)
|
|
{
|
|
if (hasError)
|
|
return false;
|
|
|
|
Loop *loop = 0;
|
|
if (ast->label.isEmpty()) {
|
|
for (loop = _loop; loop; loop = loop->parent) {
|
|
if (loop->continueBlock)
|
|
break;
|
|
}
|
|
} else {
|
|
for (loop = _loop; loop; loop = loop->parent) {
|
|
if (loop->labelledStatement && loop->labelledStatement->label == ast->label) {
|
|
if (!loop->continueBlock)
|
|
loop = 0;
|
|
break;
|
|
}
|
|
}
|
|
if (!loop) {
|
|
throwSyntaxError(ast->lastSourceLocation(), QStringLiteral("Undefined label '%1'").arg(ast->label.toString()));
|
|
return false;
|
|
}
|
|
}
|
|
if (!loop) {
|
|
throwSyntaxError(ast->lastSourceLocation(), QStringLiteral("continue outside of loop"));
|
|
return false;
|
|
}
|
|
unwindException(loop->scopeAndFinally);
|
|
_block->JUMP(loop->continueBlock);
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(DebuggerStatement *)
|
|
{
|
|
Q_UNIMPLEMENTED();
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(DoWhileStatement *ast)
|
|
{
|
|
if (hasError)
|
|
return true;
|
|
|
|
IR::BasicBlock *loopbody = _function->newBasicBlock(exceptionHandler());
|
|
IR::BasicBlock *loopcond = _function->newBasicBlock(exceptionHandler());
|
|
IR::BasicBlock *loopend = _function->newBasicBlock(exceptionHandler());
|
|
|
|
enterLoop(ast, loopend, loopcond);
|
|
|
|
_block->JUMP(loopbody);
|
|
|
|
_block = loopbody;
|
|
statement(ast->statement);
|
|
setLocation(_block->JUMP(loopcond), ast->statement->lastSourceLocation());
|
|
|
|
_block = loopcond;
|
|
condition(ast->expression, loopbody, loopend);
|
|
|
|
_block = loopend;
|
|
|
|
leaveLoop();
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(EmptyStatement *)
|
|
{
|
|
if (hasError)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(ExpressionStatement *ast)
|
|
{
|
|
if (hasError)
|
|
return true;
|
|
|
|
if (_variableEnvironment->compilationMode == EvalCode || _variableEnvironment->compilationMode == QmlBinding) {
|
|
Result e = expression(ast->expression);
|
|
if (*e)
|
|
move(_block->TEMP(_returnAddress), *e);
|
|
} else {
|
|
statement(ast->expression);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(ForEachStatement *ast)
|
|
{
|
|
if (hasError)
|
|
return true;
|
|
|
|
IR::BasicBlock *foreachin = _function->newBasicBlock(exceptionHandler());
|
|
IR::BasicBlock *foreachbody = _function->newBasicBlock(exceptionHandler());
|
|
IR::BasicBlock *foreachend = _function->newBasicBlock(exceptionHandler());
|
|
|
|
int objectToIterateOn = _block->newTemp();
|
|
Result expr = expression(ast->expression);
|
|
if (hasError)
|
|
return false;
|
|
move(_block->TEMP(objectToIterateOn), *expr);
|
|
IR::ExprList *args = _function->New<IR::ExprList>();
|
|
args->init(_block->TEMP(objectToIterateOn));
|
|
|
|
int iterator = _block->newTemp();
|
|
move(_block->TEMP(iterator), _block->CALL(_block->NAME(IR::Name::builtin_foreach_iterator_object, 0, 0), args));
|
|
|
|
enterLoop(ast, foreachend, foreachin);
|
|
_block->JUMP(foreachin);
|
|
|
|
_block = foreachbody;
|
|
int temp = _block->newTemp();
|
|
Result init = expression(ast->initialiser);
|
|
if (hasError)
|
|
return false;
|
|
move(*init, _block->TEMP(temp));
|
|
statement(ast->statement);
|
|
setLocation(_block->JUMP(foreachin), ast->lastSourceLocation());
|
|
|
|
_block = foreachin;
|
|
|
|
args = _function->New<IR::ExprList>();
|
|
args->init(_block->TEMP(iterator));
|
|
move(_block->TEMP(temp), _block->CALL(_block->NAME(IR::Name::builtin_foreach_next_property_name, 0, 0), args));
|
|
int null = _block->newTemp();
|
|
move(_block->TEMP(null), _block->CONST(IR::NullType, 0));
|
|
setLocation(cjump(_block->BINOP(IR::OpStrictNotEqual, _block->TEMP(temp), _block->TEMP(null)), foreachbody, foreachend), ast->forToken);
|
|
_block = foreachend;
|
|
|
|
leaveLoop();
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(ForStatement *ast)
|
|
{
|
|
if (hasError)
|
|
return true;
|
|
|
|
IR::BasicBlock *forcond = _function->newBasicBlock(exceptionHandler());
|
|
IR::BasicBlock *forbody = _function->newBasicBlock(exceptionHandler());
|
|
IR::BasicBlock *forstep = _function->newBasicBlock(exceptionHandler());
|
|
IR::BasicBlock *forend = _function->newBasicBlock(exceptionHandler());
|
|
|
|
statement(ast->initialiser);
|
|
_block->JUMP(forcond);
|
|
|
|
enterLoop(ast, forend, forstep);
|
|
|
|
_block = forcond;
|
|
if (ast->condition)
|
|
condition(ast->condition, forbody, forend);
|
|
else
|
|
_block->JUMP(forbody);
|
|
|
|
_block = forbody;
|
|
statement(ast->statement);
|
|
setLocation(_block->JUMP(forstep), ast->lastSourceLocation());
|
|
|
|
_block = forstep;
|
|
statement(ast->expression);
|
|
_block->JUMP(forcond);
|
|
|
|
_block = forend;
|
|
|
|
leaveLoop();
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(IfStatement *ast)
|
|
{
|
|
if (hasError)
|
|
return true;
|
|
|
|
IR::BasicBlock *iftrue = _function->newBasicBlock(exceptionHandler());
|
|
IR::BasicBlock *iffalse = ast->ko ? _function->newBasicBlock(exceptionHandler()) : 0;
|
|
IR::BasicBlock *endif = _function->newBasicBlock(exceptionHandler());
|
|
|
|
condition(ast->expression, iftrue, ast->ko ? iffalse : endif);
|
|
|
|
_block = iftrue;
|
|
statement(ast->ok);
|
|
_block->JUMP(endif);
|
|
|
|
if (ast->ko) {
|
|
_block = iffalse;
|
|
statement(ast->ko);
|
|
_block->JUMP(endif);
|
|
}
|
|
|
|
_block = endif;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(LabelledStatement *ast)
|
|
{
|
|
if (hasError)
|
|
return true;
|
|
|
|
// check that no outer loop contains the label
|
|
Loop *l = _loop;
|
|
while (l) {
|
|
if (l->labelledStatement && l->labelledStatement->label == ast->label) {
|
|
QString error = QString(QStringLiteral("Label '%1' has already been declared")).arg(ast->label.toString());
|
|
throwSyntaxError(ast->firstSourceLocation(), error);
|
|
return false;
|
|
}
|
|
l = l->parent;
|
|
}
|
|
_labelledStatement = ast;
|
|
|
|
if (AST::cast<AST::SwitchStatement *>(ast->statement) ||
|
|
AST::cast<AST::WhileStatement *>(ast->statement) ||
|
|
AST::cast<AST::DoWhileStatement *>(ast->statement) ||
|
|
AST::cast<AST::ForStatement *>(ast->statement) ||
|
|
AST::cast<AST::ForEachStatement *>(ast->statement) ||
|
|
AST::cast<AST::LocalForStatement *>(ast->statement) ||
|
|
AST::cast<AST::LocalForEachStatement *>(ast->statement)) {
|
|
statement(ast->statement); // labelledStatement will be associated with the ast->statement's loop.
|
|
} else {
|
|
IR::BasicBlock *breakBlock = _function->newBasicBlock(exceptionHandler());
|
|
enterLoop(ast->statement, breakBlock, /*continueBlock*/ 0);
|
|
statement(ast->statement);
|
|
_block->JUMP(breakBlock);
|
|
_block = breakBlock;
|
|
leaveLoop();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(LocalForEachStatement *ast)
|
|
{
|
|
if (hasError)
|
|
return true;
|
|
|
|
IR::BasicBlock *foreachin = _function->newBasicBlock(exceptionHandler());
|
|
IR::BasicBlock *foreachbody = _function->newBasicBlock(exceptionHandler());
|
|
IR::BasicBlock *foreachend = _function->newBasicBlock(exceptionHandler());
|
|
|
|
variableDeclaration(ast->declaration);
|
|
|
|
int iterator = _block->newTemp();
|
|
move(_block->TEMP(iterator), *expression(ast->expression));
|
|
IR::ExprList *args = _function->New<IR::ExprList>();
|
|
args->init(_block->TEMP(iterator));
|
|
move(_block->TEMP(iterator), _block->CALL(_block->NAME(IR::Name::builtin_foreach_iterator_object, 0, 0), args));
|
|
|
|
_block->JUMP(foreachin);
|
|
enterLoop(ast, foreachend, foreachin);
|
|
|
|
_block = foreachbody;
|
|
int temp = _block->newTemp();
|
|
move(identifier(ast->declaration->name.toString()), _block->TEMP(temp));
|
|
statement(ast->statement);
|
|
setLocation(_block->JUMP(foreachin), ast->lastSourceLocation());
|
|
|
|
_block = foreachin;
|
|
|
|
args = _function->New<IR::ExprList>();
|
|
args->init(_block->TEMP(iterator));
|
|
move(_block->TEMP(temp), _block->CALL(_block->NAME(IR::Name::builtin_foreach_next_property_name, 0, 0), args));
|
|
int null = _block->newTemp();
|
|
move(_block->TEMP(null), _block->CONST(IR::NullType, 0));
|
|
setLocation(cjump(_block->BINOP(IR::OpStrictNotEqual, _block->TEMP(temp), _block->TEMP(null)), foreachbody, foreachend), ast->forToken);
|
|
_block = foreachend;
|
|
|
|
leaveLoop();
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(LocalForStatement *ast)
|
|
{
|
|
if (hasError)
|
|
return true;
|
|
|
|
IR::BasicBlock *forcond = _function->newBasicBlock(exceptionHandler());
|
|
IR::BasicBlock *forbody = _function->newBasicBlock(exceptionHandler());
|
|
IR::BasicBlock *forstep = _function->newBasicBlock(exceptionHandler());
|
|
IR::BasicBlock *forend = _function->newBasicBlock(exceptionHandler());
|
|
|
|
variableDeclarationList(ast->declarations);
|
|
_block->JUMP(forcond);
|
|
|
|
enterLoop(ast, forend, forstep);
|
|
|
|
_block = forcond;
|
|
if (ast->condition)
|
|
condition(ast->condition, forbody, forend);
|
|
else
|
|
_block->JUMP(forbody);
|
|
|
|
_block = forbody;
|
|
statement(ast->statement);
|
|
setLocation(_block->JUMP(forstep), ast->lastSourceLocation());
|
|
|
|
_block = forstep;
|
|
statement(ast->expression);
|
|
_block->JUMP(forcond);
|
|
|
|
_block = forend;
|
|
|
|
leaveLoop();
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(ReturnStatement *ast)
|
|
{
|
|
if (hasError)
|
|
return true;
|
|
|
|
if (_variableEnvironment->compilationMode != FunctionCode && _variableEnvironment->compilationMode != QmlBinding) {
|
|
throwSyntaxError(ast->returnToken, QStringLiteral("Return statement outside of function"));
|
|
return false;
|
|
}
|
|
if (ast->expression) {
|
|
Result expr = expression(ast->expression);
|
|
move(_block->TEMP(_returnAddress), *expr);
|
|
}
|
|
|
|
// Since we're leaving, don't let any finally statements we emit as part of the unwinding
|
|
// jump to exception handlers at run-time if they throw.
|
|
IR::BasicBlock *unwindBlock = _function->newBasicBlock(/*no exception handler*/Q_NULLPTR);
|
|
_block->JUMP(unwindBlock);
|
|
_block = unwindBlock;
|
|
|
|
unwindException(0);
|
|
|
|
_block->JUMP(_exitBlock);
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(SwitchStatement *ast)
|
|
{
|
|
if (hasError)
|
|
return true;
|
|
|
|
IR::BasicBlock *switchend = _function->newBasicBlock(exceptionHandler());
|
|
|
|
if (ast->block) {
|
|
int lhs = _block->newTemp();
|
|
move(_block->TEMP(lhs), *expression(ast->expression));
|
|
IR::BasicBlock *switchcond = _function->newBasicBlock(exceptionHandler());
|
|
_block->JUMP(switchcond);
|
|
IR::BasicBlock *previousBlock = 0;
|
|
|
|
QHash<Node *, IR::BasicBlock *> blockMap;
|
|
|
|
enterLoop(ast, switchend, 0);
|
|
|
|
for (CaseClauses *it = ast->block->clauses; it; it = it->next) {
|
|
CaseClause *clause = it->clause;
|
|
|
|
_block = _function->newBasicBlock(exceptionHandler());
|
|
blockMap[clause] = _block;
|
|
|
|
if (previousBlock && !previousBlock->isTerminated())
|
|
previousBlock->JUMP(_block);
|
|
|
|
for (StatementList *it2 = clause->statements; it2; it2 = it2->next)
|
|
statement(it2->statement);
|
|
|
|
previousBlock = _block;
|
|
}
|
|
|
|
if (ast->block->defaultClause) {
|
|
_block = _function->newBasicBlock(exceptionHandler());
|
|
blockMap[ast->block->defaultClause] = _block;
|
|
|
|
if (previousBlock && !previousBlock->isTerminated())
|
|
previousBlock->JUMP(_block);
|
|
|
|
for (StatementList *it2 = ast->block->defaultClause->statements; it2; it2 = it2->next)
|
|
statement(it2->statement);
|
|
|
|
previousBlock = _block;
|
|
}
|
|
|
|
for (CaseClauses *it = ast->block->moreClauses; it; it = it->next) {
|
|
CaseClause *clause = it->clause;
|
|
|
|
_block = _function->newBasicBlock(exceptionHandler());
|
|
blockMap[clause] = _block;
|
|
|
|
if (previousBlock && !previousBlock->isTerminated())
|
|
previousBlock->JUMP(_block);
|
|
|
|
for (StatementList *it2 = clause->statements; it2; it2 = it2->next)
|
|
statement(it2->statement);
|
|
|
|
previousBlock = _block;
|
|
}
|
|
|
|
leaveLoop();
|
|
|
|
_block->JUMP(switchend);
|
|
|
|
_block = switchcond;
|
|
for (CaseClauses *it = ast->block->clauses; it; it = it->next) {
|
|
CaseClause *clause = it->clause;
|
|
Result rhs = expression(clause->expression);
|
|
IR::BasicBlock *iftrue = blockMap[clause];
|
|
IR::BasicBlock *iffalse = _function->newBasicBlock(exceptionHandler());
|
|
setLocation(cjump(binop(IR::OpStrictEqual, _block->TEMP(lhs), *rhs), iftrue, iffalse), clause->caseToken);
|
|
_block = iffalse;
|
|
}
|
|
|
|
for (CaseClauses *it = ast->block->moreClauses; it; it = it->next) {
|
|
CaseClause *clause = it->clause;
|
|
Result rhs = expression(clause->expression);
|
|
IR::BasicBlock *iftrue = blockMap[clause];
|
|
IR::BasicBlock *iffalse = _function->newBasicBlock(exceptionHandler());
|
|
setLocation(cjump(binop(IR::OpStrictEqual, _block->TEMP(lhs), *rhs), iftrue, iffalse), clause->caseToken);
|
|
_block = iffalse;
|
|
}
|
|
|
|
if (DefaultClause *defaultClause = ast->block->defaultClause) {
|
|
setLocation(_block->JUMP(blockMap[ast->block->defaultClause]), defaultClause->defaultToken);
|
|
}
|
|
}
|
|
|
|
_block->JUMP(switchend);
|
|
|
|
_block = switchend;
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(ThrowStatement *ast)
|
|
{
|
|
if (hasError)
|
|
return true;
|
|
|
|
Result expr = expression(ast->expression);
|
|
move(_block->TEMP(_returnAddress), *expr);
|
|
IR::ExprList *throwArgs = _function->New<IR::ExprList>();
|
|
throwArgs->expr = _block->TEMP(_returnAddress);
|
|
_block->EXP(_block->CALL(_block->NAME(IR::Name::builtin_throw, /*line*/0, /*column*/0), throwArgs));
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(TryStatement *ast)
|
|
{
|
|
if (hasError)
|
|
return true;
|
|
|
|
_function->hasTry = true;
|
|
|
|
if (_function->isStrict && ast->catchExpression &&
|
|
(ast->catchExpression->name == QLatin1String("eval") || ast->catchExpression->name == QLatin1String("arguments"))) {
|
|
throwSyntaxError(ast->catchExpression->identifierToken, QStringLiteral("Catch variable name may not be eval or arguments in strict mode"));
|
|
return false;
|
|
}
|
|
|
|
IR::BasicBlock *surroundingExceptionHandler = exceptionHandler();
|
|
|
|
// We always need a finally body to clean up the exception handler
|
|
// exceptions thrown in finally get caught by the surrounding catch block
|
|
IR::BasicBlock *finallyBody = 0;
|
|
IR::BasicBlock *catchBody = 0;
|
|
IR::BasicBlock *catchExceptionHandler = 0;
|
|
IR::BasicBlock *end = _function->newBasicBlock(surroundingExceptionHandler, IR::Function::DontInsertBlock);
|
|
|
|
if (ast->finallyExpression)
|
|
finallyBody = _function->newBasicBlock(surroundingExceptionHandler, IR::Function::DontInsertBlock);
|
|
|
|
if (ast->catchExpression) {
|
|
// exception handler for the catch body
|
|
catchExceptionHandler = _function->newBasicBlock(0, IR::Function::DontInsertBlock);
|
|
pushExceptionHandler(catchExceptionHandler);
|
|
catchBody = _function->newBasicBlock(catchExceptionHandler, IR::Function::DontInsertBlock);
|
|
popExceptionHandler();
|
|
pushExceptionHandler(catchBody);
|
|
} else {
|
|
Q_ASSERT(finallyBody);
|
|
pushExceptionHandler(finallyBody);
|
|
}
|
|
|
|
IR::BasicBlock *tryBody = _function->newBasicBlock(exceptionHandler());
|
|
_block->JUMP(tryBody);
|
|
|
|
ScopeAndFinally tcf(_scopeAndFinally, ast->finallyExpression);
|
|
_scopeAndFinally = &tcf;
|
|
|
|
_block = tryBody;
|
|
statement(ast->statement);
|
|
_block->JUMP(finallyBody ? finallyBody : end);
|
|
|
|
popExceptionHandler();
|
|
|
|
if (ast->catchExpression) {
|
|
pushExceptionHandler(catchExceptionHandler);
|
|
_function->addBasicBlock(catchBody);
|
|
_block = catchBody;
|
|
|
|
++_function->insideWithOrCatch;
|
|
IR::ExprList *catchArgs = _function->New<IR::ExprList>();
|
|
catchArgs->init(_block->STRING(_function->newString(ast->catchExpression->name.toString())));
|
|
_block->EXP(_block->CALL(_block->NAME(IR::Name::builtin_push_catch_scope, 0, 0), catchArgs));
|
|
{
|
|
ScopeAndFinally scope(_scopeAndFinally, ScopeAndFinally::CatchScope);
|
|
_scopeAndFinally = &scope;
|
|
statement(ast->catchExpression->statement);
|
|
_scopeAndFinally = scope.parent;
|
|
}
|
|
_block->EXP(_block->CALL(_block->NAME(IR::Name::builtin_pop_scope, 0, 0), 0));
|
|
--_function->insideWithOrCatch;
|
|
_block->JUMP(finallyBody ? finallyBody : end);
|
|
popExceptionHandler();
|
|
|
|
_function->addBasicBlock(catchExceptionHandler);
|
|
catchExceptionHandler->EXP(catchExceptionHandler->CALL(catchExceptionHandler->NAME(IR::Name::builtin_pop_scope, 0, 0), 0));
|
|
if (finallyBody || surroundingExceptionHandler)
|
|
catchExceptionHandler->JUMP(finallyBody ? finallyBody : surroundingExceptionHandler);
|
|
else
|
|
catchExceptionHandler->EXP(catchExceptionHandler->CALL(catchExceptionHandler->NAME(IR::Name::builtin_rethrow, 0, 0), 0));
|
|
}
|
|
|
|
_scopeAndFinally = tcf.parent;
|
|
|
|
if (finallyBody) {
|
|
_function->addBasicBlock(finallyBody);
|
|
_block = finallyBody;
|
|
|
|
int hasException = _block->newTemp();
|
|
move(_block->TEMP(hasException), _block->CALL(_block->NAME(IR::Name::builtin_unwind_exception, /*line*/0, /*column*/0), 0));
|
|
|
|
if (ast->finallyExpression && ast->finallyExpression->statement)
|
|
statement(ast->finallyExpression->statement);
|
|
|
|
IR::ExprList *arg = _function->New<IR::ExprList>();
|
|
arg->expr = _block->TEMP(hasException);
|
|
_block->EXP(_block->CALL(_block->NAME(IR::Name::builtin_throw, /*line*/0, /*column*/0), arg));
|
|
_block->JUMP(end);
|
|
}
|
|
|
|
_function->addBasicBlock(end);
|
|
_block = end;
|
|
|
|
return false;
|
|
}
|
|
|
|
void Codegen::unwindException(Codegen::ScopeAndFinally *outest)
|
|
{
|
|
int savedDepthForWidthOrCatch = _function->insideWithOrCatch;
|
|
ScopeAndFinally *scopeAndFinally = _scopeAndFinally;
|
|
qSwap(_scopeAndFinally, scopeAndFinally);
|
|
while (_scopeAndFinally != outest) {
|
|
switch (_scopeAndFinally->type) {
|
|
case ScopeAndFinally::WithScope:
|
|
// fall through
|
|
case ScopeAndFinally::CatchScope:
|
|
_block->EXP(_block->CALL(_block->NAME(IR::Name::builtin_pop_scope, 0, 0)));
|
|
_scopeAndFinally = _scopeAndFinally->parent;
|
|
--_function->insideWithOrCatch;
|
|
break;
|
|
case ScopeAndFinally::TryScope: {
|
|
ScopeAndFinally *tc = _scopeAndFinally;
|
|
_scopeAndFinally = tc->parent;
|
|
if (tc->finally && tc->finally->statement)
|
|
statement(tc->finally->statement);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
qSwap(_scopeAndFinally, scopeAndFinally);
|
|
_function->insideWithOrCatch = savedDepthForWidthOrCatch;
|
|
}
|
|
|
|
bool Codegen::visit(VariableStatement *ast)
|
|
{
|
|
if (hasError)
|
|
return true;
|
|
|
|
variableDeclarationList(ast->declarations);
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(WhileStatement *ast)
|
|
{
|
|
if (hasError)
|
|
return true;
|
|
|
|
IR::BasicBlock *whilecond = _function->newBasicBlock(exceptionHandler());
|
|
IR::BasicBlock *whilebody = _function->newBasicBlock(exceptionHandler());
|
|
IR::BasicBlock *whileend = _function->newBasicBlock(exceptionHandler());
|
|
|
|
enterLoop(ast, whileend, whilecond);
|
|
|
|
_block->JUMP(whilecond);
|
|
_block = whilecond;
|
|
condition(ast->expression, whilebody, whileend);
|
|
|
|
_block = whilebody;
|
|
statement(ast->statement);
|
|
setLocation(_block->JUMP(whilecond), ast->lastSourceLocation());
|
|
|
|
_block = whileend;
|
|
leaveLoop();
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(WithStatement *ast)
|
|
{
|
|
if (hasError)
|
|
return true;
|
|
|
|
_function->hasWith = true;
|
|
|
|
const int withObject = _block->newTemp();
|
|
Result src = expression(ast->expression);
|
|
if (hasError)
|
|
return false;
|
|
_block->MOVE(_block->TEMP(withObject), *src);
|
|
|
|
// need an exception handler for with to cleanup the with scope
|
|
IR::BasicBlock *withExceptionHandler = _function->newBasicBlock(exceptionHandler());
|
|
withExceptionHandler->EXP(withExceptionHandler->CALL(withExceptionHandler->NAME(IR::Name::builtin_pop_scope, 0, 0), 0));
|
|
if (!exceptionHandler())
|
|
withExceptionHandler->EXP(withExceptionHandler->CALL(withExceptionHandler->NAME(IR::Name::builtin_rethrow, 0, 0), 0));
|
|
else
|
|
withExceptionHandler->JUMP(exceptionHandler());
|
|
|
|
pushExceptionHandler(withExceptionHandler);
|
|
|
|
IR::BasicBlock *withBlock = _function->newBasicBlock(exceptionHandler());
|
|
|
|
_block->JUMP(withBlock);
|
|
_block = withBlock;
|
|
IR::ExprList *args = _function->New<IR::ExprList>();
|
|
args->init(_block->TEMP(withObject));
|
|
_block->EXP(_block->CALL(_block->NAME(IR::Name::builtin_push_with_scope, 0, 0), args));
|
|
|
|
++_function->insideWithOrCatch;
|
|
{
|
|
ScopeAndFinally scope(_scopeAndFinally);
|
|
_scopeAndFinally = &scope;
|
|
statement(ast->statement);
|
|
_scopeAndFinally = scope.parent;
|
|
}
|
|
--_function->insideWithOrCatch;
|
|
_block->EXP(_block->CALL(_block->NAME(IR::Name::builtin_pop_scope, 0, 0), 0));
|
|
popExceptionHandler();
|
|
|
|
IR::BasicBlock *next = _function->newBasicBlock(exceptionHandler());
|
|
_block->JUMP(next);
|
|
_block = next;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(UiArrayBinding *)
|
|
{
|
|
Q_ASSERT(!"not implemented");
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(UiObjectBinding *)
|
|
{
|
|
Q_ASSERT(!"not implemented");
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(UiObjectDefinition *)
|
|
{
|
|
Q_ASSERT(!"not implemented");
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(UiPublicMember *)
|
|
{
|
|
Q_ASSERT(!"not implemented");
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(UiScriptBinding *)
|
|
{
|
|
Q_ASSERT(!"not implemented");
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::visit(UiSourceElement *)
|
|
{
|
|
Q_ASSERT(!"not implemented");
|
|
return false;
|
|
}
|
|
|
|
bool Codegen::throwSyntaxErrorOnEvalOrArgumentsInStrictMode(IR::Expr *expr, const SourceLocation& loc)
|
|
{
|
|
if (!_variableEnvironment->isStrict)
|
|
return false;
|
|
if (IR::Name *n = expr->asName()) {
|
|
if (*n->id != QLatin1String("eval") && *n->id != QLatin1String("arguments"))
|
|
return false;
|
|
} else if (IR::ArgLocal *al = expr->asArgLocal()) {
|
|
if (!al->isArgumentsOrEval)
|
|
return false;
|
|
} else {
|
|
return false;
|
|
}
|
|
throwSyntaxError(loc, QStringLiteral("Variable name may not be eval or arguments in strict mode"));
|
|
return true;
|
|
}
|
|
|
|
void Codegen::throwSyntaxError(const SourceLocation &loc, const QString &detail)
|
|
{
|
|
if (hasError)
|
|
return;
|
|
|
|
hasError = true;
|
|
QQmlJS::DiagnosticMessage error;
|
|
error.message = detail;
|
|
error.loc = loc;
|
|
_errors << error;
|
|
}
|
|
|
|
void Codegen::throwReferenceError(const SourceLocation &loc, const QString &detail)
|
|
{
|
|
if (hasError)
|
|
return;
|
|
|
|
hasError = true;
|
|
QQmlJS::DiagnosticMessage error;
|
|
error.message = detail;
|
|
error.loc = loc;
|
|
_errors << error;
|
|
}
|
|
|
|
QList<QQmlJS::DiagnosticMessage> Codegen::errors() const
|
|
{
|
|
return _errors;
|
|
}
|
|
|
|
#ifndef V4_BOOTSTRAP
|
|
|
|
QList<QQmlError> Codegen::qmlErrors() const
|
|
{
|
|
QList<QQmlError> qmlErrors;
|
|
|
|
// Short circuit to avoid costly (de)heap allocation of QUrl if there are no errors.
|
|
if (_errors.size() == 0)
|
|
return qmlErrors;
|
|
|
|
qmlErrors.reserve(_errors.size());
|
|
|
|
QUrl url(_fileNameIsUrl ? QUrl(_module->fileName) : QUrl::fromLocalFile(_module->fileName));
|
|
for (const QQmlJS::DiagnosticMessage &msg: qAsConst(_errors)) {
|
|
QQmlError e;
|
|
e.setUrl(url);
|
|
e.setLine(msg.loc.startLine);
|
|
e.setColumn(msg.loc.startColumn);
|
|
e.setDescription(msg.message);
|
|
qmlErrors << e;
|
|
}
|
|
|
|
return qmlErrors;
|
|
}
|
|
|
|
void RuntimeCodegen::throwSyntaxError(const AST::SourceLocation &loc, const QString &detail)
|
|
{
|
|
if (hasError)
|
|
return;
|
|
hasError = true;
|
|
engine->throwSyntaxError(detail, _module->fileName, loc.startLine, loc.startColumn);
|
|
}
|
|
|
|
void RuntimeCodegen::throwReferenceError(const AST::SourceLocation &loc, const QString &detail)
|
|
{
|
|
if (hasError)
|
|
return;
|
|
hasError = true;
|
|
engine->throwReferenceError(detail, _module->fileName, loc.startLine, loc.startColumn);
|
|
}
|
|
|
|
#endif // V4_BOOTSTRAP
|