class ObjectTemplate;
class Platform;
class Primitive;
+class Promise;
class RawOperationDescriptor;
class Script;
class Signature;
Local<Promise> Catch(Handle<Function> handler);
Local<Promise> Then(Handle<Function> handler);
+ /**
+ * Returns true if the promise has at least one derived promise, and
+ * therefore resolve/reject handlers (including default handler).
+ */
+ bool HasHandler();
+
V8_INLINE static Promise* Cast(Value* obj);
private:
// --- Leave Script Callback ---
typedef void (*CallCompletedCallback)();
+// --- Promise Reject Callback ---
+enum PromiseRejectEvent {
+ kPromiseRejectWithNoHandler = 0,
+ kPromiseHandlerAddedAfterReject = 1
+};
+
+typedef void (*PromiseRejectCallback)(Handle<Promise> promise,
+ Handle<Value> value,
+ PromiseRejectEvent event);
+
// --- Microtask Callback ---
typedef void (*MicrotaskCallback)(void* data);
*/
void RemoveCallCompletedCallback(CallCompletedCallback callback);
+
+ /**
+ * Set callback to notify about promise reject with no handler, or
+ * revocation of such a previous notification once the handler is added.
+ */
+ void SetPromiseRejectCallback(PromiseRejectCallback callback);
+
/**
* Experimental: Runs the Microtask Work Queue until empty
* Any exceptions thrown by microtask callbacks are swallowed.
static const int kNullValueRootIndex = 7;
static const int kTrueValueRootIndex = 8;
static const int kFalseValueRootIndex = 9;
- static const int kEmptyStringRootIndex = 164;
+ static const int kEmptyStringRootIndex = 166;
// The external allocation limit should be below 256 MB on all architectures
// to avoid that resource-constrained embedders run low on memory.
}
+bool Promise::HasHandler() {
+ i::Handle<i::JSObject> promise = Utils::OpenHandle(this);
+ i::Isolate* isolate = promise->GetIsolate();
+ LOG_API(isolate, "Promise::HasRejectHandler");
+ ENTER_V8(isolate);
+ i::Handle<i::Symbol> key = isolate->factory()->promise_has_handler_symbol();
+ return i::JSObject::GetDataProperty(promise, key)->IsTrue();
+}
+
+
bool v8::ArrayBuffer::IsExternal() const {
return Utils::OpenHandle(this)->is_external();
}
}
+void Isolate::SetPromiseRejectCallback(PromiseRejectCallback callback) {
+ if (callback == NULL) return;
+ i::Isolate* isolate = reinterpret_cast<i::Isolate*>(this);
+ isolate->SetPromiseRejectCallback(callback);
+}
+
+
void Isolate::RunMicrotasks() {
reinterpret_cast<i::Isolate*>(this)->RunMicrotasks();
}
static inline Local<Message> MessageToLocal(
v8::internal::Handle<v8::internal::Object> obj);
+ static inline Local<Promise> PromiseToLocal(
+ v8::internal::Handle<v8::internal::JSObject> obj);
static inline Local<StackTrace> StackTraceToLocal(
v8::internal::Handle<v8::internal::JSArray> obj);
static inline Local<StackFrame> StackFrameToLocal(
MAKE_TO_LOCAL(AccessorSignatureToLocal, FunctionTemplateInfo, AccessorSignature)
MAKE_TO_LOCAL(ToLocal, TypeSwitchInfo, TypeSwitch)
MAKE_TO_LOCAL(MessageToLocal, Object, Message)
+MAKE_TO_LOCAL(PromiseToLocal, JSObject, Promise)
MAKE_TO_LOCAL(StackTraceToLocal, JSArray, StackTrace)
MAKE_TO_LOCAL(StackFrameToLocal, JSObject, StackFrame)
MAKE_TO_LOCAL(NumberToLocal, Object, Number)
INSTALL_NATIVE(JSFunction, "PromiseChain", promise_chain);
INSTALL_NATIVE(JSFunction, "PromiseCatch", promise_catch);
INSTALL_NATIVE(JSFunction, "PromiseThen", promise_then);
+ INSTALL_NATIVE(JSFunction, "PromiseHasRejectHandler",
+ promise_has_reject_handler);
INSTALL_NATIVE(JSFunction, "NotifyChange", observers_notify_change);
INSTALL_NATIVE(JSFunction, "EnqueueSpliceRecord", observers_enqueue_splice);
case Runtime::kDebugEvaluate:
case Runtime::kDebugGetLoadedScripts:
case Runtime::kDebugGetPropertyDetails:
- case Runtime::kDebugPromiseRejectEvent:
case Runtime::kDebugPromiseEvent:
case Runtime::kDeleteProperty:
case Runtime::kDeoptimizeFunction:
case Runtime::kParseJson:
case Runtime::kPrepareStep:
case Runtime::kPreventExtensions:
+ case Runtime::kPromiseRejectEvent:
+ case Runtime::kPromiseRevokeReject:
case Runtime::kRegExpCompile:
case Runtime::kRegExpExecMultiple:
case Runtime::kResolvePossiblyDirectEval:
V(PROMISE_CHAIN_INDEX, JSFunction, promise_chain) \
V(PROMISE_CATCH_INDEX, JSFunction, promise_catch) \
V(PROMISE_THEN_INDEX, JSFunction, promise_then) \
+ V(PROMISE_HAS_REJECT_HANDLER_INDEX, JSFunction, promise_has_reject_handler) \
V(TO_COMPLETE_PROPERTY_DESCRIPTOR_INDEX, JSFunction, \
to_complete_property_descriptor) \
V(DERIVED_HAS_TRAP_INDEX, JSFunction, derived_has_trap) \
PROMISE_CHAIN_INDEX,
PROMISE_CATCH_INDEX,
PROMISE_THEN_INDEX,
+ PROMISE_HAS_REJECT_HANDLER_INDEX,
TO_COMPLETE_PROPERTY_DESCRIPTOR_INDEX,
DERIVED_HAS_TRAP_INDEX,
DERIVED_GET_TRAP_INDEX,
}
-bool Debug::PromiseHasRejectHandler(Handle<JSObject> promise) {
- Handle<JSFunction> fun = Handle<JSFunction>::cast(
- JSObject::GetDataProperty(isolate_->js_builtins_object(),
- isolate_->factory()->NewStringFromStaticChars(
- "PromiseHasRejectHandler")));
- Handle<Object> result =
- Execution::Call(isolate_, fun, promise, 0, NULL).ToHandleChecked();
- return result->IsTrue();
-}
-
-
void Debug::PrepareStep(StepAction step_action,
int step_count,
StackFrame::Id frame_id) {
void Debug::OnPromiseReject(Handle<JSObject> promise, Handle<Object> value) {
if (in_debug_scope() || ignore_events()) return;
HandleScope scope(isolate_);
- OnException(value, false, promise);
+ // Check whether the promise has been marked as having triggered a message.
+ Handle<Symbol> key = isolate_->factory()->promise_debug_marker_symbol();
+ if (JSObject::GetDataProperty(promise, key)->IsUndefined()) {
+ OnException(value, false, promise);
+ }
+}
+
+
+MaybeHandle<Object> Debug::PromiseHasUserDefinedRejectHandler(
+ Handle<JSObject> promise) {
+ Handle<JSFunction> fun = Handle<JSFunction>::cast(
+ JSObject::GetDataProperty(isolate_->js_builtins_object(),
+ isolate_->factory()->NewStringFromStaticChars(
+ "PromiseHasUserDefinedRejectHandler")));
+ return Execution::Call(isolate_, fun, promise, 0, NULL);
}
void Debug::OnException(Handle<Object> exception, bool uncaught,
Handle<Object> promise) {
- if (promise->IsJSObject()) {
- uncaught |= !PromiseHasRejectHandler(Handle<JSObject>::cast(promise));
+ if (!uncaught && promise->IsJSObject()) {
+ Handle<JSObject> jspromise = Handle<JSObject>::cast(promise);
+ // Mark the promise as already having triggered a message.
+ Handle<Symbol> key = isolate_->factory()->promise_debug_marker_symbol();
+ JSObject::SetProperty(jspromise, key, key, STRICT).Assert();
+ // Check whether the promise reject is considered an uncaught exception.
+ Handle<Object> has_reject_handler;
+ ASSIGN_RETURN_ON_EXCEPTION_VALUE(
+ isolate_, has_reject_handler,
+ PromiseHasUserDefinedRejectHandler(jspromise), /* void */);
+ uncaught = has_reject_handler->IsFalse();
}
// Bail out if exception breaks are not active
if (uncaught) {
// Mirror cache handling.
void ClearMirrorCache();
- // Returns a promise if the pushed try-catch handler matches the current one.
- bool PromiseHasRejectHandler(Handle<JSObject> promise);
+ MaybeHandle<Object> PromiseHasUserDefinedRejectHandler(
+ Handle<JSObject> promise);
void CallEventCallback(v8::DebugEvent event,
Handle<Object> exec_state,
set_stack_trace_symbol(*factory->NewPrivateOwnSymbol());
set_uninitialized_symbol(*factory->NewPrivateOwnSymbol());
set_home_object_symbol(*factory->NewPrivateOwnSymbol());
+ set_promise_debug_marker_symbol(*factory->NewPrivateOwnSymbol());
+ set_promise_has_handler_symbol(*factory->NewPrivateOwnSymbol());
Handle<SeededNumberDictionary> slow_element_dictionary =
SeededNumberDictionary::New(isolate(), 0, TENURED);
V(Symbol, detailed_stack_trace_symbol, DetailedStackTraceSymbol) \
V(Symbol, normal_ic_symbol, NormalICSymbol) \
V(Symbol, home_object_symbol, HomeObjectSymbol) \
+ V(Symbol, promise_debug_marker_symbol, PromiseDebugMarkerSymbol) \
+ V(Symbol, promise_has_handler_symbol, PromiseHasHandlerSymbol) \
V(FixedArray, materialized_objects, MaterializedObjects) \
V(FixedArray, allocation_sites_scratchpad, AllocationSitesScratchpad) \
V(FixedArray, microtask_queue, MicrotaskQueue)
}
+// Traverse prototype chain to find out whether the object is derived from
+// the Error object.
bool Isolate::IsErrorObject(Handle<Object> obj) {
if (!obj->IsJSObject()) return false;
static int fatal_exception_depth = 0;
+
+Handle<JSMessageObject> Isolate::CreateMessage(Handle<Object> exception,
+ MessageLocation* location) {
+ Handle<JSArray> stack_trace_object;
+ if (capture_stack_trace_for_uncaught_exceptions_) {
+ if (IsErrorObject(exception)) {
+ // We fetch the stack trace that corresponds to this error object.
+ Handle<Name> key = factory()->detailed_stack_trace_symbol();
+ // Look up as own property. If the lookup fails, the exception is
+ // probably not a valid Error object. In that case, we fall through
+ // and capture the stack trace at this throw site.
+ LookupIterator lookup(exception, key,
+ LookupIterator::OWN_SKIP_INTERCEPTOR);
+ Handle<Object> stack_trace_property;
+ if (Object::GetProperty(&lookup).ToHandle(&stack_trace_property) &&
+ stack_trace_property->IsJSArray()) {
+ stack_trace_object = Handle<JSArray>::cast(stack_trace_property);
+ }
+ }
+ if (stack_trace_object.is_null()) {
+ // Not an error object, we capture at throw site.
+ stack_trace_object = CaptureCurrentStackTrace(
+ stack_trace_for_uncaught_exceptions_frame_limit_,
+ stack_trace_for_uncaught_exceptions_options_);
+ }
+ }
+
+ // If the exception argument is a custom object, turn it into a string
+ // before throwing as uncaught exception. Note that the pending
+ // exception object to be set later must not be turned into a string.
+ if (exception->IsJSObject() && !IsErrorObject(exception)) {
+ MaybeHandle<Object> maybe_exception =
+ Execution::ToDetailString(this, exception);
+ if (!maybe_exception.ToHandle(&exception)) {
+ exception =
+ factory()->InternalizeOneByteString(STATIC_CHAR_VECTOR("exception"));
+ }
+ }
+ return MessageHandler::MakeMessageObject(this, "uncaught_exception", location,
+ HandleVector<Object>(&exception, 1),
+ stack_trace_object);
+}
+
+
+void ReportBootstrappingException(Handle<Object> exception,
+ MessageLocation* location) {
+ base::OS::PrintError("Exception thrown during bootstrapping\n");
+ if (location == NULL || location->script().is_null()) return;
+ // We are bootstrapping and caught an error where the location is set
+ // and we have a script for the location.
+ // In this case we could have an extension (or an internal error
+ // somewhere) and we print out the line number at which the error occured
+ // to the console for easier debugging.
+ int line_number =
+ location->script()->GetLineNumber(location->start_pos()) + 1;
+ if (exception->IsString() && location->script()->name()->IsString()) {
+ base::OS::PrintError(
+ "Extension or internal compilation error: %s in %s at line %d.\n",
+ String::cast(*exception)->ToCString().get(),
+ String::cast(location->script()->name())->ToCString().get(),
+ line_number);
+ } else if (location->script()->name()->IsString()) {
+ base::OS::PrintError(
+ "Extension or internal compilation error in %s at line %d.\n",
+ String::cast(location->script()->name())->ToCString().get(),
+ line_number);
+ } else {
+ base::OS::PrintError("Extension or internal compilation error.\n");
+ }
+#ifdef OBJECT_PRINT
+ // Since comments and empty lines have been stripped from the source of
+ // builtins, print the actual source here so that line numbers match.
+ if (location->script()->source()->IsString()) {
+ Handle<String> src(String::cast(location->script()->source()));
+ PrintF("Failing script:\n");
+ int len = src->length();
+ int line_number = 1;
+ PrintF("%5d: ", line_number);
+ for (int i = 0; i < len; i++) {
+ uint16_t character = src->Get(i);
+ PrintF("%c", character);
+ if (character == '\n' && i < len - 2) {
+ PrintF("%5d: ", ++line_number);
+ }
+ }
+ }
+#endif
+}
+
+
void Isolate::DoThrow(Object* exception, MessageLocation* location) {
DCHECK(!has_pending_exception());
bool report_exception = catchable_by_javascript && should_report_exception;
bool try_catch_needs_message =
can_be_caught_externally && try_catch_handler()->capture_message_;
- bool bootstrapping = bootstrapper()->IsActive();
bool rethrowing_message = thread_local_top()->rethrowing_message_;
thread_local_top()->rethrowing_message_ = false;
ComputeLocation(&potential_computed_location);
location = &potential_computed_location;
}
- // It's not safe to try to make message objects or collect stack traces
- // while the bootstrapper is active since the infrastructure may not have
- // been properly initialized.
- if (!bootstrapping) {
- Handle<JSArray> stack_trace_object;
- if (capture_stack_trace_for_uncaught_exceptions_) {
- if (IsErrorObject(exception_handle)) {
- // We fetch the stack trace that corresponds to this error object.
- Handle<Name> key = factory()->detailed_stack_trace_symbol();
- // Look up as own property. If the lookup fails, the exception is
- // probably not a valid Error object. In that case, we fall through
- // and capture the stack trace at this throw site.
- LookupIterator lookup(exception_handle, key,
- LookupIterator::OWN_SKIP_INTERCEPTOR);
- Handle<Object> stack_trace_property;
- if (Object::GetProperty(&lookup).ToHandle(&stack_trace_property) &&
- stack_trace_property->IsJSArray()) {
- stack_trace_object = Handle<JSArray>::cast(stack_trace_property);
- }
- }
- if (stack_trace_object.is_null()) {
- // Not an error object, we capture at throw site.
- stack_trace_object = CaptureCurrentStackTrace(
- stack_trace_for_uncaught_exceptions_frame_limit_,
- stack_trace_for_uncaught_exceptions_options_);
- }
- }
- Handle<Object> exception_arg = exception_handle;
- // If the exception argument is a custom object, turn it into a string
- // before throwing as uncaught exception. Note that the pending
- // exception object to be set later must not be turned into a string.
- if (exception_arg->IsJSObject() && !IsErrorObject(exception_arg)) {
- MaybeHandle<Object> maybe_exception =
- Execution::ToDetailString(this, exception_arg);
- if (!maybe_exception.ToHandle(&exception_arg)) {
- exception_arg = factory()->InternalizeOneByteString(
- STATIC_CHAR_VECTOR("exception"));
- }
- }
- Handle<Object> message_obj = MessageHandler::MakeMessageObject(
- this,
- "uncaught_exception",
- location,
- HandleVector<Object>(&exception_arg, 1),
- stack_trace_object);
+ if (bootstrapper()->IsActive()) {
+ // It's not safe to try to make message objects or collect stack traces
+ // while the bootstrapper is active since the infrastructure may not have
+ // been properly initialized.
+ ReportBootstrappingException(exception_handle, location);
+ } else {
+ Handle<Object> message_obj = CreateMessage(exception_handle, location);
+
thread_local_top()->pending_message_obj_ = *message_obj;
if (location != NULL) {
thread_local_top()->pending_message_script_ = *location->script();
// exception not caught by JavaScript, even when an external handler is
// present. This flag is intended for use by JavaScript developers, so
// print a user-friendly stack trace (not an internal one).
- if (fatal_exception_depth == 0 &&
- FLAG_abort_on_uncaught_exception &&
+ if (fatal_exception_depth == 0 && FLAG_abort_on_uncaught_exception &&
(report_exception || can_be_caught_externally)) {
fatal_exception_depth++;
- PrintF(stderr,
- "%s\n\nFROM\n",
+ PrintF(stderr, "%s\n\nFROM\n",
MessageHandler::GetLocalizedMessage(this, message_obj).get());
PrintCurrentStackTrace(stderr);
base::OS::Abort();
}
- } else if (location != NULL && !location->script().is_null()) {
- // We are bootstrapping and caught an error where the location is set
- // and we have a script for the location.
- // In this case we could have an extension (or an internal error
- // somewhere) and we print out the line number at which the error occured
- // to the console for easier debugging.
- int line_number =
- location->script()->GetLineNumber(location->start_pos()) + 1;
- if (exception->IsString() && location->script()->name()->IsString()) {
- base::OS::PrintError(
- "Extension or internal compilation error: %s in %s at line %d.\n",
- String::cast(exception)->ToCString().get(),
- String::cast(location->script()->name())->ToCString().get(),
- line_number);
- } else if (location->script()->name()->IsString()) {
- base::OS::PrintError(
- "Extension or internal compilation error in %s at line %d.\n",
- String::cast(location->script()->name())->ToCString().get(),
- line_number);
- } else {
- base::OS::PrintError("Extension or internal compilation error.\n");
- }
-#ifdef OBJECT_PRINT
- // Since comments and empty lines have been stripped from the source of
- // builtins, print the actual source here so that line numbers match.
- if (location->script()->source()->IsString()) {
- Handle<String> src(String::cast(location->script()->source()));
- PrintF("Failing script:\n");
- int len = src->length();
- int line_number = 1;
- PrintF("%5d: ", line_number);
- for (int i = 0; i < len; i++) {
- uint16_t character = src->Get(i);
- PrintF("%c", character);
- if (character == '\n' && i < len - 2) {
- PrintF("%5d: ", ++line_number);
- }
- }
- }
-#endif
}
}
StackHandler* handler = StackHandler::FromAddress(Isolate::handler(tltop));
do {
if (handler == promise_try) {
- // Mark the pushed try-catch handler to prevent a later duplicate event
- // triggered with the following reject.
return tltop->promise_on_stack_->promise();
}
handler = handler->next();
}
+void Isolate::SetPromiseRejectCallback(PromiseRejectCallback callback) {
+ promise_reject_callback_ = callback;
+}
+
+
+void Isolate::ReportPromiseReject(Handle<JSObject> promise,
+ Handle<Object> value,
+ v8::PromiseRejectEvent event) {
+ if (promise_reject_callback_ == NULL) return;
+ promise_reject_callback_(v8::Utils::PromiseToLocal(promise),
+ v8::Utils::ToLocal(value), event);
+}
+
+
void Isolate::EnqueueMicrotask(Handle<Object> microtask) {
DCHECK(microtask->IsJSFunction() || microtask->IsCallHandlerInfo());
Handle<FixedArray> queue(heap()->microtask_queue(), this);
V(uint32_t, per_isolate_assert_data, 0xFFFFFFFFu) \
V(InterruptCallback, api_interrupt_callback, NULL) \
V(void*, api_interrupt_callback_data, NULL) \
+ V(PromiseRejectCallback, promise_reject_callback, NULL) \
ISOLATE_INIT_SIMULATOR_LIST(V)
#define THREAD_LOCAL_TOP_ACCESSOR(type, name) \
void RemoveCallCompletedCallback(CallCompletedCallback callback);
void FireCallCompletedCallback();
+ void SetPromiseRejectCallback(PromiseRejectCallback callback);
+ void ReportPromiseReject(Handle<JSObject> promise, Handle<Object> value,
+ v8::PromiseRejectEvent event);
+
void EnqueueMicrotask(Handle<Object> microtask);
void RunMicrotasks();
// then return true.
bool PropagatePendingExceptionToExternalTryCatch();
+ Handle<JSMessageObject> CreateMessage(Handle<Object> exception,
+ MessageLocation* location);
+
// Traverse prototype chain to find out whether the object is derived from
// the Error object.
bool IsErrorObject(Handle<Object> obj);
var PromiseCatch;
var PromiseThen;
var PromiseHasRejectHandler;
+var PromiseHasUserDefinedRejectHandler;
// mirror-debugger.js currently uses builtins.promiseStatus. It would be nice
// if we could move these property names into the closure below.
var promiseOnResolve = GLOBAL_PRIVATE("Promise#onResolve");
var promiseOnReject = GLOBAL_PRIVATE("Promise#onReject");
var promiseRaw = GLOBAL_PRIVATE("Promise#raw");
-var promiseDebug = GLOBAL_PRIVATE("Promise#debug");
+var promiseHasHandler = %PromiseHasHandlerSymbol();
var lastMicrotaskId = 0;
+
(function() {
var $Promise = function Promise(resolver) {
PromiseReject = function PromiseReject(promise, r) {
// Check promise status to confirm that this reject has an effect.
- // Check promiseDebug property to avoid duplicate event.
- if (DEBUG_IS_ACTIVE &&
- GET_PRIVATE(promise, promiseStatus) == 0 &&
- !HAS_DEFINED_PRIVATE(promise, promiseDebug)) {
- %DebugPromiseRejectEvent(promise, r);
+ // Call runtime for callbacks to the debugger or for unhandled reject.
+ if (GET_PRIVATE(promise, promiseStatus) == 0) {
+ var debug_is_active = DEBUG_IS_ACTIVE;
+ if (debug_is_active || !HAS_DEFINED_PRIVATE(promise, promiseHasHandler)) {
+ %PromiseRejectEvent(promise, r, debug_is_active);
+ }
}
PromiseDone(promise, -1, r, promiseOnReject)
}
}
function PromiseRejected(r) {
+ var promise;
if (this === $Promise) {
// Optimized case, avoid extra closure.
- return PromiseSet(new $Promise(promiseRaw), -1, r);
+ promise = PromiseSet(new $Promise(promiseRaw), -1, r);
+ // The debug event for this would always be an uncaught promise reject,
+ // which is usually simply noise. Do not trigger that debug event.
+ %PromiseRejectEvent(promise, r, false);
} else {
- return new this(function(resolve, reject) { reject(r) });
+ promise = new this(function(resolve, reject) { reject(r) });
}
+ return promise;
}
// Simple chaining.
+1);
break;
case -1: // Rejected
+ if (!HAS_DEFINED_PRIVATE(this, promiseHasHandler)) {
+ // Promise has already been rejected, but had no handler.
+ // Revoke previously triggered reject event.
+ %PromiseRevokeReject(this);
+ }
PromiseEnqueue(GET_PRIVATE(this, promiseValue),
[onReject, deferred],
-1);
break;
}
+ // Mark this promise as having handler.
+ SET_PRIVATE(this, promiseHasHandler, true);
if (DEBUG_IS_ACTIVE) {
%DebugPromiseEvent({ promise: deferred.promise, parentPromise: this });
}
// Utility for debugger
- function PromiseHasRejectHandlerRecursive(promise) {
+ function PromiseHasUserDefinedRejectHandlerRecursive(promise) {
var queue = GET_PRIVATE(promise, promiseOnReject);
if (IS_UNDEFINED(queue)) return false;
- // Do a depth first search for a reject handler that's not
- // the default PromiseIdRejectHandler.
for (var i = 0; i < queue.length; i += 2) {
if (queue[i] != PromiseIdRejectHandler) return true;
- if (PromiseHasRejectHandlerRecursive(queue[i + 1].promise)) return true;
+ if (PromiseHasUserDefinedRejectHandlerRecursive(queue[i + 1].promise)) {
+ return true;
+ }
}
return false;
}
- PromiseHasRejectHandler = function PromiseHasRejectHandler() {
- // Mark promise as already having triggered a reject event.
- SET_PRIVATE(this, promiseDebug, true);
- return PromiseHasRejectHandlerRecursive(this);
+ // Return whether the promise will be handled by a user-defined reject
+ // handler somewhere down the promise chain. For this, we do a depth-first
+ // search for a reject handler that's not the default PromiseIdRejectHandler.
+ PromiseHasUserDefinedRejectHandler =
+ function PromiseHasUserDefinedRejectHandler() {
+ return PromiseHasUserDefinedRejectHandlerRecursive(this);
};
// -------------------------------------------------------------------
}
-RUNTIME_FUNCTION(Runtime_DebugPromiseRejectEvent) {
- DCHECK(args.length() == 2);
- HandleScope scope(isolate);
- CONVERT_ARG_HANDLE_CHECKED(JSObject, promise, 0);
- CONVERT_ARG_HANDLE_CHECKED(Object, value, 1);
- isolate->debug()->OnPromiseReject(promise, value);
- return isolate->heap()->undefined_value();
-}
-
-
RUNTIME_FUNCTION(Runtime_DeleteProperty) {
HandleScope scope(isolate);
DCHECK(args.length() == 3);
}
+RUNTIME_FUNCTION(Runtime_PromiseRejectEvent) {
+ DCHECK(args.length() == 3);
+ HandleScope scope(isolate);
+ CONVERT_ARG_HANDLE_CHECKED(JSObject, promise, 0);
+ CONVERT_ARG_HANDLE_CHECKED(Object, value, 1);
+ CONVERT_BOOLEAN_ARG_CHECKED(debug_event, 2);
+ if (debug_event) isolate->debug()->OnPromiseReject(promise, value);
+ Handle<Symbol> key = isolate->factory()->promise_has_handler_symbol();
+ // Do not report if we actually have a handler.
+ if (JSObject::GetDataProperty(promise, key)->IsUndefined()) {
+ isolate->ReportPromiseReject(promise, value,
+ v8::kPromiseRejectWithNoHandler);
+ }
+ return isolate->heap()->undefined_value();
+}
+
+
+RUNTIME_FUNCTION(Runtime_PromiseRevokeReject) {
+ DCHECK(args.length() == 1);
+ HandleScope scope(isolate);
+ CONVERT_ARG_HANDLE_CHECKED(JSObject, promise, 0);
+ Handle<Symbol> key = isolate->factory()->promise_has_handler_symbol();
+ // At this point, no revocation has been issued before
+ RUNTIME_ASSERT(JSObject::GetDataProperty(promise, key)->IsUndefined());
+ isolate->ReportPromiseReject(promise, Handle<Object>(),
+ v8::kPromiseHandlerAddedAfterReject);
+ return isolate->heap()->undefined_value();
+}
+
+
+RUNTIME_FUNCTION(Runtime_PromiseHasHandlerSymbol) {
+ DCHECK(args.length() == 0);
+ return isolate->heap()->promise_has_handler_symbol();
+}
+
+
RUNTIME_FUNCTION(Runtime_StackGuard) {
SealHandleScope shs(isolate);
DCHECK(args.length() == 0);
F(DebugPushPromise, 1, 1) \
F(DebugPopPromise, 0, 1) \
F(DebugPromiseEvent, 1, 1) \
- F(DebugPromiseRejectEvent, 2, 1) \
F(DebugAsyncTaskEvent, 1, 1) \
+ F(PromiseRejectEvent, 3, 1) \
+ F(PromiseRevokeReject, 1, 1) \
+ F(PromiseHasHandlerSymbol, 0, 1) \
F(FlattenString, 1, 1) \
F(LoadMutableDouble, 2, 1) \
F(TryMigrateInstance, 1, 1) \
}
+v8::PromiseRejectEvent reject_event = v8::kPromiseRejectWithNoHandler;
+int promise_reject_counter = 0;
+int promise_revoke_counter = 0;
+
+void PromiseRejectCallback(v8::Handle<v8::Promise> promise,
+ v8::Handle<v8::Value> value,
+ v8::PromiseRejectEvent event) {
+ if (event == v8::kPromiseRejectWithNoHandler) {
+ promise_reject_counter++;
+ CcTest::global()->Set(v8_str("rejected"), promise);
+ CcTest::global()->Set(v8_str("value"), value);
+ } else {
+ promise_revoke_counter++;
+ CcTest::global()->Set(v8_str("revoked"), promise);
+ CHECK(value.IsEmpty());
+ }
+}
+
+
+v8::Handle<v8::Promise> GetPromise(const char* name) {
+ return v8::Handle<v8::Promise>::Cast(CcTest::global()->Get(v8_str(name)));
+}
+
+
+v8::Handle<v8::Value> RejectValue() {
+ return CcTest::global()->Get(v8_str("value"));
+}
+
+
+void ResetPromiseStates() {
+ promise_reject_counter = 0;
+ promise_revoke_counter = 0;
+ CcTest::global()->Set(v8_str("rejected"), v8_str(""));
+ CcTest::global()->Set(v8_str("value"), v8_str(""));
+ CcTest::global()->Set(v8_str("revoked"), v8_str(""));
+}
+
+
+TEST(PromiseRejectCallback) {
+ LocalContext env;
+ v8::Isolate* isolate = env->GetIsolate();
+ v8::HandleScope scope(isolate);
+
+ isolate->SetPromiseRejectCallback(PromiseRejectCallback);
+
+ ResetPromiseStates();
+
+ // Create promise p0.
+ CompileRun(
+ "var reject; \n"
+ "var p0 = new Promise( \n"
+ " function(res, rej) { \n"
+ " reject = rej; \n"
+ " } \n"
+ "); \n");
+ CHECK(!GetPromise("p0")->HasHandler());
+ CHECK_EQ(0, promise_reject_counter);
+ CHECK_EQ(0, promise_revoke_counter);
+
+ // Add resolve handler (and default reject handler) to p0.
+ CompileRun("var p1 = p0.then(function(){});");
+ CHECK(GetPromise("p0")->HasHandler());
+ CHECK(!GetPromise("p1")->HasHandler());
+ CHECK_EQ(0, promise_reject_counter);
+ CHECK_EQ(0, promise_revoke_counter);
+
+ // Reject p0.
+ CompileRun("reject('ppp');");
+ CHECK(GetPromise("p0")->HasHandler());
+ CHECK(!GetPromise("p1")->HasHandler());
+ CHECK_EQ(1, promise_reject_counter);
+ CHECK_EQ(0, promise_revoke_counter);
+ CHECK_EQ(v8::kPromiseRejectWithNoHandler, reject_event);
+ CHECK(GetPromise("rejected")->Equals(GetPromise("p1")));
+ CHECK(RejectValue()->Equals(v8_str("ppp")));
+
+ // Reject p0 again. Callback is not triggered again.
+ CompileRun("reject();");
+ CHECK(GetPromise("p0")->HasHandler());
+ CHECK(!GetPromise("p1")->HasHandler());
+ CHECK_EQ(1, promise_reject_counter);
+ CHECK_EQ(0, promise_revoke_counter);
+
+ // Add resolve handler to p1.
+ CompileRun("var p2 = p1.then(function(){});");
+ CHECK(GetPromise("p0")->HasHandler());
+ CHECK(GetPromise("p1")->HasHandler());
+ CHECK(!GetPromise("p2")->HasHandler());
+ CHECK_EQ(2, promise_reject_counter);
+ CHECK_EQ(1, promise_revoke_counter);
+ CHECK(GetPromise("rejected")->Equals(GetPromise("p2")));
+ CHECK(RejectValue()->Equals(v8_str("ppp")));
+ CHECK(GetPromise("revoked")->Equals(GetPromise("p1")));
+
+ ResetPromiseStates();
+
+ // Create promise q0.
+ CompileRun(
+ "var q0 = new Promise( \n"
+ " function(res, rej) { \n"
+ " reject = rej; \n"
+ " } \n"
+ "); \n");
+ CHECK(!GetPromise("q0")->HasHandler());
+ CHECK_EQ(0, promise_reject_counter);
+ CHECK_EQ(0, promise_revoke_counter);
+
+ // Add reject handler to q0.
+ CompileRun("var q1 = q0.catch(function() {});");
+ CHECK(GetPromise("q0")->HasHandler());
+ CHECK(!GetPromise("q1")->HasHandler());
+ CHECK_EQ(0, promise_reject_counter);
+ CHECK_EQ(0, promise_revoke_counter);
+
+ // Reject q0.
+ CompileRun("reject('qq')");
+ CHECK(GetPromise("q0")->HasHandler());
+ CHECK(!GetPromise("q1")->HasHandler());
+ CHECK_EQ(0, promise_reject_counter);
+ CHECK_EQ(0, promise_revoke_counter);
+
+ // Add a new reject handler, which rejects by returning Promise.reject().
+ // The returned promise q_ triggers a reject callback at first, only to
+ // revoke it when returning it causes q2 to be rejected.
+ CompileRun(
+ "var q_;"
+ "var q2 = q0.catch( \n"
+ " function() { \n"
+ " q_ = Promise.reject('qqq'); \n"
+ " return q_; \n"
+ " } \n"
+ "); \n");
+ CHECK(GetPromise("q0")->HasHandler());
+ CHECK(!GetPromise("q1")->HasHandler());
+ CHECK(!GetPromise("q2")->HasHandler());
+ CHECK(GetPromise("q_")->HasHandler());
+ CHECK_EQ(2, promise_reject_counter);
+ CHECK_EQ(1, promise_revoke_counter);
+ CHECK(GetPromise("rejected")->Equals(GetPromise("q2")));
+ CHECK(GetPromise("revoked")->Equals(GetPromise("q_")));
+ CHECK(RejectValue()->Equals(v8_str("qqq")));
+
+ // Add a reject handler to the resolved q1, which rejects by throwing.
+ CompileRun(
+ "var q3 = q1.then( \n"
+ " function() { \n"
+ " throw 'qqqq'; \n"
+ " } \n"
+ "); \n");
+ CHECK(GetPromise("q0")->HasHandler());
+ CHECK(GetPromise("q1")->HasHandler());
+ CHECK(!GetPromise("q2")->HasHandler());
+ CHECK(!GetPromise("q3")->HasHandler());
+ CHECK_EQ(3, promise_reject_counter);
+ CHECK_EQ(1, promise_revoke_counter);
+ CHECK(GetPromise("rejected")->Equals(GetPromise("q3")));
+ CHECK(RejectValue()->Equals(v8_str("qqqq")));
+
+ ResetPromiseStates();
+
+ // Create promise r0, which has three handlers, two of which handle rejects.
+ CompileRun(
+ "var r0 = new Promise( \n"
+ " function(res, rej) { \n"
+ " reject = rej; \n"
+ " } \n"
+ "); \n"
+ "var r1 = r0.catch(function() {}); \n"
+ "var r2 = r0.then(function() {}); \n"
+ "var r3 = r0.then(function() {}, \n"
+ " function() {}); \n");
+ CHECK(GetPromise("r0")->HasHandler());
+ CHECK(!GetPromise("r1")->HasHandler());
+ CHECK(!GetPromise("r2")->HasHandler());
+ CHECK(!GetPromise("r3")->HasHandler());
+ CHECK_EQ(0, promise_reject_counter);
+ CHECK_EQ(0, promise_revoke_counter);
+
+ // Reject r0.
+ CompileRun("reject('rrr')");
+ CHECK(GetPromise("r0")->HasHandler());
+ CHECK(!GetPromise("r1")->HasHandler());
+ CHECK(!GetPromise("r2")->HasHandler());
+ CHECK(!GetPromise("r3")->HasHandler());
+ CHECK_EQ(1, promise_reject_counter);
+ CHECK_EQ(0, promise_revoke_counter);
+ CHECK(GetPromise("rejected")->Equals(GetPromise("r2")));
+ CHECK(RejectValue()->Equals(v8_str("rrr")));
+
+ // Add reject handler to r2.
+ CompileRun("var r4 = r2.catch(function() {});");
+ CHECK(GetPromise("r0")->HasHandler());
+ CHECK(!GetPromise("r1")->HasHandler());
+ CHECK(GetPromise("r2")->HasHandler());
+ CHECK(!GetPromise("r3")->HasHandler());
+ CHECK(!GetPromise("r4")->HasHandler());
+ CHECK_EQ(1, promise_reject_counter);
+ CHECK_EQ(1, promise_revoke_counter);
+ CHECK(GetPromise("revoked")->Equals(GetPromise("r2")));
+ CHECK(RejectValue()->Equals(v8_str("rrr")));
+
+ // Add reject handlers to r4.
+ CompileRun("var r5 = r4.then(function() {}, function() {});");
+ CHECK(GetPromise("r0")->HasHandler());
+ CHECK(!GetPromise("r1")->HasHandler());
+ CHECK(GetPromise("r2")->HasHandler());
+ CHECK(!GetPromise("r3")->HasHandler());
+ CHECK(GetPromise("r4")->HasHandler());
+ CHECK(!GetPromise("r5")->HasHandler());
+ CHECK_EQ(1, promise_reject_counter);
+ CHECK_EQ(1, promise_revoke_counter);
+
+ ResetPromiseStates();
+
+ // Create promise s0, which has three handlers, none of which handle rejects.
+ CompileRun(
+ "var s0 = new Promise( \n"
+ " function(res, rej) { \n"
+ " reject = rej; \n"
+ " } \n"
+ "); \n"
+ "var s1 = s0.then(function() {}); \n"
+ "var s2 = s0.then(function() {}); \n"
+ "var s3 = s0.then(function() {}); \n");
+ CHECK(GetPromise("s0")->HasHandler());
+ CHECK(!GetPromise("s1")->HasHandler());
+ CHECK(!GetPromise("s2")->HasHandler());
+ CHECK(!GetPromise("s3")->HasHandler());
+ CHECK_EQ(0, promise_reject_counter);
+ CHECK_EQ(0, promise_revoke_counter);
+
+ // Reject s0.
+ CompileRun("reject('sss')");
+ CHECK(GetPromise("s0")->HasHandler());
+ CHECK(!GetPromise("s1")->HasHandler());
+ CHECK(!GetPromise("s2")->HasHandler());
+ CHECK(!GetPromise("s3")->HasHandler());
+ CHECK_EQ(3, promise_reject_counter);
+ CHECK_EQ(0, promise_revoke_counter);
+ CHECK(RejectValue()->Equals(v8_str("sss")));
+}
+
+
void AnalyzeStackOfEvalWithSourceURL(
const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::HandleScope scope(args.GetIsolate());
CompileRun("while (true);");
CHECK(try_catch.HasTerminated());
}
+
+
+static void DebugEventExpectNoException(
+ const v8::Debug::EventDetails& event_details) {
+ v8::DebugEvent event = event_details.GetEvent();
+ CHECK_NE(v8::Exception, event);
+}
+
+
+static void TryCatchWrappedThrowCallback(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ v8::TryCatch try_catch;
+ CompileRun("throw 'rejection';");
+ CHECK(try_catch.HasCaught());
+}
+
+
+TEST(DebugPromiseInterceptedByTryCatch) {
+ DebugLocalContext env;
+ v8::Isolate* isolate = env->GetIsolate();
+ v8::HandleScope scope(isolate);
+ v8::Debug::SetDebugEventListener(&DebugEventExpectNoException);
+ ChangeBreakOnException(false, true);
+
+ v8::Handle<v8::FunctionTemplate> fun =
+ v8::FunctionTemplate::New(isolate, TryCatchWrappedThrowCallback);
+ env->Global()->Set(v8_str("fun"), fun->GetFunction());
+
+ CompileRun("var p = new Promise(function(res, rej) { fun(); res(); });");
+ CompileRun(
+ "var r;"
+ "p.chain(function() { r = 'resolved'; },"
+ " function() { r = 'rejected'; });");
+ CHECK(CompileRun("r")->Equals(v8_str("resolved")));
+}
+
+
+static int exception_event_counter = 0;
+
+
+static void DebugEventCountException(
+ const v8::Debug::EventDetails& event_details) {
+ v8::DebugEvent event = event_details.GetEvent();
+ if (event == v8::Exception) exception_event_counter++;
+}
+
+
+static void ThrowCallback(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CompileRun("throw 'rejection';");
+}
+
+
+TEST(DebugPromiseRejectedByCallback) {
+ DebugLocalContext env;
+ v8::Isolate* isolate = env->GetIsolate();
+ v8::HandleScope scope(isolate);
+ v8::Debug::SetDebugEventListener(&DebugEventCountException);
+ ChangeBreakOnException(false, true);
+ exception_event_counter = 0;
+
+ v8::Handle<v8::FunctionTemplate> fun =
+ v8::FunctionTemplate::New(isolate, ThrowCallback);
+ env->Global()->Set(v8_str("fun"), fun->GetFunction());
+
+ CompileRun("var p = new Promise(function(res, rej) { fun(); res(); });");
+ CompileRun(
+ "var r;"
+ "p.chain(function() { r = 'resolved'; },"
+ " function(e) { r = 'rejected' + e; });");
+ CHECK(CompileRun("r")->Equals(v8_str("rejectedrejection")));
+ CHECK_EQ(1, exception_event_counter);
+}