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 #ifndef PANDA_PLUGINS_ETS_RUNTIME_INTEROP_JS_CALL_ARG_CONVERTORS_H
17 #define PANDA_PLUGINS_ETS_RUNTIME_INTEROP_JS_CALL_ARG_CONVERTORS_H
18
19 #include "plugins/ets/runtime/interop_js/call/proto_reader.h"
20 #include "plugins/ets/runtime/interop_js/js_convert.h"
21 #include "plugins/ets/runtime/types/ets_escompat_array.h"
22
23 namespace ark::ets::interop::js {
24
25 template <typename Convertor, typename FStore>
UnwrapVal(InteropCtx * ctx,napi_env env,napi_value jsVal,FStore & storeRes)26 static ALWAYS_INLINE bool UnwrapVal(InteropCtx *ctx, napi_env env, napi_value jsVal, FStore &storeRes)
27 {
28 using cpptype = typename Convertor::cpptype; // NOLINT(readability-identifier-naming)
29 auto res = Convertor::Unwrap(ctx, env, jsVal);
30 if (UNLIKELY(!res.has_value())) {
31 return false;
32 }
33 if constexpr (std::is_pointer_v<cpptype>) {
34 storeRes(AsEtsObject(res.value())->GetCoreType());
35 } else {
36 storeRes(Value(res.value()).GetAsLong());
37 }
38 return true;
39 }
40
41 template <typename FStore>
ConvertRefArgToEts(InteropCtx * ctx,Class * klass,FStore & storeRes,napi_value jsVal)42 [[nodiscard]] static ALWAYS_INLINE inline bool ConvertRefArgToEts(InteropCtx *ctx, Class *klass, FStore &storeRes,
43 napi_value jsVal)
44 {
45 auto env = ctx->GetJSEnv();
46
47 // start fastpath
48 if (IsUndefined(env, jsVal)) {
49 storeRes(nullptr);
50 return true;
51 }
52 if (IsNull(env, jsVal)) {
53 if (LIKELY(klass->IsAssignableFrom(ctx->GetNullValueClass()))) {
54 storeRes(ctx->GetNullValue()->GetCoreType());
55 return true;
56 }
57 }
58 if (klass == ctx->GetJSValueClass()) {
59 return UnwrapVal<JSConvertJSValue>(ctx, env, jsVal, storeRes);
60 }
61 if (klass == ctx->GetStringClass()) {
62 return UnwrapVal<JSConvertString>(ctx, env, jsVal, storeRes);
63 }
64 // start slowpath
65 auto refconv = JSRefConvertResolve<true>(ctx, klass);
66 if (UNLIKELY(refconv == nullptr)) {
67 return false;
68 }
69 ObjectHeader *res = refconv->Unwrap(ctx, jsVal)->GetCoreType();
70 storeRes(res);
71 return res != nullptr;
72 }
73
74 template <typename FStore>
ConvertPrimArgToEts(InteropCtx * ctx,panda_file::Type::TypeId id,FStore & storeRes,napi_value jsVal)75 [[nodiscard]] static ALWAYS_INLINE inline bool ConvertPrimArgToEts(InteropCtx *ctx, panda_file::Type::TypeId id,
76 FStore &storeRes, napi_value jsVal)
77 {
78 auto env = ctx->GetJSEnv();
79
80 auto unwrapVal = [&ctx, &env, &jsVal, &storeRes](auto convTag) {
81 using Convertor = typename decltype(convTag)::type; // convTag acts as lambda template parameter
82 return UnwrapVal<Convertor>(ctx, env, jsVal, storeRes);
83 };
84 switch (id) {
85 case panda_file::Type::TypeId::VOID: {
86 return true; // do nothing
87 }
88 case panda_file::Type::TypeId::U1:
89 return unwrapVal(helpers::TypeIdentity<JSConvertU1>());
90 case panda_file::Type::TypeId::I8:
91 return unwrapVal(helpers::TypeIdentity<JSConvertI8>());
92 case panda_file::Type::TypeId::U8:
93 return unwrapVal(helpers::TypeIdentity<JSConvertU8>());
94 case panda_file::Type::TypeId::I16:
95 return unwrapVal(helpers::TypeIdentity<JSConvertI16>());
96 case panda_file::Type::TypeId::U16:
97 return unwrapVal(helpers::TypeIdentity<JSConvertU16>());
98 case panda_file::Type::TypeId::I32:
99 return unwrapVal(helpers::TypeIdentity<JSConvertI32>());
100 case panda_file::Type::TypeId::U32:
101 return unwrapVal(helpers::TypeIdentity<JSConvertU32>());
102 case panda_file::Type::TypeId::I64:
103 return unwrapVal(helpers::TypeIdentity<JSConvertI64>());
104 case panda_file::Type::TypeId::U64:
105 return unwrapVal(helpers::TypeIdentity<JSConvertU64>());
106 case panda_file::Type::TypeId::F32:
107 return unwrapVal(helpers::TypeIdentity<JSConvertF32>());
108 case panda_file::Type::TypeId::F64:
109 return unwrapVal(helpers::TypeIdentity<JSConvertF64>());
110 default:
111 UNREACHABLE();
112 }
113 }
114
115 template <typename FStore, typename GetClass>
ConvertArgToEts(InteropCtx * ctx,panda_file::Type type,FStore & storeRes,const GetClass & getClass,napi_value jsVal)116 [[nodiscard]] static ALWAYS_INLINE inline bool ConvertArgToEts(InteropCtx *ctx, panda_file::Type type, FStore &storeRes,
117 const GetClass &getClass, napi_value jsVal)
118 {
119 auto id = type.GetId();
120 if (id == panda_file::Type::TypeId::REFERENCE) {
121 return ConvertRefArgToEts(ctx, getClass(), storeRes, jsVal);
122 }
123 return ConvertPrimArgToEts(ctx, id, storeRes, jsVal);
124 }
125
126 template <typename FStore>
ConvertArgToEts(InteropCtx * ctx,ProtoReader & protoReader,FStore & storeRes,napi_value jsVal)127 [[nodiscard]] static ALWAYS_INLINE inline bool ConvertArgToEts(InteropCtx *ctx, ProtoReader &protoReader,
128 FStore &storeRes, napi_value jsVal)
129 {
130 return ConvertArgToEts(
131 ctx, protoReader.GetType(), storeRes, [&protoReader]() { return protoReader.GetClass(); }, jsVal);
132 }
133
134 template <typename RestParamsArray>
DoPackRestParameters(EtsCoroutine * coro,InteropCtx * ctx,ProtoReader & protoReader,Span<napi_value> jsargv)135 static ObjectHeader **DoPackRestParameters(EtsCoroutine *coro, InteropCtx *ctx, ProtoReader &protoReader,
136 Span<napi_value> jsargv)
137 {
138 const size_t numRestParams = jsargv.size();
139
140 RestParamsArray *objArr = [&]() {
141 if constexpr (std::is_same_v<RestParamsArray, EtsObjectArray>) {
142 EtsClass *etsClass = EtsClass::FromRuntimeClass(protoReader.GetClass()->GetComponentType());
143 return RestParamsArray::Create(etsClass, numRestParams);
144 } else {
145 return RestParamsArray::Create(numRestParams);
146 }
147 }();
148
149 auto convertValue = [](auto val) -> typename RestParamsArray::ValueType {
150 constexpr bool IS_VAL_PTR = std::is_pointer_v<decltype(val)> || std::is_null_pointer_v<decltype(val)>;
151 constexpr bool IS_OBJ_ARR = std::is_same_v<RestParamsArray, EtsObjectArray>;
152 // Clang-tidy gives false positive error.
153 if constexpr (IS_OBJ_ARR) {
154 if constexpr (IS_VAL_PTR) {
155 return EtsObject::FromCoreType(static_cast<ObjectHeader *>(val));
156 }
157 }
158 if constexpr (!IS_OBJ_ARR) {
159 if constexpr (!IS_VAL_PTR) {
160 return *reinterpret_cast<typename RestParamsArray::ValueType *>(&val);
161 }
162 }
163 UNREACHABLE();
164 };
165
166 VMHandle<RestParamsArray> restArgsArray(coro, objArr->GetCoreType());
167 for (uint32_t restArgIdx = 0; restArgIdx < numRestParams; ++restArgIdx) {
168 auto jsVal = jsargv[restArgIdx];
169 auto store = [&convertValue, restArgIdx, &restArgsArray](auto val) {
170 restArgsArray.GetPtr()->Set(restArgIdx, convertValue(val));
171 };
172 auto klass = protoReader.GetClass()->GetComponentType();
173 auto klassCb = [klass]() { return klass; };
174 if (UNLIKELY(!ConvertArgToEts(ctx, klass->GetType(), store, klassCb, jsVal))) {
175 if (coro->HasPendingException()) {
176 ctx->ForwardEtsException(coro);
177 }
178 ASSERT(ctx->SanityJSExceptionPending());
179 return nullptr;
180 }
181 }
182 return reinterpret_cast<ObjectHeader **>(restArgsArray.GetAddress());
183 }
184
185 // CC-OFFNXT(G.FMT.06-CPP, huge_depth) solid logic
PackRestParameters(EtsCoroutine * coro,InteropCtx * ctx,ProtoReader & protoReader,Span<napi_value> jsargv)186 [[maybe_unused]] static ObjectHeader **PackRestParameters(EtsCoroutine *coro, InteropCtx *ctx, ProtoReader &protoReader,
187 Span<napi_value> jsargv)
188 {
189 if (!protoReader.GetClass()->IsArrayClass()) {
190 ASSERT(protoReader.GetClass() == ctx->GetArrayClass());
191 const size_t numRestParams = jsargv.size();
192
193 EtsArrayObject<EtsObject> *objArr = EtsArrayObject<EtsObject>::Create(numRestParams);
194 VMHandle<EtsArrayObject<EtsObject>> restArgsArray(coro, objArr->GetCoreType());
195 for (uint32_t restArgIdx = 0; restArgIdx < numRestParams; ++restArgIdx) {
196 auto jsVal = jsargv[restArgIdx];
197 auto store = [restArgIdx, &restArgsArray](ObjectHeader *val) {
198 restArgsArray.GetPtr()->SetRef(restArgIdx, EtsObject::FromCoreType(val));
199 };
200 if (UNLIKELY(!ConvertRefArgToEts(ctx, ctx->GetObjectClass(), store, jsVal))) {
201 if (coro->HasPendingException()) {
202 ctx->ForwardEtsException(coro);
203 }
204 ASSERT(ctx->SanityJSExceptionPending());
205 return nullptr;
206 }
207 }
208 return reinterpret_cast<ObjectHeader **>(restArgsArray.GetAddress());
209 }
210
211 panda_file::Type restParamsItemType = protoReader.GetClass()->GetComponentType()->GetType();
212 switch (restParamsItemType.GetId()) {
213 case panda_file::Type::TypeId::U1:
214 return DoPackRestParameters<EtsBooleanArray>(coro, ctx, protoReader, jsargv);
215 case panda_file::Type::TypeId::I8:
216 return DoPackRestParameters<EtsByteArray>(coro, ctx, protoReader, jsargv);
217 case panda_file::Type::TypeId::I16:
218 return DoPackRestParameters<EtsShortArray>(coro, ctx, protoReader, jsargv);
219 case panda_file::Type::TypeId::U16:
220 return DoPackRestParameters<EtsCharArray>(coro, ctx, protoReader, jsargv);
221 case panda_file::Type::TypeId::I32:
222 return DoPackRestParameters<EtsIntArray>(coro, ctx, protoReader, jsargv);
223 case panda_file::Type::TypeId::I64:
224 return DoPackRestParameters<EtsLongArray>(coro, ctx, protoReader, jsargv);
225 case panda_file::Type::TypeId::F32:
226 return DoPackRestParameters<EtsFloatArray>(coro, ctx, protoReader, jsargv);
227 case panda_file::Type::TypeId::F64:
228 return DoPackRestParameters<EtsDoubleArray>(coro, ctx, protoReader, jsargv);
229 case panda_file::Type::TypeId::REFERENCE:
230 return DoPackRestParameters<EtsObjectArray>(coro, ctx, protoReader, jsargv);
231 default:
232 UNREACHABLE();
233 }
234 }
235
236 template <typename FRead>
ConvertRefArgToJS(InteropCtx * ctx,napi_value * resSlot,FRead & readVal)237 [[nodiscard]] static ALWAYS_INLINE inline bool ConvertRefArgToJS(InteropCtx *ctx, napi_value *resSlot, FRead &readVal)
238 {
239 ASSERT(ctx != nullptr);
240 auto env = ctx->GetJSEnv();
241 auto setResult = [resSlot](napi_value res) {
242 *resSlot = res;
243 return res != nullptr;
244 };
245 auto wrapRef = [&env, setResult](auto convTag, ObjectHeader *ref) -> bool {
246 using Convertor = typename decltype(convTag)::type; // conv_tag acts as lambda template parameter
247 using cpptype = typename Convertor::cpptype; // NOLINT(readability-identifier-naming)
248 cpptype value = std::remove_pointer_t<cpptype>::FromEtsObject(EtsObject::FromCoreType(ref));
249 return setResult(Convertor::Wrap(env, value));
250 };
251
252 ObjectHeader *ref = readVal(helpers::TypeIdentity<ObjectHeader *>());
253 if (UNLIKELY(ref == nullptr)) {
254 *resSlot = GetUndefined(env);
255 return true;
256 }
257 if (UNLIKELY(ref == ctx->GetNullValue()->GetCoreType())) {
258 *resSlot = GetNull(env);
259 return true;
260 }
261
262 auto klass = ref->template ClassAddr<Class>();
263 // start fastpath
264 if (klass == ctx->GetJSValueClass()) {
265 return wrapRef(helpers::TypeIdentity<JSConvertJSValue>(), ref);
266 }
267 if (klass == ctx->GetStringClass()) {
268 return wrapRef(helpers::TypeIdentity<JSConvertString>(), ref);
269 }
270 // start slowpath
271 VMHandle<ObjectHeader> handle(EtsCoroutine::GetCurrent(), ref);
272 auto refconv = JSRefConvertResolve(ctx, klass);
273 if (refconv == nullptr) {
274 return false;
275 }
276 return setResult(refconv->Wrap(ctx, EtsObject::FromCoreType(handle.GetPtr())));
277 }
278
279 template <typename FRead>
ConvertArgToJS(InteropCtx * ctx,ProtoReader & protoReader,napi_value * resSlot,FRead & readVal)280 [[nodiscard]] static ALWAYS_INLINE inline bool ConvertArgToJS(InteropCtx *ctx, ProtoReader &protoReader,
281 napi_value *resSlot, FRead &readVal)
282 {
283 auto env = ctx->GetJSEnv();
284
285 auto wrapPrim = [&env, &readVal, resSlot](auto convTag) -> bool {
286 using Convertor = typename decltype(convTag)::type; // convTag acts as lambda template parameter
287 using cpptype = typename Convertor::cpptype; // NOLINT(readability-identifier-naming)
288 napi_value res = Convertor::Wrap(env, readVal(helpers::TypeIdentity<cpptype>()));
289 *resSlot = res;
290 return res != nullptr;
291 };
292
293 switch (protoReader.GetType().GetId()) {
294 case panda_file::Type::TypeId::VOID: {
295 *resSlot = GetUndefined(env);
296 return true;
297 }
298 case panda_file::Type::TypeId::U1:
299 return wrapPrim(helpers::TypeIdentity<JSConvertU1>());
300 case panda_file::Type::TypeId::I8:
301 return wrapPrim(helpers::TypeIdentity<JSConvertI8>());
302 case panda_file::Type::TypeId::U8:
303 return wrapPrim(helpers::TypeIdentity<JSConvertU8>());
304 case panda_file::Type::TypeId::I16:
305 return wrapPrim(helpers::TypeIdentity<JSConvertI16>());
306 case panda_file::Type::TypeId::U16:
307 return wrapPrim(helpers::TypeIdentity<JSConvertU16>());
308 case panda_file::Type::TypeId::I32:
309 return wrapPrim(helpers::TypeIdentity<JSConvertI32>());
310 case panda_file::Type::TypeId::U32:
311 return wrapPrim(helpers::TypeIdentity<JSConvertU32>());
312 case panda_file::Type::TypeId::I64:
313 return wrapPrim(helpers::TypeIdentity<JSConvertI64>());
314 case panda_file::Type::TypeId::U64:
315 return wrapPrim(helpers::TypeIdentity<JSConvertU64>());
316 case panda_file::Type::TypeId::F32:
317 return wrapPrim(helpers::TypeIdentity<JSConvertF32>());
318 case panda_file::Type::TypeId::F64:
319 return wrapPrim(helpers::TypeIdentity<JSConvertF64>());
320 case panda_file::Type::TypeId::REFERENCE:
321 return ConvertRefArgToJS(ctx, resSlot, readVal);
322 default:
323 UNREACHABLE();
324 }
325 }
326
327 } // namespace ark::ets::interop::js
328
329 #endif // PANDA_PLUGINS_ETS_RUNTIME_INTEROP_JS_CALL_ARG_CONVERTORS_H
330