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

Reply via email to