| // 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 |