GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: node_api.cc Lines: 518 563 92.0 %
Date: 2022-04-12 04:14:44 Branches: 238 390 61.0 %

Line Branch Exec Source
1
#include "async_wrap-inl.h"
2
#include "env-inl.h"
3
#define NAPI_EXPERIMENTAL
4
#include "js_native_api_v8.h"
5
#include "memory_tracker-inl.h"
6
#include "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_url.h"
13
#include "threadpoolwork-inl.h"
14
#include "tracing/traced_value.h"
15
#include "util-inl.h"
16
17
#include <atomic>
18
#include <memory>
19
20
92
node_napi_env__::node_napi_env__(v8::Local<v8::Context> context,
21
92
                                 const std::string& module_filename)
22
92
    : napi_env__(context), filename(module_filename) {
23
92
  CHECK_NOT_NULL(node_env());
24
92
}
25
26
119470
bool node_napi_env__::can_call_into_js() const {
27
119470
  return node_env()->can_call_into_js();
28
}
29
30
v8::Maybe<bool> node_napi_env__::mark_arraybuffer_as_untransferable(
31
    v8::Local<v8::ArrayBuffer> ab) const {
32
  return ab->SetPrivate(context(),
33
                        node_env()->untransferable_object_private_symbol(),
34
                        v8::True(isolate));
35
}
36
37
1013
void node_napi_env__::CallFinalizer(napi_finalize cb, void* data, void* hint) {
38
  // we need to keep the env live until the finalizer has been run
39
  // EnvRefHolder provides an exception safe wrapper to Ref and then
40
  // Unref once the lambda is freed
41
1013
  EnvRefHolder liveEnv(static_cast<napi_env>(this));
42
2026
  node_env()->SetImmediate(
43
1013
      [=, liveEnv = std::move(liveEnv)](node::Environment* node_env) {
44
1013
        napi_env env = liveEnv.env();
45
2026
        v8::HandleScope handle_scope(env->isolate);
46
1013
        v8::Context::Scope context_scope(env->context());
47
2026
        env->CallIntoModule([&](napi_env env) { cb(env, data, hint); });
48
1013
      });
49
1013
}
50
51
namespace v8impl {
52
53
namespace {
54
55
class BufferFinalizer : private Finalizer {
56
 public:
57
  // node::Buffer::FreeCallback
58
13
  static void FinalizeBufferCallback(char* data, void* hint) {
59
    std::unique_ptr<BufferFinalizer, Deleter> finalizer{
60
13
        static_cast<BufferFinalizer*>(hint)};
61
13
    finalizer->_finalize_data = data;
62
63
    node::Environment* node_env =
64
13
        static_cast<node_napi_env>(finalizer->_env)->node_env();
65
13
    node_env->SetImmediate(
66
13
        [finalizer = std::move(finalizer)](node::Environment* env) {
67
13
          if (finalizer->_finalize_callback == nullptr) return;
68
69
24
          v8::HandleScope handle_scope(finalizer->_env->isolate);
70
12
          v8::Context::Scope context_scope(finalizer->_env->context());
71
72
12
          finalizer->_env->CallIntoModule([&](napi_env env) {
73
24
            finalizer->_finalize_callback(
74
12
                env, finalizer->_finalize_data, finalizer->_finalize_hint);
75
12
          });
76
        });
77
13
  }
78
79
  struct Deleter {
80
13
    void operator()(BufferFinalizer* finalizer) {
81
13
      Finalizer::Delete(finalizer);
82
13
    }
83
  };
84
};
85
86
92
static inline napi_env NewEnv(v8::Local<v8::Context> context,
87
                              const std::string& module_filename) {
88
  node_napi_env result;
89
90
92
  result = new node_napi_env__(context, module_filename);
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
92
  result->node_env()->AddCleanupHook(
99
90
      [](void* arg) { static_cast<napi_env>(arg)->Unref(); },
100
      static_cast<void*>(result));
101
102
92
  return result;
103
}
104
105
3
static inline void trigger_fatal_exception(napi_env env,
106
                                           v8::Local<v8::Value> local_err) {
107
  v8::Local<v8::Message> local_msg =
108
3
      v8::Exception::CreateMessage(env->isolate, local_err);
109
3
  node::errors::TriggerUncaughtException(env->isolate, local_err, local_msg);
110
2
}
111
112
class ThreadSafeFunction : public node::AsyncResource {
113
 public:
114
18
  ThreadSafeFunction(v8::Local<v8::Function> func,
115
                     v8::Local<v8::Object> resource,
116
                     v8::Local<v8::String> name,
117
                     size_t thread_count_,
118
                     void* context_,
119
                     size_t max_queue_size_,
120
                     node_napi_env env_,
121
                     void* finalize_data_,
122
                     napi_finalize finalize_cb_,
123
                     napi_threadsafe_function_call_js call_js_cb_)
124
36
      : AsyncResource(env_->isolate,
125
                      resource,
126
36
                      *v8::String::Utf8Value(env_->isolate, name)),
127
        thread_count(thread_count_),
128
        is_closing(false),
129
        dispatch_state(kDispatchIdle),
130
        context(context_),
131
        max_queue_size(max_queue_size_),
132
        env(env_),
133
        finalize_data(finalize_data_),
134
        finalize_cb(finalize_cb_),
135
18
        call_js_cb(call_js_cb_ == nullptr ? CallJs : call_js_cb_),
136

54
        handles_closing(false) {
137
18
    ref.Reset(env->isolate, func);
138
18
    node::AddEnvironmentCleanupHook(env->isolate, Cleanup, this);
139
18
    env->Ref();
140
18
  }
141
142
108
  ~ThreadSafeFunction() override {
143
36
    node::RemoveEnvironmentCleanupHook(env->isolate, Cleanup, this);
144
36
    env->Unref();
145
72
  }
146
147
  // These methods can be called from any thread.
148
149
2946574
  napi_status Push(void* data, napi_threadsafe_function_call_mode mode) {
150
5893148
    node::Mutex::ScopedLock lock(this->mutex);
151
152

5882277
    while (queue.size() >= max_queue_size && max_queue_size > 0 &&
153
2883786
           !is_closing) {
154
2883783
      if (mode == napi_tsfn_nonblocking) {
155
2831866
        return napi_queue_full;
156
      }
157
51917
      cond->Wait(lock);
158
    }
159
160
114708
    if (is_closing) {
161
5
      if (thread_count == 0) {
162
        return napi_invalid_arg;
163
      } else {
164
5
        thread_count--;
165
5
        return napi_closing;
166
      }
167
    } else {
168
114703
      queue.push(data);
169
114703
      Send();
170
114703
      return napi_ok;
171
    }
172
  }
173
174
4
  napi_status Acquire() {
175
8
    node::Mutex::ScopedLock lock(this->mutex);
176
177
4
    if (is_closing) {
178
      return napi_closing;
179
    }
180
181
4
    thread_count++;
182
183
4
    return napi_ok;
184
  }
185
186
33
  napi_status Release(napi_threadsafe_function_release_mode mode) {
187
66
    node::Mutex::ScopedLock lock(this->mutex);
188
189
33
    if (thread_count == 0) {
190
      return napi_invalid_arg;
191
    }
192
193
33
    thread_count--;
194
195

33
    if (thread_count == 0 || mode == napi_tsfn_abort) {
196
16
      if (!is_closing) {
197
16
        is_closing = (mode == napi_tsfn_abort);
198

16
        if (is_closing && max_queue_size > 0) {
199
2
          cond->Signal(lock);
200
        }
201
16
        Send();
202
      }
203
    }
204
205
33
    return napi_ok;
206
  }
207
208
25
  void EmptyQueueAndDelete() {
209
25
    for (; !queue.empty(); queue.pop()) {
210
7
      call_js_cb(nullptr, nullptr, context, queue.front());
211
    }
212
18
    delete this;
213
18
  }
214
215
  // These methods must only be called from the loop thread.
216
217
18
  napi_status Init() {
218
18
    ThreadSafeFunction* ts_fn = this;
219
18
    uv_loop_t* loop = env->node_env()->event_loop();
220
221
18
    if (uv_async_init(loop, &async, AsyncCb) == 0) {
222
18
      if (max_queue_size > 0) {
223
12
        cond = std::make_unique<node::ConditionVariable>();
224
      }
225

18
      if (max_queue_size == 0 || cond) {
226
18
        return napi_ok;
227
      }
228
229
      env->node_env()->CloseHandle(
230
          reinterpret_cast<uv_handle_t*>(&async),
231
          [](uv_handle_t* handle) -> void {
232
            ThreadSafeFunction* ts_fn =
233
                node::ContainerOf(&ThreadSafeFunction::async,
234
                                  reinterpret_cast<uv_async_t*>(handle));
235
            delete ts_fn;
236
          });
237
238
      // Prevent the thread-safe function from being deleted here, because
239
      // the callback above will delete it.
240
      ts_fn = nullptr;
241
    }
242
243
    delete ts_fn;
244
245
    return napi_generic_failure;
246
  }
247
248
2
  napi_status Unref() {
249
2
    uv_unref(reinterpret_cast<uv_handle_t*>(&async));
250
251
2
    return napi_ok;
252
  }
253
254
  napi_status Ref() {
255
    uv_ref(reinterpret_cast<uv_handle_t*>(&async));
256
257
    return napi_ok;
258
  }
259
260
16
  inline void* Context() { return context; }
261
262
 protected:
263
165
  void Dispatch() {
264
165
    bool has_more = true;
265
266
    // Limit maximum synchronous iteration count to prevent event loop
267
    // starvation. See `src/node_messaging.cc` for an inspiration.
268
165
    unsigned int iterations_left = kMaxIterationCount;
269

114872
    while (has_more && --iterations_left != 0) {
270
114707
      dispatch_state = kDispatchRunning;
271
114707
      has_more = DispatchOne();
272
273
      // Send() was called while we were executing the JS function
274
114707
      if (dispatch_state.exchange(kDispatchIdle) != kDispatchRunning) {
275
87909
        has_more = true;
276
      }
277
    }
278
279
165
    if (has_more) {
280
106
      Send();
281
    }
282
165
  }
283
284
114707
  bool DispatchOne() {
285
114707
    void* data = nullptr;
286
114707
    bool popped_value = false;
287
114707
    bool has_more = false;
288
289
    {
290
229414
      node::Mutex::ScopedLock lock(this->mutex);
291
114707
      if (is_closing) {
292
3
        CloseHandlesAndMaybeDelete();
293
      } else {
294
114704
        size_t size = queue.size();
295
114704
        if (size > 0) {
296
114696
          data = queue.front();
297
114696
          queue.pop();
298
114696
          popped_value = true;
299

114696
          if (size == max_queue_size && max_queue_size > 0) {
300
87637
            cond->Signal(lock);
301
          }
302
114696
          size--;
303
        }
304
305
114704
        if (size == 0) {
306
88
          if (thread_count == 0) {
307
13
            is_closing = true;
308
13
            if (max_queue_size > 0) {
309
9
              cond->Signal(lock);
310
            }
311
13
            CloseHandlesAndMaybeDelete();
312
          }
313
        } else {
314
114616
          has_more = true;
315
        }
316
      }
317
    }
318
319
114707
    if (popped_value) {
320
229392
      v8::HandleScope scope(env->isolate);
321
114696
      CallbackScope cb_scope(this);
322
114696
      napi_value js_callback = nullptr;
323
114696
      if (!ref.IsEmpty()) {
324
        v8::Local<v8::Function> js_cb =
325
209388
            v8::Local<v8::Function>::New(env->isolate, ref);
326
104694
        js_callback = v8impl::JsValueFromV8LocalValue(js_cb);
327
      }
328
114696
      env->CallIntoModule(
329
114696
          [&](napi_env env) { call_js_cb(env, js_callback, context, data); });
330
    }
331
332
114707
    return has_more;
333
  }
334
335
18
  void Finalize() {
336
36
    v8::HandleScope scope(env->isolate);
337
18
    if (finalize_cb) {
338
18
      CallbackScope cb_scope(this);
339
18
      env->CallIntoModule(
340
18
          [&](napi_env env) { finalize_cb(env, finalize_data, context); });
341
    }
342
18
    EmptyQueueAndDelete();
343
18
  }
344
345
18
  void CloseHandlesAndMaybeDelete(bool set_closing = false) {
346
18
    v8::HandleScope scope(env->isolate);
347
18
    if (set_closing) {
348
4
      node::Mutex::ScopedLock lock(this->mutex);
349
2
      is_closing = true;
350
2
      if (max_queue_size > 0) {
351
1
        cond->Signal(lock);
352
      }
353
    }
354
18
    if (handles_closing) {
355
      return;
356
    }
357
18
    handles_closing = true;
358
18
    env->node_env()->CloseHandle(
359
18
        reinterpret_cast<uv_handle_t*>(&async),
360
18
        [](uv_handle_t* handle) -> void {
361
          ThreadSafeFunction* ts_fn =
362
18
              node::ContainerOf(&ThreadSafeFunction::async,
363
18
                                reinterpret_cast<uv_async_t*>(handle));
364
18
          ts_fn->Finalize();
365
18
        });
366
  }
367
368
114825
  void Send() {
369
    // Ask currently running Dispatch() to make one more iteration
370
114825
    unsigned char current_state = dispatch_state.fetch_or(kDispatchPending);
371
114825
    if ((current_state & kDispatchRunning) == kDispatchRunning) {
372
105393
      return;
373
    }
374
375
9432
    CHECK_EQ(0, uv_async_send(&async));
376
  }
377
378
  // Default way of calling into JavaScript. Used when ThreadSafeFunction is
379
  //  without a call_js_cb_.
380
10000
  static void CallJs(napi_env env, napi_value cb, void* context, void* data) {
381

10000
    if (!(env == nullptr || cb == nullptr)) {
382
      napi_value recv;
383
      napi_status status;
384
385
10000
      status = napi_get_undefined(env, &recv);
386
10000
      if (status != napi_ok) {
387
        napi_throw_error(env,
388
                         "ERR_NAPI_TSFN_GET_UNDEFINED",
389
                         "Failed to retrieve undefined value");
390
        return;
391
      }
392
393
10000
      status = napi_call_function(env, recv, cb, 0, nullptr, nullptr);
394

10000
      if (status != napi_ok && status != napi_pending_exception) {
395
        napi_throw_error(
396
            env, "ERR_NAPI_TSFN_CALL_JS", "Failed to call JS callback");
397
        return;
398
      }
399
    }
400
  }
401
402
165
  static void AsyncCb(uv_async_t* async) {
403
    ThreadSafeFunction* ts_fn =
404
165
        node::ContainerOf(&ThreadSafeFunction::async, async);
405
165
    ts_fn->Dispatch();
406
165
  }
407
408
2
  static void Cleanup(void* data) {
409
2
    reinterpret_cast<ThreadSafeFunction*>(data)->CloseHandlesAndMaybeDelete(
410
        true);
411
2
  }
412
413
 private:
414
  static const unsigned char kDispatchIdle = 0;
415
  static const unsigned char kDispatchRunning = 1 << 0;
416
  static const unsigned char kDispatchPending = 1 << 1;
417
418
  static const unsigned int kMaxIterationCount = 1000;
419
420
  // These are variables protected by the mutex.
421
  node::Mutex mutex;
422
  std::unique_ptr<node::ConditionVariable> cond;
423
  std::queue<void*> queue;
424
  uv_async_t async;
425
  size_t thread_count;
426
  bool is_closing;
427
  std::atomic_uchar dispatch_state;
428
429
  // These are variables set once, upon creation, and then never again, which
430
  // means we don't need the mutex to read them.
431
  void* context;
432
  size_t max_queue_size;
433
434
  // These are variables accessed only from the loop thread.
435
  v8impl::Persistent<v8::Function> ref;
436
  node_napi_env env;
437
  void* finalize_data;
438
  napi_finalize finalize_cb;
439
  napi_threadsafe_function_call_js call_js_cb;
440
  bool handles_closing;
441
};
442
443
/**
444
 * Compared to node::AsyncResource, the resource object in AsyncContext is
445
 * gc-able. AsyncContext holds a weak reference to the resource object.
446
 * AsyncContext::MakeCallback doesn't implicitly set the receiver of the
447
 * callback to the resource object.
448
 */
449
class AsyncContext {
450
 public:
451
13
  AsyncContext(node_napi_env env,
452
               v8::Local<v8::Object> resource_object,
453
               const v8::Local<v8::String> resource_name,
454
               bool externally_managed_resource)
455
13
      : env_(env) {
456
13
    async_id_ = node_env()->new_async_id();
457
13
    trigger_async_id_ = node_env()->get_default_trigger_async_id();
458
13
    resource_.Reset(node_env()->isolate(), resource_object);
459
13
    lost_reference_ = false;
460
13
    if (externally_managed_resource) {
461
11
      resource_.SetWeak(
462
          this, AsyncContext::WeakCallback, v8::WeakCallbackType::kParameter);
463
    }
464
465
13
    node::AsyncWrap::EmitAsyncInit(node_env(),
466
                                   resource_object,
467
                                   resource_name,
468
                                   async_id_,
469
                                   trigger_async_id_);
470
13
  }
471
472
13
  ~AsyncContext() {
473
13
    resource_.Reset();
474
13
    lost_reference_ = true;
475
13
    node::AsyncWrap::EmitDestroy(node_env(), async_id_);
476
13
  }
477
478
8
  inline v8::MaybeLocal<v8::Value> MakeCallback(
479
      v8::Local<v8::Object> recv,
480
      const v8::Local<v8::Function> callback,
481
      int argc,
482
      v8::Local<v8::Value> argv[]) {
483
8
    EnsureReference();
484
    return node::InternalMakeCallback(node_env(),
485
                                      resource(),
486
                                      recv,
487
                                      callback,
488
                                      argc,
489
                                      argv,
490
8
                                      {async_id_, trigger_async_id_});
491
  }
492
493
4
  inline napi_callback_scope OpenCallbackScope() {
494
4
    EnsureReference();
495
    napi_callback_scope it =
496
4
        reinterpret_cast<napi_callback_scope>(new CallbackScope(this));
497
4
    env_->open_callback_scopes++;
498
4
    return it;
499
  }
500
501
12
  inline void EnsureReference() {
502
12
    if (lost_reference_) {
503
1
      const v8::HandleScope handle_scope(node_env()->isolate());
504
1
      resource_.Reset(node_env()->isolate(),
505
1
                      v8::Object::New(node_env()->isolate()));
506
1
      lost_reference_ = false;
507
    }
508
12
  }
509
510
92
  inline node::Environment* node_env() { return env_->node_env(); }
511
8
  inline v8::Local<v8::Object> resource() {
512
16
    return resource_.Get(node_env()->isolate());
513
  }
514
4
  inline node::async_context async_context() {
515
4
    return {async_id_, trigger_async_id_};
516
  }
517
518
4
  static inline void CloseCallbackScope(node_napi_env env,
519
                                        napi_callback_scope s) {
520
4
    CallbackScope* callback_scope = reinterpret_cast<CallbackScope*>(s);
521
4
    delete callback_scope;
522
4
    env->open_callback_scopes--;
523
4
  }
524
525
2
  static void WeakCallback(const v8::WeakCallbackInfo<AsyncContext>& data) {
526
2
    AsyncContext* async_context = data.GetParameter();
527
2
    async_context->resource_.Reset();
528
2
    async_context->lost_reference_ = true;
529
2
  }
530
531
 private:
532
  class CallbackScope : public node::CallbackScope {
533
   public:
534
4
    explicit CallbackScope(AsyncContext* async_context)
535
4
        : node::CallbackScope(async_context->node_env(),
536
                              async_context->resource_.Get(
537
                                  async_context->node_env()->isolate()),
538
8
                              async_context->async_context()) {}
539
  };
540
541
  node_napi_env env_;
542
  double async_id_;
543
  double trigger_async_id_;
544
  v8::Global<v8::Object> resource_;
545
  bool lost_reference_;
546
};
547
548
}  // end of anonymous namespace
549
550
}  // end of namespace v8impl
551
552
// Intercepts the Node-V8 module registration callback. Converts parameters
553
// to NAPI equivalents and then calls the registration callback specified
554
// by the NAPI module.
555
88
static void napi_module_register_cb(v8::Local<v8::Object> exports,
556
                                    v8::Local<v8::Value> module,
557
                                    v8::Local<v8::Context> context,
558
                                    void* priv) {
559
88
  napi_module_register_by_symbol(
560
      exports,
561
      module,
562
      context,
563
88
      static_cast<const napi_module*>(priv)->nm_register_func);
564
88
}
565
566
93
void napi_module_register_by_symbol(v8::Local<v8::Object> exports,
567
                                    v8::Local<v8::Value> module,
568
                                    v8::Local<v8::Context> context,
569
                                    napi_addon_register_func init) {
570
93
  node::Environment* node_env = node::Environment::GetCurrent(context);
571
93
  std::string module_filename = "";
572
93
  if (init == nullptr) {
573
1
    CHECK_NOT_NULL(node_env);
574
1
    node_env->ThrowError("Module has no declared entry point.");
575
1
    return;
576
  }
577
578
  // We set `env->filename` from `module.filename` here, but we could just as
579
  // easily add a private property to `exports` in `process.dlopen`, which
580
  // receives the file name from JS, and retrieve *that* here. Thus, we are not
581
  // endorsing commonjs here by making use of `module.filename`.
582
  v8::Local<v8::Value> filename_js;
583
  v8::Local<v8::Object> modobj;
584
92
  if (module->ToObject(context).ToLocal(&modobj) &&
585

460
      modobj->Get(context, node_env->filename_string()).ToLocal(&filename_js) &&
586
184
      filename_js->IsString()) {
587
87
    node::Utf8Value filename(node_env->isolate(), filename_js);
588
589
    // Turn the absolute path into a URL. Currently the absolute path is always
590
    // a file system path.
591
    // TODO(gabrielschulhof): Pass the `filename` through unchanged if/when we
592
    // receive it as a URL already.
593
87
    module_filename = node::url::URL::FromFilePath(filename.ToString()).href();
594
  }
595
596
  // Create a new napi_env for this specific module.
597
92
  napi_env env = v8impl::NewEnv(context, module_filename);
598
599
  napi_value _exports;
600
92
  env->CallIntoModule([&](napi_env env) {
601
92
    _exports = init(env, v8impl::JsValueFromV8LocalValue(exports));
602
92
  });
603
604
  // If register function returned a non-null exports object different from
605
  // the exports object we passed it, set that as the "exports" property of
606
  // the module.
607
178
  if (_exports != nullptr &&
608

178
      _exports != v8impl::JsValueFromV8LocalValue(exports)) {
609
4
    napi_value _module = v8impl::JsValueFromV8LocalValue(module);
610
4
    napi_set_named_property(env, _module, "exports", _exports);
611
  }
612
}
613
614
namespace node {
615
87
node_module napi_module_to_node_module(const napi_module* mod) {
616
  return {
617
      -1,
618
87
      mod->nm_flags | NM_F_DELETEME,
619
      nullptr,
620
87
      mod->nm_filename,
621
      nullptr,
622
      napi_module_register_cb,
623
87
      mod->nm_modname,
624
      const_cast<napi_module*>(mod),  // priv
625
      nullptr,
626
87
  };
627
}
628
}  // namespace node
629
630
// Registers a NAPI module.
631
84
void napi_module_register(napi_module* mod) {
632
  node::node_module* nm =
633
84
      new node::node_module(node::napi_module_to_node_module(mod));
634
84
  node::node_module_register(nm);
635
84
}
636
637
2
napi_status napi_add_env_cleanup_hook(napi_env env,
638
                                      void (*fun)(void* arg),
639
                                      void* arg) {
640
2
  CHECK_ENV(env);
641
2
  CHECK_ARG(env, fun);
642
643
2
  node::AddEnvironmentCleanupHook(env->isolate, fun, arg);
644
645
2
  return napi_ok;
646
}
647
648
1
napi_status napi_remove_env_cleanup_hook(napi_env env,
649
                                         void (*fun)(void* arg),
650
                                         void* arg) {
651
1
  CHECK_ENV(env);
652
1
  CHECK_ARG(env, fun);
653
654
1
  node::RemoveEnvironmentCleanupHook(env->isolate, fun, arg);
655
656
1
  return napi_ok;
657
}
658
659
struct napi_async_cleanup_hook_handle__ {
660
6
  napi_async_cleanup_hook_handle__(napi_env env,
661
                                   napi_async_cleanup_hook user_hook,
662
                                   void* user_data)
663
6
      : env_(env), user_hook_(user_hook), user_data_(user_data) {
664
6
    handle_ = node::AddEnvironmentCleanupHook(env->isolate, Hook, this);
665
6
    env->Ref();
666
6
  }
667
668
18
  ~napi_async_cleanup_hook_handle__() {
669
6
    node::RemoveEnvironmentCleanupHook(std::move(handle_));
670
6
    if (done_cb_ != nullptr) done_cb_(done_data_);
671
672
    // Release the `env` handle asynchronously since it would be surprising if
673
    // a call to a N-API function would destroy `env` synchronously.
674
12
    static_cast<node_napi_env>(env_)->node_env()->SetImmediate(
675
12
        [env = env_](node::Environment*) { env->Unref(); });
676
6
  }
677
678
4
  static void Hook(void* data, void (*done_cb)(void*), void* done_data) {
679
4
    napi_async_cleanup_hook_handle__* handle =
680
        static_cast<napi_async_cleanup_hook_handle__*>(data);
681
4
    handle->done_cb_ = done_cb;
682
4
    handle->done_data_ = done_data;
683
4
    handle->user_hook_(handle, handle->user_data_);
684
4
  }
685
686
  node::AsyncCleanupHookHandle handle_;
687
  napi_env env_ = nullptr;
688
  napi_async_cleanup_hook user_hook_ = nullptr;
689
  void* user_data_ = nullptr;
690
  void (*done_cb_)(void*) = nullptr;
691
  void* done_data_ = nullptr;
692
};
693
694
6
napi_status napi_add_async_cleanup_hook(
695
    napi_env env,
696
    napi_async_cleanup_hook hook,
697
    void* arg,
698
    napi_async_cleanup_hook_handle* remove_handle) {
699
6
  CHECK_ENV(env);
700
6
  CHECK_ARG(env, hook);
701
702
  napi_async_cleanup_hook_handle__* handle =
703
6
      new napi_async_cleanup_hook_handle__(env, hook, arg);
704
705
6
  if (remove_handle != nullptr) *remove_handle = handle;
706
707
6
  return napi_clear_last_error(env);
708
}
709
710
6
napi_status napi_remove_async_cleanup_hook(
711
    napi_async_cleanup_hook_handle remove_handle) {
712
6
  if (remove_handle == nullptr) return napi_invalid_arg;
713
714
6
  delete remove_handle;
715
716
6
  return napi_ok;
717
}
718
719
1
napi_status napi_fatal_exception(napi_env env, napi_value err) {
720


2
  NAPI_PREAMBLE(env);
721
1
  CHECK_ARG(env, err);
722
723
1
  v8::Local<v8::Value> local_err = v8impl::V8LocalValueFromJsValue(err);
724
1
  v8impl::trigger_fatal_exception(env, local_err);
725
726
1
  return napi_clear_last_error(env);
727
}
728
729
NAPI_NO_RETURN void napi_fatal_error(const char* location,
730
                                     size_t location_len,
731
                                     const char* message,
732
                                     size_t message_len) {
733
  std::string location_string;
734
  std::string message_string;
735
736
  if (location_len != NAPI_AUTO_LENGTH) {
737
    location_string.assign(const_cast<char*>(location), location_len);
738
  } else {
739
    location_string.assign(const_cast<char*>(location), strlen(location));
740
  }
741
742
  if (message_len != NAPI_AUTO_LENGTH) {
743
    message_string.assign(const_cast<char*>(message), message_len);
744
  } else {
745
    message_string.assign(const_cast<char*>(message), strlen(message));
746
  }
747
748
  node::FatalError(location_string.c_str(), message_string.c_str());
749
}
750
751
4
napi_status napi_open_callback_scope(napi_env env,
752
                                     napi_value /** ignored */,
753
                                     napi_async_context async_context_handle,
754
                                     napi_callback_scope* result) {
755
  // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw
756
  // JS exceptions.
757
4
  CHECK_ENV(env);
758
4
  CHECK_ARG(env, result);
759
760
4
  v8impl::AsyncContext* node_async_context =
761
      reinterpret_cast<v8impl::AsyncContext*>(async_context_handle);
762
763
4
  *result = node_async_context->OpenCallbackScope();
764
765
4
  return napi_clear_last_error(env);
766
}
767
768
4
napi_status napi_close_callback_scope(napi_env env, napi_callback_scope scope) {
769
  // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw
770
  // JS exceptions.
771
4
  CHECK_ENV(env);
772
4
  CHECK_ARG(env, scope);
773
4
  if (env->open_callback_scopes == 0) {
774
    return napi_callback_scope_mismatch;
775
  }
776
777
4
  v8impl::AsyncContext::CloseCallbackScope(reinterpret_cast<node_napi_env>(env),
778
                                           scope);
779
780
4
  return napi_clear_last_error(env);
781
}
782
783
13
napi_status napi_async_init(napi_env env,
784
                            napi_value async_resource,
785
                            napi_value async_resource_name,
786
                            napi_async_context* result) {
787
13
  CHECK_ENV(env);
788
13
  CHECK_ARG(env, async_resource_name);
789
13
  CHECK_ARG(env, result);
790
791
13
  v8::Isolate* isolate = env->isolate;
792
13
  v8::Local<v8::Context> context = env->context();
793
794
  v8::Local<v8::Object> v8_resource;
795
  bool externally_managed_resource;
796
13
  if (async_resource != nullptr) {
797

22
    CHECK_TO_OBJECT(env, context, v8_resource, async_resource);
798
11
    externally_managed_resource = true;
799
  } else {
800
2
    v8_resource = v8::Object::New(isolate);
801
2
    externally_managed_resource = false;
802
  }
803
804
  v8::Local<v8::String> v8_resource_name;
805

26
  CHECK_TO_STRING(env, context, v8_resource_name, async_resource_name);
806
807
  v8impl::AsyncContext* async_context =
808
      new v8impl::AsyncContext(reinterpret_cast<node_napi_env>(env),
809
                               v8_resource,
810
                               v8_resource_name,
811
13
                               externally_managed_resource);
812
813
13
  *result = reinterpret_cast<napi_async_context>(async_context);
814
815
13
  return napi_clear_last_error(env);
816
}
817
818
13
napi_status napi_async_destroy(napi_env env, napi_async_context async_context) {
819
13
  CHECK_ENV(env);
820
13
  CHECK_ARG(env, async_context);
821
822
13
  v8impl::AsyncContext* node_async_context =
823
      reinterpret_cast<v8impl::AsyncContext*>(async_context);
824
825
13
  delete node_async_context;
826
827
13
  return napi_clear_last_error(env);
828
}
829
830
25
napi_status napi_make_callback(napi_env env,
831
                               napi_async_context async_context,
832
                               napi_value recv,
833
                               napi_value func,
834
                               size_t argc,
835
                               const napi_value* argv,
836
                               napi_value* result) {
837


50
  NAPI_PREAMBLE(env);
838
25
  CHECK_ARG(env, recv);
839
25
  if (argc > 0) {
840
4
    CHECK_ARG(env, argv);
841
  }
842
843
25
  v8::Local<v8::Context> context = env->context();
844
845
  v8::Local<v8::Object> v8recv;
846

75
  CHECK_TO_OBJECT(env, context, v8recv, recv);
847
848
  v8::Local<v8::Function> v8func;
849

75
  CHECK_TO_FUNCTION(env, v8func, func);
850
851
  v8::MaybeLocal<v8::Value> callback_result;
852
853
25
  if (async_context == nullptr) {
854
    callback_result = node::MakeCallback(
855
17
        env->isolate,
856
        v8recv,
857
        v8func,
858
        argc,
859
        reinterpret_cast<v8::Local<v8::Value>*>(const_cast<napi_value*>(argv)),
860
17
        {0, 0});
861
  } else {
862
8
    v8impl::AsyncContext* node_async_context =
863
        reinterpret_cast<v8impl::AsyncContext*>(async_context);
864
    callback_result = node_async_context->MakeCallback(
865
        v8recv,
866
        v8func,
867
        argc,
868
8
        reinterpret_cast<v8::Local<v8::Value>*>(const_cast<napi_value*>(argv)));
869
  }
870
871
25
  if (try_catch.HasCaught()) {
872
4
    return napi_set_last_error(env, napi_pending_exception);
873
  } else {
874
21
    CHECK_MAYBE_EMPTY(env, callback_result, napi_generic_failure);
875
21
    if (result != nullptr) {
876
8
      *result =
877
8
          v8impl::JsValueFromV8LocalValue(callback_result.ToLocalChecked());
878
    }
879
  }
880
881
21
  return GET_RETURN_STATUS(env);
882
}
883
884
1
napi_status napi_create_buffer(napi_env env,
885
                               size_t length,
886
                               void** data,
887
                               napi_value* result) {
888


2
  NAPI_PREAMBLE(env);
889
1
  CHECK_ARG(env, result);
890
891
1
  v8::MaybeLocal<v8::Object> maybe = node::Buffer::New(env->isolate, length);
892
893
1
  CHECK_MAYBE_EMPTY(env, maybe, napi_generic_failure);
894
895
1
  v8::Local<v8::Object> buffer = maybe.ToLocalChecked();
896
897
1
  *result = v8impl::JsValueFromV8LocalValue(buffer);
898
899
1
  if (data != nullptr) {
900
1
    *data = node::Buffer::Data(buffer);
901
  }
902
903
1
  return GET_RETURN_STATUS(env);
904
}
905
906
13
napi_status napi_create_external_buffer(napi_env env,
907
                                        size_t length,
908
                                        void* data,
909
                                        napi_finalize finalize_cb,
910
                                        void* finalize_hint,
911
                                        napi_value* result) {
912


26
  NAPI_PREAMBLE(env);
913
13
  CHECK_ARG(env, result);
914
915
13
  v8::Isolate* isolate = env->isolate;
916
917
  // The finalizer object will delete itself after invoking the callback.
918
  v8impl::Finalizer* finalizer =
919
13
      v8impl::Finalizer::New(env,
920
                             finalize_cb,
921
                             nullptr,
922
                             finalize_hint,
923
                             v8impl::Finalizer::kKeepEnvReference);
924
925
  v8::MaybeLocal<v8::Object> maybe =
926
      node::Buffer::New(isolate,
927
                        static_cast<char*>(data),
928
                        length,
929
                        v8impl::BufferFinalizer::FinalizeBufferCallback,
930
13
                        finalizer);
931
932
13
  CHECK_MAYBE_EMPTY(env, maybe, napi_generic_failure);
933
934
13
  *result = v8impl::JsValueFromV8LocalValue(maybe.ToLocalChecked());
935
13
  return GET_RETURN_STATUS(env);
936
  // Tell coverity that 'finalizer' should not be freed when we return
937
  // as it will be deleted when the buffer to which it is associated
938
  // is finalized.
939
  // coverity[leaked_storage]
940
}
941
942
1
napi_status napi_create_buffer_copy(napi_env env,
943
                                    size_t length,
944
                                    const void* data,
945
                                    void** result_data,
946
                                    napi_value* result) {
947


2
  NAPI_PREAMBLE(env);
948
1
  CHECK_ARG(env, result);
949
950
  v8::MaybeLocal<v8::Object> maybe =
951
1
      node::Buffer::Copy(env->isolate, static_cast<const char*>(data), length);
952
953
1
  CHECK_MAYBE_EMPTY(env, maybe, napi_generic_failure);
954
955
1
  v8::Local<v8::Object> buffer = maybe.ToLocalChecked();
956
1
  *result = v8impl::JsValueFromV8LocalValue(buffer);
957
958
1
  if (result_data != nullptr) {
959
    *result_data = node::Buffer::Data(buffer);
960
  }
961
962
1
  return GET_RETURN_STATUS(env);
963
}
964
965
1
napi_status napi_is_buffer(napi_env env, napi_value value, bool* result) {
966
1
  CHECK_ENV(env);
967
1
  CHECK_ARG(env, value);
968
1
  CHECK_ARG(env, result);
969
970
1
  *result = node::Buffer::HasInstance(v8impl::V8LocalValueFromJsValue(value));
971
1
  return napi_clear_last_error(env);
972
}
973
974
1
napi_status napi_get_buffer_info(napi_env env,
975
                                 napi_value value,
976
                                 void** data,
977
                                 size_t* length) {
978
1
  CHECK_ENV(env);
979
1
  CHECK_ARG(env, value);
980
981
1
  v8::Local<v8::Value> buffer = v8impl::V8LocalValueFromJsValue(value);
982
983
1
  if (data != nullptr) {
984
1
    *data = node::Buffer::Data(buffer);
985
  }
986
1
  if (length != nullptr) {
987
1
    *length = node::Buffer::Length(buffer);
988
  }
989
990
1
  return napi_clear_last_error(env);
991
}
992
993
1
napi_status napi_get_node_version(napi_env env,
994
                                  const napi_node_version** result) {
995
1
  CHECK_ENV(env);
996
1
  CHECK_ARG(env, result);
997
  static const napi_node_version version = {
998
      NODE_MAJOR_VERSION, NODE_MINOR_VERSION, NODE_PATCH_VERSION, NODE_RELEASE};
999
1
  *result = &version;
1000
1
  return napi_clear_last_error(env);
1001
}
1002
1003
namespace {
1004
namespace uvimpl {
1005
1006
513
static napi_status ConvertUVErrorCode(int code) {
1007

513
  switch (code) {
1008
512
    case 0:
1009
512
      return napi_ok;
1010
    case UV_EINVAL:
1011
      return napi_invalid_arg;
1012
1
    case UV_ECANCELED:
1013
1
      return napi_cancelled;
1014
    default:
1015
      return napi_generic_failure;
1016
  }
1017
}
1018
1019
// Wrapper around uv_work_t which calls user-provided callbacks.
1020
class Work : public node::AsyncResource, public node::ThreadPoolWork {
1021
 private:
1022
512
  explicit Work(node_napi_env env,
1023
                v8::Local<v8::Object> async_resource,
1024
                v8::Local<v8::String> async_resource_name,
1025
                napi_async_execute_callback execute,
1026
                napi_async_complete_callback complete = nullptr,
1027
                void* data = nullptr)
1028
512
      : AsyncResource(
1029
512
            env->isolate,
1030
            async_resource,
1031
1024
            *v8::String::Utf8Value(env->isolate, async_resource_name)),
1032
        ThreadPoolWork(env->node_env()),
1033
        _env(env),
1034
        _data(data),
1035
        _execute(execute),
1036
1536
        _complete(complete) {}
1037
1038
2032
  ~Work() override = default;
1039
1040
 public:
1041
512
  static Work* New(node_napi_env env,
1042
                   v8::Local<v8::Object> async_resource,
1043
                   v8::Local<v8::String> async_resource_name,
1044
                   napi_async_execute_callback execute,
1045
                   napi_async_complete_callback complete,
1046
                   void* data) {
1047
    return new Work(
1048
512
        env, async_resource, async_resource_name, execute, complete, data);
1049
  }
1050
1051
508
  static void Delete(Work* work) { delete work; }
1052
1053
511
  void DoThreadPoolWork() override { _execute(_env, _data); }
1054
1055
512
  void AfterThreadPoolWork(int status) override {
1056
512
    if (_complete == nullptr) return;
1057
1058
    // Establish a handle scope here so that every callback doesn't have to.
1059
    // Also it is needed for the exception-handling below.
1060
1023
    v8::HandleScope scope(_env->isolate);
1061
1062
512
    CallbackScope callback_scope(this);
1063
1064
512
    _env->CallIntoModule(
1065
512
        [&](napi_env env) {
1066
512
          _complete(env, ConvertUVErrorCode(status), _data);
1067
512
        },
1068
2
        [](napi_env env, v8::Local<v8::Value> local_err) {
1069
          // If there was an unhandled exception in the complete callback,
1070
          // report it as a fatal exception. (There is no JavaScript on the
1071
          // callstack that can possibly handle it.)
1072
2
          v8impl::trigger_fatal_exception(env, local_err);
1073
1
        });
1074
1075
    // Note: Don't access `work` after this point because it was
1076
    // likely deleted by the complete callback.
1077
  }
1078
1079
 private:
1080
  node_napi_env _env;
1081
  void* _data;
1082
  napi_async_execute_callback _execute;
1083
  napi_async_complete_callback _complete;
1084
};
1085
1086
}  // end of namespace uvimpl
1087
}  // end of anonymous namespace
1088
1089
#define CALL_UV(env, condition)                                                \
1090
  do {                                                                         \
1091
    int result = (condition);                                                  \
1092
    napi_status status = uvimpl::ConvertUVErrorCode(result);                   \
1093
    if (status != napi_ok) {                                                   \
1094
      return napi_set_last_error(env, status, result);                         \
1095
    }                                                                          \
1096
  } while (0)
1097
1098
512
napi_status napi_create_async_work(napi_env env,
1099
                                   napi_value async_resource,
1100
                                   napi_value async_resource_name,
1101
                                   napi_async_execute_callback execute,
1102
                                   napi_async_complete_callback complete,
1103
                                   void* data,
1104
                                   napi_async_work* result) {
1105
512
  CHECK_ENV(env);
1106
512
  CHECK_ARG(env, execute);
1107
512
  CHECK_ARG(env, result);
1108
1109
512
  v8::Local<v8::Context> context = env->context();
1110
1111
  v8::Local<v8::Object> resource;
1112
512
  if (async_resource != nullptr) {
1113

12
    CHECK_TO_OBJECT(env, context, resource, async_resource);
1114
  } else {
1115
508
    resource = v8::Object::New(env->isolate);
1116
  }
1117
1118
  v8::Local<v8::String> resource_name;
1119

1024
  CHECK_TO_STRING(env, context, resource_name, async_resource_name);
1120
1121
512
  uvimpl::Work* work = uvimpl::Work::New(reinterpret_cast<node_napi_env>(env),
1122
                                         resource,
1123
                                         resource_name,
1124
                                         execute,
1125
                                         complete,
1126
                                         data);
1127
1128
512
  *result = reinterpret_cast<napi_async_work>(work);
1129
1130
512
  return napi_clear_last_error(env);
1131
}
1132
1133
508
napi_status napi_delete_async_work(napi_env env, napi_async_work work) {
1134
508
  CHECK_ENV(env);
1135
508
  CHECK_ARG(env, work);
1136
1137
508
  uvimpl::Work::Delete(reinterpret_cast<uvimpl::Work*>(work));
1138
1139
508
  return napi_clear_last_error(env);
1140
}
1141
1142
518
napi_status napi_get_uv_event_loop(napi_env env, uv_loop_t** loop) {
1143
518
  CHECK_ENV(env);
1144
518
  CHECK_ARG(env, loop);
1145
518
  *loop = reinterpret_cast<node_napi_env>(env)->node_env()->event_loop();
1146
518
  return napi_clear_last_error(env);
1147
}
1148
1149
512
napi_status napi_queue_async_work(napi_env env, napi_async_work work) {
1150
512
  CHECK_ENV(env);
1151
512
  CHECK_ARG(env, work);
1152
1153
512
  uv_loop_t* event_loop = nullptr;
1154
512
  STATUS_CALL(napi_get_uv_event_loop(env, &event_loop));
1155
1156
512
  uvimpl::Work* w = reinterpret_cast<uvimpl::Work*>(work);
1157
1158
512
  w->ScheduleWork();
1159
1160
512
  return napi_clear_last_error(env);
1161
}
1162
1163
1
napi_status napi_cancel_async_work(napi_env env, napi_async_work work) {
1164
1
  CHECK_ENV(env);
1165
1
  CHECK_ARG(env, work);
1166
1167
1
  uvimpl::Work* w = reinterpret_cast<uvimpl::Work*>(work);
1168
1169
1
  CALL_UV(env, w->CancelWork());
1170
1171
1
  return napi_clear_last_error(env);
1172
}
1173
1174
18
napi_status napi_create_threadsafe_function(
1175
    napi_env env,
1176
    napi_value func,
1177
    napi_value async_resource,
1178
    napi_value async_resource_name,
1179
    size_t max_queue_size,
1180
    size_t initial_thread_count,
1181
    void* thread_finalize_data,
1182
    napi_finalize thread_finalize_cb,
1183
    void* context,
1184
    napi_threadsafe_function_call_js call_js_cb,
1185
    napi_threadsafe_function* result) {
1186
18
  CHECK_ENV(env);
1187
18
  CHECK_ARG(env, async_resource_name);
1188
18
  RETURN_STATUS_IF_FALSE(env, initial_thread_count > 0, napi_invalid_arg);
1189
18
  CHECK_ARG(env, result);
1190
1191
18
  napi_status status = napi_ok;
1192
1193
  v8::Local<v8::Function> v8_func;
1194
18
  if (func == nullptr) {
1195
3
    CHECK_ARG(env, call_js_cb);
1196
  } else {
1197

45
    CHECK_TO_FUNCTION(env, v8_func, func);
1198
  }
1199
1200
18
  v8::Local<v8::Context> v8_context = env->context();
1201
1202
  v8::Local<v8::Object> v8_resource;
1203
18
  if (async_resource == nullptr) {
1204
18
    v8_resource = v8::Object::New(env->isolate);
1205
  } else {
1206
    CHECK_TO_OBJECT(env, v8_context, v8_resource, async_resource);
1207
  }
1208
1209
  v8::Local<v8::String> v8_name;
1210

36
  CHECK_TO_STRING(env, v8_context, v8_name, async_resource_name);
1211
1212
  v8impl::ThreadSafeFunction* ts_fn =
1213
      new v8impl::ThreadSafeFunction(v8_func,
1214
                                     v8_resource,
1215
                                     v8_name,
1216
                                     initial_thread_count,
1217
                                     context,
1218
                                     max_queue_size,
1219
                                     reinterpret_cast<node_napi_env>(env),
1220
                                     thread_finalize_data,
1221
                                     thread_finalize_cb,
1222
18
                                     call_js_cb);
1223
1224
18
  if (ts_fn == nullptr) {
1225
    status = napi_generic_failure;
1226
  } else {
1227
    // Init deletes ts_fn upon failure.
1228
18
    status = ts_fn->Init();
1229
18
    if (status == napi_ok) {
1230
18
      *result = reinterpret_cast<napi_threadsafe_function>(ts_fn);
1231
    }
1232
  }
1233
1234
18
  return napi_set_last_error(env, status);
1235
}
1236
1237
16
napi_status napi_get_threadsafe_function_context(napi_threadsafe_function func,
1238
                                                 void** result) {
1239
16
  CHECK_NOT_NULL(func);
1240
16
  CHECK_NOT_NULL(result);
1241
1242
16
  *result = reinterpret_cast<v8impl::ThreadSafeFunction*>(func)->Context();
1243
16
  return napi_ok;
1244
}
1245
1246
2946574
napi_status napi_call_threadsafe_function(
1247
    napi_threadsafe_function func,
1248
    void* data,
1249
    napi_threadsafe_function_call_mode is_blocking) {
1250
2946574
  CHECK_NOT_NULL(func);
1251
2946574
  return reinterpret_cast<v8impl::ThreadSafeFunction*>(func)->Push(data,
1252
2946574
                                                                   is_blocking);
1253
}
1254
1255
4
napi_status napi_acquire_threadsafe_function(napi_threadsafe_function func) {
1256
4
  CHECK_NOT_NULL(func);
1257
4
  return reinterpret_cast<v8impl::ThreadSafeFunction*>(func)->Acquire();
1258
}
1259
1260
33
napi_status napi_release_threadsafe_function(
1261
    napi_threadsafe_function func, napi_threadsafe_function_release_mode mode) {
1262
33
  CHECK_NOT_NULL(func);
1263
33
  return reinterpret_cast<v8impl::ThreadSafeFunction*>(func)->Release(mode);
1264
}
1265
1266
2
napi_status napi_unref_threadsafe_function(napi_env env,
1267
                                           napi_threadsafe_function func) {
1268
2
  CHECK_NOT_NULL(func);
1269
2
  return reinterpret_cast<v8impl::ThreadSafeFunction*>(func)->Unref();
1270
}
1271
1272
napi_status napi_ref_threadsafe_function(napi_env env,
1273
                                         napi_threadsafe_function func) {
1274
  CHECK_NOT_NULL(func);
1275
  return reinterpret_cast<v8impl::ThreadSafeFunction*>(func)->Ref();
1276
}
1277
1278
2
napi_status node_api_get_module_file_name(napi_env env, const char** result) {
1279
2
  CHECK_ENV(env);
1280
2
  CHECK_ARG(env, result);
1281
1282
2
  *result = static_cast<node_napi_env>(env)->GetFilename();
1283
2
  return napi_clear_last_error(env);
1284
}