bool is_initializer_block)
: BreakableStatement(isolate, labels, TARGET_FOR_NAMED_ONLY),
statements_(capacity),
- is_initializer_block_(is_initializer_block) {
+ is_initializer_block_(is_initializer_block),
+ block_scope_(NULL) {
}
ZoneList<Statement*>* statements() { return &statements_; }
bool is_initializer_block() const { return is_initializer_block_; }
+ Scope* block_scope() const { return block_scope_; }
+ void set_block_scope(Scope* block_scope) { block_scope_ = block_scope; }
+
private:
ZoneList<Statement*> statements_;
bool is_initializer_block_;
+ Scope* block_scope_;
};
}
// Check extension/with/global object.
- if (context->has_extension()) {
+ if (!context->IsBlockContext() && context->has_extension()) {
if (context->IsCatchContext()) {
// Catch contexts have the variable name in the extension slot.
if (name->Equals(String::cast(context->extension()))) {
return context;
}
} else {
+ ASSERT(context->IsGlobalContext() ||
+ context->IsFunctionContext() ||
+ context->IsWithContext());
// Global, function, and with contexts may have an object in the
// extension slot.
Handle<JSObject> extension(JSObject::cast(context->extension()),
}
}
- // Only functions can have locals, parameters, and a function name.
- if (context->IsFunctionContext()) {
+ // Check serialized scope information of functions and blocks. Only
+ // functions can have parameters, and a function name.
+ if (context->IsFunctionContext() || context->IsBlockContext()) {
// We may have context-local slots. Check locals in the context.
- Handle<SerializedScopeInfo> scope_info(
- context->closure()->shared()->scope_info(), isolate);
+ Handle<SerializedScopeInfo> scope_info;
+ if (context->IsFunctionContext()) {
+ scope_info = Handle<SerializedScopeInfo>(
+ context->closure()->shared()->scope_info(), isolate);
+ } else {
+ ASSERT(context->IsBlockContext());
+ scope_info = Handle<SerializedScopeInfo>(
+ SerializedScopeInfo::cast(context->extension()), isolate);
+ }
+
Variable::Mode mode;
int index = scope_info->ContextSlotIndex(*name, &mode);
ASSERT(index < 0 || index >= MIN_CONTEXT_SLOTS);
Map* map = this->map();
return map == map->GetHeap()->with_context_map();
}
+ bool IsBlockContext() {
+ Map* map = this->map();
+ return map == map->GetHeap()->block_context_map();
+ }
// Tells whether the global context is marked with out of memory.
inline bool has_out_of_memory();
// Initialize the global objects
Handle<ObjectTemplate> global_template = CreateGlobalTemplate();
Persistent<Context> context = Context::New(NULL, global_template);
+ ASSERT(!context.IsEmpty());
Context::Scope scope(context);
#ifndef V8_SHARED
Local: 1,
With: 2,
Closure: 3,
- Catch: 4 };
+ Catch: 4,
+ Block: 5 };
// Current debug state.
#include "macro-assembler.h"
#include "objects.h"
#include "objects-visiting.h"
+#include "scopeinfo.h"
namespace v8 {
namespace internal {
}
+Handle<Context> Factory::NewBlockContext(
+ Handle<JSFunction> function,
+ Handle<Context> previous,
+ Handle<SerializedScopeInfo> scope_info) {
+ CALL_HEAP_FUNCTION(
+ isolate(),
+ isolate()->heap()->AllocateBlockContext(*function,
+ *previous,
+ *scope_info),
+ Context);
+}
+
+
Handle<Struct> Factory::NewStruct(InstanceType type) {
CALL_HEAP_FUNCTION(
isolate(),
}
+Handle<SerializedScopeInfo> Factory::NewSerializedScopeInfo(int length) {
+ CALL_HEAP_FUNCTION(
+ isolate(),
+ isolate()->heap()->AllocateSerializedScopeInfo(length),
+ SerializedScopeInfo);
+}
+
+
Handle<Code> Factory::NewCode(const CodeDesc& desc,
Code::Flags flags,
Handle<Object> self_ref,
Handle<Context> previous,
Handle<JSObject> extension);
+ // Create a 'block' context.
+ Handle<Context> NewBlockContext(Handle<JSFunction> function,
+ Handle<Context> previous,
+ Handle<SerializedScopeInfo> scope_info);
+
// Return the Symbol matching the passed in string.
Handle<String> SymbolFromString(Handle<String> value);
Handle<Context> context,
PretenureFlag pretenure = TENURED);
+ Handle<SerializedScopeInfo> NewSerializedScopeInfo(int length);
+
Handle<Code> NewCode(const CodeDesc& desc,
Code::Flags flags,
Handle<Object> self_reference,
DEFINE_bool(harmony_typeof, false, "enable harmony semantics for typeof")
DEFINE_bool(harmony_proxies, false, "enable harmony proxies")
DEFINE_bool(harmony_weakmaps, false, "enable harmony weak maps")
+DEFINE_bool(harmony_block_scoping, false, "enable harmony block scoping")
// Flags for experimental implementation features.
DEFINE_bool(unbox_double_arrays, true, "automatically unbox arrays of doubles")
#include "macro-assembler.h"
#include "prettyprinter.h"
#include "scopes.h"
+#include "scopeinfo.h"
#include "stub-cache.h"
namespace v8 {
Breakable nested_statement(this, stmt);
SetStatementPosition(stmt);
+ Scope* saved_scope = scope();
+ if (stmt->block_scope() != NULL) {
+ { Comment cmnt(masm_, "[ Extend block context");
+ scope_ = stmt->block_scope();
+ __ Push(scope_->GetSerializedScopeInfo());
+ PushFunctionArgumentForContextAllocation();
+ __ CallRuntime(Runtime::kPushBlockContext, 2);
+ StoreToFrameField(StandardFrameConstants::kContextOffset,
+ context_register());
+ }
+ { Comment cmnt(masm_, "[ Declarations");
+ VisitDeclarations(scope_->declarations());
+ }
+ }
PrepareForBailoutForId(stmt->EntryId(), NO_REGISTERS);
VisitStatements(stmt->statements());
+ scope_ = saved_scope;
__ bind(nested_statement.break_target());
PrepareForBailoutForId(stmt->ExitId(), NO_REGISTERS);
}
set_fixed_cow_array_map(Map::cast(obj));
ASSERT(fixed_array_map() != fixed_cow_array_map());
+ { MaybeObject* maybe_obj =
+ AllocateMap(FIXED_ARRAY_TYPE, kVariableSizeSentinel);
+ if (!maybe_obj->ToObject(&obj)) return false;
+ }
+ set_serialized_scope_info_map(Map::cast(obj));
+
{ MaybeObject* maybe_obj = AllocateMap(HEAP_NUMBER_TYPE, HeapNumber::kSize);
if (!maybe_obj->ToObject(&obj)) return false;
}
}
set_with_context_map(Map::cast(obj));
+ { MaybeObject* maybe_obj =
+ AllocateMap(FIXED_ARRAY_TYPE, kVariableSizeSentinel);
+ if (!maybe_obj->ToObject(&obj)) return false;
+ }
+ set_block_context_map(Map::cast(obj));
+
{ MaybeObject* maybe_obj =
AllocateMap(FIXED_ARRAY_TYPE, kVariableSizeSentinel);
if (!maybe_obj->ToObject(&obj)) return false;
}
+MaybeObject* Heap::AllocateBlockContext(JSFunction* function,
+ Context* previous,
+ SerializedScopeInfo* scope_info) {
+ Object* result;
+ { MaybeObject* maybe_result =
+ AllocateFixedArray(scope_info->NumberOfContextSlots());
+ if (!maybe_result->ToObject(&result)) return maybe_result;
+ }
+ // TODO(keuchel): properly initialize context slots.
+ Context* context = reinterpret_cast<Context*>(result);
+ context->set_map(block_context_map());
+ context->set_closure(function);
+ context->set_previous(previous);
+ context->set_extension(scope_info);
+ context->set_global(previous->global());
+ return context;
+}
+
+
+MaybeObject* Heap::AllocateSerializedScopeInfo(int length) {
+ Object* result;
+ { MaybeObject* maybe_result = AllocateFixedArray(length, TENURED);
+ if (!maybe_result->ToObject(&result)) return maybe_result;
+ }
+ SerializedScopeInfo* scope_info =
+ reinterpret_cast<SerializedScopeInfo*>(result);
+ scope_info->set_map(serialized_scope_info_map());
+ return scope_info;
+}
+
+
MaybeObject* Heap::AllocateStruct(InstanceType type) {
Map* map;
switch (type) {
V(Map, heap_number_map, HeapNumberMap) \
V(Map, global_context_map, GlobalContextMap) \
V(Map, fixed_array_map, FixedArrayMap) \
+ V(Map, serialized_scope_info_map, SerializedScopeInfoMap) \
V(Map, fixed_cow_array_map, FixedCOWArrayMap) \
V(Map, fixed_double_array_map, FixedDoubleArrayMap) \
V(Object, no_interceptor_result_sentinel, NoInterceptorResultSentinel) \
V(Map, function_context_map, FunctionContextMap) \
V(Map, catch_context_map, CatchContextMap) \
V(Map, with_context_map, WithContextMap) \
+ V(Map, block_context_map, BlockContextMap) \
V(Map, code_map, CodeMap) \
V(Map, oddball_map, OddballMap) \
V(Map, global_property_cell_map, GlobalPropertyCellMap) \
V(closure_symbol, "(closure)") \
V(use_strict, "use strict") \
V(dot_symbol, ".") \
- V(anonymous_function_symbol, "(anonymous function)")
+ V(anonymous_function_symbol, "(anonymous function)") \
+ V(block_scope_symbol, ".block")
// Forward declarations.
class GCTracer;
// Allocates an empty code cache.
MUST_USE_RESULT MaybeObject* AllocateCodeCache();
+ // Allocates a serialized scope info.
+ MUST_USE_RESULT MaybeObject* AllocateSerializedScopeInfo(int length);
+
// Allocates an empty PolymorphicCodeCache.
MUST_USE_RESULT MaybeObject* AllocatePolymorphicCodeCache();
Context* previous,
JSObject* extension);
+ // Allocate a block context.
+ MUST_USE_RESULT MaybeObject* AllocateBlockContext(JSFunction* function,
+ Context* previous,
+ SerializedScopeInfo* info);
+
// Allocates a new utility object in the old generation.
MUST_USE_RESULT MaybeObject* AllocateStruct(InstanceType type);
ASSERT(!HasStackOverflow());
ASSERT(current_block() != NULL);
ASSERT(current_block()->HasPredecessor());
+ if (stmt->block_scope() != NULL) {
+ return Bailout("ScopedBlock");
+ }
BreakAndContinueInfo break_info(stmt);
{ BreakAndContinueScope push(&break_info, this);
CHECK_BAILOUT(VisitStatements(stmt->statements()));
Local: 1,
With: 2,
Closure: 3,
- Catch: 4 };
+ Catch: 4,
+ Block: 5 };
// Mirror hierarchy:
return (map == heap->function_context_map() ||
map == heap->catch_context_map() ||
map == heap->with_context_map() ||
- map == heap->global_context_map());
+ map == heap->global_context_map() ||
+ map == heap->block_context_map());
}
return false;
}
}
+bool Object::IsSerializedScopeInfo() {
+ return Object::IsHeapObject() &&
+ HeapObject::cast(this)->map() ==
+ HeapObject::cast(this)->GetHeap()->serialized_scope_info_map();
+}
+
+
bool Object::IsJSFunction() {
return Object::IsHeapObject()
&& HeapObject::cast(this)->map()->instance_type() == JS_FUNCTION_TYPE;
V(FixedDoubleArray) \
V(Context) \
V(GlobalContext) \
+ V(SerializedScopeInfo) \
V(JSFunction) \
V(Code) \
V(Oddball) \
pre_data_(pre_data),
fni_(NULL),
stack_overflow_(false),
- parenthesized_function_(false) {
+ parenthesized_function_(false),
+ harmony_block_scoping_(false) {
AstNode::ResetIds();
}
isolate()->Throw(*result, &location);
}
+void Parser::SetHarmonyBlockScoping(bool block_scoping) {
+ harmony_block_scoping_ = block_scoping;
+}
// Base class containing common code for the different finder classes used by
// the parser.
Block* Parser::ParseBlock(ZoneStringList* labels, bool* ok) {
+ if (harmony_block_scoping_) return ParseScopedBlock(labels, ok);
+
// Block ::
// '{' Statement* '}'
}
+Block* Parser::ParseScopedBlock(ZoneStringList* labels, bool* ok) {
+ // Construct block expecting 16 statements.
+ Block* body = new(zone()) Block(isolate(), labels, 16, false);
+ Scope* saved_scope = top_scope_;
+ Scope* block_scope = NewScope(top_scope_,
+ Scope::BLOCK_SCOPE,
+ inside_with());
+ body->set_block_scope(block_scope);
+ block_scope->DeclareLocal(isolate()->factory()->block_scope_symbol(),
+ Variable::VAR);
+ if (top_scope_->is_strict_mode()) {
+ block_scope->EnableStrictMode();
+ }
+ top_scope_ = block_scope;
+
+ // Parse the statements and collect escaping labels.
+ TargetCollector collector;
+ Target target(&this->target_stack_, &collector);
+ Expect(Token::LBRACE, CHECK_OK);
+ {
+ Target target_body(&this->target_stack_, body);
+ InitializationBlockFinder block_finder(top_scope_, target_stack_);
+
+ while (peek() != Token::RBRACE) {
+ Statement* stat = ParseStatement(NULL, CHECK_OK);
+ if (stat && !stat->IsEmpty()) {
+ body->AddStatement(stat);
+ block_finder.Update(stat);
+ }
+ }
+ }
+ Expect(Token::RBRACE, CHECK_OK);
+
+ // Create exit block.
+ Block* exit = new(zone()) Block(isolate(), NULL, 1, false);
+ exit->AddStatement(new(zone()) ExitContextStatement());
+
+ // Create a try-finally statement.
+ TryFinallyStatement* try_finally =
+ new(zone()) TryFinallyStatement(body, exit);
+ try_finally->set_escaping_targets(collector.targets());
+ top_scope_ = saved_scope;
+
+ // Create a result block.
+ Block* result = new(zone()) Block(isolate(), NULL, 1, false);
+ result->AddStatement(try_finally);
+ return result;
+}
+
+
Block* Parser::ParseVariableStatement(bool* ok) {
// VariableStatement ::
// VariableDeclarations ';'
Handle<Script> script = info->script();
if (info->is_lazy()) {
Parser parser(script, true, NULL, NULL);
+ parser.SetHarmonyBlockScoping(!info->is_native() &&
+ FLAG_harmony_block_scoping);
result = parser.ParseLazy(info);
} else {
// Whether we allow %identifier(..) syntax.
info->allows_natives_syntax() || FLAG_allow_natives_syntax;
ScriptDataImpl* pre_data = info->pre_parse_data();
Parser parser(script, allow_natives_syntax, info->extension(), pre_data);
+ parser.SetHarmonyBlockScoping(!info->is_native() &&
+ FLAG_harmony_block_scoping);
if (pre_data != NULL && pre_data->has_error()) {
Scanner::Location loc = pre_data->MessageLocation();
const char* message = pre_data->BuildMessage();
info->StrictMode());
}
}
-
info->SetFunction(result);
return (result != NULL);
}
void ReportMessageAt(Scanner::Location loc,
const char* message,
Vector<Handle<String> > args);
+ void SetHarmonyBlockScoping(bool block_scoping);
private:
// Limit on number of function parameters is chosen arbitrarily.
Statement* ParseFunctionDeclaration(bool* ok);
Statement* ParseNativeDeclaration(bool* ok);
Block* ParseBlock(ZoneStringList* labels, bool* ok);
+ Block* ParseScopedBlock(ZoneStringList* labels, bool* ok);
Block* ParseVariableStatement(bool* ok);
Block* ParseVariableDeclarations(bool accept_IN,
Handle<String>* out,
// Heuristically that means that the function will be called immediately,
// so never lazily compile it.
bool parenthesized_function_;
+ bool harmony_block_scoping_;
friend class LexicalScope;
};
}
+RUNTIME_FUNCTION(MaybeObject*, Runtime_PushBlockContext) {
+ NoHandleAllocation ha;
+ ASSERT(args.length() == 2);
+ SerializedScopeInfo* scope_info = SerializedScopeInfo::cast(args[0]);
+ JSFunction* function;
+ if (args[1]->IsSmi()) {
+ // A smi sentinel indicates a context nested inside global code rather
+ // than some function. There is a canonical empty function that can be
+ // gotten from the global context.
+ function = isolate->context()->global_context()->closure();
+ } else {
+ function = JSFunction::cast(args[1]);
+ }
+ Context* context;
+ MaybeObject* maybe_context =
+ isolate->heap()->AllocateBlockContext(function,
+ isolate->context(),
+ scope_info);
+ if (!maybe_context->To(&context)) return maybe_context;
+ isolate->set_context(context);
+ return context;
+}
+
+
RUNTIME_FUNCTION(MaybeObject*, Runtime_DeleteContextSlot) {
HandleScope scope(isolate);
ASSERT(args.length() == 2);
}
+// Create a plain JSObject which materializes the block scope for the specified
+// block context.
+static Handle<JSObject> MaterializeBlockScope(
+ Isolate* isolate,
+ Handle<Context> context) {
+ ASSERT(context->IsBlockContext());
+ Handle<SerializedScopeInfo> serialized_scope_info(
+ SerializedScopeInfo::cast(context->extension()));
+ ScopeInfo<> scope_info(*serialized_scope_info);
+
+ // Allocate and initialize a JSObject with all the arguments, stack locals
+ // heap locals and extension properties of the debugged function.
+ Handle<JSObject> block_scope =
+ isolate->factory()->NewJSObject(isolate->object_function());
+
+ // Fill all context locals.
+ if (scope_info.number_of_context_slots() > Context::MIN_CONTEXT_SLOTS) {
+ if (!CopyContextLocalsToScopeObject(isolate,
+ serialized_scope_info, scope_info,
+ context, block_scope)) {
+ return Handle<JSObject>();
+ }
+ }
+
+ return block_scope;
+}
+
+
// Iterate over the actual scopes visible from a stack frame. All scopes are
// backed by an actual context except the local scope, which is inserted
// "artifically" in the context chain.
ScopeTypeLocal,
ScopeTypeWith,
ScopeTypeClosure,
- ScopeTypeCatch
+ ScopeTypeCatch,
+ ScopeTypeBlock
};
ScopeIterator(Isolate* isolate,
} else if (context_->IsFunctionContext()) {
at_local_ = true;
} else if (context_->closure() != *function_) {
- // The context_ is a with or catch block from the outer function.
- ASSERT(context_->IsWithContext() || context_->IsCatchContext());
+ // The context_ is a block or with or catch block from the outer function.
+ ASSERT(context_->IsWithContext() ||
+ context_->IsCatchContext() ||
+ context_->IsBlockContext());
at_local_ = true;
}
}
if (context_->IsCatchContext()) {
return ScopeTypeCatch;
}
+ if (context_->IsBlockContext()) {
+ return ScopeTypeBlock;
+ }
ASSERT(context_->IsWithContext());
return ScopeTypeWith;
}
case ScopeIterator::ScopeTypeClosure:
// Materialize the content of the closure scope into a JSObject.
return MaterializeClosure(isolate_, CurrentContext());
+ case ScopeIterator::ScopeTypeBlock:
+ return MaterializeBlockScope(isolate_, CurrentContext());
}
UNREACHABLE();
return Handle<JSObject>();
new_previous,
name,
thrown_object);
+ } else if (current->IsBlockContext()) {
+ Handle<SerializedScopeInfo> scope_info(
+ SerializedScopeInfo::cast(current->extension()));
+ new_current =
+ isolate->factory()->NewBlockContext(function, new_previous, scope_info);
} else {
+ ASSERT(current->IsWithContext());
Handle<JSObject> extension(JSObject::cast(current->extension()));
new_current =
isolate->factory()->NewWithContext(function, new_previous, extension);
F(NewFunctionContext, 1, 1) \
F(PushWithContext, 2, 1) \
F(PushCatchContext, 3, 1) \
+ F(PushBlockContext, 2, 1) \
F(DeleteContextSlot, 2, 1) \
F(LoadContextSlot, 2, 2) \
F(LoadContextSlotNoReferenceError, 2, 2) \
stack_slots_.length();
Handle<SerializedScopeInfo> data(
- SerializedScopeInfo::cast(*FACTORY->NewFixedArray(length, TENURED)));
+ SerializedScopeInfo::cast(*FACTORY->NewSerializedScopeInfo(length)));
AssertNoAllocation nogc;
Object** p0 = data->data_start();
public :
static SerializedScopeInfo* cast(Object* object) {
- ASSERT(object->IsFixedArray());
+ ASSERT(object->IsSerializedScopeInfo());
return reinterpret_cast<SerializedScopeInfo*>(object);
}
}
-Scope::Scope(Scope* inner_scope, Handle<SerializedScopeInfo> scope_info)
+Scope::Scope(Scope* inner_scope,
+ Type type,
+ Handle<SerializedScopeInfo> scope_info)
: isolate_(Isolate::Current()),
inner_scopes_(4),
variables_(),
decls_(4),
already_resolved_(true) {
ASSERT(!scope_info.is_null());
- SetDefaults(FUNCTION_SCOPE, NULL, scope_info);
+ SetDefaults(type, NULL, scope_info);
if (scope_info->HasHeapAllocatedLocals()) {
num_heap_slots_ = scope_info_->NumberOfContextSlots();
}
if (context->IsFunctionContext()) {
SerializedScopeInfo* scope_info =
context->closure()->shared()->scope_info();
- current_scope =
- new Scope(current_scope, Handle<SerializedScopeInfo>(scope_info));
+ current_scope = new Scope(current_scope, FUNCTION_SCOPE,
+ Handle<SerializedScopeInfo>(scope_info));
+ } else if (context->IsBlockContext()) {
+ SerializedScopeInfo* scope_info =
+ SerializedScopeInfo::cast(context->extension());
+ current_scope = new Scope(current_scope, BLOCK_SCOPE,
+ Handle<SerializedScopeInfo>(scope_info));
} else {
ASSERT(context->IsCatchContext());
String* name = String::cast(context->extension());
// instead load them directly from the stack. Currently, the only
// such parameter is 'this' which is passed on the stack when
// invoking scripts
- if (is_catch_scope()) {
+ if (is_catch_scope() || is_block_scope()) {
ASSERT(outer_scope() != NULL);
receiver_ = outer_scope()->receiver();
} else {
+ ASSERT(is_function_scope() ||
+ is_global_scope() ||
+ is_eval_scope());
Variable* var =
variables_.Declare(this,
isolate_->factory()->this_symbol(),
Scope* Scope::DeclarationScope() {
Scope* scope = this;
- while (scope->is_catch_scope()) {
+ while (scope->is_catch_scope() ||
+ scope->is_block_scope()) {
scope = scope->outer_scope();
}
return scope;
}
+Handle<SerializedScopeInfo> Scope::GetSerializedScopeInfo() {
+ if (scope_info_.is_null()) {
+ scope_info_ = SerializedScopeInfo::Create(this);
+ }
+ return scope_info_;
+}
+
+
#ifdef DEBUG
static const char* Header(Scope::Type type) {
switch (type) {
case Scope::FUNCTION_SCOPE: return "function";
case Scope::GLOBAL_SCOPE: return "global";
case Scope::CATCH_SCOPE: return "catch";
+ case Scope::BLOCK_SCOPE: return "block";
}
UNREACHABLE();
return NULL;
PrintF("; // ");
if (var->rewrite() != NULL) {
PrintF("%s, ", printer->Print(var->rewrite()));
- if (var->is_accessed_from_inner_scope()) PrintF(", ");
+ if (var->is_accessed_from_inner_function_scope()) PrintF(", ");
+ }
+ if (var->is_accessed_from_inner_function_scope()) {
+ PrintF("inner scope access");
}
- if (var->is_accessed_from_inner_scope()) PrintF("inner scope access");
PrintF("\n");
}
}
// another variable that is introduced dynamically via an 'eval' call
// or a 'with' statement).
Variable* Scope::LookupRecursive(Handle<String> name,
- bool inner_lookup,
+ bool from_inner_function,
Variable** invalidated_local) {
// If we find a variable, but the current scope calls 'eval', the found
// variable may not be the correct one (the 'eval' may introduce a
// (Even if there is an 'eval' in this scope which introduces the
// same variable again, the resulting variable remains the same.
// Note that enclosing 'with' statements are handled at the call site.)
- if (!inner_lookup)
+ if (!from_inner_function)
return var;
} else {
var = function_;
} else if (outer_scope_ != NULL) {
- var = outer_scope_->LookupRecursive(name, true, invalidated_local);
+ var = outer_scope_->LookupRecursive(
+ name,
+ is_function_scope() || from_inner_function,
+ invalidated_local);
// We may have found a variable in an outer scope. However, if
// the current scope is inside a 'with', the actual variable may
// be a property introduced via the 'with' statement. Then, the
ASSERT(var != NULL);
// If this is a lookup from an inner scope, mark the variable.
- if (inner_lookup) {
- var->MarkAsAccessedFromInnerScope();
+ if (from_inner_function) {
+ var->MarkAsAccessedFromInnerFunctionScope();
}
// If the variable we have found is just a guess, invalidate the
// via an eval() call. This is only possible if the variable has a
// visible name.
if ((var->is_this() || var->name()->length() > 0) &&
- (var->is_accessed_from_inner_scope() ||
+ (var->is_accessed_from_inner_function_scope() ||
scope_calls_eval_ ||
inner_scope_calls_eval_ ||
scope_contains_with_ ||
- is_catch_scope())) {
+ is_catch_scope() ||
+ is_block_scope())) {
var->set_is_used(true);
}
// Global variables do not need to be allocated.
// Exceptions: temporary variables are never allocated in a context;
// catch-bound variables are always allocated in a context.
if (var->mode() == Variable::TEMPORARY) return false;
- if (is_catch_scope()) return true;
- return var->is_accessed_from_inner_scope() ||
+ if (is_catch_scope() || is_block_scope()) return true;
+ return var->is_accessed_from_inner_function_scope() ||
scope_calls_eval_ ||
inner_scope_calls_eval_ ||
scope_contains_with_ ||
if (uses_nonstrict_arguments) {
// Give the parameter a use from an inner scope, to force allocation
// to the context.
- var->MarkAsAccessedFromInnerScope();
+ var->MarkAsAccessedFromInnerFunctionScope();
}
if (MustAllocate(var)) {
EVAL_SCOPE, // The top-level scope for an eval source.
FUNCTION_SCOPE, // The top-level scope for a function.
GLOBAL_SCOPE, // The top-level scope for a program or a top-level eval.
- CATCH_SCOPE // The scope introduced by catch.
+ CATCH_SCOPE, // The scope introduced by catch.
+ BLOCK_SCOPE // The scope introduced by a new block.
};
Scope(Scope* outer_scope, Type type);
bool is_function_scope() const { return type_ == FUNCTION_SCOPE; }
bool is_global_scope() const { return type_ == GLOBAL_SCOPE; }
bool is_catch_scope() const { return type_ == CATCH_SCOPE; }
+ bool is_block_scope() const { return type_ == BLOCK_SCOPE; }
bool is_strict_mode() const { return strict_mode_; }
bool is_strict_mode_eval_scope() const {
return is_eval_scope() && is_strict_mode();
// where var declarations will be hoisted to in the implementation.
Scope* DeclarationScope();
+ Handle<SerializedScopeInfo> GetSerializedScopeInfo();
+
// ---------------------------------------------------------------------------
// Strict mode support.
bool IsDeclared(Handle<String> name) {
// Variable resolution.
Variable* LookupRecursive(Handle<String> name,
- bool inner_lookup,
+ bool from_inner_function,
Variable** invalidated_local);
void ResolveVariable(Scope* global_scope,
Handle<Context> context,
void AllocateVariablesRecursively();
private:
- // Construct a function scope based on the scope info.
- Scope(Scope* inner_scope, Handle<SerializedScopeInfo> scope_info);
+ // Construct a function or block scope based on the scope info.
+ Scope(Scope* inner_scope, Type type, Handle<SerializedScopeInfo> scope_info);
// Construct a catch scope with a binding for the name.
Scope(Scope* inner_scope, Handle<String> catch_variable_name);
ASSERT(!o->IsScript());
return o->IsString() || o->IsSharedFunctionInfo() ||
o->IsHeapNumber() || o->IsCode() ||
+ o->IsSerializedScopeInfo() ||
o->map() == HEAP->fixed_cow_array_map();
}
local_if_not_shadowed_(NULL),
rewrite_(NULL),
is_valid_LHS_(is_valid_LHS),
- is_accessed_from_inner_scope_(false),
+ is_accessed_from_inner_function_scope_(false),
is_used_(false) {
// names must be canonicalized for fast equality checks
ASSERT(name->IsSymbol());
Handle<String> name() const { return name_; }
Mode mode() const { return mode_; }
- bool is_accessed_from_inner_scope() const {
- return is_accessed_from_inner_scope_;
+ bool is_accessed_from_inner_function_scope() const {
+ return is_accessed_from_inner_function_scope_;
}
- void MarkAsAccessedFromInnerScope() {
- is_accessed_from_inner_scope_ = true;
+ void MarkAsAccessedFromInnerFunctionScope() {
+ ASSERT(mode_ != TEMPORARY);
+ is_accessed_from_inner_function_scope_ = true;
}
bool is_used() { return is_used_; }
void set_is_used(bool flag) { is_used_ = flag; }
bool is_valid_LHS_;
// Usage info.
- bool is_accessed_from_inner_scope_; // set by variable resolver
+ bool is_accessed_from_inner_function_scope_; // set by variable resolver
bool is_used_;
};
"NewStrictArgumentsFast": true,
"PushWithContext": true,
"PushCatchContext": true,
+ "PushBlockContext": true,
"LazyCompile": true,
"LazyRecompile": true,
"NotifyDeoptimized": true,
--- /dev/null
+// Copyright 2011 the V8 project authors. All rights reserved.
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following
+// disclaimer in the documentation and/or other materials provided
+// with the distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived
+// from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Flags: --allow-natives-syntax
+// Test deserialization of block contexts during lazy compilation
+// of closures.
+
+function f() {
+ var g;
+ {
+ // TODO(keuchel): introduce let
+ var x = 0;
+ g = function () {
+ x = x + 1;
+ return x;
+ }
+ }
+ return g;
+}
+
+var o = f();
+assertEquals(1, o());
+assertEquals(2, o());
+assertEquals(3, o());
+%OptimizeFunctionOnNextCall(o);
+assertEquals(4, o());
--- /dev/null
+// Copyright 2011 the V8 project authors. All rights reserved.
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following
+// disclaimer in the documentation and/or other materials provided
+// with the distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived
+// from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Flags: --allow-natives-syntax --harmony-block-scoping
+// Test functionality of block scopes.
+
+// Hoisting of var declarations.
+function f1() {
+ {
+ var x = 1;
+ var y;
+ }
+ assertEquals(1, x)
+ assertEquals(undefined, y)
+}
+f1();
+
+// Dynamic lookup through block scopes.
+function f2(one) {
+ var x = one + 1;
+ // TODO(keuchel): introduce let
+ // let y = one + 2;
+ if (one == 1) {
+ // Parameter
+ assertEquals(1, eval('one'));
+ // Function local var variable
+ assertEquals(2, eval('x'));
+ // Function local let variable
+ // TODO(keuchel): introduce let
+ // assertEquals(3, eval('y'));
+ }
+}
+f2(1);
+
--- /dev/null
+// Copyright 2011 the V8 project authors. All rights reserved.
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following
+// disclaimer in the documentation and/or other materials provided
+// with the distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived
+// from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Flags: --expose-debug-as debug --harmony-block-scoping
+// The functions used for testing backtraces. They are at the top to make the
+// testing of source line/column easier.
+
+
+// Get the Debug object exposed from the debug context global object.
+Debug = debug.Debug;
+
+var test_name;
+var listener_delegate;
+var listener_called;
+var exception;
+var begin_test_count = 0;
+var end_test_count = 0;
+var break_count = 0;
+
+
+// Debug event listener which delegates.
+function listener(event, exec_state, event_data, data) {
+ try {
+ if (event == Debug.DebugEvent.Break) {
+ break_count++;
+ listener_called = true;
+ listener_delegate(exec_state);
+ }
+ } catch (e) {
+ exception = e;
+ }
+}
+
+// Add the debug event listener.
+Debug.setListener(listener);
+
+
+// Initialize for a new test.
+function BeginTest(name) {
+ test_name = name;
+ listener_delegate = null;
+ listener_called = false;
+ exception = null;
+ begin_test_count++;
+}
+
+
+// Check result of a test.
+function EndTest() {
+ assertTrue(listener_called, "listerner not called for " + test_name);
+ assertNull(exception, test_name);
+ end_test_count++;
+}
+
+
+// Check that the scope chain contains the expected types of scopes.
+function CheckScopeChain(scopes, exec_state) {
+ assertEquals(scopes.length, exec_state.frame().scopeCount());
+ for (var i = 0; i < scopes.length; i++) {
+ var scope = exec_state.frame().scope(i);
+ assertTrue(scope.isScope());
+ assertEquals(scopes[i], scope.scopeType());
+
+ // Check the global object when hitting the global scope.
+ if (scopes[i] == debug.ScopeType.Global) {
+ // Objects don't have same class (one is "global", other is "Object",
+ // so just check the properties directly.
+ assertPropertiesEqual(this, scope.scopeObject().value());
+ }
+ }
+
+ // Get the debug command processor.
+ var dcp = exec_state.debugCommandProcessor("unspecified_running_state");
+
+ // Send a scopes request and check the result.
+ var json;
+ var request_json = '{"seq":0,"type":"request","command":"scopes"}';
+ var response_json = dcp.processDebugJSONRequest(request_json);
+ var response = JSON.parse(response_json);
+ assertEquals(scopes.length, response.body.scopes.length);
+ for (var i = 0; i < scopes.length; i++) {
+ assertEquals(i, response.body.scopes[i].index);
+ assertEquals(scopes[i], response.body.scopes[i].type);
+ if (scopes[i] == debug.ScopeType.Local ||
+ scopes[i] == debug.ScopeType.Closure) {
+ assertTrue(response.body.scopes[i].object.ref < 0);
+ } else {
+ assertTrue(response.body.scopes[i].object.ref >= 0);
+ }
+ var found = false;
+ for (var j = 0; j < response.refs.length && !found; j++) {
+ found = response.refs[j].handle == response.body.scopes[i].object.ref;
+ }
+ assertTrue(found, "Scope object " + response.body.scopes[i].object.ref + " not found");
+ }
+}
+
+// Check that the content of the scope is as expected. For functions just check
+// that there is a function.
+function CheckScopeContent(content, number, exec_state) {
+ var scope = exec_state.frame().scope(number);
+ var count = 0;
+ for (var p in content) {
+ var property_mirror = scope.scopeObject().property(p);
+ if (property_mirror.isUndefined()) {
+ print('property ' + p + ' not found in scope');
+ }
+ assertFalse(property_mirror.isUndefined(), 'property ' + p + ' not found in scope');
+ if (typeof(content[p]) === 'function') {
+ assertTrue(property_mirror.value().isFunction());
+ } else {
+ assertEquals(content[p], property_mirror.value().value(), 'property ' + p + ' has unexpected value');
+ }
+ count++;
+ }
+
+ // 'arguments' and might be exposed in the local and closure scope. Just
+ // ignore this.
+ var scope_size = scope.scopeObject().properties().length;
+ if (!scope.scopeObject().property('arguments').isUndefined()) {
+ scope_size--;
+ }
+ // Also ignore synthetic variable from catch block.
+ if (!scope.scopeObject().property('.catch-var').isUndefined()) {
+ scope_size--;
+ }
+ // Skip property with empty name.
+ if (!scope.scopeObject().property('').isUndefined()) {
+ scope_size--;
+ }
+ // Also ignore synthetic variable from block scopes.
+ if (!scope.scopeObject().property('.block').isUndefined()) {
+ scope_size--;
+ }
+
+ // TODO(keuchel): print('count' + count + ' scopesize' + scope_size);
+ if (count != scope_size) {
+ print('Names found in scope:');
+ var names = scope.scopeObject().propertyNames();
+ for (var i = 0; i < names.length; i++) {
+ print(names[i]);
+ }
+ }
+ assertEquals(count, scope_size);
+
+ // Get the debug command processor.
+ var dcp = exec_state.debugCommandProcessor("unspecified_running_state");
+
+ // Send a scope request for information on a single scope and check the
+ // result.
+ var request_json = '{"seq":0,"type":"request","command":"scope","arguments":{"number":';
+ request_json += scope.scopeIndex();
+ request_json += '}}';
+ var response_json = dcp.processDebugJSONRequest(request_json);
+ var response = JSON.parse(response_json);
+ assertEquals(scope.scopeType(), response.body.type);
+ assertEquals(number, response.body.index);
+ if (scope.scopeType() == debug.ScopeType.Local ||
+ scope.scopeType() == debug.ScopeType.Closure) {
+ assertTrue(response.body.object.ref < 0);
+ } else {
+ assertTrue(response.body.object.ref >= 0);
+ }
+ var found = false;
+ for (var i = 0; i < response.refs.length && !found; i++) {
+ found = response.refs[i].handle == response.body.object.ref;
+ }
+ assertTrue(found, "Scope object " + response.body.object.ref + " not found");
+}
+
+
+// Simple empty block scope in local scope.
+BeginTest("Local block 1");
+
+function local_block_1() {
+ {
+ debugger;
+ }
+}
+
+listener_delegate = function(exec_state) {
+ CheckScopeChain([debug.ScopeType.Block,
+ debug.ScopeType.Local,
+ debug.ScopeType.Global], exec_state);
+ CheckScopeContent({}, 0, exec_state);
+ CheckScopeContent({}, 1, exec_state);
+};
+local_block_1();
+EndTest();
+
+
+// Local scope with a parameter.
+BeginTest("Local 2");
+
+function local_2(a) {
+ {
+ debugger;
+ }
+}
+
+listener_delegate = function(exec_state) {
+ CheckScopeChain([debug.ScopeType.Block,
+ debug.ScopeType.Local,
+ debug.ScopeType.Global], exec_state);
+ CheckScopeContent({a:1}, 1, exec_state);
+};
+local_2(1);
+EndTest();
+
+
+// Local scope with a parameter and a local variable.
+BeginTest("Local 3");
+
+function local_3(a) {
+ var x = 3;
+ debugger;
+}
+
+listener_delegate = function(exec_state) {
+ CheckScopeChain([debug.ScopeType.Local,
+ debug.ScopeType.Global], exec_state);
+ CheckScopeContent({a:1,x:3}, 0, exec_state);
+};
+local_3(1);
+EndTest();
+
+
+// Local scope with parameters and local variables.
+BeginTest("Local 4");
+
+function local_4(a, b) {
+ var x = 3;
+ var y = 4;
+ debugger;
+}
+
+listener_delegate = function(exec_state) {
+ CheckScopeChain([debug.ScopeType.Local,
+ debug.ScopeType.Global], exec_state);
+ CheckScopeContent({a:1,b:2,x:3,y:4}, 0, exec_state);
+};
+local_4(1, 2);
+EndTest();
+
+
+// Single empty with block.
+BeginTest("With block 1");
+
+function with_block_1() {
+ with({}) {
+ debugger;
+ }
+}
+
+listener_delegate = function(exec_state) {
+ CheckScopeChain([debug.ScopeType.Block,
+ debug.ScopeType.With,
+ debug.ScopeType.Local,
+ debug.ScopeType.Global], exec_state);
+ CheckScopeContent({}, 0, exec_state);
+ CheckScopeContent({}, 1, exec_state);
+};
+with_block_1();
+EndTest();
+
+
+// Nested empty with blocks.
+BeginTest("With block 2");
+
+function with_block_2() {
+ with({}) {
+ with({}) {
+ debugger;
+ }
+ }
+}
+
+listener_delegate = function(exec_state) {
+ CheckScopeChain([debug.ScopeType.Block,
+ debug.ScopeType.With,
+ debug.ScopeType.Block,
+ debug.ScopeType.With,
+ debug.ScopeType.Local,
+ debug.ScopeType.Global], exec_state);
+ CheckScopeContent({}, 0, exec_state);
+ CheckScopeContent({}, 1, exec_state);
+ CheckScopeContent({}, 2, exec_state);
+ CheckScopeContent({}, 3, exec_state);
+};
+with_block_2();
+EndTest();
+
+
+// With block using an in-place object literal.
+BeginTest("With block 3");
+
+function with_block_3() {
+ with({a:1,b:2}) {
+ debugger;
+ }
+}
+
+listener_delegate = function(exec_state) {
+ CheckScopeChain([debug.ScopeType.Block,
+ debug.ScopeType.With,
+ debug.ScopeType.Local,
+ debug.ScopeType.Global], exec_state);
+ CheckScopeContent({}, 0, exec_state);
+ CheckScopeContent({a:1,b:2}, 1, exec_state);
+};
+with_block_3();
+EndTest();
+
+
+// Nested with blocks using in-place object literals.
+BeginTest("With block 4");
+
+function with_block_4() {
+ with({a:1,b:2}) {
+ with({a:2,b:1}) {
+ debugger;
+ }
+ }
+}
+
+listener_delegate = function(exec_state) {
+ CheckScopeChain([debug.ScopeType.Block,
+ debug.ScopeType.With,
+ debug.ScopeType.Block,
+ debug.ScopeType.With,
+ debug.ScopeType.Local,
+ debug.ScopeType.Global], exec_state);
+ CheckScopeContent({a:2,b:1}, 1, exec_state);
+ CheckScopeContent({a:1,b:2}, 3, exec_state);
+};
+with_block_4();
+EndTest();
+
+
+// TODO(keuchel):
+// Simple closure formed by returning an inner function referering to an outer
+// block local variable and an outer function's parameter.
+// BeginTest("Closure 1");
+//
+// function closure_1(a) {
+// {
+// let x = 3;
+// function f() {
+// debugger;
+// return a + x;
+// };
+// }
+// return f;
+// }
+//
+// listener_delegate = function(exec_state) {
+// CheckScopeChain([debug.ScopeType.Local,
+// debug.ScopeType.Closure,
+// debug.ScopeType.Global], exec_state);
+// // CheckScopeContent({}, 0, exec_state);
+// // CheckScopeContent({}, 1, exec_state);
+// // CheckScopeContent({a:1}, 2, exec_state);
+// };
+// closure_1(1)();
+// EndTest();
--- /dev/null
+// Copyright 2011 the V8 project authors. All rights reserved.
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following
+// disclaimer in the documentation and/or other materials provided
+// with the distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived
+// from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Flags: --expose-debug-as debug
+
+// Test debug evaluation for functions without local context, but with
+// nested catch contexts.
+
+function f() {
+ { // Line 1.
+ var i = 1; // Line 2. // TODO(keuchel): introduce let
+ try { // Line 3.
+ throw 'stuff'; // Line 4.
+ } catch (e) { // Line 5.
+ x = 2; // Line 6.
+ }
+ }
+};
+
+// Get the Debug object exposed from the debug context global object.
+Debug = debug.Debug
+// Set breakpoint on line 6.
+var bp = Debug.setBreakPoint(f, 6);
+
+function listener(event, exec_state, event_data, data) {
+ if (event == Debug.DebugEvent.Break) {
+ result = exec_state.frame().evaluate("i").value();
+ }
+};
+
+// Add the debug event listener.
+Debug.setListener(listener);
+result = -1;
+f();
+assertEquals(1, result);
+
+// Clear breakpoint.
+Debug.clearBreakPoint(bp);
+// Get rid of the debug event listener.
+Debug.setListener(null);