Introduce new stepping mode to step into another frame.
authoryangguo@chromium.org <yangguo@chromium.org>
Wed, 5 Nov 2014 08:44:30 +0000 (08:44 +0000)
committeryangguo@chromium.org <yangguo@chromium.org>
Wed, 5 Nov 2014 08:44:54 +0000 (08:44 +0000)
R=aandrey@chromium.org
BUG=chromium:267592
LOG=Y

Review URL: https://codereview.chromium.org/690263004

Cr-Commit-Position: refs/heads/master@{#25130}
git-svn-id: https://v8.googlecode.com/svn/branches/bleeding_edge@25130 ce2b1a6d-e550-0410-aec6-3dcde31c8c00

src/debug-debugger.js
src/debug.cc
src/debug.h
src/objects.cc
src/runtime/runtime-debug.cc
test/mjsunit/debug-stepframe.js [new file with mode: 0644]

index a1468a0..fc4c5da 100644 (file)
@@ -33,7 +33,8 @@ Debug.StepAction = { StepOut: 0,
                      StepNext: 1,
                      StepIn: 2,
                      StepMin: 3,
-                     StepInMin: 4 };
+                     StepInMin: 4,
+                     StepFrame: 5 };
 
 // The different types of scripts matching enum ScriptType in objects.h.
 Debug.ScriptType = { Native: 0,
index 841b6cf..7fe9064 100644 (file)
@@ -120,21 +120,37 @@ void BreakLocationIterator::Next() {
       DCHECK(statement_position_ >= 0);
     }
 
-    if (IsDebugBreakSlot()) {
-      // There is always a possible break point at a debug break slot.
+    // Check for break at return.
+    if (RelocInfo::IsJSReturn(rmode())) {
+      // Set the positions to the end of the function.
+      if (debug_info_->shared()->HasSourceCode()) {
+        position_ = debug_info_->shared()->end_position() -
+                    debug_info_->shared()->start_position() - 1;
+      } else {
+        position_ = 0;
+      }
+      statement_position_ = position_;
       break_point_++;
       return;
-    } else if (RelocInfo::IsCodeTarget(rmode())) {
+    }
+
+    if (RelocInfo::IsCodeTarget(rmode())) {
       // Check for breakable code target. Look in the original code as setting
       // break points can cause the code targets in the running (debugged) code
       // to be of a different kind than in the original code.
       Address target = original_rinfo()->target_address();
       Code* code = Code::GetCodeFromTargetAddress(target);
-      if ((code->is_inline_cache_stub() &&
-           !code->is_binary_op_stub() &&
-           !code->is_compare_ic_stub() &&
-           !code->is_to_boolean_ic_stub()) ||
-          RelocInfo::IsConstructCall(rmode())) {
+
+      if (RelocInfo::IsConstructCall(rmode()) || code->is_call_stub()) {
+        break_point_++;
+        return;
+      }
+
+      // Skip below if we only want locations for calls and returns.
+      if (type_ == CALLS_AND_RETURNS) continue;
+
+      if ((code->is_inline_cache_stub() && !code->is_binary_op_stub() &&
+           !code->is_compare_ic_stub() && !code->is_to_boolean_ic_stub())) {
         break_point_++;
         return;
       }
@@ -157,16 +173,8 @@ void BreakLocationIterator::Next() {
       }
     }
 
-    // Check for break at return.
-    if (RelocInfo::IsJSReturn(rmode())) {
-      // Set the positions to the end of the function.
-      if (debug_info_->shared()->HasSourceCode()) {
-        position_ = debug_info_->shared()->end_position() -
-                    debug_info_->shared()->start_position() - 1;
-      } else {
-        position_ = 0;
-      }
-      statement_position_ = position_;
+    if (IsDebugBreakSlot() && type_ != CALLS_AND_RETURNS) {
+      // There is always a possible break point at a debug break slot.
       break_point_++;
       return;
     }
@@ -1189,7 +1197,8 @@ void Debug::ClearAllBreakPoints() {
 }
 
 
-void Debug::FloodWithOneShot(Handle<JSFunction> function) {
+void Debug::FloodWithOneShot(Handle<JSFunction> function,
+                             BreakLocatorType type) {
   PrepareForBreakPoints();
 
   // Make sure the function is compiled and has set up the debug info.
@@ -1200,7 +1209,7 @@ void Debug::FloodWithOneShot(Handle<JSFunction> function) {
   }
 
   // Flood the function with break points.
-  BreakLocationIterator it(GetDebugInfo(shared), ALL_BREAK_LOCATIONS);
+  BreakLocationIterator it(GetDebugInfo(shared), type);
   while (!it.Done()) {
     it.SetOneShot();
     it.Next();
@@ -1216,7 +1225,7 @@ void Debug::FloodBoundFunctionWithOneShot(Handle<JSFunction> function) {
   if (!bindee.is_null() && bindee->IsJSFunction() &&
       !JSFunction::cast(*bindee)->IsFromNativeScript()) {
     Handle<JSFunction> bindee_function(JSFunction::cast(*bindee));
-    Debug::FloodWithOneShot(bindee_function);
+    FloodWithOneShot(bindee_function);
   }
 }
 
@@ -1296,7 +1305,7 @@ void Debug::PrepareStep(StepAction step_action,
   FloodHandlerWithOneShot();
 
   // If the function on the top frame is unresolved perform step out. This will
-  // be the case when calling unknown functions and having the debugger stopped
+  // be the case when calling unknown function and having the debugger stopped
   // in an unhandled exception.
   if (!frame->function()->IsJSFunction()) {
     // Step out: Find the calling JavaScript frame and flood it with
@@ -1354,7 +1363,7 @@ void Debug::PrepareStep(StepAction step_action,
       if ((maybe_call_function_stub->kind() == Code::STUB &&
            CodeStub::GetMajorKey(maybe_call_function_stub) ==
                CodeStub::CallFunction) ||
-          maybe_call_function_stub->kind() == Code::CALL_IC) {
+          maybe_call_function_stub->is_call_stub()) {
         // Save reference to the code as we may need it to find out arguments
         // count for 'step in' later.
         call_function_stub = Handle<Code>(maybe_call_function_stub);
@@ -1395,7 +1404,9 @@ void Debug::PrepareStep(StepAction step_action,
     // Step next or step min.
 
     // Fill the current function with one-shot break points.
-    FloodWithOneShot(function);
+    // If we are stepping into another frame, only fill calls and returns.
+    FloodWithOneShot(function, step_action == StepFrame ? CALLS_AND_RETURNS
+                                                        : ALL_BREAK_LOCATIONS);
 
     // Remember source position and frame to handle step next.
     thread_local_.last_statement_position_ =
@@ -1454,7 +1465,7 @@ void Debug::PrepareStep(StepAction step_action,
       if (fun->IsJSFunction()) {
         Handle<JSFunction> js_function(JSFunction::cast(fun));
         if (js_function->shared()->bound()) {
-          Debug::FloodBoundFunctionWithOneShot(js_function);
+          FloodBoundFunctionWithOneShot(js_function);
         } else if (!js_function->IsFromNativeScript()) {
           // Don't step into builtins.
           // It will also compile target function if it's not compiled yet.
@@ -1467,7 +1478,9 @@ void Debug::PrepareStep(StepAction step_action,
     // a call target as the function called might be a native function for
     // which step in will not stop. It also prepares for stepping in
     // getters/setters.
-    FloodWithOneShot(function);
+    // If we are stepping into another frame, only fill calls and returns.
+    FloodWithOneShot(function, step_action == StepFrame ? CALLS_AND_RETURNS
+                                                        : ALL_BREAK_LOCATIONS);
 
     if (is_load_or_store) {
       // Remember source position and frame to handle step in getter/setter. If
@@ -1496,15 +1509,20 @@ bool Debug::StepNextContinue(BreakLocationIterator* break_location_iterator,
                              JavaScriptFrame* frame) {
   // StepNext and StepOut shouldn't bring us deeper in code, so last frame
   // shouldn't be a parent of current frame.
-  if (thread_local_.last_step_action_ == StepNext ||
-      thread_local_.last_step_action_ == StepOut) {
+  StepAction step_action = thread_local_.last_step_action_;
+
+  if (step_action == StepNext || step_action == StepOut) {
     if (frame->fp() < thread_local_.last_fp_) return true;
   }
 
+  // We stepped into a new frame if the frame pointer changed.
+  if (step_action == StepFrame) {
+    return frame->UnpaddedFP() == thread_local_.last_fp_;
+  }
+
   // If the step last action was step next or step in make sure that a new
   // statement is hit.
-  if (thread_local_.last_step_action_ == StepNext ||
-      thread_local_.last_step_action_ == StepIn) {
+  if (step_action == StepNext || step_action == StepIn) {
     // Never continue if returning from function.
     if (break_location_iterator->IsExit()) return false;
 
@@ -1571,10 +1589,13 @@ Handle<Object> Debug::GetSourceBreakLocations(
 
 
 // Handle stepping into a function.
-void Debug::HandleStepIn(Handle<JSFunction> function,
-                         Handle<Object> holder,
-                         Address fp,
-                         bool is_constructor) {
+void Debug::HandleStepIn(Handle<Object> function_obj, Handle<Object> holder,
+                         Address fp, bool is_constructor) {
+  // Flood getter/setter if we either step in or step to another frame.
+  bool step_frame = thread_local_.last_step_action_ == StepFrame;
+  if (!StepInActive() && !step_frame) return;
+  if (!function_obj->IsJSFunction()) return;
+  Handle<JSFunction> function = Handle<JSFunction>::cast(function_obj);
   Isolate* isolate = function->GetIsolate();
   // If the frame pointer is not supplied by the caller find it.
   if (fp == 0) {
@@ -1589,11 +1610,11 @@ void Debug::HandleStepIn(Handle<JSFunction> function,
   }
 
   // Flood the function with one-shot break points if it is called from where
-  // step into was requested.
-  if (fp == thread_local_.step_into_fp_) {
+  // step into was requested, or when stepping into a new frame.
+  if (fp == thread_local_.step_into_fp_ || step_frame) {
     if (function->shared()->bound()) {
       // Handle Function.prototype.bind
-      Debug::FloodBoundFunctionWithOneShot(function);
+      FloodBoundFunctionWithOneShot(function);
     } else if (!function->IsFromNativeScript()) {
       // Don't allow step into functions in the native context.
       if (function->shared()->code() ==
@@ -1607,14 +1628,14 @@ void Debug::HandleStepIn(Handle<JSFunction> function,
         if (!holder.is_null() && holder->IsJSFunction()) {
           Handle<JSFunction> js_function = Handle<JSFunction>::cast(holder);
           if (!js_function->IsFromNativeScript()) {
-            Debug::FloodWithOneShot(js_function);
+            FloodWithOneShot(js_function);
           } else if (js_function->shared()->bound()) {
             // Handle Function.prototype.bind
-            Debug::FloodBoundFunctionWithOneShot(js_function);
+            FloodBoundFunctionWithOneShot(js_function);
           }
         }
       } else {
-        Debug::FloodWithOneShot(function);
+        FloodWithOneShot(function);
       }
     }
   }
index 2afe0f6..3bfa02f 100644 (file)
@@ -32,13 +32,14 @@ class DebugScope;
 // Step actions. NOTE: These values are in macros.py as well.
 enum StepAction {
   StepNone = -1,  // Stepping not prepared.
-  StepOut = 0,   // Step out of the current function.
-  StepNext = 1,  // Step to the next statement in the current function.
-  StepIn = 2,    // Step into new functions invoked or the next statement
-                 // in the current function.
-  StepMin = 3,   // Perform a minimum step in the current function.
-  StepInMin = 4  // Step into new functions invoked or perform a minimum step
-                 // in the current function.
+  StepOut = 0,    // Step out of the current function.
+  StepNext = 1,   // Step to the next statement in the current function.
+  StepIn = 2,     // Step into new functions invoked or the next statement
+                  // in the current function.
+  StepMin = 3,    // Perform a minimum step in the current function.
+  StepInMin = 4,  // Step into new functions invoked or perform a minimum step
+                  // in the current function.
+  StepFrame = 5   // Step into a new frame or return to previous frame.
 };
 
 
@@ -49,10 +50,11 @@ enum ExceptionBreakType {
 };
 
 
-// Type of exception break. NOTE: These values are in macros.py as well.
+// Type of exception break.
 enum BreakLocatorType {
   ALL_BREAK_LOCATIONS = 0,
-  SOURCE_BREAK_LOCATIONS = 1
+  SOURCE_BREAK_LOCATIONS = 1,
+  CALLS_AND_RETURNS = 2,
 };
 
 
@@ -385,7 +387,8 @@ class Debug {
                               BreakPositionAlignment alignment);
   void ClearBreakPoint(Handle<Object> break_point_object);
   void ClearAllBreakPoints();
-  void FloodWithOneShot(Handle<JSFunction> function);
+  void FloodWithOneShot(Handle<JSFunction> function,
+                        BreakLocatorType type = ALL_BREAK_LOCATIONS);
   void FloodBoundFunctionWithOneShot(Handle<JSFunction> function);
   void FloodHandlerWithOneShot();
   void ChangeBreakOnException(ExceptionBreakType type, bool enable);
@@ -401,10 +404,8 @@ class Debug {
   bool StepNextContinue(BreakLocationIterator* break_location_iterator,
                         JavaScriptFrame* frame);
   bool StepInActive() { return thread_local_.step_into_fp_ != 0; }
-  void HandleStepIn(Handle<JSFunction> function,
-                    Handle<Object> holder,
-                    Address fp,
-                    bool is_constructor);
+  void HandleStepIn(Handle<Object> function_obj, Handle<Object> holder,
+                    Address fp, bool is_constructor);
   bool StepOutActive() { return thread_local_.step_out_fp_ != 0; }
 
   // Purge all code objects that have no debug break slots.
@@ -623,7 +624,7 @@ class Debug {
     // Number of steps left to perform before debug event.
     int step_count_;
 
-    // Frame pointer from last step next action.
+    // Frame pointer from last step next or step frame action.
     Address last_fp_;
 
     // Number of queued steps left to perform before debug event.
index 9933e9c..7c2a89f 100644 (file)
@@ -542,9 +542,8 @@ MaybeHandle<Object> Object::GetPropertyWithDefinedGetter(
   Debug* debug = isolate->debug();
   // Handle stepping into a getter if step into is active.
   // TODO(rossberg): should this apply to getters that are function proxies?
-  if (debug->StepInActive() && getter->IsJSFunction()) {
-    debug->HandleStepIn(
-        Handle<JSFunction>::cast(getter), Handle<Object>::null(), 0, false);
+  if (debug->is_active()) {
+    debug->HandleStepIn(getter, Handle<Object>::null(), 0, false);
   }
 
   return Execution::Call(isolate, getter, receiver, 0, NULL, true);
@@ -560,9 +559,8 @@ MaybeHandle<Object> Object::SetPropertyWithDefinedSetter(
   Debug* debug = isolate->debug();
   // Handle stepping into a setter if step into is active.
   // TODO(rossberg): should this apply to getters that are function proxies?
-  if (debug->StepInActive() && setter->IsJSFunction()) {
-    debug->HandleStepIn(
-        Handle<JSFunction>::cast(setter), Handle<Object>::null(), 0, false);
+  if (debug->is_active()) {
+    debug->HandleStepIn(setter, Handle<Object>::null(), 0, false);
   }
 
   Handle<Object> argv[] = { value };
index 2de372f..95ac77b 100644 (file)
@@ -1984,7 +1984,7 @@ RUNTIME_FUNCTION(Runtime_PrepareStep) {
   StepAction step_action = static_cast<StepAction>(NumberToInt32(args[1]));
   if (step_action != StepIn && step_action != StepNext &&
       step_action != StepOut && step_action != StepInMin &&
-      step_action != StepMin) {
+      step_action != StepMin && step_action != StepFrame) {
     return isolate->Throw(isolate->heap()->illegal_argument_string());
   }
 
diff --git a/test/mjsunit/debug-stepframe.js b/test/mjsunit/debug-stepframe.js
new file mode 100644 (file)
index 0000000..8f4ee4c
--- /dev/null
@@ -0,0 +1,111 @@
+// Copyright 2014 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Flags: --expose-debug-as debug
+
+function f0() {
+  var v00 = 0;              // Break 1
+  var v01 = 1;
+  // Normal function call in a catch scope.
+  try {
+    throw 1;
+  } catch (e) {
+    try{
+      f1();
+    } catch (e) {
+      var v02 = 2;          // Break 13
+    }
+  }
+  var v03 = 3;
+  var v04 = 4;
+}
+
+function f1() {
+  var v10 = 0;              // Break 2
+  var v11 = 1;
+  // Getter call.
+  var v12 = o.get;
+  var v13 = 3               // Break 4
+  // Setter call.
+  o.set = 2;
+  var v14 = 4;              // Break 6
+  // Function.prototype.call.
+  f2.call();
+  var v15 = 5;              // Break 12
+  var v16 = 6;
+  // Exit function by throw.
+  throw 1;
+  var v17 = 7;
+}
+
+function get() {
+  var g0 = 0;               // Break 3
+  var g1 = 1;
+  return 3;
+}
+
+function set() {
+  var s0 = 0;               // Break 5
+  return 3;
+}
+
+function f2() {
+  var v20 = 0;              // Break 7
+  // Construct call.
+  var v21 = new c0();
+  var v22 = 2;              // Break 9
+  // Bound function.
+  b0();
+  return 2;                 // Break 11
+}
+
+function c0() {
+  this.v0 = 0;              // Break 8
+  this.v1 = 1;
+}
+
+function f3() {
+  var v30 = 0;              // Break 10
+  var v31 = 1;
+  return 3;
+}
+
+var b0 = f3.bind(o);
+
+var o = {};
+Object.defineProperty(o, "get", { get : get });
+Object.defineProperty(o, "set", { set : set });
+
+Debug = debug.Debug;
+var break_count = 0
+var exception = null;
+var step_size;
+
+function listener(event, exec_state, event_data, data) {
+  if (event != Debug.DebugEvent.Break) return;
+  try {
+    var line = exec_state.frame(0).sourceLineText();
+    print(line);
+    var match = line.match(/\/\/ Break (\d+)$/);
+    assertEquals(2, match.length);
+    assertEquals(break_count, parseInt(match[1]));
+    break_count += step_size;
+    exec_state.prepareStep(Debug.StepAction.StepFrame, step_size);
+  } catch (e) {
+    print(e + e.stack);
+    exception = e;
+  }
+}
+
+for (step_size = 1; step_size < 6; step_size++) {
+  print("step size = " + step_size);
+  break_count = 0;
+  Debug.setListener(listener);
+  debugger;                 // Break 0
+  f0();
+  Debug.setListener(null);  // Break 14
+  assertTrue(break_count > 14);
+}
+
+assertNull(exception);