inlined_frame_index_(inlined_frame_index),
function_(JSFunction::cast(frame->function())),
context_(Context::cast(frame->context())),
- local_done_(false),
- at_local_(false) {
-
- // Check whether the first scope is actually a local scope.
- // If there is a stack slot for .result then this local scope has been
- // created for evaluating top level code and it is not a real local scope.
- // Checking for the existence of .result seems fragile, but the scope info
- // saved with the code object does not otherwise have that information.
- int index = function_->shared()->scope_info()->
- StackSlotIndex(isolate_->heap()->result_symbol());
- if (index >= 0) {
- local_done_ = true;
- } else if (context_->IsGlobalContext() ||
- context_->IsFunctionContext()) {
- at_local_ = true;
- } else if (context_->closure() != *function_) {
- // The context_ is a block or with or catch block from the outer function.
- ASSERT(context_->IsWithContext() ||
- context_->IsCatchContext() ||
- context_->IsBlockContext());
- at_local_ = true;
+ nested_scope_chain_(4) {
+
+ // Catch the case when the debugger stops in an internal function.
+ Handle<SharedFunctionInfo> shared_info(function_->shared());
+ Handle<ScopeInfo> scope_info(shared_info->scope_info());
+ if (shared_info->script() == isolate->heap()->undefined_value()) {
+ while (context_->closure() == *function_) {
+ context_ = Handle<Context>(context_->previous(), isolate_);
+ }
+ return;
+ }
+
+ // Get the debug info (create it if it does not exist).
+ if (!isolate->debug()->EnsureDebugInfo(shared_info)) {
+ // Return if ensuring debug info failed.
+ return;
+ }
+ Handle<DebugInfo> debug_info = Debug::GetDebugInfo(shared_info);
+
+ // Find the break point where execution has stopped.
+ BreakLocationIterator break_location_iterator(debug_info,
+ ALL_BREAK_LOCATIONS);
+ break_location_iterator.FindBreakLocationFromAddress(frame->pc());
+ if (break_location_iterator.IsExit()) {
+ // We are within the return sequence. At the momemt it is not possible to
+ // get a source position which is consistent with the current scope chain.
+ // Thus all nested with, catch and block contexts are skipped and we only
+ // provide the function scope.
+ if (scope_info->HasContext()) {
+ context_ = Handle<Context>(context_->declaration_context(), isolate_);
+ } else {
+ while (context_->closure() == *function_) {
+ context_ = Handle<Context>(context_->previous(), isolate_);
+ }
+ }
+ if (scope_info->Type() != EVAL_SCOPE) nested_scope_chain_.Add(scope_info);
+ } else {
+ // Reparse the code and analyze the scopes.
+ ZoneScope zone_scope(isolate, DELETE_ON_EXIT);
+ Handle<Script> script(Script::cast(shared_info->script()));
+ Scope* scope = NULL;
+
+ // Check whether we are in global, eval or function code.
+ Handle<ScopeInfo> scope_info(shared_info->scope_info());
+ if (scope_info->Type() != FUNCTION_SCOPE) {
+ // Global or eval code.
+ CompilationInfo info(script);
+ if (scope_info->Type() == GLOBAL_SCOPE) {
+ info.MarkAsGlobal();
+ } else {
+ ASSERT(scope_info->Type() == EVAL_SCOPE);
+ info.MarkAsEval();
+ info.SetCallingContext(Handle<Context>(function_->context()));
+ }
+ if (ParserApi::Parse(&info, kNoParsingFlags) && Scope::Analyze(&info)) {
+ scope = info.function()->scope();
+ }
+ } else {
+ // Function code
+ CompilationInfo info(shared_info);
+ if (ParserApi::Parse(&info, kNoParsingFlags) && Scope::Analyze(&info)) {
+ scope = info.function()->scope();
+ }
+ }
+
+ // Retrieve the scope chain for the current position.
+ if (scope != NULL) {
+ int source_position = shared_info->code()->SourcePosition(frame_->pc());
+ scope->GetNestedScopeChain(&nested_scope_chain_, source_position);
+ } else {
+ // A failed reparse indicates that the preparser has diverged from the
+ // parser or that the preparse data given to the initial parse has been
+ // faulty. We fail in debug mode but in release mode we only provide the
+ // information we get from the context chain but nothing about
+ // completely stack allocated scopes or stack allocated locals.
+ UNREACHABLE();
+ }
}
}
// Move to the next scope.
void Next() {
- // If at a local scope mark the local scope as passed.
- if (at_local_) {
- at_local_ = false;
- local_done_ = true;
-
- // If the current context is not associated with the local scope the
- // current context is the next real scope, so don't move to the next
- // context in this case.
- if (context_->closure() != *function_) {
- return;
- }
- }
-
- // The global scope is always the last in the chain.
- if (context_->IsGlobalContext()) {
+ ScopeType scope_type = Type();
+ if (scope_type == ScopeTypeGlobal) {
+ // The global scope is always the last in the chain.
+ ASSERT(context_->IsGlobalContext());
context_ = Handle<Context>();
return;
}
-
- // Move to the next context.
- context_ = Handle<Context>(context_->previous(), isolate_);
-
- // If passing the local scope indicate that the current scope is now the
- // local scope.
- if (!local_done_ &&
- (context_->IsGlobalContext() || context_->IsFunctionContext())) {
- at_local_ = true;
+ if (nested_scope_chain_.is_empty()) {
+ context_ = Handle<Context>(context_->previous(), isolate_);
+ } else {
+ if (nested_scope_chain_.last()->HasContext()) {
+ ASSERT(context_->previous() != NULL);
+ context_ = Handle<Context>(context_->previous(), isolate_);
+ }
+ nested_scope_chain_.RemoveLast();
}
}
// Return the type of the current scope.
ScopeType Type() {
- if (at_local_) {
- return ScopeTypeLocal;
+ if (!nested_scope_chain_.is_empty()) {
+ Handle<ScopeInfo> scope_info = nested_scope_chain_.last();
+ switch (scope_info->Type()) {
+ case FUNCTION_SCOPE:
+ ASSERT(context_->IsFunctionContext() ||
+ !scope_info->HasContext());
+ return ScopeTypeLocal;
+ case GLOBAL_SCOPE:
+ ASSERT(context_->IsGlobalContext());
+ return ScopeTypeGlobal;
+ case WITH_SCOPE:
+ ASSERT(context_->IsWithContext());
+ return ScopeTypeWith;
+ case CATCH_SCOPE:
+ ASSERT(context_->IsCatchContext());
+ return ScopeTypeCatch;
+ case BLOCK_SCOPE:
+ ASSERT(!scope_info->HasContext() ||
+ context_->IsBlockContext());
+ return ScopeTypeBlock;
+ case EVAL_SCOPE:
+ UNREACHABLE();
+ }
}
if (context_->IsGlobalContext()) {
ASSERT(context_->global()->IsGlobalObject());
return Handle<JSObject>(CurrentContext()->global());
case ScopeIterator::ScopeTypeLocal:
// Materialize the content of the local scope into a JSObject.
+ ASSERT(nested_scope_chain_.length() == 1);
return MaterializeLocalScope(isolate_, frame_, inlined_frame_index_);
case ScopeIterator::ScopeTypeWith:
// Return the with object.
return Handle<JSObject>();
}
+ Handle<ScopeInfo> CurrentScopeInfo() {
+ if (!nested_scope_chain_.is_empty()) {
+ return nested_scope_chain_.last();
+ } else if (context_->IsBlockContext()) {
+ return Handle<ScopeInfo>(ScopeInfo::cast(context_->extension()));
+ } else if (context_->IsFunctionContext()) {
+ return Handle<ScopeInfo>(context_->closure()->shared()->scope_info());
+ }
+ return Handle<ScopeInfo>::null();
+ }
+
// Return the context for this scope. For the local context there might not
// be an actual context.
Handle<Context> CurrentContext() {
- if (at_local_ && context_->closure() != *function_) {
+ if (Type() == ScopeTypeGlobal ||
+ nested_scope_chain_.is_empty()) {
+ return context_;
+ } else if (nested_scope_chain_.last()->HasContext()) {
+ return context_;
+ } else {
return Handle<Context>();
}
- return context_;
}
#ifdef DEBUG
int inlined_frame_index_;
Handle<JSFunction> function_;
Handle<Context> context_;
- bool local_done_;
- bool at_local_;
+ List<Handle<ScopeInfo> > nested_scope_chain_;
DISALLOW_IMPLICIT_CONSTRUCTORS(ScopeIterator);
};
// Creates a copy of the with context chain. The copy of the context chain is
// is linked to the function context supplied.
-static Handle<Context> CopyWithContextChain(Isolate* isolate,
- Handle<JSFunction> function,
- Handle<Context> current,
- Handle<Context> base) {
- // At the end of the chain. Return the base context to link to.
- if (current->IsFunctionContext() || current->IsGlobalContext()) {
- return base;
+static Handle<Context> CopyNestedScopeContextChain(Isolate* isolate,
+ Handle<JSFunction> function,
+ Handle<Context> base,
+ JavaScriptFrame* frame,
+ int inlined_frame_index) {
+ HandleScope scope(isolate);
+ List<Handle<ScopeInfo> > scope_chain;
+ List<Handle<Context> > context_chain;
+
+ ScopeIterator it(isolate, frame, inlined_frame_index);
+ for (; it.Type() != ScopeIterator::ScopeTypeGlobal &&
+ it.Type() != ScopeIterator::ScopeTypeLocal ; it.Next()) {
+ ASSERT(!it.Done());
+ scope_chain.Add(it.CurrentScopeInfo());
+ context_chain.Add(it.CurrentContext());
}
- // Recursively copy the with and catch contexts.
- HandleScope scope(isolate);
- Handle<Context> previous(current->previous());
- Handle<Context> new_previous =
- CopyWithContextChain(isolate, function, previous, base);
- Handle<Context> new_current;
- if (current->IsCatchContext()) {
- Handle<String> name(String::cast(current->extension()));
- Handle<Object> thrown_object(current->get(Context::THROWN_OBJECT_INDEX));
- new_current =
- isolate->factory()->NewCatchContext(function,
- new_previous,
- name,
- thrown_object);
- } else if (current->IsBlockContext()) {
- Handle<ScopeInfo> scope_info(ScopeInfo::cast(current->extension()));
- new_current =
- isolate->factory()->NewBlockContext(function, new_previous, scope_info);
- // Copy context slots.
- int num_context_slots = scope_info->ContextLength();
- for (int i = Context::MIN_CONTEXT_SLOTS; i < num_context_slots; ++i) {
- new_current->set(i, current->get(i));
+ // At the end of the chain. Return the base context to link to.
+ Handle<Context> context = base;
+
+ // Iteratively copy and or materialize the nested contexts.
+ while (!scope_chain.is_empty()) {
+ Handle<ScopeInfo> scope_info = scope_chain.RemoveLast();
+ Handle<Context> current = context_chain.RemoveLast();
+ ASSERT(!(scope_info->HasContext() & current.is_null()));
+
+ if (scope_info->Type() == CATCH_SCOPE) {
+ Handle<String> name(String::cast(current->extension()));
+ Handle<Object> thrown_object(current->get(Context::THROWN_OBJECT_INDEX));
+ context =
+ isolate->factory()->NewCatchContext(function,
+ context,
+ name,
+ thrown_object);
+ } else if (scope_info->Type() == BLOCK_SCOPE) {
+ // Materialize the contents of the block scope into a JSObject.
+ Handle<JSObject> block_scope_object =
+ MaterializeBlockScope(isolate, current);
+ if (block_scope_object.is_null()) {
+ return Handle<Context>::null();
+ }
+ // Allocate a new function context for the debug evaluation and set the
+ // extension object.
+ Handle<Context> new_context =
+ isolate->factory()->NewFunctionContext(Context::MIN_CONTEXT_SLOTS,
+ function);
+ new_context->set_extension(*block_scope_object);
+ new_context->set_previous(*context);
+ context = new_context;
+ } else {
+ ASSERT(scope_info->Type() == WITH_SCOPE);
+ ASSERT(current->IsWithContext());
+ Handle<JSObject> extension(JSObject::cast(current->extension()));
+ context =
+ isolate->factory()->NewWithContext(function, context, extension);
}
- } else {
- ASSERT(current->IsWithContext());
- Handle<JSObject> extension(JSObject::cast(current->extension()));
- new_current =
- isolate->factory()->NewWithContext(function, new_previous, extension);
}
- return scope.CloseAndEscape(new_current);
+
+ return scope.CloseAndEscape(context);
}
if (scope_info->HasContext()) {
function_context = Handle<Context>(frame_context->declaration_context());
}
- context = CopyWithContextChain(isolate, go_between, frame_context, context);
+ context = CopyNestedScopeContextChain(isolate,
+ go_between,
+ context,
+ frame,
+ inlined_frame_index);
if (additional_context->IsJSObject()) {
Handle<JSObject> extension = Handle<JSObject>::cast(additional_context);
--- /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 --expose-natives-as=builtins
+
+// Check that the ScopeIterator can properly recreate the scope at
+// every point when stepping through functions.
+
+var Debug = debug.Debug;
+
+function listener(event, exec_state, event_data, data) {
+ if (event == Debug.DebugEvent.Break) {
+ // Access scope details.
+ var scope_count = exec_state.frame().scopeCount();
+ for (var i = 0; i < scope_count; i++) {
+ var scope = exec_state.frame().scope(i);
+ // assertTrue(scope.isScope());
+ scope.scopeType();
+ scope.scopeObject();
+ }
+
+ // Do steps until we reach the global scope again.
+ if (true) {
+ exec_state.prepareStep(Debug.StepAction.StepInMin, 1);
+ }
+ }
+}
+
+Debug.setListener(listener);
+
+
+function test1() {
+ debugger;
+ with ({x:1}) {
+ x = 2;
+ }
+}
+test1();
+
+
+function test2() {
+ if (true) {
+ with ({}) {
+ debugger;
+ }
+ } else {
+ with ({}) {
+ return 10;
+ }
+ }
+}
+test2();
+
+
+function test3() {
+ if (true) {
+ debugger;
+ } else {
+ with ({}) {
+ return 10;
+ }
+ }
+}
+test3();
+
+
+function test4() {
+ debugger;
+ with ({x:1}) x = 1
+}
+test4();
+
+
+function test5() {
+ debugger;
+ var dummy = 1;
+ with ({}) {
+ with ({}) {
+ dummy = 2;
+ }
+ }
+ dummy = 3;
+}
+test5();
+
+
+function test6() {
+ debugger;
+ try {
+ throw 'stuff';
+ } catch (e) {
+ e = 1;
+ }
+}
+test6();
+
+
+function test7() {
+ debugger;
+ function foo() {}
+}
+test7();
+
+
+function test8() {
+ debugger;
+ (function foo() {})();
+}
+test8();
+
+
+var q = 42;
+var prefixes = [ "debugger; ",
+ "if (false) { try { throw 0; } catch(x) { return x; } }; debugger; " ];
+var bodies = [ "1",
+ "1 ",
+ "1;",
+ "1; ",
+ "q",
+ "q ",
+ "q;",
+ "q; ",
+ "try { throw 'stuff' } catch (e) { e = 1; }",
+ "try { throw 'stuff' } catch (e) { e = 1; } ",
+ "try { throw 'stuff' } catch (e) { e = 1; };",
+ "try { throw 'stuff' } catch (e) { e = 1; }; " ];
+var with_bodies = [ "with ({}) {}",
+ "with ({x:1}) x",
+ "with ({x:1}) x = 1",
+ "with ({x:1}) x ",
+ "with ({x:1}) x = 1 ",
+ "with ({x:1}) x;",
+ "with ({x:1}) x = 1;",
+ "with ({x:1}) x; ",
+ "with ({x:1}) x = 1; " ];
+
+
+function test9() {
+ debugger;
+ for (var i = 0; i < prefixes.length; ++i) {
+ var pre = prefixes[i];
+ for (var j = 0; j < bodies.length; ++j) {
+ var body = bodies[j];
+ eval(pre + body);
+ eval("'use strict'; " + pre + body);
+ }
+ for (var j = 0; j < with_bodies.length; ++j) {
+ var body = with_bodies[j];
+ eval(pre + body);
+ }
+ }
+}
+test9();
+
+
+function test10() {
+ debugger;
+ with ({}) {
+ return 10;
+ }
+}
+test10();
+
+
+function test11() {
+ debugger;
+ try {
+ throw 'stuff';
+ } catch (e) {
+ return 10;
+ }
+}
+test11();
+
+
+// Test global eval and function constructor.
+for (var i = 0; i < prefixes.length; ++i) {
+ var pre = prefixes[i];
+ for (var j = 0; j < bodies.length; ++j) {
+ var body = bodies[j];
+ eval(pre + body);
+ eval("'use strict'; " + pre + body);
+ Function(pre + body)();
+ }
+ for (var j = 0; j < with_bodies.length; ++j) {
+ var body = with_bodies[j];
+ eval(pre + body);
+ Function(pre + body)();
+ }
+}
+
+
+try {
+ with({}) {
+ debugger;
+ eval("{}$%:^");
+ }
+} catch(e) {
+ nop();
+}
+
+// Return from function constructed with Function constructor.
+var anon = 12;
+for (var i = 0; i < prefixes.length; ++i) {
+ var pre = prefixes[i];
+ Function(pre + "return 42")();
+ Function(pre + "return 42 ")();
+ Function(pre + "return 42;")();
+ Function(pre + "return 42; ")();
+ Function(pre + "return anon")();
+ Function(pre + "return anon ")();
+ Function(pre + "return anon;")();
+ Function(pre + "return anon; ")();
+}
+
+
+function nop() {}
+
+
+function stress() {
+ debugger;
+
+ L: with ({x:12}) {
+ break L;
+ }
+
+
+ with ({x: 'outer'}) {
+ label: {
+ with ({x: 'inner'}) {
+ break label;
+ }
+ }
+ }
+
+
+ with ({x: 'outer'}) {
+ label: {
+ with ({x: 'inner'}) {
+ break label;
+ }
+ }
+ nop();
+ }
+
+
+ with ({x: 'outer'}) {
+ label: {
+ with ({x: 'middle'}) {
+ with ({x: 'inner'}) {
+ break label;
+ }
+ }
+ }
+ }
+
+
+ with ({x: 'outer'}) {
+ label: {
+ with ({x: 'middle'}) {
+ with ({x: 'inner'}) {
+ break label;
+ }
+ }
+ }
+ nop();
+ }
+
+
+ with ({x: 'outer'}) {
+ for (var i = 0; i < 3; ++i) {
+ with ({x: 'inner' + i}) {
+ continue;
+ }
+ }
+ }
+
+
+ with ({x: 'outer'}) {
+ label: for (var i = 0; i < 3; ++i) {
+ with ({x: 'middle' + i}) {
+ for (var j = 0; j < 3; ++j) {
+ with ({x: 'inner' + j}) {
+ continue label;
+ }
+ }
+ }
+ }
+ }
+
+
+ with ({x: 'outer'}) {
+ try {
+ with ({x: 'inner'}) {
+ throw 0;
+ }
+ } catch (e) {
+ }
+ }
+
+
+ with ({x: 'outer'}) {
+ try {
+ with ({x: 'inner'}) {
+ throw 0;
+ }
+ } catch (e) {
+ nop();
+ }
+ }
+
+
+ with ({x: 'outer'}) {
+ try {
+ with ({x: 'middle'}) {
+ with ({x: 'inner'}) {
+ throw 0;
+ }
+ }
+ } catch (e) {
+ }
+ }
+
+
+ try {
+ with ({x: 'outer'}) {
+ try {
+ with ({x: 'inner'}) {
+ throw 0;
+ }
+ } finally {
+ }
+ }
+ } catch (e) {
+ }
+
+
+ try {
+ with ({x: 'outer'}) {
+ try {
+ with ({x: 'inner'}) {
+ throw 0;
+ }
+ } finally {
+ nop();
+ }
+ }
+ } catch (e) {
+ }
+
+
+ function stress1() {
+ with ({x:12}) {
+ return x;
+ }
+ }
+ stress1();
+
+
+ function stress2() {
+ with ({x: 'outer'}) {
+ with ({x: 'inner'}) {
+ return x;
+ }
+ }
+ }
+ stress2();
+
+ function stress3() {
+ try {
+ with ({x: 'inner'}) {
+ throw 0;
+ }
+ } catch (e) {
+ return e;
+ }
+ }
+ stress3();
+
+
+ function stress4() {
+ try {
+ with ({x: 'inner'}) {
+ throw 0;
+ }
+ } catch (e) {
+ with ({x: 'inner'}) {
+ return e;
+ }
+ }
+ }
+ stress4();
+
+}
+stress();
+
+
+// With block as the last(!) statement in global code.
+with ({}) { debugger; }
\ No newline at end of file