1 #include "env-inl.h"
2 #define NAPI_EXPERIMENTAL
3 #include "js_native_api_v8.h"
4 #include "node_api.h"
5 #include "node_binding.h"
6 #include "node_buffer.h"
7 #include "node_errors.h"
8 #include "node_internals.h"
9 #include "threadpoolwork-inl.h"
10 #include "util-inl.h"
11
12 #include <memory>
13
14 struct node_napi_env__ : public napi_env__ {
node_napi_env__node_napi_env__15 explicit node_napi_env__(v8::Local<v8::Context> context):
16 napi_env__(context) {
17 CHECK_NOT_NULL(node_env());
18 }
19
node_envnode_napi_env__20 inline node::Environment* node_env() const {
21 return node::Environment::GetCurrent(context());
22 }
23
can_call_into_jsnode_napi_env__24 bool can_call_into_js() const override {
25 return node_env()->can_call_into_js();
26 }
27
mark_arraybuffer_as_untransferablenode_napi_env__28 v8::Maybe<bool> mark_arraybuffer_as_untransferable(
29 v8::Local<v8::ArrayBuffer> ab) const override {
30 return ab->SetPrivate(
31 context(),
32 node_env()->untransferable_object_private_symbol(),
33 v8::True(isolate));
34 }
35
CallFinalizernode_napi_env__36 void CallFinalizer(napi_finalize cb, void* data, void* hint) override {
37 napi_env env = static_cast<napi_env>(this);
38 node_env()->SetImmediate([=](node::Environment* node_env) {
39 v8::HandleScope handle_scope(env->isolate);
40 v8::Context::Scope context_scope(env->context());
41 env->CallIntoModule([&](napi_env env) {
42 cb(env, data, hint);
43 });
44 });
45 }
46 };
47
48 typedef node_napi_env__* node_napi_env;
49
50 namespace v8impl {
51
52 namespace {
53
54 class BufferFinalizer : private Finalizer {
55 public:
56 // node::Buffer::FreeCallback
FinalizeBufferCallback(char * data,void * hint)57 static void FinalizeBufferCallback(char* data, void* hint) {
58 std::unique_ptr<BufferFinalizer, Deleter> finalizer{
59 static_cast<BufferFinalizer*>(hint)};
60 finalizer->_finalize_data = data;
61
62 node::Environment* node_env =
63 static_cast<node_napi_env>(finalizer->_env)->node_env();
64 node_env->SetImmediate(
65 [finalizer = std::move(finalizer)](node::Environment* env) {
66 if (finalizer->_finalize_callback == nullptr) return;
67
68 v8::HandleScope handle_scope(finalizer->_env->isolate);
69 v8::Context::Scope context_scope(finalizer->_env->context());
70
71 finalizer->_env->CallIntoModule([&](napi_env env) {
72 finalizer->_finalize_callback(
73 env,
74 finalizer->_finalize_data,
75 finalizer->_finalize_hint);
76 });
77 });
78 }
79
80 struct Deleter {
operator ()v8impl::__anon237310580311::BufferFinalizer::Deleter81 void operator()(BufferFinalizer* finalizer) {
82 Finalizer::Delete(finalizer);
83 }
84 };
85 };
86
NewEnv(v8::Local<v8::Context> context)87 static inline napi_env NewEnv(v8::Local<v8::Context> context) {
88 node_napi_env result;
89
90 result = new node_napi_env__(context);
91 // TODO(addaleax): There was previously code that tried to delete the
92 // napi_env when its v8::Context was garbage collected;
93 // However, as long as N-API addons using this napi_env are in place,
94 // the Context needs to be accessible and alive.
95 // Ideally, we'd want an on-addon-unload hook that takes care of this
96 // once all N-API addons using this napi_env are unloaded.
97 // For now, a per-Environment cleanup hook is the best we can do.
98 result->node_env()->AddCleanupHook(
99 [](void* arg) {
100 static_cast<napi_env>(arg)->Unref();
101 },
102 static_cast<void*>(result));
103
104 return result;
105 }
106
107 static inline napi_callback_scope
JsCallbackScopeFromV8CallbackScope(node::CallbackScope * s)108 JsCallbackScopeFromV8CallbackScope(node::CallbackScope* s) {
109 return reinterpret_cast<napi_callback_scope>(s);
110 }
111
112 static inline node::CallbackScope*
V8CallbackScopeFromJsCallbackScope(napi_callback_scope s)113 V8CallbackScopeFromJsCallbackScope(napi_callback_scope s) {
114 return reinterpret_cast<node::CallbackScope*>(s);
115 }
116
trigger_fatal_exception(napi_env env,v8::Local<v8::Value> local_err)117 static inline void trigger_fatal_exception(
118 napi_env env, v8::Local<v8::Value> local_err) {
119 v8::Local<v8::Message> local_msg =
120 v8::Exception::CreateMessage(env->isolate, local_err);
121 node::errors::TriggerUncaughtException(env->isolate, local_err, local_msg);
122 }
123
124 class ThreadSafeFunction : public node::AsyncResource {
125 public:
ThreadSafeFunction(v8::Local<v8::Function> func,v8::Local<v8::Object> resource,v8::Local<v8::String> name,size_t thread_count_,void * context_,size_t max_queue_size_,node_napi_env env_,void * finalize_data_,napi_finalize finalize_cb_,napi_threadsafe_function_call_js call_js_cb_)126 ThreadSafeFunction(v8::Local<v8::Function> func,
127 v8::Local<v8::Object> resource,
128 v8::Local<v8::String> name,
129 size_t thread_count_,
130 void* context_,
131 size_t max_queue_size_,
132 node_napi_env env_,
133 void* finalize_data_,
134 napi_finalize finalize_cb_,
135 napi_threadsafe_function_call_js call_js_cb_):
136 AsyncResource(env_->isolate,
137 resource,
138 *v8::String::Utf8Value(env_->isolate, name)),
139 thread_count(thread_count_),
140 is_closing(false),
141 context(context_),
142 max_queue_size(max_queue_size_),
143 env(env_),
144 finalize_data(finalize_data_),
145 finalize_cb(finalize_cb_),
146 call_js_cb(call_js_cb_ == nullptr ? CallJs : call_js_cb_),
147 handles_closing(false) {
148 ref.Reset(env->isolate, func);
149 node::AddEnvironmentCleanupHook(env->isolate, Cleanup, this);
150 env->Ref();
151 }
152
~ThreadSafeFunction()153 ~ThreadSafeFunction() override {
154 node::RemoveEnvironmentCleanupHook(env->isolate, Cleanup, this);
155 env->Unref();
156 }
157
158 // These methods can be called from any thread.
159
Push(void * data,napi_threadsafe_function_call_mode mode)160 napi_status Push(void* data, napi_threadsafe_function_call_mode mode) {
161 node::Mutex::ScopedLock lock(this->mutex);
162
163 while (queue.size() >= max_queue_size &&
164 max_queue_size > 0 &&
165 !is_closing) {
166 if (mode == napi_tsfn_nonblocking) {
167 return napi_queue_full;
168 }
169 cond->Wait(lock);
170 }
171
172 if (is_closing) {
173 if (thread_count == 0) {
174 return napi_invalid_arg;
175 } else {
176 thread_count--;
177 return napi_closing;
178 }
179 } else {
180 if (uv_async_send(&async) != 0) {
181 return napi_generic_failure;
182 }
183 queue.push(data);
184 return napi_ok;
185 }
186 }
187
Acquire()188 napi_status Acquire() {
189 node::Mutex::ScopedLock lock(this->mutex);
190
191 if (is_closing) {
192 return napi_closing;
193 }
194
195 thread_count++;
196
197 return napi_ok;
198 }
199
Release(napi_threadsafe_function_release_mode mode)200 napi_status Release(napi_threadsafe_function_release_mode mode) {
201 node::Mutex::ScopedLock lock(this->mutex);
202
203 if (thread_count == 0) {
204 return napi_invalid_arg;
205 }
206
207 thread_count--;
208
209 if (thread_count == 0 || mode == napi_tsfn_abort) {
210 if (!is_closing) {
211 is_closing = (mode == napi_tsfn_abort);
212 if (is_closing && max_queue_size > 0) {
213 cond->Signal(lock);
214 }
215 if (uv_async_send(&async) != 0) {
216 return napi_generic_failure;
217 }
218 }
219 }
220
221 return napi_ok;
222 }
223
EmptyQueueAndDelete()224 void EmptyQueueAndDelete() {
225 for (; !queue.empty() ; queue.pop()) {
226 call_js_cb(nullptr, nullptr, context, queue.front());
227 }
228 delete this;
229 }
230
231 // These methods must only be called from the loop thread.
232
Init()233 napi_status Init() {
234 ThreadSafeFunction* ts_fn = this;
235 uv_loop_t* loop = env->node_env()->event_loop();
236
237 if (uv_async_init(loop, &async, AsyncCb) == 0) {
238 if (max_queue_size > 0) {
239 cond = std::make_unique<node::ConditionVariable>();
240 }
241 if (max_queue_size == 0 || cond) {
242 CHECK_EQ(0, uv_idle_init(loop, &idle));
243 return napi_ok;
244 }
245
246 env->node_env()->CloseHandle(
247 reinterpret_cast<uv_handle_t*>(&async),
248 [](uv_handle_t* handle) -> void {
249 ThreadSafeFunction* ts_fn =
250 node::ContainerOf(&ThreadSafeFunction::async,
251 reinterpret_cast<uv_async_t*>(handle));
252 delete ts_fn;
253 });
254
255 // Prevent the thread-safe function from being deleted here, because
256 // the callback above will delete it.
257 ts_fn = nullptr;
258 }
259
260 delete ts_fn;
261
262 return napi_generic_failure;
263 }
264
Unref()265 napi_status Unref() {
266 uv_unref(reinterpret_cast<uv_handle_t*>(&async));
267 uv_unref(reinterpret_cast<uv_handle_t*>(&idle));
268
269 return napi_ok;
270 }
271
Ref()272 napi_status Ref() {
273 uv_ref(reinterpret_cast<uv_handle_t*>(&async));
274 uv_ref(reinterpret_cast<uv_handle_t*>(&idle));
275
276 return napi_ok;
277 }
278
DispatchOne()279 void DispatchOne() {
280 void* data = nullptr;
281 bool popped_value = false;
282
283 {
284 node::Mutex::ScopedLock lock(this->mutex);
285 if (is_closing) {
286 CloseHandlesAndMaybeDelete();
287 } else {
288 size_t size = queue.size();
289 if (size > 0) {
290 data = queue.front();
291 queue.pop();
292 popped_value = true;
293 if (size == max_queue_size && max_queue_size > 0) {
294 cond->Signal(lock);
295 }
296 size--;
297 }
298
299 if (size == 0) {
300 if (thread_count == 0) {
301 is_closing = true;
302 if (max_queue_size > 0) {
303 cond->Signal(lock);
304 }
305 CloseHandlesAndMaybeDelete();
306 } else {
307 CHECK_EQ(0, uv_idle_stop(&idle));
308 }
309 }
310 }
311 }
312
313 if (popped_value) {
314 v8::HandleScope scope(env->isolate);
315 CallbackScope cb_scope(this);
316 napi_value js_callback = nullptr;
317 if (!ref.IsEmpty()) {
318 v8::Local<v8::Function> js_cb =
319 v8::Local<v8::Function>::New(env->isolate, ref);
320 js_callback = v8impl::JsValueFromV8LocalValue(js_cb);
321 }
322 env->CallIntoModule([&](napi_env env) {
323 call_js_cb(env, js_callback, context, data);
324 });
325 }
326 }
327
Finalize()328 void Finalize() {
329 v8::HandleScope scope(env->isolate);
330 if (finalize_cb) {
331 CallbackScope cb_scope(this);
332 env->CallIntoModule([&](napi_env env) {
333 finalize_cb(env, finalize_data, context);
334 });
335 }
336 EmptyQueueAndDelete();
337 }
338
Context()339 inline void* Context() {
340 return context;
341 }
342
CloseHandlesAndMaybeDelete(bool set_closing=false)343 void CloseHandlesAndMaybeDelete(bool set_closing = false) {
344 v8::HandleScope scope(env->isolate);
345 if (set_closing) {
346 node::Mutex::ScopedLock lock(this->mutex);
347 is_closing = true;
348 if (max_queue_size > 0) {
349 cond->Signal(lock);
350 }
351 }
352 if (handles_closing) {
353 return;
354 }
355 handles_closing = true;
356 env->node_env()->CloseHandle(
357 reinterpret_cast<uv_handle_t*>(&async),
358 [](uv_handle_t* handle) -> void {
359 ThreadSafeFunction* ts_fn =
360 node::ContainerOf(&ThreadSafeFunction::async,
361 reinterpret_cast<uv_async_t*>(handle));
362 v8::HandleScope scope(ts_fn->env->isolate);
363 ts_fn->env->node_env()->CloseHandle(
364 reinterpret_cast<uv_handle_t*>(&ts_fn->idle),
365 [](uv_handle_t* handle) -> void {
366 ThreadSafeFunction* ts_fn =
367 node::ContainerOf(&ThreadSafeFunction::idle,
368 reinterpret_cast<uv_idle_t*>(handle));
369 ts_fn->Finalize();
370 });
371 });
372 }
373
374 // Default way of calling into JavaScript. Used when ThreadSafeFunction is
375 // without a call_js_cb_.
CallJs(napi_env env,napi_value cb,void * context,void * data)376 static void CallJs(napi_env env, napi_value cb, void* context, void* data) {
377 if (!(env == nullptr || cb == nullptr)) {
378 napi_value recv;
379 napi_status status;
380
381 status = napi_get_undefined(env, &recv);
382 if (status != napi_ok) {
383 napi_throw_error(env, "ERR_NAPI_TSFN_GET_UNDEFINED",
384 "Failed to retrieve undefined value");
385 return;
386 }
387
388 status = napi_call_function(env, recv, cb, 0, nullptr, nullptr);
389 if (status != napi_ok && status != napi_pending_exception) {
390 napi_throw_error(env, "ERR_NAPI_TSFN_CALL_JS",
391 "Failed to call JS callback");
392 return;
393 }
394 }
395 }
396
IdleCb(uv_idle_t * idle)397 static void IdleCb(uv_idle_t* idle) {
398 ThreadSafeFunction* ts_fn =
399 node::ContainerOf(&ThreadSafeFunction::idle, idle);
400 ts_fn->DispatchOne();
401 }
402
AsyncCb(uv_async_t * async)403 static void AsyncCb(uv_async_t* async) {
404 ThreadSafeFunction* ts_fn =
405 node::ContainerOf(&ThreadSafeFunction::async, async);
406 CHECK_EQ(0, uv_idle_start(&ts_fn->idle, IdleCb));
407 }
408
Cleanup(void * data)409 static void Cleanup(void* data) {
410 reinterpret_cast<ThreadSafeFunction*>(data)
411 ->CloseHandlesAndMaybeDelete(true);
412 }
413
414 private:
415 // These are variables protected by the mutex.
416 node::Mutex mutex;
417 std::unique_ptr<node::ConditionVariable> cond;
418 std::queue<void*> queue;
419 uv_async_t async;
420 uv_idle_t idle;
421 size_t thread_count;
422 bool is_closing;
423
424 // These are variables set once, upon creation, and then never again, which
425 // means we don't need the mutex to read them.
426 void* context;
427 size_t max_queue_size;
428
429 // These are variables accessed only from the loop thread.
430 v8impl::Persistent<v8::Function> ref;
431 node_napi_env env;
432 void* finalize_data;
433 napi_finalize finalize_cb;
434 napi_threadsafe_function_call_js call_js_cb;
435 bool handles_closing;
436 };
437
438 } // end of anonymous namespace
439
440 } // end of namespace v8impl
441
442 // Intercepts the Node-V8 module registration callback. Converts parameters
443 // to NAPI equivalents and then calls the registration callback specified
444 // by the NAPI module.
napi_module_register_cb(v8::Local<v8::Object> exports,v8::Local<v8::Value> module,v8::Local<v8::Context> context,void * priv)445 static void napi_module_register_cb(v8::Local<v8::Object> exports,
446 v8::Local<v8::Value> module,
447 v8::Local<v8::Context> context,
448 void* priv) {
449 napi_module_register_by_symbol(exports, module, context,
450 static_cast<napi_module*>(priv)->nm_register_func);
451 }
452
napi_module_register_by_symbol(v8::Local<v8::Object> exports,v8::Local<v8::Value> module,v8::Local<v8::Context> context,napi_addon_register_func init)453 void napi_module_register_by_symbol(v8::Local<v8::Object> exports,
454 v8::Local<v8::Value> module,
455 v8::Local<v8::Context> context,
456 napi_addon_register_func init) {
457 if (init == nullptr) {
458 node::Environment* node_env = node::Environment::GetCurrent(context);
459 CHECK_NOT_NULL(node_env);
460 node_env->ThrowError(
461 "Module has no declared entry point.");
462 return;
463 }
464
465 // Create a new napi_env for this specific module.
466 napi_env env = v8impl::NewEnv(context);
467
468 napi_value _exports;
469 env->CallIntoModule([&](napi_env env) {
470 _exports = init(env, v8impl::JsValueFromV8LocalValue(exports));
471 });
472
473 // If register function returned a non-null exports object different from
474 // the exports object we passed it, set that as the "exports" property of
475 // the module.
476 if (_exports != nullptr &&
477 _exports != v8impl::JsValueFromV8LocalValue(exports)) {
478 napi_value _module = v8impl::JsValueFromV8LocalValue(module);
479 napi_set_named_property(env, _module, "exports", _exports);
480 }
481 }
482
483 // Registers a NAPI module.
napi_module_register(napi_module * mod)484 void napi_module_register(napi_module* mod) {
485 node::node_module* nm = new node::node_module {
486 -1,
487 mod->nm_flags | NM_F_DELETEME,
488 nullptr,
489 mod->nm_filename,
490 nullptr,
491 napi_module_register_cb,
492 mod->nm_modname,
493 mod, // priv
494 nullptr,
495 };
496 node::node_module_register(nm);
497 }
498
napi_add_env_cleanup_hook(napi_env env,void (* fun)(void * arg),void * arg)499 napi_status napi_add_env_cleanup_hook(napi_env env,
500 void (*fun)(void* arg),
501 void* arg) {
502 CHECK_ENV(env);
503 CHECK_ARG(env, fun);
504
505 node::AddEnvironmentCleanupHook(env->isolate, fun, arg);
506
507 return napi_ok;
508 }
509
napi_remove_env_cleanup_hook(napi_env env,void (* fun)(void * arg),void * arg)510 napi_status napi_remove_env_cleanup_hook(napi_env env,
511 void (*fun)(void* arg),
512 void* arg) {
513 CHECK_ENV(env);
514 CHECK_ARG(env, fun);
515
516 node::RemoveEnvironmentCleanupHook(env->isolate, fun, arg);
517
518 return napi_ok;
519 }
520
521 struct napi_async_cleanup_hook_handle__ {
napi_async_cleanup_hook_handle__napi_async_cleanup_hook_handle__522 napi_async_cleanup_hook_handle__(napi_env env,
523 napi_async_cleanup_hook user_hook,
524 void* user_data):
525 env_(env),
526 user_hook_(user_hook),
527 user_data_(user_data) {
528 handle_ = node::AddEnvironmentCleanupHook(env->isolate, Hook, this);
529 env->Ref();
530 }
531
~napi_async_cleanup_hook_handle__napi_async_cleanup_hook_handle__532 ~napi_async_cleanup_hook_handle__() {
533 node::RemoveEnvironmentCleanupHook(std::move(handle_));
534 if (done_cb_ != nullptr)
535 done_cb_(done_data_);
536
537 // Release the `env` handle asynchronously since it would be surprising if
538 // a call to a N-API function would destroy `env` synchronously.
539 static_cast<node_napi_env>(env_)->node_env()
540 ->SetImmediate([env = env_](node::Environment*) { env->Unref(); });
541 }
542
Hooknapi_async_cleanup_hook_handle__543 static void Hook(void* data, void (*done_cb)(void*), void* done_data) {
544 auto handle = static_cast<napi_async_cleanup_hook_handle__*>(data);
545 handle->done_cb_ = done_cb;
546 handle->done_data_ = done_data;
547 handle->user_hook_(handle, handle->user_data_);
548 }
549
550 node::AsyncCleanupHookHandle handle_;
551 napi_env env_ = nullptr;
552 napi_async_cleanup_hook user_hook_ = nullptr;
553 void* user_data_ = nullptr;
554 void (*done_cb_)(void*) = nullptr;
555 void* done_data_ = nullptr;
556 };
557
napi_add_async_cleanup_hook(napi_env env,napi_async_cleanup_hook hook,void * arg,napi_async_cleanup_hook_handle * remove_handle)558 napi_status napi_add_async_cleanup_hook(
559 napi_env env,
560 napi_async_cleanup_hook hook,
561 void* arg,
562 napi_async_cleanup_hook_handle* remove_handle) {
563 CHECK_ENV(env);
564 CHECK_ARG(env, hook);
565
566 napi_async_cleanup_hook_handle__* handle =
567 new napi_async_cleanup_hook_handle__(env, hook, arg);
568
569 if (remove_handle != nullptr)
570 *remove_handle = handle;
571
572 return napi_clear_last_error(env);
573 }
574
napi_remove_async_cleanup_hook(napi_async_cleanup_hook_handle remove_handle)575 napi_status napi_remove_async_cleanup_hook(
576 napi_async_cleanup_hook_handle remove_handle) {
577
578 if (remove_handle == nullptr)
579 return napi_invalid_arg;
580
581 delete remove_handle;
582
583 return napi_ok;
584 }
585
napi_fatal_exception(napi_env env,napi_value err)586 napi_status napi_fatal_exception(napi_env env, napi_value err) {
587 NAPI_PREAMBLE(env);
588 CHECK_ARG(env, err);
589
590 v8::Local<v8::Value> local_err = v8impl::V8LocalValueFromJsValue(err);
591 v8impl::trigger_fatal_exception(env, local_err);
592
593 return napi_clear_last_error(env);
594 }
595
napi_fatal_error(const char * location,size_t location_len,const char * message,size_t message_len)596 NAPI_NO_RETURN void napi_fatal_error(const char* location,
597 size_t location_len,
598 const char* message,
599 size_t message_len) {
600 std::string location_string;
601 std::string message_string;
602
603 if (location_len != NAPI_AUTO_LENGTH) {
604 location_string.assign(
605 const_cast<char*>(location), location_len);
606 } else {
607 location_string.assign(
608 const_cast<char*>(location), strlen(location));
609 }
610
611 if (message_len != NAPI_AUTO_LENGTH) {
612 message_string.assign(
613 const_cast<char*>(message), message_len);
614 } else {
615 message_string.assign(
616 const_cast<char*>(message), strlen(message));
617 }
618
619 node::FatalError(location_string.c_str(), message_string.c_str());
620 }
621
napi_open_callback_scope(napi_env env,napi_value resource_object,napi_async_context async_context_handle,napi_callback_scope * result)622 napi_status napi_open_callback_scope(napi_env env,
623 napi_value resource_object,
624 napi_async_context async_context_handle,
625 napi_callback_scope* result) {
626 // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw
627 // JS exceptions.
628 CHECK_ENV(env);
629 CHECK_ARG(env, result);
630
631 v8::Local<v8::Context> context = env->context();
632
633 node::async_context* node_async_context =
634 reinterpret_cast<node::async_context*>(async_context_handle);
635
636 v8::Local<v8::Object> resource;
637 CHECK_TO_OBJECT(env, context, resource, resource_object);
638
639 *result = v8impl::JsCallbackScopeFromV8CallbackScope(
640 new node::CallbackScope(env->isolate,
641 resource,
642 *node_async_context));
643
644 env->open_callback_scopes++;
645 return napi_clear_last_error(env);
646 }
647
napi_close_callback_scope(napi_env env,napi_callback_scope scope)648 napi_status napi_close_callback_scope(napi_env env, napi_callback_scope scope) {
649 // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw
650 // JS exceptions.
651 CHECK_ENV(env);
652 CHECK_ARG(env, scope);
653 if (env->open_callback_scopes == 0) {
654 return napi_callback_scope_mismatch;
655 }
656
657 env->open_callback_scopes--;
658 delete v8impl::V8CallbackScopeFromJsCallbackScope(scope);
659 return napi_clear_last_error(env);
660 }
661
napi_async_init(napi_env env,napi_value async_resource,napi_value async_resource_name,napi_async_context * result)662 napi_status napi_async_init(napi_env env,
663 napi_value async_resource,
664 napi_value async_resource_name,
665 napi_async_context* result) {
666 CHECK_ENV(env);
667 CHECK_ARG(env, async_resource_name);
668 CHECK_ARG(env, result);
669
670 v8::Isolate* isolate = env->isolate;
671 v8::Local<v8::Context> context = env->context();
672
673 v8::Local<v8::Object> v8_resource;
674 if (async_resource != nullptr) {
675 CHECK_TO_OBJECT(env, context, v8_resource, async_resource);
676 } else {
677 v8_resource = v8::Object::New(isolate);
678 }
679
680 v8::Local<v8::String> v8_resource_name;
681 CHECK_TO_STRING(env, context, v8_resource_name, async_resource_name);
682
683 // TODO(jasongin): Consider avoiding allocation here by using
684 // a tagged pointer with 2×31 bit fields instead.
685 node::async_context* async_context = new node::async_context();
686
687 *async_context = node::EmitAsyncInit(isolate, v8_resource, v8_resource_name);
688 *result = reinterpret_cast<napi_async_context>(async_context);
689
690 return napi_clear_last_error(env);
691 }
692
napi_async_destroy(napi_env env,napi_async_context async_context)693 napi_status napi_async_destroy(napi_env env,
694 napi_async_context async_context) {
695 CHECK_ENV(env);
696 CHECK_ARG(env, async_context);
697
698 node::async_context* node_async_context =
699 reinterpret_cast<node::async_context*>(async_context);
700 node::EmitAsyncDestroy(
701 reinterpret_cast<node_napi_env>(env)->node_env(),
702 *node_async_context);
703
704 delete node_async_context;
705
706 return napi_clear_last_error(env);
707 }
708
napi_make_callback(napi_env env,napi_async_context async_context,napi_value recv,napi_value func,size_t argc,const napi_value * argv,napi_value * result)709 napi_status napi_make_callback(napi_env env,
710 napi_async_context async_context,
711 napi_value recv,
712 napi_value func,
713 size_t argc,
714 const napi_value* argv,
715 napi_value* result) {
716 NAPI_PREAMBLE(env);
717 CHECK_ARG(env, recv);
718 if (argc > 0) {
719 CHECK_ARG(env, argv);
720 }
721
722 v8::Local<v8::Context> context = env->context();
723
724 v8::Local<v8::Object> v8recv;
725 CHECK_TO_OBJECT(env, context, v8recv, recv);
726
727 v8::Local<v8::Function> v8func;
728 CHECK_TO_FUNCTION(env, v8func, func);
729
730 node::async_context* node_async_context =
731 reinterpret_cast<node::async_context*>(async_context);
732 if (node_async_context == nullptr) {
733 static node::async_context empty_context = { 0, 0 };
734 node_async_context = &empty_context;
735 }
736
737 v8::MaybeLocal<v8::Value> callback_result = node::MakeCallback(
738 env->isolate, v8recv, v8func, argc,
739 reinterpret_cast<v8::Local<v8::Value>*>(const_cast<napi_value*>(argv)),
740 *node_async_context);
741
742 if (try_catch.HasCaught()) {
743 return napi_set_last_error(env, napi_pending_exception);
744 } else {
745 CHECK_MAYBE_EMPTY(env, callback_result, napi_generic_failure);
746 if (result != nullptr) {
747 *result = v8impl::JsValueFromV8LocalValue(
748 callback_result.ToLocalChecked());
749 }
750 }
751
752 return GET_RETURN_STATUS(env);
753 }
754
napi_create_buffer(napi_env env,size_t length,void ** data,napi_value * result)755 napi_status napi_create_buffer(napi_env env,
756 size_t length,
757 void** data,
758 napi_value* result) {
759 NAPI_PREAMBLE(env);
760 CHECK_ARG(env, result);
761
762 auto maybe = node::Buffer::New(env->isolate, length);
763
764 CHECK_MAYBE_EMPTY(env, maybe, napi_generic_failure);
765
766 v8::Local<v8::Object> buffer = maybe.ToLocalChecked();
767
768 *result = v8impl::JsValueFromV8LocalValue(buffer);
769
770 if (data != nullptr) {
771 *data = node::Buffer::Data(buffer);
772 }
773
774 return GET_RETURN_STATUS(env);
775 }
776
napi_create_external_buffer(napi_env env,size_t length,void * data,napi_finalize finalize_cb,void * finalize_hint,napi_value * result)777 napi_status napi_create_external_buffer(napi_env env,
778 size_t length,
779 void* data,
780 napi_finalize finalize_cb,
781 void* finalize_hint,
782 napi_value* result) {
783 NAPI_PREAMBLE(env);
784 CHECK_ARG(env, result);
785
786 v8::Isolate* isolate = env->isolate;
787
788 // The finalizer object will delete itself after invoking the callback.
789 v8impl::Finalizer* finalizer = v8impl::Finalizer::New(
790 env, finalize_cb, nullptr, finalize_hint,
791 v8impl::Finalizer::kKeepEnvReference);
792
793 auto maybe = node::Buffer::New(isolate,
794 static_cast<char*>(data),
795 length,
796 v8impl::BufferFinalizer::FinalizeBufferCallback,
797 finalizer);
798
799 CHECK_MAYBE_EMPTY(env, maybe, napi_generic_failure);
800
801 *result = v8impl::JsValueFromV8LocalValue(maybe.ToLocalChecked());
802 return GET_RETURN_STATUS(env);
803 // Tell coverity that 'finalizer' should not be freed when we return
804 // as it will be deleted when the buffer to which it is associated
805 // is finalized.
806 // coverity[leaked_storage]
807 }
808
napi_create_buffer_copy(napi_env env,size_t length,const void * data,void ** result_data,napi_value * result)809 napi_status napi_create_buffer_copy(napi_env env,
810 size_t length,
811 const void* data,
812 void** result_data,
813 napi_value* result) {
814 NAPI_PREAMBLE(env);
815 CHECK_ARG(env, result);
816
817 auto maybe = node::Buffer::Copy(env->isolate,
818 static_cast<const char*>(data), length);
819
820 CHECK_MAYBE_EMPTY(env, maybe, napi_generic_failure);
821
822 v8::Local<v8::Object> buffer = maybe.ToLocalChecked();
823 *result = v8impl::JsValueFromV8LocalValue(buffer);
824
825 if (result_data != nullptr) {
826 *result_data = node::Buffer::Data(buffer);
827 }
828
829 return GET_RETURN_STATUS(env);
830 }
831
napi_is_buffer(napi_env env,napi_value value,bool * result)832 napi_status napi_is_buffer(napi_env env, napi_value value, bool* result) {
833 CHECK_ENV(env);
834 CHECK_ARG(env, value);
835 CHECK_ARG(env, result);
836
837 *result = node::Buffer::HasInstance(v8impl::V8LocalValueFromJsValue(value));
838 return napi_clear_last_error(env);
839 }
840
napi_get_buffer_info(napi_env env,napi_value value,void ** data,size_t * length)841 napi_status napi_get_buffer_info(napi_env env,
842 napi_value value,
843 void** data,
844 size_t* length) {
845 CHECK_ENV(env);
846 CHECK_ARG(env, value);
847
848 v8::Local<v8::Value> buffer = v8impl::V8LocalValueFromJsValue(value);
849
850 if (data != nullptr) {
851 *data = node::Buffer::Data(buffer);
852 }
853 if (length != nullptr) {
854 *length = node::Buffer::Length(buffer);
855 }
856
857 return napi_clear_last_error(env);
858 }
859
napi_get_node_version(napi_env env,const napi_node_version ** result)860 napi_status napi_get_node_version(napi_env env,
861 const napi_node_version** result) {
862 CHECK_ENV(env);
863 CHECK_ARG(env, result);
864 static const napi_node_version version = {
865 NODE_MAJOR_VERSION,
866 NODE_MINOR_VERSION,
867 NODE_PATCH_VERSION,
868 NODE_RELEASE
869 };
870 *result = &version;
871 return napi_clear_last_error(env);
872 }
873
874 namespace {
875 namespace uvimpl {
876
ConvertUVErrorCode(int code)877 static napi_status ConvertUVErrorCode(int code) {
878 switch (code) {
879 case 0:
880 return napi_ok;
881 case UV_EINVAL:
882 return napi_invalid_arg;
883 case UV_ECANCELED:
884 return napi_cancelled;
885 default:
886 return napi_generic_failure;
887 }
888 }
889
890 // Wrapper around uv_work_t which calls user-provided callbacks.
891 class Work : public node::AsyncResource, public node::ThreadPoolWork {
892 private:
Work(node_napi_env env,v8::Local<v8::Object> async_resource,v8::Local<v8::String> async_resource_name,napi_async_execute_callback execute,napi_async_complete_callback complete=nullptr,void * data=nullptr)893 explicit Work(node_napi_env env,
894 v8::Local<v8::Object> async_resource,
895 v8::Local<v8::String> async_resource_name,
896 napi_async_execute_callback execute,
897 napi_async_complete_callback complete = nullptr,
898 void* data = nullptr)
899 : AsyncResource(env->isolate,
900 async_resource,
901 *v8::String::Utf8Value(env->isolate, async_resource_name)),
902 ThreadPoolWork(env->node_env()),
903 _env(env),
904 _data(data),
905 _execute(execute),
906 _complete(complete) {
907 }
908
909 ~Work() override = default;
910
911 public:
New(node_napi_env env,v8::Local<v8::Object> async_resource,v8::Local<v8::String> async_resource_name,napi_async_execute_callback execute,napi_async_complete_callback complete,void * data)912 static Work* New(node_napi_env env,
913 v8::Local<v8::Object> async_resource,
914 v8::Local<v8::String> async_resource_name,
915 napi_async_execute_callback execute,
916 napi_async_complete_callback complete,
917 void* data) {
918 return new Work(env, async_resource, async_resource_name,
919 execute, complete, data);
920 }
921
Delete(Work * work)922 static void Delete(Work* work) {
923 delete work;
924 }
925
DoThreadPoolWork()926 void DoThreadPoolWork() override {
927 _execute(_env, _data);
928 }
929
AfterThreadPoolWork(int status)930 void AfterThreadPoolWork(int status) override {
931 if (_complete == nullptr)
932 return;
933
934 // Establish a handle scope here so that every callback doesn't have to.
935 // Also it is needed for the exception-handling below.
936 v8::HandleScope scope(_env->isolate);
937
938 CallbackScope callback_scope(this);
939
940 _env->CallIntoModule([&](napi_env env) {
941 _complete(env, ConvertUVErrorCode(status), _data);
942 }, [](napi_env env, v8::Local<v8::Value> local_err) {
943 // If there was an unhandled exception in the complete callback,
944 // report it as a fatal exception. (There is no JavaScript on the
945 // callstack that can possibly handle it.)
946 v8impl::trigger_fatal_exception(env, local_err);
947 });
948
949 // Note: Don't access `work` after this point because it was
950 // likely deleted by the complete callback.
951 }
952
953 private:
954 node_napi_env _env;
955 void* _data;
956 napi_async_execute_callback _execute;
957 napi_async_complete_callback _complete;
958 };
959
960 } // end of namespace uvimpl
961 } // end of anonymous namespace
962
963 #define CALL_UV(env, condition) \
964 do { \
965 int result = (condition); \
966 napi_status status = uvimpl::ConvertUVErrorCode(result); \
967 if (status != napi_ok) { \
968 return napi_set_last_error(env, status, result); \
969 } \
970 } while (0)
971
napi_create_async_work(napi_env env,napi_value async_resource,napi_value async_resource_name,napi_async_execute_callback execute,napi_async_complete_callback complete,void * data,napi_async_work * result)972 napi_status napi_create_async_work(napi_env env,
973 napi_value async_resource,
974 napi_value async_resource_name,
975 napi_async_execute_callback execute,
976 napi_async_complete_callback complete,
977 void* data,
978 napi_async_work* result) {
979 CHECK_ENV(env);
980 CHECK_ARG(env, execute);
981 CHECK_ARG(env, result);
982
983 v8::Local<v8::Context> context = env->context();
984
985 v8::Local<v8::Object> resource;
986 if (async_resource != nullptr) {
987 CHECK_TO_OBJECT(env, context, resource, async_resource);
988 } else {
989 resource = v8::Object::New(env->isolate);
990 }
991
992 v8::Local<v8::String> resource_name;
993 CHECK_TO_STRING(env, context, resource_name, async_resource_name);
994
995 uvimpl::Work* work = uvimpl::Work::New(reinterpret_cast<node_napi_env>(env),
996 resource,
997 resource_name,
998 execute,
999 complete,
1000 data);
1001
1002 *result = reinterpret_cast<napi_async_work>(work);
1003
1004 return napi_clear_last_error(env);
1005 }
1006
napi_delete_async_work(napi_env env,napi_async_work work)1007 napi_status napi_delete_async_work(napi_env env, napi_async_work work) {
1008 CHECK_ENV(env);
1009 CHECK_ARG(env, work);
1010
1011 uvimpl::Work::Delete(reinterpret_cast<uvimpl::Work*>(work));
1012
1013 return napi_clear_last_error(env);
1014 }
1015
napi_get_uv_event_loop(napi_env env,uv_loop_t ** loop)1016 napi_status napi_get_uv_event_loop(napi_env env, uv_loop_t** loop) {
1017 CHECK_ENV(env);
1018 CHECK_ARG(env, loop);
1019 *loop = reinterpret_cast<node_napi_env>(env)->node_env()->event_loop();
1020 return napi_clear_last_error(env);
1021 }
1022
napi_queue_async_work(napi_env env,napi_async_work work)1023 napi_status napi_queue_async_work(napi_env env, napi_async_work work) {
1024 CHECK_ENV(env);
1025 CHECK_ARG(env, work);
1026
1027 napi_status status;
1028 uv_loop_t* event_loop = nullptr;
1029 status = napi_get_uv_event_loop(env, &event_loop);
1030 if (status != napi_ok)
1031 return napi_set_last_error(env, status);
1032
1033 uvimpl::Work* w = reinterpret_cast<uvimpl::Work*>(work);
1034
1035 w->ScheduleWork();
1036
1037 return napi_clear_last_error(env);
1038 }
1039
napi_cancel_async_work(napi_env env,napi_async_work work)1040 napi_status napi_cancel_async_work(napi_env env, napi_async_work work) {
1041 CHECK_ENV(env);
1042 CHECK_ARG(env, work);
1043
1044 uvimpl::Work* w = reinterpret_cast<uvimpl::Work*>(work);
1045
1046 CALL_UV(env, w->CancelWork());
1047
1048 return napi_clear_last_error(env);
1049 }
1050
1051 napi_status
napi_create_threadsafe_function(napi_env env,napi_value func,napi_value async_resource,napi_value async_resource_name,size_t max_queue_size,size_t initial_thread_count,void * thread_finalize_data,napi_finalize thread_finalize_cb,void * context,napi_threadsafe_function_call_js call_js_cb,napi_threadsafe_function * result)1052 napi_create_threadsafe_function(napi_env env,
1053 napi_value func,
1054 napi_value async_resource,
1055 napi_value async_resource_name,
1056 size_t max_queue_size,
1057 size_t initial_thread_count,
1058 void* thread_finalize_data,
1059 napi_finalize thread_finalize_cb,
1060 void* context,
1061 napi_threadsafe_function_call_js call_js_cb,
1062 napi_threadsafe_function* result) {
1063 CHECK_ENV(env);
1064 CHECK_ARG(env, async_resource_name);
1065 RETURN_STATUS_IF_FALSE(env, initial_thread_count > 0, napi_invalid_arg);
1066 CHECK_ARG(env, result);
1067
1068 napi_status status = napi_ok;
1069
1070 v8::Local<v8::Function> v8_func;
1071 if (func == nullptr) {
1072 CHECK_ARG(env, call_js_cb);
1073 } else {
1074 CHECK_TO_FUNCTION(env, v8_func, func);
1075 }
1076
1077 v8::Local<v8::Context> v8_context = env->context();
1078
1079 v8::Local<v8::Object> v8_resource;
1080 if (async_resource == nullptr) {
1081 v8_resource = v8::Object::New(env->isolate);
1082 } else {
1083 CHECK_TO_OBJECT(env, v8_context, v8_resource, async_resource);
1084 }
1085
1086 v8::Local<v8::String> v8_name;
1087 CHECK_TO_STRING(env, v8_context, v8_name, async_resource_name);
1088
1089 v8impl::ThreadSafeFunction* ts_fn =
1090 new v8impl::ThreadSafeFunction(v8_func,
1091 v8_resource,
1092 v8_name,
1093 initial_thread_count,
1094 context,
1095 max_queue_size,
1096 reinterpret_cast<node_napi_env>(env),
1097 thread_finalize_data,
1098 thread_finalize_cb,
1099 call_js_cb);
1100
1101 if (ts_fn == nullptr) {
1102 status = napi_generic_failure;
1103 } else {
1104 // Init deletes ts_fn upon failure.
1105 status = ts_fn->Init();
1106 if (status == napi_ok) {
1107 *result = reinterpret_cast<napi_threadsafe_function>(ts_fn);
1108 }
1109 }
1110
1111 return napi_set_last_error(env, status);
1112 }
1113
1114 napi_status
napi_get_threadsafe_function_context(napi_threadsafe_function func,void ** result)1115 napi_get_threadsafe_function_context(napi_threadsafe_function func,
1116 void** result) {
1117 CHECK_NOT_NULL(func);
1118 CHECK_NOT_NULL(result);
1119
1120 *result = reinterpret_cast<v8impl::ThreadSafeFunction*>(func)->Context();
1121 return napi_ok;
1122 }
1123
1124 napi_status
napi_call_threadsafe_function(napi_threadsafe_function func,void * data,napi_threadsafe_function_call_mode is_blocking)1125 napi_call_threadsafe_function(napi_threadsafe_function func,
1126 void* data,
1127 napi_threadsafe_function_call_mode is_blocking) {
1128 CHECK_NOT_NULL(func);
1129 return reinterpret_cast<v8impl::ThreadSafeFunction*>(func)->Push(data,
1130 is_blocking);
1131 }
1132
1133 napi_status
napi_acquire_threadsafe_function(napi_threadsafe_function func)1134 napi_acquire_threadsafe_function(napi_threadsafe_function func) {
1135 CHECK_NOT_NULL(func);
1136 return reinterpret_cast<v8impl::ThreadSafeFunction*>(func)->Acquire();
1137 }
1138
1139 napi_status
napi_release_threadsafe_function(napi_threadsafe_function func,napi_threadsafe_function_release_mode mode)1140 napi_release_threadsafe_function(napi_threadsafe_function func,
1141 napi_threadsafe_function_release_mode mode) {
1142 CHECK_NOT_NULL(func);
1143 return reinterpret_cast<v8impl::ThreadSafeFunction*>(func)->Release(mode);
1144 }
1145
1146 napi_status
napi_unref_threadsafe_function(napi_env env,napi_threadsafe_function func)1147 napi_unref_threadsafe_function(napi_env env, napi_threadsafe_function func) {
1148 CHECK_NOT_NULL(func);
1149 return reinterpret_cast<v8impl::ThreadSafeFunction*>(func)->Unref();
1150 }
1151
1152 napi_status
napi_ref_threadsafe_function(napi_env env,napi_threadsafe_function func)1153 napi_ref_threadsafe_function(napi_env env, napi_threadsafe_function func) {
1154 CHECK_NOT_NULL(func);
1155 return reinterpret_cast<v8impl::ThreadSafeFunction*>(func)->Ref();
1156 }
1157