• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (c) 2023-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_JSVALUE_H_
17 #define PANDA_PLUGINS_ETS_RUNTIME_INTEROP_JS_JSVALUE_H_
18 
19 #include "plugins/ets/runtime/ets_coroutine.h"
20 #include "plugins/ets/runtime/interop_js/interop_common.h"
21 #include "plugins/ets/runtime/interop_js/interop_context.h"
22 #include "plugins/ets/runtime/interop_js/ets_proxy/shared_reference.h"
23 #include "plugins/ets/runtime/types/ets_object.h"
24 #include "runtime/include/coretypes/class.h"
25 #include "utils/small_vector.h"
26 #include <node_api.h>
27 
28 namespace ark::ets::interop::js {
29 
30 namespace testing {
31 class JSValueOffsets;
32 class ESValueOffsets;
33 }  // namespace testing
34 
35 struct JSValueMemberOffsets;
36 
37 class JSValue : private EtsObject {
38 public:
FromEtsObject(EtsObject * etsObject)39     static JSValue *FromEtsObject(EtsObject *etsObject)
40     {
41         ASSERT(etsObject->GetClass() == EtsClass::FromRuntimeClass(InteropCtx::Current()->GetJSValueClass()));
42         return static_cast<JSValue *>(etsObject);
43     }
44 
FromCoreType(ObjectHeader * object)45     static JSValue *FromCoreType(ObjectHeader *object)
46     {
47         return FromEtsObject(EtsObject::FromCoreType(object));
48     }
49 
AsObject()50     EtsObject *AsObject()
51     {
52         return reinterpret_cast<EtsObject *>(this);
53     }
54 
AsObject()55     const EtsObject *AsObject() const
56     {
57         return reinterpret_cast<const EtsObject *>(this);
58     }
59 
GetCoreType()60     ObjectHeader *GetCoreType() const
61     {
62         return EtsObject::GetCoreType();
63     }
64 
GetType()65     napi_valuetype GetType() const
66     {
67         return bit_cast<napi_valuetype>(type_);
68     }
69 
70     static JSValue *Create(EtsCoroutine *coro, InteropCtx *ctx, napi_value nvalue);
71 
CreateUndefined(EtsCoroutine * coro,InteropCtx * ctx)72     static JSValue *CreateUndefined(EtsCoroutine *coro, InteropCtx *ctx)
73     {
74         return AllocUndefined(coro, ctx);
75     }
76 
CreateNull(EtsCoroutine * coro,InteropCtx * ctx)77     static JSValue *CreateNull(EtsCoroutine *coro, InteropCtx *ctx)
78     {
79         auto jsvalue = AllocUndefined(coro, ctx);
80         if (UNLIKELY(jsvalue == nullptr)) {
81             return nullptr;
82         }
83         jsvalue->SetNull();
84         return jsvalue;
85     }
86 
CreateBoolean(EtsCoroutine * coro,InteropCtx * ctx,bool value)87     static JSValue *CreateBoolean(EtsCoroutine *coro, InteropCtx *ctx, bool value)
88     {
89         auto jsvalue = AllocUndefined(coro, ctx);
90         if (UNLIKELY(jsvalue == nullptr)) {
91             return nullptr;
92         }
93         jsvalue->SetBoolean(value);
94         return jsvalue;
95     }
96 
CreateNumber(EtsCoroutine * coro,InteropCtx * ctx,double value)97     static JSValue *CreateNumber(EtsCoroutine *coro, InteropCtx *ctx, double value)
98     {
99         auto jsvalue = AllocUndefined(coro, ctx);
100         if (UNLIKELY(jsvalue == nullptr)) {
101             return nullptr;
102         }
103         jsvalue->SetNumber(value);
104         return jsvalue;
105     }
106 
CreateString(EtsCoroutine * coro,InteropCtx * ctx,std::string && value)107     static JSValue *CreateString(EtsCoroutine *coro, InteropCtx *ctx, std::string &&value)
108     {
109         auto jsvalue = AllocUndefined(coro, ctx);
110         if (UNLIKELY(jsvalue == nullptr)) {
111             return nullptr;
112         }
113         jsvalue->SetString(ctx->GetStringStor()->Get(std::move(value)));
114         return JSValue::AttachFinalizer(EtsCoroutine::GetCurrent(), jsvalue);
115     }
116 
CreateRefValue(EtsCoroutine * coro,InteropCtx * ctx,napi_value value,napi_valuetype type)117     static JSValue *CreateRefValue(EtsCoroutine *coro, InteropCtx *ctx, napi_value value, napi_valuetype type)
118     {
119         auto jsvalue = AllocUndefined(coro, ctx);
120         if (UNLIKELY(jsvalue == nullptr)) {
121             return nullptr;
122         }
123         jsvalue->SetRefValue(ctx, value, type);
124         return jsvalue;
125     }
126 
127     // prefer JSConvertJSValue::WrapWithNullCheck
128     napi_value GetNapiValue(napi_env env);
129 
GetBoolean()130     bool GetBoolean() const
131     {
132         ASSERT(GetType() == napi_boolean);
133         return GetData<bool>();
134     }
135 
GetNumber()136     double GetNumber() const
137     {
138         ASSERT(GetType() == napi_number);
139         return GetData<double>();
140     }
141 
GetBigInt()142     std::pair<SmallVector<uint64_t, 4U>, int> *GetBigInt() const
143     {
144         ASSERT(GetType() == napi_bigint);
145         return GetData<std::pair<SmallVector<uint64_t, 4U>, int> *>();
146     }
147 
GetString()148     JSValueStringStorage::CachedEntry GetString() const
149     {
150         ASSERT(GetType() == napi_string);
151         return JSValueStringStorage::CachedEntry(GetData<std::string const *>());
152     }
153 
GetRefValue(napi_env env)154     napi_value GetRefValue(napi_env env)
155     {
156         napi_value value;
157         auto napiRef = GetNapiRef(env);
158         NAPI_ASSERT_OK(napi_get_reference_value(env, napiRef, &value));
159         return value;
160     }
161 
162     static void FinalizeETSWeak(InteropCtx *ctx, EtsObject *cbarg);
163 
GetTypeOffset()164     static constexpr uint32_t GetTypeOffset()
165     {
166         return MEMBER_OFFSET(JSValue, type_);
167     }
168 
169     JSValue() = delete;
170 
171 private:
172     static JSValue *CreateByType(InteropCtx *ctx, napi_env env, napi_value nvalue, napi_valuetype jsType,
173                                  JSValue *jsvalue);
174 
IsRefType(napi_valuetype type)175     static constexpr bool IsRefType(napi_valuetype type)
176     {
177         return type == napi_object || type == napi_function || type == napi_symbol;
178     }
179 
IsFinalizableType(napi_valuetype type)180     static constexpr bool IsFinalizableType(napi_valuetype type)
181     {
182         return type == napi_string || type == napi_bigint || IsRefType(type);
183     }
184 
SetType(napi_valuetype type)185     void SetType(napi_valuetype type)
186     {
187         type_ = bit_cast<decltype(type_)>(type);
188     }
189 
190     template <typename T>
SetData(T val)191     std::enable_if_t<std::is_trivially_copyable_v<T>> SetData(T val)
192     {
193         static_assert(sizeof(data_) >= sizeof(T));
194         std::copy_n(reinterpret_cast<uint8_t *>(&val), sizeof(T), reinterpret_cast<uint8_t *>(&data_));
195     }
196 
197     template <typename T>
GetData()198     std::enable_if_t<std::is_trivially_copyable_v<T> && std::is_trivially_constructible_v<T>, T> GetData() const
199     {
200         static_assert(sizeof(data_) >= sizeof(T));
201         T val;
202         std::copy_n(reinterpret_cast<const uint8_t *>(&data_), sizeof(T), reinterpret_cast<uint8_t *>(&val));
203         return val;
204     }
205 
AllocUndefined(EtsCoroutine * coro,InteropCtx * ctx)206     static JSValue *AllocUndefined(EtsCoroutine *coro, InteropCtx *ctx)
207     {
208         JSValue *jsValue;
209         {
210             auto obj = ObjectHeader::Create(coro, ctx->GetJSValueClass());
211             if (UNLIKELY(!obj)) {
212                 return nullptr;
213             }
214             jsValue = FromCoreType(obj);
215         }
216         static_assert(napi_undefined == 0);  // zero-initialized
217         ASSERT(jsValue->GetType() == napi_undefined);
218         return jsValue;
219     }
220 
221     // Returns moved jsValue
222     [[nodiscard]] static JSValue *AttachFinalizer(EtsCoroutine *coro, JSValue *jsValue);
223 
GetNapiRef(napi_env env)224     napi_ref GetNapiRef(napi_env env) const
225     {
226         ASSERT(IsRefType(GetType()));
227 
228         ets_proxy::SharedReference *sharedRef = GetData<ets_proxy::SharedReference *>();
229 
230         // Interop ctx check:
231         // check if the ctx is the same as the one that created the reference
232         if (sharedRef->GetCtx()->GetJSEnv() != env) {
233             // NOTE(MockMockBlack, #24062): to be replaced with a runtime exception
234             InteropFatal("InteropFatal, interop object must be used in the same interopCtx as it was created.");
235         }
236 
237         return sharedRef->GetJsRef();
238     }
239 
SetUndefined()240     void SetUndefined()
241     {
242         SetType(napi_undefined);
243     }
244 
SetNull()245     void SetNull()
246     {
247         SetType(napi_null);
248     }
249 
SetBoolean(bool value)250     void SetBoolean(bool value)
251     {
252         SetType(napi_boolean);
253         SetData(value);
254     }
255 
SetNumber(double value)256     void SetNumber(double value)
257     {
258         SetType(napi_number);
259         SetData(value);
260     }
261 
SetString(JSValueStringStorage::CachedEntry value)262     void SetString(JSValueStringStorage::CachedEntry value)
263     {
264         SetType(napi_string);
265         SetData(value);
266     }
267 
SetBigInt(std::pair<SmallVector<uint64_t,4U>,int> && value)268     void SetBigInt(std::pair<SmallVector<uint64_t, 4U>, int> &&value)
269     {
270         SetType(napi_bigint);
271         SetData(new std::pair<SmallVector<uint64_t, 4U>, int>(std::move(value)));
272         // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
273     }
274 
SetRefValue(InteropCtx * ctx,napi_value jsValue,napi_valuetype type)275     void SetRefValue(InteropCtx *ctx, napi_value jsValue, napi_valuetype type)
276     {
277         ASSERT(GetValueType(ctx->GetJSEnv(), jsValue) == type);
278         ASSERT(IsRefType(type));
279         SetType(type);
280         SetData(ctx->GetSharedRefStorage()->CreateJSObjectRef(ctx, this, jsValue));
281     }
282 
283     FIELD_UNUSED uint32_t type_;
284     FIELD_UNUSED uint32_t padding_;
285     FIELD_UNUSED uint64_t data_;
286 
287     friend class testing::JSValueOffsets;
288 };
289 
290 class ESValue : private EtsObject {
291 public:
GetEo()292     JSValue *GetEo()
293     {
294         return JSValue::FromCoreType(ObjectAccessor::GetObject(this, GetEoOffset()));
295     }
296 
GetEoOffset()297     static constexpr uint32_t GetEoOffset()
298     {
299         return MEMBER_OFFSET(ESValue, eo_);
300     }
301 
302     ESValue() = delete;
303     ~ESValue() = delete;
304 
305     NO_COPY_SEMANTIC(ESValue);
306     NO_MOVE_SEMANTIC(ESValue);
307 
308 private:
309     FIELD_UNUSED ObjectPointer<JSValue> eo_;
310     FIELD_UNUSED EtsBoolean isStatic_;
311     friend class testing::ESValueOffsets;
312 };
313 
314 static_assert(JSValue::GetTypeOffset() == sizeof(ObjectHeader));
315 static_assert(ESValue::GetEoOffset() == sizeof(ObjectHeader));
316 
317 }  // namespace ark::ets::interop::js
318 
319 #endif  // !PANDA_PLUGINS_ETS_RUNTIME_INTEROP_JS_JSVALUE_H_
320