• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (c) 2024 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/call/call.h"
17 #include "plugins/ets/runtime/interop_js/call/arg_convertors.h"
18 #include "plugins/ets/runtime/interop_js/call/proto_reader.h"
19 #include "plugins/ets/runtime/interop_js/code_scopes.h"
20 
21 namespace ark::ets::interop::js {
22 
CreateProxyBridgeArgReader(uint8_t * args,uint8_t * inStackArgs)23 static ALWAYS_INLINE inline arch::ArgReader<RUNTIME_ARCH> CreateProxyBridgeArgReader(uint8_t *args,
24                                                                                      uint8_t *inStackArgs)
25 {
26     Span<uint8_t> inGprArgs(args, arch::ExtArchTraits<RUNTIME_ARCH>::GP_ARG_NUM_BYTES);
27     Span<uint8_t> inFprArgs(inGprArgs.end(), arch::ExtArchTraits<RUNTIME_ARCH>::FP_ARG_NUM_BYTES);
28     return arch::ArgReader<RUNTIME_ARCH>(inGprArgs, inFprArgs, inStackArgs);
29 }
30 
31 class CallJSHandler {
32 public:
CallJSHandler(EtsCoroutine * coro,Method * method,uint8_t * args,uint8_t * inStackArgs)33     ALWAYS_INLINE CallJSHandler(EtsCoroutine *coro, Method *method, uint8_t *args, uint8_t *inStackArgs)
34         : coro_(coro),
35           ctx_(InteropCtx::Current(coro_)),
36           protoReader_(method, ctx_->GetClassLinker(), ctx_->LinkerCtx()),
37           argReader_(CreateProxyBridgeArgReader(args, inStackArgs))
38     {
39     }
40 
SetupArgreader(bool isInstance)41     ALWAYS_INLINE ObjectHeader *SetupArgreader(bool isInstance)
42     {
43         auto method = protoReader_.GetMethod();
44         protoReader_.Advance();                // skip return type
45         argReader_.template Read<Method *>();  // skip method
46         ASSERT(isInstance == !method->IsStatic());
47         numArgs_ = method->GetNumArgs() - static_cast<uint32_t>(isInstance);
48         return isInstance ? argReader_.Read<ObjectHeader *>() : nullptr;
49     }
50 
51     template <panda_file::Type::TypeId PF_TYPEID, typename T>
ReadFixedArg()52     ALWAYS_INLINE T ReadFixedArg()
53     {
54         ASSERT(protoReader_.GetType() == panda_file::Type(PF_TYPEID));
55         protoReader_.Advance();
56         numArgs_--;
57         return argReader_.Read<T>();
58     }
59 
60     template <typename T>
ReadFixedRefArg(Class * expected)61     ALWAYS_INLINE T *ReadFixedRefArg([[maybe_unused]] Class *expected)
62     {
63         ASSERT(expected == nullptr || protoReader_.GetClass() == expected);
64         return ReadFixedArg<panda_file::Type::TypeId::REFERENCE, T *>();
65     }
66 
SetupJSCallee(napi_value jsThis,napi_value jsFn)67     ALWAYS_INLINE void SetupJSCallee(napi_value jsThis, napi_value jsFn)
68     {
69         jsThis_ = jsThis;
70         jsFn_ = jsFn;
71     }
72 
73     template <bool IS_NEWCALL, typename ArgSetup>
HandleImpl(Method * method,uint8_t * args,uint8_t * inStackArgs)74     static ALWAYS_INLINE uint64_t HandleImpl(Method *method, uint8_t *args, uint8_t *inStackArgs)
75     {
76         CallJSHandler st(EtsCoroutine::GetCurrent(), method, args, inStackArgs);
77         return st.Handle<IS_NEWCALL, ArgSetup>();
78     }
79 
GetMethod()80     ALWAYS_INLINE Method *GetMethod()
81     {
82         return protoReader_.GetMethod();
83     }
84 
85     ~CallJSHandler() = default;
86 
87 private:
ForwardException(InteropCtx * ctx,EtsCoroutine * coro)88     static uint64_t __attribute__((noinline)) ForwardException(InteropCtx *ctx, EtsCoroutine *coro)
89     {
90         if (NapiIsExceptionPending(ctx->GetJSEnv())) {
91             ctx->ForwardJSException(coro);
92         }
93         ASSERT(ctx->SanityETSExceptionPending());
94         return 0;
95     }
96 
97     template <bool IS_NEWCALL, typename ArgSetup>
98     ALWAYS_INLINE uint64_t Handle();
99 
100     template <bool IS_NEWCALL>
101     ALWAYS_INLINE std::optional<napi_value> ConvertArgsAndCall();
102 
103     template <bool IS_NEWCALL>
104     ALWAYS_INLINE std::optional<Value> ConvertRetval(napi_value jsRet);
105 
106     NO_COPY_SEMANTIC(CallJSHandler);
107     NO_MOVE_SEMANTIC(CallJSHandler);
108 
109     EtsCoroutine *const coro_;
110     InteropCtx *const ctx_;
111 
112     ProtoReader protoReader_;
113     arch::ArgReader<RUNTIME_ARCH> argReader_;
114     uint32_t numArgs_ {};
115     napi_value jsThis_ {};
116     napi_value jsFn_ {};
117 };
118 
119 template <bool IS_NEWCALL, typename ArgSetup>
Handle()120 ALWAYS_INLINE inline uint64_t CallJSHandler::Handle()
121 {
122     [[maybe_unused]] InteropCodeScopeETS codeScope(coro_, __PRETTY_FUNCTION__);
123     napi_env env = ctx_->GetJSEnv();
124     NapiScope jsHandleScope(env);
125 
126     if (UNLIKELY(!ArgSetup()(ctx_, this))) {
127         return ForwardException(ctx_, coro_);
128     }
129     if (UNLIKELY(GetValueType(env, jsFn_) != napi_function)) {
130         ctx_->ThrowJSTypeError(env, "call target is not a function");
131         return ForwardException(ctx_, coro_);
132     }
133 
134     std::optional<napi_value> jsRes = ConvertArgsAndCall<IS_NEWCALL>();
135     if (UNLIKELY(!jsRes.has_value())) {
136         return ForwardException(ctx_, coro_);
137     }
138     std::optional<Value> etsRes = ConvertRetval<IS_NEWCALL>(jsRes.value());
139     if (UNLIKELY(!etsRes.has_value())) {
140         return ForwardException(ctx_, coro_);
141     }
142     return static_cast<uint64_t>(etsRes.value().GetAsLong());
143 }
144 
145 template <bool IS_NEWCALL>
ConvertArgsAndCall()146 ALWAYS_INLINE inline std::optional<napi_value> CallJSHandler::ConvertArgsAndCall()
147 {
148     napi_env env = ctx_->GetJSEnv();
149     auto jsargs = ctx_->GetTempArgs<napi_value>(numArgs_);
150 
151     for (uint32_t argIdx = 0; argIdx < numArgs_; ++argIdx, protoReader_.Advance()) {
152         auto readVal = [this](auto typeTag) { return argReader_.template Read<typename decltype(typeTag)::type>(); };
153         if (UNLIKELY(!ConvertArgToJS(ctx_, protoReader_, &jsargs[argIdx], readVal))) {
154             return std::nullopt;
155         }
156     }
157 
158     napi_value jsRetval;
159     napi_status jsStatus;
160     {
161         ScopedNativeCodeThread nativeScope(coro_);
162         if constexpr (IS_NEWCALL) {
163             jsStatus = napi_new_instance(env, jsFn_, jsargs->size(), jsargs->data(), &jsRetval);
164         } else {
165             jsStatus = napi_call_function(env, jsThis_, jsFn_, jsargs->size(), jsargs->data(), &jsRetval);
166         }
167     }
168     if (UNLIKELY(jsStatus != napi_ok)) {
169         INTEROP_FATAL_IF(jsStatus != napi_pending_exception);
170         return std::nullopt;
171     }
172     return jsRetval;
173 }
174 
175 template <bool IS_NEWCALL>
ConvertRetval(napi_value jsRet)176 ALWAYS_INLINE inline std::optional<Value> CallJSHandler::ConvertRetval(napi_value jsRet)
177 {
178     napi_env env = ctx_->GetJSEnv();
179     Value etsRet;
180     protoReader_.Reset();
181 
182     if constexpr (IS_NEWCALL) {
183         ASSERT(protoReader_.GetClass() == ctx_->GetJSValueClass());
184         auto res = JSConvertJSValue::Unwrap(ctx_, env, jsRet);
185         if (UNLIKELY(!res.has_value())) {
186             return std::nullopt;
187         }
188         etsRet = Value(res.value()->GetCoreType());
189     } else {
190         auto store = [&etsRet](auto val) { etsRet = Value(val); };
191         if (UNLIKELY(!ConvertArgToEts(ctx_, protoReader_, store, jsRet))) {
192             return std::nullopt;
193         }
194     }
195     return etsRet;
196 }
197 
ResolveQualifiedReceiverTarget(napi_env env,napi_value jsVal,coretypes::String * qnameStr)198 static std::optional<std::pair<napi_value, napi_value>> ResolveQualifiedReceiverTarget(napi_env env, napi_value jsVal,
199                                                                                        coretypes::String *qnameStr)
200 {
201     napi_value jsThis {};
202     ASSERT(qnameStr->IsMUtf8());
203     auto qname = std::string(utf::Mutf8AsCString(qnameStr->GetDataMUtf8()), qnameStr->GetMUtf8Length());
204 
205     auto resolveName = [&jsThis, &jsVal, &env](const std::string &name) -> bool {
206         jsThis = jsVal;
207         INTEROP_LOG(DEBUG) << "JSRuntimeCallJS: resolve name: " << name;
208         napi_status rc = napi_get_named_property(env, jsVal, name.c_str(), &jsVal);
209         if (UNLIKELY(rc == napi_object_expected || NapiThrownGeneric(rc))) {
210             ASSERT(NapiIsExceptionPending(env));
211             return false;
212         }
213         return true;
214     };
215     jsThis = jsVal;
216     if (UNLIKELY(!WalkQualifiedName(qname, resolveName))) {
217         return std::nullopt;
218     }
219     return std::make_pair(jsThis, jsVal);
220 }
221 
222 template <bool IS_NEWCALL>
JSRuntimeCallJSQNameBase(Method * method,uint8_t * args,uint8_t * inStackArgs)223 static ALWAYS_INLINE inline uint64_t JSRuntimeCallJSQNameBase(Method *method, uint8_t *args, uint8_t *inStackArgs)
224 {
225     struct ArgSetup {
226         ALWAYS_INLINE bool operator()(InteropCtx *ctx, CallJSHandler *st)
227         {
228             st->SetupArgreader(false);
229             napi_env env = ctx->GetJSEnv();
230 
231             napi_value jsVal = JSConvertJSValue::Wrap(env, st->ReadFixedRefArg<JSValue>(ctx->GetJSValueClass()));
232             auto qnameStr = st->ReadFixedRefArg<coretypes::String>(ctx->GetStringClass());
233 
234             auto res = ResolveQualifiedReceiverTarget(env, jsVal, qnameStr);
235             if (UNLIKELY(!res.has_value())) {
236                 ASSERT(NapiIsExceptionPending(env));
237                 return false;
238             }
239 
240             st->SetupJSCallee(res->first, res->second);
241             return true;
242         }
243     };
244     return CallJSHandler::HandleImpl<IS_NEWCALL, ArgSetup>(method, args, inStackArgs);
245 }
246 
JSRuntimeCallJSQName(Method * method,uint8_t * args,uint8_t * inStackArgs)247 extern "C" uint64_t JSRuntimeCallJSQName(Method *method, uint8_t *args, uint8_t *inStackArgs)
248 {
249     return JSRuntimeCallJSQNameBase<false>(method, args, inStackArgs);  // IS_NEWCALL is false
250 }
251 extern "C" void JSRuntimeCallJSQNameBridge(Method *method, ...);
252 
JSRuntimeNewCallJSQName(Method * method,uint8_t * args,uint8_t * inStackArgs)253 extern "C" uint64_t JSRuntimeNewCallJSQName(Method *method, uint8_t *args, uint8_t *inStackArgs)
254 {
255     return JSRuntimeCallJSQNameBase<true>(method, args, inStackArgs);  // IS_NEWCALL is true
256 }
257 extern "C" void JSRuntimeNewCallJSQNameBridge(Method *method, ...);
258 
GetClassQnameOffset(InteropCtx * ctx,Method * method)259 static uint32_t GetClassQnameOffset(InteropCtx *ctx, Method *method)
260 {
261     auto klass = method->GetClass();
262     ctx->GetConstStringStorage()->LoadDynamicCallClass(klass);
263     auto fields = klass->GetStaticFields();
264     ASSERT(fields.size() == 1);
265     return klass->GetFieldPrimitive<uint32_t>(fields[0]);
266 }
267 
268 template <bool IS_NEWCALL>
JSRuntimeCallJSBase(Method * method,uint8_t * args,uint8_t * inStackArgs)269 static ALWAYS_INLINE inline uint64_t JSRuntimeCallJSBase(Method *method, uint8_t *args, uint8_t *inStackArgs)
270 {
271     struct ArgSetup {
272         ALWAYS_INLINE bool operator()(InteropCtx *ctx, CallJSHandler *st)
273         {
274             st->SetupArgreader(false);
275             napi_env env = ctx->GetJSEnv();
276 
277             napi_value jsVal = JSConvertJSValue::Wrap(env, st->ReadFixedRefArg<JSValue>(ctx->GetJSValueClass()));
278 
279             auto classQnameOffset = GetClassQnameOffset(ctx, st->GetMethod());
280             auto qnameStart = st->ReadFixedArg<panda_file::Type::TypeId::I32, int32_t>() + classQnameOffset;
281             auto qnameLen = st->ReadFixedArg<panda_file::Type::TypeId::I32, int32_t>();
282             napi_value jsThis {};
283 
284             auto success = ctx->GetConstStringStorage()->EnumerateStrings(
285                 qnameStart, qnameLen, [&jsThis, &jsVal, env](napi_value jsStr) {
286                     jsThis = jsVal;
287                     napi_status rc = napi_get_property(env, jsVal, jsStr, &jsVal);
288                     if (UNLIKELY(rc == napi_object_expected || NapiThrownGeneric(rc))) {
289                         ASSERT(NapiIsExceptionPending(env));
290                         return false;
291                     }
292                     return true;
293                 });
294 
295             if (!success) {
296                 ASSERT(NapiIsExceptionPending(env));
297                 return false;
298             }
299             st->SetupJSCallee(jsThis, jsVal);
300             return true;
301         }
302     };
303     return CallJSHandler::HandleImpl<IS_NEWCALL, ArgSetup>(method, args, inStackArgs);
304 }
305 
JSRuntimeCallJS(Method * method,uint8_t * args,uint8_t * inStackArgs)306 extern "C" uint64_t JSRuntimeCallJS(Method *method, uint8_t *args, uint8_t *inStackArgs)
307 {
308     return JSRuntimeCallJSBase<false>(method, args, inStackArgs);  // IS_NEWCALL is false
309 }
310 extern "C" void JSRuntimeCallJSBridge(Method *method, ...);
311 
JSRuntimeNewCallJS(Method * method,uint8_t * args,uint8_t * inStackArgs)312 extern "C" uint64_t JSRuntimeNewCallJS(Method *method, uint8_t *args, uint8_t *inStackArgs)
313 {
314     return JSRuntimeCallJSBase<true>(method, args, inStackArgs);  // IS_NEWCALL is true
315 }
316 extern "C" void JSRuntimeNewCallJSBridge(Method *method, ...);
317 
JSRuntimeCallJSByValue(Method * method,uint8_t * args,uint8_t * inStackArgs)318 extern "C" uint64_t JSRuntimeCallJSByValue(Method *method, uint8_t *args, uint8_t *inStackArgs)
319 {
320     struct ArgSetup {
321         ALWAYS_INLINE bool operator()(InteropCtx *ctx, CallJSHandler *st)
322         {
323             st->SetupArgreader(false);
324             napi_env env = ctx->GetJSEnv();
325 
326             napi_value jsFn = JSConvertJSValue::Wrap(env, st->ReadFixedRefArg<JSValue>(ctx->GetJSValueClass()));
327             napi_value jsThis = JSConvertJSValue::Wrap(env, st->ReadFixedRefArg<JSValue>(ctx->GetJSValueClass()));
328 
329             st->SetupJSCallee(jsThis, jsFn);
330             return true;
331         }
332     };
333     return CallJSHandler::HandleImpl<false, ArgSetup>(method, args, inStackArgs);
334 }
335 extern "C" void JSRuntimeCallJSByValueBridge(Method *method, ...);
336 
CallJSProxy(Method * method,uint8_t * args,uint8_t * inStackArgs)337 extern "C" uint64_t CallJSProxy(Method *method, uint8_t *args, uint8_t *inStackArgs)
338 {
339     struct ArgSetup {
340         ALWAYS_INLINE bool operator()(InteropCtx *ctx, CallJSHandler *st)
341         {
342             ObjectHeader *etsThis = st->SetupArgreader(true);
343             napi_env env = ctx->GetJSEnv();
344 
345             auto refconv = JSRefConvertResolve(ctx, etsThis->ClassAddr<Class>());
346             napi_value jsThis = refconv->Wrap(ctx, EtsObject::FromCoreType(etsThis));
347             ASSERT(GetValueType(env, jsThis) == napi_object);
348             const char *methodName = utf::Mutf8AsCString(st->GetMethod()->GetName().data);
349             napi_value jsFn;
350             napi_status rc = napi_get_named_property(env, jsThis, methodName, &jsFn);
351             if (UNLIKELY(rc == napi_object_expected || NapiThrownGeneric(rc))) {
352                 ASSERT(NapiIsExceptionPending(env));
353                 return false;
354             }
355 
356             st->SetupJSCallee(jsThis, jsFn);
357             return true;
358         }
359     };
360     return CallJSHandler::HandleImpl<false, ArgSetup>(method, args, inStackArgs);
361 }
362 extern "C" void CallJSProxyBridge(Method *method, ...);
363 
SelectCallJSEntrypoint(InteropCtx * ctx,Method * method)364 static void *SelectCallJSEntrypoint(InteropCtx *ctx, Method *method)
365 {
366     ASSERT(method->IsStatic());
367     ProtoReader protoReader(method, ctx->GetClassLinker(), ctx->LinkerCtx());
368 
369     // skip return type
370     protoReader.Advance();
371 
372     ASSERT(protoReader.GetClass() == ctx->GetJSValueClass());
373     protoReader.Advance();
374 
375     if (protoReader.GetType().IsPrimitive()) {
376         if (protoReader.GetType().GetId() != panda_file::Type::TypeId::I32) {
377             return nullptr;
378         }
379         protoReader.Advance();
380 
381         if (protoReader.GetType().GetId() != panda_file::Type::TypeId::I32) {
382             return nullptr;
383         }
384         return reinterpret_cast<void *>(JSRuntimeCallJSBridge);
385     }
386     if (protoReader.GetClass() == ctx->GetStringClass()) {
387         return reinterpret_cast<void *>(JSRuntimeCallJSQNameBridge);
388     }
389     if (protoReader.GetClass() == ctx->GetJSValueClass()) {
390         return reinterpret_cast<void *>(JSRuntimeCallJSByValueBridge);
391     }
392     InteropFatal("Bad jscall signature");
393 }
394 
SelectNewCallJSEntrypoint(InteropCtx * ctx,Method * method)395 static void *SelectNewCallJSEntrypoint(InteropCtx *ctx, Method *method)
396 {
397     ASSERT(method->IsStatic());
398     ProtoReader protoReader(method, ctx->GetClassLinker(), ctx->LinkerCtx());
399 
400     if (protoReader.GetClass() != ctx->GetJSValueClass()) {
401         return nullptr;
402     }
403     protoReader.Advance();
404 
405     if (protoReader.GetClass() != ctx->GetJSValueClass()) {
406         return nullptr;
407     }
408     protoReader.Advance();
409 
410     if (protoReader.GetType().IsPrimitive()) {
411         if (protoReader.GetType().GetId() != panda_file::Type::TypeId::I32) {
412             return nullptr;
413         }
414         protoReader.Advance();
415 
416         if (protoReader.GetType().GetId() != panda_file::Type::TypeId::I32) {
417             return nullptr;
418         }
419         return reinterpret_cast<void *>(JSRuntimeNewCallJSBridge);
420     }
421     if (protoReader.GetClass() != ctx->GetStringClass()) {
422         return nullptr;
423     }
424     return reinterpret_cast<void *>(JSRuntimeNewCallJSQNameBridge);
425 }
426 
GetQnameCount(Class * klass)427 static std::optional<uint32_t> GetQnameCount(Class *klass)
428 {
429     auto pf = klass->GetPandaFile();
430     panda_file::ClassDataAccessor cda(*pf, klass->GetFileId());
431     auto qnameCount =
432         cda.EnumerateAnnotation("Lets/annotation/DynamicCall;", [pf](panda_file::AnnotationDataAccessor &ada) {
433             for (uint32_t i = 0; i < ada.GetCount(); i++) {
434                 auto adae = ada.GetElement(i);
435                 auto *elemName = pf->GetStringData(adae.GetNameId()).data;
436                 if (utf::IsEqual(utf::CStringAsMutf8("value"), elemName)) {
437                     return adae.GetArrayValue().GetCount();
438                 }
439             }
440             UNREACHABLE();
441         });
442     return qnameCount;
443 }
444 
InitCallJSClass(coretypes::String * descriptorStr,bool isNewCall)445 static uint8_t InitCallJSClass(coretypes::String *descriptorStr, bool isNewCall)
446 {
447     auto coro = EtsCoroutine::GetCurrent();
448     auto ctx = InteropCtx::Current(coro);
449     auto *klass = ctx->GetClassLinker()->GetClass(descriptorStr->GetDataMUtf8(), true, ctx->LinkerCtx());
450     INTEROP_FATAL_IF(klass == nullptr);
451     INTEROP_LOG(DEBUG) << "Bind bridge call methods for " << utf::Mutf8AsCString(klass->GetDescriptor());
452 
453     for (auto &method : klass->GetMethods()) {
454         if (method.IsConstructor()) {
455             continue;
456         }
457         void *ep = nullptr;
458         if (method.IsStatic()) {
459             ep = isNewCall ? SelectNewCallJSEntrypoint(ctx, &method) : SelectCallJSEntrypoint(ctx, &method);
460         }
461         if (ep == nullptr) {
462             InteropFatal("Bad interop call bridge signature");
463         }
464         method.SetCompiledEntryPoint(ep);
465         method.SetNativePointer(nullptr);
466     }
467 
468     auto qnameCount = GetQnameCount(klass);
469     // JSCallClass which was manually created in test will not have the required annotation and field
470     if (qnameCount.has_value()) {
471         auto fields = klass->GetStaticFields();
472         INTEROP_FATAL_IF(fields.Size() != 1);
473         INTEROP_FATAL_IF(klass->GetFieldPrimitive<uint32_t>(fields[0]) != 0);
474         klass->SetFieldPrimitive<uint32_t>(fields[0], ctx->AllocateSlotsInStringBuffer(*qnameCount));
475     }
476     return 1;
477 }
478 
JSRuntimeInitJSCallClass(EtsString * clsStr)479 uint8_t JSRuntimeInitJSCallClass(EtsString *clsStr)
480 {
481     return InitCallJSClass(clsStr->GetCoreType(), false);
482 }
483 
JSRuntimeInitJSNewClass(EtsString * clsStr)484 uint8_t JSRuntimeInitJSNewClass(EtsString *clsStr)
485 {
486     return InitCallJSClass(clsStr->GetCoreType(), true);
487 }
488 
489 }  // namespace ark::ets::interop::js
490