• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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_CONVERT_H
17 #define PANDA_PLUGINS_ETS_RUNTIME_INTEROP_JS_JS_CONVERT_H
18 
19 #include "js_convert_base.h"
20 #include "js_convert_stdlib.h"
21 
22 namespace ark::ets::interop::js {
23 
24 template <typename Cpptype>
25 struct JSConvertNumeric : public JSConvertBase<JSConvertNumeric<Cpptype>, Cpptype> {
26     static constexpr const char *TYPE_NAME = "number";
27 
28     template <typename P = Cpptype>
WrapImplJSConvertNumeric29     static std::enable_if_t<std::is_integral_v<P>, napi_value> WrapImpl(napi_env env, Cpptype etsVal)
30     {
31         napi_value jsVal;
32         if constexpr (sizeof(Cpptype) >= sizeof(int32_t)) {
33             NAPI_CHECK_FATAL(napi_create_int64(env, etsVal, &jsVal));
34         } else if constexpr (std::is_signed_v<Cpptype>) {
35             NAPI_CHECK_FATAL(napi_create_int32(env, etsVal, &jsVal));
36         } else {
37             NAPI_CHECK_FATAL(napi_create_uint32(env, etsVal, &jsVal));
38         }
39         return jsVal;
40     }
41 
42     template <typename P = Cpptype>
UnwrapImplJSConvertNumeric43     static std::enable_if_t<std::is_integral_v<P>, std::optional<Cpptype>> UnwrapImpl([[maybe_unused]] InteropCtx *ctx,
44                                                                                       napi_env env, napi_value jsVal)
45     {
46         napi_valuetype valueType = GetValueType(env, jsVal);
47         napi_value result = jsVal;
48         if (valueType == napi_object && !GetValueByValueOf(env, jsVal, CONSTRUCTOR_NAME_NUMBER, &result)) {
49             JSConvertNumeric::TypeCheckFailed();
50             return {};
51         }
52         if (UNLIKELY(GetValueType(env, result) != napi_number)) {
53             JSConvertNumeric::TypeCheckFailed();
54             return {};
55         }
56         Cpptype etsVal;
57         if constexpr (sizeof(Cpptype) >= sizeof(int32_t)) {
58             int64_t val;
59             NAPI_CHECK_FATAL(napi_get_value_int64(env, result, &val));
60             etsVal = static_cast<Cpptype>(val);
61         } else if constexpr (std::is_signed_v<Cpptype>) {
62             int32_t val;
63             NAPI_CHECK_FATAL(napi_get_value_int32(env, result, &val));
64             etsVal = static_cast<Cpptype>(val);
65         } else {
66             uint32_t val;
67             NAPI_CHECK_FATAL(napi_get_value_uint32(env, result, &val));
68             etsVal = static_cast<Cpptype>(val);
69         }
70         return etsVal;
71     }
72 
73     template <typename P = Cpptype>
WrapImplJSConvertNumeric74     static std::enable_if_t<std::is_floating_point_v<P>, napi_value> WrapImpl(napi_env env, Cpptype etsVal)
75     {
76         napi_value jsVal;
77         NAPI_CHECK_FATAL(napi_create_double(env, etsVal, &jsVal));
78         return jsVal;
79     }
80 
81     template <typename P = Cpptype>
UnwrapImplJSConvertNumeric82     static std::enable_if_t<std::is_floating_point_v<P>, std::optional<Cpptype>> UnwrapImpl(
83         [[maybe_unused]] InteropCtx *ctx, napi_env env, napi_value jsVal)
84     {
85         napi_valuetype valueType = GetValueType(env, jsVal);
86         napi_value result = jsVal;
87         if (valueType == napi_object && !GetValueByValueOf(env, jsVal, CONSTRUCTOR_NAME_NUMBER, &result)) {
88             JSConvertNumeric::TypeCheckFailed();
89             return {};
90         }
91         if (UNLIKELY(GetValueType(env, result) != napi_number)) {
92             JSConvertNumeric::TypeCheckFailed();
93             return {};
94         }
95         double val;
96         NAPI_CHECK_FATAL(napi_get_value_double(env, result, &val));
97         return val;
98     }
99 };
100 
101 using JSConvertI8 = JSConvertNumeric<int8_t>;
102 using JSConvertU8 = JSConvertNumeric<uint8_t>;
103 using JSConvertI16 = JSConvertNumeric<int16_t>;
104 using JSConvertI32 = JSConvertNumeric<int32_t>;
105 using JSConvertU32 = JSConvertNumeric<uint32_t>;
106 using JSConvertI64 = JSConvertNumeric<int64_t>;
107 using JSConvertU64 = JSConvertNumeric<uint64_t>;
108 using JSConvertF32 = JSConvertNumeric<float>;
109 using JSConvertF64 = JSConvertNumeric<double>;
110 
111 JSCONVERT_DEFINE_TYPE(U1, bool);
JSCONVERT_WRAP(U1)112 JSCONVERT_WRAP(U1)
113 {
114     napi_value jsVal;
115     NAPI_CHECK_FATAL(napi_get_boolean(env, static_cast<bool>(etsVal), &jsVal));
116     return jsVal;
117 }
JSCONVERT_UNWRAP(U1)118 JSCONVERT_UNWRAP(U1)
119 {
120     if (IsUndefined(env, jsVal)) {
121         TypeCheckFailed();
122         return {};
123     }
124     auto objVal = JSConvertStdlibBoolean::UnwrapWithNullCheck(ctx, env, jsVal);
125     if (!objVal || (objVal.has_value() && objVal.value() == nullptr)) {
126         return {};
127     }
128     if (objVal.has_value()) {
129         return EtsBoxPrimitive<EtsBoolean>::FromCoreType(objVal.value())->GetValue();
130     }
131     return {};
132 }
133 
134 JSCONVERT_DEFINE_TYPE(U16, char16_t);
JSCONVERT_WRAP(U16)135 JSCONVERT_WRAP(U16)
136 {
137     napi_value jsVal;
138     NAPI_CHECK_FATAL(napi_create_string_utf16(env, &etsVal, 1, &jsVal));
139     return jsVal;
140 }
JSCONVERT_UNWRAP(U16)141 JSCONVERT_UNWRAP(U16)
142 {
143     if (IsUndefined(env, jsVal)) {
144         TypeCheckFailed();
145         return {};
146     }
147     auto objVal = JSConvertStdlibChar::UnwrapWithNullCheck(ctx, env, jsVal);
148     if (!objVal || (objVal.has_value() && objVal.value() == nullptr)) {
149         return {};
150     }
151     if (objVal.has_value()) {
152         return EtsBoxPrimitive<EtsChar>::FromCoreType(objVal.value())->GetValue();
153     }
154     return {};
155 }
156 
157 JSCONVERT_DEFINE_TYPE(String, EtsString *);
JSCONVERT_WRAP(String)158 JSCONVERT_WRAP(String)
159 {
160     napi_value jsVal;
161     if (UNLIKELY(etsVal->IsUtf16())) {
162         auto str = reinterpret_cast<char16_t *>(etsVal->GetDataUtf16());
163         NAPI_CHECK_FATAL(napi_create_string_utf16(env, str, etsVal->GetUtf16Length(), &jsVal));
164     } else {
165         auto str = utf::Mutf8AsCString(etsVal->GetDataMUtf8());
166         // -1 for NULL terminated Mutf8 string!!!
167         NAPI_CHECK_FATAL(napi_create_string_utf8(env, str, etsVal->GetMUtf8Length() - 1, &jsVal));
168     }
169     return jsVal;
170 }
JSCONVERT_UNWRAP(String)171 JSCONVERT_UNWRAP(String)
172 {
173     napi_value result = jsVal;
174     napi_valuetype valueType = GetValueType(env, jsVal);
175     if (valueType == napi_object && !GetValueByValueOf(env, jsVal, CONSTRUCTOR_NAME_STRING, &result)) {
176         TypeCheckFailed();
177         return {};
178     }
179     if (UNLIKELY(GetValueType(env, result) != napi_string)) {
180         TypeCheckFailed();
181         return {};
182     }
183     std::string value = GetString(env, result);
184     return EtsString::CreateFromUtf8(value.data(), value.length());
185 }
186 
187 JSCONVERT_DEFINE_TYPE(BigInt, EtsBigInt *);
JSCONVERT_WRAP(BigInt)188 JSCONVERT_WRAP(BigInt)
189 {
190     auto size = etsVal->GetBytes()->GetLength();
191     auto data = etsVal->GetBytes();
192 
193     std::vector<uint32_t> etsArray;
194     etsArray.reserve(size);
195     for (size_t i = 0; i < size; ++i) {
196         etsArray.emplace_back(static_cast<uint32_t>(data->Get(i)));
197     }
198 
199     SmallVector<uint64_t, 4U> jsArray;
200     if (size > 0) {
201         jsArray = ConvertBigIntArrayFromEtsToJs(etsArray);
202     } else {
203         jsArray = {0};
204     }
205 
206     napi_value jsVal;
207     int sign = etsVal->GetSign() < 0 ? 1 : 0;
208     NAPI_CHECK_FATAL(napi_create_bigint_words(env, sign, jsArray.size(), jsArray.data(), &jsVal));
209 
210     return jsVal;
211 }
JSCONVERT_UNWRAP(BigInt)212 JSCONVERT_UNWRAP(BigInt)
213 {
214     if (UNLIKELY(GetValueType(env, jsVal) != napi_bigint)) {
215         TypeCheckFailed();
216         return {};
217     }
218 
219     auto [words, signBit] = GetBigInt(env, jsVal);
220     std::vector<EtsInt> array = ConvertBigIntArrayFromJsToEts(words);
221 
222     auto *etsIntArray = EtsIntArray::Create(array.size());
223     ASSERT(etsIntArray != nullptr);
224     for (uint32_t i = 0; i < array.size(); ++i) {
225         etsIntArray->Set(i, array[i]);
226     }
227 
228     auto bigintKlass = EtsClass::FromRuntimeClass(ctx->GetBigIntClass());
229     auto bigInt = EtsBigInt::FromEtsObject(EtsObject::Create(bigintKlass));
230     bigInt->SetFieldObject(EtsBigInt::GetBytesOffset(), reinterpret_cast<EtsObject *>(etsIntArray));
231     bigInt->SetFieldPrimitive(EtsBigInt::GetSignOffset(), array.empty() ? 0 : signBit == 0 ? 1 : -1);
232 
233     return bigInt;
234 }
235 
236 JSCONVERT_DEFINE_TYPE(JSValue, JSValue *);
JSCONVERT_WRAP(JSValue)237 JSCONVERT_WRAP(JSValue)
238 {
239     return etsVal->GetNapiValue(env);
240 }
JSCONVERT_UNWRAP(JSValue)241 JSCONVERT_UNWRAP(JSValue)
242 {
243     return JSValue::Create(EtsCoroutine::GetCurrent(), ctx, jsVal);
244 }
245 
246 JSCONVERT_DEFINE_TYPE(EtsObject, EtsObject *);
JSCONVERT_WRAP(EtsObject)247 JSCONVERT_WRAP(EtsObject)
248 {
249     InteropFatal("Wrap of EtsObject should be done with relevant converter");
250     UNREACHABLE();
251 }
252 
JSCONVERT_UNWRAP(EtsObject)253 JSCONVERT_UNWRAP(EtsObject)
254 {
255     auto objectConverter = ctx->GetEtsClassWrappersCache()->Lookup(PlatformTypes()->coreObject);
256     return objectConverter->Unwrap(ctx, jsVal);
257 }
258 
259 // ESError convertors are supposed to box JSValue objects, do not treat them in any other way
260 JSCONVERT_DEFINE_TYPE(ESError, EtsObject *);
JSCONVERT_WRAP(ESError)261 JSCONVERT_WRAP(ESError)
262 {
263     auto coro = EtsCoroutine::GetCurrent();
264     auto ctx = InteropCtx::Current(coro);
265     if (ctx == nullptr) {
266         ThrowNoInteropContextException();
267         return {};
268     }
269 
270     auto klass = etsVal->GetClass();
271     INTEROP_FATAL_IF(klass->GetRuntimeClass() != ctx->GetESErrorClass());
272 
273     auto method = klass->GetInstanceMethod("getJsError", nullptr);
274     ASSERT(method != nullptr);
275     std::array args = {Value(etsVal->GetCoreType())};
276     auto val = JSValue::FromCoreType(method->GetPandaMethod()->Invoke(coro, args.data()).GetAs<ObjectHeader *>());
277     INTEROP_FATAL_IF(val == nullptr);
278     return val->GetNapiValue(env);
279 }
JSCONVERT_UNWRAP(ESError)280 JSCONVERT_UNWRAP(ESError)
281 {
282     auto coro = EtsCoroutine::GetCurrent();
283     JSValue *value = nullptr;
284     value = JSValue::Create(coro, ctx, jsVal);
285     if (UNLIKELY(value == nullptr)) {
286         return {};
287     }
288     auto res = ctx->CreateETSCoreESError(coro, value);
289     if (UNLIKELY(res == nullptr)) {
290         return {};
291     }
292     return res;
293 }
294 
295 JSCONVERT_DEFINE_TYPE(Promise, EtsPromise *);
296 
JSCONVERT_WRAP(Promise)297 JSCONVERT_WRAP(Promise)
298 {
299     auto *coro = EtsCoroutine::GetCurrent();
300     auto ctx = InteropCtx::Current(coro);
301     if (ctx == nullptr) {
302         ThrowNoInteropContextException();
303         return {};
304     }
305     ets_proxy::SharedReferenceStorage *storage = ctx->GetSharedRefStorage();
306     // SharedReferenceStorage uses object's MarkWord to store interop hash.
307     // Also runtime may lock a Promise object (this operation also requires MarkWord modification).
308     // Interop hash and locks cannot co-exist. That is why a separate object (EtsPromiseRef) is used for
309     // mapping between js and ets Promise objects.
310     // When a ets Promise object goes to JS we should get the corresponding EtsPromiseRef object.
311     // So the ets Promise object should know about EtsPromiseRef which is stored in 'interopObject' field.
312     EtsObject *interopObj = etsVal->GetInteropObject(coro);
313     if (interopObj != nullptr && storage->HasReference(interopObj, env)) {
314         return storage->GetJsObject(interopObj, env);
315     }
316 
317     [[maybe_unused]] EtsHandleScope s(coro);
318     EtsHandle<EtsPromise> hpromise(coro, etsVal);
319     ASSERT(hpromise.GetPtr() != nullptr);
320     napi_deferred deferred;
321     napi_value jsPromise;
322     NAPI_CHECK_FATAL(napi_create_promise(env, &deferred, &jsPromise));
323 
324     hpromise->Lock();
325     // NOTE(alimovilya, #23064) This if should be removed. Only else branch should remain.
326     if (!hpromise->IsPending() && !hpromise->IsLinked()) {  // it will never get PENDING again
327         EtsHandle<EtsObject> value(coro, hpromise->GetValue(coro));
328         napi_value completionValue;
329         if (value.GetPtr() == nullptr) {
330             completionValue = GetUndefined(env);
331         } else {
332             auto refconv = JSRefConvertResolve(ctx, value->GetClass()->GetRuntimeClass());
333             completionValue = refconv->Wrap(ctx, value.GetPtr());
334         }
335         if (hpromise->IsResolved()) {
336             NAPI_CHECK_FATAL(napi_resolve_deferred(env, deferred, completionValue));
337         } else {
338             NAPI_CHECK_FATAL(napi_reject_deferred(env, deferred, completionValue));
339         }
340     } else {
341         // connect->Invoke calls EtsPromiseSubmitCallback that acquires the mutex and checks the state again
342         hpromise->Unlock();
343         ASSERT_MANAGED_CODE();
344         Method *connect = ctx->GetPromiseInteropConnectMethod();
345         std::array<Value, 2U> args = {Value(hpromise.GetPtr()), Value(reinterpret_cast<int64_t>(deferred))};
346         connect->Invoke(coro, args.data());
347         hpromise->Lock();
348     }
349     EtsPromiseRef *ref = EtsPromiseRef::Create(coro);
350     ref->SetTarget(coro, hpromise->AsObject());
351     hpromise->SetInteropObject(coro, ref);
352     [[maybe_unused]] auto *sharedRef = storage->CreateETSObjectRef(ctx, ref, jsPromise);
353     ASSERT(sharedRef != nullptr);
354     hpromise->Unlock();
355     return jsPromise;
356 }
357 
JSCONVERT_UNWRAP(Promise)358 JSCONVERT_UNWRAP(Promise)
359 {
360 #ifndef NDEBUG
361     bool isPromise = false;
362     NAPI_CHECK_FATAL(napi_is_promise(env, jsVal, &isPromise));
363     ASSERT(isPromise);
364 #endif
365     auto coro = EtsCoroutine::GetCurrent();
366     ets_proxy::SharedReferenceStorage *storage = ctx->GetSharedRefStorage();
367     ets_proxy::SharedReference *sharedRef = storage->GetReference(env, jsVal);
368     if (sharedRef != nullptr) {
369         auto *ref = reinterpret_cast<EtsPromiseRef *>(sharedRef->GetEtsObject());
370         ASSERT(ref->GetTarget(coro) != nullptr);
371         return EtsPromise::FromEtsObject(ref->GetTarget(coro));
372     }
373 
374     [[maybe_unused]] EtsHandleScope s(coro);
375     auto *promise = EtsPromise::Create(coro);
376     EtsHandle<EtsPromise> hpromise(coro, promise);
377     ASSERT(hpromise.GetPtr() != nullptr);
378     EtsHandle<EtsPromiseRef> href(coro, EtsPromiseRef::Create(coro));
379     href->SetTarget(coro, hpromise->AsObject());
380     hpromise->SetInteropObject(coro, href.GetPtr());
381     storage->CreateJSObjectRef(ctx, href.GetPtr(), jsVal);
382     hpromise->SetLinkedPromise(coro, href->AsObject());
383     EtsPromise::CreateLink(hpromise->GetLinkedPromise(coro), hpromise.GetPtr());
384     return hpromise.GetPtr();
385 }
386 
387 JSCONVERT_DEFINE_TYPE(EtsNull, EtsObject *);
JSCONVERT_WRAP(EtsNull)388 JSCONVERT_WRAP(EtsNull)
389 {
390     return GetNull(env);
391 }
392 
JSCONVERT_UNWRAP(EtsNull)393 JSCONVERT_UNWRAP(EtsNull)
394 {
395     if (UNLIKELY(!IsNull(env, jsVal))) {
396         TypeCheckFailed();
397         return {};
398     }
399     return ctx->GetNullValue();
400 }
401 
402 #undef JSCONVERT_DEFINE_TYPE
403 #undef JSCONVERT_WRAP
404 #undef JSCONVERT_UNWRAP
405 
406 template <typename T>
JSValueGetByName(InteropCtx * ctx,JSValue * jsvalue,const char * name)407 static ALWAYS_INLINE inline std::optional<typename T::cpptype> JSValueGetByName(InteropCtx *ctx, JSValue *jsvalue,
408                                                                                 const char *name)
409 {
410     auto env = ctx->GetJSEnv();
411     napi_value jsVal = jsvalue->GetNapiValue(env);
412     napi_status jsStatus;
413     auto coro = EtsCoroutine::GetCurrent();
414     {
415         ScopedNativeCodeThread nativeScope(coro);
416         jsStatus = napi_get_named_property(env, jsVal, name, &jsVal);
417     }
418     if (jsStatus != napi_ok) {
419         return {};
420     }
421     return T::UnwrapWithNullCheck(ctx, env, jsVal);
422 }
423 
424 template <typename T>
JSValueSetByName(InteropCtx * ctx,JSValue * jsvalue,const char * name,typename T::cpptype etsPropVal)425 ALWAYS_INLINE inline void JSValueSetByName(InteropCtx *ctx, JSValue *jsvalue, const char *name,
426                                            typename T::cpptype etsPropVal)
427 {
428     auto env = ctx->GetJSEnv();
429     napi_value jsVal = jsvalue->GetNapiValue(env);
430     napi_value jsPropVal = T::WrapWithNullCheck(env, etsPropVal);
431     if (UNLIKELY(jsPropVal == nullptr)) {
432         return;
433     }
434     auto coro = EtsCoroutine::GetCurrent();
435     napi_status jsStatus;
436     {
437         ScopedNativeCodeThread nativeScope(coro);
438         jsStatus = napi_set_named_property(env, jsVal, name, jsPropVal);
439     }
440     if (jsStatus != napi_ok) {
441         ctx->ForwardJSException(coro);
442     }
443 }
444 
445 }  // namespace ark::ets::interop::js
446 
447 #endif  // !PANDA_PLUGINS_ETS_RUNTIME_INTEROP_JS_JS_CONVERT_H
448