Revision: 11399
Author: [email protected]
Date: Fri Apr 20 04:06:12 2012
Log: Enable stepping into callback passed to builtins (e.g.
Array.forEach).
BUG=109564
TEST=
Review URL: https://chromiumcodereview.appspot.com/10078014
http://code.google.com/p/v8/source/detail?r=11399
Added:
/branches/bleeding_edge/test/mjsunit/debug-stepin-builtin-callback.js
Modified:
/branches/bleeding_edge/src/array.js
/branches/bleeding_edge/src/debug.h
/branches/bleeding_edge/src/runtime.cc
/branches/bleeding_edge/src/runtime.h
/branches/bleeding_edge/src/string.js
=======================================
--- /dev/null
+++ /branches/bleeding_edge/test/mjsunit/debug-stepin-builtin-callback.js
Fri Apr 20 04:06:12 2012
@@ -0,0 +1,206 @@
+// Copyright 2012 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 stepping into callbacks passed to builtin functions.
+
+Debug = debug.Debug
+
+var exception = false;
+
+function array_listener(event, exec_state, event_data, data) {
+ try {
+ if (event == Debug.DebugEvent.Break) {
+ if (breaks == 0) {
+ exec_state.prepareStep(Debug.StepAction.StepIn, 2);
+ breaks = 1;
+ } else if (breaks <= 3) {
+ breaks++;
+ // Check whether we break at the expected line.
+ print(event_data.sourceLineText());
+ assertTrue(event_data.sourceLineText().indexOf("Expected to step")
0);
+ exec_state.prepareStep(Debug.StepAction.StepIn, 3);
+ }
+ }
+ } catch (e) {
+ exception = true;
+ }
+};
+
+function cb_false(num) {
+ print("element " + num); // Expected to step to this point.
+ return false;
+}
+
+function cb_true(num) {
+ print("element " + num); // Expected to step to this point.
+ return true;
+}
+
+function cb_reduce(a, b) {
+ print("elements " + a + " and " + b); // Expected to step to this point.
+ return a + b;
+}
+
+var a = [1, 2, 3, 4];
+
+Debug.setListener(array_listener);
+
+var breaks = 0;
+debugger;
+a.forEach(cb_true);
+assertFalse(exception);
+assertEquals(4, breaks);
+
+breaks = 0;
+debugger;
+a.some(cb_false);
+assertFalse(exception);
+assertEquals(4, breaks);
+
+breaks = 0;
+debugger;
+a.every(cb_true);
+assertEquals(4, breaks);
+assertFalse(exception);
+
+breaks = 0;
+debugger;
+a.map(cb_true);
+assertFalse(exception);
+assertEquals(4, breaks);
+
+breaks = 0;
+debugger;
+a.filter(cb_true);
+assertFalse(exception);
+assertEquals(4, breaks);
+
+breaks = 0;
+debugger;
+a.reduce(cb_reduce);
+assertFalse(exception);
+assertEquals(4, breaks);
+
+breaks = 0;
+debugger;
+a.reduceRight(cb_reduce);
+assertFalse(exception);
+assertEquals(4, breaks);
+
+Debug.setListener(null);
+
+
+// Test two levels of builtin callbacks:
+// Array.forEach calls a callback function, which by itself uses
+// Array.forEach with another callback function.
+
+function second_level_listener(event, exec_state, event_data, data) {
+ try {
+ if (event == Debug.DebugEvent.Break) {
+ if (breaks == 0) {
+ exec_state.prepareStep(Debug.StepAction.StepIn, 3);
+ breaks = 1;
+ } else if (breaks <= 16) {
+ breaks++;
+ // Check whether we break at the expected line.
+ assertTrue(event_data.sourceLineText().indexOf("Expected to step")
0);
+ // Step two steps further every four breaks to skip the
+ // forEach call in the first level of recurision.
+ var step = (breaks % 4 == 1) ? 6 : 3;
+ exec_state.prepareStep(Debug.StepAction.StepIn, step);
+ }
+ }
+ } catch (e) {
+ exception = true;
+ }
+};
+
+function cb_foreach(num) {
+ a.forEach(cb_true);
+ print("back to the first level of recursion.");
+}
+
+Debug.setListener(second_level_listener);
+
+breaks = 0;
+debugger;
+a.forEach(cb_foreach);
+assertFalse(exception);
+assertEquals(17, breaks);
+
+Debug.setListener(null);
+
+
+//Test replace callback in String.replace.
+
+function replace_listener(event, exec_state, event_data, data) {
+ try {
+ if (event == Debug.DebugEvent.Break) {
+ if (breaks == 0) {
+ exec_state.prepareStep(Debug.StepAction.StepIn, 2);
+ breaks = 1;
+ } else {
+ // Check whether we break at the expected line.
+ assertTrue(event_data.sourceLineText().indexOf("Expected to step")
0);
+ }
+ }
+ } catch (e) {
+ exception = true;
+ }
+};
+
+function cb_replace(match) {
+ print("matching string: " + match); // Expected to step to this point.
+ return "_";
+}
+
+var s = "abcdefgehijke";
+
+Debug.setListener(replace_listener);
+
+breaks = 0;
+debugger;
+assertEquals("ab_defgehijke", s.replace("c", cb_replace));
+assertFalse(exception);
+assertEquals(1, breaks);
+
+breaks = 0;
+debugger;
+assertEquals("abcdefgehij_", s.replace(/..$/, cb_replace));
+assertFalse(exception);
+assertEquals(1, breaks);
+
+breaks = 0;
+debugger;
+assertEquals("abcd_fg_hijk_", s.replace(/e/g, cb_replace));
+assertFalse(exception);
+assertEquals(1, breaks);
+
+
+Debug.setListener(null);
=======================================
--- /branches/bleeding_edge/src/array.js Mon Apr 16 01:12:12 2012
+++ /branches/bleeding_edge/src/array.js Fri Apr 20 04:06:12 2012
@@ -1027,13 +1027,28 @@
var result = new $Array();
var accumulator = new InternalArray();
var accumulator_length = 0;
- for (var i = 0; i < length; i++) {
- if (i in array) {
- var element = array[i];
- if (%_CallFunction(receiver, element, i, array, f)) {
- accumulator[accumulator_length++] = element;
+ if (%DebugCallbackSupportsStepping(f)) {
+ for (var i = 0; i < length; i++) {
+ if (i in array) {
+ var element = array[i];
+ // Prepare break slots for debugger step in.
+ %DebugPrepareStepInIfStepping(f);
+ if (%_CallFunction(receiver, element, i, array, f)) {
+ accumulator[accumulator_length++] = element;
+ }
}
}
+ } else {
+ // This is a duplicate of the previous loop sans debug stepping.
+ for (var i = 0; i < length; i++) {
+ if (i in array) {
+ var element = array[i];
+ if (%_CallFunction(receiver, element, i, array, f)) {
+ accumulator[accumulator_length++] = element;
+ }
+ }
+ }
+ // End of duplicate.
}
%MoveArrayContents(accumulator, result);
return result;
@@ -1059,12 +1074,24 @@
} else if (!IS_SPEC_OBJECT(receiver)) {
receiver = ToObject(receiver);
}
-
- for (var i = 0; i < length; i++) {
- if (i in array) {
- var element = array[i];
- %_CallFunction(receiver, element, i, array, f);
- }
+ if (%DebugCallbackSupportsStepping(f)) {
+ for (var i = 0; i < length; i++) {
+ if (i in array) {
+ var element = array[i];
+ // Prepare break slots for debugger step in.
+ %DebugPrepareStepInIfStepping(f);
+ %_CallFunction(receiver, element, i, array, f);
+ }
+ }
+ } else {
+ // This is a duplicate of the previous loop sans debug stepping.
+ for (var i = 0; i < length; i++) {
+ if (i in array) {
+ var element = array[i];
+ %_CallFunction(receiver, element, i, array, f);
+ }
+ }
+ // End of duplicate.
}
}
@@ -1091,11 +1118,24 @@
receiver = ToObject(receiver);
}
- for (var i = 0; i < length; i++) {
- if (i in array) {
- var element = array[i];
- if (%_CallFunction(receiver, element, i, array, f)) return true;
- }
+ if (%DebugCallbackSupportsStepping(f)) {
+ for (var i = 0; i < length; i++) {
+ if (i in array) {
+ var element = array[i];
+ // Prepare break slots for debugger step in.
+ %DebugPrepareStepInIfStepping(f);
+ if (%_CallFunction(receiver, element, i, array, f)) return true;
+ }
+ }
+ } else {
+ // This is a duplicate of the previous loop sans debug stepping.
+ for (var i = 0; i < length; i++) {
+ if (i in array) {
+ var element = array[i];
+ if (%_CallFunction(receiver, element, i, array, f)) return true;
+ }
+ }
+ // End of duplicate.
}
return false;
}
@@ -1121,11 +1161,24 @@
receiver = ToObject(receiver);
}
- for (var i = 0; i < length; i++) {
- if (i in array) {
- var element = array[i];
- if (!%_CallFunction(receiver, element, i, array, f)) return false;
- }
+ if (%DebugCallbackSupportsStepping(f)) {
+ for (var i = 0; i < length; i++) {
+ if (i in array) {
+ var element = array[i];
+ // Prepare break slots for debugger step in.
+ %DebugPrepareStepInIfStepping(f);
+ if (!%_CallFunction(receiver, element, i, array, f)) return false;
+ }
+ }
+ } else {
+ // This is a duplicate of the previous loop sans debug stepping.
+ for (var i = 0; i < length; i++) {
+ if (i in array) {
+ var element = array[i];
+ if (!%_CallFunction(receiver, element, i, array, f)) return false;
+ }
+ }
+ // End of duplicate.
}
return true;
}
@@ -1152,11 +1205,24 @@
var result = new $Array();
var accumulator = new InternalArray(length);
- for (var i = 0; i < length; i++) {
- if (i in array) {
- var element = array[i];
- accumulator[i] = %_CallFunction(receiver, element, i, array, f);
- }
+ if (%DebugCallbackSupportsStepping(f)) {
+ for (var i = 0; i < length; i++) {
+ if (i in array) {
+ var element = array[i];
+ // Prepare break slots for debugger step in.
+ %DebugPrepareStepInIfStepping(f);
+ accumulator[i] = %_CallFunction(receiver, element, i, array, f);
+ }
+ }
+ } else {
+ // This is a duplicate of the previous loop sans debug stepping.
+ for (var i = 0; i < length; i++) {
+ if (i in array) {
+ var element = array[i];
+ accumulator[i] = %_CallFunction(receiver, element, i, array, f);
+ }
+ }
+ // End of duplicate.
}
%MoveArrayContents(accumulator, result);
return result;
@@ -1311,11 +1377,27 @@
}
var receiver = %GetDefaultReceiver(callback);
- for (; i < length; i++) {
- if (i in array) {
- var element = array[i];
- current = %_CallFunction(receiver, current, element, i, array,
callback);
- }
+
+ if (%DebugCallbackSupportsStepping(callback)) {
+ for (; i < length; i++) {
+ if (i in array) {
+ var element = array[i];
+ // Prepare break slots for debugger step in.
+ %DebugPrepareStepInIfStepping(callback);
+ current =
+ %_CallFunction(receiver, current, element, i, array, callback);
+ }
+ }
+ } else {
+ // This is a duplicate of the previous loop sans debug stepping.
+ for (; i < length; i++) {
+ if (i in array) {
+ var element = array[i];
+ current =
+ %_CallFunction(receiver, current, element, i, array, callback);
+ }
+ }
+ // End of duplicate.
}
return current;
}
@@ -1348,11 +1430,27 @@
}
var receiver = %GetDefaultReceiver(callback);
- for (; i >= 0; i--) {
- if (i in array) {
- var element = array[i];
- current = %_CallFunction(receiver, current, element, i, array,
callback);
- }
+
+ if (%DebugCallbackSupportsStepping(callback)) {
+ for (; i >= 0; i--) {
+ if (i in array) {
+ var element = array[i];
+ // Prepare break slots for debugger step in.
+ %DebugPrepareStepInIfStepping(callback);
+ current =
+ %_CallFunction(receiver, current, element, i, array, callback);
+ }
+ }
+ } else {
+ // This is a duplicate of the previous loop sans debug stepping.
+ for (; i >= 0; i--) {
+ if (i in array) {
+ var element = array[i];
+ current =
+ %_CallFunction(receiver, current, element, i, array, callback);
+ }
+ }
+ // End of duplicate.
}
return current;
}
=======================================
--- /branches/bleeding_edge/src/debug.h Thu Mar 15 07:17:22 2012
+++ /branches/bleeding_edge/src/debug.h Fri Apr 20 04:06:12 2012
@@ -1,4 +1,4 @@
-// Copyright 2011 the V8 project authors. All rights reserved.
+// Copyright 2012 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:
@@ -245,6 +245,8 @@
bool IsBreakOnException(ExceptionBreakType type);
void PrepareStep(StepAction step_action, int step_count);
void ClearStepping();
+ void ClearStepOut();
+ bool IsStepping() { return thread_local_.step_count_ > 0; }
bool StepNextContinue(BreakLocationIterator* break_location_iterator,
JavaScriptFrame* frame);
static Handle<DebugInfo> GetDebugInfo(Handle<SharedFunctionInfo> shared);
@@ -464,7 +466,6 @@
void ActivateStepIn(StackFrame* frame);
void ClearStepIn();
void ActivateStepOut(StackFrame* frame);
- void ClearStepOut();
void ClearStepNext();
// Returns whether the compile succeeded.
void RemoveDebugInfo(Handle<DebugInfo> debug_info);
=======================================
--- /branches/bleeding_edge/src/runtime.cc Tue Apr 17 00:16:19 2012
+++ /branches/bleeding_edge/src/runtime.cc Fri Apr 20 04:06:12 2012
@@ -4694,6 +4694,36 @@
}
return *object;
}
+
+
+// Check whether debugger and is about to step into the callback that is
passed
+// to a built-in function such as Array.forEach.
+RUNTIME_FUNCTION(MaybeObject*, Runtime_DebugCallbackSupportsStepping) {
+ if (!isolate->IsDebuggerActive()) return isolate->heap()->false_value();
+ CONVERT_ARG_CHECKED(Object, callback, 0);
+ // We do not step into the callback if it's a builtin or not even a
function.
+ if (!callback->IsJSFunction() ||
JSFunction::cast(callback)->IsBuiltin()) {
+ return isolate->heap()->false_value();
+ }
+ return isolate->heap()->true_value();
+}
+
+
+// Set one shot breakpoints for the callback function that is passed to a
+// built-in function such as Array.forEach to enable stepping into the
callback.
+RUNTIME_FUNCTION(MaybeObject*, Runtime_DebugPrepareStepInIfStepping) {
+ Debug* debug = isolate->debug();
+ if (!debug->IsStepping()) return NULL;
+ CONVERT_ARG_CHECKED(Object, callback, 0);
+ HandleScope scope(isolate);
+ Handle<SharedFunctionInfo>
shared_info(JSFunction::cast(callback)->shared());
+ // When leaving the callback, step out has been activated, but not
performed
+ // if we do not leave the builtin. To be able to step into the callback
+ // again, we need to clear the step out at this point.
+ debug->ClearStepOut();
+ debug->FloodWithOneShot(shared_info);
+ return NULL;
+}
// Set a local property, even if it is READ_ONLY. If the property does not
=======================================
--- /branches/bleeding_edge/src/runtime.h Mon Apr 16 07:43:27 2012
+++ /branches/bleeding_edge/src/runtime.h Fri Apr 20 04:06:12 2012
@@ -97,6 +97,8 @@
F(AllocateInNewSpace, 1, 1) \
F(SetNativeFlag, 1, 1) \
F(StoreArrayLiteralElement, 5, 1) \
+ F(DebugCallbackSupportsStepping, 1, 1) \
+ F(DebugPrepareStepInIfStepping, 1, 1) \
\
/* Array join support */ \
F(PushIfAbsent, 2, 1) \
=======================================
--- /branches/bleeding_edge/src/string.js Tue Apr 10 03:42:25 2012
+++ /branches/bleeding_edge/src/string.js Fri Apr 20 04:06:12 2012
@@ -266,6 +266,10 @@
// Compute the string to replace with.
if (IS_SPEC_FUNCTION(replace)) {
var receiver = %GetDefaultReceiver(replace);
+ // Prepare break slots for debugger step in.
+ if (%DebugCallbackSupportsStepping(replace)) {
+ %DebugPrepareStepInIfStepping(replace);
+ }
builder.add(%_CallFunction(receiver,
search,
start,
@@ -434,24 +438,49 @@
var match_start = 0;
var override = new InternalArray(null, 0, subject);
var receiver = %GetDefaultReceiver(replace);
- while (i < len) {
- var elem = res[i];
- if (%_IsSmi(elem)) {
- if (elem > 0) {
- match_start = (elem >> 11) + (elem & 0x7ff);
+ if (%DebugCallbackSupportsStepping(replace)) {
+ while (i < len) {
+ var elem = res[i];
+ if (%_IsSmi(elem)) {
+ if (elem > 0) {
+ match_start = (elem >> 11) + (elem & 0x7ff);
+ } else {
+ match_start = res[++i] - elem;
+ }
} else {
- match_start = res[++i] - elem;
- }
- } else {
- override[0] = elem;
- override[1] = match_start;
- lastMatchInfoOverride = override;
- var func_result =
- %_CallFunction(receiver, elem, match_start, subject, replace);
- res[i] = TO_STRING_INLINE(func_result);
- match_start += elem.length;
- }
- i++;
+ override[0] = elem;
+ override[1] = match_start;
+ lastMatchInfoOverride = override;
+ %DebugPrepareStepInIfStepping(replace);
+ var func_result =
+ %_CallFunction(receiver, elem, match_start, subject,
replace);
+ res[i] = TO_STRING_INLINE(func_result);
+ match_start += elem.length;
+ }
+ i++;
+ }
+ } else {
+ // This is a duplicate of the previous loop sans debug stepping.
+ while (i < len) {
+ var elem = res[i];
+ if (%_IsSmi(elem)) {
+ if (elem > 0) {
+ match_start = (elem >> 11) + (elem & 0x7ff);
+ } else {
+ match_start = res[++i] - elem;
+ }
+ } else {
+ override[0] = elem;
+ override[1] = match_start;
+ lastMatchInfoOverride = override;
+ var func_result =
+ %_CallFunction(receiver, elem, match_start, subject,
replace);
+ res[i] = TO_STRING_INLINE(func_result);
+ match_start += elem.length;
+ }
+ i++;
+ }
+ // End of duplicate.
}
} else {
var receiver = %GetDefaultReceiver(replace);
@@ -491,9 +520,12 @@
if (m == 1) {
// No captures, only the match, which is always valid.
var s = SubString(subject, index, endOfMatch);
+ // Prepare break slots for debugger step in.
+ if (%DebugCallbackSupportsStepping(replace)) {
+ %DebugPrepareStepInIfStepping(replace);
+ }
// Don't call directly to avoid exposing the built-in global object.
- replacement =
- %_CallFunction(receiver, s, index, subject, replace);
+ replacement = %_CallFunction(receiver, s, index, subject, replace);
} else {
var parameters = new InternalArray(m + 2);
for (var j = 0; j < m; j++) {
--
v8-dev mailing list
[email protected]
http://groups.google.com/group/v8-dev