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 | |
11 | void AsyncHooksWrap::Enable() { enabled_ = true; } |
12 | |
13 | void AsyncHooksWrap::Disable() { enabled_ = false; } |
14 | |
15 | v8::Local<v8::Function> AsyncHooksWrap::init_function() const { |
16 | return init_function_.Get(isolate_); |
17 | } |
18 | void AsyncHooksWrap::set_init_function(v8::Local<v8::Function> value) { |
19 | init_function_.Reset(isolate_, value); |
20 | } |
21 | v8::Local<v8::Function> AsyncHooksWrap::before_function() const { |
22 | return before_function_.Get(isolate_); |
23 | } |
24 | void AsyncHooksWrap::set_before_function(v8::Local<v8::Function> value) { |
25 | before_function_.Reset(isolate_, value); |
26 | } |
27 | v8::Local<v8::Function> AsyncHooksWrap::after_function() const { |
28 | return after_function_.Get(isolate_); |
29 | } |
30 | void AsyncHooksWrap::set_after_function(v8::Local<v8::Function> value) { |
31 | after_function_.Reset(isolate_, value); |
32 | } |
33 | v8::Local<v8::Function> AsyncHooksWrap::promiseResolve_function() const { |
34 | return promiseResolve_function_.Get(isolate_); |
35 | } |
36 | void AsyncHooksWrap::set_promiseResolve_function( |
37 | v8::Local<v8::Function> value) { |
38 | promiseResolve_function_.Reset(isolate_, value); |
39 | } |
40 | |
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 | |
63 | static void EnableHook(const v8::FunctionCallbackInfo<v8::Value>& args) { |
64 | AsyncHooksWrap* wrap = UnwrapHook(args); |
65 | if (wrap) { |
66 | wrap->Enable(); |
67 | } |
68 | } |
69 | |
70 | static void DisableHook(const v8::FunctionCallbackInfo<v8::Value>& args) { |
71 | AsyncHooksWrap* wrap = UnwrapHook(args); |
72 | if (wrap) { |
73 | wrap->Disable(); |
74 | } |
75 | } |
76 | |
77 | async_id_t AsyncHooks::GetExecutionAsyncId() const { |
78 | return asyncContexts.top().execution_async_id; |
79 | } |
80 | |
81 | async_id_t AsyncHooks::GetTriggerAsyncId() const { |
82 | return asyncContexts.top().trigger_async_id; |
83 | } |
84 | |
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 | |
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 | CHECK(!promise |
146 | ->HasPrivate(currentContext, |
147 | hooks->async_id_smb.Get(hooks->isolate_)) |
148 | .ToChecked()); |
149 | promise->SetPrivate(currentContext, |
150 | hooks->async_id_smb.Get(hooks->isolate_), async_id); |
151 | |
152 | if (parent->IsPromise()) { |
153 | Local<Promise> parent_promise = parent.As<Promise>(); |
154 | Local<Value> parent_async_id = |
155 | parent_promise |
156 | ->GetPrivate(hooks->isolate_->GetCurrentContext(), |
157 | hooks->async_id_smb.Get(hooks->isolate_)) |
158 | .ToLocalChecked(); |
159 | promise->SetPrivate(currentContext, |
160 | hooks->trigger_id_smb.Get(hooks->isolate_), |
161 | parent_async_id); |
162 | } else { |
163 | CHECK(parent->IsUndefined()); |
164 | Local<Integer> trigger_id = Integer::New(hooks->isolate_, 0); |
165 | promise->SetPrivate(currentContext, |
166 | hooks->trigger_id_smb.Get(hooks->isolate_), |
167 | trigger_id); |
168 | } |
169 | } else if (type == PromiseHookType::kBefore) { |
170 | AsyncContext ctx; |
171 | ctx.execution_async_id = |
172 | promise |
173 | ->GetPrivate(hooks->isolate_->GetCurrentContext(), |
174 | hooks->async_id_smb.Get(hooks->isolate_)) |
175 | .ToLocalChecked() |
176 | .As<Integer>() |
177 | ->Value(); |
178 | ctx.trigger_async_id = |
179 | promise |
180 | ->GetPrivate(hooks->isolate_->GetCurrentContext(), |
181 | hooks->trigger_id_smb.Get(hooks->isolate_)) |
182 | .ToLocalChecked() |
183 | .As<Integer>() |
184 | ->Value(); |
185 | hooks->asyncContexts.push(ctx); |
186 | } else if (type == PromiseHookType::kAfter) { |
187 | hooks->asyncContexts.pop(); |
188 | } |
189 | |
190 | for (AsyncHooksWrap* wrap : hooks->async_wraps_) { |
191 | PromiseHookDispatch(type, promise, parent, wrap, hooks); |
192 | } |
193 | } |
194 | |
195 | void AsyncHooks::Initialize() { |
196 | HandleScope handle_scope(isolate_); |
197 | |
198 | async_hook_ctor.Reset(isolate_, FunctionTemplate::New(isolate_)); |
199 | async_hook_ctor.Get(isolate_)->SetClassName( |
200 | String::NewFromUtf8(isolate_, "AsyncHook" , NewStringType::kNormal) |
201 | .ToLocalChecked()); |
202 | |
203 | async_hooks_templ.Reset(isolate_, |
204 | async_hook_ctor.Get(isolate_)->InstanceTemplate()); |
205 | async_hooks_templ.Get(isolate_)->SetInternalFieldCount(1); |
206 | async_hooks_templ.Get(isolate_)->Set( |
207 | String::NewFromUtf8(isolate_, "enable" , v8::NewStringType::kNormal) |
208 | .ToLocalChecked(), |
209 | FunctionTemplate::New(isolate_, EnableHook)); |
210 | async_hooks_templ.Get(isolate_)->Set( |
211 | String::NewFromUtf8(isolate_, "disable" , v8::NewStringType::kNormal) |
212 | .ToLocalChecked(), |
213 | FunctionTemplate::New(isolate_, DisableHook)); |
214 | |
215 | async_id_smb.Reset(isolate_, Private::New(isolate_)); |
216 | trigger_id_smb.Reset(isolate_, Private::New(isolate_)); |
217 | |
218 | isolate_->SetPromiseHook(ShellPromiseHook); |
219 | } |
220 | |
221 | void AsyncHooks::Deinitialize() { |
222 | isolate_->SetPromiseHook(nullptr); |
223 | for (AsyncHooksWrap* wrap : async_wraps_) { |
224 | delete wrap; |
225 | } |
226 | } |
227 | |
228 | void AsyncHooks::PromiseHookDispatch(PromiseHookType type, |
229 | Local<Promise> promise, |
230 | Local<Value> parent, AsyncHooksWrap* wrap, |
231 | AsyncHooks* hooks) { |
232 | if (!wrap->IsEnabled()) { |
233 | return; |
234 | } |
235 | |
236 | HandleScope handle_scope(hooks->isolate_); |
237 | |
238 | TryCatch try_catch(hooks->isolate_); |
239 | try_catch.SetVerbose(true); |
240 | |
241 | i::Isolate* isolate = reinterpret_cast<i::Isolate*>(hooks->isolate_); |
242 | if (isolate->has_scheduled_exception()) { |
243 | isolate->ScheduleThrow(isolate->scheduled_exception()); |
244 | |
245 | DCHECK(try_catch.HasCaught()); |
246 | Shell::ReportException(hooks->isolate_, &try_catch); |
247 | return; |
248 | } |
249 | |
250 | Local<Value> rcv = Undefined(hooks->isolate_); |
251 | Local<Context> context = hooks->isolate_->GetCurrentContext(); |
252 | Local<Value> async_id = |
253 | promise->GetPrivate(context, hooks->async_id_smb.Get(hooks->isolate_)) |
254 | .ToLocalChecked(); |
255 | Local<Value> args[1] = {async_id}; |
256 | |
257 | // This is unused. It's here to silence the warning about |
258 | // not using the MaybeLocal return value from Call. |
259 | MaybeLocal<Value> result; |
260 | |
261 | // Sacrifice the brevity for readability and debugfulness |
262 | if (type == PromiseHookType::kInit) { |
263 | if (!wrap->init_function().IsEmpty()) { |
264 | Local<Value> initArgs[4] = { |
265 | async_id, |
266 | String::NewFromUtf8(hooks->isolate_, "PROMISE" , |
267 | NewStringType::kNormal) |
268 | .ToLocalChecked(), |
269 | promise |
270 | ->GetPrivate(context, hooks->trigger_id_smb.Get(hooks->isolate_)) |
271 | .ToLocalChecked(), |
272 | promise}; |
273 | result = wrap->init_function()->Call(context, rcv, 4, initArgs); |
274 | } |
275 | } else if (type == PromiseHookType::kBefore) { |
276 | if (!wrap->before_function().IsEmpty()) { |
277 | result = wrap->before_function()->Call(context, rcv, 1, args); |
278 | } |
279 | } else if (type == PromiseHookType::kAfter) { |
280 | if (!wrap->after_function().IsEmpty()) { |
281 | result = wrap->after_function()->Call(context, rcv, 1, args); |
282 | } |
283 | } else if (type == PromiseHookType::kResolve) { |
284 | if (!wrap->promiseResolve_function().IsEmpty()) { |
285 | result = wrap->promiseResolve_function()->Call(context, rcv, 1, args); |
286 | } |
287 | } |
288 | |
289 | if (try_catch.HasCaught()) { |
290 | Shell::ReportException(hooks->isolate_, &try_catch); |
291 | } |
292 | } |
293 | |
294 | } // namespace v8 |
295 | |