1 /**
2 * Copyright (c) 2023-2024 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/napi_env_scope.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 panda::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 [[maybe_unused]] EtsJSNapiEnvScope scope(ctx, env);
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 ASSERT(res != nullptr || ctx->SanityJSExceptionPending());
86 return res;
87 }
88
89 template <typename FieldAccessor, bool IS_STATIC>
EtsFieldSetter(napi_env env,napi_callback_info cinfo)90 static napi_value EtsFieldSetter(napi_env env, napi_callback_info cinfo)
91 {
92 size_t argc = 1;
93 napi_value jsValue;
94 napi_value jsThis;
95 void *data;
96 NAPI_CHECK_FATAL(napi_get_cb_info(env, cinfo, &argc, &jsValue, &jsThis, &data));
97 if (UNLIKELY(argc != 1)) {
98 InteropCtx::ThrowJSError(env, "setter called in wrong context");
99 return napi_value {};
100 }
101
102 auto etsFieldWrapper = reinterpret_cast<EtsFieldWrapper *>(data);
103 EtsCoroutine *coro = EtsCoroutine::GetCurrent();
104 InteropCtx *ctx = InteropCtx::Current(coro);
105 [[maybe_unused]] EtsJSNapiEnvScope scope(ctx, env);
106 ScopedManagedCodeThread managedScope(coro);
107
108 EtsObject *etsThis = EtsAccessorsHandleThis<IS_STATIC>(etsFieldWrapper, coro, ctx, env, jsThis);
109 if (UNLIKELY(etsThis == nullptr)) {
110 ASSERT(ctx->SanityJSExceptionPending());
111 return nullptr;
112 }
113
114 LocalObjectHandle<EtsObject> etsThisHandle(coro, etsThis);
115 auto res = FieldAccessor::Setter(ctx, env, EtsHandle<EtsObject>(VMHandle<EtsObject>(etsThisHandle)),
116 etsFieldWrapper, jsValue);
117 if (UNLIKELY(!res)) {
118 if (coro->HasPendingException()) {
119 ctx->ForwardEtsException(coro);
120 }
121 ASSERT(ctx->SanityJSExceptionPending());
122 }
123 return nullptr;
124 }
125
126 struct EtsFieldAccessorREFERENCE {
Getterpanda::ets::interop::js::ets_proxy::EtsFieldAccessorREFERENCE127 static napi_value Getter(InteropCtx *ctx, napi_env env, EtsObject *etsObject, EtsFieldWrapper *etsFieldWrapper)
128 {
129 EtsObject *etsValue = etsObject->GetFieldObject(etsFieldWrapper->GetObjOffset());
130 if (etsValue == nullptr) {
131 return GetNull(env);
132 }
133 auto refconv = JSRefConvertResolve(ctx, etsValue->GetClass()->GetRuntimeClass());
134 ASSERT(refconv != nullptr);
135 return refconv->Wrap(ctx, etsValue);
136 }
137
Setterpanda::ets::interop::js::ets_proxy::EtsFieldAccessorREFERENCE138 static bool Setter(InteropCtx *ctx, napi_env env, EtsHandle<EtsObject> etsObject, EtsFieldWrapper *etsFieldWrapper,
139 napi_value jsValue)
140 {
141 EtsObject *etsValue;
142 if (IsNull(env, jsValue)) {
143 etsValue = nullptr;
144 } else if (IsUndefined(env, jsValue)) {
145 etsValue = ctx->GetUndefinedObject();
146 } else {
147 JSRefConvert *refconv = etsFieldWrapper->GetRefConvert<true>(ctx);
148 if (UNLIKELY(refconv == nullptr)) {
149 return false;
150 }
151 etsValue = refconv->Unwrap(ctx, jsValue);
152 if (UNLIKELY(etsValue == nullptr)) {
153 return false;
154 }
155 }
156 etsObject->SetFieldObject(etsFieldWrapper->GetObjOffset(), etsValue);
157 return true;
158 }
159 };
160
161 template <typename Convertor>
162 struct EtsFieldAccessorPRIMITIVE {
163 using PrimitiveType = typename Convertor::cpptype;
164
Getterpanda::ets::interop::js::ets_proxy::EtsFieldAccessorPRIMITIVE165 static napi_value Getter(InteropCtx * /*ctx*/, napi_env env, EtsObject *etsObject, EtsFieldWrapper *etsFieldWrapper)
166 {
167 auto etsValue = etsObject->GetFieldPrimitive<PrimitiveType>(etsFieldWrapper->GetObjOffset());
168 return Convertor::Wrap(env, etsValue);
169 }
170
171 // NOTE(vpukhov): elide etsObject handle
Setterpanda::ets::interop::js::ets_proxy::EtsFieldAccessorPRIMITIVE172 static bool Setter(InteropCtx *ctx, napi_env env, EtsHandle<EtsObject> etsObject, EtsFieldWrapper *etsFieldWrapper,
173 napi_value jsValue)
174 {
175 std::optional<PrimitiveType> etsValue = Convertor::Unwrap(ctx, env, jsValue);
176 if (LIKELY(etsValue.has_value())) {
177 etsObject->SetFieldPrimitive<PrimitiveType>(etsFieldWrapper->GetObjOffset(), etsValue.value());
178 }
179 return etsValue.has_value();
180 }
181 };
182
183 template <bool ALLOW_INIT>
GetRefConvert(InteropCtx * ctx)184 JSRefConvert *EtsFieldWrapper::GetRefConvert(InteropCtx *ctx)
185 {
186 if (LIKELY(lazyRefconvertLink_.IsResolved())) {
187 return lazyRefconvertLink_.GetResolved();
188 }
189
190 const Field *field = lazyRefconvertLink_.GetUnresolved();
191 ASSERT(field->GetTypeId() == panda_file::Type::TypeId::REFERENCE);
192
193 const auto *pandaFile = field->GetPandaFile();
194 auto *classLinker = Runtime::GetCurrent()->GetClassLinker();
195 Class *fieldClass =
196 classLinker->GetClass(*pandaFile, panda_file::FieldDataAccessor::GetTypeId(*pandaFile, field->GetFileId()),
197 ctx->LinkerCtx(), nullptr);
198
199 JSRefConvert *refconv = JSRefConvertResolve<ALLOW_INIT>(ctx, fieldClass);
200 if (UNLIKELY(refconv == nullptr)) {
201 return nullptr;
202 }
203 lazyRefconvertLink_.Set(refconv); // Update link
204 return refconv;
205 }
206
207 // Explicit instantiation
208 template JSRefConvert *EtsFieldWrapper::GetRefConvert<false>(InteropCtx *ctx);
209 template JSRefConvert *EtsFieldWrapper::GetRefConvert<true>(InteropCtx *ctx);
210
211 template <bool IS_STATIC>
DoMakeNapiProperty(EtsFieldWrapper * wrapper)212 static napi_property_descriptor DoMakeNapiProperty(EtsFieldWrapper *wrapper)
213 {
214 Field *field = wrapper->GetField();
215 napi_property_descriptor prop {};
216 prop.utf8name = utf::Mutf8AsCString(field->GetName().data);
217 prop.attributes = IS_STATIC ? EtsClassWrapper::STATIC_FIELD_ATTR : EtsClassWrapper::FIELD_ATTR;
218 prop.data = wrapper;
219
220 // NOTE(vpukhov): apply the same rule to instance fields?
221 ASSERT(!IS_STATIC || wrapper->GetOwner()->GetEtsClass()->GetRuntimeClass() == field->GetClass());
222
223 auto setupAccessors = [&prop](auto accessorTag) {
224 using Accessor = typename decltype(accessorTag)::type;
225 prop.getter = EtsFieldGetter<Accessor, IS_STATIC>;
226 prop.setter = EtsFieldSetter<Accessor, IS_STATIC>;
227 return prop;
228 };
229
230 panda_file::Type type = field->GetType();
231 switch (type.GetId()) {
232 case panda_file::Type::TypeId::U1:
233 return setupAccessors(helpers::TypeIdentity<EtsFieldAccessorPRIMITIVE<JSConvertU1>>());
234 case panda_file::Type::TypeId::I8:
235 return setupAccessors(helpers::TypeIdentity<EtsFieldAccessorPRIMITIVE<JSConvertI8>>());
236 case panda_file::Type::TypeId::U8:
237 return setupAccessors(helpers::TypeIdentity<EtsFieldAccessorPRIMITIVE<JSConvertU8>>());
238 case panda_file::Type::TypeId::I16:
239 return setupAccessors(helpers::TypeIdentity<EtsFieldAccessorPRIMITIVE<JSConvertI16>>());
240 case panda_file::Type::TypeId::U16:
241 return setupAccessors(helpers::TypeIdentity<EtsFieldAccessorPRIMITIVE<JSConvertU16>>());
242 case panda_file::Type::TypeId::I32:
243 return setupAccessors(helpers::TypeIdentity<EtsFieldAccessorPRIMITIVE<JSConvertI32>>());
244 case panda_file::Type::TypeId::U32:
245 return setupAccessors(helpers::TypeIdentity<EtsFieldAccessorPRIMITIVE<JSConvertU32>>());
246 case panda_file::Type::TypeId::I64:
247 return setupAccessors(helpers::TypeIdentity<EtsFieldAccessorPRIMITIVE<JSConvertI64>>());
248 case panda_file::Type::TypeId::U64:
249 return setupAccessors(helpers::TypeIdentity<EtsFieldAccessorPRIMITIVE<JSConvertU64>>());
250 case panda_file::Type::TypeId::F32:
251 return setupAccessors(helpers::TypeIdentity<EtsFieldAccessorPRIMITIVE<JSConvertF32>>());
252 case panda_file::Type::TypeId::F64:
253 return setupAccessors(helpers::TypeIdentity<EtsFieldAccessorPRIMITIVE<JSConvertF64>>());
254 case panda_file::Type::TypeId::REFERENCE:
255 return setupAccessors(helpers::TypeIdentity<EtsFieldAccessorREFERENCE>());
256 default:
257 InteropCtx::Fatal(std::string("ConvertEtsVal: unsupported typeid ") +
258 panda_file::Type::GetSignatureByTypeId(type));
259 }
260 UNREACHABLE();
261 }
262
MakeInstanceProperty(EtsClassWrapper * owner,Field * field)263 napi_property_descriptor EtsFieldWrapper::MakeInstanceProperty(EtsClassWrapper *owner, Field *field)
264 {
265 new (this) EtsFieldWrapper(owner, field);
266 return DoMakeNapiProperty<false>(this);
267 }
268
MakeStaticProperty(EtsClassWrapper * owner,Field * field)269 napi_property_descriptor EtsFieldWrapper::MakeStaticProperty(EtsClassWrapper *owner, Field *field)
270 {
271 new (this) EtsFieldWrapper(owner, field);
272 return DoMakeNapiProperty<true>(this);
273 }
274
275 } // namespace panda::ets::interop::js::ets_proxy
276