Allow stepping into Promise handlers.
authoraandrey <aandrey@chromium.org>
Tue, 18 Nov 2014 09:50:12 +0000 (01:50 -0800)
committerCommit bot <commit-bot@chromium.org>
Tue, 18 Nov 2014 09:50:32 +0000 (09:50 +0000)
BUG=chromium:432468
R=yangguo@chromium.org, adamk@chromium.org, arv@chromium.org
LOG=N

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

Cr-Commit-Position: refs/heads/master@{#25389}

src/debug.cc
src/macros.py
src/promise.js
test/mjsunit/es6/debug-stepin-promises.js [new file with mode: 0644]

index 3029adb..f5c2cbb 100644 (file)
@@ -1199,6 +1199,9 @@ void Debug::ClearAllBreakPoints() {
 
 void Debug::FloodWithOneShot(Handle<JSFunction> function,
                              BreakLocatorType type) {
+  // Do not ever break in native functions.
+  if (function->IsFromNativeScript()) return;
+
   PrepareForBreakPoints();
 
   // Make sure the function is compiled and has set up the debug info.
@@ -1249,7 +1252,7 @@ void Debug::FloodWithOneShotGeneric(Handle<JSFunction> function,
     FloodBoundFunctionWithOneShot(function);
   } else if (function->shared()->is_default_constructor()) {
     FloodDefaultConstructorWithOneShot(function);
-  } else if (!function->IsFromNativeScript()) {
+  } else {
     Isolate* isolate = function->GetIsolate();
     // Don't allow step into functions in the native context.
     if (function->shared()->code() ==
index d8741f7..e8572e6 100644 (file)
@@ -291,3 +291,5 @@ const ITERATOR_KIND_ENTRIES = 3;
 
 # Check whether debug is active.
 const DEBUG_IS_ACTIVE = (%_DebugIsActive() != 0);
+macro DEBUG_IS_STEPPING(function) = (%_DebugIsActive() != 0 && %DebugCallbackSupportsStepping(function));
+macro DEBUG_PREPARE_STEP_IN_IF_STEPPING(function) = if (DEBUG_IS_STEPPING(function)) %DebugPrepareStepInIfStepping(function);
index 443a3b8..7d5be88 100644 (file)
@@ -103,6 +103,7 @@ var lastMicrotaskId = 0;
   function PromiseHandle(value, handler, deferred) {
     try {
       %DebugPushPromise(deferred.promise);
+      DEBUG_PREPARE_STEP_IN_IF_STEPPING(handler);
       var result = handler(value);
       if (result === deferred.promise)
         throw MakeTypeError('promise_cyclic', [result]);
@@ -270,8 +271,15 @@ var lastMicrotaskId = 0;
       this,
       function(x) {
         x = PromiseCoerce(constructor, x);
-        return x === that ? onReject(MakeTypeError('promise_cyclic', [x])) :
-               IsPromise(x) ? x.then(onResolve, onReject) : onResolve(x);
+        if (x === that) {
+          DEBUG_PREPARE_STEP_IN_IF_STEPPING(onReject);
+          return onReject(MakeTypeError('promise_cyclic', [x]));
+        } else if (IsPromise(x)) {
+          return x.then(onResolve, onReject);
+        } else {
+          DEBUG_PREPARE_STEP_IN_IF_STEPPING(onResolve);
+          return onResolve(x);
+        }
       },
       onReject,
       PromiseChain
diff --git a/test/mjsunit/es6/debug-stepin-promises.js b/test/mjsunit/es6/debug-stepin-promises.js
new file mode 100644 (file)
index 0000000..2db6b66
--- /dev/null
@@ -0,0 +1,80 @@
+// 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: --allow-natives-syntax --expose-debug-as debug
+
+Debug = debug.Debug
+var exception = null;
+var break_count = 0;
+var expected_breaks = 7;
+
+function listener(event, exec_state, event_data, data) {
+  try {
+    if (event == Debug.DebugEvent.Break) {
+      assertTrue(exec_state.frameCount() != 0, "FAIL: Empty stack trace");
+      var source = exec_state.frame(0).sourceLineText();
+      print("paused at: " + source);
+      assertTrue(source.indexOf("// Break " + break_count + ".") > 0,
+                 "Unexpected pause at: " + source);
+      if (source.indexOf("StepOver.") !== -1) {
+        exec_state.prepareStep(Debug.StepAction.StepNext, 1);
+      } else {
+        exec_state.prepareStep(Debug.StepAction.StepIn, 1);
+      }
+      ++break_count;
+    } else if (event == Debug.DebugEvent.AsyncTaskEvent &&
+               event_data.type() === "willHandle" &&
+               event_data.name() !== "Object.observe" &&
+               break_count > 0) {
+      exec_state.prepareStep(Debug.StepAction.StepIn, 1);
+    }
+  } catch (e) {
+    exception = e;
+    print(e, e.stack);
+  }
+};
+
+Debug.setListener(listener);
+
+Promise.resolve(42)
+  .then(promise1)
+  .then(Object) // Should skip stepping into native.
+  .then(Boolean) // Should skip stepping into native.
+  .then(promise2)
+  .then(undefined, promise3)
+  .catch(function(e) {
+    %AbortJS("FAIL: uncaught exception " + e);
+  });
+
+function promise1()
+{
+  debugger; // Break 0.
+  return exception || 1; // Break 1.
+} // Break 2.
+
+function promise2()
+{
+  throw new Error; // Break 3.
+}
+
+function promise3()
+{
+  finalize(); // Break 4. StepOver.
+  return break_count; // Break 5.
+} // Break 6.
+
+function finalize()
+{
+  var dummy = {};
+  Object.observe(dummy, function() {
+    if (expected_breaks !== break_count) {
+      %AbortJS("FAIL: expected <" + expected_breaks + "> breaks instead of <" +
+               break_count + ">");
+    }
+    if (exception !== null) {
+      %AbortJS("FAIL: exception: " + exception);
+    }
+  });
+  dummy.foo = 1;
+}