• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (c) 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/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 
23 class CallETSHandler {
24 public:
CallETSHandler(EtsCoroutine * coro,InteropCtx * ctx,Method * method,Span<napi_value> jsargv,EtsObject * thisObj)25     ALWAYS_INLINE CallETSHandler(EtsCoroutine *coro, InteropCtx *ctx, Method *method, Span<napi_value> jsargv,
26                                  EtsObject *thisObj)
27         : coro_(coro),
28           ctx_(ctx),
29           protoReader_(method, ctx_->GetClassLinker(), ctx_->LinkerCtx()),
30           thisObj_(thisObj),
31           jsargv_(jsargv)
32     {
33     }
34 
35     template <bool IS_STATIC>
HandleImpl(EtsCoroutine * coro,InteropCtx * ctx,Method * method,Span<napi_value> jsargv,EtsObject * thisObj)36     static ALWAYS_INLINE napi_value HandleImpl(EtsCoroutine *coro, InteropCtx *ctx, Method *method,
37                                                Span<napi_value> jsargv, EtsObject *thisObj)
38     {
39         CallETSHandler st(coro, ctx, method, jsargv, thisObj);
40         return st.HandleImpl<IS_STATIC>();
41     }
42 
43     ~CallETSHandler() = default;
44 
45 private:
46     template <bool IS_STATIC>
47     ALWAYS_INLINE bool ConvertArgs(Span<Value> etsArgs);
48     ALWAYS_INLINE ObjectHeader **ConvertRestParams(Span<napi_value> restArgs);
49 
50     ALWAYS_INLINE bool CheckNumArgs(size_t numArgs) const;
51 
52     template <bool IS_STATIC>
53     napi_value HandleImpl();
54 
ForwardException(InteropCtx * ctx,EtsCoroutine * coro)55     static napi_value __attribute__((noinline)) ForwardException(InteropCtx *ctx, EtsCoroutine *coro)
56     {
57         if (coro->HasPendingException()) {
58             ctx->ForwardEtsException(coro);
59         }
60         ASSERT(ctx->SanityJSExceptionPending());
61         return nullptr;
62     }
63 
64     NO_COPY_SEMANTIC(CallETSHandler);
65     NO_MOVE_SEMANTIC(CallETSHandler);
66 
67     EtsCoroutine *const coro_;
68     InteropCtx *const ctx_;
69 
70     ProtoReader protoReader_;
71 
72     EtsObject *thisObj_;
73     Span<napi_value> jsargv_;
74 };
75 
76 template <bool IS_STATIC>
77 // CC-OFFNXT(huge_method) solid logic
ConvertArgs(Span<Value> etsArgs)78 ALWAYS_INLINE inline bool CallETSHandler::ConvertArgs(Span<Value> etsArgs)
79 {
80     HandleScope<ObjectHeader *> etsHandleScope(coro_);
81     auto const createRoot = [coro = coro_](ObjectHeader *val) {
82         return reinterpret_cast<ObjectHeader **>(VMHandle<ObjectHeader>(coro, val).GetAddress());
83     };
84 
85     [[maybe_unused]] ObjectHeader **thisObjRoot = nullptr;
86     if (!IS_STATIC) {
87         ASSERT(thisObj_ != nullptr);
88         thisObjRoot = createRoot(thisObj_->GetCoreType());
89     }
90 
91     using ArgValueBox = std::variant<uint64_t, ObjectHeader **>;
92     auto const numArgs = protoReader_.GetMethod()->GetNumArgs() - !IS_STATIC;
93     auto const numNonRest = numArgs - protoReader_.GetMethod()->HasVarArgs();
94     auto etsBoxedArgs = ctx_->GetTempArgs<ArgValueBox>(numArgs);
95 
96     // Convert and store in root if necessary
97     for (uint32_t argIdx = 0; argIdx < numNonRest; ++argIdx, protoReader_.Advance()) {
98         auto store = [&etsBoxedArgs, &argIdx, createRoot](auto val) {
99             if constexpr (std::is_pointer_v<decltype(val)> || std::is_null_pointer_v<decltype(val)>) {
100                 etsBoxedArgs[argIdx] = createRoot(val);
101             } else {
102                 etsBoxedArgs[argIdx] = static_cast<uint64_t>(val);
103             }
104         };
105 
106         if (argIdx >= jsargv_.size()) {
107             // for the non-present args, set them to nullptr
108             // `nullptr` stand for undefined here
109             store(nullptr);
110         } else {
111             auto jsVal = jsargv_[argIdx];
112 
113             if (UNLIKELY(!ConvertArgToEts(ctx_, protoReader_, store, jsVal))) {
114                 return false;
115             }
116         }
117     }
118 
119     if (protoReader_.GetMethod()->HasVarArgs()) {
120         const auto restIdx = numArgs - 1;
121         auto restParams = ConvertRestParams(jsargv_.SubSpan(restIdx));
122         if (UNLIKELY(restParams == nullptr)) {
123             return false;
124         }
125         etsBoxedArgs[restIdx] = restParams;
126     }
127 
128     // Unbox values
129     if constexpr (!IS_STATIC) {
130         ASSERT(thisObjRoot != nullptr);
131         etsArgs[0] = Value(*thisObjRoot);
132     }
133     static constexpr size_t ETS_ARGS_DISP = IS_STATIC ? 0 : 1;
134 
135     for (size_t i = 0; i < numArgs; ++i) {
136         ArgValueBox &box = etsBoxedArgs[i];
137         if (std::holds_alternative<ObjectHeader **>(box)) {
138             ObjectHeader **slot = std::get<1>(box);
139             etsArgs[ETS_ARGS_DISP + i] = Value(slot != nullptr ? *slot : nullptr);
140         } else {
141             etsArgs[ETS_ARGS_DISP + i] = Value(std::get<0>(box));
142         }
143     }
144     return true;
145 }
146 
ConvertRestParams(Span<napi_value> restArgs)147 ObjectHeader **CallETSHandler::ConvertRestParams(Span<napi_value> restArgs)
148 {
149     ASSERT(protoReader_.GetType().IsReference());
150     return PackRestParameters(coro_, ctx_, protoReader_, restArgs);
151 }
152 
CheckNumArgs(size_t numArgs) const153 bool CallETSHandler::CheckNumArgs(size_t numArgs) const
154 {
155     const auto method = protoReader_.GetMethod();
156     bool const hasRestParams = method->HasVarArgs();
157     auto const numMandatoryParams = EtsMethod::FromRuntimeMethod(const_cast<Method *>(method))->GetNumMandatoryArgs();
158     ASSERT((hasRestParams && numArgs > 0) || !hasRestParams);
159 
160     // handle fast path: if the js argv of args is equal to numArgs
161     // then the check is passed
162     if (jsargv_.size() >= numArgs) {
163         return true;
164     }
165 
166     // handle slow path
167     if (jsargv_.size() < numMandatoryParams) {
168         std::string msg = "CallEtsFunction: wrong argc, ets_argc=" + std::to_string(numArgs) +
169                           " js_argc=" + std::to_string(jsargv_.size()) +
170                           " hasRestParam=" + std::to_string(static_cast<int>(hasRestParams)) +
171                           " numMandatoryParams=" + std::to_string(numMandatoryParams) + " ets_method='" +
172                           std::string(method->GetFullName(true)) + "'";
173         InteropCtx::ThrowJSTypeError(ctx_->GetJSEnv(), msg);
174         return false;
175     }
176     return true;
177 }
178 
179 template <bool IS_STATIC>
HandleImpl()180 napi_value CallETSHandler::HandleImpl()
181 {
182     ASSERT_MANAGED_CODE();
183     auto method = protoReader_.GetMethod();
184 
185     protoReader_.Advance();  // skip return type
186     ASSERT(method->IsStatic() == IS_STATIC);
187     ASSERT(IS_STATIC == (thisObj_ == nullptr));
188 
189     auto const numArgs = method->GetNumArgs() - (IS_STATIC ? 0 : 1);
190     if (UNLIKELY(!CheckNumArgs(numArgs))) {
191         return ForwardException(ctx_, coro_);
192     }
193 
194     auto etsArgs = ctx_->GetTempArgs<Value>(method->GetNumArgs());
195     if (UNLIKELY(!ConvertArgs<IS_STATIC>(*etsArgs))) {
196         return ForwardException(ctx_, coro_);
197     }
198 
199     Value etsRes = method->Invoke(coro_, etsArgs->data());
200     if (UNLIKELY(coro_->HasPendingException())) {
201         return ForwardException(ctx_, coro_);
202     }
203 
204     protoReader_.Reset();
205     napi_value jsRes;
206     auto readVal = [&etsRes](auto typeTag) { return etsRes.GetAs<typename decltype(typeTag)::type>(); };
207     // scope is required to ConvertRefArgToJS
208     HandleScope<ObjectHeader *> scope(coro_);
209     if (UNLIKELY(!ConvertArgToJS(ctx_, protoReader_, &jsRes, readVal))) {
210         return ForwardException(ctx_, coro_);
211     }
212     return jsRes;
213 }
214 
CallETSInstance(EtsCoroutine * coro,InteropCtx * ctx,Method * method,Span<napi_value> jsargv,EtsObject * thisObj)215 napi_value CallETSInstance(EtsCoroutine *coro, InteropCtx *ctx, Method *method, Span<napi_value> jsargv,
216                            EtsObject *thisObj)
217 {
218     return CallETSHandler::HandleImpl<false>(coro, ctx, method, jsargv, thisObj);
219 }
CallETSStatic(EtsCoroutine * coro,InteropCtx * ctx,Method * method,Span<napi_value> jsargv)220 napi_value CallETSStatic(EtsCoroutine *coro, InteropCtx *ctx, Method *method, Span<napi_value> jsargv)
221 {
222     return CallETSHandler::HandleImpl<true>(coro, ctx, method, jsargv, nullptr);
223 }
224 
ResolveEntryPoint(InteropCtx * ctx,std::string_view entryPoint)225 Expected<Method *, PandaString> ResolveEntryPoint(InteropCtx *ctx, std::string_view entryPoint)
226 {
227     ASSERT_MANAGED_CODE();
228     auto const packageSep = entryPoint.rfind('.');
229     if (UNLIKELY(packageSep == PandaString::npos)) {
230         return Unexpected(PandaString("Bad entrypoint format: ") + PandaString(entryPoint));
231     }
232 
233     PandaString complexClassName =
234         'L' + (UNLIKELY(packageSep == 0) ? "" : PandaString(entryPoint.substr(0, packageSep + 1))) + "ETSGLOBAL;";
235     std::replace(complexClassName.begin(), complexClassName.end(), '.', '/');
236 
237     uint8_t const *classDescriptor = utf::CStringAsMutf8(complexClassName.data());
238     uint8_t const *methodName = utf::CStringAsMutf8(&entryPoint.at(packageSep + 1));
239 
240     Class *cls = ctx->GetClassLinker()->GetClass(classDescriptor, true, ctx->LinkerCtx());
241     if (UNLIKELY(cls == nullptr)) {
242         return Unexpected(PandaString("Cannot find class ") + utf::Mutf8AsCString(classDescriptor));
243     }
244 
245     Method *method = cls->GetDirectMethod(methodName);
246     if (UNLIKELY(method == nullptr)) {
247         return Unexpected(PandaString("Cannot find method ") + utf::Mutf8AsCString(methodName));
248     }
249     return method;
250 }
251 
252 }  // namespace ark::ets::interop::js
253