Commit 335a141b authored by Marja Hölttä's avatar Marja Hölttä Committed by Commit Bot

[Promise.any] Implement async stack traces for Promise.any

We can't attach a meaningful stack trace to the AggregateError
Promise.any rejects with, but we can augment the individual errors'
stack traces with Promise.any and the index of the corresponding
Promise in the input.

Bug: v8:9808
Change-Id: I7ba754c9b043594decaac8b3a23be74f05c3dffd
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2198983
Commit-Queue: Marja Hölttä <marja@chromium.org>
Reviewed-by: 's avatarBenedikt Meurer <bmeurer@chromium.org>
Reviewed-by: 's avatarDominik Inführ <dinfuehr@chromium.org>
Cr-Commit-Position: refs/heads/master@{#67778}
parent 10e8664b
......@@ -588,21 +588,28 @@ class FrameArrayBuilder {
offset, flags, parameters);
}
void AppendPromiseAllFrame(Handle<Context> context, int offset) {
void AppendPromiseCombinatorFrame(Handle<JSFunction> element_function,
Handle<JSFunction> combinator,
FrameArray::Flag combinator_flag,
Handle<Context> context) {
if (full()) return;
int flags = FrameArray::kIsAsync | FrameArray::kIsPromiseAll;
int flags = FrameArray::kIsAsync | combinator_flag;
Handle<Context> native_context(context->native_context(), isolate_);
Handle<JSFunction> function(native_context->promise_all(), isolate_);
if (!IsVisibleInStackTrace(function)) return;
if (!IsVisibleInStackTrace(combinator)) return;
Handle<Object> receiver(native_context->promise_function(), isolate_);
Handle<AbstractCode> code(AbstractCode::cast(function->code()), isolate_);
Handle<AbstractCode> code(AbstractCode::cast(combinator->code()), isolate_);
// TODO(mmarchini) save Promises list from Promise.all()
// TODO(mmarchini) save Promises list from the Promise combinator
Handle<FixedArray> parameters = isolate_->factory()->empty_fixed_array();
elements_ = FrameArray::AppendJSFrame(elements_, receiver, function, code,
// We store the offset of the promise into the element function's
// hash field for element callbacks.
int const offset =
Smi::ToInt(Smi::cast(element_function->GetIdentityHash())) - 1;
elements_ = FrameArray::AppendJSFrame(elements_, receiver, combinator, code,
offset, flags, parameters);
}
......@@ -861,11 +868,10 @@ void CaptureAsyncStackTrace(Isolate* isolate, Handle<JSPromise> promise,
Handle<JSFunction> function(JSFunction::cast(reaction->fulfill_handler()),
isolate);
Handle<Context> context(function->context(), isolate);
// We store the offset of the promise into the {function}'s
// hash field for promise resolve element callbacks.
int const offset = Smi::ToInt(Smi::cast(function->GetIdentityHash())) - 1;
builder->AppendPromiseAllFrame(context, offset);
Handle<JSFunction> combinator(context->native_context().promise_all(),
isolate);
builder->AppendPromiseCombinatorFrame(function, combinator,
FrameArray::kIsPromiseAll, context);
// Now peak into the Promise.all() resolve element context to
// find the promise capability that's being resolved when all
......@@ -876,6 +882,24 @@ void CaptureAsyncStackTrace(Isolate* isolate, Handle<JSPromise> promise,
PromiseCapability::cast(context->get(index)), isolate);
if (!capability->promise().IsJSPromise()) return;
promise = handle(JSPromise::cast(capability->promise()), isolate);
} else if (IsBuiltinFunction(isolate, reaction->reject_handler(),
Builtins::kPromiseAnyRejectElementClosure)) {
Handle<JSFunction> function(JSFunction::cast(reaction->reject_handler()),
isolate);
Handle<Context> context(function->context(), isolate);
Handle<JSFunction> combinator(context->native_context().promise_any(),
isolate);
builder->AppendPromiseCombinatorFrame(function, combinator,
FrameArray::kIsPromiseAny, context);
// Now peak into the Promise.any() reject element context to
// find the promise capability that's being resolved when any of
// the concurrent promises resolve.
int const index = PromiseBuiltins::kPromiseAnyRejectElementCapabilitySlot;
Handle<PromiseCapability> capability(
PromiseCapability::cast(context->get(index)), isolate);
if (!capability->promise().IsJSPromise()) return;
promise = handle(JSPromise::cast(capability->promise()), isolate);
} else if (IsBuiltinFunction(isolate, reaction->fulfill_handler(),
Builtins::kPromiseCapabilityDefaultResolve)) {
Handle<JSFunction> function(JSFunction::cast(reaction->fulfill_handler()),
......
......@@ -351,6 +351,7 @@ void JSStackFrame::FromFrameArray(Isolate* isolate, Handle<FrameArray> array,
is_strict_ = (flags & FrameArray::kIsStrict) != 0;
is_async_ = (flags & FrameArray::kIsAsync) != 0;
is_promise_all_ = (flags & FrameArray::kIsPromiseAll) != 0;
is_promise_any_ = (flags & FrameArray::kIsPromiseAny) != 0;
}
JSStackFrame::JSStackFrame(Isolate* isolate, Handle<Object> receiver,
......@@ -514,7 +515,7 @@ int JSStackFrame::GetColumnNumber() {
}
int JSStackFrame::GetPromiseIndex() const {
return is_promise_all_ ? offset_ : kNone;
return (is_promise_all_ || is_promise_any_) ? offset_ : kNone;
}
bool JSStackFrame::IsNative() {
......
......@@ -86,7 +86,8 @@ class StackFrameBase {
// Return 0-based Wasm function index. Returns -1 for non-Wasm frames.
virtual int GetWasmFunctionIndex();
// Returns index for Promise.all() async frames, or -1 for other frames.
// Returns the index of the rejected promise in the Promise combinator input,
// or -1 if this frame is not a Promise combinator frame.
virtual int GetPromiseIndex() const = 0;
virtual bool IsNative() = 0;
......@@ -94,6 +95,7 @@ class StackFrameBase {
virtual bool IsEval();
virtual bool IsAsync() const = 0;
virtual bool IsPromiseAll() const = 0;
virtual bool IsPromiseAny() const = 0;
virtual bool IsConstructor() = 0;
virtual bool IsStrict() const = 0;
......@@ -136,6 +138,7 @@ class JSStackFrame : public StackFrameBase {
bool IsToplevel() override;
bool IsAsync() const override { return is_async_; }
bool IsPromiseAll() const override { return is_promise_all_; }
bool IsPromiseAny() const override { return is_promise_any_; }
bool IsConstructor() override { return is_constructor_; }
bool IsStrict() const override { return is_strict_; }
......@@ -155,6 +158,7 @@ class JSStackFrame : public StackFrameBase {
bool is_async_ : 1;
bool is_constructor_ : 1;
bool is_promise_all_ : 1;
bool is_promise_any_ : 1;
bool is_strict_ : 1;
friend class FrameArrayIterator;
......@@ -186,6 +190,7 @@ class WasmStackFrame : public StackFrameBase {
bool IsToplevel() override { return false; }
bool IsAsync() const override { return false; }
bool IsPromiseAll() const override { return false; }
bool IsPromiseAny() const override { return false; }
bool IsConstructor() override { return false; }
bool IsStrict() const override { return false; }
bool IsInterpreted() const { return code_ == nullptr; }
......
......@@ -3159,7 +3159,8 @@ Handle<StackFrameInfo> Factory::NewStackFrameInfo(
info->set_is_toplevel(is_toplevel);
info->set_is_async(frame->IsAsync());
info->set_is_promise_all(frame->IsPromiseAll());
info->set_promise_all_index(frame->GetPromiseIndex());
info->set_is_promise_any(frame->IsPromiseAny());
info->set_promise_combinator_index(frame->GetPromiseIndex());
return info;
}
......
......@@ -4260,8 +4260,9 @@ void Genesis::InitializeGlobal_harmony_promise_any() {
JSFunction::cast(
isolate()->native_context()->get(Context::PROMISE_FUNCTION_INDEX)),
isolate());
InstallFunctionWithBuiltinId(isolate_, promise_fun, "any",
Builtins::kPromiseAny, 1, true);
Handle<JSFunction> promise_any = InstallFunctionWithBuiltinId(
isolate_, promise_fun, "any", Builtins::kPromiseAny, 1, true);
native_context()->set_promise_any(*promise_any);
DCHECK(promise_fun->HasFastProperties());
}
......
......@@ -309,6 +309,7 @@ enum ContextLookupFlags {
V(OBJECT_TO_STRING, JSFunction, object_to_string) \
V(OBJECT_VALUE_OF_FUNCTION_INDEX, JSFunction, object_value_of_function) \
V(PROMISE_ALL_INDEX, JSFunction, promise_all) \
V(PROMISE_ANY_INDEX, JSFunction, promise_any) \
V(PROMISE_CATCH_INDEX, JSFunction, promise_catch) \
V(PROMISE_FUNCTION_INDEX, JSFunction, promise_function) \
V(RANGE_ERROR_FUNCTION_INDEX, JSFunction, range_error_function) \
......
......@@ -52,7 +52,8 @@ class FrameArray : public FixedArray {
kIsConstructor = 1 << 3,
kAsmJsAtNumberConversion = 1 << 4,
kIsAsync = 1 << 5,
kIsPromiseAll = 1 << 6
kIsPromiseAll = 1 << 6,
kIsPromiseAny = 1 << 7
};
static Handle<FrameArray> AppendJSFrame(Handle<FrameArray> in,
......
......@@ -28,9 +28,10 @@ SMI_ACCESSORS(StackFrameInfo, line_number, kLineNumberOffset)
SMI_ACCESSORS(StackFrameInfo, column_number, kColumnNumberOffset)
SMI_ACCESSORS(StackFrameInfo, script_id, kScriptIdOffset)
SMI_ACCESSORS(StackFrameInfo, wasm_function_index, kWasmFunctionIndexOffset)
SMI_ACCESSORS(StackFrameInfo, promise_all_index, kPromiseAllIndexOffset)
SMI_ACCESSORS_CHECKED(StackFrameInfo, function_offset, kPromiseAllIndexOffset,
is_wasm())
SMI_ACCESSORS(StackFrameInfo, promise_combinator_index,
kPromiseCombinatorIndexOffset)
SMI_ACCESSORS_CHECKED(StackFrameInfo, function_offset,
kPromiseCombinatorIndexOffset, is_wasm())
ACCESSORS(StackFrameInfo, script_name, Object, kScriptNameOffset)
ACCESSORS(StackFrameInfo, script_name_or_source_url, Object,
kScriptNameOrSourceUrlOffset)
......@@ -49,6 +50,7 @@ BOOL_ACCESSORS(StackFrameInfo, flag, is_user_java_script, kIsUserJavaScriptBit)
BOOL_ACCESSORS(StackFrameInfo, flag, is_toplevel, kIsToplevelBit)
BOOL_ACCESSORS(StackFrameInfo, flag, is_async, kIsAsyncBit)
BOOL_ACCESSORS(StackFrameInfo, flag, is_promise_all, kIsPromiseAllBit)
BOOL_ACCESSORS(StackFrameInfo, flag, is_promise_any, kIsPromiseAnyBit)
TQ_OBJECT_CONSTRUCTORS_IMPL(StackTraceFrame)
NEVER_READ_ONLY_SPACE_IMPL(StackTraceFrame)
......
......@@ -47,8 +47,8 @@ int StackTraceFrame::GetScriptId(Handle<StackTraceFrame> frame) {
}
// static
int StackTraceFrame::GetPromiseAllIndex(Handle<StackTraceFrame> frame) {
return GetFrameInfo(frame)->promise_all_index();
int StackTraceFrame::GetPromiseCombinatorIndex(Handle<StackTraceFrame> frame) {
return GetFrameInfo(frame)->promise_combinator_index();
}
// static
......@@ -168,6 +168,11 @@ bool StackTraceFrame::IsPromiseAll(Handle<StackTraceFrame> frame) {
return GetFrameInfo(frame)->is_promise_all();
}
// static
bool StackTraceFrame::IsPromiseAny(Handle<StackTraceFrame> frame) {
return GetFrameInfo(frame)->is_promise_any();
}
// static
Handle<StackFrameInfo> StackTraceFrame::GetFrameInfo(
Handle<StackTraceFrame> frame) {
......@@ -326,6 +331,7 @@ void SerializeJSStackFrame(Isolate* isolate, Handle<StackTraceFrame> frame,
const bool is_toplevel = StackTraceFrame::IsToplevel(frame);
const bool is_async = StackTraceFrame::IsAsync(frame);
const bool is_promise_all = StackTraceFrame::IsPromiseAll(frame);
const bool is_promise_any = StackTraceFrame::IsPromiseAny(frame);
const bool is_constructor = StackTraceFrame::IsConstructor(frame);
// Note: Keep the {is_method_call} predicate in sync with the corresponding
// predicate in factory.cc where the StackFrameInfo is created.
......@@ -338,7 +344,13 @@ void SerializeJSStackFrame(Isolate* isolate, Handle<StackTraceFrame> frame,
}
if (is_promise_all) {
builder->AppendCString("Promise.all (index ");
builder->AppendInt(StackTraceFrame::GetPromiseAllIndex(frame));
builder->AppendInt(StackTraceFrame::GetPromiseCombinatorIndex(frame));
builder->AppendCString(")");
return;
}
if (is_promise_any) {
builder->AppendCString("Promise.any (index ");
builder->AppendInt(StackTraceFrame::GetPromiseCombinatorIndex(frame));
builder->AppendCString(")");
return;
}
......
......@@ -23,8 +23,8 @@ class StackFrameInfo : public Struct {
DECL_INT_ACCESSORS(column_number)
DECL_INT_ACCESSORS(script_id)
DECL_INT_ACCESSORS(wasm_function_index)
DECL_INT_ACCESSORS(promise_all_index)
// Wasm frames only: function_offset instead of promise_all_index.
DECL_INT_ACCESSORS(promise_combinator_index)
// Wasm frames only: function_offset instead of promise_combinator_index.
DECL_INT_ACCESSORS(function_offset)
DECL_ACCESSORS(script_name, Object)
DECL_ACCESSORS(script_name_or_source_url, Object)
......@@ -42,6 +42,7 @@ class StackFrameInfo : public Struct {
DECL_BOOLEAN_ACCESSORS(is_toplevel)
DECL_BOOLEAN_ACCESSORS(is_async)
DECL_BOOLEAN_ACCESSORS(is_promise_all)
DECL_BOOLEAN_ACCESSORS(is_promise_any)
DECL_INT_ACCESSORS(flag)
DECL_CAST(StackFrameInfo)
......@@ -63,6 +64,7 @@ class StackFrameInfo : public Struct {
static const int kIsToplevelBit = 5;
static const int kIsAsyncBit = 6;
static const int kIsPromiseAllBit = 7;
static const int kIsPromiseAnyBit = 8;
OBJECT_CONSTRUCTORS(StackFrameInfo, Struct);
};
......@@ -85,7 +87,7 @@ class StackTraceFrame
static int GetColumnNumber(Handle<StackTraceFrame> frame);
static int GetOneBasedColumnNumber(Handle<StackTraceFrame> frame);
static int GetScriptId(Handle<StackTraceFrame> frame);
static int GetPromiseAllIndex(Handle<StackTraceFrame> frame);
static int GetPromiseCombinatorIndex(Handle<StackTraceFrame> frame);
static int GetFunctionOffset(Handle<StackTraceFrame> frame);
static int GetWasmFunctionIndex(Handle<StackTraceFrame> frame);
......@@ -107,6 +109,7 @@ class StackTraceFrame
static bool IsToplevel(Handle<StackTraceFrame> frame);
static bool IsAsync(Handle<StackTraceFrame> frame);
static bool IsPromiseAll(Handle<StackTraceFrame> frame);
static bool IsPromiseAny(Handle<StackTraceFrame> frame);
private:
static Handle<StackFrameInfo> GetFrameInfo(Handle<StackTraceFrame> frame);
......
......@@ -5,7 +5,7 @@
extern class StackFrameInfo extends Struct {
line_number: Smi;
column_number: Smi;
promise_all_index: Smi;
promise_combinator_index: Smi;
script_id: Smi;
wasm_function_index: Smi;
script_name: String|Null|Undefined;
......
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --allow-natives-syntax --async-stack-traces --harmony-promise-any
// Basic test with Promise.any().
(function() {
async function fine() { }
async function thrower() {
await fine();
throw new Error();
}
async function driver() {
await Promise.any([thrower(), thrower()]);
}
async function test(f) {
try {
await f();
assertUnreachable();
} catch (e) {
assertInstanceof(e, AggregateError);
assertEquals(2, e.errors.length);
assertMatches(/Error.+at thrower.+at async Promise.any \(index 0\).+at async driver.+at async test/ms, e.errors[0].stack);
assertMatches(/Error.+at thrower.+at async Promise.any \(index 1\).+at async driver.+at async test/ms, e.errors[1].stack);
}
}
assertPromiseResult((async () => {
%PrepareFunctionForOptimization(thrower);
%PrepareFunctionForOptimization(driver);
await test(driver);
await test(driver);
%OptimizeFunctionOnNextCall(thrower);
await test(driver);
%OptimizeFunctionOnNextCall(driver);
await test(driver);
})());
})();
......@@ -2,3 +2,4 @@ IsConstructor
IsEval
IsAsync
IsPromiseAll
IsPromiseAny
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment