1 #include "async_wrap-inl.h"
2 #include "env-inl.h"
3 #define JSVM_EXPERIMENTAL
4 #include "js_native_api_v8.h"
5 #include "memory_tracker-inl.h"
6 #include "jsvm_node_api.h"
7 #include "node_api_internals.h"
8 #include "node_binding.h"
9 #include "node_buffer.h"
10 #include "node_errors.h"
11 #include "node_internals.h"
12 #include "node_process.h"
13 #include "node_url.h"
14 #include "threadpoolwork-inl.h"
15 #include "tracing/traced_value.h"
16 #include "util-inl.h"
17
18 #include <atomic>
19 #include <cstring>
20 #include <memory>
21
node_jsvm_env__(v8::Local<v8::Context> context,const std::string & module_filename,int32_t module_api_version)22 node_jsvm_env__::node_jsvm_env__(v8::Local<v8::Context> context,
23 const std::string& module_filename,
24 int32_t module_api_version)
25 : JSVM_Env__(context, module_api_version), filename(module_filename) {
26 CHECK_NOT_NULL(node_env());
27 }
28
DeleteMe()29 void node_jsvm_env__::DeleteMe() {
30 destructing = true;
31 DrainFinalizerQueue();
32 JSVM_Env__::DeleteMe();
33 }
34
can_call_into_js() const35 bool node_jsvm_env__::can_call_into_js() const {
36 return node_env()->can_call_into_js();
37 }
38
CallFinalizer(JSVM_Finalize cb,void * data,void * hint)39 void node_jsvm_env__::CallFinalizer(JSVM_Finalize cb, void* data, void* hint) {
40 CallFinalizer<true>(cb, data, hint);
41 }
42
43 template <bool enforceUncaughtExceptionPolicy>
CallFinalizer(JSVM_Finalize cb,void * data,void * hint)44 void node_jsvm_env__::CallFinalizer(JSVM_Finalize cb, void* data, void* hint) {
45 v8::HandleScope handle_scope(isolate);
46 v8::Context::Scope context_scope(context());
47 CallbackIntoModule<enforceUncaughtExceptionPolicy>(
48 [&](JSVM_Env env) { cb(env, data, hint); });
49 }
50
EnqueueFinalizer(v8impl::RefTracker * finalizer)51 void node_jsvm_env__::EnqueueFinalizer(v8impl::RefTracker* finalizer) {
52 JSVM_Env__::EnqueueFinalizer(finalizer);
53 // Schedule a second pass only when it has not been scheduled, and not
54 // destructing the env.
55 // When the env is being destructed, queued finalizers are drained in the
56 // loop of `node_jsvm_env__::DrainFinalizerQueue`.
57 if (!finalization_scheduled && !destructing) {
58 finalization_scheduled = true;
59 Ref();
60 node_env()->SetImmediate([this](node::Environment* node_env) {
61 finalization_scheduled = false;
62 Unref();
63 DrainFinalizerQueue();
64 });
65 }
66 }
67
DrainFinalizerQueue()68 void node_jsvm_env__::DrainFinalizerQueue() {
69 // As userland code can delete additional references in one finalizer,
70 // the list of pending finalizers may be mutated as we execute them, so
71 // we keep iterating it until it is empty.
72 while (!pending_finalizers.empty()) {
73 v8impl::RefTracker* ref_tracker = *pending_finalizers.begin();
74 pending_finalizers.erase(ref_tracker);
75 ref_tracker->Finalize();
76 }
77 }
78
trigger_fatal_exception(v8::Local<v8::Value> local_err)79 void node_jsvm_env__::trigger_fatal_exception(v8::Local<v8::Value> local_err) {
80 v8::Local<v8::Message> local_msg =
81 v8::Exception::CreateMessage(isolate, local_err);
82 node::errors::TriggerUncaughtException(isolate, local_err, local_msg);
83 }
84
85 // option enforceUncaughtExceptionPolicy is added for not breaking existing
86 // running n-api add-ons, and should be deprecated in the next major Node.js
87 // release.
88 template <bool enforceUncaughtExceptionPolicy, typename T>
CallbackIntoModule(T && call)89 void node_jsvm_env__::CallbackIntoModule(T&& call) {
90 CallIntoModule(call, [](JSVM_Env env_, v8::Local<v8::Value> local_err) {
91 node_jsvm_env__* env = static_cast<node_jsvm_env__*>(env_);
92 if (env->terminatedOrTerminating()) {
93 return;
94 }
95 node::Environment* node_env = env->node_env();
96 if (env->module_api_version < JSVM_VERSION_EXPERIMENTAL &&
97 !node_env->options()->force_node_api_uncaught_exceptions_policy &&
98 !enforceUncaughtExceptionPolicy) {
99 ProcessEmitDeprecationWarning(
100 node_env,
101 "Uncaught N-API callback exception detected, please run node "
102 "with option --force-node-api-uncaught-exceptions-policy=true"
103 "to handle those exceptions properly.",
104 "DEP0168");
105 return;
106 }
107 // If there was an unhandled exception in the complete callback,
108 // report it as a fatal exception. (There is no JavaScript on the
109 // callstack that can possibly handle it.)
110 env->trigger_fatal_exception(local_err);
111 });
112 }
113
114 namespace v8impl {
115
116 namespace {
117
118 class BufferFinalizer : private Finalizer {
119 public:
New(JSVM_Env env,JSVM_Finalize finalizeCallback=nullptr,void * finalizeData=nullptr,void * finalizeHint=nullptr)120 static BufferFinalizer* New(JSVM_Env env,
121 JSVM_Finalize finalizeCallback = nullptr,
122 void* finalizeData = nullptr,
123 void* finalizeHint = nullptr) {
124 return new BufferFinalizer(
125 env, finalizeCallback, finalizeData, finalizeHint);
126 }
127 // node::Buffer::FreeCallback
FinalizeBufferCallback(char * data,void * hint)128 static void FinalizeBufferCallback(char* data, void* hint) {
129 std::unique_ptr<BufferFinalizer, Deleter> finalizer{
130 static_cast<BufferFinalizer*>(hint)};
131 finalizer->finalize_data_ = data;
132
133 // It is safe to call into JavaScript at this point.
134 if (finalizer->finalize_callback_ == nullptr) return;
135 finalizer->env_->CallFinalizer(finalizer->finalize_callback_,
136 finalizer->finalize_data_,
137 finalizer->finalize_hint_);
138 }
139
140 struct Deleter {
operator ()v8impl::__anon8e1fe26e0411::BufferFinalizer::Deleter141 void operator()(BufferFinalizer* finalizer) { delete finalizer; }
142 };
143
144 private:
BufferFinalizer(JSVM_Env env,JSVM_Finalize finalizeCallback,void * finalizeData,void * finalizeHint)145 BufferFinalizer(JSVM_Env env,
146 JSVM_Finalize finalizeCallback,
147 void* finalizeData,
148 void* finalizeHint)
149 : Finalizer(env, finalizeCallback, finalizeData, finalizeHint) {
150 env_->Ref();
151 }
152
~BufferFinalizer()153 ~BufferFinalizer() { env_->Unref(); }
154 };
155
ThrowNodeApiVersionError(node::Environment * node_env,const char * module_name,int32_t module_api_version)156 void ThrowNodeApiVersionError(node::Environment* node_env,
157 const char* module_name,
158 int32_t module_api_version) {
159 std::string errorMessage;
160 errorMessage += module_name;
161 errorMessage += " requires Node-API version ";
162 errorMessage += std::to_string(module_api_version);
163 errorMessage += ", but this version of Node.js only supports version ";
164 errorMessage += NODE_STRINGIFY(NAPI_VERSION) " add-ons.";
165 node_env->ThrowError(errorMessage.c_str());
166 }
167
NewEnv(v8::Local<v8::Context> context,const std::string & module_filename,int32_t module_api_version)168 inline JSVM_Env NewEnv(v8::Local<v8::Context> context,
169 const std::string& module_filename,
170 int32_t module_api_version) {
171 node_jsvm_env result;
172
173 // Validate module_api_version.
174 if (module_api_version < NODE_API_DEFAULT_MODULE_API_VERSION) {
175 module_api_version = NODE_API_DEFAULT_MODULE_API_VERSION;
176 } else if (module_api_version > NAPI_VERSION &&
177 module_api_version != JSVM_VERSION_EXPERIMENTAL) {
178 node::Environment* node_env = node::Environment::GetCurrent(context);
179 CHECK_NOT_NULL(node_env);
180 ThrowNodeApiVersionError(
181 node_env, module_filename.c_str(), module_api_version);
182 return nullptr;
183 }
184
185 result = new node_jsvm_env__(context, module_filename, module_api_version);
186 // TODO(addaleax): There was previously code that tried to delete the
187 // JSVM_Env when its v8::Context was garbage collected;
188 // However, as long as N-API addons using this JSVM_Env are in place,
189 // the Context needs to be accessible and alive.
190 // Ideally, we'd want an on-addon-unload hook that takes care of this
191 // once all N-API addons using this JSVM_Env are unloaded.
192 // For now, a per-Environment cleanup hook is the best we can do.
193 result->node_env()->AddCleanupHook(
194 [](void* arg) { static_cast<JSVM_Env>(arg)->Unref(); },
195 static_cast<void*>(result));
196
197 return result;
198 }
199
200 class ThreadSafeFunction : public node::AsyncResource {
201 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_jsvm_env env_,void * finalize_data_,JSVM_Finalize finalize_cb_,jsvm_threadsafe_function_call_js call_js_cb_)202 ThreadSafeFunction(v8::Local<v8::Function> func,
203 v8::Local<v8::Object> resource,
204 v8::Local<v8::String> name,
205 size_t thread_count_,
206 void* context_,
207 size_t max_queue_size_,
208 node_jsvm_env env_,
209 void* finalize_data_,
210 JSVM_Finalize finalize_cb_,
211 jsvm_threadsafe_function_call_js call_js_cb_)
212 : AsyncResource(env_->isolate,
213 resource,
214 *v8::String::Utf8Value(env_->isolate, name)),
215 thread_count(thread_count_),
216 is_closing(false),
217 dispatch_state(kDispatchIdle),
218 context(context_),
219 max_queue_size(max_queue_size_),
220 env(env_),
221 finalizeData(finalize_data_),
222 finalizeCb(finalize_cb_),
223 call_js_cb(call_js_cb_ == nullptr ? CallJs : call_js_cb_),
224 handles_closing(false) {
225 ref.Reset(env->isolate, func);
226 node::AddEnvironmentCleanupHook(env->isolate, Cleanup, this);
227 env->Ref();
228 }
229
~ThreadSafeFunction()230 ~ThreadSafeFunction() override {
231 node::RemoveEnvironmentCleanupHook(env->isolate, Cleanup, this);
232 env->Unref();
233 }
234
235 // These methods can be called from any thread.
236
Push(void * data,jsvm_threadsafe_function_call_mode mode)237 JSVM_Status Push(void* data, jsvm_threadsafe_function_call_mode mode) {
238 node::Mutex::ScopedLock lock(this->mutex);
239
240 while (queue.size() >= max_queue_size && max_queue_size > 0 &&
241 !is_closing) {
242 if (mode == jsvm_tsfn_nonblocking) {
243 return JSVM_QUEUE_FULL;
244 }
245 cond->Wait(lock);
246 }
247
248 if (is_closing) {
249 if (thread_count == 0) {
250 return JSVM_INVALID_ARG;
251 } else {
252 thread_count--;
253 return JSVM_CLOSING;
254 }
255 } else {
256 queue.push(data);
257 Send();
258 return JSVM_OK;
259 }
260 }
261
Acquire()262 JSVM_Status Acquire() {
263 node::Mutex::ScopedLock lock(this->mutex);
264
265 if (is_closing) {
266 return JSVM_CLOSING;
267 }
268
269 thread_count++;
270
271 return JSVM_OK;
272 }
273
Release(jsvm_threadsafe_function_release_mode mode)274 JSVM_Status Release(jsvm_threadsafe_function_release_mode mode) {
275 node::Mutex::ScopedLock lock(this->mutex);
276
277 if (thread_count == 0) {
278 return JSVM_INVALID_ARG;
279 }
280
281 thread_count--;
282
283 if (thread_count == 0 || mode == jsvm_tsfn_abort) {
284 if (!is_closing) {
285 is_closing = (mode == jsvm_tsfn_abort);
286 if (is_closing && max_queue_size > 0) {
287 cond->Signal(lock);
288 }
289 Send();
290 }
291 }
292
293 return JSVM_OK;
294 }
295
EmptyQueueAndDelete()296 void EmptyQueueAndDelete() {
297 for (; !queue.empty(); queue.pop()) {
298 call_js_cb(nullptr, nullptr, context, queue.front());
299 }
300 delete this;
301 }
302
303 // These methods must only be called from the loop thread.
304
Init()305 JSVM_Status Init() {
306 ThreadSafeFunction* ts_fn = this;
307 uv_loop_t* loop = env->node_env()->event_loop();
308
309 if (uv_async_init(loop, &async, AsyncCb) == 0) {
310 if (max_queue_size > 0) {
311 cond = std::make_unique<node::ConditionVariable>();
312 }
313 if (max_queue_size == 0 || cond) {
314 return JSVM_OK;
315 }
316
317 env->node_env()->CloseHandle(
318 reinterpret_cast<uv_handle_t*>(&async),
319 [](uv_handle_t* handle) -> void {
320 ThreadSafeFunction* ts_fn =
321 node::ContainerOf(&ThreadSafeFunction::async,
322 reinterpret_cast<uv_async_t*>(handle));
323 delete ts_fn;
324 });
325
326 // Prevent the thread-safe function from being deleted here, because
327 // the callback above will delete it.
328 ts_fn = nullptr;
329 }
330
331 delete ts_fn;
332
333 return JSVM_GENERIC_FAILURE;
334 }
335
Unref()336 JSVM_Status Unref() {
337 uv_unref(reinterpret_cast<uv_handle_t*>(&async));
338
339 return JSVM_OK;
340 }
341
Ref()342 JSVM_Status Ref() {
343 uv_ref(reinterpret_cast<uv_handle_t*>(&async));
344
345 return JSVM_OK;
346 }
347
Context()348 inline void* Context() { return context; }
349
350 protected:
Dispatch()351 void Dispatch() {
352 bool has_more = true;
353
354 // Limit maximum synchronous iteration count to prevent event loop
355 // starvation. See `src/node_messaging.cc` for an inspiration.
356 unsigned int iterations_left = kMaxIterationCount;
357 while (has_more && --iterations_left != 0) {
358 dispatch_state = kDispatchRunning;
359 has_more = DispatchOne();
360
361 // Send() was called while we were executing the JS function
362 if (dispatch_state.exchange(kDispatchIdle) != kDispatchRunning) {
363 has_more = true;
364 }
365 }
366
367 if (has_more) {
368 Send();
369 }
370 }
371
DispatchOne()372 bool DispatchOne() {
373 void* data = nullptr;
374 bool popped_value = false;
375 bool has_more = false;
376
377 {
378 node::Mutex::ScopedLock lock(this->mutex);
379 if (is_closing) {
380 CloseHandlesAndMaybeDelete();
381 } else {
382 size_t size = queue.size();
383 if (size > 0) {
384 data = queue.front();
385 queue.pop();
386 popped_value = true;
387 if (size == max_queue_size && max_queue_size > 0) {
388 cond->Signal(lock);
389 }
390 size--;
391 }
392
393 if (size == 0) {
394 if (thread_count == 0) {
395 is_closing = true;
396 if (max_queue_size > 0) {
397 cond->Signal(lock);
398 }
399 CloseHandlesAndMaybeDelete();
400 }
401 } else {
402 has_more = true;
403 }
404 }
405 }
406
407 if (popped_value) {
408 v8::HandleScope scope(env->isolate);
409 CallbackScope cb_scope(this);
410 JSVM_Value js_callback = nullptr;
411 if (!ref.IsEmpty()) {
412 v8::Local<v8::Function> js_cb =
413 v8::Local<v8::Function>::New(env->isolate, ref);
414 js_callback = v8impl::JsValueFromV8LocalValue(js_cb);
415 }
416 env->CallbackIntoModule<false>(
417 [&](JSVM_Env env) { call_js_cb(env, js_callback, context, data); });
418 }
419
420 return has_more;
421 }
422
Finalize()423 void Finalize() {
424 v8::HandleScope scope(env->isolate);
425 if (finalizeCb) {
426 CallbackScope cb_scope(this);
427 env->CallFinalizer<false>(finalizeCb, finalizeData, context);
428 }
429 EmptyQueueAndDelete();
430 }
431
CloseHandlesAndMaybeDelete(bool set_closing=false)432 void CloseHandlesAndMaybeDelete(bool set_closing = false) {
433 v8::HandleScope scope(env->isolate);
434 if (set_closing) {
435 node::Mutex::ScopedLock lock(this->mutex);
436 is_closing = true;
437 if (max_queue_size > 0) {
438 cond->Signal(lock);
439 }
440 }
441 if (handles_closing) {
442 return;
443 }
444 handles_closing = true;
445 env->node_env()->CloseHandle(
446 reinterpret_cast<uv_handle_t*>(&async),
447 [](uv_handle_t* handle) -> void {
448 ThreadSafeFunction* ts_fn =
449 node::ContainerOf(&ThreadSafeFunction::async,
450 reinterpret_cast<uv_async_t*>(handle));
451 ts_fn->Finalize();
452 });
453 }
454
Send()455 void Send() {
456 // Ask currently running Dispatch() to make one more iteration
457 unsigned char current_state = dispatch_state.fetch_or(kDispatchPending);
458 if ((current_state & kDispatchRunning) == kDispatchRunning) {
459 return;
460 }
461
462 CHECK_EQ(0, uv_async_send(&async));
463 }
464
465 // Default way of calling into JavaScript. Used when ThreadSafeFunction is
466 // without a call_js_cb_.
CallJs(JSVM_Env env,JSVM_Value cb,void * context,void * data)467 static void CallJs(JSVM_Env env, JSVM_Value cb, void* context, void* data) {
468 if (!(env == nullptr || cb == nullptr)) {
469 JSVM_Value recv;
470 JSVM_Status status;
471
472 status = OH_JSVM_GetUndefined(env, &recv);
473 if (status != JSVM_OK) {
474 OH_JSVM_ThrowError(env,
475 "ERR_JSVM_TSFN_GET_UNDEFINED",
476 "Failed to retrieve undefined value");
477 return;
478 }
479
480 status = OH_JSVM_CallFunction(env, recv, cb, 0, nullptr, nullptr);
481 if (status != JSVM_OK && status != JSVM_PENDING_EXCEPTION) {
482 OH_JSVM_ThrowError(
483 env, "ERR_JSVM_TSFN_CALL_JS", "Failed to call JS callback");
484 return;
485 }
486 }
487 }
488
AsyncCb(uv_async_t * async)489 static void AsyncCb(uv_async_t* async) {
490 ThreadSafeFunction* ts_fn =
491 node::ContainerOf(&ThreadSafeFunction::async, async);
492 ts_fn->Dispatch();
493 }
494
Cleanup(void * data)495 static void Cleanup(void* data) {
496 reinterpret_cast<ThreadSafeFunction*>(data)->CloseHandlesAndMaybeDelete(
497 true);
498 }
499
500 private:
501 static const unsigned char kDispatchIdle = 0;
502 static const unsigned char kDispatchRunning = 1 << 0;
503 static const unsigned char kDispatchPending = 1 << 1;
504
505 static const unsigned int kMaxIterationCount = 1000;
506
507 // These are variables protected by the mutex.
508 node::Mutex mutex;
509 std::unique_ptr<node::ConditionVariable> cond;
510 std::queue<void*> queue;
511 uv_async_t async;
512 size_t thread_count;
513 bool is_closing;
514 std::atomic_uchar dispatch_state;
515
516 // These are variables set once, upon creation, and then never again, which
517 // means we don't need the mutex to read them.
518 void* context;
519 size_t max_queue_size;
520
521 // These are variables accessed only from the loop thread.
522 v8impl::Persistent<v8::Function> ref;
523 node_jsvm_env env;
524 void* finalizeData;
525 JSVM_Finalize finalizeCb;
526 jsvm_threadsafe_function_call_js call_js_cb;
527 bool handles_closing;
528 };
529
530 /**
531 * Compared to node::AsyncResource, the resource object in AsyncContext is
532 * gc-able. AsyncContext holds a weak reference to the resource object.
533 * AsyncContext::MakeCallback doesn't implicitly set the receiver of the
534 * callback to the resource object.
535 */
536 class AsyncContext {
537 public:
AsyncContext(node_jsvm_env env,v8::Local<v8::Object> resource_object,const v8::Local<v8::String> resource_name,bool externally_managed_resource)538 AsyncContext(node_jsvm_env env,
539 v8::Local<v8::Object> resource_object,
540 const v8::Local<v8::String> resource_name,
541 bool externally_managed_resource)
542 : env_(env) {
543 async_id_ = node_env()->new_async_id();
544 trigger_async_id_ = node_env()->get_default_trigger_async_id();
545 resource_.Reset(node_env()->isolate(), resource_object);
546 lost_reference_ = false;
547 if (externally_managed_resource) {
548 resource_.SetWeak(
549 this, AsyncContext::WeakCallback, v8::WeakCallbackType::kParameter);
550 }
551
552 node::AsyncWrap::EmitAsyncInit(node_env(),
553 resource_object,
554 resource_name,
555 async_id_,
556 trigger_async_id_);
557 }
558
~AsyncContext()559 ~AsyncContext() {
560 resource_.Reset();
561 lost_reference_ = true;
562 node::AsyncWrap::EmitDestroy(node_env(), async_id_);
563 }
564
MakeCallback(v8::Local<v8::Object> recv,const v8::Local<v8::Function> callback,int argc,v8::Local<v8::Value> argv[])565 inline v8::MaybeLocal<v8::Value> MakeCallback(
566 v8::Local<v8::Object> recv,
567 const v8::Local<v8::Function> callback,
568 int argc,
569 v8::Local<v8::Value> argv[]) {
570 EnsureReference();
571 return node::InternalMakeCallback(node_env(),
572 resource(),
573 recv,
574 callback,
575 argc,
576 argv,
577 {async_id_, trigger_async_id_});
578 }
579
OpenCallbackScope()580 inline jsvm_callback_scope OpenCallbackScope() {
581 EnsureReference();
582 jsvm_callback_scope it =
583 reinterpret_cast<jsvm_callback_scope>(new CallbackScope(this));
584 env_->open_callback_scopes++;
585 return it;
586 }
587
EnsureReference()588 inline void EnsureReference() {
589 if (lost_reference_) {
590 const v8::HandleScope handle_scope(node_env()->isolate());
591 resource_.Reset(node_env()->isolate(),
592 v8::Object::New(node_env()->isolate()));
593 lost_reference_ = false;
594 }
595 }
596
node_env()597 inline node::Environment* node_env() { return env_->node_env(); }
resource()598 inline v8::Local<v8::Object> resource() {
599 return resource_.Get(node_env()->isolate());
600 }
async_context()601 inline node::async_context async_context() {
602 return {async_id_, trigger_async_id_};
603 }
604
CloseCallbackScope(node_jsvm_env env,jsvm_callback_scope s)605 static inline void CloseCallbackScope(node_jsvm_env env,
606 jsvm_callback_scope s) {
607 CallbackScope* callback_scope = reinterpret_cast<CallbackScope*>(s);
608 delete callback_scope;
609 env->open_callback_scopes--;
610 }
611
WeakCallback(const v8::WeakCallbackInfo<AsyncContext> & data)612 static void WeakCallback(const v8::WeakCallbackInfo<AsyncContext>& data) {
613 AsyncContext* async_context = data.GetParameter();
614 async_context->resource_.Reset();
615 async_context->lost_reference_ = true;
616 }
617
618 private:
619 class CallbackScope : public node::CallbackScope {
620 public:
CallbackScope(AsyncContext * async_context)621 explicit CallbackScope(AsyncContext* async_context)
622 : node::CallbackScope(async_context->node_env(),
623 async_context->resource_.Get(
624 async_context->node_env()->isolate()),
625 async_context->async_context()) {}
626 };
627
628 node_jsvm_env env_;
629 double async_id_;
630 double trigger_async_id_;
631 v8::Global<v8::Object> resource_;
632 bool lost_reference_;
633 };
634
635 } // end of anonymous namespace
636
637 } // end of namespace v8impl
638
639 // Intercepts the Node-V8 module registration callback. Converts parameters
640 // to NAPI equivalents and then calls the registration callback specified
641 // by the NAPI module.
jsvm_module_register_cb(v8::Local<v8::Object> exports,v8::Local<v8::Value> module,v8::Local<v8::Context> context,void * priv)642 static void jsvm_module_register_cb(v8::Local<v8::Object> exports,
643 v8::Local<v8::Value> module,
644 v8::Local<v8::Context> context,
645 void* priv) {
646 jsvm_module_register_by_symbol(
647 exports,
648 module,
649 context,
650 static_cast<const jsvm_module*>(priv)->nm_register_func);
651 }
652
653 template <int32_t module_api_version>
node_api_context_register_func(v8::Local<v8::Object> exports,v8::Local<v8::Value> module,v8::Local<v8::Context> context,void * priv)654 static void node_api_context_register_func(v8::Local<v8::Object> exports,
655 v8::Local<v8::Value> module,
656 v8::Local<v8::Context> context,
657 void* priv) {
658 jsvm_module_register_by_symbol(
659 exports,
660 module,
661 context,
662 reinterpret_cast<jsvm_addon_register_func>(priv),
663 module_api_version);
664 }
665
666 // This function must be augmented for each new Node API version.
667 // The key role of this function is to encode module_api_version in the function
668 // pointer. We are not going to have many Node API versions and having one
669 // function per version is relatively cheap. It avoids dynamic memory
670 // allocations or implementing more expensive changes to module registration.
671 // Currently AddLinkedBinding is the only user of this function.
get_node_api_context_register_func(node::Environment * node_env,const char * module_name,int32_t module_api_version)672 node::addon_context_register_func get_node_api_context_register_func(
673 node::Environment* node_env,
674 const char* module_name,
675 int32_t module_api_version) {
676 static_assert(
677 NAPI_VERSION == 9,
678 "New version of Node-API requires adding another else-if statement below "
679 "for the new version and updating this assert condition.");
680 if (module_api_version <= NODE_API_DEFAULT_MODULE_API_VERSION) {
681 return node_api_context_register_func<NODE_API_DEFAULT_MODULE_API_VERSION>;
682 } else if (module_api_version == 9) {
683 return node_api_context_register_func<9>;
684 } else if (module_api_version == JSVM_VERSION_EXPERIMENTAL) {
685 return node_api_context_register_func<JSVM_VERSION_EXPERIMENTAL>;
686 } else {
687 v8impl::ThrowNodeApiVersionError(node_env, module_name, module_api_version);
688 return nullptr;
689 }
690 }
691
jsvm_module_register_by_symbol(v8::Local<v8::Object> exports,v8::Local<v8::Value> module,v8::Local<v8::Context> context,jsvm_addon_register_func init,int32_t module_api_version)692 void jsvm_module_register_by_symbol(v8::Local<v8::Object> exports,
693 v8::Local<v8::Value> module,
694 v8::Local<v8::Context> context,
695 jsvm_addon_register_func init,
696 int32_t module_api_version) {
697 node::Environment* node_env = node::Environment::GetCurrent(context);
698 std::string module_filename = "";
699 if (init == nullptr) {
700 CHECK_NOT_NULL(node_env);
701 node_env->ThrowError("Module has no declared entry point.");
702 return;
703 }
704
705 // We set `env->filename` from `module.filename` here, but we could just as
706 // easily add a private property to `exports` in `process.dlopen`, which
707 // receives the file name from JS, and retrieve *that* here. Thus, we are not
708 // endorsing commonjs here by making use of `module.filename`.
709 v8::Local<v8::Value> filename_js;
710 v8::Local<v8::Object> modobj;
711 if (module->ToObject(context).ToLocal(&modobj) &&
712 modobj->Get(context, node_env->filename_string()).ToLocal(&filename_js) &&
713 filename_js->IsString()) {
714 node::Utf8Value filename(node_env->isolate(), filename_js);
715
716 // Turn the absolute path into a URL. Currently the absolute path is always
717 // a file system path.
718 // TODO(gabrielschulhof): Pass the `filename` through unchanged if/when we
719 // receive it as a URL already.
720 module_filename = node::url::FromFilePath(filename.ToStringView());
721 }
722
723 // Create a new JSVM_Env for this specific module.
724 JSVM_Env env = v8impl::NewEnv(context, module_filename, module_api_version);
725
726 JSVM_Value _exports = nullptr;
727 env->CallIntoModule([&](JSVM_Env env) {
728 _exports = init(env, v8impl::JsValueFromV8LocalValue(exports));
729 });
730
731 // If register function returned a non-null exports object different from
732 // the exports object we passed it, set that as the "exports" property of
733 // the module.
734 if (_exports != nullptr &&
735 _exports != v8impl::JsValueFromV8LocalValue(exports)) {
736 JSVM_Value _module = v8impl::JsValueFromV8LocalValue(module);
737 OH_JSVM_SetNamedProperty(env, _module, "exports", _exports);
738 }
739 }
740
741 namespace node {
jsvm_module_to_node_module(const jsvm_module * mod)742 node_module jsvm_module_to_node_module(const jsvm_module* mod) {
743 return {
744 -1,
745 mod->nm_flags | NM_F_DELETEME,
746 nullptr,
747 mod->nm_filename,
748 nullptr,
749 jsvm_module_register_cb,
750 mod->nm_modname,
751 const_cast<jsvm_module*>(mod), // priv
752 nullptr,
753 };
754 }
755 } // namespace node
756
757 // Registers a NAPI module.
jsvm_module_register(jsvm_module * mod)758 void JSVM_CDECL jsvm_module_register(jsvm_module* mod) {
759 node::node_module* nm =
760 new node::node_module(node::jsvm_module_to_node_module(mod));
761 node::node_module_register(nm);
762 }
763
jsvm_add_env_cleanup_hook(JSVM_Env env,jsvm_cleanup_hook fun,void * arg)764 JSVM_Status JSVM_CDECL jsvm_add_env_cleanup_hook(JSVM_Env env,
765 jsvm_cleanup_hook fun,
766 void* arg) {
767 CHECK_ENV(env);
768 CHECK_ARG(env, fun);
769
770 node::AddEnvironmentCleanupHook(env->isolate, fun, arg);
771
772 return JSVM_OK;
773 }
774
jsvm_remove_env_cleanup_hook(JSVM_Env env,jsvm_cleanup_hook fun,void * arg)775 JSVM_Status JSVM_CDECL jsvm_remove_env_cleanup_hook(JSVM_Env env,
776 jsvm_cleanup_hook fun,
777 void* arg) {
778 CHECK_ENV(env);
779 CHECK_ARG(env, fun);
780
781 node::RemoveEnvironmentCleanupHook(env->isolate, fun, arg);
782
783 return JSVM_OK;
784 }
785
786 struct jsvm_async_cleanup_hook_handle__ {
jsvm_async_cleanup_hook_handle__jsvm_async_cleanup_hook_handle__787 jsvm_async_cleanup_hook_handle__(JSVM_Env env,
788 jsvm_async_cleanup_hook user_hook,
789 void* user_data)
790 : env_(env), user_hook_(user_hook), user_data_(user_data) {
791 handle_ = node::AddEnvironmentCleanupHook(env->isolate, Hook, this);
792 env->Ref();
793 }
794
~jsvm_async_cleanup_hook_handle__jsvm_async_cleanup_hook_handle__795 ~jsvm_async_cleanup_hook_handle__() {
796 node::RemoveEnvironmentCleanupHook(std::move(handle_));
797 if (done_cb_ != nullptr) done_cb_(done_data_);
798
799 // Release the `env` handle asynchronously since it would be surprising if
800 // a call to a N-API function would destroy `env` synchronously.
801 static_cast<node_jsvm_env>(env_)->node_env()->SetImmediate(
802 [env = env_](node::Environment*) { env->Unref(); });
803 }
804
Hookjsvm_async_cleanup_hook_handle__805 static void Hook(void* data, void (*done_cb)(void*), void* done_data) {
806 jsvm_async_cleanup_hook_handle__* handle =
807 static_cast<jsvm_async_cleanup_hook_handle__*>(data);
808 handle->done_cb_ = done_cb;
809 handle->done_data_ = done_data;
810 handle->user_hook_(handle, handle->user_data_);
811 }
812
813 node::AsyncCleanupHookHandle handle_;
814 JSVM_Env env_ = nullptr;
815 jsvm_async_cleanup_hook user_hook_ = nullptr;
816 void* user_data_ = nullptr;
817 void (*done_cb_)(void*) = nullptr;
818 void* done_data_ = nullptr;
819 };
820
821 JSVM_Status JSVM_CDECL
jsvm_add_async_cleanup_hook(JSVM_Env env,jsvm_async_cleanup_hook hook,void * arg,jsvm_async_cleanup_hook_handle * remove_handle)822 jsvm_add_async_cleanup_hook(JSVM_Env env,
823 jsvm_async_cleanup_hook hook,
824 void* arg,
825 jsvm_async_cleanup_hook_handle* remove_handle) {
826 CHECK_ENV(env);
827 CHECK_ARG(env, hook);
828
829 jsvm_async_cleanup_hook_handle__* handle =
830 new jsvm_async_cleanup_hook_handle__(env, hook, arg);
831
832 if (remove_handle != nullptr) *remove_handle = handle;
833
834 return jsvm_clear_last_error(env);
835 }
836
837 JSVM_Status JSVM_CDECL
jsvm_remove_async_cleanup_hook(jsvm_async_cleanup_hook_handle remove_handle)838 jsvm_remove_async_cleanup_hook(jsvm_async_cleanup_hook_handle remove_handle) {
839 if (remove_handle == nullptr) return JSVM_INVALID_ARG;
840
841 delete remove_handle;
842
843 return JSVM_OK;
844 }
845
jsvm_fatal_exception(JSVM_Env env,JSVM_Value err)846 JSVM_Status JSVM_CDECL jsvm_fatal_exception(JSVM_Env env, JSVM_Value err) {
847 JSVM_PREAMBLE(env);
848 CHECK_ARG(env, err);
849
850 v8::Local<v8::Value> local_err = v8impl::V8LocalValueFromJsValue(err);
851 static_cast<node_jsvm_env>(env)->trigger_fatal_exception(local_err);
852
853 return jsvm_clear_last_error(env);
854 }
855
jsvm_fatal_error(const char * location,size_t location_len,const char * message,size_t message_len)856 JSVM_NO_RETURN void JSVM_CDECL jsvm_fatal_error(const char* location,
857 size_t location_len,
858 const char* message,
859 size_t message_len) {
860 std::string location_string;
861 std::string message_string;
862
863 if (location_len != JSVM_AUTO_LENGTH) {
864 location_string.assign(const_cast<char*>(location), location_len);
865 } else {
866 location_string.assign(const_cast<char*>(location), strlen(location));
867 }
868
869 if (message_len != JSVM_AUTO_LENGTH) {
870 message_string.assign(const_cast<char*>(message), message_len);
871 } else {
872 message_string.assign(const_cast<char*>(message), strlen(message));
873 }
874
875 node::OnFatalError(location_string.c_str(), message_string.c_str());
876 }
877
878 JSVM_Status JSVM_CDECL
jsvm_open_callback_scope(JSVM_Env env,JSVM_Value,jsvm_async_context async_context_handle,jsvm_callback_scope * result)879 jsvm_open_callback_scope(JSVM_Env env,
880 JSVM_Value /** ignored */,
881 jsvm_async_context async_context_handle,
882 jsvm_callback_scope* result) {
883 // Omit JSVM_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw
884 // JS exceptions.
885 CHECK_ENV(env);
886 CHECK_ARG(env, result);
887
888 v8impl::AsyncContext* node_async_context =
889 reinterpret_cast<v8impl::AsyncContext*>(async_context_handle);
890
891 *result = node_async_context->OpenCallbackScope();
892
893 return jsvm_clear_last_error(env);
894 }
895
jsvm_close_callback_scope(JSVM_Env env,jsvm_callback_scope scope)896 JSVM_Status JSVM_CDECL jsvm_close_callback_scope(JSVM_Env env,
897 jsvm_callback_scope scope) {
898 // Omit JSVM_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw
899 // JS exceptions.
900 CHECK_ENV(env);
901 CHECK_ARG(env, scope);
902 if (env->open_callback_scopes == 0) {
903 return JSVM_CALLBACK_SCOPE_MISMATCH;
904 }
905
906 v8impl::AsyncContext::CloseCallbackScope(reinterpret_cast<node_jsvm_env>(env),
907 scope);
908
909 return jsvm_clear_last_error(env);
910 }
911
jsvm_async_init(JSVM_Env env,JSVM_Value async_resource,JSVM_Value async_resource_name,jsvm_async_context * result)912 JSVM_Status JSVM_CDECL jsvm_async_init(JSVM_Env env,
913 JSVM_Value async_resource,
914 JSVM_Value async_resource_name,
915 jsvm_async_context* result) {
916 CHECK_ENV(env);
917 CHECK_ARG(env, async_resource_name);
918 CHECK_ARG(env, result);
919
920 v8::Isolate* isolate = env->isolate;
921 v8::Local<v8::Context> context = env->context();
922
923 v8::Local<v8::Object> v8_resource;
924 bool externally_managed_resource;
925 if (async_resource != nullptr) {
926 CHECK_TO_OBJECT(env, context, v8_resource, async_resource);
927 externally_managed_resource = true;
928 } else {
929 v8_resource = v8::Object::New(isolate);
930 externally_managed_resource = false;
931 }
932
933 v8::Local<v8::String> v8_resource_name;
934 CHECK_TO_STRING(env, context, v8_resource_name, async_resource_name);
935
936 v8impl::AsyncContext* async_context =
937 new v8impl::AsyncContext(reinterpret_cast<node_jsvm_env>(env),
938 v8_resource,
939 v8_resource_name,
940 externally_managed_resource);
941
942 *result = reinterpret_cast<jsvm_async_context>(async_context);
943
944 return jsvm_clear_last_error(env);
945 }
946
jsvm_async_destroy(JSVM_Env env,jsvm_async_context async_context)947 JSVM_Status JSVM_CDECL jsvm_async_destroy(JSVM_Env env,
948 jsvm_async_context async_context) {
949 CHECK_ENV(env);
950 CHECK_ARG(env, async_context);
951
952 v8impl::AsyncContext* node_async_context =
953 reinterpret_cast<v8impl::AsyncContext*>(async_context);
954
955 delete node_async_context;
956
957 return jsvm_clear_last_error(env);
958 }
959
jsvm_make_callback(JSVM_Env env,jsvm_async_context async_context,JSVM_Value recv,JSVM_Value func,size_t argc,const JSVM_Value * argv,JSVM_Value * result)960 JSVM_Status JSVM_CDECL jsvm_make_callback(JSVM_Env env,
961 jsvm_async_context async_context,
962 JSVM_Value recv,
963 JSVM_Value func,
964 size_t argc,
965 const JSVM_Value* argv,
966 JSVM_Value* result) {
967 JSVM_PREAMBLE(env);
968 CHECK_ARG(env, recv);
969 if (argc > 0) {
970 CHECK_ARG(env, argv);
971 }
972
973 v8::Local<v8::Context> context = env->context();
974
975 v8::Local<v8::Object> v8recv;
976 CHECK_TO_OBJECT(env, context, v8recv, recv);
977
978 v8::Local<v8::Function> v8func;
979 CHECK_TO_FUNCTION(env, v8func, func);
980
981 v8::MaybeLocal<v8::Value> callback_result;
982
983 if (async_context == nullptr) {
984 callback_result = node::MakeCallback(
985 env->isolate,
986 v8recv,
987 v8func,
988 argc,
989 reinterpret_cast<v8::Local<v8::Value>*>(const_cast<JSVM_Value*>(argv)),
990 {0, 0});
991 } else {
992 v8impl::AsyncContext* node_async_context =
993 reinterpret_cast<v8impl::AsyncContext*>(async_context);
994 callback_result = node_async_context->MakeCallback(
995 v8recv,
996 v8func,
997 argc,
998 reinterpret_cast<v8::Local<v8::Value>*>(const_cast<JSVM_Value*>(argv)));
999 }
1000
1001 if (try_catch.HasCaught()) {
1002 return jsvm_set_last_error(env, JSVM_PENDING_EXCEPTION);
1003 } else {
1004 CHECK_MAYBE_EMPTY(env, callback_result, JSVM_GENERIC_FAILURE);
1005 if (result != nullptr) {
1006 *result =
1007 v8impl::JsValueFromV8LocalValue(callback_result.ToLocalChecked());
1008 }
1009 }
1010
1011 return GET_RETURN_STATUS(env);
1012 }
1013
jsvm_create_buffer(JSVM_Env env,size_t length,void ** data,JSVM_Value * result)1014 JSVM_Status JSVM_CDECL jsvm_create_buffer(JSVM_Env env,
1015 size_t length,
1016 void** data,
1017 JSVM_Value* result) {
1018 JSVM_PREAMBLE(env);
1019 CHECK_ARG(env, result);
1020
1021 v8::MaybeLocal<v8::Object> maybe = node::Buffer::New(env->isolate, length);
1022
1023 CHECK_MAYBE_EMPTY(env, maybe, JSVM_GENERIC_FAILURE);
1024
1025 v8::Local<v8::Object> buffer = maybe.ToLocalChecked();
1026
1027 *result = v8impl::JsValueFromV8LocalValue(buffer);
1028
1029 if (data != nullptr) {
1030 *data = node::Buffer::Data(buffer);
1031 }
1032
1033 return GET_RETURN_STATUS(env);
1034 }
1035
OH_JSVM_CreateExternal_buffer(JSVM_Env env,size_t length,void * data,JSVM_Finalize finalizeCb,void * finalizeHint,JSVM_Value * result)1036 JSVM_Status JSVM_CDECL OH_JSVM_CreateExternal_buffer(JSVM_Env env,
1037 size_t length,
1038 void* data,
1039 JSVM_Finalize finalizeCb,
1040 void* finalizeHint,
1041 JSVM_Value* result) {
1042 JSVM_PREAMBLE(env);
1043 CHECK_ARG(env, result);
1044
1045 #if defined(V8_ENABLE_SANDBOX)
1046 return jsvm_set_last_error(env, JSVM_NO_EXTERNAL_BUFFERS_ALLOWED);
1047 #endif
1048
1049 v8::Isolate* isolate = env->isolate;
1050
1051 // The finalizer object will delete itself after invoking the callback.
1052 v8impl::BufferFinalizer* finalizer =
1053 v8impl::BufferFinalizer::New(env, finalizeCb, nullptr, finalizeHint);
1054
1055 v8::MaybeLocal<v8::Object> maybe =
1056 node::Buffer::New(isolate,
1057 static_cast<char*>(data),
1058 length,
1059 v8impl::BufferFinalizer::FinalizeBufferCallback,
1060 finalizer);
1061
1062 CHECK_MAYBE_EMPTY(env, maybe, JSVM_GENERIC_FAILURE);
1063
1064 *result = v8impl::JsValueFromV8LocalValue(maybe.ToLocalChecked());
1065 return GET_RETURN_STATUS(env);
1066 // Tell coverity that 'finalizer' should not be freed when we return
1067 // as it will be deleted when the buffer to which it is associated
1068 // is finalized.
1069 // coverity[leaked_storage]
1070 }
1071
jsvm_create_buffer_copy(JSVM_Env env,size_t length,const void * data,void ** result_data,JSVM_Value * result)1072 JSVM_Status JSVM_CDECL jsvm_create_buffer_copy(JSVM_Env env,
1073 size_t length,
1074 const void* data,
1075 void** result_data,
1076 JSVM_Value* result) {
1077 JSVM_PREAMBLE(env);
1078 CHECK_ARG(env, result);
1079
1080 v8::MaybeLocal<v8::Object> maybe =
1081 node::Buffer::Copy(env->isolate, static_cast<const char*>(data), length);
1082
1083 CHECK_MAYBE_EMPTY(env, maybe, JSVM_GENERIC_FAILURE);
1084
1085 v8::Local<v8::Object> buffer = maybe.ToLocalChecked();
1086 *result = v8impl::JsValueFromV8LocalValue(buffer);
1087
1088 if (result_data != nullptr) {
1089 *result_data = node::Buffer::Data(buffer);
1090 }
1091
1092 return GET_RETURN_STATUS(env);
1093 }
1094
jsvm_is_buffer(JSVM_Env env,JSVM_Value value,bool * result)1095 JSVM_Status JSVM_CDECL jsvm_is_buffer(JSVM_Env env,
1096 JSVM_Value value,
1097 bool* result) {
1098 CHECK_ENV(env);
1099 CHECK_ARG(env, value);
1100 CHECK_ARG(env, result);
1101
1102 *result = node::Buffer::HasInstance(v8impl::V8LocalValueFromJsValue(value));
1103 return jsvm_clear_last_error(env);
1104 }
1105
jsvm_get_buffer_info(JSVM_Env env,JSVM_Value value,void ** data,size_t * length)1106 JSVM_Status JSVM_CDECL jsvm_get_buffer_info(JSVM_Env env,
1107 JSVM_Value value,
1108 void** data,
1109 size_t* length) {
1110 CHECK_ENV(env);
1111 CHECK_ARG(env, value);
1112
1113 v8::Local<v8::Value> buffer = v8impl::V8LocalValueFromJsValue(value);
1114
1115 if (data != nullptr) {
1116 *data = node::Buffer::Data(buffer);
1117 }
1118 if (length != nullptr) {
1119 *length = node::Buffer::Length(buffer);
1120 }
1121
1122 return jsvm_clear_last_error(env);
1123 }
1124
jsvm_get_node_version(JSVM_Env env,const jsvm_node_version ** result)1125 JSVM_Status JSVM_CDECL jsvm_get_node_version(JSVM_Env env,
1126 const jsvm_node_version** result) {
1127 CHECK_ENV(env);
1128 CHECK_ARG(env, result);
1129 static const jsvm_node_version version = {
1130 NODE_MAJOR_VERSION, NODE_MINOR_VERSION, NODE_PATCH_VERSION, NODE_RELEASE};
1131 *result = &version;
1132 return jsvm_clear_last_error(env);
1133 }
1134
1135 namespace {
1136 namespace uvimpl {
1137
ConvertUVErrorCode(int code)1138 static JSVM_Status ConvertUVErrorCode(int code) {
1139 switch (code) {
1140 case 0:
1141 return JSVM_OK;
1142 case UV_EINVAL:
1143 return JSVM_INVALID_ARG;
1144 case UV_ECANCELED:
1145 return JSVM_CANCELLED;
1146 default:
1147 return JSVM_GENERIC_FAILURE;
1148 }
1149 }
1150
1151 // Wrapper around uv_work_t which calls user-provided callbacks.
1152 class Work : public node::AsyncResource, public node::ThreadPoolWork {
1153 private:
Work(node_jsvm_env env,v8::Local<v8::Object> async_resource,v8::Local<v8::String> async_resource_name,jsvm_async_execute_callback execute,jsvm_async_complete_callback complete=nullptr,void * data=nullptr)1154 explicit Work(node_jsvm_env env,
1155 v8::Local<v8::Object> async_resource,
1156 v8::Local<v8::String> async_resource_name,
1157 jsvm_async_execute_callback execute,
1158 jsvm_async_complete_callback complete = nullptr,
1159 void* data = nullptr)
1160 : AsyncResource(
1161 env->isolate,
1162 async_resource,
1163 *v8::String::Utf8Value(env->isolate, async_resource_name)),
1164 ThreadPoolWork(env->node_env(), "node_api"),
1165 _env(env),
1166 _data(data),
1167 _execute(execute),
1168 _complete(complete) {}
1169
1170 ~Work() override = default;
1171
1172 public:
New(node_jsvm_env env,v8::Local<v8::Object> async_resource,v8::Local<v8::String> async_resource_name,jsvm_async_execute_callback execute,jsvm_async_complete_callback complete,void * data)1173 static Work* New(node_jsvm_env env,
1174 v8::Local<v8::Object> async_resource,
1175 v8::Local<v8::String> async_resource_name,
1176 jsvm_async_execute_callback execute,
1177 jsvm_async_complete_callback complete,
1178 void* data) {
1179 return new Work(
1180 env, async_resource, async_resource_name, execute, complete, data);
1181 }
1182
Delete(Work * work)1183 static void Delete(Work* work) { delete work; }
1184
DoThreadPoolWork()1185 void DoThreadPoolWork() override { _execute(_env, _data); }
1186
AfterThreadPoolWork(int status)1187 void AfterThreadPoolWork(int status) override {
1188 if (_complete == nullptr) return;
1189
1190 // Establish a handle scope here so that every callback doesn't have to.
1191 // Also it is needed for the exception-handling below.
1192 v8::HandleScope scope(_env->isolate);
1193
1194 CallbackScope callback_scope(this);
1195
1196 _env->CallbackIntoModule<true>([&](JSVM_Env env) {
1197 _complete(env, ConvertUVErrorCode(status), _data);
1198 });
1199
1200 // Note: Don't access `work` after this point because it was
1201 // likely deleted by the complete callback.
1202 }
1203
1204 private:
1205 node_jsvm_env _env;
1206 void* _data;
1207 jsvm_async_execute_callback _execute;
1208 jsvm_async_complete_callback _complete;
1209 };
1210
1211 } // end of namespace uvimpl
1212 } // end of anonymous namespace
1213
1214 #define CALL_UV(env, condition) \
1215 do { \
1216 int result = (condition); \
1217 JSVM_Status status = uvimpl::ConvertUVErrorCode(result); \
1218 if (status != JSVM_OK) { \
1219 return jsvm_set_last_error(env, status, result); \
1220 } \
1221 } while (0)
1222
1223 JSVM_Status JSVM_CDECL
jsvm_create_async_work(JSVM_Env env,JSVM_Value async_resource,JSVM_Value async_resource_name,jsvm_async_execute_callback execute,jsvm_async_complete_callback complete,void * data,jsvm_async_work * result)1224 jsvm_create_async_work(JSVM_Env env,
1225 JSVM_Value async_resource,
1226 JSVM_Value async_resource_name,
1227 jsvm_async_execute_callback execute,
1228 jsvm_async_complete_callback complete,
1229 void* data,
1230 jsvm_async_work* result) {
1231 CHECK_ENV(env);
1232 CHECK_ARG(env, execute);
1233 CHECK_ARG(env, result);
1234
1235 v8::Local<v8::Context> context = env->context();
1236
1237 v8::Local<v8::Object> resource;
1238 if (async_resource != nullptr) {
1239 CHECK_TO_OBJECT(env, context, resource, async_resource);
1240 } else {
1241 resource = v8::Object::New(env->isolate);
1242 }
1243
1244 v8::Local<v8::String> resource_name;
1245 CHECK_TO_STRING(env, context, resource_name, async_resource_name);
1246
1247 uvimpl::Work* work = uvimpl::Work::New(reinterpret_cast<node_jsvm_env>(env),
1248 resource,
1249 resource_name,
1250 execute,
1251 complete,
1252 data);
1253
1254 *result = reinterpret_cast<jsvm_async_work>(work);
1255
1256 return jsvm_clear_last_error(env);
1257 }
1258
jsvm_delete_async_work(JSVM_Env env,jsvm_async_work work)1259 JSVM_Status JSVM_CDECL jsvm_delete_async_work(JSVM_Env env,
1260 jsvm_async_work work) {
1261 CHECK_ENV(env);
1262 CHECK_ARG(env, work);
1263
1264 uvimpl::Work::Delete(reinterpret_cast<uvimpl::Work*>(work));
1265
1266 return jsvm_clear_last_error(env);
1267 }
1268
jsvm_get_uv_event_loop(JSVM_Env env,uv_loop_t ** loop)1269 JSVM_Status JSVM_CDECL jsvm_get_uv_event_loop(JSVM_Env env, uv_loop_t** loop) {
1270 CHECK_ENV(env);
1271 CHECK_ARG(env, loop);
1272 *loop = reinterpret_cast<node_jsvm_env>(env)->node_env()->event_loop();
1273 return jsvm_clear_last_error(env);
1274 }
1275
jsvm_queue_async_work(JSVM_Env env,jsvm_async_work work)1276 JSVM_Status JSVM_CDECL jsvm_queue_async_work(JSVM_Env env,
1277 jsvm_async_work work) {
1278 CHECK_ENV(env);
1279 CHECK_ARG(env, work);
1280
1281 uv_loop_t* event_loop = nullptr;
1282 STATUS_CALL(jsvm_get_uv_event_loop(env, &event_loop));
1283
1284 uvimpl::Work* w = reinterpret_cast<uvimpl::Work*>(work);
1285
1286 w->ScheduleWork();
1287
1288 return jsvm_clear_last_error(env);
1289 }
1290
jsvm_cancel_async_work(JSVM_Env env,jsvm_async_work work)1291 JSVM_Status JSVM_CDECL jsvm_cancel_async_work(JSVM_Env env,
1292 jsvm_async_work work) {
1293 CHECK_ENV(env);
1294 CHECK_ARG(env, work);
1295
1296 uvimpl::Work* w = reinterpret_cast<uvimpl::Work*>(work);
1297
1298 CALL_UV(env, w->CancelWork());
1299
1300 return jsvm_clear_last_error(env);
1301 }
1302
1303 JSVM_Status JSVM_CDECL
jsvm_create_threadsafe_function(JSVM_Env env,JSVM_Value func,JSVM_Value async_resource,JSVM_Value async_resource_name,size_t max_queue_size,size_t initial_thread_count,void * thread_finalize_data,JSVM_Finalize thread_finalize_cb,void * context,jsvm_threadsafe_function_call_js call_js_cb,jsvm_threadsafe_function * result)1304 jsvm_create_threadsafe_function(JSVM_Env env,
1305 JSVM_Value func,
1306 JSVM_Value async_resource,
1307 JSVM_Value async_resource_name,
1308 size_t max_queue_size,
1309 size_t initial_thread_count,
1310 void* thread_finalize_data,
1311 JSVM_Finalize thread_finalize_cb,
1312 void* context,
1313 jsvm_threadsafe_function_call_js call_js_cb,
1314 jsvm_threadsafe_function* result) {
1315 CHECK_ENV(env);
1316 CHECK_ARG(env, async_resource_name);
1317 RETURN_STATUS_IF_FALSE(env, initial_thread_count > 0, JSVM_INVALID_ARG);
1318 CHECK_ARG(env, result);
1319
1320 JSVM_Status status = JSVM_OK;
1321
1322 v8::Local<v8::Function> v8_func;
1323 if (func == nullptr) {
1324 CHECK_ARG(env, call_js_cb);
1325 } else {
1326 CHECK_TO_FUNCTION(env, v8_func, func);
1327 }
1328
1329 v8::Local<v8::Context> v8_context = env->context();
1330
1331 v8::Local<v8::Object> v8_resource;
1332 if (async_resource == nullptr) {
1333 v8_resource = v8::Object::New(env->isolate);
1334 } else {
1335 CHECK_TO_OBJECT(env, v8_context, v8_resource, async_resource);
1336 }
1337
1338 v8::Local<v8::String> v8_name;
1339 CHECK_TO_STRING(env, v8_context, v8_name, async_resource_name);
1340
1341 v8impl::ThreadSafeFunction* ts_fn =
1342 new v8impl::ThreadSafeFunction(v8_func,
1343 v8_resource,
1344 v8_name,
1345 initial_thread_count,
1346 context,
1347 max_queue_size,
1348 reinterpret_cast<node_jsvm_env>(env),
1349 thread_finalize_data,
1350 thread_finalize_cb,
1351 call_js_cb);
1352
1353 if (ts_fn == nullptr) {
1354 status = JSVM_GENERIC_FAILURE;
1355 } else {
1356 // Init deletes ts_fn upon failure.
1357 status = ts_fn->Init();
1358 if (status == JSVM_OK) {
1359 *result = reinterpret_cast<jsvm_threadsafe_function>(ts_fn);
1360 }
1361 }
1362
1363 return jsvm_set_last_error(env, status);
1364 }
1365
jsvm_get_threadsafe_function_context(jsvm_threadsafe_function func,void ** result)1366 JSVM_Status JSVM_CDECL jsvm_get_threadsafe_function_context(
1367 jsvm_threadsafe_function func, void** result) {
1368 CHECK_NOT_NULL(func);
1369 CHECK_NOT_NULL(result);
1370
1371 *result = reinterpret_cast<v8impl::ThreadSafeFunction*>(func)->Context();
1372 return JSVM_OK;
1373 }
1374
1375 JSVM_Status JSVM_CDECL
jsvm_call_threadsafe_function(jsvm_threadsafe_function func,void * data,jsvm_threadsafe_function_call_mode is_blocking)1376 jsvm_call_threadsafe_function(jsvm_threadsafe_function func,
1377 void* data,
1378 jsvm_threadsafe_function_call_mode is_blocking) {
1379 CHECK_NOT_NULL(func);
1380 return reinterpret_cast<v8impl::ThreadSafeFunction*>(func)->Push(data,
1381 is_blocking);
1382 }
1383
1384 JSVM_Status JSVM_CDECL
jsvm_acquire_threadsafe_function(jsvm_threadsafe_function func)1385 jsvm_acquire_threadsafe_function(jsvm_threadsafe_function func) {
1386 CHECK_NOT_NULL(func);
1387 return reinterpret_cast<v8impl::ThreadSafeFunction*>(func)->Acquire();
1388 }
1389
jsvm_release_threadsafe_function(jsvm_threadsafe_function func,jsvm_threadsafe_function_release_mode mode)1390 JSVM_Status JSVM_CDECL jsvm_release_threadsafe_function(
1391 jsvm_threadsafe_function func, jsvm_threadsafe_function_release_mode mode) {
1392 CHECK_NOT_NULL(func);
1393 return reinterpret_cast<v8impl::ThreadSafeFunction*>(func)->Release(mode);
1394 }
1395
1396 JSVM_Status JSVM_CDECL
jsvm_unref_threadsafe_function(JSVM_Env env,jsvm_threadsafe_function func)1397 jsvm_unref_threadsafe_function(JSVM_Env env, jsvm_threadsafe_function func) {
1398 CHECK_NOT_NULL(func);
1399 return reinterpret_cast<v8impl::ThreadSafeFunction*>(func)->Unref();
1400 }
1401
1402 JSVM_Status JSVM_CDECL
jsvm_ref_threadsafe_function(JSVM_Env env,jsvm_threadsafe_function func)1403 jsvm_ref_threadsafe_function(JSVM_Env env, jsvm_threadsafe_function func) {
1404 CHECK_NOT_NULL(func);
1405 return reinterpret_cast<v8impl::ThreadSafeFunction*>(func)->Ref();
1406 }
1407
node_api_get_module_file_name(JSVM_Env env,const char ** result)1408 JSVM_Status JSVM_CDECL node_api_get_module_file_name(JSVM_Env env,
1409 const char** result) {
1410 CHECK_ENV(env);
1411 CHECK_ARG(env, result);
1412
1413 *result = static_cast<node_jsvm_env>(env)->GetFilename();
1414 return jsvm_clear_last_error(env);
1415 }
1416