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