• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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