• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 #ifndef SRC_JS_NATIVE_API_V8_H_
2 #define SRC_JS_NATIVE_API_V8_H_
3 
4 #include "js_native_api_types.h"
5 #include "js_native_api_v8_internals.h"
6 
7 inline napi_status napi_clear_last_error(napi_env env);
8 
9 namespace v8impl {
10 
11 class RefTracker {
12  public:
RefTracker()13   RefTracker() {}
~RefTracker()14   virtual ~RefTracker() {}
Finalize()15   virtual void Finalize() {}
16 
17   typedef RefTracker RefList;
18 
Link(RefList * list)19   inline void Link(RefList* list) {
20     prev_ = list;
21     next_ = list->next_;
22     if (next_ != nullptr) {
23       next_->prev_ = this;
24     }
25     list->next_ = this;
26   }
27 
Unlink()28   inline void Unlink() {
29     if (prev_ != nullptr) {
30       prev_->next_ = next_;
31     }
32     if (next_ != nullptr) {
33       next_->prev_ = prev_;
34     }
35     prev_ = nullptr;
36     next_ = nullptr;
37   }
38 
FinalizeAll(RefList * list)39   static void FinalizeAll(RefList* list) {
40     while (list->next_ != nullptr) {
41       list->next_->Finalize();
42     }
43   }
44 
45  private:
46   RefList* next_ = nullptr;
47   RefList* prev_ = nullptr;
48 };
49 
50 class Finalizer;
51 }  // end of namespace v8impl
52 
53 struct napi_env__ {
napi_env__napi_env__54   explicit napi_env__(v8::Local<v8::Context> context,
55                       int32_t module_api_version)
56       : isolate(context->GetIsolate()),
57         context_persistent(isolate, context),
58         module_api_version(module_api_version) {
59     napi_clear_last_error(this);
60   }
61 
contextnapi_env__62   inline v8::Local<v8::Context> context() const {
63     return v8impl::PersistentToLocal::Strong(context_persistent);
64   }
65 
Refnapi_env__66   inline void Ref() { refs++; }
Unrefnapi_env__67   inline void Unref() {
68     if (--refs == 0) DeleteMe();
69   }
70 
can_call_into_jsnapi_env__71   virtual bool can_call_into_js() const { return true; }
72 
HandleThrownapi_env__73   static inline void HandleThrow(napi_env env, v8::Local<v8::Value> value) {
74     if (env->terminatedOrTerminating()) {
75       return;
76     }
77     env->isolate->ThrowException(value);
78   }
79 
80   // i.e. whether v8 exited or is about to exit
terminatedOrTerminatingnapi_env__81   inline bool terminatedOrTerminating() {
82     return this->isolate->IsExecutionTerminating() || !can_call_into_js();
83   }
84 
85   // v8 uses a special exception to indicate termination, the
86   // `handle_exception` callback should identify such case using
87   // terminatedOrTerminating() before actually handle the exception
88   template <typename T, typename U = decltype(HandleThrow)>
89   inline void CallIntoModule(T&& call, U&& handle_exception = HandleThrow) {
90     int open_handle_scopes_before = open_handle_scopes;
91     int open_callback_scopes_before = open_callback_scopes;
92     napi_clear_last_error(this);
93     call(this);
94     CHECK_EQ(open_handle_scopes, open_handle_scopes_before);
95     CHECK_EQ(open_callback_scopes, open_callback_scopes_before);
96     if (!last_exception.IsEmpty()) {
97       handle_exception(this, last_exception.Get(this->isolate));
98       last_exception.Reset();
99     }
100   }
101 
102   // Call finalizer immediately.
CallFinalizernapi_env__103   virtual void CallFinalizer(napi_finalize cb, void* data, void* hint) {
104     v8::HandleScope handle_scope(isolate);
105     CallIntoModule([&](napi_env env) { cb(env, data, hint); });
106   }
107 
108   // Enqueue the finalizer to the napi_env's own queue of the second pass
109   // weak callback.
110   // Implementation should drain the queue at the time it is safe to call
111   // into JavaScript.
EnqueueFinalizernapi_env__112   virtual void EnqueueFinalizer(v8impl::RefTracker* finalizer) {
113     pending_finalizers.emplace(finalizer);
114   }
115 
116   // Remove the finalizer from the scheduled second pass weak callback queue.
117   // The finalizer can be deleted after this call.
DequeueFinalizernapi_env__118   virtual void DequeueFinalizer(v8impl::RefTracker* finalizer) {
119     pending_finalizers.erase(finalizer);
120   }
121 
DeleteMenapi_env__122   virtual void DeleteMe() {
123     // First we must finalize those references that have `napi_finalizer`
124     // callbacks. The reason is that addons might store other references which
125     // they delete during their `napi_finalizer` callbacks. If we deleted such
126     // references here first, they would be doubly deleted when the
127     // `napi_finalizer` deleted them subsequently.
128     v8impl::RefTracker::FinalizeAll(&finalizing_reflist);
129     v8impl::RefTracker::FinalizeAll(&reflist);
130     delete this;
131   }
132 
133   v8::Isolate* const isolate;  // Shortcut for context()->GetIsolate()
134   v8impl::Persistent<v8::Context> context_persistent;
135 
136   v8impl::Persistent<v8::Value> last_exception;
137 
138   // We store references in two different lists, depending on whether they have
139   // `napi_finalizer` callbacks, because we must first finalize the ones that
140   // have such a callback. See `~napi_env__()` above for details.
141   v8impl::RefTracker::RefList reflist;
142   v8impl::RefTracker::RefList finalizing_reflist;
143   // The invocation order of the finalizers is not determined.
144   std::unordered_set<v8impl::RefTracker*> pending_finalizers;
145   napi_extended_error_info last_error;
146   int open_handle_scopes = 0;
147   int open_callback_scopes = 0;
148   int refs = 1;
149   void* instance_data = nullptr;
150   int32_t module_api_version = NODE_API_DEFAULT_MODULE_API_VERSION;
151 
152  protected:
153   // Should not be deleted directly. Delete with `napi_env__::DeleteMe()`
154   // instead.
155   virtual ~napi_env__() = default;
156 };
157 
napi_clear_last_error(napi_env env)158 inline napi_status napi_clear_last_error(napi_env env) {
159   env->last_error.error_code = napi_ok;
160   env->last_error.engine_error_code = 0;
161   env->last_error.engine_reserved = nullptr;
162   env->last_error.error_message = nullptr;
163   return napi_ok;
164 }
165 
166 inline napi_status napi_set_last_error(napi_env env,
167                                        napi_status error_code,
168                                        uint32_t engine_error_code = 0,
169                                        void* engine_reserved = nullptr) {
170   env->last_error.error_code = error_code;
171   env->last_error.engine_error_code = engine_error_code;
172   env->last_error.engine_reserved = engine_reserved;
173   return error_code;
174 }
175 
176 #define RETURN_STATUS_IF_FALSE(env, condition, status)                         \
177   do {                                                                         \
178     if (!(condition)) {                                                        \
179       return napi_set_last_error((env), (status));                             \
180     }                                                                          \
181   } while (0)
182 
183 #define RETURN_STATUS_IF_FALSE_WITH_PREAMBLE(env, condition, status)           \
184   do {                                                                         \
185     if (!(condition)) {                                                        \
186       return napi_set_last_error(                                              \
187           (env), try_catch.HasCaught() ? napi_pending_exception : (status));   \
188     }                                                                          \
189   } while (0)
190 
191 #define CHECK_ENV(env)                                                         \
192   do {                                                                         \
193     if ((env) == nullptr) {                                                    \
194       return napi_invalid_arg;                                                 \
195     }                                                                          \
196   } while (0)
197 
198 #define CHECK_ARG(env, arg)                                                    \
199   RETURN_STATUS_IF_FALSE((env), ((arg) != nullptr), napi_invalid_arg)
200 
201 #define CHECK_ARG_WITH_PREAMBLE(env, arg)                                      \
202   RETURN_STATUS_IF_FALSE_WITH_PREAMBLE(                                        \
203       (env), ((arg) != nullptr), napi_invalid_arg)
204 
205 #define CHECK_MAYBE_EMPTY(env, maybe, status)                                  \
206   RETURN_STATUS_IF_FALSE((env), !((maybe).IsEmpty()), (status))
207 
208 #define CHECK_MAYBE_EMPTY_WITH_PREAMBLE(env, maybe, status)                    \
209   RETURN_STATUS_IF_FALSE_WITH_PREAMBLE((env), !((maybe).IsEmpty()), (status))
210 
211 // NAPI_PREAMBLE is not wrapped in do..while: try_catch must have function scope
212 #define NAPI_PREAMBLE(env)                                                     \
213   CHECK_ENV((env));                                                            \
214   RETURN_STATUS_IF_FALSE(                                                      \
215       (env), (env)->last_exception.IsEmpty(), napi_pending_exception);         \
216   RETURN_STATUS_IF_FALSE((env),                                                \
217                          (env)->can_call_into_js(),                            \
218                          (env->module_api_version == NAPI_VERSION_EXPERIMENTAL \
219                               ? napi_cannot_run_js                             \
220                               : napi_pending_exception));                      \
221   napi_clear_last_error((env));                                                \
222   v8impl::TryCatch try_catch((env))
223 
224 #define CHECK_TO_TYPE(env, type, context, result, src, status)                 \
225   do {                                                                         \
226     CHECK_ARG((env), (src));                                                   \
227     auto maybe = v8impl::V8LocalValueFromJsValue((src))->To##type((context));  \
228     CHECK_MAYBE_EMPTY((env), maybe, (status));                                 \
229     (result) = maybe.ToLocalChecked();                                         \
230   } while (0)
231 
232 #define CHECK_TO_TYPE_WITH_PREAMBLE(env, type, context, result, src, status)   \
233   do {                                                                         \
234     CHECK_ARG_WITH_PREAMBLE((env), (src));                                     \
235     auto maybe = v8impl::V8LocalValueFromJsValue((src))->To##type((context));  \
236     CHECK_MAYBE_EMPTY_WITH_PREAMBLE((env), maybe, (status));                   \
237     (result) = maybe.ToLocalChecked();                                         \
238   } while (0)
239 
240 #define CHECK_TO_FUNCTION(env, result, src)                                    \
241   do {                                                                         \
242     CHECK_ARG((env), (src));                                                   \
243     v8::Local<v8::Value> v8value = v8impl::V8LocalValueFromJsValue((src));     \
244     RETURN_STATUS_IF_FALSE((env), v8value->IsFunction(), napi_invalid_arg);    \
245     (result) = v8value.As<v8::Function>();                                     \
246   } while (0)
247 
248 #define CHECK_TO_OBJECT(env, context, result, src)                             \
249   CHECK_TO_TYPE((env), Object, (context), (result), (src), napi_object_expected)
250 
251 #define CHECK_TO_OBJECT_WITH_PREAMBLE(env, context, result, src)               \
252   CHECK_TO_TYPE_WITH_PREAMBLE(                                                 \
253       (env), Object, (context), (result), (src), napi_object_expected)
254 
255 #define CHECK_TO_STRING(env, context, result, src)                             \
256   CHECK_TO_TYPE((env), String, (context), (result), (src), napi_string_expected)
257 
258 #define GET_RETURN_STATUS(env)                                                 \
259   (!try_catch.HasCaught()                                                      \
260        ? napi_ok                                                               \
261        : napi_set_last_error((env), napi_pending_exception))
262 
263 #define THROW_RANGE_ERROR_IF_FALSE(env, condition, error, message)             \
264   do {                                                                         \
265     if (!(condition)) {                                                        \
266       napi_throw_range_error((env), (error), (message));                       \
267       return napi_set_last_error((env), napi_generic_failure);                 \
268     }                                                                          \
269   } while (0)
270 
271 #define RETURN_STATUS_IF_FALSE_WITH_PREAMBLE(env, condition, status)           \
272   do {                                                                         \
273     if (!(condition)) {                                                        \
274       return napi_set_last_error(                                              \
275           (env), try_catch.HasCaught() ? napi_pending_exception : (status));   \
276     }                                                                          \
277   } while (0)
278 
279 #define CHECK_MAYBE_EMPTY_WITH_PREAMBLE(env, maybe, status)                    \
280   RETURN_STATUS_IF_FALSE_WITH_PREAMBLE((env), !((maybe).IsEmpty()), (status))
281 
282 #define STATUS_CALL(call)                                                      \
283   do {                                                                         \
284     napi_status status = (call);                                               \
285     if (status != napi_ok) return status;                                      \
286   } while (0)
287 
288 namespace v8impl {
289 
290 //=== Conversion between V8 Handles and napi_value ========================
291 
292 // This asserts v8::Local<> will always be implemented with a single
293 // pointer field so that we can pass it around as a void*.
294 static_assert(sizeof(v8::Local<v8::Value>) == sizeof(napi_value),
295               "Cannot convert between v8::Local<v8::Value> and napi_value");
296 
JsValueFromV8LocalValue(v8::Local<v8::Value> local)297 inline napi_value JsValueFromV8LocalValue(v8::Local<v8::Value> local) {
298   return reinterpret_cast<napi_value>(*local);
299 }
300 
V8LocalValueFromJsValue(napi_value v)301 inline v8::Local<v8::Value> V8LocalValueFromJsValue(napi_value v) {
302   v8::Local<v8::Value> local;
303   memcpy(static_cast<void*>(&local), &v, sizeof(v));
304   return local;
305 }
306 
307 // Adapter for napi_finalize callbacks.
308 class Finalizer {
309  protected:
Finalizer(napi_env env,napi_finalize finalize_callback,void * finalize_data,void * finalize_hint)310   Finalizer(napi_env env,
311             napi_finalize finalize_callback,
312             void* finalize_data,
313             void* finalize_hint)
314       : env_(env),
315         finalize_callback_(finalize_callback),
316         finalize_data_(finalize_data),
317         finalize_hint_(finalize_hint) {}
318 
319   virtual ~Finalizer() = default;
320 
321  public:
322   static Finalizer* New(napi_env env,
323                         napi_finalize finalize_callback = nullptr,
324                         void* finalize_data = nullptr,
325                         void* finalize_hint = nullptr) {
326     return new Finalizer(env, finalize_callback, finalize_data, finalize_hint);
327   }
328 
callback()329   napi_finalize callback() { return finalize_callback_; }
data()330   void* data() { return finalize_data_; }
hint()331   void* hint() { return finalize_hint_; }
332 
333   void ResetFinalizer();
334 
335  protected:
336   napi_env env_;
337   napi_finalize finalize_callback_;
338   void* finalize_data_;
339   void* finalize_hint_;
340 };
341 
342 class TryCatch : public v8::TryCatch {
343  public:
TryCatch(napi_env env)344   explicit TryCatch(napi_env env) : v8::TryCatch(env->isolate), _env(env) {}
345 
~TryCatch()346   ~TryCatch() {
347     if (HasCaught()) {
348       _env->last_exception.Reset(_env->isolate, Exception());
349     }
350   }
351 
352  private:
353   napi_env _env;
354 };
355 
356 // Ownership of a reference.
357 enum class Ownership {
358   // The reference is owned by the runtime. No userland call is needed to
359   // destruct the reference.
360   kRuntime,
361   // The reference is owned by the userland. User code is responsible to delete
362   // the reference with appropriate node-api calls.
363   kUserland,
364 };
365 
366 // Wrapper around Finalizer that implements reference counting.
367 class RefBase : public Finalizer, public RefTracker {
368  protected:
369   RefBase(napi_env env,
370           uint32_t initial_refcount,
371           Ownership ownership,
372           napi_finalize finalize_callback,
373           void* finalize_data,
374           void* finalize_hint);
375 
376  public:
377   static RefBase* New(napi_env env,
378                       uint32_t initial_refcount,
379                       Ownership ownership,
380                       napi_finalize finalize_callback,
381                       void* finalize_data,
382                       void* finalize_hint);
383   virtual ~RefBase();
384 
385   void* Data();
386   uint32_t Ref();
387   uint32_t Unref();
388   uint32_t RefCount();
389 
ownership()390   Ownership ownership() { return ownership_; }
391 
392  protected:
393   void Finalize() override;
394 
395  private:
396   uint32_t refcount_;
397   Ownership ownership_;
398 };
399 
400 // Wrapper around v8impl::Persistent.
401 class Reference : public RefBase {
402  protected:
403   template <typename... Args>
404   Reference(napi_env env, v8::Local<v8::Value> value, Args&&... args);
405 
406  public:
407   static Reference* New(napi_env env,
408                         v8::Local<v8::Value> value,
409                         uint32_t initial_refcount,
410                         Ownership ownership,
411                         napi_finalize finalize_callback = nullptr,
412                         void* finalize_data = nullptr,
413                         void* finalize_hint = nullptr);
414 
415   virtual ~Reference();
416   uint32_t Ref();
417   uint32_t Unref();
418   v8::Local<v8::Value> Get();
419 
420  protected:
421   void Finalize() override;
422 
423  private:
424   static void WeakCallback(const v8::WeakCallbackInfo<Reference>& data);
425 
426   void SetWeak();
427 
428   v8impl::Persistent<v8::Value> persistent_;
429   bool can_be_weak_;
430 };
431 
432 }  // end of namespace v8impl
433 
434 #endif  // SRC_JS_NATIVE_API_V8_H_
435