• 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 #include "plugins/ets/runtime/interop_js/ets_proxy/ets_field_wrapper.h"
17 
18 #include "libpandafile/file.h"
19 #include "plugins/ets/runtime/ets_class_root.h"
20 #include "plugins/ets/runtime/ets_handle.h"
21 #include "plugins/ets/runtime/ets_handle_scope.h"
22 #include "plugins/ets/runtime/interop_js/js_convert.h"
23 #include "plugins/ets/runtime/interop_js/ets_proxy/shared_reference.h"
24 #include "plugins/ets/runtime/interop_js/code_scopes.h"
25 #include "plugins/ets/runtime/types/ets_object.h"
26 #include "runtime/mem/local_object_handle.h"
27 
28 #include "runtime/mem/vm_handle-inl.h"
29 
30 namespace ark::ets::interop::js::ets_proxy {
31 
32 template <bool IS_STATIC>
EtsAccessorsHandleThis(EtsFieldWrapper * fieldWrapper,EtsCoroutine * coro,InteropCtx * ctx,napi_env env,napi_value jsThis)33 static EtsObject *EtsAccessorsHandleThis(EtsFieldWrapper *fieldWrapper, EtsCoroutine *coro, InteropCtx *ctx,
34                                          napi_env env, napi_value jsThis)
35 {
36     if constexpr (IS_STATIC) {
37         EtsClass *etsClass = fieldWrapper->GetOwner()->GetEtsClass();
38         if (UNLIKELY(!coro->GetPandaVM()->GetClassLinker()->InitializeClass(coro, etsClass))) {
39             ctx->ForwardEtsException(coro);
40             return nullptr;
41         }
42         return etsClass->AsObject();
43     }
44 
45     if (UNLIKELY(IsNullOrUndefined(env, jsThis))) {
46         ctx->ThrowJSTypeError(env, "ets this in set accessor cannot be null or undefined");
47         return nullptr;
48     }
49 
50     EtsObject *etsThis = fieldWrapper->GetOwner()->UnwrapEtsProxy(ctx, jsThis);
51     if (UNLIKELY(etsThis == nullptr)) {
52         if (coro->HasPendingException()) {
53             ctx->ForwardEtsException(coro);
54         }
55         return nullptr;
56     }
57     return etsThis;
58 }
59 
60 template <typename FieldAccessor, bool IS_STATIC>
EtsFieldGetter(napi_env env,napi_callback_info cinfo)61 static napi_value EtsFieldGetter(napi_env env, napi_callback_info cinfo)
62 {
63     size_t argc = 0;
64     napi_value jsThis;
65     void *data;
66     NAPI_CHECK_FATAL(napi_get_cb_info(env, cinfo, &argc, nullptr, &jsThis, &data));
67     if (UNLIKELY(argc != 0)) {
68         InteropCtx::ThrowJSError(env, "getter called in wrong context");
69         return napi_value {};
70     }
71 
72     auto etsFieldWrapper = reinterpret_cast<EtsFieldWrapper *>(data);
73     EtsCoroutine *coro = EtsCoroutine::GetCurrent();
74     InteropCtx *ctx = InteropCtx::Current(coro);
75     INTEROP_CODE_SCOPE_JS(coro);
76     ScopedManagedCodeThread managedScope(coro);
77 
78     EtsObject *etsThis = EtsAccessorsHandleThis<IS_STATIC>(etsFieldWrapper, coro, ctx, env, jsThis);
79     if (UNLIKELY(etsThis == nullptr)) {
80         ASSERT(ctx->SanityJSExceptionPending());
81         return nullptr;
82     }
83 
84     napi_value res = FieldAccessor::Getter(ctx, env, etsThis, etsFieldWrapper);
85     if (UNLIKELY(res == nullptr)) {
86         if (coro->HasPendingException()) {
87             ctx->ForwardEtsException(coro);
88         }
89     }
90     ASSERT(res != nullptr || ctx->SanityJSExceptionPending());
91     return res;
92 }
93 
94 template <typename FieldAccessor, bool IS_STATIC>
EtsFieldSetter(napi_env env,napi_callback_info cinfo)95 static napi_value EtsFieldSetter(napi_env env, napi_callback_info cinfo)
96 {
97     size_t argc = 1;
98     napi_value jsValue;
99     napi_value jsThis;
100     void *data;
101     NAPI_CHECK_FATAL(napi_get_cb_info(env, cinfo, &argc, &jsValue, &jsThis, &data));
102     if (UNLIKELY(argc != 1)) {
103         InteropCtx::ThrowJSError(env, "setter called in wrong context");
104         return napi_value {};
105     }
106 
107     auto etsFieldWrapper = reinterpret_cast<EtsFieldWrapper *>(data);
108     EtsCoroutine *coro = EtsCoroutine::GetCurrent();
109     InteropCtx *ctx = InteropCtx::Current(coro);
110     INTEROP_CODE_SCOPE_JS(coro);
111     ScopedManagedCodeThread managedScope(coro);
112 
113     EtsObject *etsThis = EtsAccessorsHandleThis<IS_STATIC>(etsFieldWrapper, coro, ctx, env, jsThis);
114     if (UNLIKELY(etsThis == nullptr)) {
115         ASSERT(ctx->SanityJSExceptionPending());
116         return nullptr;
117     }
118 
119     LocalObjectHandle<EtsObject> etsThisHandle(coro, etsThis);
120     auto res = FieldAccessor::Setter(ctx, env, EtsHandle<EtsObject>(VMHandle<EtsObject>(etsThisHandle)),
121                                      etsFieldWrapper, jsValue);
122     if (UNLIKELY(!res)) {
123         if (coro->HasPendingException()) {
124             ctx->ForwardEtsException(coro);
125         }
126         ASSERT(ctx->SanityJSExceptionPending());
127     }
128     return nullptr;
129 }
130 
131 struct EtsFieldAccessorREFERENCE {
Getterark::ets::interop::js::ets_proxy::EtsFieldAccessorREFERENCE132     static napi_value Getter(InteropCtx *ctx, napi_env env, EtsObject *etsObject, EtsFieldWrapper *etsFieldWrapper)
133     {
134         EtsObject *etsValue = etsObject->GetFieldObject(etsFieldWrapper->GetObjOffset());
135         if (etsValue == nullptr) {
136             return GetUndefined(env);
137         }
138         auto refconv = JSRefConvertResolve(ctx, etsValue->GetClass()->GetRuntimeClass());
139         if (refconv == nullptr) {
140             return nullptr;
141         }
142         return refconv->Wrap(ctx, etsValue);
143     }
144 
Setterark::ets::interop::js::ets_proxy::EtsFieldAccessorREFERENCE145     static bool Setter(InteropCtx *ctx, napi_env env, EtsHandle<EtsObject> etsObject, EtsFieldWrapper *etsFieldWrapper,
146                        napi_value jsValue)
147     {
148         EtsObject *etsValue;
149         if (IsUndefined(env, jsValue)) {
150             etsValue = nullptr;
151         } else if (IsNull(env, jsValue)) {
152             etsValue = ctx->GetNullValue();
153         } else {
154             JSRefConvert *refconv = etsFieldWrapper->GetRefConvert<true>(ctx);
155             if (UNLIKELY(refconv == nullptr)) {
156                 return false;
157             }
158             etsValue = refconv->Unwrap(ctx, jsValue);
159             if (UNLIKELY(etsValue == nullptr)) {
160                 return false;
161             }
162         }
163         ASSERT(etsObject.GetPtr() != nullptr);
164         etsObject->SetFieldObject(etsFieldWrapper->GetObjOffset(), etsValue);
165         return true;
166     }
167 };
168 
169 template <typename Convertor>
170 struct EtsFieldAccessorPRIMITIVE {
171     using PrimitiveType = typename Convertor::cpptype;
172 
Getterark::ets::interop::js::ets_proxy::EtsFieldAccessorPRIMITIVE173     static napi_value Getter(InteropCtx * /*ctx*/, napi_env env, EtsObject *etsObject, EtsFieldWrapper *etsFieldWrapper)
174     {
175         auto etsValue = etsObject->GetFieldPrimitive<PrimitiveType>(etsFieldWrapper->GetObjOffset());
176         return Convertor::Wrap(env, etsValue);
177     }
178 
179     // NOTE(vpukhov): elide etsObject handle
Setterark::ets::interop::js::ets_proxy::EtsFieldAccessorPRIMITIVE180     static bool Setter(InteropCtx *ctx, napi_env env, EtsHandle<EtsObject> etsObject, EtsFieldWrapper *etsFieldWrapper,
181                        napi_value jsValue)
182     {
183         std::optional<PrimitiveType> etsValue = Convertor::Unwrap(ctx, env, jsValue);
184         if (LIKELY(etsValue.has_value())) {
185             ASSERT(etsObject.GetPtr() != nullptr);
186             etsObject->SetFieldPrimitive<PrimitiveType>(etsFieldWrapper->GetObjOffset(), etsValue.value());
187         }
188         return etsValue.has_value();
189     }
190 };
191 
192 template <bool ALLOW_INIT>
GetRefConvert(InteropCtx * ctx)193 JSRefConvert *EtsFieldWrapper::GetRefConvert(InteropCtx *ctx)
194 {
195     if (LIKELY(lazyRefconvertLink_.IsResolved())) {
196         return lazyRefconvertLink_.GetResolved();
197     }
198 
199     const Field *field = lazyRefconvertLink_.GetUnresolved();
200     ASSERT(field->GetTypeId() == panda_file::Type::TypeId::REFERENCE);
201 
202     const auto *pandaFile = field->GetPandaFile();
203     auto *classLinker = Runtime::GetCurrent()->GetClassLinker();
204     Class *fieldClass =
205         classLinker->GetClass(*pandaFile, panda_file::FieldDataAccessor::GetTypeId(*pandaFile, field->GetFileId()),
206                               ctx->LinkerCtx(), nullptr);
207 
208     JSRefConvert *refconv = JSRefConvertResolve<ALLOW_INIT>(ctx, fieldClass);
209     if (UNLIKELY(refconv == nullptr)) {
210         return nullptr;
211     }
212     lazyRefconvertLink_.Set(refconv);  // Update link
213     return refconv;
214 }
215 
216 // Explicit instantiation
217 template JSRefConvert *EtsFieldWrapper::GetRefConvert<false>(InteropCtx *ctx);
218 template JSRefConvert *EtsFieldWrapper::GetRefConvert<true>(InteropCtx *ctx);
219 
220 template <bool IS_STATIC>
DoMakeNapiProperty(EtsFieldWrapper * wrapper)221 static napi_property_descriptor DoMakeNapiProperty(EtsFieldWrapper *wrapper)
222 {
223     Field *field = wrapper->GetField();
224     napi_property_descriptor prop {};
225     prop.utf8name = utf::Mutf8AsCString(field->GetName().data);
226     prop.attributes = IS_STATIC ? EtsClassWrapper::STATIC_FIELD_ATTR : EtsClassWrapper::FIELD_ATTR;
227     prop.data = wrapper;
228 
229     // NOTE(vpukhov): apply the same rule to instance fields?
230     ASSERT(!IS_STATIC || wrapper->GetOwner()->GetEtsClass()->GetRuntimeClass() == field->GetClass());
231 
232     auto setupAccessors = [&prop](auto accessorTag) {
233         using Accessor = typename decltype(accessorTag)::type;
234         prop.getter = EtsFieldGetter<Accessor, IS_STATIC>;
235         prop.setter = EtsFieldSetter<Accessor, IS_STATIC>;
236         return prop;
237     };
238 
239     panda_file::Type type = field->GetType();
240     switch (type.GetId()) {
241         case panda_file::Type::TypeId::U1:
242             return setupAccessors(helpers::TypeIdentity<EtsFieldAccessorPRIMITIVE<JSConvertU1>>());
243         case panda_file::Type::TypeId::I8:
244             return setupAccessors(helpers::TypeIdentity<EtsFieldAccessorPRIMITIVE<JSConvertI8>>());
245         case panda_file::Type::TypeId::U8:
246             return setupAccessors(helpers::TypeIdentity<EtsFieldAccessorPRIMITIVE<JSConvertU8>>());
247         case panda_file::Type::TypeId::I16:
248             return setupAccessors(helpers::TypeIdentity<EtsFieldAccessorPRIMITIVE<JSConvertI16>>());
249         case panda_file::Type::TypeId::U16:
250             return setupAccessors(helpers::TypeIdentity<EtsFieldAccessorPRIMITIVE<JSConvertU16>>());
251         case panda_file::Type::TypeId::I32:
252             return setupAccessors(helpers::TypeIdentity<EtsFieldAccessorPRIMITIVE<JSConvertI32>>());
253         case panda_file::Type::TypeId::U32:
254             return setupAccessors(helpers::TypeIdentity<EtsFieldAccessorPRIMITIVE<JSConvertU32>>());
255         case panda_file::Type::TypeId::I64:
256             return setupAccessors(helpers::TypeIdentity<EtsFieldAccessorPRIMITIVE<JSConvertI64>>());
257         case panda_file::Type::TypeId::U64:
258             return setupAccessors(helpers::TypeIdentity<EtsFieldAccessorPRIMITIVE<JSConvertU64>>());
259         case panda_file::Type::TypeId::F32:
260             return setupAccessors(helpers::TypeIdentity<EtsFieldAccessorPRIMITIVE<JSConvertF32>>());
261         case panda_file::Type::TypeId::F64:
262             return setupAccessors(helpers::TypeIdentity<EtsFieldAccessorPRIMITIVE<JSConvertF64>>());
263         case panda_file::Type::TypeId::REFERENCE:
264             return setupAccessors(helpers::TypeIdentity<EtsFieldAccessorREFERENCE>());
265         default:
266             InteropCtx::Fatal(std::string("DoMakeNapiProperty: unsupported typeid ") +
267                               panda_file::Type::GetSignatureByTypeId(type));
268     }
269     UNREACHABLE();
270 }
271 
MakeInstanceProperty(EtsClassWrapper * owner,Field * field)272 napi_property_descriptor EtsFieldWrapper::MakeInstanceProperty(EtsClassWrapper *owner, Field *field)
273 {
274     new (this) EtsFieldWrapper(owner, field);
275     /*IS_STATIC=false*/
276     return DoMakeNapiProperty<false>(this);
277 }
278 
MakeStaticProperty(EtsClassWrapper * owner,Field * field)279 napi_property_descriptor EtsFieldWrapper::MakeStaticProperty(EtsClassWrapper *owner, Field *field)
280 {
281     new (this) EtsFieldWrapper(owner, field);
282     /*IS_STATIC=true*/
283     return DoMakeNapiProperty<true>(this);
284 }
285 
286 }  // namespace ark::ets::interop::js::ets_proxy
287