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_class_wrapper.h"
17
18 #include "plugins/ets/runtime/ets_handle.h"
19 #include "plugins/ets/runtime/ets_handle_scope.h"
20 #include "plugins/ets/runtime/interop_js/interop_context.h"
21 #include "plugins/ets/runtime/interop_js/ets_proxy/shared_reference.h"
22 #include "plugins/ets/runtime/interop_js/call/call.h"
23 #include "plugins/ets/runtime/interop_js/code_scopes.h"
24 #include "runtime/mem/local_object_handle.h"
25
26 namespace ark::ets::interop::js::ets_proxy {
27
28 class JSRefConvertEtsProxy : public JSRefConvert {
29 public:
JSRefConvertEtsProxy(EtsClassWrapper * etsClassWrapper)30 explicit JSRefConvertEtsProxy(EtsClassWrapper *etsClassWrapper)
31 : JSRefConvert(this), etsClassWrapper_(etsClassWrapper)
32 {
33 }
34
WrapImpl(InteropCtx * ctx,EtsObject * etsObject)35 napi_value WrapImpl(InteropCtx *ctx, EtsObject *etsObject)
36 {
37 return etsClassWrapper_->Wrap(ctx, etsObject);
38 }
UnwrapImpl(InteropCtx * ctx,napi_value jsValue)39 EtsObject *UnwrapImpl(InteropCtx *ctx, napi_value jsValue)
40 {
41 return etsClassWrapper_->Unwrap(ctx, jsValue);
42 }
43
44 private:
45 EtsClassWrapper *etsClassWrapper_ {};
46 };
47
Wrap(InteropCtx * ctx,EtsObject * etsObject)48 napi_value EtsClassWrapper::Wrap(InteropCtx *ctx, EtsObject *etsObject)
49 {
50 CheckClassInitialized(etsClass_->GetRuntimeClass());
51
52 napi_env env = ctx->GetJSEnv();
53
54 ASSERT(etsObject != nullptr);
55 ASSERT(etsClass_ == etsObject->GetClass());
56
57 SharedReferenceStorage *storage = ctx->GetSharedRefStorage();
58 if (LIKELY(storage->HasReference(etsObject))) {
59 SharedReference *sharedRef = storage->GetReference(etsObject);
60 ASSERT(sharedRef != nullptr);
61 return sharedRef->GetJsObject(env);
62 }
63
64 napi_value jsValue;
65 // etsObject will be wrapped in jsValue in responce to jsCtor call
66 ctx->SetPendingNewInstance(etsObject);
67 NAPI_CHECK_FATAL(napi_new_instance(env, GetJsCtor(env), 0, nullptr, &jsValue));
68 return jsValue;
69 }
70
71 // Use UnwrapEtsProxy if you expect exactly a EtsProxy
Unwrap(InteropCtx * ctx,napi_value jsValue)72 EtsObject *EtsClassWrapper::Unwrap(InteropCtx *ctx, napi_value jsValue)
73 {
74 CheckClassInitialized(etsClass_->GetRuntimeClass());
75
76 napi_env env = ctx->GetJSEnv();
77
78 ASSERT(!IsNull(env, jsValue));
79
80 // Check if object has SharedReference
81 SharedReference *sharedRef = ctx->GetSharedRefStorage()->GetReference(env, jsValue);
82 if (LIKELY(sharedRef != nullptr)) {
83 EtsObject *etsObject = sharedRef->GetEtsObject(ctx);
84 if (UNLIKELY(!etsClass_->IsAssignableFrom(etsObject->GetClass()))) {
85 ThrowJSErrorNotAssignable(env, etsObject->GetClass(), etsClass_);
86 return nullptr;
87 }
88 return etsObject;
89 }
90
91 // Check if object is subtype of js builtin class
92 if (LIKELY(HasBuiltin())) {
93 ASSERT(jsBuiltinMatcher_ != nullptr);
94 auto res = jsBuiltinMatcher_(ctx, jsValue, false);
95 ASSERT(res != nullptr || ctx->SanityJSExceptionPending() || ctx->SanityETSExceptionPending());
96 return res;
97 }
98
99 InteropCtx::ThrowJSTypeError(env, std::string("Value is not assignable to ") + etsClass_->GetDescriptor());
100 return nullptr;
101 }
102
103 // Special method to ensure unwrapped object is not a JSProxy
UnwrapEtsProxy(InteropCtx * ctx,napi_value jsValue)104 EtsObject *EtsClassWrapper::UnwrapEtsProxy(InteropCtx *ctx, napi_value jsValue)
105 {
106 CheckClassInitialized(etsClass_->GetRuntimeClass());
107
108 napi_env env = ctx->GetJSEnv();
109
110 ASSERT(!IsNullOrUndefined(env, jsValue));
111
112 // Check if object has SharedReference
113 SharedReference *sharedRef = ctx->GetSharedRefStorage()->GetReference(env, jsValue);
114 if (LIKELY(sharedRef != nullptr)) {
115 EtsObject *etsObject = sharedRef->GetEtsObject(ctx);
116 if (UNLIKELY(!etsClass_->IsAssignableFrom(etsObject->GetClass()))) {
117 ThrowJSErrorNotAssignable(env, etsObject->GetClass(), etsClass_);
118 return nullptr;
119 }
120 if (UNLIKELY(!sharedRef->GetField<SharedReference::HasETSObject>())) {
121 InteropCtx::ThrowJSTypeError(env, std::string("JS object in context of EtsProxy of class ") +
122 etsClass_->GetDescriptor());
123 return nullptr;
124 }
125 return etsObject;
126 }
127 return nullptr;
128 }
129
ThrowJSErrorNotAssignable(napi_env env,EtsClass * fromKlass,EtsClass * toKlass)130 void EtsClassWrapper::ThrowJSErrorNotAssignable(napi_env env, EtsClass *fromKlass, EtsClass *toKlass)
131 {
132 const char *from = fromKlass->GetDescriptor();
133 const char *to = toKlass->GetDescriptor();
134 InteropCtx::ThrowJSTypeError(env, std::string(from) + " is not assignable to " + to);
135 }
136
CreateJSBuiltinProxy(InteropCtx * ctx,napi_value jsValue)137 EtsObject *EtsClassWrapper::CreateJSBuiltinProxy(InteropCtx *ctx, napi_value jsValue)
138 {
139 ASSERT(jsproxyWrapper_ != nullptr);
140 ASSERT(SharedReference::ExtractMaybeReference(ctx->GetJSEnv(), jsValue) == nullptr);
141
142 EtsObject *etsObject = EtsObject::Create(jsproxyWrapper_->GetProxyClass());
143 if (UNLIKELY(etsObject == nullptr)) {
144 ctx->ForwardEtsException(EtsCoroutine::GetCurrent());
145 return nullptr;
146 }
147
148 SharedReference *sharedRef = ctx->GetSharedRefStorage()->CreateJSObjectRef(ctx, etsObject, jsValue);
149 if (UNLIKELY(sharedRef == nullptr)) {
150 ASSERT(InteropCtx::SanityJSExceptionPending());
151 return nullptr;
152 }
153 return sharedRef->GetEtsObject(ctx); // fetch again after gc
154 }
155
156 /*static*/
CreateJSRefConvertEtsProxy(InteropCtx * ctx,Class * klass)157 std::unique_ptr<JSRefConvert> EtsClassWrapper::CreateJSRefConvertEtsProxy(InteropCtx *ctx, Class *klass)
158 {
159 ASSERT(!klass->IsInterface());
160 EtsClass *etsClass = EtsClass::FromRuntimeClass(klass);
161 EtsClassWrapper *wrapper = EtsClassWrapper::Get(ctx, etsClass);
162 if (UNLIKELY(wrapper == nullptr)) {
163 return nullptr;
164 }
165 ASSERT(wrapper->etsClass_ == etsClass);
166 return std::make_unique<JSRefConvertEtsProxy>(wrapper);
167 }
168
169 class JSRefConvertJSProxy : public JSRefConvert {
170 public:
JSRefConvertJSProxy()171 explicit JSRefConvertJSProxy() : JSRefConvert(this) {}
172
WrapImpl(InteropCtx * ctx,EtsObject * etsObject)173 napi_value WrapImpl(InteropCtx *ctx, EtsObject *etsObject)
174 {
175 SharedReferenceStorage *storage = ctx->GetSharedRefStorage();
176 INTEROP_FATAL_IF(!storage->HasReference(etsObject));
177 SharedReference *sharedRef = storage->GetReference(etsObject);
178 ASSERT(sharedRef != nullptr);
179 return sharedRef->GetJsObject(ctx->GetJSEnv());
180 }
181
UnwrapImpl(InteropCtx * ctx,napi_value jsValue)182 EtsObject *UnwrapImpl(InteropCtx *ctx, [[maybe_unused]] napi_value jsValue)
183 {
184 ctx->Fatal("Unwrap called on JSProxy class");
185 return nullptr;
186 }
187 };
188
189 /*static*/
CreateJSRefConvertJSProxy(InteropCtx * ctx,Class * klass)190 std::unique_ptr<JSRefConvert> EtsClassWrapper::CreateJSRefConvertJSProxy([[maybe_unused]] InteropCtx *ctx,
191 [[maybe_unused]] Class *klass)
192 {
193 ASSERT(js_proxy::JSProxy::IsProxyClass(klass));
194 return std::make_unique<JSRefConvertJSProxy>();
195 }
196
GetMethod(const std::string & name) const197 EtsMethodSet *EtsClassWrapper::GetMethod(const std::string &name) const
198 {
199 for (const auto &item : etsMethods_) {
200 if (name == item->GetName()) {
201 return item.get();
202 }
203 }
204 return nullptr;
205 }
206
207 /*static*/
Get(InteropCtx * ctx,EtsClass * etsClass)208 EtsClassWrapper *EtsClassWrapper::Get(InteropCtx *ctx, EtsClass *etsClass)
209 {
210 EtsClassWrappersCache *cache = ctx->GetEtsClassWrappersCache();
211
212 ASSERT(etsClass != nullptr);
213 EtsClassWrapper *etsClassWrapper = cache->Lookup(etsClass);
214 if (LIKELY(etsClassWrapper != nullptr)) {
215 return etsClassWrapper;
216 }
217
218 ASSERT(!etsClass->IsPrimitive() && etsClass->GetComponentType() == nullptr);
219 ASSERT(ctx->GetRefConvertCache()->Lookup(etsClass->GetRuntimeClass()) == nullptr);
220
221 if (IsStdClass(etsClass) &&
222 !etsClass->IsInterface()) { // NOTE(gogabr): temporary ugly workaround for Function... interfaces
223 ctx->Fatal(std::string("ets_proxy requested for ") + etsClass->GetDescriptor() + " must add or forbid");
224 }
225 ASSERT(!js_proxy::JSProxy::IsProxyClass((etsClass->GetRuntimeClass())));
226
227 std::unique_ptr<EtsClassWrapper> wrapper = EtsClassWrapper::Create(ctx, etsClass);
228 if (UNLIKELY(wrapper == nullptr)) {
229 return nullptr;
230 }
231 return cache->Insert(etsClass, std::move(wrapper));
232 }
233
SetupHierarchy(InteropCtx * ctx,const char * jsBuiltinName)234 bool EtsClassWrapper::SetupHierarchy(InteropCtx *ctx, const char *jsBuiltinName)
235 {
236 ASSERT(etsClass_->GetBase() != etsClass_);
237 if (etsClass_->GetBase() != nullptr) {
238 baseWrapper_ = EtsClassWrapper::Get(ctx, etsClass_->GetBase());
239 if (baseWrapper_ == nullptr) {
240 return false;
241 }
242 }
243
244 if (jsBuiltinName != nullptr) {
245 auto env = ctx->GetJSEnv();
246 napi_value jsBuiltinCtor;
247 NAPI_CHECK_FATAL(napi_get_named_property(env, GetGlobal(env), jsBuiltinName, &jsBuiltinCtor));
248 NAPI_CHECK_FATAL(napi_create_reference(env, jsBuiltinCtor, 1, &jsBuiltinCtorRef_));
249 }
250 return true;
251 }
252
CalculateProperties(const OverloadsMap * overloads)253 std::pair<EtsClassWrapper::FieldsVec, EtsClassWrapper::MethodsVec> EtsClassWrapper::CalculateProperties(
254 const OverloadsMap *overloads)
255 {
256 auto fatalNoMethod = [](Class *klass, const uint8_t *name, const char *signature) {
257 InteropCtx::Fatal(std::string("No method ") + utf::Mutf8AsCString(name) + " " + signature + " in " +
258 klass->GetName());
259 };
260
261 auto klass = etsClass_->GetRuntimeClass();
262 PropsMap props;
263
264 // Collect fields
265 for (auto &f : klass->GetFields()) {
266 if (f.IsPublic()) {
267 props.insert({f.GetName().data, &f});
268 }
269 }
270 // Select preferred overloads
271 if (overloads != nullptr) {
272 for (auto &[name, signature] : *overloads) {
273 Method *method = etsClass_->GetDirectMethod(name, signature)->GetPandaMethod();
274 if (UNLIKELY(method == nullptr)) {
275 fatalNoMethod(klass, name, signature);
276 }
277 auto etsMethodSet = std::make_unique<EtsMethodSet>(EtsMethodSet::Create(method));
278 auto it = props.insert({method->GetName().data, etsMethodSet.get()});
279 etsMethods_.push_back(std::move(etsMethodSet));
280 INTEROP_FATAL_IF(!it.second);
281 }
282 }
283
284 // If class is std.core.Object
285 auto klassDesc = utf::Mutf8AsCString(klass->GetDescriptor());
286 if (klassDesc == panda_file_items::class_descriptors::OBJECT) {
287 // Ingore all methods of std.core.Object due to names intersection with JS Object
288 // Keep constructors only
289 CollectConstructors(&props);
290 // NOTE(shumilov-petr): Think about removing methods from std.core.Object
291 // that are already presented in JS Object, others should be kept
292 } else {
293 // Collect methods
294 CollectClassMethods(&props, overloads);
295 }
296
297 UpdatePropsWithBaseClasses(&props);
298
299 return CalculateFieldsAndMethods(props);
300 }
301
CollectConstructors(EtsClassWrapper::PropsMap * props)302 void EtsClassWrapper::CollectConstructors(EtsClassWrapper::PropsMap *props)
303 {
304 auto objCtors = etsClass_->GetConstructors();
305 // Assuming that ETS StdLib guarantee that Object has the only one ctor
306 ASSERT(objCtors.size() == 1);
307 auto ctor = objCtors[0]->GetPandaMethod();
308 auto etsMethodSet = std::make_unique<EtsMethodSet>(EtsMethodSet::Create(ctor));
309 props->insert({ctor->GetName().data, etsMethodSet.get()});
310 etsMethods_.push_back(std::move(etsMethodSet));
311 }
312
CollectClassMethods(EtsClassWrapper::PropsMap * props,const OverloadsMap * overloads)313 void EtsClassWrapper::CollectClassMethods(EtsClassWrapper::PropsMap *props, const OverloadsMap *overloads)
314 {
315 auto fatalMethodOverloaded = [](Method *method) {
316 for (auto &m : method->GetClass()->GetMethods()) {
317 if (utf::IsEqual(m.GetName().data, method->GetName().data)) {
318 INTEROP_LOG(ERROR) << "overload: " << EtsMethod::FromRuntimeMethod(&m)->GetMethodSignature(true);
319 }
320 }
321 InteropCtx::Fatal(std::string("Method ") + utf::Mutf8AsCString(method->GetName().data) + " of class " +
322 utf::Mutf8AsCString(method->GetClass()->GetDescriptor()) + " is overloaded");
323 };
324
325 auto klass = etsClass_->GetRuntimeClass();
326 for (auto &m : klass->GetMethods()) {
327 if (m.IsPrivate()) {
328 continue;
329 }
330 auto name = m.GetName().data;
331 if (overloads != nullptr && overloads->find(name) != overloads->end()) {
332 continue;
333 }
334 auto methodSet = std::make_unique<EtsMethodSet>(EtsMethodSet::Create(&m));
335 auto it = props->insert({m.GetName().data, methodSet.get()});
336 if (!it.second && !std::holds_alternative<EtsMethodSet *>(it.first->second)) {
337 // Method overloads non-method field
338 fatalMethodOverloaded(&m);
339 }
340 if (!it.second && std::holds_alternative<EtsMethodSet *>(it.first->second)) {
341 // Possible correct method overloading: merge to existing entry
342 auto addedMethods = std::get<EtsMethodSet *>(it.first->second);
343 addedMethods->MergeWith(*methodSet.get());
344 } else {
345 etsMethods_.push_back(std::move(methodSet));
346 }
347 }
348 }
349
UpdatePropsWithBaseClasses(EtsClassWrapper::PropsMap * props)350 void EtsClassWrapper::UpdatePropsWithBaseClasses(EtsClassWrapper::PropsMap *props)
351 {
352 auto hasSquashedProtoOhosImpl = [](EtsClassWrapper *wclass) {
353 ASSERT(wclass->HasBuiltin() || wclass->baseWrapper_ != nullptr);
354 return wclass->HasBuiltin() || wclass->baseWrapper_->HasBuiltin();
355 };
356
357 auto hasSquashedProtoOtherImpl = [](EtsClassWrapper *wclass) {
358 // NOTE(vpukhov): some napi implementations add explicit receiver checks in call handler,
359 // thus method inheritance via prototype chain wont work
360 (void)wclass;
361 return true;
362 };
363
364 #if PANDA_TARGET_OHOS
365 auto hasSquashedProto = hasSquashedProtoOhosImpl;
366 (void)hasSquashedProtoOtherImpl;
367 #else
368 auto hasSquashedProto = hasSquashedProtoOtherImpl;
369 (void)hasSquashedProtoOhosImpl;
370 #endif
371
372 if (hasSquashedProto(this)) {
373 // Copy properties of base classes if we have to split prototype chain
374 for (auto wclass = baseWrapper_; wclass != nullptr; wclass = wclass->baseWrapper_) {
375 for (auto &wfield : wclass->GetFields()) {
376 Field *field = wfield.GetField();
377 props->insert({field->GetName().data, field});
378 }
379 for (auto &link : wclass->GetMethods()) {
380 EtsMethodSet *methodSet = link.IsResolved() ? link.GetResolved()->GetMethodSet() : link.GetUnresolved();
381 props->insert({utf::CStringAsMutf8(methodSet->GetName()), methodSet});
382 }
383
384 if (hasSquashedProto(wclass)) {
385 break;
386 }
387 }
388 }
389 }
390
CalculateFieldsAndMethods(const PropsMap & props)391 std::pair<EtsClassWrapper::FieldsVec, EtsClassWrapper::MethodsVec> EtsClassWrapper::CalculateFieldsAndMethods(
392 const PropsMap &props)
393 {
394 std::vector<EtsMethodSet *> methods;
395 std::vector<Field *> fields;
396
397 for (auto &[n, p] : props) {
398 if (std::holds_alternative<EtsMethodSet *>(p)) {
399 auto method = std::get<EtsMethodSet *>(p);
400 if (method->IsConstructor() && !method->IsStatic()) {
401 etsCtorLink_ = LazyEtsMethodWrapperLink(method);
402 }
403 if (!method->IsConstructor()) {
404 methods.push_back(method);
405 }
406 } else if (std::holds_alternative<Field *>(p)) {
407 fields.push_back(std::get<Field *>(p));
408 } else {
409 UNREACHABLE();
410 }
411 }
412
413 return {fields, methods};
414 }
415
BuildJSProperties(Span<Field * > fields,Span<EtsMethodSet * > methods)416 std::vector<napi_property_descriptor> EtsClassWrapper::BuildJSProperties(Span<Field *> fields,
417 Span<EtsMethodSet *> methods)
418 {
419 std::vector<napi_property_descriptor> jsProps;
420 jsProps.reserve(fields.size() + methods.size());
421
422 // Process fields
423 numFields_ = fields.size();
424 // NOLINTNEXTLINE(modernize-avoid-c-arrays)
425 etsFieldWrappers_ = std::make_unique<EtsFieldWrapper[]>(numFields_);
426 Span<EtsFieldWrapper> etsFieldWrappers(etsFieldWrappers_.get(), numFields_);
427 size_t fieldIdx = 0;
428
429 for (Field *field : fields) {
430 auto wfield = &etsFieldWrappers[fieldIdx++];
431 if (field->IsStatic()) {
432 EtsClassWrapper *fieldWclass = LookupBaseWrapper(EtsClass::FromRuntimeClass(field->GetClass()));
433 ASSERT(fieldWclass != nullptr);
434 jsProps.emplace_back(wfield->MakeStaticProperty(fieldWclass, field));
435 } else {
436 jsProps.emplace_back(wfield->MakeInstanceProperty(this, field));
437 }
438 }
439
440 // Process methods
441 numMethods_ = methods.size();
442 // NOLINTNEXTLINE(modernize-avoid-c-arrays)
443 etsMethodWrappers_ = std::make_unique<LazyEtsMethodWrapperLink[]>(numMethods_);
444 Span<LazyEtsMethodWrapperLink> etsMethodWrappers(etsMethodWrappers_.get(), numMethods_);
445 size_t methodIdx = 0;
446
447 for (auto &method : methods) {
448 ASSERT(!method->IsConstructor());
449 auto lazyLink = &etsMethodWrappers[methodIdx++];
450 lazyLink->Set(method);
451 jsProps.emplace_back(EtsMethodWrapper::MakeNapiProperty(method, lazyLink));
452 }
453
454 if (UNLIKELY(!IsEtsGlobalClass() && etsCtorLink_.GetUnresolved() == nullptr)) {
455 InteropCtx::Fatal("Class " + etsClass_->GetRuntimeClass()->GetName() + " has no constructor");
456 }
457
458 return jsProps;
459 }
460
LookupBaseWrapper(EtsClass * klass)461 EtsClassWrapper *EtsClassWrapper::LookupBaseWrapper(EtsClass *klass)
462 {
463 for (auto wclass = this; wclass != nullptr; wclass = wclass->baseWrapper_) {
464 if (wclass->etsClass_ == klass) {
465 return wclass;
466 }
467 }
468 return nullptr;
469 }
470
SimulateJSInheritance(napi_env env,napi_value jsCtor,napi_value jsBaseCtor)471 static void SimulateJSInheritance(napi_env env, napi_value jsCtor, napi_value jsBaseCtor)
472 {
473 napi_value builtinObject;
474 napi_value setprotoFn;
475 NAPI_CHECK_FATAL(napi_get_named_property(env, GetGlobal(env), "Object", &builtinObject));
476 NAPI_CHECK_FATAL(napi_get_named_property(env, builtinObject, "setPrototypeOf", &setprotoFn));
477
478 auto setproto = [&env, &builtinObject, &setprotoFn](napi_value obj, napi_value proto) {
479 std::array args = {obj, proto};
480 NAPI_CHECK_FATAL(NapiCallFunction(env, builtinObject, setprotoFn, args.size(), args.data(), nullptr));
481 };
482
483 napi_value cprototype;
484 napi_value baseCprototype;
485 NAPI_CHECK_FATAL(napi_get_named_property(env, jsCtor, "prototype", &cprototype));
486 NAPI_CHECK_FATAL(napi_get_named_property(env, jsBaseCtor, "prototype", &baseCprototype));
487
488 setproto(jsCtor, jsBaseCtor);
489 setproto(cprototype, baseCprototype);
490 }
491
492 /*static*/
Create(InteropCtx * ctx,EtsClass * etsClass,const char * jsBuiltinName,const OverloadsMap * overloads)493 std::unique_ptr<EtsClassWrapper> EtsClassWrapper::Create(InteropCtx *ctx, EtsClass *etsClass, const char *jsBuiltinName,
494 const OverloadsMap *overloads)
495 {
496 auto env = ctx->GetJSEnv();
497
498 // CC-OFFNXT(G.RES.09) private constructor
499 // NOLINTNEXTLINE(readability-identifier-naming)
500 auto _this = std::unique_ptr<EtsClassWrapper>(new EtsClassWrapper(etsClass));
501 if (!_this->SetupHierarchy(ctx, jsBuiltinName)) {
502 return nullptr;
503 }
504
505 auto [fields, methods] = _this->CalculateProperties(overloads);
506 for (auto method : methods) {
507 const std::string methodName(method->GetName());
508 auto baseClassWrapper = _this->baseWrapper_;
509 while (nullptr != baseClassWrapper) {
510 EtsMethodSet *baseMethodSet = baseClassWrapper->GetMethod(methodName);
511 if (nullptr != baseMethodSet) {
512 method->SetBaseMethodSet(baseMethodSet);
513 break;
514 }
515 baseClassWrapper = baseClassWrapper->baseWrapper_;
516 }
517 }
518
519 auto jsProps = _this->BuildJSProperties({fields.data(), fields.size()}, {methods.data(), methods.size()});
520
521 // NOTE(vpukhov): fatal no-public-fields check when escompat adopt accessors
522 if (_this->HasBuiltin() && !fields.empty()) {
523 INTEROP_LOG(ERROR) << "built-in class " << etsClass->GetDescriptor() << " has field properties";
524 }
525 if (_this->HasBuiltin() && etsClass->IsFinal()) {
526 INTEROP_LOG(FATAL) << "built-in class " << etsClass->GetDescriptor() << " is final";
527 }
528 // NOTE(vpukhov): forbid "true" ets-field overriding in js-derived class, as it cannot be proxied back
529 if (!etsClass->IsFinal()) {
530 auto ungroupedMethods = CollectAllPandaMethods(methods.begin(), methods.end());
531 _this->jsproxyWrapper_ =
532 js_proxy::JSProxy::Create(etsClass, {ungroupedMethods.data(), ungroupedMethods.size()});
533 }
534
535 napi_value jsCtor {};
536 NAPI_CHECK_FATAL(napi_define_class(env, etsClass->GetDescriptor(), NAPI_AUTO_LENGTH,
537 EtsClassWrapper::JSCtorCallback, _this.get(), jsProps.size(), jsProps.data(),
538 &jsCtor));
539
540 auto base = _this->baseWrapper_;
541 napi_value fakeSuper = _this->HasBuiltin() ? _this->GetBuiltin(env)
542 : (base->HasBuiltin() ? base->GetBuiltin(env) : base->GetJsCtor(env));
543
544 SimulateJSInheritance(env, jsCtor, fakeSuper);
545 NAPI_CHECK_FATAL(NapiObjectSeal(env, jsCtor));
546 NAPI_CHECK_FATAL(napi_create_reference(env, jsCtor, 1, &_this->jsCtorRef_));
547
548 return _this;
549 }
550
551 /*static*/
JSCtorCallback(napi_env env,napi_callback_info cinfo)552 napi_value EtsClassWrapper::JSCtorCallback(napi_env env, napi_callback_info cinfo)
553 {
554 EtsCoroutine *coro = EtsCoroutine::GetCurrent();
555 InteropCtx *ctx = InteropCtx::Current(coro);
556 INTEROP_CODE_SCOPE_JS(coro, env);
557
558 napi_value jsThis;
559 size_t argc;
560 void *data;
561 NAPI_CHECK_FATAL(napi_get_cb_info(env, cinfo, &argc, nullptr, &jsThis, &data));
562 auto etsClassWrapper = reinterpret_cast<EtsClassWrapper *>(data);
563
564 EtsObject *etsObject = ctx->AcquirePendingNewInstance();
565
566 if (LIKELY(etsObject != nullptr)) {
567 // Create shared reference for existing ets object
568 SharedReferenceStorage *storage = ctx->GetSharedRefStorage();
569 if (UNLIKELY(!storage->CreateETSObjectRef(ctx, etsObject, jsThis))) {
570 ASSERT(InteropCtx::SanityJSExceptionPending());
571 return nullptr;
572 }
573 NAPI_CHECK_FATAL(napi_object_seal(env, jsThis));
574 return nullptr;
575 }
576
577 auto jsArgs = ctx->GetTempArgs<napi_value>(argc);
578 NAPI_CHECK_FATAL(napi_get_cb_info(env, cinfo, &argc, jsArgs->data(), nullptr, nullptr));
579
580 napi_value jsNewtarget;
581 NAPI_CHECK_FATAL(napi_get_new_target(env, cinfo, &jsNewtarget));
582
583 // create new object and wrap it
584 if (UNLIKELY(!etsClassWrapper->CreateAndWrap(env, jsNewtarget, jsThis, *jsArgs))) {
585 ASSERT(InteropCtx::SanityJSExceptionPending());
586 return nullptr;
587 }
588
589 // NOTE(ivagin): JS constructor is not required to return 'this', but ArkUI NAPI requires it
590 return jsThis;
591 }
592
CreateAndWrap(napi_env env,napi_value jsNewtarget,napi_value jsThis,Span<napi_value> jsArgs)593 bool EtsClassWrapper::CreateAndWrap(napi_env env, napi_value jsNewtarget, napi_value jsThis, Span<napi_value> jsArgs)
594 {
595 EtsCoroutine *coro = EtsCoroutine::GetCurrent();
596 InteropCtx *ctx = InteropCtx::Current(coro);
597
598 if (UNLIKELY(!CheckClassInitialized<true>(etsClass_->GetRuntimeClass()))) {
599 ctx->ForwardEtsException(coro);
600 return false;
601 }
602
603 bool notExtensible;
604 NAPI_CHECK_FATAL(napi_strict_equals(env, jsNewtarget, GetJsCtor(env), ¬Extensible));
605
606 EtsClass *instanceClass {};
607
608 if (LIKELY(notExtensible)) {
609 #ifndef PANDA_TARGET_OHOS
610 // In case of OHOS sealed object can't be wrapped, therefore seal it after wrapping
611 NAPI_CHECK_FATAL(napi_object_seal(env, jsThis));
612 #endif // PANDA_TARGET_OHOS
613 instanceClass = etsClass_;
614 } else {
615 if (UNLIKELY(jsproxyWrapper_ == nullptr)) {
616 ctx->ThrowJSTypeError(env, std::string("Proxy for ") + etsClass_->GetDescriptor() + " is not extensible");
617 return false;
618 }
619 instanceClass = jsproxyWrapper_->GetProxyClass();
620 }
621
622 LocalObjectHandle<EtsObject> etsObject(coro, EtsObject::Create(instanceClass));
623 if (UNLIKELY(etsObject.GetPtr() == nullptr)) {
624 return false;
625 }
626
627 SharedReference *sharedRef;
628 if (LIKELY(notExtensible)) {
629 sharedRef = ctx->GetSharedRefStorage()->CreateETSObjectRef(ctx, etsObject.GetPtr(), jsThis);
630 #ifdef PANDA_TARGET_OHOS
631 // In case of OHOS sealed object can't be wrapped, therefore seal it after wrapping
632 NAPI_CHECK_FATAL(napi_object_seal(env, jsThis));
633 #endif // PANDA_TARGET_OHOS
634 } else {
635 sharedRef = ctx->GetSharedRefStorage()->CreateHybridObjectRef(ctx, etsObject.GetPtr(), jsThis);
636 }
637 if (UNLIKELY(sharedRef == nullptr)) {
638 ASSERT(InteropCtx::SanityJSExceptionPending());
639 return false;
640 }
641
642 EtsMethodWrapper *ctorWrapper = EtsMethodWrapper::ResolveLazyLink(ctx, etsCtorLink_);
643 ASSERT(ctorWrapper != nullptr);
644 EtsMethod *ctorMethod = ctorWrapper->GetEtsMethod(jsArgs.Size());
645 ASSERT(ctorMethod->IsInstanceConstructor());
646
647 napi_value callRes = CallETSInstance(coro, ctx, ctorMethod->GetPandaMethod(), jsArgs, etsObject.GetPtr());
648 return callRes != nullptr;
649 }
650
651 } // namespace ark::ets::interop::js::ets_proxy
652