1 /**
2 * Copyright (c) 2021-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_JS_REFCONVERT_ARRAY_H_
17 #define PANDA_PLUGINS_ETS_RUNTIME_INTEROP_JS_JS_REFCONVERT_ARRAY_H_
18
19 #include "libpandabase/macros.h"
20 #include "plugins/ets/runtime/ets_class_linker_extension.h"
21 #include "plugins/ets/runtime/interop_js/interop_context.h"
22 #include "plugins/ets/runtime/interop_js/interop_common.h"
23 #include "plugins/ets/runtime/interop_js/js_refconvert.h"
24 #include "plugins/ets/runtime/interop_js/js_convert.h"
25 #include "runtime/mem/local_object_handle.h"
26 #include "plugins/ets/runtime/types/ets_object.h"
27
28 namespace ark::ets::interop::js {
29
30 // JSRefConvert adapter for builtin[] types
31 template <typename Conv>
32 class JSRefConvertBuiltinArray : public JSRefConvert {
33 public:
JSRefConvertBuiltinArray(Class * klass)34 explicit JSRefConvertBuiltinArray(Class *klass) : JSRefConvert(this), klass_(klass) {}
35
36 private:
37 using ElemCpptype = typename Conv::cpptype;
38
GetElem(VMHandle<coretypes::Array> arr,size_t idx)39 static ElemCpptype GetElem(VMHandle<coretypes::Array> arr, size_t idx)
40 {
41 if constexpr (Conv::IS_REFTYPE) {
42 auto elem = EtsObject::FromCoreType(arr->Get<ObjectHeader *>(idx));
43 return FromEtsObject<std::remove_pointer_t<ElemCpptype>>(elem);
44 } else {
45 return arr->Get<ElemCpptype>(idx);
46 }
47 }
48
SetElem(coretypes::Array * arr,size_t idx,ElemCpptype value)49 static void SetElem(coretypes::Array *arr, size_t idx, ElemCpptype value)
50 {
51 if constexpr (Conv::IS_REFTYPE) {
52 arr->Set(idx, AsEtsObject(value)->GetCoreType());
53 } else {
54 arr->Set(idx, value);
55 }
56 }
57
58 public:
WrapImpl(InteropCtx * ctx,EtsObject * obj)59 napi_value WrapImpl(InteropCtx *ctx, EtsObject *obj)
60 {
61 auto *coro = EtsCoroutine::GetCurrent();
62 auto env = ctx->GetJSEnv();
63 [[maybe_unused]] HandleScope<ObjectHeader *> hscope(coro);
64
65 VMHandle<coretypes::Array> etsArr(coro, coretypes::Array::Cast(obj->GetCoreType()));
66 auto len = etsArr->GetLength();
67
68 NapiEscapableScope jsHandleScope(env);
69 napi_value jsArr;
70 {
71 ScopedNativeCodeThread nativeScope(coro);
72 NAPI_CHECK_FATAL(napi_create_array_with_length(env, len, &jsArr));
73 }
74
75 for (size_t idx = 0; idx < len; ++idx) {
76 ElemCpptype etsElem = GetElem(etsArr, idx);
77 auto jsElem = Conv::WrapWithNullCheck(env, etsElem);
78 if (UNLIKELY(jsElem == nullptr)) {
79 return nullptr;
80 }
81 {
82 // NOTE(audovichenko): try to do not change thread state in each iteration.
83 ScopedNativeCodeThread s(coro);
84 napi_status rc = napi_set_element(env, jsArr, idx, jsElem);
85 if (UNLIKELY(NapiThrownGeneric(rc))) {
86 return nullptr;
87 }
88 }
89 }
90 jsHandleScope.Escape(jsArr);
91 return jsArr;
92 }
93
UnwrapImpl(InteropCtx * ctx,napi_value jsArr)94 EtsObject *UnwrapImpl(InteropCtx *ctx, napi_value jsArr)
95 {
96 auto coro = EtsCoroutine::GetCurrent();
97 auto env = ctx->GetJSEnv();
98 {
99 bool isArray;
100 NAPI_CHECK_FATAL(napi_is_array(env, jsArr, &isArray));
101 if (UNLIKELY(!isArray)) {
102 JSConvertTypeCheckFailed("array");
103 return nullptr;
104 }
105 }
106
107 uint32_t len;
108 napi_status rc = napi_get_array_length(env, jsArr, &len);
109 if (UNLIKELY(NapiThrownGeneric(rc))) {
110 return nullptr;
111 }
112
113 // NOTE(vpukhov): elide handles for primitive arrays
114 LocalObjectHandle<coretypes::Array> etsArr(coro, coretypes::Array::Create(klass_, len));
115 NapiScope jsHandleScope(env);
116
117 for (size_t idx = 0; idx < len; ++idx) {
118 napi_value jsElem;
119 rc = napi_get_element(env, jsArr, idx, &jsElem);
120 if (UNLIKELY(NapiThrownGeneric(rc))) {
121 return nullptr;
122 }
123 auto res = Conv::UnwrapWithNullCheck(ctx, env, jsElem);
124 if (UNLIKELY(!res)) {
125 return nullptr;
126 }
127 SetElem(etsArr.GetPtr(), idx, res.value());
128 }
129
130 return EtsObject::FromCoreType(etsArr.GetPtr());
131 }
132
133 private:
134 Class *klass_ {};
135 };
136
137 template <ClassRoot CLASS_ROOT, typename Conv>
RegisterBuiltinArrayConvertor(JSRefConvertCache * cache,EtsClassLinkerExtension * ext)138 static inline void RegisterBuiltinArrayConvertor(JSRefConvertCache *cache, EtsClassLinkerExtension *ext)
139 {
140 auto aklass = ext->GetClassRoot(CLASS_ROOT);
141 cache->Insert(aklass, std::make_unique<JSRefConvertBuiltinArray<Conv>>(aklass));
142 }
143
144 // JSRefConvert adapter for reference[] types
145 class JSRefConvertReftypeArray : public JSRefConvert {
146 public:
JSRefConvertReftypeArray(Class * klass)147 explicit JSRefConvertReftypeArray(Class *klass) : JSRefConvert(this), klass_(klass) {}
148
WrapImpl(InteropCtx * ctx,EtsObject * obj)149 napi_value WrapImpl(InteropCtx *ctx, EtsObject *obj)
150 {
151 auto coro = EtsCoroutine::GetCurrent();
152 auto env = ctx->GetJSEnv();
153 [[maybe_unused]] HandleScope<ObjectHeader *> hscope(coro);
154
155 VMHandle<coretypes::Array> etsArr(coro, obj->GetCoreType());
156 ASSERT(etsArr.GetPtr() != nullptr);
157 auto len = etsArr->GetLength();
158
159 NapiEscapableScope jsHandleScope(env);
160 napi_value jsArr;
161 {
162 ScopedNativeCodeThread nativeScope(coro);
163 NAPI_CHECK_FATAL(napi_create_array_with_length(env, len, &jsArr));
164 }
165
166 for (size_t idx = 0; idx < len; ++idx) {
167 EtsObject *etsElem = EtsObject::FromCoreType(etsArr->Get<ObjectHeader *>(idx));
168 napi_value jsElem;
169 if (LIKELY(etsElem != nullptr)) {
170 JSRefConvert *elemConv = GetElemConvertor(ctx, etsElem->GetClass());
171 if (UNLIKELY(elemConv == nullptr)) {
172 return nullptr;
173 }
174 // Need to read element pointer again, since it could have been moved by GC
175 etsElem = EtsObject::FromCoreType(etsArr->Get<ObjectHeader *>(idx));
176 ASSERT(etsElem != nullptr);
177 jsElem = elemConv->Wrap(ctx, etsElem);
178 if (UNLIKELY(jsElem == nullptr)) {
179 return nullptr;
180 }
181 } else {
182 jsElem = GetUndefined(env);
183 }
184 {
185 ScopedNativeCodeThread s(coro);
186 napi_status rc = napi_set_element(env, jsArr, idx, jsElem);
187 if (UNLIKELY(NapiThrownGeneric(rc))) {
188 return nullptr;
189 }
190 }
191 }
192 jsHandleScope.Escape(jsArr);
193 return jsArr;
194 }
195
UnwrapNonUndefined(InteropCtx * ctx,napi_value jsElem)196 EtsObject *UnwrapNonUndefined(InteropCtx *ctx, napi_value jsElem)
197 {
198 if (UNLIKELY(baseElemConv_ == nullptr)) {
199 baseElemConv_ = JSRefConvertResolve(ctx, klass_->GetComponentType());
200 if (UNLIKELY(baseElemConv_ == nullptr)) {
201 return nullptr;
202 }
203 }
204 EtsObject *etsElem = baseElemConv_->Unwrap(ctx, jsElem);
205 if (UNLIKELY(etsElem == nullptr)) {
206 return nullptr;
207 }
208 return etsElem;
209 }
210
UnwrapImpl(InteropCtx * ctx,napi_value jsArr)211 EtsObject *UnwrapImpl(InteropCtx *ctx, napi_value jsArr)
212 {
213 auto coro = EtsCoroutine::GetCurrent();
214 auto env = ctx->GetJSEnv();
215 {
216 bool isArray;
217 NAPI_CHECK_FATAL(napi_is_array(env, jsArr, &isArray));
218 if (UNLIKELY(!isArray)) {
219 JSConvertTypeCheckFailed("array");
220 return nullptr;
221 }
222 }
223
224 uint32_t len;
225 napi_status rc = napi_get_array_length(env, jsArr, &len);
226 if (UNLIKELY(NapiThrownGeneric(rc))) {
227 return nullptr;
228 }
229
230 LocalObjectHandle<coretypes::Array> etsArr(coro, coretypes::Array::Create(klass_, len));
231 NapiScope jsHandleScope(env);
232
233 for (size_t idx = 0; idx < len; ++idx) {
234 napi_value jsElem;
235 rc = napi_get_element(env, jsArr, idx, &jsElem);
236 if (UNLIKELY(NapiThrownGeneric(rc))) {
237 return nullptr;
238 }
239 if (LIKELY(!IsNullOrUndefined(env, jsElem))) {
240 auto *etsElem = UnwrapNonUndefined(ctx, jsElem);
241 if (UNLIKELY(etsElem == nullptr)) {
242 return nullptr;
243 }
244 etsArr->Set(idx, etsElem->GetCoreType());
245 }
246 }
247
248 return EtsObject::FromCoreType(etsArr.GetPtr());
249 }
250
251 private:
252 static constexpr auto ELEM_SIZE = ClassHelper::OBJECT_POINTER_SIZE;
253
GetElemConvertor(InteropCtx * ctx,EtsClass * elemEtsKlass)254 JSRefConvert *GetElemConvertor(InteropCtx *ctx, EtsClass *elemEtsKlass)
255 {
256 Class *elemKlass = elemEtsKlass->GetRuntimeClass();
257 if (elemKlass != klass_->GetComponentType()) {
258 return JSRefConvertResolve(ctx, elemKlass);
259 }
260 if (LIKELY(baseElemConv_ != nullptr)) {
261 return baseElemConv_;
262 }
263 return baseElemConv_ = JSRefConvertResolve(ctx, klass_->GetComponentType());
264 }
265
266 Class *klass_ {};
267 JSRefConvert *baseElemConv_ {};
268 };
269
270 } // namespace ark::ets::interop::js
271
272 #endif // !PANDA_PLUGINS_ETS_RUNTIME_INTEROP_JS_JS_REFCONVERT_ARRAY_H_
273