1 /**
2 * Copyright (c) 2023-2025 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "plugins/ets/runtime/interop_js/interop_context.h"
17 #include "plugins/ets/runtime/interop_js/interop_context_api.h"
18
19 #include "plugins/ets/runtime/ets_exceptions.h"
20 #include "plugins/ets/runtime/ets_class_linker_extension.h"
21 #include "plugins/ets/runtime/ets_vm.h"
22 #include "plugins/ets/runtime/interop_js/js_convert.h"
23 #include "plugins/ets/runtime/interop_js/interop_common.h"
24 #include "plugins/ets/runtime/interop_js/code_scopes.h"
25 #include "plugins/ets/runtime/interop_js/sts_vm_interface_impl.h"
26 #include "plugins/ets/runtime/types/ets_abc_runtime_linker.h"
27 #include "plugins/ets/runtime/types/ets_method.h"
28 #include "runtime/include/runtime.h"
29 #include "runtime/mem/local_object_handle.h"
30
31 #include "plugins/ets/runtime/interop_js/event_loop_module.h"
32 #include "plugins/ets/runtime/interop_js/timer_module.h"
33 #include "plugins/ets/runtime/interop_js/xgc/xgc.h"
34 #include "plugins/ets/runtime/interop_js/handshake.h"
35 #include "plugins/ets/runtime/interop_js/napi_impl/napi_impl.h"
36 #include "plugins/ets/runtime/interop_js/timer_helper/interop_timer_helper.h"
37
38 #if defined(PANDA_TARGET_OHOS) || defined(PANDA_JS_ETS_HYBRID_MODE)
39 // NOLINTBEGIN(readability-identifier-naming, readability-redundant-declaration)
40 // CC-OFFNXT(G.FMT.10-CPP) project code style
41 napi_status __attribute__((weak)) napi_create_runtime(napi_env env, napi_env *resultEnv);
42 napi_status __attribute__((weak)) napi_throw_jsvalue(napi_env env, napi_value error);
43 napi_status __attribute__((weak)) napi_setup_hybrid_environment(napi_env env);
44 // NOLINTEND(readability-identifier-naming, readability-redundant-declaration)
45 #endif
46
47 // NOTE(konstanting, #23205): this function is not listed in the ENUMERATE_NAPI macro, but now runtime needs it.
48 // I will find a cleaner solution later, but for now let it stay like this, to make aot and verifier happy.
49 #if (!defined(PANDA_TARGET_OHOS) && !defined(PANDA_JS_ETS_HYBRID_MODE)) || \
50 defined(PANDA_JS_ETS_HYBRID_MODE_NEED_WEAK_SYMBOLS)
51 extern "C" napi_status __attribute__((weak)) // CC-OFF(G.FMT.10) project code style
napi_add_env_cleanup_hook(napi_env env,void (* fun)(void * arg),void * arg)52 napi_add_env_cleanup_hook([[maybe_unused]] napi_env env, [[maybe_unused]] void (*fun)(void *arg),
53 [[maybe_unused]] void *arg)
54 {
55 // NOTE: Empty stub. In CI currently used OHOS 4.0.8, but `napi_add_env_cleanup_hook`
56 // is public since 4.1.0. Remove this method after CI upgrade.
57 INTEROP_LOG(ERROR) << "napi_add_env_cleanup_hook is implemented in OHOS since 4.1.0, please update" << std::endl;
58 return napi_ok;
59 }
60 #endif
61
62 namespace ark::ets::interop::js {
63
64 namespace descriptors = panda_file_items::class_descriptors;
65
66 // NOLINTNEXTLINE(modernize-avoid-c-arrays)
67 static constexpr std::string_view const FUNCTION_INTERFACE_DESCRIPTORS[] = {
68 descriptors::FUNCTION0, descriptors::FUNCTION1, descriptors::FUNCTION2, descriptors::FUNCTION3,
69 descriptors::FUNCTION4, descriptors::FUNCTION5, descriptors::FUNCTION6, descriptors::FUNCTION7,
70 descriptors::FUNCTION8, descriptors::FUNCTION9, descriptors::FUNCTION10, descriptors::FUNCTION11,
71 descriptors::FUNCTION12, descriptors::FUNCTION13, descriptors::FUNCTION14, descriptors::FUNCTION15,
72 descriptors::FUNCTION16, descriptors::FUNCTIONN, descriptors::FUNCTIONR0, descriptors::FUNCTIONR1,
73 descriptors::FUNCTIONR2, descriptors::FUNCTIONR3, descriptors::FUNCTIONR4, descriptors::FUNCTIONR5,
74 descriptors::FUNCTIONR6, descriptors::FUNCTIONR7, descriptors::FUNCTIONR8, descriptors::FUNCTIONR9,
75 descriptors::FUNCTIONR10, descriptors::FUNCTIONR11, descriptors::FUNCTIONR12, descriptors::FUNCTIONR13,
76 descriptors::FUNCTIONR14, descriptors::FUNCTIONR15, descriptors::FUNCTIONR16,
77 };
78
CacheClass(EtsClassLinker * etsClassLinker,std::string_view descriptor)79 static Class *CacheClass(EtsClassLinker *etsClassLinker, std::string_view descriptor)
80 {
81 ASSERT(etsClassLinker != nullptr);
82 ASSERT(etsClassLinker->GetClass(descriptor.data()));
83 auto klass = etsClassLinker->GetClass(descriptor.data())->GetRuntimeClass();
84 ASSERT(klass != nullptr);
85 return klass;
86 }
87
88 #if defined(PANDA_TARGET_OHOS)
AppStateCallback(int state,int64_t timeStamp)89 static void AppStateCallback(int state, int64_t timeStamp)
90 {
91 auto appState = AppState(static_cast<AppState::State>(state), timeStamp);
92 auto *etsVm = static_cast<PandaEtsVM *>(Runtime::GetCurrent()->GetPandaVM());
93 AppStateManager::GetCurrent()->UpdateAppState(appState);
94 etsVm->GetGC()->SetFastGCFlag(appState.GetState() == AppState::State::SENSITIVE_START);
95 switch (static_cast<AppState::State>(state)) {
96 case AppState::State::SENSITIVE_START:
97 etsVm->GetGC()->PostponeGCStart();
98 break;
99 case AppState::State::SENSITIVE_END:
100 [[fallthrough]];
101 case AppState::State::COLD_START_FINISHED:
102 etsVm->GetGC()->PostponeGCEnd();
103 break;
104 default:
105 break;
106 }
107 }
108 #endif // PANDA_TARGET_OHOS
109
RegisterAppStateCallback(napi_env env)110 static bool RegisterAppStateCallback([[maybe_unused]] napi_env env)
111 {
112 #if defined(PANDA_TARGET_OHOS)
113 auto status = napi_register_appstate_callback(env, AppStateCallback);
114 return status == napi_ok;
115 #else
116 return true;
117 #endif
118 }
119
RegisterTimerModule()120 static bool RegisterTimerModule()
121 {
122 ani_vm *vm = nullptr;
123 ani_size count = 0;
124 // NOTE(konstanting, #23205): port to ANI ASAP
125 [[maybe_unused]] auto status = ANI_GetCreatedVMs(&vm, 1, &count);
126 ASSERT(status == ANI_OK);
127
128 if (count != 1) {
129 INTEROP_LOG(ERROR) << "RegisterTimerModule: No VM found";
130 return false;
131 }
132
133 ani_env *aniEnv = nullptr;
134 status = vm->GetEnv(ANI_VERSION_1, &aniEnv);
135 ASSERT(status == ANI_OK);
136 return TimerModule::Init(aniEnv);
137 }
138
RegisterEventLoopModule(EtsCoroutine * coro)139 static void RegisterEventLoopModule(EtsCoroutine *coro)
140 {
141 ASSERT(coro != nullptr);
142 ASSERT(coro == coro->GetPandaVM()->GetCoroutineManager()->GetMainThread());
143 coro->GetPandaVM()->CreateCallbackPosterFactory<EventLoopCallbackPosterFactoryImpl>();
144 coro->GetPandaVM()->SetRunEventLoopFunction(
145 [](EventLoopRunMode mode) { EventLoop::RunEventLoop(static_cast<EventLoopRunMode>(mode)); });
146 coro->GetPandaVM()->SetWalkEventLoopFunction(
147 [](WalkEventLoopCallback &cb, void *args) { EventLoop::WalkEventLoop(cb, args); });
148 }
149
150 std::atomic_uint32_t ConstStringStorage::qnameBufferSize_ {0U};
151
LoadDynamicCallClass(Class * klass)152 void ConstStringStorage::LoadDynamicCallClass(Class *klass)
153 {
154 if (klass == lastLoadedClass_) {
155 return;
156 }
157 lastLoadedClass_ = klass;
158 auto fields = klass->GetStaticFields();
159 ASSERT(fields.Size() == 1);
160 auto startFrom = klass->GetFieldPrimitive<uint32_t>(fields[0]);
161 napi_value jsArr;
162 auto *env = Ctx()->GetJSEnv();
163 if (jsStringBufferRef_ == nullptr) {
164 jsArr = InitBuffer(GetStringBufferSize());
165 } else {
166 jsArr = GetReferenceValue(env, jsStringBufferRef_);
167 if (startFrom >= stringBuffer_.size()) {
168 stringBuffer_.resize(GetStringBufferSize());
169 } else if (stringBuffer_[startFrom] != nullptr) {
170 return;
171 }
172 }
173
174 auto *pf = klass->GetPandaFile();
175 panda_file::ClassDataAccessor cda(*pf, klass->GetFileId());
176 [[maybe_unused]] auto annotationFound = cda.EnumerateAnnotation(
177 "Lets/annotation/DynamicCall;", [this, pf, startFrom, jsArr, env](panda_file::AnnotationDataAccessor &ada) {
178 for (uint32_t i = 0; i < ada.GetCount(); i++) {
179 auto adae = ada.GetElement(i);
180 auto *elemName = pf->GetStringData(adae.GetNameId()).data;
181 if (!utf::IsEqual(utf::CStringAsMutf8("value"), elemName)) {
182 continue;
183 }
184 auto arr = adae.GetArrayValue();
185 auto count = arr.GetCount();
186 for (uint32_t j = 0, bufferIndex = startFrom; j < count; j++, bufferIndex++) {
187 panda_file::File::EntityId stringId(arr.Get<uint32_t>(j));
188 auto data = pf->GetStringData(stringId);
189 napi_value jsStr;
190 NAPI_CHECK_FATAL(
191 napi_create_string_utf8(env, utf::Mutf8AsCString(data.data), data.utf16Length, &jsStr));
192 ASSERT(stringBuffer_[bufferIndex] == nullptr);
193 napi_set_element(env, jsArr, bufferIndex, jsStr);
194 stringBuffer_[bufferIndex] = jsStr;
195 }
196 return true;
197 }
198 UNREACHABLE();
199 });
200 ASSERT(annotationFound.has_value());
201 }
202
GetConstantPool()203 napi_value ConstStringStorage::GetConstantPool()
204 {
205 return GetReferenceValue(Ctx()->GetJSEnv(), jsStringBufferRef_);
206 }
207
InitBuffer(size_t length)208 napi_value ConstStringStorage::InitBuffer(size_t length)
209 {
210 napi_value jsArr;
211 NAPI_CHECK_FATAL(napi_create_array_with_length(Ctx()->GetJSEnv(), length, &jsArr));
212 NAPI_CHECK_FATAL(napi_create_reference(Ctx()->GetJSEnv(), jsArr, 1, &jsStringBufferRef_));
213 stringBuffer_.resize(GetStringBufferSize());
214 return jsArr;
215 }
216
CommonJSObjectCache(InteropCtx * ctx)217 CommonJSObjectCache::CommonJSObjectCache(InteropCtx *ctx) : ctx_(ctx)
218 {
219 InitializeCache();
220 }
221
~CommonJSObjectCache()222 CommonJSObjectCache::~CommonJSObjectCache()
223 {
224 if (proxyRef_ != nullptr) {
225 napi_env env = ctx_->GetJSEnv();
226 napi_delete_reference(env, proxyRef_);
227 }
228 }
229
GetProxy() const230 napi_value CommonJSObjectCache::GetProxy() const
231 {
232 if (proxyRef_ == nullptr) {
233 const_cast<CommonJSObjectCache *>(this)->InitializeCache();
234 }
235 return GetReferenceValue(ctx_->GetJSEnv(), proxyRef_);
236 }
237
InitializeCache()238 void CommonJSObjectCache::InitializeCache()
239 {
240 napi_env env = ctx_->GetJSEnv();
241
242 napi_value global;
243 NAPI_CHECK_FATAL(napi_get_global(env, &global));
244
245 napi_value proxy;
246 NAPI_CHECK_FATAL(napi_get_named_property(env, global, "Proxy", &proxy));
247 NAPI_CHECK_FATAL(napi_create_reference(env, proxy, 1, &proxyRef_));
248 }
249
Ctx()250 InteropCtx *ConstStringStorage::Ctx()
251 {
252 ASSERT(ctx_ != nullptr);
253 return ctx_;
254 }
255
GetInstance(PandaEtsVM * vm)256 std::shared_ptr<InteropCtx::SharedEtsVmState> InteropCtx::SharedEtsVmState::GetInstance(PandaEtsVM *vm)
257 {
258 os::memory::LockHolder lock(mutex_);
259 if (instance_ == nullptr) {
260 instance_ = MakePandaShared<SharedEtsVmState>(vm);
261 }
262 return instance_;
263 }
264
265 // should be called when we would like to check if there are no more InteropCtx instances left
TryReleaseInstance()266 void InteropCtx::SharedEtsVmState::TryReleaseInstance()
267 {
268 os::memory::LockHolder lock(mutex_);
269 if (instance_.unique()) {
270 // assuming that if the main InteropCtx is destroyed, then the VM shuts down
271 instance_ = nullptr;
272 }
273 }
274
GetJsProxyInstance(EtsClass * cls) const275 js_proxy::JSProxy *InteropCtx::SharedEtsVmState::GetJsProxyInstance(EtsClass *cls) const
276 {
277 os::memory::LockHolder lock(mutex_);
278 auto item = jsProxies_.find(cls);
279 if (item != jsProxies_.end()) {
280 return item->second.get();
281 }
282 return nullptr;
283 }
284
SetJsProxyInstance(EtsClass * cls,js_proxy::JSProxy * proxy)285 void InteropCtx::SharedEtsVmState::SetJsProxyInstance(EtsClass *cls, js_proxy::JSProxy *proxy)
286 {
287 os::memory::LockHolder lock(mutex_);
288 jsProxies_.insert_or_assign(cls, PandaUniquePtr<js_proxy::JSProxy>(proxy));
289 }
290
GetInterfaceProxyInstance(std::string & interfaceName) const291 js_proxy::JSProxy *InteropCtx::SharedEtsVmState::GetInterfaceProxyInstance(std::string &interfaceName) const
292 {
293 os::memory::LockHolder lock(mutex_);
294 auto item = interfaceProxies_.find(interfaceName);
295 if (item != interfaceProxies_.end()) {
296 return item->second.get();
297 }
298 return nullptr;
299 }
300
SetInterfaceProxyInstance(std::string & interfaceName,js_proxy::JSProxy * proxy)301 void InteropCtx::SharedEtsVmState::SetInterfaceProxyInstance(std::string &interfaceName, js_proxy::JSProxy *proxy)
302 {
303 os::memory::LockHolder lock(mutex_);
304 interfaceProxies_.insert_or_assign(interfaceName, PandaUniquePtr<js_proxy::JSProxy>(proxy));
305 }
306
SharedEtsVmState(PandaEtsVM * vm)307 InteropCtx::SharedEtsVmState::SharedEtsVmState(PandaEtsVM *vm)
308 {
309 EtsClassLinker *etsClassLinker = vm->GetClassLinker();
310 pandaEtsVm = vm;
311 if (linkerCtx_ == nullptr) {
312 linkerCtx_ = etsClassLinker->GetEtsClassLinkerExtension()->GetBootContext();
313 }
314 CacheClasses(etsClassLinker);
315 etsProxyRefStorage = ets_proxy::SharedReferenceStorage::Create(pandaEtsVm);
316 ASSERT(etsProxyRefStorage.get() != nullptr);
317
318 EtsClass *promiseInteropClass =
319 EtsClass::FromRuntimeClass(CacheClass(etsClassLinker, "Lstd/interop/js/PromiseInterop;"));
320 ASSERT(promiseInteropClass != nullptr);
321 promiseInteropConnectMethod =
322 promiseInteropClass->GetStaticMethod("connectPromise", "Lstd/core/Promise;J:V")->GetPandaMethod();
323 ASSERT(promiseInteropConnectMethod != nullptr);
324
325 // xgc-related things
326 stsVMInterface = MakePandaUnique<STSVMInterfaceImpl>();
327 [[maybe_unused]] bool xgcCreated =
328 XGC::Create(vm, etsProxyRefStorage.get(), static_cast<STSVMInterfaceImpl *>(stsVMInterface.get()));
329 ASSERT(xgcCreated);
330
331 // the event loop framework is per-EtsVM. Further on, it uses local InteropCtx instances
332 // to access the JSVM-specific data
333 RegisterEventLoopModule(EtsCoroutine::GetCurrent());
334 }
335
~SharedEtsVmState()336 InteropCtx::SharedEtsVmState::~SharedEtsVmState()
337 {
338 // will happen in the runtime destruction flow
339 XGC::Destroy();
340 }
341
CacheClasses(EtsClassLinker * etsClassLinker)342 void InteropCtx::SharedEtsVmState::CacheClasses(EtsClassLinker *etsClassLinker)
343 {
344 jsRuntimeClass = CacheClass(etsClassLinker, descriptors::JS_RUNTIME);
345 jsValueClass = CacheClass(etsClassLinker, descriptors::JS_VALUE);
346 esErrorClass = CacheClass(etsClassLinker, descriptors::ES_ERROR);
347 objectClass = CacheClass(etsClassLinker, descriptors::OBJECT);
348 stringClass = CacheClass(etsClassLinker, descriptors::STRING);
349 bigintClass = CacheClass(etsClassLinker, descriptors::BIG_INT);
350 nullValueClass = CacheClass(etsClassLinker, descriptors::NULL_VALUE);
351 promiseClass = CacheClass(etsClassLinker, descriptors::PROMISE);
352 errorClass = CacheClass(etsClassLinker, descriptors::ERROR);
353 exceptionClass = CacheClass(etsClassLinker, descriptors::EXCEPTION);
354 typeClass = CacheClass(etsClassLinker, descriptors::TYPE);
355
356 boxIntClass = CacheClass(etsClassLinker, descriptors::BOX_INT);
357 boxLongClass = CacheClass(etsClassLinker, descriptors::BOX_LONG);
358
359 arrayClass = CacheClass(etsClassLinker, descriptors::ARRAY);
360
361 arrayAsListIntClass = CacheClass(etsClassLinker, descriptors::ARRAY_AS_LIST_INT);
362
363 for (auto descr : FUNCTION_INTERFACE_DESCRIPTORS) {
364 functionalInterfaces.insert(CacheClass(etsClassLinker, descr));
365 }
366 }
367
368 // NOLINTBEGIN(fuchsia-statically-constructed-objects)
369 std::shared_ptr<InteropCtx::SharedEtsVmState> InteropCtx::SharedEtsVmState::instance_ {nullptr};
370 os::memory::Mutex InteropCtx::SharedEtsVmState::mutex_;
371 ClassLinkerContext *InteropCtx::SharedEtsVmState::linkerCtx_ {nullptr};
372 ark::mem::Reference *InteropCtx::SharedEtsVmState::refToDefaultLinker_ {nullptr};
373 // NOLINTEND(fuchsia-statically-constructed-objects)
374
InitExternalInterfaces()375 void InteropCtx::InitExternalInterfaces()
376 {
377 interfaceTable_.SetJobQueue(MakePandaUnique<JsJobQueue>());
378 // should be called in the deoptimization and exception handlers
379 interfaceTable_.SetClearInteropHandleScopesFunction(
380 [this](Frame *frame) { this->DestroyLocalScopeForTopFrame(frame); });
381 #if defined(PANDA_TARGET_OHOS) || defined(PANDA_JS_ETS_HYBRID_MODE)
382 interfaceTable_.SetCreateJSRuntimeFunction([env = GetJSEnv()]() -> ExternalIfaceTable::JSEnv {
383 napi_env resultJsEnv = nullptr;
384 [[maybe_unused]] auto status = napi_create_runtime(env, &resultJsEnv);
385 ASSERT(status == napi_ok);
386 napi_value global = nullptr;
387 napi_get_global(resultJsEnv, &global);
388 ark::ets::interop::js::helper::Init(resultJsEnv, global);
389 return resultJsEnv;
390 });
391 #endif
392 interfaceTable_.SetCreateInteropCtxFunction([](Coroutine *coro, ExternalIfaceTable::JSEnv jsEnv) {
393 auto env = static_cast<napi_env>(jsEnv);
394 auto *etsCoro = static_cast<EtsCoroutine *>(coro);
395 InteropCtx::Init(etsCoro, env);
396 // It's a hack and we should use INTEROP_CODE_SCOPE to allocate records.
397 // Will be fixed in near future.
398 InteropCtx::Current()->CallStack().AllocRecord(etsCoro->GetCurrentFrame(), nullptr);
399 });
400 }
401
InteropCtx(EtsCoroutine * coro,napi_env env)402 InteropCtx::InteropCtx(EtsCoroutine *coro, napi_env env)
403 : sharedEtsVmState_(SharedEtsVmState::GetInstance(coro->GetPandaVM())),
404 jsEnv_(env),
405 constStringStorage_(this),
406 commonJSObjectCache_(this),
407 stackInfoManager_(this, coro)
408 {
409 stackInfoManager_.InitStackInfoIfNeeded();
410 ecmaVMIterfaceAdaptor_ = MakePandaUnique<XGCVmAdaptor>(env, nullptr);
411 // the per-EtsVM part has to be initialized first
412 RegisterBuiltinJSRefConvertors(this);
413
414 InitExternalInterfaces();
415 InitJsValueFinalizationRegistry(coro);
416 }
417
~InteropCtx()418 InteropCtx::~InteropCtx()
419 {
420 Refstor()->Remove(jsvalueFregistryRef_);
421 }
422
InitJsValueFinalizationRegistry(EtsCoroutine * coro)423 void InteropCtx::InitJsValueFinalizationRegistry(EtsCoroutine *coro)
424 {
425 auto *method = EtsClass::FromRuntimeClass(sharedEtsVmState_->jsRuntimeClass)
426 ->GetStaticMethod("createFinalizationRegistry", ":Lstd/core/FinalizationRegistry;");
427 ASSERT(method != nullptr);
428 Value res;
429 if (coro->IsManagedCode()) {
430 res = method->GetPandaMethod()->Invoke(coro, nullptr);
431 } else {
432 ScopedManagedCodeThread threadScope {coro};
433 res = method->GetPandaMethod()->Invoke(coro, nullptr);
434 }
435 ASSERT(!coro->HasPendingException());
436 auto queue = EtsObject::FromCoreType(res.GetAs<ObjectHeader *>());
437 ASSERT(queue != nullptr);
438 jsvalueFregistryRef_ = Refstor()->Add(queue->GetCoreType(), mem::Reference::ObjectType::GLOBAL);
439 ASSERT(jsvalueFregistryRef_ != nullptr);
440 jsvalueFregistryRegister_ =
441 queue->GetClass()
442 ->GetInstanceMethod("register", "Lstd/core/Object;Lstd/core/Object;Lstd/core/Object;:V")
443 ->GetPandaMethod();
444 ASSERT(jsvalueFregistryRegister_ != nullptr);
445 }
446
CreateETSCoreESError(EtsCoroutine * coro,JSValue * jsvalue)447 EtsObject *InteropCtx::CreateETSCoreESError(EtsCoroutine *coro, JSValue *jsvalue)
448 {
449 [[maybe_unused]] HandleScope<ObjectHeader *> scope(coro);
450 VMHandle<ObjectHeader> jsvalueHandle(coro, jsvalue->GetCoreType());
451
452 Method::Proto proto(Method::Proto::ShortyVector {panda_file::Type(panda_file::Type::TypeId::VOID),
453 panda_file::Type(panda_file::Type::TypeId::REFERENCE)},
454 Method::Proto::RefTypeVector {utf::Mutf8AsCString(GetJSValueClass()->GetDescriptor())});
455 auto ctorName = utf::CStringAsMutf8(panda_file_items::CTOR.data());
456 auto ctor = GetESErrorClass()->GetDirectMethod(ctorName, proto);
457 ASSERT(ctor != nullptr);
458
459 auto excObj = ObjectHeader::Create(coro, GetESErrorClass());
460 if (UNLIKELY(excObj == nullptr)) {
461 return nullptr;
462 }
463 VMHandle<ObjectHeader> excHandle(coro, excObj);
464
465 std::array<Value, 2U> args {Value(excHandle.GetPtr()), Value(jsvalueHandle.GetPtr())};
466 ctor->InvokeVoid(coro, args.data());
467 auto res = EtsObject::FromCoreType(excHandle.GetPtr());
468 if (UNLIKELY(coro->HasPendingException())) {
469 return nullptr;
470 }
471 return res;
472 }
473
ThrowETSError(EtsCoroutine * coro,napi_value val)474 void InteropCtx::ThrowETSError(EtsCoroutine *coro, napi_value val)
475 {
476 auto ctx = Current(coro);
477
478 if (coro->IsUsePreAllocObj()) {
479 coro->SetUsePreAllocObj(false);
480 coro->SetException(coro->GetVM()->GetOOMErrorObject());
481 return;
482 }
483 ASSERT(!coro->HasPendingException());
484
485 if (IsNullOrUndefined(ctx->GetJSEnv(), val)) {
486 ctx->ThrowETSError(coro, "interop/js throws undefined/null");
487 return;
488 }
489
490 // To catch `TypeError` or `UserError extends TypeError`
491 // 1. Frontend puts catch(compat/TypeError) { <instanceof-rethrow? if UserError expected> }
492 // Where js.UserError will be wrapped into compat/TypeError
493 // NOTE(vpukhov): compat: add intrinsic to obtain JSValue from compat/ instances
494
495 auto env = InteropCtx::Current(coro)->GetJSEnv();
496 bool isInstanceof = false;
497 NAPI_CHECK_FATAL(napi_is_error(env, val, &isInstanceof));
498 auto objRefconv = JSRefConvertResolve(ctx, isInstanceof ? ctx->GetErrorClass() : ctx->GetESErrorClass());
499 ASSERT(objRefconv != nullptr);
500 LocalObjectHandle<EtsObject> etsObj(coro, objRefconv->Unwrap(ctx, val));
501 if (UNLIKELY(etsObj.GetPtr() == nullptr)) {
502 INTEROP_LOG(INFO) << "Something went wrong while unwrapping pending js exception";
503 ASSERT(ctx->SanityETSExceptionPending());
504 return;
505 }
506
507 auto klass = etsObj->GetClass()->GetRuntimeClass();
508 if (LIKELY(ctx->GetErrorClass()->IsAssignableFrom(klass) || ctx->GetExceptionClass()->IsAssignableFrom(klass))) {
509 coro->SetException(etsObj->GetCoreType());
510 return;
511 }
512
513 // NOTE(vpukhov): should throw a special error (ESError) with cause set
514 auto exc = JSConvertESError::Unwrap(ctx, ctx->GetJSEnv(), val);
515 if (LIKELY(exc.has_value())) {
516 ASSERT(exc != nullptr);
517 coro->SetException(exc.value()->GetCoreType());
518 } // otherwise exception is already set
519 }
520
ThrowETSError(EtsCoroutine * coro,const char * msg)521 void InteropCtx::ThrowETSError(EtsCoroutine *coro, const char *msg)
522 {
523 ASSERT(!coro->HasPendingException());
524 ets::ThrowEtsException(coro, panda_file_items::class_descriptors::ERROR, msg);
525 }
526
ThrowJSError(napi_env env,const std::string & msg)527 void InteropCtx::ThrowJSError(napi_env env, const std::string &msg)
528 {
529 INTEROP_LOG(INFO) << "ThrowJSError: " << msg;
530 ASSERT(!NapiIsExceptionPending(env));
531 NAPI_CHECK_FATAL(napi_throw_error(env, nullptr, msg.c_str()));
532 }
533
ThrowJSTypeError(napi_env env,const std::string & msg)534 void InteropCtx::ThrowJSTypeError(napi_env env, const std::string &msg)
535 {
536 INTEROP_LOG(INFO) << "ThrowJSTypeError: " << msg;
537 ASSERT(!NapiIsExceptionPending(env));
538 NAPI_CHECK_FATAL(napi_throw_type_error(env, nullptr, msg.c_str()));
539 }
540
ThrowJSValue(napi_env env,napi_value val)541 void InteropCtx::ThrowJSValue(napi_env env, napi_value val)
542 {
543 INTEROP_LOG(INFO) << "ThrowJSValue";
544 ASSERT(!NapiIsExceptionPending(env));
545 #if defined(PANDA_TARGET_OHOS) || defined(PANDA_JS_ETS_HYBRID_MODE)
546 NAPI_CHECK_FATAL(napi_throw_jsvalue(env, val));
547 #else
548 // At present, UT relies on NodeJS, and the napi_throw interface is still used in UT runtime mode
549 // When UT migrates to ArkJS VM, this branch needs to be removed
550 NAPI_CHECK_FATAL(napi_throw(env, val));
551 #endif
552 }
553
CreateJSTypeError(napi_env env,const std::string & code,const std::string & msg)554 napi_value InteropCtx::CreateJSTypeError(napi_env env, const std::string &code, const std::string &msg)
555 {
556 INTEROP_LOG(INFO) << "CreateJSTypeError: code: " << code << ", msg: " << msg;
557 ASSERT(!NapiIsExceptionPending(env));
558 napi_value errorCode;
559 NAPI_CHECK_FATAL(napi_create_string_utf8(env, code.data(), code.size(), &errorCode));
560 napi_value errorMessage;
561 NAPI_CHECK_FATAL(napi_create_string_utf8(env, msg.data(), msg.size(), &errorMessage));
562 napi_value error;
563 NAPI_CHECK_FATAL(napi_create_type_error(env, errorCode, errorMessage, &error));
564 return error;
565 }
566
InitializeDefaultLinkerCtxIfNeeded(EtsRuntimeLinker * linker)567 void InteropCtx::InitializeDefaultLinkerCtxIfNeeded(EtsRuntimeLinker *linker)
568 {
569 os::memory::LockHolder lock(InteropCtx::SharedEtsVmState::mutex_);
570 // Only cache the first application class linker context
571 if (InteropCtx::SharedEtsVmState::linkerCtx_ != nullptr &&
572 !InteropCtx::SharedEtsVmState::linkerCtx_->IsBootContext()) {
573 return;
574 }
575 if (linker->GetClass() != PlatformTypes()->coreAbcRuntimeLinker) {
576 return;
577 }
578 InteropCtx::SharedEtsVmState::linkerCtx_ = linker->GetClassLinkerContext();
579 InteropCtx::SharedEtsVmState::refToDefaultLinker_ = PandaVM::GetCurrent()->GetGlobalObjectStorage()->Add(
580 linker->AsObject()->GetCoreType(), mem::Reference::ObjectType::GLOBAL);
581 }
582
SetDefaultLinkerContext(EtsRuntimeLinker * linker)583 void InteropCtx::SetDefaultLinkerContext(EtsRuntimeLinker *linker)
584 {
585 os::memory::LockHolder lock(SharedEtsVmState::mutex_);
586 if (!linker->IsInstanceOf(PlatformTypes()->coreRuntimeLinker)) {
587 return;
588 }
589 SharedEtsVmState::linkerCtx_ = linker->GetClassLinkerContext();
590 PandaVM::GetCurrent()->GetGlobalObjectStorage()->Remove(SharedEtsVmState::refToDefaultLinker_);
591 SharedEtsVmState::refToDefaultLinker_ = PandaVM::GetCurrent()->GetGlobalObjectStorage()->Add(
592 linker->AsObject()->GetCoreType(), mem::Reference::ObjectType::GLOBAL);
593 }
594
ForwardEtsException(EtsCoroutine * coro)595 void InteropCtx::ForwardEtsException(EtsCoroutine *coro)
596 {
597 auto env = GetJSEnv();
598 ASSERT(coro != nullptr);
599 ASSERT(coro->HasPendingException());
600 LocalObjectHandle<ObjectHeader> exc(coro, coro->GetException());
601 coro->ClearException();
602
603 auto klass = exc->ClassAddr<Class>();
604 ASSERT(GetErrorClass()->IsAssignableFrom(klass) || GetExceptionClass()->IsAssignableFrom(klass));
605 JSRefConvert *refconv = JSRefConvertResolve<true>(this, klass);
606 if (UNLIKELY(refconv == nullptr)) {
607 INTEROP_LOG(INFO) << "Exception thrown while forwarding ets exception: " << klass->GetDescriptor();
608 return;
609 }
610 napi_value res = refconv->Wrap(this, EtsObject::FromCoreType(exc.GetPtr()));
611 if (UNLIKELY(res == nullptr)) {
612 return;
613 }
614 ThrowJSValue(env, res);
615 }
616
ForwardJSException(EtsCoroutine * coro)617 void InteropCtx::ForwardJSException(EtsCoroutine *coro)
618 {
619 auto env = GetJSEnv();
620 const napi_extended_error_info *info = nullptr;
621 NAPI_CHECK_FATAL(napi_get_last_error_info(env, &info));
622 if (info->error_code != napi_ok && info->error_code != napi_pending_exception) {
623 INTEROP_LOG(INFO) << "Napi last error: " << info->error_message;
624 ThrowETSError(coro, info->error_message);
625 return;
626 }
627 napi_value excval;
628 ASSERT(NapiIsExceptionPending(env));
629 NAPI_CHECK_FATAL(napi_get_and_clear_last_exception(env, &excval));
630 ThrowETSError(coro, excval);
631 }
632
JSConvertTypeCheckFailed(const char * typeName)633 void JSConvertTypeCheckFailed(const char *typeName)
634 {
635 auto ctx = InteropCtx::Current();
636 auto env = ctx->GetJSEnv();
637 InteropCtx::ThrowJSTypeError(env, typeName + std::string(" expected"));
638 }
639
GetErrorStack(napi_env env,napi_value jsErr)640 static std::optional<std::string> GetErrorStack(napi_env env, napi_value jsErr)
641 {
642 bool isError;
643 if (napi_ok != napi_is_error(env, jsErr, &isError)) {
644 return {};
645 }
646 if (!isError) {
647 return "not an Error instance";
648 }
649 napi_value jsStk;
650 if (napi_ok != napi_get_named_property(env, jsErr, "stack", &jsStk)) {
651 return {};
652 }
653 size_t length;
654 if (napi_ok != napi_get_value_string_utf8(env, jsStk, nullptr, 0, &length)) {
655 return {};
656 }
657 std::string value;
658 value.resize(length);
659 // +1 for NULL terminated string!!!
660 if (napi_ok != napi_get_value_string_utf8(env, jsStk, value.data(), value.size() + 1, &length)) {
661 return {};
662 }
663 return value;
664 }
665
NapiTryDumpStack(napi_env env)666 static std::optional<std::string> NapiTryDumpStack(napi_env env)
667 {
668 bool isPending;
669 if (napi_ok != napi_is_exception_pending(env, &isPending)) {
670 return {};
671 }
672
673 std::string pendingErrorMsg;
674 if (isPending) {
675 napi_value valuePending;
676 if (napi_ok != napi_get_and_clear_last_exception(env, &valuePending)) {
677 return {};
678 }
679 auto resStk = GetErrorStack(env, valuePending);
680 if (resStk.has_value()) {
681 pendingErrorMsg = "\nWith pending exception:\n" + resStk.value();
682 } else {
683 pendingErrorMsg = "\nFailed to stringify pending exception";
684 }
685 }
686
687 std::string stacktraceMsg;
688 {
689 napi_value jsDummyStr;
690 if (napi_ok !=
691 napi_create_string_utf8(env, "probe-stacktrace-not-actual-error", NAPI_AUTO_LENGTH, &jsDummyStr)) {
692 return {};
693 }
694 napi_value jsErr;
695 auto rc = napi_create_error(env, nullptr, jsDummyStr, &jsErr);
696 if (napi_ok != rc) {
697 return {};
698 }
699 auto resStk = GetErrorStack(env, jsErr);
700 stacktraceMsg = resStk.has_value() ? resStk.value() : "failed to stringify probe exception";
701 }
702
703 return stacktraceMsg + pendingErrorMsg;
704 }
705
Fatal(const char * msg)706 [[noreturn]] void InteropCtx::Fatal(const char *msg)
707 {
708 INTEROP_LOG(ERROR) << "InteropCtx::Fatal: " << msg;
709
710 auto coro = EtsCoroutine::GetCurrent();
711 auto ctx = InteropCtx::Current(coro);
712
713 INTEROP_LOG(ERROR) << "======================== ETS stack ============================";
714 auto istk = ctx->interopStk_.GetRecords();
715 auto istkIt = istk.rbegin();
716
717 auto printIstkFrames = [&istkIt, &istk](void *fp) {
718 while (istkIt != istk.rend() && fp == istkIt->etsFrame) {
719 INTEROP_LOG(ERROR) << "<interop> " << (istkIt->descr != nullptr ? istkIt->descr : "unknown");
720 istkIt++;
721 }
722 };
723
724 for (auto stack = StackWalker::Create(coro); stack.HasFrame(); stack.NextFrame()) {
725 printIstkFrames(istkIt->etsFrame);
726 Method *method = stack.GetMethod();
727 ASSERT(method != nullptr);
728 INTEROP_LOG(ERROR) << method->GetClass()->GetName() << "." << method->GetName().data << " at "
729 << method->GetLineNumberAndSourceFile(stack.GetBytecodePc());
730 }
731 ASSERT(istkIt == istk.rend() || istkIt->etsFrame == nullptr);
732 printIstkFrames(nullptr);
733
734 auto env = ctx->GetJSEnv();
735 INTEROP_LOG(ERROR) << (env != nullptr ? "<ets-entrypoint>" : "current js env is nullptr!");
736
737 if (coro->HasPendingException()) {
738 auto exc = EtsObject::FromCoreType(coro->GetException());
739 INTEROP_LOG(ERROR) << "With pending exception: " << exc->GetClass()->GetDescriptor();
740 }
741
742 if (env != nullptr) {
743 INTEROP_LOG(ERROR) << "======================== JS stack =============================";
744 std::optional<std::string> jsStk = NapiTryDumpStack(env);
745 if (jsStk.has_value()) {
746 INTEROP_LOG(ERROR) << jsStk.value();
747 } else {
748 INTEROP_LOG(ERROR) << "JS stack print failed";
749 }
750 }
751
752 INTEROP_LOG(ERROR) << "======================== Native stack =========================";
753 PrintStack(Logger::Message(Logger::Level::ERROR, Logger::Component::ETS_INTEROP_JS, false).GetStream());
754 std::abort();
755 }
756
Init(EtsCoroutine * coro,napi_env env)757 void InteropCtx::Init(EtsCoroutine *coro, napi_env env)
758 {
759 auto *ctx = Runtime::GetCurrent()->GetInternalAllocator()->New<InteropCtx>(coro, env);
760 ASSERT(ctx != nullptr);
761 auto *worker = coro->GetWorker();
762 worker->GetLocalStorage().Set<CoroutineWorker::DataIdx::INTEROP_CTX_PTR>(ctx, Destroy);
763 worker->GetLocalStorage().Set<CoroutineWorker::DataIdx::EXTERNAL_IFACES>(&ctx->interfaceTable_);
764 #ifdef PANDA_JS_ETS_HYBRID_MODE
765 Handshake::VmHandshake(env, ctx);
766 XGC::GetInstance()->OnAttach(ctx);
767 auto workerPoster = coro->GetPandaVM()->CreateCallbackPoster();
768 ASSERT(workerPoster != nullptr);
769 worker->SetCallbackPoster(std::move(workerPoster));
770 #endif // PANDA_JS_ETS_HYBRID_MODE
771 }
772
Destroy(void * ptr)773 void InteropCtx::Destroy(void *ptr)
774 {
775 auto *instance = static_cast<InteropCtx *>(ptr);
776 #if defined(PANDA_JS_ETS_HYBRID_MODE)
777 XGC::GetInstance()->OnDetach(instance);
778 #endif // PANDA_JS_ETS_HYBRID_MODE
779 Runtime::GetCurrent()->GetInternalAllocator()->Delete(instance);
780 SharedEtsVmState::TryReleaseInstance();
781 }
782
CheckRuntimeOptions(const ark::ets::EtsCoroutine * mainCoro)783 static bool CheckRuntimeOptions([[maybe_unused]] const ark::ets::EtsCoroutine *mainCoro)
784 {
785 #if defined(PANDA_JS_ETS_HYBRID_MODE) && !defined(ARK_HYBRID)
786 ASSERT(mainCoro != nullptr);
787 auto gcType = mainCoro->GetVM()->GetGC()->GetType();
788 if ((Runtime::GetOptions().GetXgcTriggerType() != "never") &&
789 (gcType != mem::GCType::G1_GC || Runtime::GetOptions().IsNoAsyncJit())) {
790 // XGC is not implemented for other GC types
791 LOG(ERROR, RUNTIME) << "XGC requires GC type to be g1-gc and no-async-jit option must be false";
792 return false;
793 }
794 #endif // PANDA_JS_ETS_HYBRID_MODE
795 return true;
796 }
797
798 // NOTE(wupengyong, #24099): load interop module need formal plan.
TryInitInteropInJsEnv(void * napiEnv)799 bool TryInitInteropInJsEnv(void *napiEnv)
800 {
801 auto env = static_cast<napi_env>(napiEnv);
802 // NOLINTBEGIN(modernize-avoid-c-arrays,readability-identifier-naming)
803 constexpr char requireNapi[] = "requireNapi";
804 constexpr char interopSo[] = "ets_interop_js_napi";
805 constexpr char panda[] = "Panda";
806 // NOLINTEND(modernize-avoid-c-arrays,readability-identifier-naming)
807 napi_value modObj;
808 {
809 NapiEscapableScope jsHandleScope(env);
810 napi_value pandaObj;
811 NAPI_CHECK_RETURN(napi_get_named_property(env, GetGlobal(env), panda, &pandaObj));
812 if (!IsUndefined(env, pandaObj)) {
813 return true;
814 }
815 napi_value requireFn;
816 NAPI_CHECK_RETURN(napi_get_named_property(env, GetGlobal(env), requireNapi, &requireFn));
817
818 INTEROP_RETURN_IF(GetValueType(env, requireFn) != napi_function);
819 {
820 napi_value jsName;
821 NAPI_CHECK_RETURN(napi_create_string_utf8(env, interopSo, NAPI_AUTO_LENGTH, &jsName));
822 std::array<napi_value, 1> args = {jsName};
823 napi_value recv;
824 NAPI_CHECK_RETURN(napi_get_undefined(env, &recv));
825 auto status = (napi_call_function(env, recv, requireFn, args.size(), args.data(), &modObj));
826 if (status == napi_pending_exception) {
827 INTEROP_LOG(ERROR) << "Unable to load module due to exception";
828 return false;
829 }
830 INTEROP_RETURN_IF(status != napi_ok);
831 }
832 INTEROP_RETURN_IF(IsNull(env, modObj));
833 napi_value key;
834 NAPI_CHECK_RETURN(napi_create_string_utf8(env, panda, NAPI_AUTO_LENGTH, &key));
835 NAPI_CHECK_RETURN(napi_set_property(env, GetGlobal(env), key, modObj));
836 jsHandleScope.Escape(modObj);
837 }
838 return true;
839 }
840
841 // The external interface for ANI
CreateMainInteropContext(ark::ets::EtsCoroutine * mainCoro,void * napiEnv)842 bool CreateMainInteropContext(ark::ets::EtsCoroutine *mainCoro, void *napiEnv)
843 {
844 ASSERT(mainCoro->GetCoroutineManager()->GetMainThread() == mainCoro);
845 if (!CheckRuntimeOptions(mainCoro)) {
846 return false;
847 }
848 #if defined(PANDA_TARGET_OHOS) || defined(PANDA_JS_ETS_HYBRID_MODE)
849 auto env = static_cast<napi_env>(napiEnv);
850 NAPI_CHECK_RETURN(napi_setup_hybrid_environment(env));
851 #endif
852 AppStateManager::Create();
853 {
854 ScopedManagedCodeThread sm(mainCoro);
855 InteropCtx::Init(mainCoro, static_cast<napi_env>(napiEnv));
856 }
857
858 // NOTE(konstanting): support instantiation in the TimerModule and move this code to the InteropCtx constructor.
859 // The TimerModule should be bound to the exact JsEnv
860 if (!RegisterTimerModule()) {
861 // throw some errors
862 }
863 if (!RegisterAppStateCallback(InteropCtx::Current()->GetJSEnv())) {
864 INTEROP_LOG(ERROR) << "RegisterAppStateCallback failed";
865 return false;
866 }
867
868 // In the hybrid mode with JSVM=leading VM, we are binding the EtsVM lifetime to the JSVM's env lifetime
869 napi_add_env_cleanup_hook(
870 InteropCtx::Current()->GetJSEnv(),
871 [](void *) {
872 AppStateManager::Destroy();
873 ark::Runtime::Destroy();
874 },
875 nullptr);
876 #if defined(PANDA_TARGET_OHOS)
877 return TryInitInteropInJsEnv(napiEnv);
878 #else
879 return true;
880 #endif
881 }
882
883 } // namespace ark::ets::interop::js
884