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