[promisehook] Check for JSReceiver in runtime function

PromiseHooks can be called with a proxy which fails the cast and
crashes. This patch changes the runtime functions to
explicitly check for a JSPromise.

This has the side effect of removing the existing broken support for
catch prediction for non native promises.

Bug: v8:7398, v8:7190
Change-Id: I66dbe5f9935943a91afb7ee14919bd9248f9f7e4
Reviewed-on: https://chromium-review.googlesource.com/907677
Reviewed-by: Aleksey Kozyatinskiy <kozyatinskiy@chromium.org>
Reviewed-by: Jakob Gruber <jgruber@chromium.org>
Commit-Queue: Sathya Gunasekaran <gsathya@chromium.org>
Cr-Commit-Position: refs/heads/master@{#51182}
This commit is contained in:
Sathya Gunasekaran 2018-02-07 17:31:38 -08:00 committed by Commit Bot
parent 3916401e4b
commit 46488f71bb
4 changed files with 75 additions and 27 deletions

View File

@ -136,7 +136,9 @@ RUNTIME_FUNCTION(Runtime_PromiseHookInit) {
RUNTIME_FUNCTION(Runtime_PromiseHookResolve) {
HandleScope scope(isolate);
DCHECK_EQ(1, args.length());
CONVERT_ARG_HANDLE_CHECKED(JSPromise, promise, 0);
CONVERT_ARG_HANDLE_CHECKED(JSReceiver, maybe_promise, 0);
if (!maybe_promise->IsJSPromise()) return isolate->heap()->undefined_value();
Handle<JSPromise> promise = Handle<JSPromise>::cast(maybe_promise);
isolate->RunPromiseHook(PromiseHookType::kResolve, promise,
isolate->factory()->undefined_value());
return isolate->heap()->undefined_value();
@ -145,11 +147,12 @@ RUNTIME_FUNCTION(Runtime_PromiseHookResolve) {
RUNTIME_FUNCTION(Runtime_PromiseHookBefore) {
HandleScope scope(isolate);
DCHECK_EQ(1, args.length());
CONVERT_ARG_HANDLE_CHECKED(JSObject, promise, 0);
CONVERT_ARG_HANDLE_CHECKED(JSReceiver, maybe_promise, 0);
if (!maybe_promise->IsJSPromise()) return isolate->heap()->undefined_value();
Handle<JSPromise> promise = Handle<JSPromise>::cast(maybe_promise);
if (isolate->debug()->is_active()) isolate->PushPromise(promise);
if (promise->IsJSPromise()) {
isolate->RunPromiseHook(PromiseHookType::kBefore,
Handle<JSPromise>::cast(promise),
isolate->RunPromiseHook(PromiseHookType::kBefore, promise,
isolate->factory()->undefined_value());
}
return isolate->heap()->undefined_value();
@ -158,11 +161,12 @@ RUNTIME_FUNCTION(Runtime_PromiseHookBefore) {
RUNTIME_FUNCTION(Runtime_PromiseHookAfter) {
HandleScope scope(isolate);
DCHECK_EQ(1, args.length());
CONVERT_ARG_HANDLE_CHECKED(JSObject, promise, 0);
CONVERT_ARG_HANDLE_CHECKED(JSReceiver, maybe_promise, 0);
if (!maybe_promise->IsJSPromise()) return isolate->heap()->undefined_value();
Handle<JSPromise> promise = Handle<JSPromise>::cast(maybe_promise);
if (isolate->debug()->is_active()) isolate->PopPromise();
if (promise->IsJSPromise()) {
isolate->RunPromiseHook(PromiseHookType::kAfter,
Handle<JSPromise>::cast(promise),
isolate->RunPromiseHook(PromiseHookType::kAfter, promise,
isolate->factory()->undefined_value());
}
return isolate->heap()->undefined_value();

View File

@ -17963,6 +17963,21 @@ TEST(PromiseHook) {
// 6) resolve hook (p1)
CHECK_EQ(6, promise_hook_data->promise_hook_count);
promise_hook_data->Reset();
source =
"class X extends Promise {\n"
" static get [Symbol.species]() {\n"
" return Y;\n"
" }\n"
"}\n"
"class Y {\n"
" constructor(executor) {\n"
" return new Proxy(new Promise(executor), {});\n"
" }\n"
"}\n"
"var x = X.resolve().then(() => {});\n";
CompileRun(source);
delete promise_hook_data;
isolate->SetPromiseHook(nullptr);
}

View File

@ -0,0 +1,42 @@
// Copyright 2018 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.
// Test debug events when we listen to all exceptions and
// there is a catch handler for the exception thrown in a Promise.
// We expect a normal Exception debug event to be triggered.
Debug = debug.Debug;
var expected_events = 1;
var log = [];
class P extends Promise {
constructor(...args) {
super(...args);
return new Proxy(this, {
get(target, property, receiver) {
if (property in target) {
return Reflect.get(target, property, receiver);
} else {
return (...args) =>
new Promise((resolve, reject) =>
target.then(v => resolve(v[property](...args)))
.catch(reject)
);
}
}
});
}
}
P.resolve({doStuff(){log.push(1)}}).doStuff()
function listener(event, exec_state, event_data, data) {}
Debug.setBreakOnUncaughtException();
Debug.setListener(listener);
%RunMicrotasks();

View File

@ -3,15 +3,13 @@
// found in the LICENSE file.
// 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 two Exception debug events:
// 1) when the exception is thrown in the promise q.
// 2) when the custom reject closure in MyPromise throws an exception.
// 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 no Exception debug events.
Debug = debug.Debug;
var expected_events = 1;
var expected_events = 0;
var log = [];
var p = new Promise(function(resolve, reject) {
@ -21,11 +19,9 @@ var p = new Promise(function(resolve, reject) {
function MyPromise(resolver) {
var reject = function() {
log.push("throw in reject");
throw new Error("reject"); // event
};
var resolve = function() { };
log.push("construct");
resolver(resolve, reject);
};
@ -42,16 +38,7 @@ var q = p.then(
function listener(event, exec_state, event_data, data) {
try {
if (event == Debug.DebugEvent.Exception) {
expected_events--;
assertTrue(expected_events >= 0);
if (expected_events == 0) {
assertEquals(["resolve", "construct", "end main",
"throw caught"], log);
assertEquals("caught", event_data.exception().message);
} else {
assertUnreachable();
}
assertTrue(exec_state.frame(0).sourceLineText().indexOf('// event') > 0);
assertUnreachable();
}
} catch (e) {
%AbortJS(e + "\n" + e.stack);
@ -68,8 +55,8 @@ function testDone(iteration) {
try {
assertTrue(iteration < 10);
if (expected_events === 0) {
assertEquals(["resolve", "construct", "end main",
"throw caught", "throw in reject"], log);
assertEquals(["resolve", "end main",
"throw caught"], log);
} else {
testDone(iteration + 1);
}