• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (c) 2024-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 "macros.h"
17 #include "runtime/mem/local_object_handle.h"
18 #include "plugins/ets/runtime/interop_js/call/call.h"
19 #include "plugins/ets/runtime/interop_js/call/arg_convertors.h"
20 #include "plugins/ets/runtime/interop_js/call/proto_reader.h"
21 #include "plugins/ets/runtime/interop_js/code_scopes.h"
22 #include "plugins/ets/runtime/ets_stubs-inl.h"
23 
24 namespace ark::ets::interop::js {
25 
CreateProxyBridgeArgReader(uint8_t * args,uint8_t * inStackArgs)26 static ALWAYS_INLINE inline arch::ArgReader<RUNTIME_ARCH> CreateProxyBridgeArgReader(uint8_t *args,
27                                                                                      uint8_t *inStackArgs)
28 {
29     Span<uint8_t> inGprArgs(args, arch::ExtArchTraits<RUNTIME_ARCH>::GP_ARG_NUM_BYTES);
30     Span<uint8_t> inFprArgs(inGprArgs.end(), arch::ExtArchTraits<RUNTIME_ARCH>::FP_ARG_NUM_BYTES);
31     return arch::ArgReader<RUNTIME_ARCH>(inGprArgs, inFprArgs, inStackArgs);
32 }
33 
34 class CallJSHandler {
35 public:
CallJSHandler(EtsCoroutine * coro,Method * method,uint8_t * args,uint8_t * inStackArgs)36     ALWAYS_INLINE CallJSHandler(EtsCoroutine *coro, Method *method, uint8_t *args, uint8_t *inStackArgs)
37         : coro_(coro),
38           ctx_(InteropCtx::Current(coro_)),
39           protoReader_(method, ctx_->GetClassLinker(), ctx_->LinkerCtx()),
40           argReader_(CreateProxyBridgeArgReader(args, inStackArgs))
41     {
42     }
43 
SetupArgreader(bool isInstance)44     ALWAYS_INLINE ObjectHeader *SetupArgreader(bool isInstance)
45     {
46         auto method = protoReader_.GetMethod();
47         protoReader_.Advance();                // skip return type
48         argReader_.template Read<Method *>();  // skip method
49         ASSERT(isInstance == !method->IsStatic());
50         numArgs_ = method->GetNumArgs() - static_cast<uint32_t>(isInstance);
51         return isInstance ? argReader_.Read<ObjectHeader *>() : nullptr;
52     }
53 
54     template <panda_file::Type::TypeId PF_TYPEID, typename T>
ReadFixedArg()55     ALWAYS_INLINE T ReadFixedArg()
56     {
57         ASSERT(protoReader_.GetType() == panda_file::Type(PF_TYPEID));
58         protoReader_.Advance();
59         numArgs_--;
60         return argReader_.Read<T>();
61     }
62 
63     template <typename T>
ReadFixedRefArg(Class * expected)64     ALWAYS_INLINE T *ReadFixedRefArg([[maybe_unused]] Class *expected)
65     {
66         ASSERT(expected == nullptr || protoReader_.GetClass() == expected);
67         return ReadFixedArg<panda_file::Type::TypeId::REFERENCE, T *>();
68     }
69 
SetupJSCallee(napi_value jsThis,napi_value jsFn)70     ALWAYS_INLINE void SetupJSCallee(napi_value jsThis, napi_value jsFn)
71     {
72         jsThis_ = jsThis;
73         jsFn_ = jsFn;
74     }
75 
76     template <bool IS_NEWCALL, typename ArgSetup>
HandleImpl(Method * method,uint8_t * args,uint8_t * inStackArgs)77     static ALWAYS_INLINE uint64_t HandleImpl(Method *method, uint8_t *args, uint8_t *inStackArgs)
78     {
79         CallJSHandler st(EtsCoroutine::GetCurrent(), method, args, inStackArgs);
80         return st.Handle<IS_NEWCALL, ArgSetup>();
81     }
82 
GetMethod()83     ALWAYS_INLINE Method *GetMethod()
84     {
85         return protoReader_.GetMethod();
86     }
87 
88     ~CallJSHandler() = default;
89 
90 private:
ForwardException(InteropCtx * ctx,EtsCoroutine * coro)91     static uint64_t __attribute__((noinline)) ForwardException(InteropCtx *ctx, EtsCoroutine *coro)
92     {
93         if (NapiIsExceptionPending(ctx->GetJSEnv())) {
94             ctx->ForwardJSException(coro);
95         }
96         ASSERT(ctx->SanityETSExceptionPending());
97         return 0;
98     }
99 
100     template <bool IS_NEWCALL, typename ArgSetup>
101     ALWAYS_INLINE uint64_t Handle();
102 
103     template <bool IS_NEWCALL>
104     ALWAYS_INLINE std::optional<napi_value> ConvertArgsAndCall();
105 
106     template <bool IS_NEWCALL, typename FRead>
107     ALWAYS_INLINE inline std::optional<napi_value> ConvertVarargsAndCall(FRead &readVal, Span<napi_value> jsargs);
108 
109     template <bool IS_NEWCALL>
110     ALWAYS_INLINE std::optional<napi_value> CallConverted(Span<napi_value> jsargs);
111 
112     template <bool IS_NEWCALL>
113     ALWAYS_INLINE std::optional<Value> ConvertRetval(napi_value jsRet);
114 
115     napi_value HandleSpecialMethod(Span<napi_value> jsargs);
116 
117     NO_COPY_SEMANTIC(CallJSHandler);
118     NO_MOVE_SEMANTIC(CallJSHandler);
119 
120     EtsCoroutine *const coro_;
121     InteropCtx *const ctx_;
122 
123     ProtoReader protoReader_;
124     arch::ArgReader<RUNTIME_ARCH> argReader_;
125     uint32_t numArgs_ {};
126     napi_value jsThis_ {};
127     napi_value jsFn_ {};
128 };
129 
130 template <bool IS_NEWCALL, typename ArgSetup>
Handle()131 ALWAYS_INLINE inline uint64_t CallJSHandler::Handle()
132 {
133     [[maybe_unused]] InteropCodeScopeETS codeScope(coro_, __PRETTY_FUNCTION__);
134     napi_env env = ctx_->GetJSEnv();
135     NapiScope jsHandleScope(env);
136 
137     if (UNLIKELY(!ArgSetup()(ctx_, this))) {
138         return ForwardException(ctx_, coro_);
139     }
140 
141     std::optional<napi_value> jsRes = ConvertArgsAndCall<IS_NEWCALL>();
142     if (UNLIKELY(!jsRes.has_value())) {
143         return ForwardException(ctx_, coro_);
144     }
145     std::optional<Value> etsRes = ConvertRetval<IS_NEWCALL>(jsRes.value());
146     if (UNLIKELY(!etsRes.has_value())) {
147         return ForwardException(ctx_, coro_);
148     }
149     return static_cast<uint64_t>(etsRes.value().GetAsLong());
150 }
151 
152 template <bool IS_NEWCALL, typename FRead>
ConvertVarargsAndCall(FRead & readVal,Span<napi_value> jsargs)153 ALWAYS_INLINE inline std::optional<napi_value> CallJSHandler::ConvertVarargsAndCall(FRead &readVal,
154                                                                                     Span<napi_value> jsargs)
155 {
156     auto *ref = readVal(helpers::TypeIdentity<ObjectHeader *>());
157     auto *klass = ref->template ClassAddr<Class>();
158     if (!klass->IsArrayClass()) {
159         ASSERT(klass == ctx_->GetArrayClass());
160         VMHandle<EtsArrayObject<EtsObject>> etsArr(coro_, ref);
161         auto allJsArgs = ctx_->GetTempArgs<napi_value>(etsArr->GetActualLength() + jsargs.size());
162         for (uint32_t el = 0; el < jsargs.size(); ++el) {
163             allJsArgs[el] = jsargs[el];
164         }
165         for (uint32_t el = 0; el < etsArr->GetActualLength(); ++el) {
166             EtsObject *etsElem = nullptr;
167             etsArr->GetRef(el, &etsElem);
168             auto refConv = JSRefConvertResolve<true>(ctx_, etsElem->GetClass()->GetRuntimeClass());
169             ASSERT(refConv != nullptr);
170             allJsArgs[el + jsargs.size()] = refConv->Wrap(ctx_, etsElem);
171         }
172         return CallConverted<IS_NEWCALL>(*allJsArgs);
173     }
174 
175     LocalObjectHandle<coretypes::Array> etsArr(coro_, ref);
176 
177     auto allJsArgs = ctx_->GetTempArgs<napi_value>(etsArr->GetLength() + jsargs.size());
178     for (uint32_t el = 0; el < jsargs.size(); ++el) {
179         allJsArgs[el] = jsargs[el];
180     }
181     for (uint32_t el = 0; el < etsArr->GetLength(); ++el) {
182         auto *etsElem = EtsObject::FromCoreType(etsArr->Get<ObjectHeader *>(el));
183         auto refConv = JSRefConvertResolve<true>(ctx_, etsElem->GetClass()->GetRuntimeClass());
184         ASSERT(refConv != nullptr);
185         allJsArgs[el + jsargs.size()] = refConv->Wrap(ctx_, etsElem);
186     }
187     return CallConverted<IS_NEWCALL>(*allJsArgs);
188 }
189 
190 template <bool IS_NEWCALL>
ConvertArgsAndCall()191 ALWAYS_INLINE inline std::optional<napi_value> CallJSHandler::ConvertArgsAndCall()
192 {
193     bool isVarArgs = UNLIKELY(protoReader_.GetMethod()->HasVarArgs());
194     auto readVal = [this](auto typeTag) { return argReader_.template Read<typename decltype(typeTag)::type>(); };
195     auto const numNonRest = numArgs_ - (isVarArgs ? 1 : 0);
196     auto jsargs = ctx_->GetTempArgs<napi_value>(numNonRest);
197 
198     // scope is required to ConvertRefArgToJS
199     HandleScope<ObjectHeader *> scope(coro_);
200     for (uint32_t argIdx = 0; argIdx < numNonRest; ++argIdx, protoReader_.Advance()) {
201         if (UNLIKELY(!ConvertArgToJS(ctx_, protoReader_, &jsargs[argIdx], readVal))) {
202             return std::nullopt;
203         }
204     }
205 
206     napi_value handlerResult = HandleSpecialMethod(*jsargs);
207     if (handlerResult) {
208         return handlerResult;
209     }
210 
211     napi_env env = ctx_->GetJSEnv();
212     if (UNLIKELY(GetValueType(env, jsFn_) != napi_function)) {
213         ctx_->ThrowJSTypeError(env, "call target is not a function");
214         return std::nullopt;
215     }
216 
217     if (isVarArgs) {
218         return ConvertVarargsAndCall<IS_NEWCALL>(readVal, *jsargs);
219     }
220 
221     return CallConverted<IS_NEWCALL>(*jsargs);
222 }
223 
HandleSpecialMethod(Span<napi_value> jsargs)224 napi_value CallJSHandler::HandleSpecialMethod(Span<napi_value> jsargs)
225 {
226     napi_value handlerResult {};
227     napi_env env = ctx_->GetJSEnv();
228     ScopedNativeCodeThread nativeScope(coro_);
229     const char *methodName = EtsMethod::FromRuntimeMethod(protoReader_.GetMethod())->GetName();
230     if (methodName != nullptr && std::strlen(methodName) >= SETTER_GETTER_PREFIX_LENGTH) {
231         std::string content = std::string(methodName).substr(SETTER_GETTER_PREFIX_LENGTH);
232         if (std::strncmp(methodName, GETTER_BEGIN, SETTER_GETTER_PREFIX_LENGTH) == 0) {
233             NAPI_CHECK_FATAL(napi_get_named_property(env, jsThis_, content.c_str(), &handlerResult));
234         } else if (std::strncmp(methodName, SETTER_BEGIN, SETTER_GETTER_PREFIX_LENGTH) == 0) {
235             NAPI_CHECK_FATAL(napi_create_string_utf8(env, content.c_str(), NAPI_AUTO_LENGTH, &handlerResult));
236             NAPI_CHECK_FATAL(napi_set_property(env, jsThis_, handlerResult, jsargs[0]));
237             napi_get_undefined(env, &handlerResult);
238         } else if (std::strncmp(methodName, GET_INDEX_METHOD, SETTER_GETTER_PREFIX_LENGTH) == 0) {
239             int32_t idx;
240             NAPI_CHECK_FATAL(napi_get_value_int32(env, jsargs[0], &idx));
241             NAPI_CHECK_FATAL(napi_get_element(env, jsThis_, idx, &handlerResult));
242         } else if (std::strncmp(methodName, SET_INDEX_METHOD, SETTER_GETTER_PREFIX_LENGTH) == 0) {
243             int32_t idx;
244             NAPI_CHECK_FATAL(napi_get_value_int32(env, jsargs[0], &idx));
245             NAPI_CHECK_FATAL(napi_set_element(env, jsThis_, idx, jsargs[1]));
246             napi_get_undefined(env, &handlerResult);
247         } else if (std::strncmp(methodName, ITERATOR_METHOD, SETTER_GETTER_PREFIX_LENGTH) == 0) {
248             napi_value global;
249             NAPI_CHECK_FATAL(napi_get_global(env, &global));
250             napi_value symbol;
251             NAPI_CHECK_FATAL(napi_get_named_property(env, global, "Symbol", &symbol));
252             napi_value symbolIterator;
253             NAPI_CHECK_FATAL(napi_get_named_property(env, symbol, "iterator", &symbolIterator));
254             napi_value iteratorMethod;
255             NAPI_CHECK_FATAL(napi_get_property(env, jsThis_, symbolIterator, &iteratorMethod));
256             if (GetValueType(env, iteratorMethod) == napi_undefined) {
257                 NAPI_CHECK_FATAL(napi_get_undefined(env, &handlerResult));
258                 return handlerResult;
259             }
260             size_t jsArgc = 0;
261             NAPI_CHECK_FATAL(napi_call_function(env, jsThis_, iteratorMethod, jsArgc, nullptr, &handlerResult));
262         }
263     }
264     return handlerResult;
265 }
266 
267 template <bool IS_NEWCALL>
CallConverted(Span<napi_value> jsargs)268 ALWAYS_INLINE inline std::optional<napi_value> CallJSHandler::CallConverted(Span<napi_value> jsargs)
269 {
270     napi_env env = ctx_->GetJSEnv();
271     napi_value jsRetval;
272     napi_status jsStatus;
273     {
274         ScopedNativeCodeThread nativeScope(coro_);
275         if constexpr (IS_NEWCALL) {
276             jsStatus = napi_new_instance(env, jsFn_, jsargs.size(), jsargs.data(), &jsRetval);
277         } else {
278             jsStatus = napi_call_function(env, jsThis_, jsFn_, jsargs.size(), jsargs.data(), &jsRetval);
279         }
280     }
281     if (UNLIKELY(jsStatus != napi_ok)) {
282         INTEROP_FATAL_IF(jsStatus != napi_pending_exception);
283         return std::nullopt;
284     }
285     return jsRetval;
286 }
287 
288 template <bool IS_NEWCALL>
ConvertRetval(napi_value jsRet)289 ALWAYS_INLINE inline std::optional<Value> CallJSHandler::ConvertRetval(napi_value jsRet)
290 {
291     [[maybe_unused]] napi_env env = ctx_->GetJSEnv();
292     Value etsRet;
293     protoReader_.Reset();
294 
295     if constexpr (IS_NEWCALL) {
296         ASSERT(protoReader_.GetClass() == ctx_->GetJSValueClass());
297         auto res = JSConvertJSValue::Unwrap(ctx_, env, jsRet);
298         if (UNLIKELY(!res.has_value())) {
299             return std::nullopt;
300         }
301         etsRet = Value(res.value()->GetCoreType());
302     } else {
303         auto store = [&etsRet](auto val) { etsRet = Value(val); };
304         if (UNLIKELY(!ConvertArgToEts(ctx_, protoReader_, store, jsRet))) {
305             return std::nullopt;
306         }
307     }
308     return etsRet;
309 }
310 
ResolveQualifiedReceiverTarget(napi_env env,napi_value jsVal,coretypes::String * qnameStr)311 static std::optional<std::pair<napi_value, napi_value>> ResolveQualifiedReceiverTarget(napi_env env, napi_value jsVal,
312                                                                                        coretypes::String *qnameStr)
313 {
314     napi_value jsThis {};
315     ASSERT(qnameStr->IsMUtf8());
316     auto qname = std::string(utf::Mutf8AsCString(qnameStr->GetDataMUtf8()), qnameStr->GetMUtf8Length());
317 
318     auto resolveName = [&jsThis, &jsVal, &env](const std::string &name) -> bool {
319         jsThis = jsVal;
320 
321         if (!NapiGetNamedProperty(env, jsVal, name.c_str(), &jsVal)) {
322             ASSERT(NapiIsExceptionPending(env));
323             return false;
324         }
325         return true;
326     };
327     jsThis = jsVal;
328     if (UNLIKELY(!WalkQualifiedName(qname, resolveName))) {
329         return std::nullopt;
330     }
331     return std::make_pair(jsThis, jsVal);
332 }
333 
334 template <bool IS_NEWCALL>
JSRuntimeCallJSQNameBase(Method * method,uint8_t * args,uint8_t * inStackArgs)335 static ALWAYS_INLINE inline uint64_t JSRuntimeCallJSQNameBase(Method *method, uint8_t *args, uint8_t *inStackArgs)
336 {
337     struct ArgSetup {
338         ALWAYS_INLINE bool operator()(InteropCtx *ctx, CallJSHandler *st)
339         {
340             st->SetupArgreader(false);
341             napi_env env = ctx->GetJSEnv();
342 
343             napi_value jsVal = JSConvertJSValue::Wrap(env, st->ReadFixedRefArg<JSValue>(ctx->GetJSValueClass()));
344             auto qnameStr = st->ReadFixedRefArg<coretypes::String>(ctx->GetStringClass());
345 
346             auto res = ResolveQualifiedReceiverTarget(env, jsVal, qnameStr);
347             if (UNLIKELY(!res.has_value())) {
348                 ASSERT(NapiIsExceptionPending(env));
349                 return false;
350             }
351 
352             st->SetupJSCallee(res->first, res->second);
353             return true;
354         }
355     };
356     return CallJSHandler::HandleImpl<IS_NEWCALL, ArgSetup>(method, args, inStackArgs);
357 }
358 
JSRuntimeCallJSQName(Method * method,uint8_t * args,uint8_t * inStackArgs)359 extern "C" uint64_t JSRuntimeCallJSQName(Method *method, uint8_t *args, uint8_t *inStackArgs)
360 {
361     return JSRuntimeCallJSQNameBase<false>(method, args, inStackArgs);  // IS_NEWCALL is false
362 }
363 extern "C" void JSRuntimeCallJSQNameBridge(Method *method, ...);
364 
JSRuntimeNewCallJSQName(Method * method,uint8_t * args,uint8_t * inStackArgs)365 extern "C" uint64_t JSRuntimeNewCallJSQName(Method *method, uint8_t *args, uint8_t *inStackArgs)
366 {
367     return JSRuntimeCallJSQNameBase<true>(method, args, inStackArgs);  // IS_NEWCALL is true
368 }
369 extern "C" void JSRuntimeNewCallJSQNameBridge(Method *method, ...);
370 
GetClassQnameOffset(InteropCtx * ctx,Method * method)371 static uint32_t GetClassQnameOffset(InteropCtx *ctx, Method *method)
372 {
373     auto klass = method->GetClass();
374     ctx->GetConstStringStorage()->LoadDynamicCallClass(klass);
375     auto fields = klass->GetStaticFields();
376     ASSERT(fields.size() == 1);
377     return klass->GetFieldPrimitive<uint32_t>(fields[0]);
378 }
379 
380 template <bool IS_NEWCALL>
JSRuntimeCallJSBase(Method * method,uint8_t * args,uint8_t * inStackArgs)381 static ALWAYS_INLINE inline uint64_t JSRuntimeCallJSBase(Method *method, uint8_t *args, uint8_t *inStackArgs)
382 {
383     struct ArgSetup {
384         ALWAYS_INLINE bool operator()(InteropCtx *ctx, CallJSHandler *st)
385         {
386             st->SetupArgreader(false);
387             napi_env env = ctx->GetJSEnv();
388 
389             napi_value jsVal = JSConvertJSValue::Wrap(env, st->ReadFixedRefArg<JSValue>(ctx->GetJSValueClass()));
390 
391             auto classQnameOffset = GetClassQnameOffset(ctx, st->GetMethod());
392             auto qnameStart = st->ReadFixedArg<panda_file::Type::TypeId::I32, int32_t>() + classQnameOffset;
393             auto qnameLen = st->ReadFixedArg<panda_file::Type::TypeId::I32, int32_t>();
394             napi_value jsThis {};
395 
396             auto success = ctx->GetConstStringStorage()->EnumerateStrings(
397                 qnameStart, qnameLen, [&jsThis, &jsVal, env](napi_value jsStr) {
398                     jsThis = jsVal;
399 
400                     if (!NapiGetProperty(env, jsVal, jsStr, &jsVal)) {
401                         ASSERT(NapiIsExceptionPending(env));
402                         return false;
403                     }
404                     return true;
405                 });
406 
407             if (!success) {
408                 ASSERT(NapiIsExceptionPending(env));
409                 return false;
410             }
411             st->SetupJSCallee(jsThis, jsVal);
412             return true;
413         }
414     };
415     return CallJSHandler::HandleImpl<IS_NEWCALL, ArgSetup>(method, args, inStackArgs);
416 }
417 
JSRuntimeCallJS(Method * method,uint8_t * args,uint8_t * inStackArgs)418 extern "C" uint64_t JSRuntimeCallJS(Method *method, uint8_t *args, uint8_t *inStackArgs)
419 {
420     return JSRuntimeCallJSBase<false>(method, args, inStackArgs);  // IS_NEWCALL is false
421 }
422 extern "C" void JSRuntimeCallJSBridge(Method *method, ...);
423 
JSRuntimeNewCallJS(Method * method,uint8_t * args,uint8_t * inStackArgs)424 extern "C" uint64_t JSRuntimeNewCallJS(Method *method, uint8_t *args, uint8_t *inStackArgs)
425 {
426     return JSRuntimeCallJSBase<true>(method, args, inStackArgs);  // IS_NEWCALL is true
427 }
428 extern "C" void JSRuntimeNewCallJSBridge(Method *method, ...);
429 
JSRuntimeCallJSByValue(Method * method,uint8_t * args,uint8_t * inStackArgs)430 extern "C" uint64_t JSRuntimeCallJSByValue(Method *method, uint8_t *args, uint8_t *inStackArgs)
431 {
432     struct ArgSetup {
433         ALWAYS_INLINE bool operator()(InteropCtx *ctx, CallJSHandler *st)
434         {
435             st->SetupArgreader(false);
436             napi_env env = ctx->GetJSEnv();
437 
438             napi_value jsFn = JSConvertJSValue::Wrap(env, st->ReadFixedRefArg<JSValue>(ctx->GetJSValueClass()));
439             napi_value jsThis = JSConvertJSValue::Wrap(env, st->ReadFixedRefArg<JSValue>(ctx->GetJSValueClass()));
440 
441             st->SetupJSCallee(jsThis, jsFn);
442             return true;
443         }
444     };
445     return CallJSHandler::HandleImpl<false, ArgSetup>(method, args, inStackArgs);
446 }
447 extern "C" void JSRuntimeCallJSByValueBridge(Method *method, ...);
448 
CallJSProxy(Method * method,uint8_t * args,uint8_t * inStackArgs)449 extern "C" uint64_t CallJSProxy(Method *method, uint8_t *args, uint8_t *inStackArgs)
450 {
451     struct ArgSetup {
452         ALWAYS_INLINE bool operator()(InteropCtx *ctx, CallJSHandler *st)
453         {
454             ObjectHeader *etsThis = st->SetupArgreader(true);
455             napi_env env = ctx->GetJSEnv();
456 
457             auto refconv = JSRefConvertResolve(ctx, etsThis->ClassAddr<Class>());
458             napi_value jsThis = refconv->Wrap(ctx, EtsObject::FromCoreType(etsThis));
459             ASSERT(GetValueType(env, jsThis) == napi_object);
460             const char *methodName = utf::Mutf8AsCString(st->GetMethod()->GetName().data);
461             napi_value jsFn;
462             if (!NapiGetNamedProperty(env, jsThis, methodName, &jsFn)) {
463                 ASSERT(NapiIsExceptionPending(env));
464                 return false;
465             }
466 
467             st->SetupJSCallee(jsThis, jsFn);
468             return true;
469         }
470     };
471     return CallJSHandler::HandleImpl<false, ArgSetup>(method, args, inStackArgs);
472 }
473 
CallJSFunction(Method * method,uint8_t * args,uint8_t * inStackArgs)474 extern "C" uint64_t CallJSFunction(Method *method, uint8_t *args, uint8_t *inStackArgs)
475 {
476     struct ArgSetup {
477         ALWAYS_INLINE bool operator()([[maybe_unused]] InteropCtx *ctx, [[maybe_unused]] CallJSHandler *st)
478         {
479             ObjectHeader *etsThis = st->SetupArgreader(true);
480 
481             auto refconv = JSRefConvertResolve(ctx, etsThis->ClassAddr<Class>());
482             napi_value jsCallBackFn = refconv->Wrap(ctx, EtsObject::FromCoreType(etsThis));
483 
484             st->SetupJSCallee(jsCallBackFn, jsCallBackFn);
485             return true;
486         }
487     };
488     return CallJSHandler::HandleImpl<false, ArgSetup>(method, args, inStackArgs);
489 }
490 
491 extern "C" void CallJSProxyBridge(Method *method, ...);
492 
SelectCallJSEntrypoint(InteropCtx * ctx,Method * method)493 static void *SelectCallJSEntrypoint(InteropCtx *ctx, Method *method)
494 {
495     ASSERT(method->IsStatic());
496     ProtoReader protoReader(method, ctx->GetClassLinker(), ctx->LinkerCtx());
497 
498     // skip return type
499     protoReader.Advance();
500 
501     ASSERT(protoReader.GetClass() == ctx->GetJSValueClass());
502     protoReader.Advance();
503 
504     if (protoReader.GetType().IsPrimitive()) {
505         if (protoReader.GetType().GetId() != panda_file::Type::TypeId::I32) {
506             return nullptr;
507         }
508         protoReader.Advance();
509 
510         if (protoReader.GetType().GetId() != panda_file::Type::TypeId::I32) {
511             return nullptr;
512         }
513         return reinterpret_cast<void *>(JSRuntimeCallJSBridge);
514     }
515     if (protoReader.GetClass() == ctx->GetStringClass()) {
516         return reinterpret_cast<void *>(JSRuntimeCallJSQNameBridge);
517     }
518     if (protoReader.GetClass() == ctx->GetJSValueClass()) {
519         return reinterpret_cast<void *>(JSRuntimeCallJSByValueBridge);
520     }
521     InteropFatal("Bad jscall signature");
522 }
523 
SelectNewCallJSEntrypoint(InteropCtx * ctx,Method * method)524 static void *SelectNewCallJSEntrypoint(InteropCtx *ctx, Method *method)
525 {
526     ASSERT(method->IsStatic());
527     ProtoReader protoReader(method, ctx->GetClassLinker(), ctx->LinkerCtx());
528 
529     if (protoReader.GetClass() != ctx->GetJSValueClass()) {
530         return nullptr;
531     }
532     protoReader.Advance();
533 
534     if (protoReader.GetClass() != ctx->GetJSValueClass()) {
535         return nullptr;
536     }
537     protoReader.Advance();
538 
539     if (protoReader.GetType().IsPrimitive()) {
540         if (protoReader.GetType().GetId() != panda_file::Type::TypeId::I32) {
541             return nullptr;
542         }
543         protoReader.Advance();
544 
545         if (protoReader.GetType().GetId() != panda_file::Type::TypeId::I32) {
546             return nullptr;
547         }
548         return reinterpret_cast<void *>(JSRuntimeNewCallJSBridge);
549     }
550     if (protoReader.GetClass() != ctx->GetStringClass()) {
551         return nullptr;
552     }
553     return reinterpret_cast<void *>(JSRuntimeNewCallJSQNameBridge);
554 }
555 
GetQnameCount(Class * klass)556 static std::optional<uint32_t> GetQnameCount(Class *klass)
557 {
558     auto pf = klass->GetPandaFile();
559     panda_file::ClassDataAccessor cda(*pf, klass->GetFileId());
560     auto qnameCount =
561         cda.EnumerateAnnotation("Lets/annotation/DynamicCall;", [pf](panda_file::AnnotationDataAccessor &ada) {
562             for (uint32_t i = 0; i < ada.GetCount(); i++) {
563                 auto adae = ada.GetElement(i);
564                 auto *elemName = pf->GetStringData(adae.GetNameId()).data;
565                 if (utf::IsEqual(utf::CStringAsMutf8("value"), elemName)) {
566                     return adae.GetArrayValue().GetCount();
567                 }
568             }
569             UNREACHABLE();
570         });
571     return qnameCount;
572 }
573 
InitCallJSClass(bool isNewCall)574 static uint8_t InitCallJSClass(bool isNewCall)
575 {
576     auto coro = EtsCoroutine::GetCurrent();
577     auto ctx = InteropCtx::Current(coro);
578     auto *klass = GetMethodOwnerClassInFrames(coro, 0)->GetRuntimeClass();
579     INTEROP_LOG(DEBUG) << "Bind bridge call methods for " << utf::Mutf8AsCString(klass->GetDescriptor());
580 
581     for (auto &method : klass->GetMethods()) {
582         if (method.IsConstructor()) {
583             continue;
584         }
585         void *ep = nullptr;
586         if (method.IsStatic()) {
587             ep = isNewCall ? SelectNewCallJSEntrypoint(ctx, &method) : SelectCallJSEntrypoint(ctx, &method);
588         }
589         if (ep == nullptr) {
590             InteropFatal("Bad interop call bridge signature");
591         }
592         method.SetCompiledEntryPoint(ep);
593         method.SetNativePointer(nullptr);
594     }
595 
596     auto qnameCount = GetQnameCount(klass);
597     // JSCallClass which was manually created in test will not have the required annotation and field
598     if (qnameCount.has_value()) {
599         auto fields = klass->GetStaticFields();
600         INTEROP_FATAL_IF(fields.Size() != 1);
601         INTEROP_FATAL_IF(klass->GetFieldPrimitive<uint32_t>(fields[0]) != 0);
602         auto *stringStorage = ctx->GetConstStringStorage();
603         klass->SetFieldPrimitive<uint32_t>(fields[0], stringStorage->AllocateSlotsInStringBuffer(*qnameCount));
604     }
605     return 1;
606 }
607 
JSRuntimeInitJSCallClass()608 uint8_t JSRuntimeInitJSCallClass()
609 {
610     return InitCallJSClass(false);
611 }
612 
JSRuntimeInitJSNewClass()613 uint8_t JSRuntimeInitJSNewClass()
614 {
615     return InitCallJSClass(true);
616 }
617 
618 }  // namespace ark::ets::interop::js
619