1 // Copyright 2018 the V8 project authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "src/d8/async-hooks-wrapper.h"
6 #include "src/d8/d8.h"
7 #include "src/execution/isolate-inl.h"
8
9 namespace v8 {
10
Enable()11 void AsyncHooksWrap::Enable() { enabled_ = true; }
12
Disable()13 void AsyncHooksWrap::Disable() { enabled_ = false; }
14
init_function() const15 v8::Local<v8::Function> AsyncHooksWrap::init_function() const {
16 return init_function_.Get(isolate_);
17 }
set_init_function(v8::Local<v8::Function> value)18 void AsyncHooksWrap::set_init_function(v8::Local<v8::Function> value) {
19 init_function_.Reset(isolate_, value);
20 }
before_function() const21 v8::Local<v8::Function> AsyncHooksWrap::before_function() const {
22 return before_function_.Get(isolate_);
23 }
set_before_function(v8::Local<v8::Function> value)24 void AsyncHooksWrap::set_before_function(v8::Local<v8::Function> value) {
25 before_function_.Reset(isolate_, value);
26 }
after_function() const27 v8::Local<v8::Function> AsyncHooksWrap::after_function() const {
28 return after_function_.Get(isolate_);
29 }
set_after_function(v8::Local<v8::Function> value)30 void AsyncHooksWrap::set_after_function(v8::Local<v8::Function> value) {
31 after_function_.Reset(isolate_, value);
32 }
promiseResolve_function() const33 v8::Local<v8::Function> AsyncHooksWrap::promiseResolve_function() const {
34 return promiseResolve_function_.Get(isolate_);
35 }
set_promiseResolve_function(v8::Local<v8::Function> value)36 void AsyncHooksWrap::set_promiseResolve_function(
37 v8::Local<v8::Function> value) {
38 promiseResolve_function_.Reset(isolate_, value);
39 }
40
UnwrapHook(const v8::FunctionCallbackInfo<v8::Value> & args)41 static AsyncHooksWrap* UnwrapHook(
42 const v8::FunctionCallbackInfo<v8::Value>& args) {
43 Isolate* isolate = args.GetIsolate();
44 HandleScope scope(isolate);
45 Local<Object> hook = args.This();
46
47 AsyncHooks* hooks = PerIsolateData::Get(isolate)->GetAsyncHooks();
48
49 if (!hooks->async_hook_ctor.Get(isolate)->HasInstance(hook)) {
50 isolate->ThrowException(String::NewFromUtf8Literal(
51 isolate, "Invalid 'this' passed instead of AsyncHooks instance"));
52 return nullptr;
53 }
54
55 Local<External> wrap = Local<External>::Cast(hook->GetInternalField(0));
56 void* ptr = wrap->Value();
57 return static_cast<AsyncHooksWrap*>(ptr);
58 }
59
EnableHook(const v8::FunctionCallbackInfo<v8::Value> & args)60 static void EnableHook(const v8::FunctionCallbackInfo<v8::Value>& args) {
61 AsyncHooksWrap* wrap = UnwrapHook(args);
62 if (wrap) {
63 wrap->Enable();
64 }
65 }
66
DisableHook(const v8::FunctionCallbackInfo<v8::Value> & args)67 static void DisableHook(const v8::FunctionCallbackInfo<v8::Value>& args) {
68 AsyncHooksWrap* wrap = UnwrapHook(args);
69 if (wrap) {
70 wrap->Disable();
71 }
72 }
73
GetExecutionAsyncId() const74 async_id_t AsyncHooks::GetExecutionAsyncId() const {
75 return asyncContexts.top().execution_async_id;
76 }
77
GetTriggerAsyncId() const78 async_id_t AsyncHooks::GetTriggerAsyncId() const {
79 return asyncContexts.top().trigger_async_id;
80 }
81
CreateHook(const v8::FunctionCallbackInfo<v8::Value> & args)82 Local<Object> AsyncHooks::CreateHook(
83 const v8::FunctionCallbackInfo<v8::Value>& args) {
84 Isolate* isolate = args.GetIsolate();
85 EscapableHandleScope handle_scope(isolate);
86
87 Local<Context> currentContext = isolate->GetCurrentContext();
88
89 if (args.Length() != 1 || !args[0]->IsObject()) {
90 isolate->ThrowException(String::NewFromUtf8Literal(
91 isolate, "Invalid arguments passed to createHook"));
92 return Local<Object>();
93 }
94
95 AsyncHooksWrap* wrap = new AsyncHooksWrap(isolate);
96
97 Local<Object> fn_obj = args[0].As<Object>();
98
99 #define SET_HOOK_FN(name) \
100 Local<Value> name##_v = \
101 fn_obj->Get(currentContext, String::NewFromUtf8Literal(isolate, #name)) \
102 .ToLocalChecked(); \
103 if (name##_v->IsFunction()) { \
104 wrap->set_##name##_function(name##_v.As<Function>()); \
105 }
106
107 SET_HOOK_FN(init);
108 SET_HOOK_FN(before);
109 SET_HOOK_FN(after);
110 SET_HOOK_FN(promiseResolve);
111 #undef SET_HOOK_FN
112
113 async_wraps_.push_back(wrap);
114
115 Local<Object> obj = async_hooks_templ.Get(isolate)
116 ->NewInstance(currentContext)
117 .ToLocalChecked();
118 obj->SetInternalField(0, External::New(isolate, wrap));
119
120 return handle_scope.Escape(obj);
121 }
122
ShellPromiseHook(PromiseHookType type,Local<Promise> promise,Local<Value> parent)123 void AsyncHooks::ShellPromiseHook(PromiseHookType type, Local<Promise> promise,
124 Local<Value> parent) {
125 AsyncHooks* hooks =
126 PerIsolateData::Get(promise->GetIsolate())->GetAsyncHooks();
127
128 HandleScope handle_scope(hooks->isolate_);
129
130 Local<Context> currentContext = hooks->isolate_->GetCurrentContext();
131 DCHECK(!currentContext.IsEmpty());
132
133 if (type == PromiseHookType::kInit) {
134 ++hooks->current_async_id;
135 Local<Integer> async_id =
136 Integer::New(hooks->isolate_, hooks->current_async_id);
137
138 CHECK(!promise
139 ->HasPrivate(currentContext,
140 hooks->async_id_smb.Get(hooks->isolate_))
141 .ToChecked());
142 promise->SetPrivate(currentContext,
143 hooks->async_id_smb.Get(hooks->isolate_), async_id);
144
145 if (parent->IsPromise()) {
146 Local<Promise> parent_promise = parent.As<Promise>();
147 Local<Value> parent_async_id =
148 parent_promise
149 ->GetPrivate(hooks->isolate_->GetCurrentContext(),
150 hooks->async_id_smb.Get(hooks->isolate_))
151 .ToLocalChecked();
152 promise->SetPrivate(currentContext,
153 hooks->trigger_id_smb.Get(hooks->isolate_),
154 parent_async_id);
155 } else {
156 CHECK(parent->IsUndefined());
157 Local<Integer> trigger_id = Integer::New(hooks->isolate_, 0);
158 promise->SetPrivate(currentContext,
159 hooks->trigger_id_smb.Get(hooks->isolate_),
160 trigger_id);
161 }
162 } else if (type == PromiseHookType::kBefore) {
163 AsyncContext ctx;
164 ctx.execution_async_id =
165 promise
166 ->GetPrivate(hooks->isolate_->GetCurrentContext(),
167 hooks->async_id_smb.Get(hooks->isolate_))
168 .ToLocalChecked()
169 .As<Integer>()
170 ->Value();
171 ctx.trigger_async_id =
172 promise
173 ->GetPrivate(hooks->isolate_->GetCurrentContext(),
174 hooks->trigger_id_smb.Get(hooks->isolate_))
175 .ToLocalChecked()
176 .As<Integer>()
177 ->Value();
178 hooks->asyncContexts.push(ctx);
179 } else if (type == PromiseHookType::kAfter) {
180 hooks->asyncContexts.pop();
181 }
182
183 for (AsyncHooksWrap* wrap : hooks->async_wraps_) {
184 PromiseHookDispatch(type, promise, parent, wrap, hooks);
185 }
186 }
187
Initialize()188 void AsyncHooks::Initialize() {
189 HandleScope handle_scope(isolate_);
190
191 async_hook_ctor.Reset(isolate_, FunctionTemplate::New(isolate_));
192 async_hook_ctor.Get(isolate_)->SetClassName(
193 String::NewFromUtf8Literal(isolate_, "AsyncHook"));
194
195 async_hooks_templ.Reset(isolate_,
196 async_hook_ctor.Get(isolate_)->InstanceTemplate());
197 async_hooks_templ.Get(isolate_)->SetInternalFieldCount(1);
198 async_hooks_templ.Get(isolate_)->Set(
199 isolate_, "enable", FunctionTemplate::New(isolate_, EnableHook));
200 async_hooks_templ.Get(isolate_)->Set(
201 isolate_, "disable", FunctionTemplate::New(isolate_, DisableHook));
202
203 async_id_smb.Reset(isolate_, Private::New(isolate_));
204 trigger_id_smb.Reset(isolate_, Private::New(isolate_));
205
206 isolate_->SetPromiseHook(ShellPromiseHook);
207 }
208
Deinitialize()209 void AsyncHooks::Deinitialize() {
210 isolate_->SetPromiseHook(nullptr);
211 for (AsyncHooksWrap* wrap : async_wraps_) {
212 delete wrap;
213 }
214 }
215
PromiseHookDispatch(PromiseHookType type,Local<Promise> promise,Local<Value> parent,AsyncHooksWrap * wrap,AsyncHooks * hooks)216 void AsyncHooks::PromiseHookDispatch(PromiseHookType type,
217 Local<Promise> promise,
218 Local<Value> parent, AsyncHooksWrap* wrap,
219 AsyncHooks* hooks) {
220 if (!wrap->IsEnabled()) {
221 return;
222 }
223
224 HandleScope handle_scope(hooks->isolate_);
225
226 TryCatch try_catch(hooks->isolate_);
227 try_catch.SetVerbose(true);
228
229 i::Isolate* isolate = reinterpret_cast<i::Isolate*>(hooks->isolate_);
230 if (isolate->has_scheduled_exception()) {
231 isolate->ScheduleThrow(isolate->scheduled_exception());
232
233 DCHECK(try_catch.HasCaught());
234 Shell::ReportException(hooks->isolate_, &try_catch);
235 return;
236 }
237
238 Local<Value> rcv = Undefined(hooks->isolate_);
239 Local<Context> context = hooks->isolate_->GetCurrentContext();
240 Local<Value> async_id =
241 promise->GetPrivate(context, hooks->async_id_smb.Get(hooks->isolate_))
242 .ToLocalChecked();
243 Local<Value> args[1] = {async_id};
244
245 // This is unused. It's here to silence the warning about
246 // not using the MaybeLocal return value from Call.
247 MaybeLocal<Value> result;
248
249 // Sacrifice the brevity for readability and debugfulness
250 if (type == PromiseHookType::kInit) {
251 if (!wrap->init_function().IsEmpty()) {
252 Local<Value> initArgs[4] = {
253 async_id, String::NewFromUtf8Literal(hooks->isolate_, "PROMISE"),
254 promise
255 ->GetPrivate(context, hooks->trigger_id_smb.Get(hooks->isolate_))
256 .ToLocalChecked(),
257 promise};
258 result = wrap->init_function()->Call(context, rcv, 4, initArgs);
259 }
260 } else if (type == PromiseHookType::kBefore) {
261 if (!wrap->before_function().IsEmpty()) {
262 result = wrap->before_function()->Call(context, rcv, 1, args);
263 }
264 } else if (type == PromiseHookType::kAfter) {
265 if (!wrap->after_function().IsEmpty()) {
266 result = wrap->after_function()->Call(context, rcv, 1, args);
267 }
268 } else if (type == PromiseHookType::kResolve) {
269 if (!wrap->promiseResolve_function().IsEmpty()) {
270 result = wrap->promiseResolve_function()->Call(context, rcv, 1, args);
271 }
272 }
273 }
274
275 } // namespace v8
276