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/async-hooks-wrapper.h"
6 #include "src/d8.h"
7 #include "src/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(
51 String::NewFromUtf8(
52 isolate, "Invalid 'this' passed instead of AsyncHooks instance",
53 NewStringType::kNormal)
54 .ToLocalChecked());
55 return nullptr;
56 }
57
58 Local<External> wrap = Local<External>::Cast(hook->GetInternalField(0));
59 void* ptr = wrap->Value();
60 return static_cast<AsyncHooksWrap*>(ptr);
61 }
62
EnableHook(const v8::FunctionCallbackInfo<v8::Value> & args)63 static void EnableHook(const v8::FunctionCallbackInfo<v8::Value>& args) {
64 AsyncHooksWrap* wrap = UnwrapHook(args);
65 if (wrap) {
66 wrap->Enable();
67 }
68 }
69
DisableHook(const v8::FunctionCallbackInfo<v8::Value> & args)70 static void DisableHook(const v8::FunctionCallbackInfo<v8::Value>& args) {
71 AsyncHooksWrap* wrap = UnwrapHook(args);
72 if (wrap) {
73 wrap->Disable();
74 }
75 }
76
GetExecutionAsyncId() const77 async_id_t AsyncHooks::GetExecutionAsyncId() const {
78 return asyncContexts.top().execution_async_id;
79 }
80
GetTriggerAsyncId() const81 async_id_t AsyncHooks::GetTriggerAsyncId() const {
82 return asyncContexts.top().trigger_async_id;
83 }
84
CreateHook(const v8::FunctionCallbackInfo<v8::Value> & args)85 Local<Object> AsyncHooks::CreateHook(
86 const v8::FunctionCallbackInfo<v8::Value>& args) {
87 Isolate* isolate = args.GetIsolate();
88 EscapableHandleScope handle_scope(isolate);
89
90 Local<Context> currentContext = isolate->GetCurrentContext();
91
92 if (args.Length() != 1 || !args[0]->IsObject()) {
93 isolate->ThrowException(
94 String::NewFromUtf8(isolate, "Invalid arguments passed to createHook",
95 NewStringType::kNormal)
96 .ToLocalChecked());
97 return Local<Object>();
98 }
99
100 AsyncHooksWrap* wrap = new AsyncHooksWrap(isolate);
101
102 Local<Object> fn_obj = args[0].As<Object>();
103
104 #define SET_HOOK_FN(name) \
105 Local<Value> name##_v = \
106 fn_obj \
107 ->Get(currentContext, \
108 String::NewFromUtf8(isolate, #name, NewStringType::kNormal) \
109 .ToLocalChecked()) \
110 .ToLocalChecked(); \
111 if (name##_v->IsFunction()) { \
112 wrap->set_##name##_function(name##_v.As<Function>()); \
113 }
114
115 SET_HOOK_FN(init);
116 SET_HOOK_FN(before);
117 SET_HOOK_FN(after);
118 SET_HOOK_FN(promiseResolve);
119 #undef SET_HOOK_FN
120
121 async_wraps_.push_back(wrap);
122
123 Local<Object> obj = async_hooks_templ.Get(isolate)
124 ->NewInstance(currentContext)
125 .ToLocalChecked();
126 obj->SetInternalField(0, External::New(isolate, wrap));
127
128 return handle_scope.Escape(obj);
129 }
130
ShellPromiseHook(PromiseHookType type,Local<Promise> promise,Local<Value> parent)131 void AsyncHooks::ShellPromiseHook(PromiseHookType type, Local<Promise> promise,
132 Local<Value> parent) {
133 AsyncHooks* hooks =
134 PerIsolateData::Get(promise->GetIsolate())->GetAsyncHooks();
135
136 HandleScope handle_scope(hooks->isolate_);
137
138 Local<Context> currentContext = hooks->isolate_->GetCurrentContext();
139
140 if (type == PromiseHookType::kInit) {
141 ++hooks->current_async_id;
142 Local<Integer> async_id =
143 Integer::New(hooks->isolate_, hooks->current_async_id);
144
145 promise->SetPrivate(currentContext,
146 hooks->async_id_smb.Get(hooks->isolate_), async_id);
147 if (parent->IsPromise()) {
148 Local<Promise> parent_promise = parent.As<Promise>();
149 Local<Value> parent_async_id =
150 parent_promise
151 ->GetPrivate(hooks->isolate_->GetCurrentContext(),
152 hooks->async_id_smb.Get(hooks->isolate_))
153 .ToLocalChecked();
154 promise->SetPrivate(currentContext,
155 hooks->trigger_id_smb.Get(hooks->isolate_),
156 parent_async_id);
157 } else {
158 CHECK(parent->IsUndefined());
159 Local<Integer> trigger_id = Integer::New(hooks->isolate_, 0);
160 promise->SetPrivate(currentContext,
161 hooks->trigger_id_smb.Get(hooks->isolate_),
162 trigger_id);
163 }
164 } else if (type == PromiseHookType::kBefore) {
165 AsyncContext ctx;
166 ctx.execution_async_id =
167 promise
168 ->GetPrivate(hooks->isolate_->GetCurrentContext(),
169 hooks->async_id_smb.Get(hooks->isolate_))
170 .ToLocalChecked()
171 .As<Integer>()
172 ->Value();
173 ctx.trigger_async_id =
174 promise
175 ->GetPrivate(hooks->isolate_->GetCurrentContext(),
176 hooks->trigger_id_smb.Get(hooks->isolate_))
177 .ToLocalChecked()
178 .As<Integer>()
179 ->Value();
180 hooks->asyncContexts.push(ctx);
181 } else if (type == PromiseHookType::kAfter) {
182 hooks->asyncContexts.pop();
183 }
184
185 for (AsyncHooksWrap* wrap : hooks->async_wraps_) {
186 PromiseHookDispatch(type, promise, parent, wrap, hooks);
187 }
188 }
189
Initialize()190 void AsyncHooks::Initialize() {
191 HandleScope handle_scope(isolate_);
192
193 async_hook_ctor.Reset(isolate_, FunctionTemplate::New(isolate_));
194 async_hook_ctor.Get(isolate_)->SetClassName(
195 String::NewFromUtf8(isolate_, "AsyncHook", NewStringType::kNormal)
196 .ToLocalChecked());
197
198 async_hooks_templ.Reset(isolate_,
199 async_hook_ctor.Get(isolate_)->InstanceTemplate());
200 async_hooks_templ.Get(isolate_)->SetInternalFieldCount(1);
201 async_hooks_templ.Get(isolate_)->Set(
202 String::NewFromUtf8(isolate_, "enable", v8::NewStringType::kNormal)
203 .ToLocalChecked(),
204 FunctionTemplate::New(isolate_, EnableHook));
205 async_hooks_templ.Get(isolate_)->Set(
206 String::NewFromUtf8(isolate_, "disable", v8::NewStringType::kNormal)
207 .ToLocalChecked(),
208 FunctionTemplate::New(isolate_, DisableHook));
209
210 async_id_smb.Reset(isolate_, Private::New(isolate_));
211 trigger_id_smb.Reset(isolate_, Private::New(isolate_));
212
213 isolate_->SetPromiseHook(ShellPromiseHook);
214 }
215
Deinitialize()216 void AsyncHooks::Deinitialize() {
217 isolate_->SetPromiseHook(nullptr);
218 for (AsyncHooksWrap* wrap : async_wraps_) {
219 delete wrap;
220 }
221 }
222
PromiseHookDispatch(PromiseHookType type,Local<Promise> promise,Local<Value> parent,AsyncHooksWrap * wrap,AsyncHooks * hooks)223 void AsyncHooks::PromiseHookDispatch(PromiseHookType type,
224 Local<Promise> promise,
225 Local<Value> parent, AsyncHooksWrap* wrap,
226 AsyncHooks* hooks) {
227 if (!wrap->IsEnabled()) {
228 return;
229 }
230
231 HandleScope handle_scope(hooks->isolate_);
232
233 TryCatch try_catch(hooks->isolate_);
234 try_catch.SetVerbose(true);
235
236 i::Isolate* isolate = reinterpret_cast<i::Isolate*>(hooks->isolate_);
237 if (isolate->has_scheduled_exception()) {
238 isolate->ScheduleThrow(isolate->scheduled_exception());
239
240 DCHECK(try_catch.HasCaught());
241 Shell::ReportException(hooks->isolate_, &try_catch);
242 return;
243 }
244
245 Local<Value> rcv = Undefined(hooks->isolate_);
246 Local<Context> context = hooks->isolate_->GetCurrentContext();
247 Local<Value> async_id =
248 promise->GetPrivate(context, hooks->async_id_smb.Get(hooks->isolate_))
249 .ToLocalChecked();
250 Local<Value> args[1] = {async_id};
251
252 // This is unused. It's here to silence the warning about
253 // not using the MaybeLocal return value from Call.
254 MaybeLocal<Value> result;
255
256 // Sacrifice the brevity for readability and debugfulness
257 if (type == PromiseHookType::kInit) {
258 if (!wrap->init_function().IsEmpty()) {
259 Local<Value> initArgs[4] = {
260 async_id,
261 String::NewFromUtf8(hooks->isolate_, "PROMISE",
262 NewStringType::kNormal)
263 .ToLocalChecked(),
264 promise
265 ->GetPrivate(context, hooks->trigger_id_smb.Get(hooks->isolate_))
266 .ToLocalChecked(),
267 promise};
268 result = wrap->init_function()->Call(context, rcv, 4, initArgs);
269 }
270 } else if (type == PromiseHookType::kBefore) {
271 if (!wrap->before_function().IsEmpty()) {
272 result = wrap->before_function()->Call(context, rcv, 1, args);
273 }
274 } else if (type == PromiseHookType::kAfter) {
275 if (!wrap->after_function().IsEmpty()) {
276 result = wrap->after_function()->Call(context, rcv, 1, args);
277 }
278 } else if (type == PromiseHookType::kResolve) {
279 if (!wrap->promiseResolve_function().IsEmpty()) {
280 result = wrap->promiseResolve_function()->Call(context, rcv, 1, args);
281 }
282 }
283
284 if (try_catch.HasCaught()) {
285 Shell::ReportException(hooks->isolate_, &try_catch);
286 }
287 }
288
289 } // namespace v8
290