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
17namespace v8 {
18
19namespace {
20std::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
37void EnableHook(const v8::FunctionCallbackInfo<v8::Value>& args) {
38  auto wrap = UnwrapHook(args);
39  if (wrap) wrap->Enable();
40}
41
42void DisableHook(const v8::FunctionCallbackInfo<v8::Value>& args) {
43  auto wrap = UnwrapHook(args);
44  if (wrap) wrap->Disable();
45}
46
47}  // namespace
48
49AsyncHooks::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
76AsyncHooks::~AsyncHooks() {
77  isolate_->SetPromiseHook(nullptr);
78  base::RecursiveMutexGuard lock_guard(&async_wraps_mutex_);
79  async_wraps_.clear();
80}
81
82void AsyncHooksWrap::Enable() { enabled_ = true; }
83
84void AsyncHooksWrap::Disable() { enabled_ = false; }
85
86v8::Local<v8::Function> AsyncHooksWrap::init_function() const {
87  return init_function_.Get(isolate_);
88}
89void AsyncHooksWrap::set_init_function(v8::Local<v8::Function> value) {
90  init_function_.Reset(isolate_, value);
91}
92v8::Local<v8::Function> AsyncHooksWrap::before_function() const {
93  return before_function_.Get(isolate_);
94}
95void AsyncHooksWrap::set_before_function(v8::Local<v8::Function> value) {
96  before_function_.Reset(isolate_, value);
97}
98v8::Local<v8::Function> AsyncHooksWrap::after_function() const {
99  return after_function_.Get(isolate_);
100}
101void AsyncHooksWrap::set_after_function(v8::Local<v8::Function> value) {
102  after_function_.Reset(isolate_, value);
103}
104v8::Local<v8::Function> AsyncHooksWrap::promiseResolve_function() const {
105  return promiseResolve_function_.Get(isolate_);
106}
107void AsyncHooksWrap::set_promiseResolve_function(
108    v8::Local<v8::Function> value) {
109  promiseResolve_function_.Reset(isolate_, value);
110}
111
112async_id_t AsyncHooks::GetExecutionAsyncId() const {
113  return asyncContexts.top().execution_async_id;
114}
115
116async_id_t AsyncHooks::GetTriggerAsyncId() const {
117  return asyncContexts.top().trigger_async_id;
118}
119
120Local<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
166void 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
240void 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