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