• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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), &notExtensible));
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