• 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 "jsvm_types.h"
5 #include "js_native_api_v8_internals.h"
6 
7 inline JSVM_Status jsvm_clear_last_error(JSVM_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 class Agent;
52 }  // end of namespace v8impl
53 
54 struct JSVM_Env__ {
JSVM_Env__JSVM_Env__55   explicit JSVM_Env__(v8::Local<v8::Context> context,
56                       int32_t module_api_version)
57       : isolate(context->GetIsolate()),
58         context_persistent(isolate, context),
59         module_api_version(module_api_version) {
60     jsvm_clear_last_error(this);
61   }
62 
63   // Constructor for creating partial env.
64   explicit JSVM_Env__(v8::Isolate* isolate, int32_t module_api_version);
65 
66   template <typename Fn>
RequestInterruptJSVM_Env__67   inline void RequestInterrupt(Fn&& cb) {
68     auto callback = native_immediates_interrupts_.CreateCallback(
69         std::move(cb), node::CallbackFlags::kRefed);
70     {
71       node::Mutex::ScopedLock lock(native_immediates_threadsafe_mutex_);
72       native_immediates_interrupts_.Push(std::move(callback));
73     }
74     isolate->RequestInterrupt([](v8::Isolate* isolate, void* data) {
75       static_cast<JSVM_Env__*>(data)->RunAndClearInterrupts();
76     }, this);
77   }
78 
79   void RunAndClearInterrupts();
80 
inspector_agentJSVM_Env__81   v8impl::Agent* inspector_agent() { return inspector_agent_; }
82 
83   v8::Platform* platform();
84 
contextJSVM_Env__85   inline v8::Local<v8::Context> context() const {
86     return v8impl::PersistentToLocal::Strong(context_persistent);
87   }
88 
RefJSVM_Env__89   inline void Ref() { refs++; }
UnrefJSVM_Env__90   inline void Unref() {
91     if (--refs == 0) DeleteMe();
92   }
93 
can_call_into_jsJSVM_Env__94   virtual bool can_call_into_js() const { return true; }
95 
HandleThrowJSVM_Env__96   static inline void HandleThrow(JSVM_Env env, v8::Local<v8::Value> value) {
97     if (env->terminatedOrTerminating()) {
98       return;
99     }
100     env->isolate->ThrowException(value);
101   }
102 
103   // i.e. whether v8 exited or is about to exit
terminatedOrTerminatingJSVM_Env__104   inline bool terminatedOrTerminating() {
105     return this->isolate->IsExecutionTerminating() || !can_call_into_js();
106   }
107 
108   // v8 uses a special exception to indicate termination, the
109   // `handle_exception` callback should identify such case using
110   // terminatedOrTerminating() before actually handle the exception
111   template <typename T, typename U = decltype(HandleThrow)>
112   inline void CallIntoModule(T&& call, U&& handle_exception = HandleThrow) {
113     int open_handle_scopes_before = open_handle_scopes;
114     int open_callback_scopes_before = open_callback_scopes;
115     jsvm_clear_last_error(this);
116     call(this);
117     CHECK_EQ(open_handle_scopes, open_handle_scopes_before);
118     CHECK_EQ(open_callback_scopes, open_callback_scopes_before);
119     if (!last_exception.IsEmpty()) {
120       handle_exception(this, last_exception.Get(this->isolate));
121       last_exception.Reset();
122     }
123   }
124 
125   // Call finalizer immediately.
CallFinalizerJSVM_Env__126   virtual void CallFinalizer(JSVM_Finalize cb, void* data, void* hint) {
127     v8::HandleScope handle_scope(isolate);
128     CallIntoModule([&](JSVM_Env env) { cb(env, data, hint); });
129   }
130 
131   // Invoke finalizer from V8 garbage collector.
132   void InvokeFinalizerFromGC(v8impl::RefTracker* finalizer);
133 
134   // Enqueue the finalizer to the JSVM_Env's own queue of the second pass
135   // weak callback.
136   // Implementation should drain the queue at the time it is safe to call
137   // into JavaScript.
EnqueueFinalizerJSVM_Env__138   virtual void EnqueueFinalizer(v8impl::RefTracker* finalizer) {
139     pending_finalizers.emplace(finalizer);
140   }
141 
142   // Remove the finalizer from the scheduled second pass weak callback queue.
143   // The finalizer can be deleted after this call.
DequeueFinalizerJSVM_Env__144   virtual void DequeueFinalizer(v8impl::RefTracker* finalizer) {
145     pending_finalizers.erase(finalizer);
146   }
147 
148   virtual void DeleteMe();
149 
CheckGCAccessJSVM_Env__150   void CheckGCAccess() {
151     if (module_api_version == JSVM_VERSION_EXPERIMENTAL && in_gc_finalizer) {
152       v8impl::OnFatalError(
153           nullptr,
154           "Finalizer is calling a function that may affect GC state.\n"
155           "The finalizers are run directly from GC and must not affect GC "
156           "state.\n"
157           "Use `node_api_post_finalizer` from inside of the finalizer to work "
158           "around this issue.\n"
159           "It schedules the call as a new task in the event loop.");
160     }
161   }
162 
163   v8::Isolate* const isolate;  // Shortcut for context()->GetIsolate()
164   v8impl::Persistent<v8::Context> context_persistent;
165 
166   v8impl::Persistent<v8::Value> last_exception;
167 
168   // We store references in two different lists, depending on whether they have
169   // `JSVM_Finalizer` callbacks, because we must first finalize the ones that
170   // have such a callback. See `~JSVM_Env__()` above for details.
171   v8impl::RefTracker::RefList reflist;
172   v8impl::RefTracker::RefList finalizing_reflist;
173   // The invocation order of the finalizers is not determined.
174   std::unordered_set<v8impl::RefTracker*> pending_finalizers;
175   JSVM_ExtendedErrorInfo last_error;
176   int open_handle_scopes = 0;
177   int open_callback_scopes = 0;
178   int refs = 1;
179   void* instance_data = nullptr;
180   int32_t module_api_version = NODE_API_DEFAULT_MODULE_API_VERSION;
181   bool in_gc_finalizer = false;
182   v8::Locker* locker = nullptr;
183 
184  private:
185   v8impl::Agent* inspector_agent_;
186   typedef node::CallbackQueue<void, JSVM_Env__*> NativeImmediateQueue;
187   node::Mutex native_immediates_threadsafe_mutex_;
188   NativeImmediateQueue native_immediates_interrupts_;
189 
190  protected:
191   // Should not be deleted directly. Delete with `JSVM_Env__::DeleteMe()`
192   // instead.
193   virtual ~JSVM_Env__() = default;
194 };
195 
jsvm_clear_last_error(JSVM_Env env)196 inline JSVM_Status jsvm_clear_last_error(JSVM_Env env) {
197   env->last_error.errorCode = JSVM_OK;
198   env->last_error.engineErrorCode = 0;
199   env->last_error.engineReserved = nullptr;
200   env->last_error.errorMessage = nullptr;
201   return JSVM_OK;
202 }
203 
204 inline JSVM_Status jsvm_set_last_error(JSVM_Env env,
205                                        JSVM_Status errorCode,
206                                        uint32_t engineErrorCode = 0,
207                                        void* engineReserved = nullptr) {
208   env->last_error.errorCode = errorCode;
209   env->last_error.engineErrorCode = engineErrorCode;
210   env->last_error.engineReserved = engineReserved;
211   return errorCode;
212 }
213 
214 #define RETURN_STATUS_IF_FALSE(env, condition, status)                         \
215   do {                                                                         \
216     if (!(condition)) {                                                        \
217       return jsvm_set_last_error((env), (status));                             \
218     }                                                                          \
219   } while (0)
220 
221 #define RETURN_STATUS_IF_FALSE_WITH_PREAMBLE(env, condition, status)           \
222   do {                                                                         \
223     if (!(condition)) {                                                        \
224       return jsvm_set_last_error(                                              \
225           (env), try_catch.HasCaught() ? JSVM_PENDING_EXCEPTION : (status));   \
226     }                                                                          \
227   } while (0)
228 
229 #define CHECK_ENV(env)                                                         \
230   do {                                                                         \
231     if ((env) == nullptr) {                                                    \
232       return JSVM_INVALID_ARG;                                                 \
233     }                                                                          \
234   } while (0)
235 
236 #define CHECK_ENV_NOT_IN_GC(env)                                               \
237   do {                                                                         \
238     CHECK_ENV((env));                                                          \
239     (env)->CheckGCAccess();                                                    \
240   } while (0)
241 
242 #define CHECK_ARG(env, arg)                                                    \
243   RETURN_STATUS_IF_FALSE((env), ((arg) != nullptr), JSVM_INVALID_ARG)
244 
245 #define CHECK_ARG_WITH_PREAMBLE(env, arg)                                      \
246   RETURN_STATUS_IF_FALSE_WITH_PREAMBLE(                                        \
247       (env), ((arg) != nullptr), JSVM_INVALID_ARG)
248 
249 #define CHECK_MAYBE_EMPTY(env, maybe, status)                                  \
250   RETURN_STATUS_IF_FALSE((env), !((maybe).IsEmpty()), (status))
251 
252 #define CHECK_MAYBE_EMPTY_WITH_PREAMBLE(env, maybe, status)                    \
253   RETURN_STATUS_IF_FALSE_WITH_PREAMBLE((env), !((maybe).IsEmpty()), (status))
254 
255 // JSVM_PREAMBLE is not wrapped in do..while: try_catch must have function scope
256 #define JSVM_PREAMBLE(env)                                                     \
257   CHECK_ENV((env));                                                            \
258   RETURN_STATUS_IF_FALSE(                                                      \
259       (env), (env)->last_exception.IsEmpty(), JSVM_PENDING_EXCEPTION);         \
260   RETURN_STATUS_IF_FALSE((env),                                                \
261                          (env)->can_call_into_js(),                            \
262                          (env->module_api_version == JSVM_VERSION_EXPERIMENTAL \
263                               ? JSVM_CANNOT_RUN_JS                             \
264                               : JSVM_PENDING_EXCEPTION));                      \
265   jsvm_clear_last_error((env));                                                \
266   v8impl::TryCatch try_catch((env))
267 
268 #define CHECK_TO_TYPE(env, type, context, result, src, status)                 \
269   do {                                                                         \
270     CHECK_ARG((env), (src));                                                   \
271     auto maybe = v8impl::V8LocalValueFromJsValue((src))->To##type((context));  \
272     CHECK_MAYBE_EMPTY((env), maybe, (status));                                 \
273     (result) = maybe.ToLocalChecked();                                         \
274   } while (0)
275 
276 #define CHECK_TO_TYPE_WITH_PREAMBLE(env, type, context, result, src, status)   \
277   do {                                                                         \
278     CHECK_ARG_WITH_PREAMBLE((env), (src));                                     \
279     auto maybe = v8impl::V8LocalValueFromJsValue((src))->To##type((context));  \
280     CHECK_MAYBE_EMPTY_WITH_PREAMBLE((env), maybe, (status));                   \
281     (result) = maybe.ToLocalChecked();                                         \
282   } while (0)
283 
284 #define CHECK_TO_FUNCTION(env, result, src)                                    \
285   do {                                                                         \
286     CHECK_ARG((env), (src));                                                   \
287     v8::Local<v8::Value> v8value = v8impl::V8LocalValueFromJsValue((src));     \
288     RETURN_STATUS_IF_FALSE((env), v8value->IsFunction(), JSVM_INVALID_ARG);    \
289     (result) = v8value.As<v8::Function>();                                     \
290   } while (0)
291 
292 #define CHECK_TO_OBJECT(env, context, result, src)                             \
293   CHECK_TO_TYPE((env), Object, (context), (result), (src), JSVM_OBJECT_EXPECTED)
294 
295 #define CHECK_TO_OBJECT_WITH_PREAMBLE(env, context, result, src)               \
296   CHECK_TO_TYPE_WITH_PREAMBLE(                                                 \
297       (env), Object, (context), (result), (src), JSVM_OBJECT_EXPECTED)
298 
299 #define CHECK_TO_STRING(env, context, result, src)                             \
300   CHECK_TO_TYPE((env), String, (context), (result), (src), JSVM_STRING_EXPECTED)
301 
302 #define GET_RETURN_STATUS(env)                                                 \
303   (!try_catch.HasCaught()                                                      \
304        ? JSVM_OK                                                               \
305        : jsvm_set_last_error((env), JSVM_PENDING_EXCEPTION))
306 
307 #define THROW_RANGE_ERROR_IF_FALSE(env, condition, error, message)             \
308   do {                                                                         \
309     if (!(condition)) {                                                        \
310       OH_JSVM_ThrowRangeError((env), (error), (message));                       \
311       return jsvm_set_last_error((env), JSVM_GENERIC_FAILURE);                 \
312     }                                                                          \
313   } while (0)
314 
315 #define CHECK_MAYBE_EMPTY_WITH_PREAMBLE(env, maybe, status)                    \
316   RETURN_STATUS_IF_FALSE_WITH_PREAMBLE((env), !((maybe).IsEmpty()), (status))
317 
318 #define STATUS_CALL(call)                                                      \
319   do {                                                                         \
320     JSVM_Status status = (call);                                               \
321     if (status != JSVM_OK) return status;                                      \
322   } while (0)
323 
324 namespace v8impl {
325 
326 //=== Conversion between V8 Handles and JSVM_Value ========================
327 
328 // This asserts v8::Local<> will always be implemented with a single
329 // pointer field so that we can pass it around as a void*.
330 static_assert(sizeof(v8::Local<v8::Value>) == sizeof(JSVM_Value),
331               "Cannot convert between v8::Local<v8::Value> and JSVM_Value");
332 
JsValueFromV8LocalValue(v8::Local<v8::Value> local)333 inline JSVM_Value JsValueFromV8LocalValue(v8::Local<v8::Value> local) {
334   return reinterpret_cast<JSVM_Value>(*local);
335 }
336 
V8LocalValueFromJsValue(JSVM_Value v)337 inline v8::Local<v8::Value> V8LocalValueFromJsValue(JSVM_Value v) {
338   v8::Local<v8::Value> local;
339   memcpy(static_cast<void*>(&local), &v, sizeof(v));
340   return local;
341 }
342 
343 // Adapter for JSVM_Finalize callbacks.
344 class Finalizer {
345  protected:
Finalizer(JSVM_Env env,JSVM_Finalize finalize_callback,void * finalize_data,void * finalize_hint)346   Finalizer(JSVM_Env env,
347             JSVM_Finalize finalize_callback,
348             void* finalize_data,
349             void* finalize_hint)
350       : env_(env),
351         finalize_callback_(finalize_callback),
352         finalize_data_(finalize_data),
353         finalize_hint_(finalize_hint) {}
354 
355   virtual ~Finalizer() = default;
356 
357  public:
358   static Finalizer* New(JSVM_Env env,
359                         JSVM_Finalize finalize_callback = nullptr,
360                         void* finalize_data = nullptr,
361                         void* finalize_hint = nullptr) {
362     return new Finalizer(env, finalize_callback, finalize_data, finalize_hint);
363   }
364 
callback()365   JSVM_Finalize callback() { return finalize_callback_; }
data()366   void* data() { return finalize_data_; }
hint()367   void* hint() { return finalize_hint_; }
368 
369   void ResetFinalizer();
370 
371  protected:
372   JSVM_Env env_;
373   JSVM_Finalize finalize_callback_;
374   void* finalize_data_;
375   void* finalize_hint_;
376 };
377 
378 class TryCatch : public v8::TryCatch {
379  public:
TryCatch(JSVM_Env env)380   explicit TryCatch(JSVM_Env env) : v8::TryCatch(env->isolate), _env(env) {}
381 
~TryCatch()382   ~TryCatch() {
383     if (HasCaught()) {
384       _env->last_exception.Reset(_env->isolate, Exception());
385     }
386   }
387 
388  private:
389   JSVM_Env _env;
390 };
391 
392 // Ownership of a reference.
393 enum class Ownership {
394   // The reference is owned by the runtime. No userland call is needed to
395   // destruct the reference.
396   kRuntime,
397   // The reference is owned by the userland. User code is responsible to delete
398   // the reference with appropriate node-api calls.
399   kUserland,
400 };
401 
402 // Wrapper around Finalizer that can be tracked.
403 class TrackedFinalizer : public Finalizer, public RefTracker {
404  protected:
405   TrackedFinalizer(JSVM_Env env,
406                    JSVM_Finalize finalizeCallback,
407                    void* finalizeData,
408                    void* finalizeHint);
409 
410  public:
411   static TrackedFinalizer* New(JSVM_Env env,
412                                JSVM_Finalize finalizeCallback,
413                                void* finalizeData,
414                                void* finalizeHint);
415   ~TrackedFinalizer() override;
416 
417  protected:
418   void Finalize() override;
419   void FinalizeCore(bool deleteMe);
420 };
421 
422 // Wrapper around Finalizer that implements reference counting.
423 class RefBase : public TrackedFinalizer {
424  protected:
425   RefBase(JSVM_Env env,
426           uint32_t initialRefcount,
427           Ownership ownership,
428           JSVM_Finalize finalizeCallback,
429           void* finalizeData,
430           void* finalizeHint);
431 
432  public:
433   static RefBase* New(JSVM_Env env,
434                       uint32_t initialRefcount,
435                       Ownership ownership,
436                       JSVM_Finalize finalizeCallback,
437                       void* finalizeData,
438                       void* finalizeHint);
439 
440   void* Data();
441   uint32_t Ref();
442   uint32_t Unref();
443   uint32_t RefCount();
444 
ownership()445   Ownership ownership() { return ownership_; }
446 
447  protected:
448   void Finalize() override;
449 
450  private:
451   uint32_t refcount_;
452   Ownership ownership_;
453 };
454 
455 // Wrapper around v8impl::Persistent.
456 class Reference : public RefBase {
457  protected:
458   template <typename... Args>
459   Reference(JSVM_Env env, v8::Local<v8::Value> value, Args&&... args);
460 
461  public:
462   static Reference* New(JSVM_Env env,
463                         v8::Local<v8::Value> value,
464                         uint32_t initialRefcount,
465                         Ownership ownership,
466                         JSVM_Finalize finalizeCallback = nullptr,
467                         void* finalizeData = nullptr,
468                         void* finalizeHint = nullptr);
469 
470   virtual ~Reference();
471   uint32_t Ref();
472   uint32_t Unref();
473   v8::Local<v8::Value> Get();
474 
475  protected:
476   void Finalize() override;
477 
478  private:
479   static void WeakCallback(const v8::WeakCallbackInfo<Reference>& data);
480 
481   void SetWeak();
482 
483   v8impl::Persistent<v8::Value> persistent_;
484   bool can_be_weak_;
485 };
486 
487 typedef JSVM_Value (* GetterCallback)(JSVM_Env, JSVM_Value, JSVM_Value, JSVM_Value);
488 typedef JSVM_Value (* SetterCallback)(JSVM_Env, JSVM_Value, JSVM_Value, JSVM_Value, JSVM_Value);
489 typedef JSVM_Value (* DeleterCallback)(JSVM_Env, JSVM_Value, JSVM_Value, JSVM_Value);
490 typedef JSVM_Value (* EnumeratorCallback)(JSVM_Env, JSVM_Value, JSVM_Value);
491 
492 struct JSVM_PropertyHandlerCfgStruct {
493   GetterCallback namedGetterCallback_;
494   SetterCallback namedSetterCallback_;
495   DeleterCallback nameDeleterCallback_;
496   EnumeratorCallback namedEnumeratorCallback_;
497   GetterCallback indexedGetterCallback_;
498   SetterCallback indexedSetterCallback_;
499   DeleterCallback indexedDeleterCallback_;
500   EnumeratorCallback indexedEnumeratorCallback_;
501   JSVM_Ref namedPropertyData_;
502   JSVM_Ref indexedPropertyData_;
503 };
504 
CreatePropertyCfg(JSVM_Env env,JSVM_PropertyHandlerCfg propertyCfg)505 inline JSVM_PropertyHandlerCfgStruct* CreatePropertyCfg(JSVM_Env env, JSVM_PropertyHandlerCfg propertyCfg)
506 {
507   JSVM_PropertyHandlerCfgStruct* newPropertyCfg = new JSVM_PropertyHandlerCfgStruct;
508   if (newPropertyCfg != nullptr && propertyCfg != nullptr) {
509     newPropertyCfg->namedGetterCallback_ = propertyCfg->genericNamedPropertyGetterCallback;
510     newPropertyCfg->namedSetterCallback_ = propertyCfg->genericNamedPropertySetterCallback;
511     newPropertyCfg->nameDeleterCallback_ = propertyCfg->genericNamedPropertyDeleterCallback;
512     newPropertyCfg->namedEnumeratorCallback_ = propertyCfg->genericNamedPropertyEnumeratorCallback;
513     newPropertyCfg->indexedGetterCallback_ = propertyCfg->genericIndexedPropertyGetterCallback;
514     newPropertyCfg->indexedSetterCallback_ = propertyCfg->genericIndexedPropertySetterCallback;
515     newPropertyCfg->indexedDeleterCallback_ = propertyCfg->genericIndexedPropertyDeleterCallback;
516     newPropertyCfg->indexedEnumeratorCallback_ = propertyCfg->genericIndexedPropertyEnumeratorCallback;
517     newPropertyCfg->namedPropertyData_ = nullptr;
518     newPropertyCfg->indexedPropertyData_ = nullptr;
519     if (propertyCfg->namedPropertyData != nullptr) {
520       v8::Local<v8::Value> v8_value = v8impl::V8LocalValueFromJsValue(propertyCfg->namedPropertyData);
521       v8impl::Reference* reference = v8impl::Reference::New(env, v8_value, 1, v8impl::Ownership::kUserland);
522       newPropertyCfg->namedPropertyData_ = reinterpret_cast<JSVM_Ref>(reference);
523     }
524 
525     if (propertyCfg->indexedPropertyData != nullptr) {
526       v8::Local<v8::Value> v8_value = v8impl::V8LocalValueFromJsValue(propertyCfg->indexedPropertyData);
527       v8impl::Reference* reference = v8impl::Reference::New(env, v8_value, 1, v8impl::Ownership::kUserland);
528       newPropertyCfg->indexedPropertyData_ = reinterpret_cast<JSVM_Ref>(reference);
529     }
530   }
531 
532   return newPropertyCfg;
533 }
534 
CfgFinalizedCallback(JSVM_Env env,void * finalizeData,void * finalizeHint)535 inline void CfgFinalizedCallback(JSVM_Env env, void* finalizeData, void* finalizeHint)
536 {
537   auto cfg = reinterpret_cast<JSVM_PropertyHandlerCfgStruct *>(finalizeData);
538   if (cfg->namedPropertyData_ != nullptr) {
539     delete reinterpret_cast<v8impl::Reference*>(cfg->namedPropertyData_);
540   }
541   if (cfg->indexedPropertyData_ != nullptr) {
542     delete reinterpret_cast<v8impl::Reference*>(cfg->indexedPropertyData_);
543   }
544   delete cfg;
545 }
546 }  // end of namespace v8impl
547 
548 #endif  // SRC_JS_NATIVE_API_V8_H_
549