• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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