[inspector] reworked async stack instrumentation for async functions

New intstrumentation consists of:
- kAsyncFunctionSuspended when async function is suspended on await
  (called on each await),
- kAsyncFunctionFinished when async function is finished.

Old instrumentation was based on reusing async function promise.
Using this promise produces couple side effects:
- for any promise instrumentation we first need to check if it is
  special case for async function promise or not - it requires
  expensive reading from promise object.
- we capture stack for async functions even if it does not contain
  awaits.
- we do not properly cancel async task created for async function.

New intsrumntation resolved all these problems as well as provide
clear mapping between async task and generator which we can use later
to fetch scope information for async functions on pause.

R=dgozman@chromium.org,yangguo@chromium.org

Bug: v8:7078
Cq-Include-Trybots: master.tryserver.blink:linux_trusty_blink_rel
Change-Id: Ifdcec947d91e6e3d4d5f9029bc080a19b8e23d41
Reviewed-on: https://chromium-review.googlesource.com/1043096
Reviewed-by: Sathya Gunasekaran <gsathya@chromium.org>
Reviewed-by: Benedikt Meurer <bmeurer@chromium.org>
Reviewed-by: Dmitry Gozman <dgozman@chromium.org>
Commit-Queue: Aleksey Kozyatinskiy <kozyatinskiy@chromium.org>
Cr-Commit-Position: refs/heads/master@{#53445}
This commit is contained in:
Alexey Kozyatinskiy 2018-05-30 07:21:39 -07:00 committed by Commit Bot
parent 9f4e74848b
commit b6c9086ca1
28 changed files with 287 additions and 157 deletions

View File

@ -4110,7 +4110,7 @@ void Bootstrapper::ExportFromRuntime(Isolate* isolate,
{
Handle<JSFunction> function = SimpleCreateFunction(
isolate, factory->empty_string(),
Builtins::kAsyncFunctionPromiseRelease, 1, false);
Builtins::kAsyncFunctionPromiseRelease, 2, false);
native_context->set_async_function_promise_release(*function);
}
}

View File

@ -120,6 +120,11 @@ void AsyncFunctionBuiltinsAssembler::AsyncFunctionAwait(
// TODO(jgruber): Use a faster specialized version of
// InternalPerformPromiseThen.
Label after_debug_hook(this), call_debug_hook(this, Label::kDeferred);
GotoIf(IsDebugActive(), &call_debug_hook);
Goto(&after_debug_hook);
BIND(&after_debug_hook);
Await(context, generator, awaited, outer_promise, AwaitContext::kLength,
init_closure_context, Context::ASYNC_FUNCTION_AWAIT_RESOLVE_SHARED_FUN,
Context::ASYNC_FUNCTION_AWAIT_REJECT_SHARED_FUN,
@ -128,6 +133,10 @@ void AsyncFunctionBuiltinsAssembler::AsyncFunctionAwait(
// Return outer promise to avoid adding an load of the outer promise before
// suspending in BytecodeGenerator.
Return(outer_promise);
BIND(&call_debug_hook);
CallRuntime(Runtime::kDebugAsyncFunctionSuspended, context, outer_promise);
Goto(&after_debug_hook);
}
// Called by the parser from the desugaring of 'await' when catch
@ -177,15 +186,13 @@ TF_BUILTIN(AsyncFunctionPromiseCreate, AsyncFunctionBuiltinsAssembler) {
// Push the Promise under construction in an async function on
// the catch prediction stack to handle exceptions thrown before
// the first await.
// Assign ID and create a recurring task to save stack for future
// resumptions from await.
CallRuntime(Runtime::kDebugAsyncFunctionPromiseCreated, context, promise);
CallRuntime(Runtime::kDebugPushPromise, context, promise);
Return(promise);
}
}
TF_BUILTIN(AsyncFunctionPromiseRelease, AsyncFunctionBuiltinsAssembler) {
CSA_ASSERT_JS_ARGC_EQ(this, 1);
CSA_ASSERT_JS_ARGC_EQ(this, 2);
Node* const promise = Parameter(Descriptor::kPromise);
Node* const context = Parameter(Descriptor::kContext);
@ -199,7 +206,8 @@ TF_BUILTIN(AsyncFunctionPromiseRelease, AsyncFunctionBuiltinsAssembler) {
{
// Pop the Promise under construction in an async function on
// from catch prediction stack.
CallRuntime(Runtime::kDebugPopPromise, context);
CallRuntime(Runtime::kDebugAsyncFunctionFinished, context,
Parameter(Descriptor::kCanSuspend), promise);
Return(promise);
}
}

View File

@ -99,9 +99,8 @@ Node* AsyncBuiltinsAssembler::Await(
// Add PromiseHooks if needed
Label next(this);
GotoIfNot(IsPromiseHookEnabledOrDebugIsActive(), &next);
CallRuntime(Runtime::kPromiseHookInit, context, wrapped_value,
outer_promise);
CallRuntime(Runtime::kPromiseHookInit, context, throwaway, wrapped_value);
CallRuntime(Runtime::kAwaitPromisesInit, context, wrapped_value,
outer_promise, throwaway);
Goto(&next);
BIND(&next);
}

View File

@ -421,7 +421,7 @@ namespace internal {
TFJ(AsyncFunctionAwaitRejectClosure, 1, kSentError) \
TFJ(AsyncFunctionAwaitResolveClosure, 1, kSentValue) \
TFJ(AsyncFunctionPromiseCreate, 0) \
TFJ(AsyncFunctionPromiseRelease, 1, kPromise) \
TFJ(AsyncFunctionPromiseRelease, 2, kPromise, kCanSuspend) \
\
/* BigInt */ \
CPP(BigIntConstructor) \

View File

@ -171,8 +171,8 @@ MaybeLocal<UnboundScript> CompileInspectorScript(Isolate* isolate,
class DebugDelegate {
public:
virtual ~DebugDelegate() {}
virtual void PromiseEventOccurred(debug::PromiseDebugActionType type, int id,
bool is_blackboxed) {}
virtual void AsyncEventOccurred(debug::DebugAsyncActionType type, int id,
bool is_blackboxed) {}
virtual void ScriptCompiled(v8::Local<Script> script, bool is_live_edited,
bool has_compile_error) {}
// |inspector_break_points_hit| contains id of breakpoints installed with

View File

@ -1734,7 +1734,7 @@ MaybeHandle<Object> Debug::MakeCompileEvent(Handle<Script> script,
}
MaybeHandle<Object> Debug::MakeAsyncTaskEvent(
v8::debug::PromiseDebugActionType type, int id) {
v8::debug::DebugAsyncActionType type, int id) {
// Create the async task event object.
Handle<Object> argv[] = {Handle<Smi>(Smi::FromInt(type), isolate_),
Handle<Smi>(Smi::FromInt(id), isolate_)};
@ -1771,6 +1771,15 @@ void Debug::OnPromiseReject(Handle<Object> promise, Handle<Object> value) {
}
}
void Debug::OnAsyncFunctionStateChanged(Handle<JSPromise> promise,
debug::DebugAsyncActionType event) {
if (in_debug_scope() || ignore_events()) return;
if (!debug_delegate_) return;
PostponeInterruptsScope no_interrupts(isolate_);
int id = NextAsyncTaskId(promise);
debug_delegate_->AsyncEventOccurred(event, id, false);
}
namespace {
v8::Local<v8::Context> GetDebugEventContext(Isolate* isolate) {
Handle<Context> context = isolate->debug()->debugger_entry()->GetContext();
@ -1909,39 +1918,6 @@ void Debug::OnAfterCompile(Handle<Script> script) {
ProcessCompileEvent(v8::AfterCompile, script);
}
namespace {
// In an async function, reuse the existing stack related to the outer
// Promise. Otherwise, e.g. in a direct call to then, save a new stack.
// Promises with multiple reactions with one or more of them being async
// functions will not get a good stack trace, as async functions require
// different stacks from direct Promise use, but we save and restore a
// stack once for all reactions.
//
// If this isn't a case of async function, we return false, otherwise
// we set the correct id and return true.
//
// TODO(littledan): Improve this case.
int GetReferenceAsyncTaskId(Isolate* isolate, Handle<JSPromise> promise) {
Handle<Symbol> handled_by_symbol =
isolate->factory()->promise_handled_by_symbol();
Handle<Object> handled_by_promise =
JSObject::GetDataProperty(promise, handled_by_symbol);
if (!handled_by_promise->IsJSPromise()) {
return isolate->debug()->NextAsyncTaskId(promise);
}
Handle<JSPromise> handled_by_promise_js =
Handle<JSPromise>::cast(handled_by_promise);
Handle<Symbol> async_stack_id_symbol =
isolate->factory()->promise_async_stack_id_symbol();
Handle<Object> async_task_id =
JSObject::GetDataProperty(handled_by_promise_js, async_stack_id_symbol);
if (!async_task_id->IsSmi()) {
return isolate->debug()->NextAsyncTaskId(promise);
}
return Handle<Smi>::cast(async_task_id)->value();
}
} // namespace
void Debug::RunPromiseHook(PromiseHookType hook_type, Handle<JSPromise> promise,
Handle<Object> parent) {
if (hook_type == PromiseHookType::kResolve) return;
@ -1949,14 +1925,17 @@ void Debug::RunPromiseHook(PromiseHookType hook_type, Handle<JSPromise> promise,
if (!debug_delegate_) return;
PostponeInterruptsScope no_interrupts(isolate_);
int id = GetReferenceAsyncTaskId(isolate_, promise);
if (hook_type == PromiseHookType::kBefore) {
debug_delegate_->PromiseEventOccurred(debug::kDebugWillHandle, id, false);
if (!promise->async_task_id()) return;
debug_delegate_->AsyncEventOccurred(debug::kDebugWillHandle,
promise->async_task_id(), false);
} else if (hook_type == PromiseHookType::kAfter) {
debug_delegate_->PromiseEventOccurred(debug::kDebugDidHandle, id, false);
if (!promise->async_task_id()) return;
debug_delegate_->AsyncEventOccurred(debug::kDebugDidHandle,
promise->async_task_id(), false);
} else {
DCHECK(hook_type == PromiseHookType::kInit);
debug::PromiseDebugActionType type = debug::kDebugPromiseThen;
debug::DebugAsyncActionType type = debug::kDebugPromiseThen;
bool last_frame_was_promise_builtin = false;
JavaScriptFrameIterator it(isolate_);
while (!it.done()) {
@ -1967,18 +1946,15 @@ void Debug::RunPromiseHook(PromiseHookType hook_type, Handle<JSPromise> promise,
if (info->IsUserJavaScript()) {
// We should not report PromiseThen and PromiseCatch which is called
// indirectly, e.g. Promise.all calls Promise.then internally.
if (type == debug::kDebugAsyncFunctionPromiseCreated ||
last_frame_was_promise_builtin) {
debug_delegate_->PromiseEventOccurred(type, id, IsBlackboxed(info));
if (last_frame_was_promise_builtin) {
int id = NextAsyncTaskId(promise);
debug_delegate_->AsyncEventOccurred(type, id, IsBlackboxed(info));
}
return;
}
last_frame_was_promise_builtin = false;
if (info->HasBuiltinId()) {
if (info->builtin_id() == Builtins::kAsyncFunctionPromiseCreate) {
type = debug::kDebugAsyncFunctionPromiseCreated;
last_frame_was_promise_builtin = true;
} else if (info->builtin_id() == Builtins::kPromisePrototypeThen) {
if (info->builtin_id() == Builtins::kPromisePrototypeThen) {
type = debug::kDebugPromiseThen;
last_frame_was_promise_builtin = true;
} else if (info->builtin_id() == Builtins::kPromisePrototypeCatch) {
@ -1995,19 +1971,11 @@ void Debug::RunPromiseHook(PromiseHookType hook_type, Handle<JSPromise> promise,
}
}
int Debug::NextAsyncTaskId(Handle<JSObject> promise) {
LookupIterator it(promise, isolate_->factory()->promise_async_id_symbol());
Maybe<bool> maybe = JSReceiver::HasProperty(&it);
if (maybe.ToChecked()) {
MaybeHandle<Object> result = Object::GetProperty(&it);
return Handle<Smi>::cast(result.ToHandleChecked())->value();
int Debug::NextAsyncTaskId(Handle<JSPromise> promise) {
if (!promise->async_task_id()) {
promise->set_async_task_id(++thread_local_.async_task_count_);
}
Handle<Smi> async_id =
handle(Smi::FromInt(++thread_local_.async_task_count_), isolate_);
Object::SetProperty(&it, async_id, LanguageMode::kSloppy,
Object::MAY_BE_STORE_FROM_KEYED)
.ToChecked();
return async_id->value();
return promise->async_task_id();
}
namespace {
@ -2494,8 +2462,8 @@ bool Debug::PerformSideEffectCheckForObject(Handle<Object> object) {
return false;
}
void LegacyDebugDelegate::PromiseEventOccurred(
v8::debug::PromiseDebugActionType type, int id, bool is_blackboxed) {
void LegacyDebugDelegate::AsyncEventOccurred(
v8::debug::DebugAsyncActionType type, int id, bool is_blackboxed) {
DebugScope debug_scope(isolate_->debug());
if (debug_scope.failed()) return;
HandleScope scope(isolate_);

View File

@ -225,6 +225,9 @@ class Debug {
void OnCompileError(Handle<Script> script);
void OnAfterCompile(Handle<Script> script);
void OnAsyncFunctionStateChanged(Handle<JSPromise> promise,
debug::DebugAsyncActionType);
V8_WARN_UNUSED_RESULT MaybeHandle<Object> Call(Handle<Object> fun,
Handle<Object> data);
Handle<Context> GetDebugContext();
@ -279,7 +282,7 @@ class Debug {
void RunPromiseHook(PromiseHookType hook_type, Handle<JSPromise> promise,
Handle<Object> parent);
int NextAsyncTaskId(Handle<JSObject> promise);
int NextAsyncTaskId(Handle<JSPromise> promise);
bool IsBlackboxed(Handle<SharedFunctionInfo> shared);
@ -459,7 +462,7 @@ class Debug {
V8_WARN_UNUSED_RESULT MaybeHandle<Object> MakeCompileEvent(
Handle<Script> script, v8::DebugEvent type);
V8_WARN_UNUSED_RESULT MaybeHandle<Object> MakeAsyncTaskEvent(
v8::debug::PromiseDebugActionType type, int id);
v8::debug::DebugAsyncActionType type, int id);
void ProcessCompileEvent(v8::DebugEvent event, Handle<Script> script);
void ProcessDebugEvent(v8::DebugEvent event, Handle<JSObject> event_data);
@ -623,8 +626,8 @@ class Debug {
class LegacyDebugDelegate : public v8::debug::DebugDelegate {
public:
explicit LegacyDebugDelegate(Isolate* isolate) : isolate_(isolate) {}
void PromiseEventOccurred(v8::debug::PromiseDebugActionType type, int id,
bool is_blackboxed) override;
void AsyncEventOccurred(v8::debug::DebugAsyncActionType type, int id,
bool is_blackboxed) override;
void ScriptCompiled(v8::Local<v8::debug::Script> script, bool is_live_edited,
bool has_compile_error) override;
void BreakProgramRequested(v8::Local<v8::Context> paused_context,

View File

@ -69,13 +69,14 @@ struct WasmDisassembly {
OffsetTable offset_table;
};
enum PromiseDebugActionType {
kDebugAsyncFunctionPromiseCreated,
enum DebugAsyncActionType {
kDebugPromiseThen,
kDebugPromiseCatch,
kDebugPromiseFinally,
kDebugWillHandle,
kDebugDidHandle,
kAsyncFunctionSuspended,
kAsyncFunctionFinished
};
enum BreakLocationType {

View File

@ -604,15 +604,12 @@ bool V8Debugger::IsFunctionBlackboxed(v8::Local<v8::debug::Script> script,
return hasAgents && allBlackboxed;
}
void V8Debugger::PromiseEventOccurred(v8::debug::PromiseDebugActionType type,
int id, bool isBlackboxed) {
void V8Debugger::AsyncEventOccurred(v8::debug::DebugAsyncActionType type,
int id, bool isBlackboxed) {
// Async task events from Promises are given misaligned pointers to prevent
// from overlapping with other Blink task identifiers.
void* task = reinterpret_cast<void*>(id * 2 + 1);
switch (type) {
case v8::debug::kDebugAsyncFunctionPromiseCreated:
asyncTaskScheduledForStack("async function", task, true);
break;
case v8::debug::kDebugPromiseThen:
asyncTaskScheduledForStack("Promise.then", task, false);
if (!isBlackboxed) asyncTaskCandidateForStepping(task, true);
@ -633,6 +630,14 @@ void V8Debugger::PromiseEventOccurred(v8::debug::PromiseDebugActionType type,
asyncTaskFinishedForStack(task);
asyncTaskFinishedForStepping(task);
break;
case v8::debug::kAsyncFunctionSuspended:
if (m_asyncTaskStacks.find(task) == m_asyncTaskStacks.end()) {
asyncTaskScheduledForStack("async function", task, true);
}
break;
case v8::debug::kAsyncFunctionFinished:
asyncTaskCanceledForStack(task);
break;
}
}

View File

@ -170,8 +170,8 @@ class V8Debugger : public v8::debug::DebugDelegate {
void asyncTaskCanceledForStepping(void* task);
// v8::debug::DebugEventListener implementation.
void PromiseEventOccurred(v8::debug::PromiseDebugActionType type, int id,
bool isBlackboxed) override;
void AsyncEventOccurred(v8::debug::DebugAsyncActionType type, int id,
bool isBlackboxed) override;
void ScriptCompiled(v8::Local<v8::debug::Script> script, bool is_live_edited,
bool has_compile_error) override;
void BreakProgramRequested(

View File

@ -15984,6 +15984,14 @@ const char* JSPromise::Status(v8::Promise::PromiseState status) {
UNREACHABLE();
}
int JSPromise::async_task_id() const {
return AsyncTaskIdField::decode(flags());
}
void JSPromise::set_async_task_id(int id) {
set_flags(AsyncTaskIdField::update(flags(), id));
}
// static
Handle<Object> JSPromise::Fulfill(Handle<JSPromise> promise,
Handle<Object> value) {

View File

@ -45,6 +45,9 @@ class JSPromise : public JSObject {
// block in an async function.
DECL_BOOLEAN_ACCESSORS(handled_hint)
int async_task_id() const;
void set_async_task_id(int id);
static const char* Status(Promise::PromiseState status);
Promise::PromiseState status() const;
void set_status(Promise::PromiseState status);
@ -77,6 +80,7 @@ class JSPromise : public JSObject {
static const int kStatusBits = 2;
static const int kHasHandlerBit = 2;
static const int kHandledHintBit = 3;
class AsyncTaskIdField : public BitField<int, kHandledHintBit + 1, 22> {};
static const int kStatusShift = 0;
static const int kStatusMask = 0x3;

View File

@ -407,6 +407,7 @@ class ParserBase {
void AddSuspend() { suspend_count_++; }
int suspend_count() const { return suspend_count_; }
bool CanSuspend() const { return suspend_count_ > 0; }
FunctionKind kind() const { return scope()->function_kind(); }

View File

@ -2970,11 +2970,14 @@ Block* Parser::BuildRejectPromiseOnException(Block* inner_block) {
// There is no TryCatchFinally node, so wrap it in an outer try/finally
Block* outer_try_block = IgnoreCompletion(try_catch_statement);
// finally { %AsyncFunctionPromiseRelease(.promise) }
// finally { %AsyncFunctionPromiseRelease(.promise, can_suspend) }
Block* finally_block;
{
ZoneList<Expression*>* args = new (zone()) ZoneList<Expression*>(1, zone());
args->Add(factory()->NewVariableProxy(PromiseVariable()), zone());
args->Add(factory()->NewBooleanLiteral(function_state_->CanSuspend(),
kNoSourcePosition),
zone());
Expression* call_promise_release = factory()->NewCallRuntime(
Context::ASYNC_FUNCTION_PROMISE_RELEASE_INDEX, args, kNoSourcePosition);
Statement* promise_release = factory()->NewExpressionStatement(

View File

@ -1718,21 +1718,6 @@ RUNTIME_FUNCTION(Runtime_DebugPopPromise) {
return isolate->heap()->undefined_value();
}
RUNTIME_FUNCTION(Runtime_DebugAsyncFunctionPromiseCreated) {
DCHECK_EQ(1, args.length());
HandleScope scope(isolate);
CONVERT_ARG_HANDLE_CHECKED(JSObject, promise, 0);
isolate->PushPromise(promise);
int id = isolate->debug()->NextAsyncTaskId(promise);
Handle<Symbol> async_stack_id_symbol =
isolate->factory()->promise_async_stack_id_symbol();
JSObject::SetProperty(promise, async_stack_id_symbol,
handle(Smi::FromInt(id), isolate),
LanguageMode::kStrict)
.Assert();
return isolate->heap()->undefined_value();
}
RUNTIME_FUNCTION(Runtime_DebugIsActive) {
SealHandleScope shs(isolate);
return Smi::FromInt(isolate->debug()->is_active());
@ -1843,5 +1828,27 @@ RUNTIME_FUNCTION(Runtime_IncBlockCounter) {
return isolate->heap()->undefined_value();
}
RUNTIME_FUNCTION(Runtime_DebugAsyncFunctionSuspended) {
DCHECK_EQ(1, args.length());
HandleScope scope(isolate);
CONVERT_ARG_HANDLE_CHECKED(JSPromise, promise, 0);
isolate->debug()->OnAsyncFunctionStateChanged(promise,
debug::kAsyncFunctionSuspended);
return isolate->heap()->undefined_value();
}
RUNTIME_FUNCTION(Runtime_DebugAsyncFunctionFinished) {
DCHECK_EQ(2, args.length());
HandleScope scope(isolate);
CONVERT_BOOLEAN_ARG_CHECKED(has_suspend, 0);
CONVERT_ARG_HANDLE_CHECKED(JSPromise, promise, 1);
isolate->PopPromise();
if (has_suspend) {
isolate->debug()->OnAsyncFunctionStateChanged(
promise, debug::kAsyncFunctionFinished);
}
return isolate->heap()->undefined_value();
}
} // namespace internal
} // namespace v8

View File

@ -112,6 +112,23 @@ RUNTIME_FUNCTION(Runtime_PromiseHookInit) {
return isolate->heap()->undefined_value();
}
RUNTIME_FUNCTION(Runtime_AwaitPromisesInit) {
DCHECK_EQ(3, args.length());
HandleScope scope(isolate);
CONVERT_ARG_HANDLE_CHECKED(JSPromise, wrapped_value, 0);
CONVERT_ARG_HANDLE_CHECKED(JSPromise, outer_promise, 1);
CONVERT_ARG_HANDLE_CHECKED(JSPromise, throwaway, 2);
isolate->RunPromiseHook(PromiseHookType::kInit, wrapped_value, outer_promise);
isolate->RunPromiseHook(PromiseHookType::kInit, throwaway, wrapped_value);
// On inspector side we capture async stack trace and store it by
// outer_promise->async_task_id when async function is suspended first time.
// To use captured stack trace later throwaway promise should have the same
// async_task_id as outer_promise since we generate WillHandle and DidHandle
// events using throwaway promise.
throwaway->set_async_task_id(outer_promise->async_task_id());
return isolate->heap()->undefined_value();
}
RUNTIME_FUNCTION(Runtime_PromiseHookBefore) {
HandleScope scope(isolate);
DCHECK_EQ(1, args.length());

View File

@ -132,7 +132,6 @@ namespace internal {
F(ClearStepping, 0, 1) \
F(CollectGarbage, 1, 1) \
F(DebugApplyInstrumentation, 1, 1) \
F(DebugAsyncFunctionPromiseCreated, 1, 1) \
F(DebugBreakAtEntry, 1, 1) \
F(DebugCollectCoverage, 0, 1) \
F(DebugConstructedBy, 2, 1) \
@ -150,6 +149,8 @@ namespace internal {
F(DebugPropertyAttributesFromDetails, 1, 1) \
F(DebugPropertyKindFromDetails, 1, 1) \
F(DebugPushPromise, 1, 1) \
F(DebugAsyncFunctionSuspended, 1, 1) \
F(DebugAsyncFunctionFinished, 2, 1) \
F(DebugReferencedBy, 3, 1) \
F(DebugSetScriptSource, 2, 1) \
F(DebugToggleBlockCoverage, 1, 1) \
@ -426,6 +427,7 @@ namespace internal {
F(PromiseHookAfter, 1, 1) \
F(PromiseHookBefore, 1, 1) \
F(PromiseHookInit, 2, 1) \
F(AwaitPromisesInit, 3, 1) \
F(PromiseMarkAsHandled, 1, 1) \
F(PromiseRejectEventFromStack, 2, 1) \
F(PromiseResult, 1, 1) \

View File

@ -16,7 +16,7 @@ snippet: "
"
frame size: 23
parameter count: 1
bytecode array length: 508
bytecode array length: 514
bytecodes: [
B(SwitchOnGeneratorState), R(2), U8(0), U8(3),
B(Mov), R(closure), R(12),
@ -219,7 +219,10 @@ bytecodes: [
B(LdaTheHole),
B(SetPendingMessage),
B(Star), R(14),
B(CallJSRuntime), U8(%async_function_promise_release), R(11), U8(1),
B(LdaTrue),
B(Star), R(16),
B(Mov), R(11), R(15),
B(CallJSRuntime), U8(%async_function_promise_release), R(15), U8(2),
B(Ldar), R(14),
B(SetPendingMessage),
B(Ldar), R(12),
@ -266,7 +269,7 @@ snippet: "
"
frame size: 23
parameter count: 1
bytecode array length: 537
bytecode array length: 543
bytecodes: [
B(SwitchOnGeneratorState), R(2), U8(0), U8(3),
B(Mov), R(closure), R(12),
@ -474,7 +477,10 @@ bytecodes: [
B(LdaTheHole),
B(SetPendingMessage),
B(Star), R(14),
B(CallJSRuntime), U8(%async_function_promise_release), R(11), U8(1),
B(LdaTrue),
B(Star), R(16),
B(Mov), R(11), R(15),
B(CallJSRuntime), U8(%async_function_promise_release), R(15), U8(2),
B(Ldar), R(14),
B(SetPendingMessage),
B(Ldar), R(12),
@ -532,7 +538,7 @@ snippet: "
"
frame size: 23
parameter count: 1
bytecode array length: 526
bytecode array length: 532
bytecodes: [
B(SwitchOnGeneratorState), R(2), U8(0), U8(3),
B(Mov), R(closure), R(12),
@ -743,7 +749,10 @@ bytecodes: [
B(LdaTheHole),
B(SetPendingMessage),
B(Star), R(14),
B(CallJSRuntime), U8(%async_function_promise_release), R(11), U8(1),
B(LdaTrue),
B(Star), R(16),
B(Mov), R(11), R(15),
B(CallJSRuntime), U8(%async_function_promise_release), R(15), U8(2),
B(Ldar), R(14),
B(SetPendingMessage),
B(Ldar), R(12),
@ -791,7 +800,7 @@ snippet: "
"
frame size: 20
parameter count: 1
bytecode array length: 397
bytecode array length: 403
bytecodes: [
/* 16 E> */ B(StackCheck),
B(CallJSRuntime), U8(%async_function_promise_create), R(0), U8(0),
@ -947,7 +956,10 @@ bytecodes: [
B(LdaTheHole),
B(SetPendingMessage),
B(Star), R(12),
B(CallJSRuntime), U8(%async_function_promise_release), R(9), U8(1),
B(LdaFalse),
B(Star), R(14),
B(Mov), R(9), R(13),
B(CallJSRuntime), U8(%async_function_promise_release), R(13), U8(2),
B(Ldar), R(12),
B(SetPendingMessage),
B(Ldar), R(10),

View File

@ -926,7 +926,7 @@ snippet: "
"
frame size: 23
parameter count: 2
bytecode array length: 357
bytecode array length: 363
bytecodes: [
/* 16 E> */ B(StackCheck),
B(CallJSRuntime), U8(%async_function_promise_create), R(0), U8(0),
@ -1074,7 +1074,10 @@ bytecodes: [
B(LdaTheHole),
B(SetPendingMessage),
B(Star), R(15),
B(CallJSRuntime), U8(%async_function_promise_release), R(12), U8(1),
B(LdaFalse),
B(Star), R(17),
B(Mov), R(12), R(16),
B(CallJSRuntime), U8(%async_function_promise_release), R(16), U8(2),
B(Ldar), R(15),
B(SetPendingMessage),
B(Ldar), R(13),
@ -1116,7 +1119,7 @@ snippet: "
"
frame size: 23
parameter count: 2
bytecode array length: 408
bytecode array length: 414
bytecodes: [
B(SwitchOnGeneratorState), R(2), U8(0), U8(1),
B(Mov), R(closure), R(12),
@ -1282,7 +1285,10 @@ bytecodes: [
B(LdaTheHole),
B(SetPendingMessage),
B(Star), R(14),
B(CallJSRuntime), U8(%async_function_promise_release), R(11), U8(1),
B(LdaTrue),
B(Star), R(16),
B(Mov), R(11), R(15),
B(CallJSRuntime), U8(%async_function_promise_release), R(15), U8(2),
B(Ldar), R(14),
B(SetPendingMessage),
B(Ldar), R(12),

View File

@ -381,7 +381,7 @@ snippet: "
"
frame size: 12
parameter count: 1
bytecode array length: 134
bytecode array length: 140
bytecodes: [
/* 16 E> */ B(StackCheck),
B(CallJSRuntime), U8(%async_function_promise_create), R(0), U8(0),
@ -436,7 +436,10 @@ bytecodes: [
B(LdaTheHole),
B(SetPendingMessage),
B(Star), R(6),
B(CallJSRuntime), U8(%async_function_promise_release), R(3), U8(1),
B(LdaFalse),
B(Star), R(8),
B(Mov), R(3), R(7),
B(CallJSRuntime), U8(%async_function_promise_release), R(7), U8(2),
B(Ldar), R(6),
B(SetPendingMessage),
B(Ldar), R(4),
@ -468,7 +471,7 @@ snippet: "
"
frame size: 11
parameter count: 1
bytecode array length: 185
bytecode array length: 191
bytecodes: [
B(SwitchOnGeneratorState), R(1), U8(0), U8(1),
B(Mov), R(closure), R(3),
@ -541,7 +544,10 @@ bytecodes: [
B(LdaTheHole),
B(SetPendingMessage),
B(Star), R(5),
B(CallJSRuntime), U8(%async_function_promise_release), R(2), U8(1),
B(LdaTrue),
B(Star), R(7),
B(Mov), R(2), R(6),
B(CallJSRuntime), U8(%async_function_promise_release), R(6), U8(2),
B(Ldar), R(5),
B(SetPendingMessage),
B(Ldar), R(3),

View File

@ -1,58 +1,58 @@
Checks that async chains for for-await-of are correct.
Running test: testBasic
Debugger (test.js:12:2)
Basic (test.js:50:4)
Debugger (test.js:10:2)
Basic (test.js:48:4)
-- async function --
Basic (test.js:48:20)
Basic (test.js:47:19)
(anonymous) (testBasic.js:0:0)
Running test: testUncaughtReject
Debugger (test.js:12:2)
-- async function --
UncaughtReject (test.js:54:29)
Debugger (test.js:10:2)
-- Promise.catch --
UncaughtReject (test.js:58:21)
(anonymous) (testUncaughtReject.js:0:0)
Running test: testUncaughtThrow
Debugger (test.js:12:2)
-- async function --
UncaughtThrow (test.js:63:28)
Debugger (test.js:10:2)
-- Promise.catch --
UncaughtThrow (test.js:67:21)
(anonymous) (testUncaughtThrow.js:0:0)
Running test: testCaughtReject
Debugger (test.js:12:2)
CaughtReject (test.js:78:4)
Debugger (test.js:10:2)
CaughtReject (test.js:76:4)
-- async function --
CaughtReject (test.js:72:27)
CaughtReject (test.js:72:21)
(anonymous) (testCaughtReject.js:0:0)
Running test: testCaughtThrow
Debugger (test.js:12:2)
CaughtThrow (test.js:88:4)
Debugger (test.js:10:2)
CaughtThrow (test.js:86:4)
-- async function --
CaughtThrow (test.js:82:26)
CaughtThrow (test.js:82:21)
(anonymous) (testCaughtThrow.js:0:0)
Running test: testUncaughtRejectOnBreak
Running test: testUncaughtThrowOnBreak
Debugger (test.js:12:2)
-- async function --
UncaughtThrowOnBreak (test.js:101:35)
Debugger (test.js:10:2)
-- Promise.catch --
UncaughtThrowOnBreak (test.js:105:21)
(anonymous) (testUncaughtThrowOnBreak.js:0:0)
Running test: testCaughtRejectOnBreak
Running test: testCaughtThrowOnBreak
Debugger (test.js:12:2)
CaughtThrowOnBreak (test.js:126:4)
Debugger (test.js:10:2)
CaughtThrowOnBreak (test.js:124:4)
-- async function --
CaughtThrowOnBreak (test.js:120:33)
CaughtThrowOnBreak (test.js:120:21)
(anonymous) (testCaughtThrowOnBreak.js:0:0)

View File

@ -4,7 +4,7 @@
let {session, contextGroup, Protocol} = InspectorTest.start('Checks that async chains for for-await-of are correct.');
contextGroup.addScript(`
contextGroup.addInlineScript(`
function Debugger(value) {
debugger;
@ -48,7 +48,7 @@ async function Basic() {
Debugger();
}
}
// TODO(kozyatinskiy): this stack trace is suspicious.
async function UncaughtReject() {
async function loop() {
for await (let x of [Reject(new Error("boop"))]) {
@ -57,7 +57,7 @@ async function UncaughtReject() {
}
return loop().catch(Debugger);
}
// TODO(kozyatinskiy): this stack trace is suspicious.
async function UncaughtThrow() {
async function loop() {
for await (let x of [Throw(new Error("boop"))]) {
@ -86,7 +86,7 @@ async function CaughtThrow() {
Debugger(e);
}
}
// TODO(kozyatinskiy): this stack trace is suspicious.
async function UncaughtRejectOnBreak() {
async function loop() {
for await (let x of RejectOnReturn(["0", "1"])) {
@ -95,7 +95,7 @@ async function UncaughtRejectOnBreak() {
}
return loop().catch(Debugger);
}
// TODO(kozyatinskiy): this stack trace is suspicious.
async function UncaughtThrowOnBreak() {
async function loop() {
for await (let x of ThrowOnReturn(["0", "1"])) {
@ -104,7 +104,7 @@ async function UncaughtThrowOnBreak() {
}
return loop().catch(Debugger);
}
// TODO(kozyatinskiy): this stack trace is suspicious.
async function CaughtRejectOnBreak() {
try {
for await (let x of RejectOnReturn(["0", "1"])) {
@ -123,8 +123,7 @@ async function CaughtThrowOnBreak() {
} catch (e) {
Debugger(e);
}
}
//# sourceURL=test.js`, 9, 26);
}`, 'test.js');
session.setupScriptMap();
Protocol.Debugger.onPaused(message => {

View File

@ -0,0 +1,25 @@
stepOut async function
bar (test.js:22:2)
-- async function --
bar (test.js:21:16)
foo (test.js:17:8)
-- async function --
foo (test.js:16:16)
test (test.js:12:8)
-- async function --
test (test.js:11:16)
(anonymous) (:0:0)
foo (test.js:18:0)
-- async function --
foo (test.js:16:16)
test (test.js:12:8)
-- async function --
test (test.js:11:16)
(anonymous) (:0:0)
test (test.js:13:0)
-- async function --
test (test.js:11:16)
(anonymous) (:0:0)

View File

@ -0,0 +1,50 @@
// 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.
let {session, contextGroup, Protocol} =
InspectorTest.start('stepOut async function');
session.setupScriptMap();
contextGroup.addInlineScript(`
async function test() {
await Promise.resolve();
await foo();
}
async function foo() {
await Promise.resolve();
await bar();
}
async function bar() {
await Promise.resolve();
debugger;
}
`, 'test.js');
(async function test() {
Protocol.Runtime.enable();
Protocol.Runtime.onConsoleAPICalled(
msg => InspectorTest.log(msg.params.args[0].value));
Protocol.Debugger.enable();
Protocol.Debugger.setAsyncCallStackDepth({maxDepth: 128});
let finished =
Protocol.Runtime.evaluate({expression: 'test()', awaitPromise: true})
.then(() => false);
while (true) {
const r = await Promise.race([finished, waitPauseAndDumpStack()]);
if (!r) break;
Protocol.Debugger.stepOut();
}
InspectorTest.completeTest();
})()
async function
waitPauseAndDumpStack() {
const {params} = await Protocol.Debugger.oncePaused();
session.logCallFrames(params.callFrames);
session.logAsyncStackTrace(params.asyncStackTrace);
InspectorTest.log('');
return true;
}

View File

@ -1,20 +1,20 @@
Checks that async stacks works for async/await
foo2 (test.js:15:2)
-- async function --
foo2 (test.js:13:19)
foo2 (test.js:14:16)
test (test.js:24:8)
(anonymous) (expr.js:0:0)
foo2 (test.js:17:2)
-- async function --
foo2 (test.js:13:19)
foo2 (test.js:14:16)
test (test.js:24:8)
(anonymous) (expr.js:0:0)
foo1 (test.js:9:2)
foo2 (test.js:18:8)
-- async function --
foo2 (test.js:13:19)
foo2 (test.js:14:16)
test (test.js:24:8)
(anonymous) (expr.js:0:0)
@ -22,13 +22,13 @@ foo1 (test.js:9:2)
-- Promise.then --
foo2 (test.js:19:43)
-- async function --
foo2 (test.js:13:19)
foo2 (test.js:14:16)
test (test.js:24:8)
(anonymous) (expr.js:0:0)
foo2 (test.js:20:2)
-- async function --
foo2 (test.js:13:19)
foo2 (test.js:14:16)
test (test.js:24:8)
(anonymous) (expr.js:0:0)

View File

@ -4,7 +4,7 @@
let {session, contextGroup, Protocol} = InspectorTest.start('Checks that async stacks works for async/await');
contextGroup.addScript(`
contextGroup.addInlineScript(`
async function foo1() {
debugger;
return Promise.resolve();
@ -22,8 +22,7 @@ async function foo2() {
async function test() {
await foo2();
}
//# sourceURL=test.js`, 7, 26);
}`, 'test.js');
session.setupScriptMap();
Protocol.Debugger.onPaused(message => {

View File

@ -8,7 +8,7 @@ asyncFact (test.js:9:2)
asyncFact (test.js:11:2)
-- async function --
asyncFact (test.js:8:24)
asyncFact (test.js:10:20)
asyncFact (test.js:10:20)
asyncFact (test.js:10:20)
asyncFact (test.js:10:20)
@ -23,7 +23,7 @@ asyncFact (test.js:9:2)
asyncFact (test.js:11:2)
-- async function --
asyncFact (test.js:8:24)
asyncFact (test.js:10:20)
(anonymous) (expr.js:0:0)

View File

@ -120,6 +120,12 @@ InspectorTest.ContextGroup = class {
utils.compileAndRunWithOrigin(this.id, string, url || '', lineOffset || 0, columnOffset || 0, false);
}
addInlineScript(string, url) {
const match = (new Error().stack).split('\n')[2].match(/([0-9]+):([0-9]+)/);
this.addScript(
string, match[1] * 1, match[1] * 1 + '.addInlineScript('.length, url);
}
addModule(string, url, lineOffset, columnOffset) {
utils.compileAndRunWithOrigin(this.id, string, url, lineOffset || 0, columnOffset || 0, true);
}
@ -213,7 +219,8 @@ InspectorTest.Session = class {
logCallFrames(callFrames) {
for (var frame of callFrames) {
var functionName = frame.functionName || '(anonymous)';
var url = frame.url ? frame.url : this._scriptMap.get(frame.location.scriptId).url;
var scriptId = frame.location ? frame.location.scriptId : frame.scriptId;
var url = frame.url ? frame.url : this._scriptMap.get(scriptId).url;
var lineNumber = frame.location ? frame.location.lineNumber : frame.lineNumber;
var columnNumber = frame.location ? frame.location.columnNumber : frame.columnNumber;
InspectorTest.log(`${functionName} (${url}:${lineNumber}:${columnNumber})`);