• 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/d8/async-hooks-wrapper.h"
6 
7 #include "include/v8-function.h"
8 #include "include/v8-local-handle.h"
9 #include "include/v8-primitive.h"
10 #include "include/v8-template.h"
11 #include "src/api/api-inl.h"
12 #include "src/api/api.h"
13 #include "src/d8/d8.h"
14 #include "src/execution/isolate-inl.h"
15 #include "src/objects/managed-inl.h"
16 
17 namespace v8 {
18 
19 namespace {
UnwrapHook(const v8::FunctionCallbackInfo<v8::Value> & args)20 std::shared_ptr<AsyncHooksWrap> UnwrapHook(
21     const v8::FunctionCallbackInfo<v8::Value>& args) {
22   Isolate* isolate = args.GetIsolate();
23   HandleScope scope(isolate);
24   Local<Object> hook = args.This();
25 
26   AsyncHooks* hooks = PerIsolateData::Get(isolate)->GetAsyncHooks();
27 
28   if (!hooks->async_hook_ctor.Get(isolate)->HasInstance(hook)) {
29     isolate->ThrowError("Invalid 'this' passed instead of AsyncHooks instance");
30     return nullptr;
31   }
32 
33   i::Handle<i::Object> handle = Utils::OpenHandle(*hook->GetInternalField(0));
34   return i::Handle<i::Managed<AsyncHooksWrap>>::cast(handle)->get();
35 }
36 
EnableHook(const v8::FunctionCallbackInfo<v8::Value> & args)37 void EnableHook(const v8::FunctionCallbackInfo<v8::Value>& args) {
38   auto wrap = UnwrapHook(args);
39   if (wrap) wrap->Enable();
40 }
41 
DisableHook(const v8::FunctionCallbackInfo<v8::Value> & args)42 void DisableHook(const v8::FunctionCallbackInfo<v8::Value>& args) {
43   auto wrap = UnwrapHook(args);
44   if (wrap) wrap->Disable();
45 }
46 
47 }  // namespace
48 
AsyncHooks(Isolate * isolate)49 AsyncHooks::AsyncHooks(Isolate* isolate) : isolate_(isolate) {
50   AsyncContext ctx;
51   ctx.execution_async_id = 1;
52   ctx.trigger_async_id = 0;
53   asyncContexts.push(ctx);
54   current_async_id = 1;
55 
56   HandleScope handle_scope(isolate_);
57 
58   async_hook_ctor.Reset(isolate_, FunctionTemplate::New(isolate_));
59   async_hook_ctor.Get(isolate_)->SetClassName(
60       String::NewFromUtf8Literal(isolate_, "AsyncHook"));
61 
62   async_hooks_templ.Reset(isolate_,
63                           async_hook_ctor.Get(isolate_)->InstanceTemplate());
64   async_hooks_templ.Get(isolate_)->SetInternalFieldCount(1);
65   async_hooks_templ.Get(isolate_)->Set(
66       isolate_, "enable", FunctionTemplate::New(isolate_, EnableHook));
67   async_hooks_templ.Get(isolate_)->Set(
68       isolate_, "disable", FunctionTemplate::New(isolate_, DisableHook));
69 
70   async_id_smb.Reset(isolate_, Private::New(isolate_));
71   trigger_id_smb.Reset(isolate_, Private::New(isolate_));
72 
73   isolate_->SetPromiseHook(ShellPromiseHook);
74 }
75 
~AsyncHooks()76 AsyncHooks::~AsyncHooks() {
77   isolate_->SetPromiseHook(nullptr);
78   base::RecursiveMutexGuard lock_guard(&async_wraps_mutex_);
79   async_wraps_.clear();
80 }
81 
Enable()82 void AsyncHooksWrap::Enable() { enabled_ = true; }
83 
Disable()84 void AsyncHooksWrap::Disable() { enabled_ = false; }
85 
init_function() const86 v8::Local<v8::Function> AsyncHooksWrap::init_function() const {
87   return init_function_.Get(isolate_);
88 }
set_init_function(v8::Local<v8::Function> value)89 void AsyncHooksWrap::set_init_function(v8::Local<v8::Function> value) {
90   init_function_.Reset(isolate_, value);
91 }
before_function() const92 v8::Local<v8::Function> AsyncHooksWrap::before_function() const {
93   return before_function_.Get(isolate_);
94 }
set_before_function(v8::Local<v8::Function> value)95 void AsyncHooksWrap::set_before_function(v8::Local<v8::Function> value) {
96   before_function_.Reset(isolate_, value);
97 }
after_function() const98 v8::Local<v8::Function> AsyncHooksWrap::after_function() const {
99   return after_function_.Get(isolate_);
100 }
set_after_function(v8::Local<v8::Function> value)101 void AsyncHooksWrap::set_after_function(v8::Local<v8::Function> value) {
102   after_function_.Reset(isolate_, value);
103 }
promiseResolve_function() const104 v8::Local<v8::Function> AsyncHooksWrap::promiseResolve_function() const {
105   return promiseResolve_function_.Get(isolate_);
106 }
set_promiseResolve_function(v8::Local<v8::Function> value)107 void AsyncHooksWrap::set_promiseResolve_function(
108     v8::Local<v8::Function> value) {
109   promiseResolve_function_.Reset(isolate_, value);
110 }
111 
GetExecutionAsyncId() const112 async_id_t AsyncHooks::GetExecutionAsyncId() const {
113   return asyncContexts.top().execution_async_id;
114 }
115 
GetTriggerAsyncId() const116 async_id_t AsyncHooks::GetTriggerAsyncId() const {
117   return asyncContexts.top().trigger_async_id;
118 }
119 
CreateHook(const v8::FunctionCallbackInfo<v8::Value> & args)120 Local<Object> AsyncHooks::CreateHook(
121     const v8::FunctionCallbackInfo<v8::Value>& args) {
122   Isolate* isolate = args.GetIsolate();
123   EscapableHandleScope handle_scope(isolate);
124 
125   Local<Context> currentContext = isolate->GetCurrentContext();
126 
127   if (args.Length() != 1 || !args[0]->IsObject()) {
128     isolate->ThrowError("Invalid arguments passed to createHook");
129     return Local<Object>();
130   }
131 
132   std::shared_ptr<AsyncHooksWrap> wrap =
133       std::make_shared<AsyncHooksWrap>(isolate);
134 
135   Local<Object> fn_obj = args[0].As<Object>();
136 
137 #define SET_HOOK_FN(name)                                                     \
138   Local<Value> name##_v =                                                     \
139       fn_obj->Get(currentContext, String::NewFromUtf8Literal(isolate, #name)) \
140           .ToLocalChecked();                                                  \
141   if (name##_v->IsFunction()) {                                               \
142     wrap->set_##name##_function(name##_v.As<Function>());                     \
143   }
144 
145   SET_HOOK_FN(init);
146   SET_HOOK_FN(before);
147   SET_HOOK_FN(after);
148   SET_HOOK_FN(promiseResolve);
149 #undef SET_HOOK_FN
150 
151   Local<Object> obj = async_hooks_templ.Get(isolate)
152                           ->NewInstance(currentContext)
153                           .ToLocalChecked();
154   i::Handle<i::Object> managed = i::Managed<AsyncHooksWrap>::FromSharedPtr(
155       reinterpret_cast<i::Isolate*>(isolate), sizeof(AsyncHooksWrap), wrap);
156   obj->SetInternalField(0, Utils::ToLocal(managed));
157 
158   {
159     base::RecursiveMutexGuard lock_guard(&async_wraps_mutex_);
160     async_wraps_.push_back(std::move(wrap));
161   }
162 
163   return handle_scope.Escape(obj);
164 }
165 
ShellPromiseHook(PromiseHookType type,Local<Promise> promise,Local<Value> parent)166 void AsyncHooks::ShellPromiseHook(PromiseHookType type, Local<Promise> promise,
167                                   Local<Value> parent) {
168   v8::Isolate* isolate = promise->GetIsolate();
169   i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
170 
171   AsyncHooks* hooks = PerIsolateData::Get(isolate)->GetAsyncHooks();
172   HandleScope handle_scope(isolate);
173   // Temporarily clear any scheduled_exception to allow evaluating JS that can
174   // throw.
175   i::Handle<i::Object> scheduled_exception;
176   if (i_isolate->has_scheduled_exception()) {
177     scheduled_exception = handle(i_isolate->scheduled_exception(), i_isolate);
178     i_isolate->clear_scheduled_exception();
179   }
180   {
181     TryCatch try_catch(isolate);
182     try_catch.SetVerbose(true);
183 
184     Local<Context> currentContext = isolate->GetCurrentContext();
185     DCHECK(!currentContext.IsEmpty());
186 
187     if (type == PromiseHookType::kInit) {
188       ++hooks->current_async_id;
189       Local<Integer> async_id = Integer::New(isolate, hooks->current_async_id);
190       CHECK(
191           !promise->HasPrivate(currentContext, hooks->async_id_smb.Get(isolate))
192                .ToChecked());
193       promise->SetPrivate(currentContext, hooks->async_id_smb.Get(isolate),
194                           async_id);
195 
196       if (parent->IsPromise()) {
197         Local<Promise> parent_promise = parent.As<Promise>();
198         Local<Value> parent_async_id =
199             parent_promise
200                 ->GetPrivate(currentContext, hooks->async_id_smb.Get(isolate))
201                 .ToLocalChecked();
202         promise->SetPrivate(currentContext, hooks->trigger_id_smb.Get(isolate),
203                             parent_async_id);
204       } else {
205         CHECK(parent->IsUndefined());
206         promise->SetPrivate(currentContext, hooks->trigger_id_smb.Get(isolate),
207                             Integer::New(isolate, 0));
208       }
209     } else if (type == PromiseHookType::kBefore) {
210       AsyncContext ctx;
211       ctx.execution_async_id =
212           promise->GetPrivate(currentContext, hooks->async_id_smb.Get(isolate))
213               .ToLocalChecked()
214               .As<Integer>()
215               ->Value();
216       ctx.trigger_async_id =
217           promise
218               ->GetPrivate(currentContext, hooks->trigger_id_smb.Get(isolate))
219               .ToLocalChecked()
220               .As<Integer>()
221               ->Value();
222       hooks->asyncContexts.push(ctx);
223     } else if (type == PromiseHookType::kAfter) {
224       hooks->asyncContexts.pop();
225     }
226     if (!i::StackLimitCheck{i_isolate}.HasOverflowed()) {
227       base::RecursiveMutexGuard lock_guard(&hooks->async_wraps_mutex_);
228       for (const auto& wrap : hooks->async_wraps_) {
229         PromiseHookDispatch(type, promise, parent, *wrap, hooks);
230         if (try_catch.HasCaught()) break;
231       }
232       if (try_catch.HasCaught()) Shell::ReportException(isolate, &try_catch);
233     }
234   }
235   if (!scheduled_exception.is_null()) {
236     i_isolate->set_scheduled_exception(*scheduled_exception);
237   }
238 }
239 
PromiseHookDispatch(PromiseHookType type,Local<Promise> promise,Local<Value> parent,const AsyncHooksWrap & wrap,AsyncHooks * hooks)240 void AsyncHooks::PromiseHookDispatch(PromiseHookType type,
241                                      Local<Promise> promise,
242                                      Local<Value> parent,
243                                      const AsyncHooksWrap& wrap,
244                                      AsyncHooks* hooks) {
245   if (!wrap.IsEnabled()) return;
246   v8::Isolate* v8_isolate = hooks->isolate_;
247   HandleScope handle_scope(v8_isolate);
248 
249   Local<Value> rcv = Undefined(v8_isolate);
250   Local<Context> context = v8_isolate->GetCurrentContext();
251   Local<Value> async_id =
252       promise->GetPrivate(context, hooks->async_id_smb.Get(v8_isolate))
253           .ToLocalChecked();
254   Local<Value> args[1] = {async_id};
255 
256   switch (type) {
257     case PromiseHookType::kInit:
258       if (!wrap.init_function().IsEmpty()) {
259         Local<Value> initArgs[4] = {
260             async_id, String::NewFromUtf8Literal(v8_isolate, "PROMISE"),
261             promise->GetPrivate(context, hooks->trigger_id_smb.Get(v8_isolate))
262                 .ToLocalChecked(),
263             promise};
264         USE(wrap.init_function()->Call(context, rcv, 4, initArgs));
265       }
266       break;
267     case PromiseHookType::kBefore:
268       if (!wrap.before_function().IsEmpty()) {
269         USE(wrap.before_function()->Call(context, rcv, 1, args));
270       }
271       break;
272     case PromiseHookType::kAfter:
273       if (!wrap.after_function().IsEmpty()) {
274         USE(wrap.after_function()->Call(context, rcv, 1, args));
275       }
276       break;
277     case PromiseHookType::kResolve:
278       if (!wrap.promiseResolve_function().IsEmpty()) {
279         USE(wrap.promiseResolve_function()->Call(context, rcv, 1, args));
280       }
281   }
282 }
283 
284 }  // namespace v8
285