From cc6be014dc5e864512d5244db496f65645fb6265 Mon Sep 17 00:00:00 2001 From: "yurys@chromium.org" Date: Tue, 14 Jul 2009 16:55:32 +0000 Subject: [PATCH] Support stepping into getters and setters. Related Chromium bug: http://code.google.com/p/chromium/issues/detail?id=16427 Review URL: http://codereview.chromium.org/149542 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@2458 ce2b1a6d-e550-0410-aec6-3dcde31c8c00 --- src/debug.cc | 34 +++- src/objects.cc | 12 ++ test/mjsunit/debug-stepin-accessor.js | 248 ++++++++++++++++++++++++++ test/mjsunit/mjsunit.status | 1 + 4 files changed, 289 insertions(+), 6 deletions(-) create mode 100644 test/mjsunit/debug-stepin-accessor.js diff --git a/src/debug.cc b/src/debug.cc index 52be9301ff..64f98c7609 100644 --- a/src/debug.cc +++ b/src/debug.cc @@ -334,8 +334,11 @@ void BreakLocationIterator::PrepareStepIn() { rinfo()->set_target_address(stub->entry()); } } else { - // Step in through constructs call requires no changes to the running code. - ASSERT(RelocInfo::IsConstructCall(rmode())); + // Step in through construct call requires no changes to the running code. + // Step in through getters/setters should already be prepared as well + // because caller of this function (Debug::PrepareStep) is expected to + // flood the top frame's function with one shot breakpoints. + ASSERT(RelocInfo::IsConstructCall(rmode()) || code->is_inline_cache_stub()); } } @@ -1087,10 +1090,18 @@ void Debug::PrepareStep(StepAction step_action, int step_count) { // Compute whether or not the target is a call target. bool is_call_target = false; + bool is_load_or_store = false; + bool is_inline_cache_stub = false; if (RelocInfo::IsCodeTarget(it.rinfo()->rmode())) { Address target = it.rinfo()->target_address(); Code* code = Code::GetCodeFromTargetAddress(target); - if (code->is_call_stub()) is_call_target = true; + if (code->is_call_stub()) { + is_call_target = true; + } + if (code->is_inline_cache_stub()) { + is_inline_cache_stub = true; + is_load_or_store = !is_call_target; + } } // If this is the last break code target step out is the only possibility. @@ -1103,8 +1114,8 @@ void Debug::PrepareStep(StepAction step_action, int step_count) { JSFunction* function = JSFunction::cast(frames_it.frame()->function()); FloodWithOneShot(Handle(function->shared())); } - } else if (!(is_call_target || RelocInfo::IsConstructCall(it.rmode())) || - step_action == StepNext || step_action == StepMin) { + } else if (!(is_inline_cache_stub || RelocInfo::IsConstructCall(it.rmode())) + || step_action == StepNext || step_action == StepMin) { // Step next or step min. // Fill the current function with one-shot break points. @@ -1117,9 +1128,20 @@ void Debug::PrepareStep(StepAction step_action, int step_count) { } else { // Fill the current function with one-shot break points even for step in on // a call target as the function called might be a native function for - // which step in will not stop. + // which step in will not stop. It also prepares for stepping in + // getters/setters. FloodWithOneShot(shared); + if (is_load_or_store) { + // Remember source position and frame to handle step in getter/setter. If + // there is a custom getter/setter it will be handled in + // Object::Get/SetPropertyWithCallback, otherwise the step action will be + // propagated on the next Debug::Break. + thread_local_.last_statement_position_ = + debug_info->code()->SourceStatementPosition(frame->pc()); + thread_local_.last_fp_ = frame->fp(); + } + // Step in or Step in min it.PrepareStepIn(); ActivateStepIn(frame); diff --git a/src/objects.cc b/src/objects.cc index a9004c924b..72412c15c7 100644 --- a/src/objects.cc +++ b/src/objects.cc @@ -216,6 +216,12 @@ Object* Object::GetPropertyWithDefinedGetter(Object* receiver, HandleScope scope; Handle fun(JSFunction::cast(getter)); Handle self(receiver); +#ifdef ENABLE_DEBUGGER_SUPPORT + // Handle stepping into a getter if step into is active. + if (Debug::StepInActive()) { + Debug::HandleStepIn(fun, Handle::null(), 0, false); + } +#endif bool has_pending_exception; Handle result = Execution::Call(fun, self, 0, NULL, &has_pending_exception); @@ -1624,6 +1630,12 @@ Object* JSObject::SetPropertyWithDefinedSetter(JSFunction* setter, Handle value_handle(value); Handle fun(JSFunction::cast(setter)); Handle self(this); +#ifdef ENABLE_DEBUGGER_SUPPORT + // Handle stepping into a setter if step into is active. + if (Debug::StepInActive()) { + Debug::HandleStepIn(fun, Handle::null(), 0, false); + } +#endif bool has_pending_exception; Object** argv[] = { value_handle.location() }; Execution::Call(fun, self, 1, argv, &has_pending_exception); diff --git a/test/mjsunit/debug-stepin-accessor.js b/test/mjsunit/debug-stepin-accessor.js new file mode 100644 index 0000000000..86a9dcf258 --- /dev/null +++ b/test/mjsunit/debug-stepin-accessor.js @@ -0,0 +1,248 @@ +// Copyright 2008 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 + +// Get the Debug object exposed from the debug context global object. +Debug = debug.Debug + +var exception = null; +var state = 1; +var expected_source_line_text = null; +var expected_function_name = null; + +// Simple debug event handler which first time will cause 'step in' action +// to get into g.call and than check that execution is pauesed inside +// function 'g'. +function listener(event, exec_state, event_data, data) { + try { + if (event == Debug.DebugEvent.Break) { + if (state == 1) { + exec_state.prepareStep(Debug.StepAction.StepIn, 2); + state = 2; + } else if (state == 2) { + assertEquals(expected_source_line_text, + event_data.sourceLineText()); + assertEquals(expected_function_name, event_data.func().name()); + state = 3; + } + } + } catch(e) { + exception = e; + } +}; + +// Add the debug event listener. +Debug.setListener(listener); + + +var c = { + name: 'name ', + get getter1() { + return this.name; // getter 1 + }, + get getter2() { + return { // getter 2 + 'a': c.name + }; + }, + set setter1(n) { + this.name = n; // setter 1 + } +}; + +c.__defineGetter__('y', function getterY() { + return this.name; // getter y +}); + +c.__defineGetter__(3, function getter3() { + return this.name; // getter 3 +}); + +c.__defineSetter__('y', function setterY(n) { + this.name = n; // setter y +}); + +c.__defineSetter__(3, function setter3(n) { + this.name = n; // setter 3 +}); + +var d = { + 'c': c, +}; + +function testGetter1_1() { + expected_function_name = 'getter1'; + expected_source_line_text = ' return this.name; // getter 1'; + debugger; + var x = c.getter1; +} + +function testGetter1_2() { + expected_function_name = 'getter1'; + expected_source_line_text = ' return this.name; // getter 1'; + debugger; + var x = c['getter1']; +} + +function testGetter1_3() { + expected_function_name = 'getter1'; + expected_source_line_text = ' return this.name; // getter 1'; + debugger; + for (var i = 1; i < 2; i++) { + var x = c['getter' + i]; + } +} + +function testGetter1_4() { + expected_function_name = 'getter1'; + expected_source_line_text = ' return this.name; // getter 1'; + debugger; + var x = d.c.getter1; +} + +function testGetter1_5() { + expected_function_name = 'getter1'; + expected_source_line_text = ' return this.name; // getter 1'; + for (var i = 2; i != 1; i--); + debugger; + var x = d.c['getter' + i]; +} + +function testGetter2_1() { + expected_function_name = 'getter2'; + expected_source_line_text = ' return { // getter 2'; + for (var i = 2; i != 1; i--); + debugger; + var t = d.c.getter2.name; +} + + +function testGetterY_1() { + expected_function_name = 'getterY'; + expected_source_line_text = ' return this.name; // getter y'; + debugger; + var t = d.c.y; +} + +function testIndexedGetter3_1() { + expected_function_name = 'getter3'; + expected_source_line_text = ' return this.name; // getter 3'; + debugger; + var r = d.c[3]; +} + +function testSetterY_1() { + expected_function_name = 'setterY'; + expected_source_line_text = ' this.name = n; // setter y'; + debugger; + d.c.y = 'www'; +} + +function testIndexedSetter3_1() { + expected_function_name = 'setter3'; + expected_source_line_text = ' this.name = n; // setter 3'; + var i = 3 + debugger; + d.c[3] = 'www'; +} + +function testSetter1_1() { + expected_function_name = 'setter1'; + expected_source_line_text = ' this.name = n; // setter 1'; + debugger; + d.c.setter1 = 'aa'; +} + +function testSetter1_2() { + expected_function_name = 'setter1'; + expected_source_line_text = ' this.name = n; // setter 1'; + debugger; + d.c['setter1'] = 'bb'; +} + +function testSetter1_3() { + expected_function_name = 'setter1'; + expected_source_line_text = ' this.name = n; // setter 1'; + for (var i = 2; i != 1; i--); + debugger; + d.c['setter' + i] = i; +} + +var e = { + name: 'e' +}; +e.__proto__ = c; + +function testProtoGetter1_1() { + expected_function_name = 'getter1'; + expected_source_line_text = ' return this.name; // getter 1'; + debugger; + var x = e.getter1; +} + +function testProtoSetter1_1() { + expected_function_name = 'setter1'; + expected_source_line_text = ' this.name = n; // setter 1'; + debugger; + e.setter1 = 'aa'; +} + +function testProtoIndexedGetter3_1() { + expected_function_name = 'getter3'; + expected_source_line_text = ' return this.name; // getter 3'; + debugger; + var x = e[3]; +} + +function testProtoIndexedSetter3_1() { + expected_function_name = 'setter3'; + expected_source_line_text = ' this.name = n; // setter 3'; + debugger; + e[3] = 'new val'; +} + +function testProtoSetter1_2() { + expected_function_name = 'setter1'; + expected_source_line_text = ' this.name = n; // setter 1'; + for (var i = 2; i != 1; i--); + debugger; + e['setter' + i] = 'aa'; +} + +for (var n in this) { + if (n.substr(0, 4) != 'test') { + continue; + } + state = 1; + this[n](); + assertNull(exception); + assertEquals(3, state); +} + +// Get rid of the debug event listener. +Debug.setListener(null); \ No newline at end of file diff --git a/test/mjsunit/mjsunit.status b/test/mjsunit/mjsunit.status index cd70ebc3bc..a1e8fb42ad 100644 --- a/test/mjsunit/mjsunit.status +++ b/test/mjsunit/mjsunit.status @@ -58,6 +58,7 @@ debug-ignore-breakpoints: CRASH || FAIL debug-multiple-breakpoints: CRASH || FAIL debug-setbreakpoint: CRASH || FAIL || PASS debug-step-stub-callfunction: SKIP +debug-stepin-accessor: CRASH || FAIL debug-stepin-constructor: CRASH, FAIL debug-stepin-function-call: CRASH || FAIL debug-step: SKIP