Based on Lars' idea, done together with Erik.
For proper scoping of the exception variable in catch blocks, it is necessary
to define the new scope (ExecutionContext for us) at run-time, it cannot be
determined at compile time.
This patch implements the necessary logic to create the new execution context
when entering a try block and re-uses the existing pop_scope infrastructure for
destroying it again. Within the catch scope it is necessary to do all lookups by
name, so the existing _function->insideWith variable was re-used and renamed
to _function->insideWithOrCatch. Additionally the new context also stores the
name and value of the separately scoped exception variable that shadows any
existing equally named variables in outter scopes.
CodeGen::unwindException also had a bug that it would generate the finally code
with the wrong _function->insideWithOrCatch level, resulting in name lookups
inside finally instead of local index lookups.
Change-Id: I5616af38c3558e553e971a6a894ce5239ccb8422
Reviewed-by: Lars Knoll <lars.knoll@digia.com>
F(CallBuiltinDeleteExceptionHandler, callBuiltinDeleteExceptionHandler) \
F(CallBuiltinGetException, callBuiltinGetException) \
F(CallBuiltinPushScope, callBuiltinPushScope) \
+ F(CallBuiltinPushCatchScope, callBuiltinPushCatchScope) \
F(CallBuiltinPopScope, callBuiltinPopScope) \
F(CallBuiltinForeachIteratorObject, callBuiltinForeachIteratorObject) \
F(CallBuiltinForeachNextPropertyName, callBuiltinForeachNextPropertyName) \
MOTH_INSTR_HEADER
Param arg;
};
+ struct instr_callBuiltinPushCatchScope {
+ MOTH_INSTR_HEADER
+ VM::String *varName;
+ };
struct instr_callBuiltinPopScope {
MOTH_INSTR_HEADER
};
instr_callBuiltinDeleteExceptionHandler callBuiltinDeleteExceptionHandler;
instr_callBuiltinGetException callBuiltinGetException;
instr_callBuiltinPushScope callBuiltinPushScope;
+ instr_callBuiltinPushCatchScope callBuiltinPushCatchScope;
instr_callBuiltinPopScope callBuiltinPopScope;
instr_callBuiltinForeachIteratorObject callBuiltinForeachIteratorObject;
instr_callBuiltinForeachNextPropertyName callBuiltinForeachNextPropertyName;
addInstruction(call);
}
+void InstructionSelection::callBuiltinPushCatchScope(const QString &exceptionVarName)
+{
+ Instruction::CallBuiltinPushCatchScope call;
+ call.varName = identifier(exceptionVarName);
+ addInstruction(call);
+}
+
void InstructionSelection::callBuiltinPopScope()
{
Instruction::CallBuiltinPopScope call;
virtual void callBuiltinForeachIteratorObject(IR::Temp *arg, IR::Temp *result);
virtual void callBuiltinForeachNextPropertyname(IR::Temp *arg, IR::Temp *result);
virtual void callBuiltinPushWithScope(IR::Temp *arg);
+ virtual void callBuiltinPushCatchScope(const QString &exceptionVarName);
virtual void callBuiltinPopScope();
virtual void callBuiltinDeclareVar(bool deletable, const QString &name);
virtual void callBuiltinDefineGetterSetter(IR::Temp *object, const QString &name, IR::Temp *getter, IR::Temp *setter);
context = __qmljs_builtin_push_with_scope(VALUE(instr.arg), context);
MOTH_END_INSTR(CallBuiltinPushScope)
+ MOTH_BEGIN_INSTR(CallBuiltinPushCatchScope)
+ context = __qmljs_builtin_push_catch_scope(instr.varName, context);
+ MOTH_END_INSTR(CallBuiltinPushCatchScope)
+
MOTH_BEGIN_INSTR(CallBuiltinPopScope)
context = __qmljs_builtin_pop_scope(context);
MOTH_END_INSTR(CallBuiltinPopScope)
return withCtx;
}
+ExecutionContext *ExecutionContext::createCatchScope(String *exceptionVarName)
+{
+ ExecutionContext *catchCtx = engine->newContext();
+ catchCtx->initForCatch(this, exceptionVarName);
+ engine->current = catchCtx;
+ return catchCtx;
+}
+
ExecutionContext *ExecutionContext::popScope()
{
assert(engine->current == this);
- assert(withObject != 0);
+ assert(withObject != 0 || exceptionVarName != 0);
engine->current = parent;
parent = 0;
arguments = 0;
argumentCount = 0;
locals = 0;
+ exceptionVarName = 0;
+ exceptionValue = Value::undefinedValue();
strictMode = false;
activation = 0;
withObject = 0;
arguments = 0;
argumentCount = 0;
locals = 0;
+ exceptionVarName = 0;
+ exceptionValue = Value::undefinedValue();
strictMode = false;
activation = 0;
withObject = with;
}
+void ExecutionContext::initForCatch(ExecutionContext *p, String *exceptionVarName)
+{
+ engine = p->engine;
+ parent = p;
+ outer = p;
+ thisObject = p->thisObject;
+
+ function = 0;
+ arguments = 0;
+ argumentCount = 0;
+ locals = 0;
+ this->exceptionVarName = exceptionVarName;
+ exceptionValue = engine->exception;
+ strictMode = p->strictMode;
+ activation = 0;
+ withObject = 0;
+}
+
void ExecutionContext::destroy()
{
delete[] arguments;
if (ctx->activation && ctx->activation->__hasProperty__(this, name))
return ctx->activation->__delete__(this, name);
}
+ if (ctx->exceptionVarName && ctx->exceptionVarName->isEqualTo(name))
+ return false;
if (FunctionObject *f = ctx->function) {
if (f->needsActivation || hasWith) {
for (unsigned int i = 0; i < f->varCount; ++i)
activation->mark();
if (withObject)
withObject->mark();
+ if (exceptionVarName)
+ exceptionVarName->mark();
+ exceptionValue.mark();
}
void ExecutionContext::setProperty(String *name, Value value)
w->__put__(ctx, name, value);
return;
}
+ } else if (ctx->exceptionVarName && ctx->exceptionVarName->isEqualTo(name)) {
+ ctx->exceptionValue = value;
+ return;
} else {
// qDebug() << ctx << "setting mutable binding";
if (ctx->setMutableBinding(this, name, value))
return thisObject;
bool hasWith = false;
+ bool hasCatchScope = false;
// qDebug() << "=== getProperty" << name->toQString();
for (ExecutionContext *ctx = this; ctx; ctx = ctx->outer) {
if (Object *w = ctx->withObject) {
continue;
}
+ if (ctx->exceptionVarName) {
+ hasCatchScope = true;
+ if (ctx->exceptionVarName->isEqualTo(name))
+ return ctx->exceptionValue;
+ }
+
if (FunctionObject *f = ctx->function) {
- if (f->needsActivation || hasWith) {
+ if (f->needsActivation || hasWith || hasCatchScope) {
for (unsigned int i = 0; i < f->varCount; ++i)
if (f->varList[i]->isEqualTo(name))
return ctx->locals[i];
return thisObject;
bool hasWith = false;
+ bool hasCatchScope = false;
for (ExecutionContext *ctx = this; ctx; ctx = ctx->outer) {
if (Object *w = ctx->withObject) {
hasWith = true;
continue;
}
+ if (ctx->exceptionVarName) {
+ hasCatchScope = true;
+ if (ctx->exceptionVarName->isEqualTo(name))
+ return ctx->exceptionValue;
+ }
+
if (FunctionObject *f = ctx->function) {
- if (f->needsActivation || hasWith) {
+ if (f->needsActivation || hasWith || hasCatchScope) {
for (unsigned int i = 0; i < f->varCount; ++i)
if (f->varList[i]->isEqualTo(name))
return ctx->locals[i];
return thisObject;
bool hasWith = false;
+ bool hasCatchScope = false;
for (ExecutionContext *ctx = this; ctx; ctx = ctx->outer) {
if (Object *w = ctx->withObject) {
hasWith = true;
continue;
}
+ if (ctx->exceptionVarName) {
+ hasCatchScope = true;
+ if (ctx->exceptionVarName->isEqualTo(name))
+ return ctx->exceptionValue;
+ }
+
if (FunctionObject *f = ctx->function) {
- if (f->needsActivation || hasWith) {
+ if (f->needsActivation || hasWith || hasCatchScope) {
for (unsigned int i = 0; i < f->varCount; ++i)
if (f->varList[i]->isEqualTo(name))
return ctx->locals[i];
outer = function->scope;
engine->current = this;
+ exceptionVarName = 0;
+ exceptionValue = Value::undefinedValue();
+
activation = 0;
withObject = 0;
unsigned int argumentCount;
Value *locals;
+ String *exceptionVarName;
+ Value exceptionValue;
+
String * const *formals() const;
unsigned int formalCount() const;
String * const *variables() const;
void init(ExecutionEngine *e);
void init(ExecutionContext *p, Object *with);
+ void initForCatch(ExecutionContext *p, String *exceptionVarName);
void destroy();
bool hasBinding(String *name) const;
bool deleteBinding(ExecutionContext *ctx, String *name);
ExecutionContext *createWithScope(Object *with);
+ ExecutionContext *createCatchScope(String* exceptionVarName);
ExecutionContext *popScope();
void initCallContext(ExecutionContext *parent);
return ctx->createWithScope(obj);
}
+ExecutionContext *__qmljs_builtin_push_catch_scope(String *exceptionVarName, ExecutionContext *ctx)
+{
+ return ctx->createCatchScope(exceptionVarName);
+}
+
ExecutionContext *__qmljs_builtin_pop_scope(ExecutionContext *ctx)
{
return ctx->popScope();
void __qmljs_builtin_throw(Value val, ExecutionContext *context);
void __qmljs_builtin_rethrow(ExecutionContext *context);
ExecutionContext *__qmljs_builtin_push_with_scope(Value o, ExecutionContext *ctx);
+ExecutionContext *__qmljs_builtin_push_catch_scope(String *exceptionVarName, ExecutionContext *ctx);
ExecutionContext *__qmljs_builtin_pop_scope(ExecutionContext *ctx);
void __qmljs_builtin_declare_var(ExecutionContext *ctx, bool deletable, String *name);
void __qmljs_builtin_define_property(Value object, String *name, Value val, ExecutionContext *ctx);
assert(expr.code);
initializer = *expr;
- if (! _env->parent || _function->insideWith) {
+ if (! _env->parent || _function->insideWithOrCatch) {
// it's global code.
move(_block->NAME(ast->name.toString(), ast->identifierToken.startLine, ast->identifierToken.startColumn), initializer);
} else {
{
int index = _env->findMember(name);
- if (! _function->hasDirectEval && !_function->insideWith && _env->parent) {
+ if (! _function->hasDirectEval && !_function->insideWithOrCatch && _env->parent) {
if (index != -1) {
return _block->TEMP(index);
}
move(_block->TEMP(inCatch), _block->CONST(IR::BoolType, true));
move(_block->TEMP(hasException), _block->CONST(IR::BoolType, false));
- const int exception = _block->newTemp();
- move(_block->TEMP(exception), _block->CALL(_block->NAME(IR::Name::builtin_get_exception, 0, 0), 0));
+ IR::ExprList *catchScopeArgs = _function->New<IR::ExprList>();
+ catchScopeArgs->init(_block->NAME(ast->catchExpression->name.toString(), ast->catchExpression->identifierToken.startLine, ast->catchExpression->identifierToken.startColumn));
+ _block->EXP(_block->CALL(_block->NAME(IR::Name::builtin_push_catch_scope, 0, 0), catchScopeArgs));
- // the variable used in the catch statement is local and hides any global
- // variable with the same name.
- const Environment::Member undefinedMember = { Environment::UndefinedMember, -1 , 0 };
- const Environment::Member catchMember = { Environment::VariableDefinition, exception, 0 };
- Environment::Member m = _env->members.value(ast->catchExpression->name.toString(), undefinedMember);
- _env->members.insert(ast->catchExpression->name.toString(), catchMember);
-
- statement(ast->catchExpression->statement);
+ ++_function->insideWithOrCatch;
+ {
+ ScopeAndFinally scope(_scopeAndFinally);
+ _scopeAndFinally = &scope;
+ statement(ast->catchExpression->statement);
+ _scopeAndFinally = scope.parent;
+ }
+ --_function->insideWithOrCatch;
- // reset the variable name to the one from the outer scope
- if (m.type == Environment::UndefinedMember)
- _env->members.remove(ast->catchExpression->name.toString());
- else
- _env->members.insert(ast->catchExpression->name.toString(), m);
+ _block->EXP(_block->CALL(_block->NAME(IR::Name::builtin_pop_scope, 0, 0)));
_block->JUMP(finallyBody);
}
void Codegen::unwindException(Codegen::ScopeAndFinally *outest)
{
+ int savedDepthForWidthOrCatch = _function->insideWithOrCatch;
ScopeAndFinally *scopeAndFinally = _scopeAndFinally;
qSwap(_scopeAndFinally, scopeAndFinally);
while (_scopeAndFinally != outest) {
if (_scopeAndFinally->popScope) {
_block->EXP(_block->CALL(_block->NAME(IR::Name::builtin_pop_scope, 0, 0)));
_scopeAndFinally = _scopeAndFinally->parent;
+ --_function->insideWithOrCatch;
} else {
_block->EXP(_block->CALL(_block->NAME(IR::Name::builtin_delete_exception_handler, 0, 0), _scopeAndFinally->deleteExceptionArgs));
ScopeAndFinally *tc = _scopeAndFinally;
}
}
qSwap(_scopeAndFinally, scopeAndFinally);
+ _function->insideWithOrCatch = savedDepthForWidthOrCatch;
}
bool Codegen::visit(VariableStatement *ast)
args->init(_block->TEMP(withObject));
_block->EXP(_block->CALL(_block->NAME(IR::Name::builtin_push_with_scope, 0, 0), args));
- ++_function->insideWith;
+ ++_function->insideWithOrCatch;
{
ScopeAndFinally scope(_scopeAndFinally);
_scopeAndFinally = &scope;
statement(ast->statement);
_scopeAndFinally = scope.parent;
}
- --_function->insideWith;
+ --_function->insideWithOrCatch;
_block->EXP(_block->CALL(_block->NAME(IR::Name::builtin_pop_scope, 0, 0), 0));
IR::BasicBlock *next = _function->newBasicBlock();
return "builtin_foreach_next_property_name";
case IR::Name::builtin_push_with_scope:
return "builtin_push_with_scope";
+ case IR::Name::builtin_push_catch_scope:
+ return "builtin_push_catch_scope";
case IR::Name::builtin_pop_scope:
return "builtin_pop_scope";
case IR::Name::builtin_declare_vars:
builtin_foreach_iterator_object,
builtin_foreach_next_property_name,
builtin_push_with_scope,
+ builtin_push_catch_scope,
builtin_pop_scope,
builtin_declare_vars,
builtin_define_property,
QList<const QString *> locals;
QVector<Function *> nestedFunctions;
- int insideWith;
+ int insideWithOrCatch;
uint hasDirectEval: 1;
uint usesArgumentsObject : 1;
, pool(&module->pool)
, tempCount(0)
, maxNumberOfArguments(0)
- , insideWith(0)
+ , insideWithOrCatch(0)
, hasDirectEval(false)
, usesArgumentsObject(false)
, isStrict(false)
generateFunctionCall(Assembler::ContextRegister, __qmljs_builtin_push_with_scope, arg, Assembler::ContextRegister);
}
+void InstructionSelection::callBuiltinPushCatchScope(const QString &exceptionVarName)
+{
+ generateFunctionCall(Assembler::ContextRegister, __qmljs_builtin_push_catch_scope, identifier(exceptionVarName), Assembler::ContextRegister);
+}
+
void InstructionSelection::callBuiltinPopScope()
{
generateFunctionCall(Assembler::ContextRegister, __qmljs_builtin_pop_scope, Assembler::ContextRegister);
virtual void callBuiltinForeachIteratorObject(IR::Temp *arg, IR::Temp *result);
virtual void callBuiltinForeachNextPropertyname(IR::Temp *arg, IR::Temp *result);
virtual void callBuiltinPushWithScope(IR::Temp *arg);
+ virtual void callBuiltinPushCatchScope(const QString &exceptionVarName);
virtual void callBuiltinPopScope();
virtual void callBuiltinDeclareVar(bool deletable, const QString &name);
virtual void callBuiltinDefineGetterSetter(IR::Temp *object, const QString &name, IR::Temp *getter, IR::Temp *setter);
callBuiltinPushWithScope(arg);
} return;
+ case IR::Name::builtin_push_catch_scope: {
+ IR::Name *arg = call->args->expr->asName();
+ assert(arg != 0);
+ callBuiltinPushCatchScope(*arg->id);
+ } return;
+
case IR::Name::builtin_pop_scope:
callBuiltinPopScope();
return;
virtual void callBuiltinForeachIteratorObject(IR::Temp *arg, IR::Temp *result) = 0;
virtual void callBuiltinForeachNextPropertyname(IR::Temp *arg, IR::Temp *result) = 0;
virtual void callBuiltinPushWithScope(IR::Temp *arg) = 0;
+ virtual void callBuiltinPushCatchScope(const QString &exceptionVarName) = 0;
virtual void callBuiltinPopScope() = 0;
virtual void callBuiltinDeclareVar(bool deletable, const QString &name) = 0;
virtual void callBuiltinDefineGetterSetter(IR::Temp *object, const QString &name, IR::Temp *getter, IR::Temp *setter) = 0;
Sbp_A4_T2 failing
S12.4_A1 failing
S15.2.4.4_A14 failing
-# Try/catch scoping issue
-S12.14_A4 failing
# Function declaration inside if / while
Sbp_12.5_A9_T3 failing
-Sbp_12.6.2_A13_T3 failing
\ No newline at end of file
+Sbp_12.6.2_A13_T3 failing