Introduce PromiseRejectCallback.

R=aandrey@chromium.org, yurys@chromium.org, rossberg@chromium.org
API=v8::Isolate::SetPromiseRejectCallback, v8::Promise::HasHandler
LOG=Y
BUG=v8:3093

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

git-svn-id: https://v8.googlecode.com/svn/branches/bleeding_edge@24335 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
This commit is contained in:
yangguo@chromium.org 2014-09-30 15:29:08 +00:00
parent 552a9c2447
commit e68e62c891
17 changed files with 594 additions and 137 deletions

View File

@ -85,6 +85,7 @@ class ObjectOperationDescriptor;
class ObjectTemplate;
class Platform;
class Primitive;
class Promise;
class RawOperationDescriptor;
class Script;
class Signature;
@ -2851,6 +2852,12 @@ class V8_EXPORT Promise : public Object {
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:
@ -4196,6 +4203,16 @@ typedef void (*MemoryAllocationCallback)(ObjectSpace space,
// --- 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);
@ -4771,6 +4788,13 @@ class V8_EXPORT Isolate {
*/
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.
@ -5880,7 +5904,7 @@ class Internals {
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.

View File

@ -5985,6 +5985,16 @@ Local<Promise> Promise::Then(Handle<Function> handler) {
}
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();
}
@ -6732,6 +6742,13 @@ void Isolate::RemoveCallCompletedCallback(CallCompletedCallback callback) {
}
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();
}

View File

@ -232,6 +232,8 @@ class Utils {
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(
@ -355,6 +357,7 @@ MAKE_TO_LOCAL(ToLocal, SignatureInfo, Signature)
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)

View File

@ -1538,6 +1538,8 @@ void Genesis::InstallNativeFunctions() {
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);

View File

@ -145,7 +145,6 @@ bool Linkage::NeedsFrameState(Runtime::FunctionId function) {
case Runtime::kDebugEvaluate:
case Runtime::kDebugGetLoadedScripts:
case Runtime::kDebugGetPropertyDetails:
case Runtime::kDebugPromiseRejectEvent:
case Runtime::kDebugPromiseEvent:
case Runtime::kDeleteProperty:
case Runtime::kDeoptimizeFunction:
@ -164,6 +163,8 @@ bool Linkage::NeedsFrameState(Runtime::FunctionId function) {
case Runtime::kParseJson:
case Runtime::kPrepareStep:
case Runtime::kPreventExtensions:
case Runtime::kPromiseRejectEvent:
case Runtime::kPromiseRevokeReject:
case Runtime::kRegExpCompile:
case Runtime::kRegExpExecMultiple:
case Runtime::kResolvePossiblyDirectEval:

View File

@ -180,6 +180,7 @@ enum BindingFlags {
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) \
@ -377,6 +378,7 @@ class Context: public FixedArray {
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,

View File

@ -1262,17 +1262,6 @@ bool Debug::IsBreakOnException(ExceptionBreakType type) {
}
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) {
@ -2521,14 +2510,37 @@ void Debug::OnThrow(Handle<Object> exception, bool uncaught) {
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) {

View File

@ -533,8 +533,8 @@ class Debug {
// 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,

View File

@ -2880,6 +2880,8 @@ void Heap::CreateInitialObjects() {
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);

View File

@ -189,6 +189,8 @@ namespace internal {
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)

View File

@ -978,6 +978,8 @@ bool Isolate::ShouldReportException(bool* can_be_caught_externally,
}
// 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;
@ -1000,6 +1002,96 @@ bool Isolate::IsErrorObject(Handle<Object> obj) {
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());
@ -1014,7 +1106,6 @@ void Isolate::DoThrow(Object* exception, MessageLocation* location) {
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;
@ -1032,52 +1123,15 @@ void Isolate::DoThrow(Object* exception, MessageLocation* location) {
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();
@ -1089,56 +1143,14 @@ void Isolate::DoThrow(Object* exception, MessageLocation* location) {
// 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
}
}
@ -1314,8 +1326,6 @@ Handle<Object> Isolate::GetPromiseOnStackOnThrow() {
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();
@ -2278,6 +2288,20 @@ void Isolate::FireCallCompletedCallback() {
}
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);

View File

@ -385,6 +385,7 @@ typedef List<HeapObject*> DebugObjectCache;
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) \
@ -1102,6 +1103,10 @@ class Isolate {
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();
@ -1216,6 +1221,9 @@ class Isolate {
// 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);

View File

@ -19,6 +19,7 @@ var PromiseChain;
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.
@ -30,9 +31,10 @@ var promiseValue = GLOBAL_PRIVATE("Promise#value");
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) {
@ -159,11 +161,12 @@ var lastMicrotaskId = 0;
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)
}
@ -199,12 +202,17 @@ var lastMicrotaskId = 0;
}
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.
@ -227,11 +235,18 @@ var lastMicrotaskId = 0;
+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 });
}
@ -325,22 +340,24 @@ var lastMicrotaskId = 0;
// 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);
};
// -------------------------------------------------------------------

View File

@ -1183,16 +1183,6 @@ RUNTIME_FUNCTION(Runtime_TransitionElementsKind) {
}
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);
@ -1872,6 +1862,42 @@ RUNTIME_FUNCTION(Runtime_ThrowUnsupportedSuperError) {
}
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);

View File

@ -80,8 +80,10 @@ namespace internal {
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) \

View File

@ -17618,6 +17618,249 @@ TEST(RethrowBogusErrorStackTrace) {
}
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());

View File

@ -7518,3 +7518,75 @@ TEST(DebugBreakOffThreadTerminate) {
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);
}