1 #include "node.h"
2 #include "async_wrap-inl.h"
3 #include "env-inl.h"
4 #include "v8.h"
5
6 namespace node {
7
8 using v8::Context;
9 using v8::EscapableHandleScope;
10 using v8::Function;
11 using v8::HandleScope;
12 using v8::Isolate;
13 using v8::Local;
14 using v8::MaybeLocal;
15 using v8::Object;
16 using v8::String;
17 using v8::Value;
18
CallbackScope(Isolate * isolate,Local<Object> object,async_context async_context)19 CallbackScope::CallbackScope(Isolate* isolate,
20 Local<Object> object,
21 async_context async_context)
22 : CallbackScope(Environment::GetCurrent(isolate), object, async_context) {}
23
CallbackScope(Environment * env,Local<Object> object,async_context asyncContext)24 CallbackScope::CallbackScope(Environment* env,
25 Local<Object> object,
26 async_context asyncContext)
27 : private_(new InternalCallbackScope(env,
28 object,
29 asyncContext)),
30 try_catch_(env->isolate()) {
31 try_catch_.SetVerbose(true);
32 }
33
~CallbackScope()34 CallbackScope::~CallbackScope() {
35 if (try_catch_.HasCaught())
36 private_->MarkAsFailed();
37 delete private_;
38 }
39
InternalCallbackScope(AsyncWrap * async_wrap,int flags)40 InternalCallbackScope::InternalCallbackScope(AsyncWrap* async_wrap, int flags)
41 : InternalCallbackScope(async_wrap->env(),
42 async_wrap->object(),
43 { async_wrap->get_async_id(),
44 async_wrap->get_trigger_async_id() },
45 flags) {}
46
InternalCallbackScope(Environment * env,Local<Object> object,const async_context & asyncContext,int flags)47 InternalCallbackScope::InternalCallbackScope(Environment* env,
48 Local<Object> object,
49 const async_context& asyncContext,
50 int flags)
51 : env_(env),
52 async_context_(asyncContext),
53 object_(object),
54 skip_hooks_(flags & kSkipAsyncHooks),
55 skip_task_queues_(flags & kSkipTaskQueues) {
56 CHECK_NOT_NULL(env);
57 env->PushAsyncCallbackScope();
58
59 if (!env->can_call_into_js()) {
60 failed_ = true;
61 return;
62 }
63
64 Isolate* isolate = env->isolate();
65
66 HandleScope handle_scope(isolate);
67 Local<Context> current_context = isolate->GetCurrentContext();
68 // If you hit this assertion, the caller forgot to enter the right Node.js
69 // Environment's v8::Context first.
70 // We first check `env->context() != current_context` because the contexts
71 // likely *are* the same, in which case we can skip the slightly more
72 // expensive Environment::GetCurrent() call.
73 if (UNLIKELY(env->context() != current_context)) {
74 CHECK_EQ(Environment::GetCurrent(isolate), env);
75 }
76
77 isolate->SetIdle(false);
78
79 env->async_hooks()->push_async_context(
80 async_context_.async_id, async_context_.trigger_async_id, object);
81
82 pushed_ids_ = true;
83
84 if (asyncContext.async_id != 0 && !skip_hooks_) {
85 // No need to check a return value because the application will exit if
86 // an exception occurs.
87 AsyncWrap::EmitBefore(env, asyncContext.async_id);
88 }
89 }
90
~InternalCallbackScope()91 InternalCallbackScope::~InternalCallbackScope() {
92 Close();
93 env_->PopAsyncCallbackScope();
94 }
95
Close()96 void InternalCallbackScope::Close() {
97 if (closed_) return;
98 closed_ = true;
99
100 // This function must ends up with either cleanup the
101 // async id stack or pop the topmost one from it
102
103 auto perform_stopping_check = [&]() {
104 if (env_->is_stopping()) {
105 MarkAsFailed();
106 env_->async_hooks()->clear_async_id_stack();
107 }
108 };
109 perform_stopping_check();
110
111 if (env_->is_stopping()) return;
112
113 Isolate* isolate = env_->isolate();
114 auto idle = OnScopeLeave([&]() { isolate->SetIdle(true); });
115
116 if (!failed_ && async_context_.async_id != 0 && !skip_hooks_) {
117 AsyncWrap::EmitAfter(env_, async_context_.async_id);
118 }
119
120 if (pushed_ids_)
121 env_->async_hooks()->pop_async_context(async_context_.async_id);
122
123 if (failed_) return;
124
125 if (env_->async_callback_scope_depth() > 1 || skip_task_queues_) {
126 return;
127 }
128
129 TickInfo* tick_info = env_->tick_info();
130
131 if (!env_->can_call_into_js()) return;
132
133 auto weakref_cleanup = OnScopeLeave([&]() { env_->RunWeakRefCleanup(); });
134
135 Local<Context> context = env_->context();
136 if (!tick_info->has_tick_scheduled()) {
137 context->GetMicrotaskQueue()->PerformCheckpoint(isolate);
138
139 perform_stopping_check();
140 }
141
142 // Make sure the stack unwound properly. If there are nested MakeCallback's
143 // then it should return early and not reach this code.
144 if (env_->async_hooks()->fields()[AsyncHooks::kTotals]) {
145 CHECK_EQ(env_->execution_async_id(), 0);
146 CHECK_EQ(env_->trigger_async_id(), 0);
147 }
148
149 if (!tick_info->has_tick_scheduled() && !tick_info->has_rejection_to_warn()) {
150 return;
151 }
152
153 HandleScope handle_scope(isolate);
154 Local<Object> process = env_->process_object();
155
156 if (!env_->can_call_into_js()) return;
157
158 Local<Function> tick_callback = env_->tick_callback_function();
159
160 // The tick is triggered before JS land calls SetTickCallback
161 // to initializes the tick callback during bootstrap.
162 CHECK(!tick_callback.IsEmpty());
163
164 if (tick_callback->Call(context, process, 0, nullptr).IsEmpty()) {
165 failed_ = true;
166 }
167 perform_stopping_check();
168 }
169
InternalMakeCallback(Environment * env,Local<Object> resource,Local<Object> recv,const Local<Function> callback,int argc,Local<Value> argv[],async_context asyncContext)170 MaybeLocal<Value> InternalMakeCallback(Environment* env,
171 Local<Object> resource,
172 Local<Object> recv,
173 const Local<Function> callback,
174 int argc,
175 Local<Value> argv[],
176 async_context asyncContext) {
177 CHECK(!recv.IsEmpty());
178 #ifdef DEBUG
179 for (int i = 0; i < argc; i++)
180 CHECK(!argv[i].IsEmpty());
181 #endif
182
183 Local<Function> hook_cb = env->async_hooks_callback_trampoline();
184 int flags = InternalCallbackScope::kNoFlags;
185 bool use_async_hooks_trampoline = false;
186 AsyncHooks* async_hooks = env->async_hooks();
187 if (!hook_cb.IsEmpty()) {
188 // Use the callback trampoline if there are any before or after hooks, or
189 // we can expect some kind of usage of async_hooks.executionAsyncResource().
190 flags = InternalCallbackScope::kSkipAsyncHooks;
191 use_async_hooks_trampoline =
192 async_hooks->fields()[AsyncHooks::kBefore] +
193 async_hooks->fields()[AsyncHooks::kAfter] +
194 async_hooks->fields()[AsyncHooks::kUsesExecutionAsyncResource] > 0;
195 }
196
197 InternalCallbackScope scope(env, resource, asyncContext, flags);
198 if (scope.Failed()) {
199 return MaybeLocal<Value>();
200 }
201
202 MaybeLocal<Value> ret;
203
204 Local<Context> context = env->context();
205 if (use_async_hooks_trampoline) {
206 MaybeStackBuffer<Local<Value>, 16> args(3 + argc);
207 args[0] = v8::Number::New(env->isolate(), asyncContext.async_id);
208 args[1] = resource;
209 args[2] = callback;
210 for (int i = 0; i < argc; i++) {
211 args[i + 3] = argv[i];
212 }
213 ret = hook_cb->Call(context, recv, args.length(), &args[0]);
214 } else {
215 ret = callback->Call(context, recv, argc, argv);
216 }
217
218 if (ret.IsEmpty()) {
219 scope.MarkAsFailed();
220 return MaybeLocal<Value>();
221 }
222
223 scope.Close();
224 if (scope.Failed()) {
225 return MaybeLocal<Value>();
226 }
227
228 return ret;
229 }
230
231 // Public MakeCallback()s
232
MakeCallback(Isolate * isolate,Local<Object> recv,const char * method,int argc,Local<Value> argv[],async_context asyncContext)233 MaybeLocal<Value> MakeCallback(Isolate* isolate,
234 Local<Object> recv,
235 const char* method,
236 int argc,
237 Local<Value> argv[],
238 async_context asyncContext) {
239 Local<String> method_string =
240 String::NewFromUtf8(isolate, method).ToLocalChecked();
241 return MakeCallback(isolate, recv, method_string, argc, argv, asyncContext);
242 }
243
MakeCallback(Isolate * isolate,Local<Object> recv,Local<String> symbol,int argc,Local<Value> argv[],async_context asyncContext)244 MaybeLocal<Value> MakeCallback(Isolate* isolate,
245 Local<Object> recv,
246 Local<String> symbol,
247 int argc,
248 Local<Value> argv[],
249 async_context asyncContext) {
250 // Check can_call_into_js() first because calling Get() might do so.
251 Environment* env =
252 Environment::GetCurrent(recv->GetCreationContext().ToLocalChecked());
253 CHECK_NOT_NULL(env);
254 if (!env->can_call_into_js()) return Local<Value>();
255
256 Local<Value> callback_v;
257 if (!recv->Get(isolate->GetCurrentContext(), symbol).ToLocal(&callback_v))
258 return Local<Value>();
259 if (!callback_v->IsFunction()) {
260 // This used to return an empty value, but Undefined() makes more sense
261 // since no exception is pending here.
262 return Undefined(isolate);
263 }
264 Local<Function> callback = callback_v.As<Function>();
265 return MakeCallback(isolate, recv, callback, argc, argv, asyncContext);
266 }
267
MakeCallback(Isolate * isolate,Local<Object> recv,Local<Function> callback,int argc,Local<Value> argv[],async_context asyncContext)268 MaybeLocal<Value> MakeCallback(Isolate* isolate,
269 Local<Object> recv,
270 Local<Function> callback,
271 int argc,
272 Local<Value> argv[],
273 async_context asyncContext) {
274 // Observe the following two subtleties:
275 //
276 // 1. The environment is retrieved from the callback function's context.
277 // 2. The context to enter is retrieved from the environment.
278 //
279 // Because of the AssignToContext() call in src/node_contextify.cc,
280 // the two contexts need not be the same.
281 Environment* env =
282 Environment::GetCurrent(callback->GetCreationContext().ToLocalChecked());
283 CHECK_NOT_NULL(env);
284 Context::Scope context_scope(env->context());
285 MaybeLocal<Value> ret =
286 InternalMakeCallback(env, recv, recv, callback, argc, argv, asyncContext);
287 if (ret.IsEmpty() && env->async_callback_scope_depth() == 0) {
288 // This is only for legacy compatibility and we may want to look into
289 // removing/adjusting it.
290 return Undefined(isolate);
291 }
292 return ret;
293 }
294
295 // Use this if you just want to safely invoke some JS callback and
296 // would like to retain the currently active async_context, if any.
297 // In case none is available, a fixed default context will be
298 // installed otherwise.
MakeSyncCallback(Isolate * isolate,Local<Object> recv,Local<Function> callback,int argc,Local<Value> argv[])299 MaybeLocal<Value> MakeSyncCallback(Isolate* isolate,
300 Local<Object> recv,
301 Local<Function> callback,
302 int argc,
303 Local<Value> argv[]) {
304 Environment* env =
305 Environment::GetCurrent(callback->GetCreationContext().ToLocalChecked());
306 CHECK_NOT_NULL(env);
307 if (!env->can_call_into_js()) return Local<Value>();
308
309 Local<Context> context = env->context();
310 Context::Scope context_scope(context);
311 if (env->async_callback_scope_depth()) {
312 // There's another MakeCallback() on the stack, piggy back on it.
313 // In particular, retain the current async_context.
314 return callback->Call(context, recv, argc, argv);
315 }
316
317 // This is a toplevel invocation and the caller (intentionally)
318 // didn't provide any async_context to run in. Install a default context.
319 MaybeLocal<Value> ret =
320 InternalMakeCallback(env, env->process_object(), recv, callback, argc, argv,
321 async_context{0, 0});
322 return ret;
323 }
324
325 // Legacy MakeCallback()s
326
MakeCallback(Isolate * isolate,Local<Object> recv,const char * method,int argc,Local<Value> * argv)327 Local<Value> MakeCallback(Isolate* isolate,
328 Local<Object> recv,
329 const char* method,
330 int argc,
331 Local<Value>* argv) {
332 EscapableHandleScope handle_scope(isolate);
333 return handle_scope.Escape(
334 MakeCallback(isolate, recv, method, argc, argv, {0, 0})
335 .FromMaybe(Local<Value>()));
336 }
337
MakeCallback(Isolate * isolate,Local<Object> recv,Local<String> symbol,int argc,Local<Value> * argv)338 Local<Value> MakeCallback(Isolate* isolate,
339 Local<Object> recv,
340 Local<String> symbol,
341 int argc,
342 Local<Value>* argv) {
343 EscapableHandleScope handle_scope(isolate);
344 return handle_scope.Escape(
345 MakeCallback(isolate, recv, symbol, argc, argv, {0, 0})
346 .FromMaybe(Local<Value>()));
347 }
348
MakeCallback(Isolate * isolate,Local<Object> recv,Local<Function> callback,int argc,Local<Value> * argv)349 Local<Value> MakeCallback(Isolate* isolate,
350 Local<Object> recv,
351 Local<Function> callback,
352 int argc,
353 Local<Value>* argv) {
354 EscapableHandleScope handle_scope(isolate);
355 return handle_scope.Escape(
356 MakeCallback(isolate, recv, callback, argc, argv, {0, 0})
357 .FromMaybe(Local<Value>()));
358 }
359
360 } // namespace node
361