From 9d6445cf3200fda112ae80131ced04abfb261d44 Mon Sep 17 00:00:00 2001 From: "yangguo@chromium.org" Date: Wed, 17 Jul 2013 15:29:00 +0000 Subject: [PATCH] Do not materialize context-allocated values for debug-evaluate. BUG=259300 R=ulan@chromium.org, verwaest@chromium.org Review URL: https://codereview.chromium.org/19569003 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@15727 ce2b1a6d-e550-0410-aec6-3dcde31c8c00 --- src/mirror-debugger.js | 30 +-- src/runtime.cc | 369 ++++++++++----------------- src/scopes.cc | 8 + test/mjsunit/debug-evaluate-closure.js | 91 +++++++ test/mjsunit/regress/regress-crbug-259300.js | 49 ++++ 5 files changed, 289 insertions(+), 258 deletions(-) create mode 100644 test/mjsunit/debug-evaluate-closure.js create mode 100644 test/mjsunit/regress/regress-crbug-259300.js diff --git a/src/mirror-debugger.js b/src/mirror-debugger.js index 28b8fc8..28ef24d 100644 --- a/src/mirror-debugger.js +++ b/src/mirror-debugger.js @@ -1699,30 +1699,12 @@ FrameMirror.prototype.stepInPositions = function() { FrameMirror.prototype.evaluate = function(source, disable_break, opt_context_object) { - var result_array = %DebugEvaluate(this.break_id_, - this.details_.frameId(), - this.details_.inlinedFrameIndex(), - source, - Boolean(disable_break), - opt_context_object); - // Silently ignore local variables changes if the frame is optimized. - if (!this.isOptimizedFrame()) { - var local_scope_on_stack = result_array[1]; - var local_scope_modifed = result_array[2]; - for (var n in local_scope_modifed) { - var value_on_stack = local_scope_on_stack[n]; - var value_modifed = local_scope_modifed[n]; - if (value_on_stack !== value_modifed) { - %SetScopeVariableValue(this.break_id_, - this.details_.frameId(), - this.details_.inlinedFrameIndex(), - 0, - n, - value_modifed); - } - } - } - return MakeMirror(result_array[0]); + return MakeMirror(%DebugEvaluate(this.break_id_, + this.details_.frameId(), + this.details_.inlinedFrameIndex(), + source, + Boolean(disable_break), + opt_context_object)); }; diff --git a/src/runtime.cc b/src/runtime.cc index c259cb4..c894363 100644 --- a/src/runtime.cc +++ b/src/runtime.cc @@ -11180,19 +11180,14 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_GetFrameDetails) { // Create a plain JSObject which materializes the local scope for the specified // frame. -static Handle MaterializeLocalScopeWithFrameInspector( +static Handle MaterializeStackLocalsWithFrameInspector( Isolate* isolate, - JavaScriptFrame* frame, + Handle target, + Handle function, FrameInspector* frame_inspector) { - Handle function(JSFunction::cast(frame_inspector->GetFunction())); Handle shared(function->shared()); Handle scope_info(shared->scope_info()); - // Allocate and initialize a JSObject with all the arguments, stack locals - // heap locals and extension properties of the debugged function. - Handle local_scope = - isolate->factory()->NewJSObject(isolate->object_function()); - // First fill all parameters. for (int i = 0; i < scope_info->ParameterCount(); ++i) { Handle value(i < frame_inspector->GetParametersCount() @@ -11203,7 +11198,7 @@ static Handle MaterializeLocalScopeWithFrameInspector( RETURN_IF_EMPTY_HANDLE_VALUE( isolate, SetProperty(isolate, - local_scope, + target, Handle(scope_info->ParameterName(i)), value, NONE, @@ -11216,7 +11211,7 @@ static Handle MaterializeLocalScopeWithFrameInspector( RETURN_IF_EMPTY_HANDLE_VALUE( isolate, SetProperty(isolate, - local_scope, + target, Handle(scope_info->StackLocalName(i)), Handle(frame_inspector->GetExpression(i), isolate), NONE, @@ -11224,45 +11219,90 @@ static Handle MaterializeLocalScopeWithFrameInspector( Handle()); } - if (scope_info->HasContext()) { - // Third fill all context locals. - Handle frame_context(Context::cast(frame->context())); - Handle function_context(frame_context->declaration_context()); - if (!scope_info->CopyContextLocalsToScopeObject( - isolate, function_context, local_scope)) { - return Handle(); - } + return target; +} - // Finally copy any properties from the function context extension. - // These will be variables introduced by eval. - if (function_context->closure() == *function) { - if (function_context->has_extension() && - !function_context->IsNativeContext()) { - Handle ext(JSObject::cast(function_context->extension())); - bool threw = false; - Handle keys = - GetKeysInFixedArrayFor(ext, INCLUDE_PROTOS, &threw); - if (threw) return Handle(); - - for (int i = 0; i < keys->length(); i++) { - // Names of variables introduced by eval are strings. - ASSERT(keys->get(i)->IsString()); - Handle key(String::cast(keys->get(i))); - RETURN_IF_EMPTY_HANDLE_VALUE( - isolate, - SetProperty(isolate, - local_scope, - key, - GetProperty(isolate, ext, key), - NONE, - kNonStrictMode), - Handle()); - } + +static void UpdateStackLocalsFromMaterializedObject(Isolate* isolate, + Handle target, + Handle function, + JavaScriptFrame* frame, + int inlined_jsframe_index) { + if (inlined_jsframe_index != 0 || frame->is_optimized()) { + // Optimized frames are not supported. + // TODO(yangguo): make sure all code deoptimized when debugger is active + // and assert that this cannot happen. + return; + } + + Handle shared(function->shared()); + Handle scope_info(shared->scope_info()); + + // Parameters. + for (int i = 0; i < scope_info->ParameterCount(); ++i) { + HandleScope scope(isolate); + Handle value = GetProperty( + isolate, target, Handle(scope_info->ParameterName(i))); + frame->SetParameterValue(i, *value); + } + + // Stack locals. + for (int i = 0; i < scope_info->StackLocalCount(); ++i) { + HandleScope scope(isolate); + Handle value = GetProperty( + isolate, target, Handle(scope_info->StackLocalName(i))); + frame->SetExpression(i, *value); + } +} + + +static Handle MaterializeLocalContext(Isolate* isolate, + Handle target, + Handle function, + JavaScriptFrame* frame) { + HandleScope scope(isolate); + Handle shared(function->shared()); + Handle scope_info(shared->scope_info()); + + if (!scope_info->HasContext()) return target; + + // Third fill all context locals. + Handle frame_context(Context::cast(frame->context())); + Handle function_context(frame_context->declaration_context()); + if (!scope_info->CopyContextLocalsToScopeObject( + isolate, function_context, target)) { + return Handle(); + } + + // Finally copy any properties from the function context extension. + // These will be variables introduced by eval. + if (function_context->closure() == *function) { + if (function_context->has_extension() && + !function_context->IsNativeContext()) { + Handle ext(JSObject::cast(function_context->extension())); + bool threw = false; + Handle keys = + GetKeysInFixedArrayFor(ext, INCLUDE_PROTOS, &threw); + if (threw) return Handle(); + + for (int i = 0; i < keys->length(); i++) { + // Names of variables introduced by eval are strings. + ASSERT(keys->get(i)->IsString()); + Handle key(String::cast(keys->get(i))); + RETURN_IF_EMPTY_HANDLE_VALUE( + isolate, + SetProperty(isolate, + target, + key, + GetProperty(isolate, ext, key), + NONE, + kNonStrictMode), + Handle()); } } } - return local_scope; + return target; } @@ -11271,9 +11311,15 @@ static Handle MaterializeLocalScope( JavaScriptFrame* frame, int inlined_jsframe_index) { FrameInspector frame_inspector(frame, inlined_jsframe_index, isolate); - return MaterializeLocalScopeWithFrameInspector(isolate, - frame, - &frame_inspector); + Handle function(JSFunction::cast(frame_inspector.GetFunction())); + + Handle local_scope = + isolate->factory()->NewJSObject(isolate->object_function()); + local_scope = MaterializeStackLocalsWithFrameInspector( + isolate, local_scope, function, &frame_inspector); + RETURN_IF_EMPTY_HANDLE_VALUE(isolate, local_scope, Handle()); + + return MaterializeLocalContext(isolate, local_scope, function, frame); } @@ -12426,111 +12472,31 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_ClearStepping) { } -static bool IsBlockOrCatchOrWithScope(ScopeIterator::ScopeType type) { - return type == ScopeIterator::ScopeTypeBlock || - type == ScopeIterator::ScopeTypeCatch || - type == ScopeIterator::ScopeTypeWith; -} - - -// Creates a copy of the with context chain. The copy of the context chain is -// is linked to the function context supplied. -static Handle CopyNestedScopeContextChain(Isolate* isolate, - Handle function, - Handle base, - JavaScriptFrame* frame, - int inlined_jsframe_index) { - HandleScope scope(isolate); - List > scope_chain; - List > context_chain; - - ScopeIterator it(isolate, frame, inlined_jsframe_index); - if (it.Failed()) return Handle::null(); - - for ( ; IsBlockOrCatchOrWithScope(it.Type()); it.Next()) { - ASSERT(!it.Done()); - scope_chain.Add(it.CurrentScopeInfo()); - context_chain.Add(it.CurrentContext()); - } - - // At the end of the chain. Return the base context to link to. - Handle context = base; - - // Iteratively copy and or materialize the nested contexts. - while (!scope_chain.is_empty()) { - Handle scope_info = scope_chain.RemoveLast(); - Handle current = context_chain.RemoveLast(); - ASSERT(!(scope_info->HasContext() & current.is_null())); - - if (scope_info->scope_type() == CATCH_SCOPE) { - ASSERT(current->IsCatchContext()); - Handle name(String::cast(current->extension())); - Handle thrown_object(current->get(Context::THROWN_OBJECT_INDEX), - isolate); - context = - isolate->factory()->NewCatchContext(function, - context, - name, - thrown_object); - } else if (scope_info->scope_type() == BLOCK_SCOPE) { - // Materialize the contents of the block scope into a JSObject. - ASSERT(current->IsBlockContext()); - Handle block_scope_object = - MaterializeBlockScope(isolate, current); - CHECK(!block_scope_object.is_null()); - // Allocate a new function context for the debug evaluation and set the - // extension object. - Handle 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->scope_type() == WITH_SCOPE); - ASSERT(current->IsWithContext()); - Handle extension(JSObject::cast(current->extension())); - context = - isolate->factory()->NewWithContext(function, context, extension); - } - } - - return scope.CloseAndEscape(context); -} - - // Helper function to find or create the arguments object for // Runtime_DebugEvaluate. -static Handle GetArgumentsObject(Isolate* isolate, - JavaScriptFrame* frame, - FrameInspector* frame_inspector, - Handle scope_info, - Handle function_context) { - // Try to find the value of 'arguments' to pass as parameter. If it is not - // found (that is the debugged function does not reference 'arguments' and - // does not support eval) then create an 'arguments' object. - int index; - if (scope_info->StackLocalCount() > 0) { - index = scope_info->StackSlotIndex(isolate->heap()->arguments_string()); - if (index != -1) { - return Handle(frame->GetExpression(index), isolate); - } - } - - if (scope_info->HasHeapAllocatedLocals()) { - VariableMode mode; - InitializationFlag init_flag; - index = scope_info->ContextSlotIndex( - isolate->heap()->arguments_string(), &mode, &init_flag); - if (index != -1) { - return Handle(function_context->get(index), isolate); - } +static Handle MaterializeArgumentsObject( + Isolate* isolate, + Handle target, + Handle function, + FrameInspector* frame_inspector) { + // Do not materialize the arguments object for eval or top-level code. + // Skip if "arguments" is already taken. + if (!function->shared()->is_function() || + target->HasLocalProperty(isolate->heap()->arguments_string())) { + return target; } // FunctionGetArguments can't return a non-Object. - return Handle(JSObject::cast( + Handle arguments(JSObject::cast( Accessors::FunctionGetArguments(frame_inspector->GetFunction(), NULL)->ToObjectUnchecked()), isolate); + SetProperty(isolate, + target, + isolate->factory()->arguments_string(), + arguments, + ::NONE, + kNonStrictMode); + return target; } @@ -12577,24 +12543,10 @@ static MaybeObject* DebugEvaluate(Isolate* isolate, // Evaluate a piece of JavaScript in the context of a stack frame for -// debugging. This is done by creating a new context which in its extension -// part has all the parameters and locals of the function on the stack frame -// as well as a materialized arguments object. As this context replaces -// the context of the function on the stack frame a new (empty) function -// is created as well to be used as the closure for the context. -// This closure as replacements for the one on the stack frame presenting -// the same view of the values of parameters and local variables as if the -// piece of JavaScript was evaluated at the point where the function on the -// stack frame is currently stopped when we compile and run the (direct) eval. -// Returns array of -// #0: evaluate result -// #1: local variables materizalized again as object after evaluation, contain -// original variable values as they remained on stack -// #2: local variables materizalized as object before evaluation (and possibly -// modified by expression having been executed) -// Since user expression only reaches (and modifies) copies of local variables, -// those copies are returned to the caller to allow tracking the changes and -// manually updating the actual variables. +// debugging. Things that need special attention are: +// - Parameters and stack-allocated locals need to be materialized. Altered +// values need to be written back to the stack afterwards. +// - The arguments object needs to materialized. RUNTIME_FUNCTION(MaybeObject*, Runtime_DebugEvaluate) { HandleScope scope(isolate); @@ -12629,69 +12581,24 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_DebugEvaluate) { SaveContext savex(isolate); isolate->set_context(*(save->context())); - // Create the (empty) function replacing the function on the stack frame for - // the purpose of evaluating in the context created below. It is important - // that this function does not describe any parameters and local variables - // in the context. If it does then this will cause problems with the lookup - // in Context::Lookup, where context slots for parameters and local variables - // are looked at before the extension object. - Handle go_between = - isolate->factory()->NewFunction(isolate->factory()->empty_string(), - isolate->factory()->undefined_value()); - go_between->set_context(function->context()); -#ifdef DEBUG - Handle go_between_scope_info(go_between->shared()->scope_info()); - ASSERT(go_between_scope_info->ParameterCount() == 0); - ASSERT(go_between_scope_info->ContextLocalCount() == 0); -#endif + // Evaluate on the context of the frame. + Handle context(Context::cast(frame->context())); + ASSERT(!context.is_null()); - // Materialize the content of the local scope including the arguments object. - Handle local_scope = MaterializeLocalScopeWithFrameInspector( - isolate, frame, &frame_inspector); - RETURN_IF_EMPTY_HANDLE(isolate, local_scope); + // Materialize stack locals and the arguments object. + Handle materialized = + isolate->factory()->NewJSObject(isolate->object_function()); - // Do not materialize the arguments object for eval or top-level code. - if (function->shared()->is_function()) { - Handle frame_context(Context::cast(frame->context())); - Handle function_context; - Handle scope_info(function->shared()->scope_info()); - if (scope_info->HasContext()) { - function_context = Handle(frame_context->declaration_context()); - } - Handle arguments = GetArgumentsObject(isolate, - frame, - &frame_inspector, - scope_info, - function_context); - SetProperty(isolate, - local_scope, - isolate->factory()->arguments_string(), - arguments, - ::NONE, - kNonStrictMode); - } - - // Allocate a new context for the debug evaluation and set the extension - // object build. - Handle context = isolate->factory()->NewFunctionContext( - Context::MIN_CONTEXT_SLOTS, go_between); - - // Use the materialized local scope in a with context. - context = - isolate->factory()->NewWithContext(go_between, context, local_scope); - - // Copy any with contexts present and chain them in front of this context. - context = CopyNestedScopeContextChain(isolate, - go_between, - context, - frame, - inlined_jsframe_index); - if (context.is_null()) { - ASSERT(isolate->has_pending_exception()); - MaybeObject* exception = isolate->pending_exception(); - isolate->clear_pending_exception(); - return exception; - } + materialized = MaterializeStackLocalsWithFrameInspector( + isolate, materialized, function, &frame_inspector); + RETURN_IF_EMPTY_HANDLE(isolate, materialized); + + materialized = MaterializeArgumentsObject( + isolate, materialized, function, &frame_inspector); + RETURN_IF_EMPTY_HANDLE(isolate, materialized); + + // Add the materialized object in a with-scope to shadow the stack locals. + context = isolate->factory()->NewWithContext(function, context, materialized); Handle receiver(frame->receiver(), isolate); Object* evaluate_result_object; @@ -12699,18 +12606,12 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_DebugEvaluate) { DebugEvaluate(isolate, context, context_extension, receiver, source); if (!maybe_result->ToObject(&evaluate_result_object)) return maybe_result; } - Handle evaluate_result(evaluate_result_object, isolate); - - Handle local_scope_control_copy = - MaterializeLocalScopeWithFrameInspector(isolate, frame, - &frame_inspector); - Handle resultArray = isolate->factory()->NewFixedArray(3); - resultArray->set(0, *evaluate_result); - resultArray->set(1, *local_scope_control_copy); - resultArray->set(2, *local_scope); + // Write back potential changes to materialized stack locals to the stack. + UpdateStackLocalsFromMaterializedObject( + isolate, materialized, function, frame, inlined_jsframe_index); - return *(isolate->factory()->NewJSArrayWithElements(resultArray)); + return evaluate_result_object; } diff --git a/src/scopes.cc b/src/scopes.cc index 6ae7cc0..e631332 100644 --- a/src/scopes.cc +++ b/src/scopes.cc @@ -970,6 +970,13 @@ Variable* Scope::LookupRecursive(Handle name, BindingKind* binding_kind, AstNodeFactory* factory) { ASSERT(binding_kind != NULL); + if (already_resolved() && is_with_scope()) { + // Short-cut: if the scope is deserialized from a scope info, variable + // allocation is already fixed. We can simply return with dynamic lookup. + *binding_kind = DYNAMIC_LOOKUP; + return NULL; + } + // Try to find the variable in this scope. Variable* var = LocalLookup(name); @@ -998,6 +1005,7 @@ Variable* Scope::LookupRecursive(Handle name, } if (is_with_scope()) { + ASSERT(!already_resolved()); // The current scope is a with scope, so the variable binding can not be // statically resolved. However, note that it was necessary to do a lookup // in the outer scope anyway, because if a binding exists in an outer scope, diff --git a/test/mjsunit/debug-evaluate-closure.js b/test/mjsunit/debug-evaluate-closure.js new file mode 100644 index 0000000..778defd --- /dev/null +++ b/test/mjsunit/debug-evaluate-closure.js @@ -0,0 +1,91 @@ +// Copyright 2013 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 --allow-natives-syntax + +Debug = debug.Debug; +var listened = false; + +function listener(event, exec_state, event_data, data) { + if (event != Debug.DebugEvent.Break) return; + try { + assertEquals("goo", exec_state.frame(0).evaluate("goo").value()); + exec_state.frame(0).evaluate("goo = 'goo foo'"); + assertEquals("bar return", exec_state.frame(0).evaluate("bar()").value()); + assertEquals("inner bar", exec_state.frame(0).evaluate("inner").value()); + assertEquals("outer bar", exec_state.frame(0).evaluate("outer").value()); + assertEquals("baz inner", exec_state.frame(0).evaluate("baz").value()); + assertEquals("baz outer", exec_state.frame(1).evaluate("baz").value()); + exec_state.frame(0).evaluate("w = 'w foo'"); + exec_state.frame(0).evaluate("inner = 'inner foo'"); + exec_state.frame(0).evaluate("outer = 'outer foo'"); + exec_state.frame(0).evaluate("baz = 'baz inner foo'"); + exec_state.frame(1).evaluate("baz = 'baz outer foo'"); + listened = true; + } catch (e) { + print(e); + print(e.stack); + } +} + +Debug.setListener(listener); + +var outer = "outer"; +var baz = "baz outer"; + +function foo() { + var inner = "inner"; + var baz = "baz inner"; + var goo = "goo"; + var withw = { w: "w" }; + var withv = { v: "v" }; + + with (withv) { + var bar = function bar() { + assertEquals("goo foo", goo); + inner = "inner bar"; + outer = "outer bar"; + v = "v bar"; + return "bar return"; + }; + } + + with (withw) { + debugger; + } + + assertEquals("inner foo", inner); + assertEquals("baz inner foo", baz); + assertEquals("w foo", withw.w); + assertEquals("v bar", withv.v); +} + +foo(); +assertEquals("outer foo", outer); +assertEquals("baz outer foo", baz); +assertTrue(listened); +Debug.setListener(null); diff --git a/test/mjsunit/regress/regress-crbug-259300.js b/test/mjsunit/regress/regress-crbug-259300.js new file mode 100644 index 0000000..c57b0e6 --- /dev/null +++ b/test/mjsunit/regress/regress-crbug-259300.js @@ -0,0 +1,49 @@ +// Copyright 2013 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 + +Debug = debug.Debug; +var listened = false; +var recursion_depth = 0; + +function listener(event, exec_state, event_data, data) { + if (event == Debug.DebugEvent.Break) { + recursion_depth++; + var disable_break = (recursion_depth > 2); + for (var i = 0; i < exec_state.frameCount(); i++) { + exec_state.frame(i).evaluate("debugger", disable_break); + } + } + listened = true; +} + +Debug.setListener(listener); +eval("debugger"); +Debug.setListener(null); +assertTrue(listened); + -- 2.7.4