Revision: 20956
Author: [email protected]
Date: Fri Apr 25 07:03:05 2014 UTC
Log: Trigger debug event on not yet caught exception in promises.
[email protected], [email protected], [email protected]
BUG=v8:3093
LOG=Y
Review URL: https://codereview.chromium.org/249503002
http://code.google.com/p/v8/source/detail?r=20956
Added:
/branches/bleeding_edge/test/mjsunit/es6/debug-promises-caught-all.js
/branches/bleeding_edge/test/mjsunit/es6/debug-promises-caught-uncaught.js
/branches/bleeding_edge/test/mjsunit/es6/debug-promises-throw-in-reject.js
/branches/bleeding_edge/test/mjsunit/es6/debug-promises-uncaught-all.js
/branches/bleeding_edge/test/mjsunit/es6/debug-promises-uncaught-uncaught.js
/branches/bleeding_edge/test/mjsunit/es6/debug-promises-undefined-reject.js
Modified:
/branches/bleeding_edge/include/v8-debug.h
/branches/bleeding_edge/src/debug-debugger.js
/branches/bleeding_edge/src/debug.cc
/branches/bleeding_edge/src/debug.h
/branches/bleeding_edge/src/execution.cc
/branches/bleeding_edge/src/isolate.cc
/branches/bleeding_edge/src/promise.js
/branches/bleeding_edge/src/runtime.cc
/branches/bleeding_edge/src/runtime.h
=======================================
--- /dev/null
+++ /branches/bleeding_edge/test/mjsunit/es6/debug-promises-caught-all.js
Fri Apr 25 07:03:05 2014 UTC
@@ -0,0 +1,56 @@
+// 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: --harmony-promises --expose-debug-as debug
+
+// Test debug events when we listen to all exceptions and
+// there is a catch handler for the exception thrown in a Promise.
+// Expectation:
+// - only the normal Exception debug event is triggered.
+
+Debug = debug.Debug;
+
+var log = [];
+var step = 0;
+
+var p = new Promise(function(resolve, reject) {
+ log.push("resolve");
+ resolve();
+});
+
+var q = p.chain(
+ function() {
+ log.push("throw");
+ throw new Error("caught");
+ });
+
+q.catch(
+ function(e) {
+ assertEquals("caught", e.message);
+ });
+
+function listener(event, exec_state, event_data, data) {
+ try {
+ // Ignore exceptions during startup in stress runs.
+ if (step >= 1) return;
+ assertEquals(["resolve", "end main", "throw"], log);
+ assertTrue(event != Debug.DebugEvent.PendingExceptionInPromise);
+ if (event == Debug.DebugEvent.Exception) {
+ assertEquals("caught", event_data.exception().message);
+ assertEquals(undefined, event_data.promise());
+ step++;
+ }
+ } catch (e) {
+ // Signal a failure with exit code 1. This is necessary since the
+ // debugger swallows exceptions and we expect the chained function
+ // and this listener to be executed after the main script is finished.
+ print("Unexpected exception: " + e + "\n" + e.stack);
+ quit(1);
+ }
+}
+
+Debug.setBreakOnException();
+Debug.setListener(listener);
+
+log.push("end main");
=======================================
--- /dev/null
+++
/branches/bleeding_edge/test/mjsunit/es6/debug-promises-caught-uncaught.js
Fri Apr 25 07:03:05 2014 UTC
@@ -0,0 +1,42 @@
+// 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: --harmony-promises --expose-debug-as debug
+
+// Test debug events when we only listen to uncaught exceptions and
+// there is a catch handler for the exception thrown in a Promise.
+// Expectation:
+// - no debug event is triggered.
+
+Debug = debug.Debug;
+
+var p = new Promise(function(resolve, reject) {
+ resolve();
+});
+
+var q = p.chain(
+ function() {
+ throw new Error("caught");
+ });
+
+q.catch(
+ function(e) {
+ assertEquals("caught", e.message);
+ });
+
+function listener(event, exec_state, event_data, data) {
+ try {
+ assertTrue(event != Debug.DebugEvent.Exception);
+ assertTrue(event != Debug.DebugEvent.PendingExceptionInPromise);
+ } catch (e) {
+ // Signal a failure with exit code 1. This is necessary since the
+ // debugger swallows exceptions and we expect the chained function
+ // and this listener to be executed after the main script is finished.
+ print("Unexpected exception: " + e + "\n" + e.stack);
+ quit(1);
+ }
+}
+
+Debug.setBreakOnUncaughtException();
+Debug.setListener(listener);
=======================================
--- /dev/null
+++
/branches/bleeding_edge/test/mjsunit/es6/debug-promises-throw-in-reject.js
Fri Apr 25 07:03:05 2014 UTC
@@ -0,0 +1,61 @@
+// 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: --harmony-promises --expose-debug-as debug
+
+// Test debug events when an exception is thrown inside a Promise, which is
+// caught by a custom promise, which throws a new exception in its reject
+// handler. We expect a PendingExceptionInPromise event to be triggered.
+
+Debug = debug.Debug;
+
+var log = [];
+var step = 0;
+
+var p = new Promise(function(resolve, reject) {
+ log.push("resolve");
+ resolve();
+});
+
+function MyPromise(resolver) {
+ var reject = function() {
+ log.push("throw reject");
+ throw new Error("reject");
+ };
+ var resolve = function() { };
+ log.push("construct");
+ resolver(resolve, reject);
+};
+
+MyPromise.prototype = p;
+p.constructor = MyPromise;
+
+var q = p.chain(
+ function() {
+ log.push("throw caught");
+ throw new Error("caught");
+ });
+
+function listener(event, exec_state, event_data, data) {
+ try {
+ if (event == Debug.DebugEvent.PendingExceptionInPromise) {
+ assertEquals(["resolve", "construct", "end main",
+ "throw caught", "throw reject"], log);
+ assertEquals("caught", event_data.exception().message);
+ } else if (event == Debug.DebugEvent.Exception) {
+ assertUnreachable();
+ }
+ } catch (e) {
+ // Signal a failure with exit code 1. This is necessary since the
+ // debugger swallows exceptions and we expect the chained function
+ // and this listener to be executed after the main script is finished.
+ print("Unexpected exception: " + e + "\n" + e.stack);
+ quit(1);
+ }
+}
+
+Debug.setBreakOnUncaughtException();
+Debug.setListener(listener);
+
+log.push("end main");
=======================================
--- /dev/null
+++ /branches/bleeding_edge/test/mjsunit/es6/debug-promises-uncaught-all.js
Fri Apr 25 07:03:05 2014 UTC
@@ -0,0 +1,62 @@
+// 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: --harmony-promises --expose-debug-as debug
+
+// Test debug events when we listen to all exceptions and
+// there is a catch handler for the exception thrown in a Promise.
+// Expectation:
+// - the normal Exception debug event is triggered.
+// - the PendingExceptionInPromise debug event is triggered afterwards,
+// with the same exception object.
+
+Debug = debug.Debug;
+
+var log = [];
+var step = 0;
+var exception = undefined;
+
+var p = new Promise(function(resolve, reject) {
+ log.push("resolve");
+ resolve();
+});
+
+var q = p.chain(
+ function() {
+ log.push("throw");
+ throw new Error("uncaught");
+ });
+
+function listener(event, exec_state, event_data, data) {
+ try {
+ // Ignore exceptions during startup in stress runs.
+ if (step > 1) return;
+ assertEquals(["resolve", "end main", "throw"], log);
+ if (event == Debug.DebugEvent.Exception) {
+ assertEquals(0, step);
+ exception = event_data.exception();
+ assertEquals(undefined, event_data.promise());
+ } else if (event == Debug.DebugEvent.PendingExceptionInPromise) {
+ assertEquals(1, step);
+ assertEquals(exception, event_data.exception());
+ assertEquals("uncaught", exception.message);
+ assertTrue(event_data.promise() instanceof Promise);
+ assertTrue(event_data.uncaught());
+ } else {
+ return;
+ }
+ step++;
+ } catch (e) {
+ // Signal a failure with exit code 1. This is necessary since the
+ // debugger swallows exceptions and we expect the chained function
+ // and this listener to be executed after the main script is finished.
+ print("Unexpected exception: " + e + "\n" + e.stack);
+ quit(1);
+ }
+}
+
+Debug.setBreakOnException();
+Debug.setListener(listener);
+
+log.push("end main");
=======================================
--- /dev/null
+++
/branches/bleeding_edge/test/mjsunit/es6/debug-promises-uncaught-uncaught.js
Fri Apr 25 07:03:05 2014 UTC
@@ -0,0 +1,54 @@
+// 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: --harmony-promises --expose-debug-as debug
+
+// Test debug events when we only listen to uncaught exceptions and
+// there is a catch handler for the exception thrown in a Promise.
+// Expectation:
+// - only the PendingExceptionInPromise debug event is triggered.
+
+Debug = debug.Debug;
+
+var log = [];
+var step = 0;
+
+var p = new Promise(function(resolve, reject) {
+ log.push("resolve");
+ resolve();
+});
+
+var q = p.chain(
+ function() {
+ log.push("throw");
+ throw new Error("uncaught");
+ });
+
+function listener(event, exec_state, event_data, data) {
+ try {
+ // Ignore exceptions during startup in stress runs.
+ if (step >= 1) return;
+ assertEquals(["resolve", "end main", "throw"], log);
+ if (event == Debug.DebugEvent.Exception) {
+ assertUnreachable();
+ } else if (event == Debug.DebugEvent.PendingExceptionInPromise) {
+ assertEquals(0, step);
+ assertEquals("uncaught", event_data.exception().message);
+ assertTrue(event_data.promise() instanceof Promise);
+ assertTrue(event_data.uncaught());
+ step++;
+ }
+ } catch (e) {
+ // Signal a failure with exit code 1. This is necessary since the
+ // debugger swallows exceptions and we expect the chained function
+ // and this listener to be executed after the main script is finished.
+ print("Unexpected exception: " + e + "\n" + e.stack);
+ quit(1);
+ }
+}
+
+Debug.setBreakOnUncaughtException();
+Debug.setListener(listener);
+
+log.push("end main");
=======================================
--- /dev/null
+++
/branches/bleeding_edge/test/mjsunit/es6/debug-promises-undefined-reject.js
Fri Apr 25 07:03:05 2014 UTC
@@ -0,0 +1,57 @@
+// 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: --harmony-promises --expose-debug-as debug
+
+// Test debug events when an exception is thrown inside a Promise, which is
+// caught by a custom promise, which has no reject handler.
+// We expect a PendingExceptionInPromise event to be triggered.
+
+Debug = debug.Debug;
+
+var log = [];
+var step = 0;
+
+var p = new Promise(function(resolve, reject) {
+ log.push("resolve");
+ resolve();
+});
+
+function MyPromise(resolver) {
+ var reject = undefined;
+ var resolve = function() { };
+ log.push("construct");
+ resolver(resolve, reject);
+};
+
+MyPromise.prototype = p;
+p.constructor = MyPromise;
+
+var q = p.chain(
+ function() {
+ log.push("throw caught");
+ throw new Error("caught");
+ });
+
+function listener(event, exec_state, event_data, data) {
+ try {
+ if (event == Debug.DebugEvent.PendingExceptionInPromise) {
+ assertEquals(["resolve", "construct", "end main", "throw caught"],
log);
+ assertEquals("caught", event_data.exception().message);
+ } else if (event == Debug.DebugEvent.Exception) {
+ assertUnreachable();
+ }
+ } catch (e) {
+ // Signal a failure with exit code 1. This is necessary since the
+ // debugger swallows exceptions and we expect the chained function
+ // and this listener to be executed after the main script is finished.
+ print("Unexpected exception: " + e + "\n" + e.stack);
+ quit(1);
+ }
+}
+
+Debug.setBreakOnUncaughtException();
+Debug.setListener(listener);
+
+log.push("end main");
=======================================
--- /branches/bleeding_edge/include/v8-debug.h Tue Apr 15 07:47:33 2014 UTC
+++ /branches/bleeding_edge/include/v8-debug.h Fri Apr 25 07:03:05 2014 UTC
@@ -43,7 +43,8 @@
BeforeCompile = 4,
AfterCompile = 5,
ScriptCollected = 6,
- BreakForCommand = 7
+ UncaughtExceptionInPromise = 7,
+ BreakForCommand = 8
};
=======================================
--- /branches/bleeding_edge/src/debug-debugger.js Fri Apr 11 10:41:09 2014
UTC
+++ /branches/bleeding_edge/src/debug-debugger.js Fri Apr 25 07:03:05 2014
UTC
@@ -42,7 +42,8 @@
NewFunction: 3,
BeforeCompile: 4,
AfterCompile: 5,
- ScriptCollected: 6 };
+ ScriptCollected: 6,
+ PendingExceptionInPromise: 7 };
// Types of exceptions that can be broken upon.
Debug.ExceptionBreak = { Caught : 0,
@@ -1093,15 +1094,16 @@
};
-function MakeExceptionEvent(exec_state, exception, uncaught) {
- return new ExceptionEvent(exec_state, exception, uncaught);
+function MakeExceptionEvent(exec_state, exception, uncaught, promise) {
+ return new ExceptionEvent(exec_state, exception, uncaught, promise);
}
-function ExceptionEvent(exec_state, exception, uncaught) {
+function ExceptionEvent(exec_state, exception, uncaught, promise) {
this.exec_state_ = exec_state;
this.exception_ = exception;
this.uncaught_ = uncaught;
+ this.promise_ = promise;
}
@@ -1125,6 +1127,11 @@
};
+ExceptionEvent.prototype.promise = function() {
+ return this.promise_;
+};
+
+
ExceptionEvent.prototype.func = function() {
return this.exec_state_.frame(0).func();
};
=======================================
--- /branches/bleeding_edge/src/debug.cc Tue Apr 22 14:55:47 2014 UTC
+++ /branches/bleeding_edge/src/debug.cc Fri Apr 25 07:03:05 2014 UTC
@@ -2628,13 +2628,15 @@
MaybeHandle<Object> Debugger::MakeExceptionEvent(Handle<Object> exception,
- bool uncaught) {
+ bool uncaught,
+ Handle<Object> promise) {
Handle<Object> exec_state;
if (!MakeExecutionState().ToHandle(&exec_state)) return
MaybeHandle<Object>();
// Create the new exception event object.
Handle<Object> argv[] = { exec_state,
exception,
- isolate_->factory()->ToBoolean(uncaught) };
+ isolate_->factory()->ToBoolean(uncaught),
+ promise };
return MakeJSObject(CStrVector("MakeExceptionEvent"), ARRAY_SIZE(argv),
argv);
}
@@ -2664,7 +2666,9 @@
}
-void Debugger::OnException(Handle<Object> exception, bool uncaught) {
+void Debugger::OnException(Handle<Object> exception,
+ bool uncaught,
+ Handle<Object> promise) {
HandleScope scope(isolate_);
Debug* debug = isolate_->debug();
@@ -2688,13 +2692,21 @@
// Clear all current stepping setup.
debug->ClearStepping();
+
+ // Determine event;
+ DebugEvent event = promise->IsUndefined()
+ ? v8::Exception : v8::UncaughtExceptionInPromise;
+
// Create the event data object.
Handle<Object> event_data;
// Bail out and don't call debugger if exception.
- if (!MakeExceptionEvent(exception, uncaught).ToHandle(&event_data))
return;
+ if (!MakeExceptionEvent(
+ exception, uncaught, promise).ToHandle(&event_data)) {
+ return;
+ }
// Process debug event.
- ProcessDebugEvent(v8::Exception, Handle<JSObject>::cast(event_data),
false);
+ ProcessDebugEvent(event, Handle<JSObject>::cast(event_data), false);
// Return to continue execution from where the exception was thrown.
}
=======================================
--- /branches/bleeding_edge/src/debug.h Tue Apr 22 12:50:58 2014 UTC
+++ /branches/bleeding_edge/src/debug.h Fri Apr 25 07:03:05 2014 UTC
@@ -793,13 +793,17 @@
MUST_USE_RESULT MaybeHandle<Object> MakeBreakEvent(
Handle<Object> break_points_hit);
MUST_USE_RESULT MaybeHandle<Object> MakeExceptionEvent(
- Handle<Object> exception, bool uncaught);
+ Handle<Object> exception,
+ bool uncaught,
+ Handle<Object> promise);
MUST_USE_RESULT MaybeHandle<Object> MakeCompileEvent(
Handle<Script> script, bool before);
MUST_USE_RESULT MaybeHandle<Object> MakeScriptCollectedEvent(int id);
void OnDebugBreak(Handle<Object> break_points_hit, bool auto_continue);
- void OnException(Handle<Object> exception, bool uncaught);
+ void OnException(Handle<Object> exception,
+ bool uncaught,
+ Handle<Object> promise = Handle<Object>::null());
void OnBeforeCompile(Handle<Script> script);
enum AfterCompileFlags {
=======================================
--- /branches/bleeding_edge/src/execution.cc Wed Apr 23 15:08:03 2014 UTC
+++ /branches/bleeding_edge/src/execution.cc Fri Apr 25 07:03:05 2014 UTC
@@ -316,7 +316,7 @@
isolate->run_microtasks(),
isolate->factory()->undefined_value(),
0,
- NULL).Assert();
+ NULL).Check();
}
@@ -327,7 +327,7 @@
isolate->enqueue_external_microtask(),
isolate->factory()->undefined_value(),
1,
- args).Assert();
+ args).Check();
}
=======================================
--- /branches/bleeding_edge/src/isolate.cc Tue Apr 22 12:50:58 2014 UTC
+++ /branches/bleeding_edge/src/isolate.cc Fri Apr 25 07:03:05 2014 UTC
@@ -1055,7 +1055,8 @@
#ifdef ENABLE_DEBUGGER_SUPPORT
// Notify debugger of exception.
if (catchable_by_javascript) {
- debugger_->OnException(exception_handle, report_exception);
+ debugger_->OnException(
+ exception_handle, report_exception, factory()->undefined_value());
}
#endif
=======================================
--- /branches/bleeding_edge/src/promise.js Mon Apr 14 11:24:40 2014 UTC
+++ /branches/bleeding_edge/src/promise.js Fri Apr 25 07:03:05 2014 UTC
@@ -191,9 +191,22 @@
%_CallFunction(result, deferred.resolve, deferred.reject,
PromiseChain);
else
deferred.resolve(result);
- } catch(e) {
- // TODO(rossberg): perhaps log uncaught exceptions below.
- try { deferred.reject(e) } catch(e) {}
+ } catch (exception) {
+ var uncaught = false;
+ var reject_queue = GET_PRIVATE(deferred.promise, promiseOnReject);
+ if (reject_queue && reject_queue.length == 0) {
+ // The deferred promise may get a reject handler attached later.
+ // For now, we consider the exception to be (for the moment)
uncaught.
+ uncaught = true;
+ }
+ try {
+ deferred.reject(exception);
+ } catch (e) {
+ // The reject handler can only throw for a custom deferred promise.
+ // We consider the original exception to be uncaught.
+ uncaught = true;
+ }
+ if (uncaught) %DebugPendingExceptionInPromise(exception,
deferred.promise);
}
}
=======================================
--- /branches/bleeding_edge/src/runtime.cc Thu Apr 24 15:16:26 2014 UTC
+++ /branches/bleeding_edge/src/runtime.cc Fri Apr 25 07:03:05 2014 UTC
@@ -5625,7 +5625,6 @@
// 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(Runtime_DebugCallbackSupportsStepping) {
- SealHandleScope shs(isolate);
#ifdef ENABLE_DEBUGGER_SUPPORT
ASSERT(args.length() == 1);
if (!isolate->IsDebuggerActive() || !isolate->debug()->StepInActive()) {
@@ -5644,7 +5643,6 @@
// 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(Runtime_DebugPrepareStepInIfStepping) {
- SealHandleScope shs(isolate);
#ifdef ENABLE_DEBUGGER_SUPPORT
ASSERT(args.length() == 1);
Debug* debug = isolate->debug();
@@ -5659,6 +5657,19 @@
#endif // ENABLE_DEBUGGER_SUPPORT
return isolate->heap()->undefined_value();
}
+
+
+// Notify the debugger if an expcetion in a promise is not caught (yet).
+RUNTIME_FUNCTION(Runtime_DebugPendingExceptionInPromise) {
+#ifdef ENABLE_DEBUGGER_SUPPORT
+ ASSERT(args.length() == 2);
+ HandleScope scope(isolate);
+ CONVERT_ARG_HANDLE_CHECKED(Object, exception, 0);
+ CONVERT_ARG_HANDLE_CHECKED(JSObject, promise, 1);
+ isolate->debugger()->OnException(exception, true, promise);
+#endif // ENABLE_DEBUGGER_SUPPORT
+ return isolate->heap()->undefined_value();
+}
// Set a local property, even if it is READ_ONLY. If the property does not
=======================================
--- /branches/bleeding_edge/src/runtime.h Tue Apr 22 12:50:58 2014 UTC
+++ /branches/bleeding_edge/src/runtime.h Fri Apr 25 07:03:05 2014 UTC
@@ -99,6 +99,7 @@
F(StoreArrayLiteralElement, 5, 1) \
F(DebugCallbackSupportsStepping, 1, 1) \
F(DebugPrepareStepInIfStepping, 1, 1) \
+ F(DebugPendingExceptionInPromise, 2, 1) \
F(FlattenString, 1, 1) \
F(LoadMutableDouble, 2, 1) \
F(TryMigrateInstance, 1, 1) \
--
--
v8-dev mailing list
[email protected]
http://groups.google.com/group/v8-dev
---
You received this message because you are subscribed to the Google Groups "v8-dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
For more options, visit https://groups.google.com/d/optout.