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