• 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/js_value_call.h"
23 #include "plugins/ets/runtime/interop_js/napi_env_scope.h"
24 #include "runtime/mem/local_object_handle.h"
25 
26 namespace panda::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     EtsClass *etsClass = EtsClass::FromRuntimeClass(klass);
160     EtsClassWrapper *wrapper = EtsClassWrapper::Get(ctx, etsClass);
161     if (UNLIKELY(wrapper == nullptr)) {
162         return nullptr;
163     }
164     ASSERT(wrapper->etsClass_ == etsClass);
165     return std::make_unique<JSRefConvertEtsProxy>(wrapper);
166 }
167 
168 class JSRefConvertJSProxy : public JSRefConvert {
169 public:
JSRefConvertJSProxy()170     explicit JSRefConvertJSProxy() : JSRefConvert(this) {}
171 
WrapImpl(InteropCtx * ctx,EtsObject * etsObject)172     napi_value WrapImpl(InteropCtx *ctx, EtsObject *etsObject)
173     {
174         SharedReferenceStorage *storage = ctx->GetSharedRefStorage();
175         INTEROP_FATAL_IF(!storage->HasReference(etsObject));
176         SharedReference *sharedRef = storage->GetReference(etsObject);
177         ASSERT(sharedRef != nullptr);
178         return sharedRef->GetJsObject(ctx->GetJSEnv());
179     }
180 
UnwrapImpl(InteropCtx * ctx,napi_value jsValue)181     EtsObject *UnwrapImpl(InteropCtx *ctx, [[maybe_unused]] napi_value jsValue)
182     {
183         ctx->Fatal("Unwrap called on JSProxy class");
184     }
185 };
186 
187 /*static*/
CreateJSRefConvertJSProxy(InteropCtx * ctx,Class * klass)188 std::unique_ptr<JSRefConvert> EtsClassWrapper::CreateJSRefConvertJSProxy([[maybe_unused]] InteropCtx *ctx,
189                                                                          [[maybe_unused]] Class *klass)
190 {
191     ASSERT(js_proxy::JSProxy::IsProxyClass(klass));
192     return std::make_unique<JSRefConvertJSProxy>();
193 }
194 
195 /*static*/
Get(InteropCtx * ctx,EtsClass * etsClass)196 EtsClassWrapper *EtsClassWrapper::Get(InteropCtx *ctx, EtsClass *etsClass)
197 {
198     EtsClassWrappersCache *cache = ctx->GetEtsClassWrappersCache();
199 
200     ASSERT(etsClass != nullptr);
201     EtsClassWrapper *etsClassWrapper = cache->Lookup(etsClass);
202     if (LIKELY(etsClassWrapper != nullptr)) {
203         return etsClassWrapper;
204     }
205 
206     ASSERT(!etsClass->IsPrimitive() && etsClass->GetComponentType() == nullptr);
207     ASSERT(ctx->GetRefConvertCache()->Lookup(etsClass->GetRuntimeClass()) == nullptr);
208 
209     if (IsStdClass(etsClass)) {
210         ctx->Fatal(std::string("ets_proxy requested for ") + etsClass->GetDescriptor() + " must add or forbid");
211     }
212     ASSERT(!js_proxy::JSProxy::IsProxyClass((etsClass->GetRuntimeClass())));
213 
214     std::unique_ptr<EtsClassWrapper> wrapper = EtsClassWrapper::Create(ctx, etsClass);
215     if (UNLIKELY(wrapper == nullptr)) {
216         return nullptr;
217     }
218     return cache->Insert(etsClass, std::move(wrapper));
219 }
220 
SetupHierarchy(InteropCtx * ctx,const char * jsBuiltinName)221 bool EtsClassWrapper::SetupHierarchy(InteropCtx *ctx, const char *jsBuiltinName)
222 {
223     ASSERT(etsClass_->GetBase() != etsClass_);
224     if (etsClass_->GetBase() != nullptr) {
225         baseWrapper_ = EtsClassWrapper::Get(ctx, etsClass_->GetBase());
226         if (baseWrapper_ == nullptr) {
227             return false;
228         }
229     }
230 
231     if (jsBuiltinName != nullptr) {
232         auto env = ctx->GetJSEnv();
233         napi_value jsBuiltinCtor;
234         NAPI_CHECK_FATAL(napi_get_named_property(env, GetGlobal(env), jsBuiltinName, &jsBuiltinCtor));
235         NAPI_CHECK_FATAL(napi_create_reference(env, jsBuiltinCtor, 1, &jsBuiltinCtorRef_));
236     }
237     return true;
238 }
239 
CalculateProperties(const OverloadsMap * overloads)240 std::pair<std::vector<Field *>, std::vector<Method *>> EtsClassWrapper::CalculateProperties(
241     const OverloadsMap *overloads)
242 {
243     auto fatalMethodOverloaded = [](Method *method) {
244         for (auto &m : method->GetClass()->GetMethods()) {
245             if (utf::IsEqual(m.GetName().data, method->GetName().data)) {
246                 INTEROP_LOG(ERROR) << "overload: " << EtsMethod::FromRuntimeMethod(&m)->GetMethodSignature(true);
247             }
248         }
249         InteropCtx::Fatal(std::string("Method ") + utf::Mutf8AsCString(method->GetName().data) + " of class " +
250                           utf::Mutf8AsCString(method->GetClass()->GetDescriptor()) + " is overloaded");
251     };
252     auto fatalNoMethod = [](Class *klass, const uint8_t *name, const char *signature) {
253         InteropCtx::Fatal(std::string("No method ") + utf::Mutf8AsCString(name) + " " + signature + " in " +
254                           klass->GetName());
255     };
256 
257     auto klass = etsClass_->GetRuntimeClass();
258     std::unordered_map<uint8_t const *, std::variant<Method *, Field *>, utf::Mutf8Hash, utf::Mutf8Equal> props;
259 
260     // Collect fields
261     for (auto &f : klass->GetFields()) {
262         if (f.IsPublic()) {
263             props.insert({f.GetName().data, &f});
264         }
265     }
266     // Select preferred overloads
267     if (overloads != nullptr) {
268         for (auto &[name, signature] : *overloads) {
269             Method *method = etsClass_->GetDirectMethod(name, signature)->GetPandaMethod();
270             if (UNLIKELY(method == nullptr)) {
271                 fatalNoMethod(klass, name, signature);
272             }
273             auto it = props.insert({method->GetName().data, method});
274             INTEROP_FATAL_IF(!it.second);
275         }
276     }
277 
278     // If class is std.core.Object
279     auto klassDesc = utf::Mutf8AsCString(klass->GetDescriptor());
280     if (klassDesc == panda_file_items::class_descriptors::OBJECT) {
281         // Ingore all methods of std.core.Object due to names intersection with JS Object
282         // Keep constructors only
283         auto objCtors = etsClass_->GetConstructors();
284         // Assuming that ETS StdLib guarantee that Object has the only one ctor
285         ASSERT(objCtors.size() == 1);
286         auto ctor = objCtors[0]->GetPandaMethod();
287         props.insert({ctor->GetName().data, ctor});
288         // NOTE(shumilov-petr): Think about removing methods from std.core.Object
289         // that are already presented in JS Object, others should be kept
290     } else {
291         // Collect methods
292         for (auto &m : klass->GetMethods()) {
293             if (m.IsPrivate()) {
294                 continue;
295             }
296             auto name = m.GetName().data;
297             if (overloads != nullptr && overloads->find(name) != overloads->end()) {
298                 continue;
299             }
300             auto it = props.insert({m.GetName().data, &m});
301             if (UNLIKELY(!it.second)) {
302                 fatalMethodOverloaded(&m);
303             }
304         }
305     }
306 
307     auto hasSquashedProto = [](EtsClassWrapper *wclass) {
308         ASSERT(wclass->HasBuiltin() || wclass->baseWrapper_ != nullptr);
309 #if PANDA_TARGET_OHOS
310         return wclass->HasBuiltin() || wclass->baseWrapper_->HasBuiltin();
311 #else
312         (void)wclass;
313         return true;  // NOTE(vpukhov): some napi implementations add explicit receiver checks in call handler,
314                       // thus method inheritance via prototype chain wont work
315 #endif
316     };
317     if (hasSquashedProto(this)) {
318         // Copy properties of base classes if we have to split prototype chain
319         for (auto wclass = baseWrapper_; wclass != nullptr; wclass = wclass->baseWrapper_) {
320             for (auto &wfield : wclass->GetFields()) {
321                 Field *field = wfield.GetField();
322                 props.insert({field->GetName().data, field});
323             }
324             for (auto &link : wclass->GetMethods()) {
325                 Method *method = link.IsResolved() ? link.GetResolved()->GetMethod() : link.GetUnresolved();
326                 props.insert({method->GetName().data, method});
327             }
328 
329             if (hasSquashedProto(wclass)) {
330                 break;
331             }
332         }
333     }
334 
335     std::vector<Method *> methods;
336     std::vector<Field *> fields;
337 
338     for (auto &[n, p] : props) {
339         if (std::holds_alternative<Method *>(p)) {
340             auto method = std::get<Method *>(p);
341             if (method->IsConstructor()) {
342                 if (!method->IsStatic()) {
343                     etsCtorLink_ = LazyEtsMethodWrapperLink(method);
344                 }
345             } else {
346                 methods.push_back(method);
347             }
348         } else if (std::holds_alternative<Field *>(p)) {
349             fields.push_back(std::get<Field *>(p));
350         } else {
351             UNREACHABLE();
352         }
353     }
354 
355     return {fields, methods};
356 }
357 
BuildJSProperties(Span<Field * > fields,Span<Method * > methods)358 std::vector<napi_property_descriptor> EtsClassWrapper::BuildJSProperties(Span<Field *> fields, Span<Method *> methods)
359 {
360     std::vector<napi_property_descriptor> jsProps;
361     jsProps.reserve(fields.size() + methods.size());
362 
363     // Process fields
364     numFields_ = fields.size();
365     // NOLINTNEXTLINE(modernize-avoid-c-arrays)
366     etsFieldWrappers_ = std::make_unique<EtsFieldWrapper[]>(numFields_);
367     Span<EtsFieldWrapper> etsFieldWrappers(etsFieldWrappers_.get(), numFields_);
368     size_t fieldIdx = 0;
369 
370     for (Field *field : fields) {
371         auto wfield = &etsFieldWrappers[fieldIdx++];
372         if (field->IsStatic()) {
373             EtsClassWrapper *fieldWclass = LookupBaseWrapper(EtsClass::FromRuntimeClass(field->GetClass()));
374             ASSERT(fieldWclass != nullptr);
375             jsProps.emplace_back(wfield->MakeStaticProperty(fieldWclass, field));
376         } else {
377             jsProps.emplace_back(wfield->MakeInstanceProperty(this, field));
378         }
379     }
380 
381     // Process methods
382     numMethods_ = methods.size();
383     // NOLINTNEXTLINE(modernize-avoid-c-arrays)
384     etsMethodWrappers_ = std::make_unique<LazyEtsMethodWrapperLink[]>(numMethods_);
385     Span<LazyEtsMethodWrapperLink> etsMethodWrappers(etsMethodWrappers_.get(), numMethods_);
386     size_t methodIdx = 0;
387 
388 #ifndef NDEBUG
389     std::unordered_set<const uint8_t *> methodNames;
390 #endif
391 
392     for (auto &method : methods) {
393         ASSERT(!method->IsConstructor());
394         ASSERT(methodNames.insert(method->GetName().data).second);
395         auto lazyLink = &etsMethodWrappers[methodIdx++];
396         lazyLink->Set(method);
397         jsProps.emplace_back(EtsMethodWrapper::MakeNapiProperty(method, lazyLink));
398     }
399     ASSERT(etsCtorLink_.GetUnresolved() != nullptr);
400 
401     return jsProps;
402 }
403 
LookupBaseWrapper(EtsClass * klass)404 EtsClassWrapper *EtsClassWrapper::LookupBaseWrapper(EtsClass *klass)
405 {
406     for (auto wclass = this; wclass != nullptr; wclass = wclass->baseWrapper_) {
407         if (wclass->etsClass_ == klass) {
408             return wclass;
409         }
410     }
411     return nullptr;
412 }
413 
SimulateJSInheritance(napi_env env,napi_value jsCtor,napi_value jsBaseCtor)414 static void SimulateJSInheritance(napi_env env, napi_value jsCtor, napi_value jsBaseCtor)
415 {
416     napi_value builtinObject;
417     napi_value setprotoFn;
418     NAPI_CHECK_FATAL(napi_get_named_property(env, GetGlobal(env), "Object", &builtinObject));
419     NAPI_CHECK_FATAL(napi_get_named_property(env, builtinObject, "setPrototypeOf", &setprotoFn));
420 
421     auto setproto = [&env, &builtinObject, &setprotoFn](napi_value obj, napi_value proto) {
422         std::array args = {obj, proto};
423         NAPI_CHECK_FATAL(NapiCallFunction(env, builtinObject, setprotoFn, args.size(), args.data(), nullptr));
424     };
425 
426     napi_value cprototype;
427     napi_value baseCprototype;
428     NAPI_CHECK_FATAL(napi_get_named_property(env, jsCtor, "prototype", &cprototype));
429     NAPI_CHECK_FATAL(napi_get_named_property(env, jsBaseCtor, "prototype", &baseCprototype));
430 
431     setproto(jsCtor, jsBaseCtor);
432     setproto(cprototype, baseCprototype);
433 }
434 
435 /*static*/
Create(InteropCtx * ctx,EtsClass * etsClass,const char * jsBuiltinName,const OverloadsMap * overloads)436 std::unique_ptr<EtsClassWrapper> EtsClassWrapper::Create(InteropCtx *ctx, EtsClass *etsClass, const char *jsBuiltinName,
437                                                          const OverloadsMap *overloads)
438 {
439     auto env = ctx->GetJSEnv();
440 
441     // NOLINTNEXTLINE(readability-identifier-naming)
442     auto _this = std::unique_ptr<EtsClassWrapper>(new EtsClassWrapper(etsClass));
443     if (!_this->SetupHierarchy(ctx, jsBuiltinName)) {
444         return nullptr;
445     }
446 
447     auto [fields, methods] = _this->CalculateProperties(overloads);
448     auto jsProps = _this->BuildJSProperties({fields.data(), fields.size()}, {methods.data(), methods.size()});
449 
450     // NOTE(vpukhov): fatal no-public-fields check when escompat adopt accessors
451     if (_this->HasBuiltin() && !fields.empty()) {
452         INTEROP_LOG(ERROR) << "built-in class " << etsClass->GetDescriptor() << " has field properties";
453     }
454     // NOTE(vpukhov): forbid "true" ets-field overriding in js-derived class, as it cannot be proxied back
455     //                simple solution: ban JSProxy if !fields.empty()
456     _this->jsproxyWrapper_ = js_proxy::JSProxy::Create(etsClass, {methods.data(), methods.size()});
457 
458     napi_value jsCtor {};
459     NAPI_CHECK_FATAL(napi_define_class(env, etsClass->GetDescriptor(), NAPI_AUTO_LENGTH,
460                                        EtsClassWrapper::JSCtorCallback, _this.get(), jsProps.size(), jsProps.data(),
461                                        &jsCtor));
462 
463     auto base = _this->baseWrapper_;
464     napi_value fakeSuper = _this->HasBuiltin() ? _this->GetBuiltin(env)
465                                                : (base->HasBuiltin() ? base->GetBuiltin(env) : base->GetJsCtor(env));
466 
467     SimulateJSInheritance(env, jsCtor, fakeSuper);
468     NAPI_CHECK_FATAL(NapiObjectSeal(env, jsCtor));
469     NAPI_CHECK_FATAL(napi_create_reference(env, jsCtor, 1, &_this->jsCtorRef_));
470 
471     return _this;
472 }
473 
474 /*static*/
JSCtorCallback(napi_env env,napi_callback_info cinfo)475 napi_value EtsClassWrapper::JSCtorCallback(napi_env env, napi_callback_info cinfo)
476 {
477     EtsCoroutine *coro = EtsCoroutine::GetCurrent();
478     InteropCtx *ctx = InteropCtx::Current(coro);
479     [[maybe_unused]] EtsJSNapiEnvScope envscope(ctx, env);
480 
481     napi_value jsThis;
482     size_t argc;
483     void *data;
484     NAPI_CHECK_FATAL(napi_get_cb_info(env, cinfo, &argc, nullptr, &jsThis, &data));
485     auto etsClassWrapper = reinterpret_cast<EtsClassWrapper *>(data);
486 
487     EtsObject *etsObject = ctx->AcquirePendingNewInstance();
488 
489     if (LIKELY(etsObject != nullptr)) {
490         // Create shared reference for existing ets object
491         SharedReferenceStorage *storage = ctx->GetSharedRefStorage();
492         if (UNLIKELY(!storage->CreateETSObjectRef(ctx, etsObject, jsThis))) {
493             ASSERT(InteropCtx::SanityJSExceptionPending());
494             return nullptr;
495         }
496         NAPI_CHECK_FATAL(napi_object_seal(env, jsThis));
497         return nullptr;
498     }
499 
500     auto jsArgs = ctx->GetTempArgs<napi_value>(argc);
501     NAPI_CHECK_FATAL(napi_get_cb_info(env, cinfo, &argc, jsArgs->data(), nullptr, nullptr));
502 
503     napi_value jsNewtarget;
504     NAPI_CHECK_FATAL(napi_get_new_target(env, cinfo, &jsNewtarget));
505 
506     // create new object and wrap it
507     if (UNLIKELY(!etsClassWrapper->CreateAndWrap(env, jsNewtarget, jsThis, *jsArgs))) {
508         ASSERT(InteropCtx::SanityJSExceptionPending());
509         return nullptr;
510     }
511 
512     // NOTE(ivagin): JS constructor is not required to return 'this', but ArkUI NAPI requires it
513     return jsThis;
514 }
515 
CreateAndWrap(napi_env env,napi_value jsNewtarget,napi_value jsThis,Span<napi_value> jsArgs)516 bool EtsClassWrapper::CreateAndWrap(napi_env env, napi_value jsNewtarget, napi_value jsThis, Span<napi_value> jsArgs)
517 {
518     EtsCoroutine *coro = EtsCoroutine::GetCurrent();
519     InteropCtx *ctx = InteropCtx::Current(coro);
520 
521     ScopedManagedCodeThread managedScope(coro);
522 
523     if (UNLIKELY(!CheckClassInitialized<true>(etsClass_->GetRuntimeClass()))) {
524         ctx->ForwardEtsException(coro);
525         return false;
526     }
527 
528     bool notExtensible;
529     NAPI_CHECK_FATAL(napi_strict_equals(env, jsNewtarget, GetJsCtor(env), &notExtensible));
530 
531     EtsClass *instanceClass {};
532 
533     if (LIKELY(notExtensible)) {
534 #ifndef PANDA_TARGET_OHOS
535         // In case of OHOS sealed object can't be wrapped, therefore seal it after wrapping
536         NAPI_CHECK_FATAL(napi_object_seal(env, jsThis));
537 #endif  // PANDA_TARGET_OHOS
538         instanceClass = etsClass_;
539     } else {
540         if (UNLIKELY(jsproxyWrapper_ == nullptr)) {
541             ctx->ThrowJSTypeError(env, std::string("Proxy for ") + etsClass_->GetDescriptor() + " is not extensible");
542             return false;
543         }
544         instanceClass = jsproxyWrapper_->GetProxyClass();
545     }
546 
547     LocalObjectHandle<EtsObject> etsObject(coro, EtsObject::Create(instanceClass));
548     if (UNLIKELY(etsObject.GetPtr() == nullptr)) {
549         return false;
550     }
551 
552     SharedReference *sharedRef;
553     if (LIKELY(notExtensible)) {
554         sharedRef = ctx->GetSharedRefStorage()->CreateETSObjectRef(ctx, etsObject.GetPtr(), jsThis);
555 #ifdef PANDA_TARGET_OHOS
556         // In case of OHOS sealed object can't be wrapped, therefore seal it after wrapping
557         NAPI_CHECK_FATAL(napi_object_seal(env, jsThis));
558 #endif  // PANDA_TARGET_OHOS
559     } else {
560         sharedRef = ctx->GetSharedRefStorage()->CreateHybridObjectRef(ctx, etsObject.GetPtr(), jsThis);
561     }
562     if (UNLIKELY(sharedRef == nullptr)) {
563         ASSERT(InteropCtx::SanityJSExceptionPending());
564         return false;
565     }
566 
567     EtsMethodWrapper *ctorWrapper = EtsMethodWrapper::ResolveLazyLink(ctx, etsCtorLink_);
568     ASSERT(ctorWrapper != nullptr);
569     EtsMethod *ctorMethod = ctorWrapper->GetEtsMethod();
570     ASSERT(ctorMethod->IsInstanceConstructor());
571 
572     napi_value callRes = EtsCallImplInstance(coro, ctx, ctorMethod->GetPandaMethod(), jsArgs, etsObject.GetPtr());
573     return callRes != nullptr;
574 }
575 
576 }  // namespace panda::ets::interop::js::ets_proxy
577