// 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.
|
|
#include "src/async-hooks-wrapper.h"
|
#include "src/d8.h"
|
#include "src/isolate-inl.h"
|
|
namespace v8 {
|
|
void AsyncHooksWrap::Enable() { enabled_ = true; }
|
|
void AsyncHooksWrap::Disable() { enabled_ = false; }
|
|
v8::Local<v8::Function> AsyncHooksWrap::init_function() const {
|
return init_function_.Get(isolate_);
|
}
|
void AsyncHooksWrap::set_init_function(v8::Local<v8::Function> value) {
|
init_function_.Reset(isolate_, value);
|
}
|
v8::Local<v8::Function> AsyncHooksWrap::before_function() const {
|
return before_function_.Get(isolate_);
|
}
|
void AsyncHooksWrap::set_before_function(v8::Local<v8::Function> value) {
|
before_function_.Reset(isolate_, value);
|
}
|
v8::Local<v8::Function> AsyncHooksWrap::after_function() const {
|
return after_function_.Get(isolate_);
|
}
|
void AsyncHooksWrap::set_after_function(v8::Local<v8::Function> value) {
|
after_function_.Reset(isolate_, value);
|
}
|
v8::Local<v8::Function> AsyncHooksWrap::promiseResolve_function() const {
|
return promiseResolve_function_.Get(isolate_);
|
}
|
void AsyncHooksWrap::set_promiseResolve_function(
|
v8::Local<v8::Function> value) {
|
promiseResolve_function_.Reset(isolate_, value);
|
}
|
|
static AsyncHooksWrap* UnwrapHook(
|
const v8::FunctionCallbackInfo<v8::Value>& args) {
|
Isolate* isolate = args.GetIsolate();
|
HandleScope scope(isolate);
|
Local<Object> hook = args.This();
|
|
AsyncHooks* hooks = PerIsolateData::Get(isolate)->GetAsyncHooks();
|
|
if (!hooks->async_hook_ctor.Get(isolate)->HasInstance(hook)) {
|
isolate->ThrowException(
|
String::NewFromUtf8(
|
isolate, "Invalid 'this' passed instead of AsyncHooks instance",
|
NewStringType::kNormal)
|
.ToLocalChecked());
|
return nullptr;
|
}
|
|
Local<External> wrap = Local<External>::Cast(hook->GetInternalField(0));
|
void* ptr = wrap->Value();
|
return static_cast<AsyncHooksWrap*>(ptr);
|
}
|
|
static void EnableHook(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
AsyncHooksWrap* wrap = UnwrapHook(args);
|
if (wrap) {
|
wrap->Enable();
|
}
|
}
|
|
static void DisableHook(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
AsyncHooksWrap* wrap = UnwrapHook(args);
|
if (wrap) {
|
wrap->Disable();
|
}
|
}
|
|
async_id_t AsyncHooks::GetExecutionAsyncId() const {
|
return asyncContexts.top().execution_async_id;
|
}
|
|
async_id_t AsyncHooks::GetTriggerAsyncId() const {
|
return asyncContexts.top().trigger_async_id;
|
}
|
|
Local<Object> AsyncHooks::CreateHook(
|
const v8::FunctionCallbackInfo<v8::Value>& args) {
|
Isolate* isolate = args.GetIsolate();
|
EscapableHandleScope handle_scope(isolate);
|
|
Local<Context> currentContext = isolate->GetCurrentContext();
|
|
if (args.Length() != 1 || !args[0]->IsObject()) {
|
isolate->ThrowException(
|
String::NewFromUtf8(isolate, "Invalid arguments passed to createHook",
|
NewStringType::kNormal)
|
.ToLocalChecked());
|
return Local<Object>();
|
}
|
|
AsyncHooksWrap* wrap = new AsyncHooksWrap(isolate);
|
|
Local<Object> fn_obj = args[0].As<Object>();
|
|
#define SET_HOOK_FN(name) \
|
Local<Value> name##_v = \
|
fn_obj \
|
->Get(currentContext, \
|
String::NewFromUtf8(isolate, #name, NewStringType::kNormal) \
|
.ToLocalChecked()) \
|
.ToLocalChecked(); \
|
if (name##_v->IsFunction()) { \
|
wrap->set_##name##_function(name##_v.As<Function>()); \
|
}
|
|
SET_HOOK_FN(init);
|
SET_HOOK_FN(before);
|
SET_HOOK_FN(after);
|
SET_HOOK_FN(promiseResolve);
|
#undef SET_HOOK_FN
|
|
async_wraps_.push_back(wrap);
|
|
Local<Object> obj = async_hooks_templ.Get(isolate)
|
->NewInstance(currentContext)
|
.ToLocalChecked();
|
obj->SetInternalField(0, External::New(isolate, wrap));
|
|
return handle_scope.Escape(obj);
|
}
|
|
void AsyncHooks::ShellPromiseHook(PromiseHookType type, Local<Promise> promise,
|
Local<Value> parent) {
|
AsyncHooks* hooks =
|
PerIsolateData::Get(promise->GetIsolate())->GetAsyncHooks();
|
|
HandleScope handle_scope(hooks->isolate_);
|
|
Local<Context> currentContext = hooks->isolate_->GetCurrentContext();
|
|
if (type == PromiseHookType::kInit) {
|
++hooks->current_async_id;
|
Local<Integer> async_id =
|
Integer::New(hooks->isolate_, hooks->current_async_id);
|
|
promise->SetPrivate(currentContext,
|
hooks->async_id_smb.Get(hooks->isolate_), async_id);
|
if (parent->IsPromise()) {
|
Local<Promise> parent_promise = parent.As<Promise>();
|
Local<Value> parent_async_id =
|
parent_promise
|
->GetPrivate(hooks->isolate_->GetCurrentContext(),
|
hooks->async_id_smb.Get(hooks->isolate_))
|
.ToLocalChecked();
|
promise->SetPrivate(currentContext,
|
hooks->trigger_id_smb.Get(hooks->isolate_),
|
parent_async_id);
|
} else {
|
CHECK(parent->IsUndefined());
|
Local<Integer> trigger_id = Integer::New(hooks->isolate_, 0);
|
promise->SetPrivate(currentContext,
|
hooks->trigger_id_smb.Get(hooks->isolate_),
|
trigger_id);
|
}
|
} else if (type == PromiseHookType::kBefore) {
|
AsyncContext ctx;
|
ctx.execution_async_id =
|
promise
|
->GetPrivate(hooks->isolate_->GetCurrentContext(),
|
hooks->async_id_smb.Get(hooks->isolate_))
|
.ToLocalChecked()
|
.As<Integer>()
|
->Value();
|
ctx.trigger_async_id =
|
promise
|
->GetPrivate(hooks->isolate_->GetCurrentContext(),
|
hooks->trigger_id_smb.Get(hooks->isolate_))
|
.ToLocalChecked()
|
.As<Integer>()
|
->Value();
|
hooks->asyncContexts.push(ctx);
|
} else if (type == PromiseHookType::kAfter) {
|
hooks->asyncContexts.pop();
|
}
|
|
for (AsyncHooksWrap* wrap : hooks->async_wraps_) {
|
PromiseHookDispatch(type, promise, parent, wrap, hooks);
|
}
|
}
|
|
void AsyncHooks::Initialize() {
|
HandleScope handle_scope(isolate_);
|
|
async_hook_ctor.Reset(isolate_, FunctionTemplate::New(isolate_));
|
async_hook_ctor.Get(isolate_)->SetClassName(
|
String::NewFromUtf8(isolate_, "AsyncHook", NewStringType::kNormal)
|
.ToLocalChecked());
|
|
async_hooks_templ.Reset(isolate_,
|
async_hook_ctor.Get(isolate_)->InstanceTemplate());
|
async_hooks_templ.Get(isolate_)->SetInternalFieldCount(1);
|
async_hooks_templ.Get(isolate_)->Set(
|
String::NewFromUtf8(isolate_, "enable", v8::NewStringType::kNormal)
|
.ToLocalChecked(),
|
FunctionTemplate::New(isolate_, EnableHook));
|
async_hooks_templ.Get(isolate_)->Set(
|
String::NewFromUtf8(isolate_, "disable", v8::NewStringType::kNormal)
|
.ToLocalChecked(),
|
FunctionTemplate::New(isolate_, DisableHook));
|
|
async_id_smb.Reset(isolate_, Private::New(isolate_));
|
trigger_id_smb.Reset(isolate_, Private::New(isolate_));
|
|
isolate_->SetPromiseHook(ShellPromiseHook);
|
}
|
|
void AsyncHooks::Deinitialize() {
|
isolate_->SetPromiseHook(nullptr);
|
for (AsyncHooksWrap* wrap : async_wraps_) {
|
delete wrap;
|
}
|
}
|
|
void AsyncHooks::PromiseHookDispatch(PromiseHookType type,
|
Local<Promise> promise,
|
Local<Value> parent, AsyncHooksWrap* wrap,
|
AsyncHooks* hooks) {
|
if (!wrap->IsEnabled()) {
|
return;
|
}
|
|
HandleScope handle_scope(hooks->isolate_);
|
|
TryCatch try_catch(hooks->isolate_);
|
try_catch.SetVerbose(true);
|
|
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(hooks->isolate_);
|
if (isolate->has_scheduled_exception()) {
|
isolate->ScheduleThrow(isolate->scheduled_exception());
|
|
DCHECK(try_catch.HasCaught());
|
Shell::ReportException(hooks->isolate_, &try_catch);
|
return;
|
}
|
|
Local<Value> rcv = Undefined(hooks->isolate_);
|
Local<Context> context = hooks->isolate_->GetCurrentContext();
|
Local<Value> async_id =
|
promise->GetPrivate(context, hooks->async_id_smb.Get(hooks->isolate_))
|
.ToLocalChecked();
|
Local<Value> args[1] = {async_id};
|
|
// This is unused. It's here to silence the warning about
|
// not using the MaybeLocal return value from Call.
|
MaybeLocal<Value> result;
|
|
// Sacrifice the brevity for readability and debugfulness
|
if (type == PromiseHookType::kInit) {
|
if (!wrap->init_function().IsEmpty()) {
|
Local<Value> initArgs[4] = {
|
async_id,
|
String::NewFromUtf8(hooks->isolate_, "PROMISE",
|
NewStringType::kNormal)
|
.ToLocalChecked(),
|
promise
|
->GetPrivate(context, hooks->trigger_id_smb.Get(hooks->isolate_))
|
.ToLocalChecked(),
|
promise};
|
result = wrap->init_function()->Call(context, rcv, 4, initArgs);
|
}
|
} else if (type == PromiseHookType::kBefore) {
|
if (!wrap->before_function().IsEmpty()) {
|
result = wrap->before_function()->Call(context, rcv, 1, args);
|
}
|
} else if (type == PromiseHookType::kAfter) {
|
if (!wrap->after_function().IsEmpty()) {
|
result = wrap->after_function()->Call(context, rcv, 1, args);
|
}
|
} else if (type == PromiseHookType::kResolve) {
|
if (!wrap->promiseResolve_function().IsEmpty()) {
|
result = wrap->promiseResolve_function()->Call(context, rcv, 1, args);
|
}
|
}
|
|
if (try_catch.HasCaught()) {
|
Shell::ReportException(hooks->isolate_, &try_catch);
|
}
|
}
|
|
} // namespace v8
|