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_class_wrapper.h"
17 #include <js_native_api.h>
18 #include <js_native_api_types.h>
19 #include <iostream>
20 #include <ostream>
21 #include <vector>
22
23 #include "include/mem/panda_containers.h"
24 #include "interop_js/interop_common.h"
25 #include "interop_js/js_proxy/js_proxy.h"
26 #include "interop_js/js_refconvert.h"
27 #include "plugins/ets/runtime/ets_handle.h"
28 #include "plugins/ets/runtime/ets_handle_scope.h"
29 #include "plugins/ets/runtime/interop_js/interop_context.h"
30 #include "plugins/ets/runtime/interop_js/ets_proxy/shared_reference.h"
31 #include "plugins/ets/runtime/interop_js/call/call.h"
32 #include "plugins/ets/runtime/interop_js/code_scopes.h"
33 #include "runtime/mem/local_object_handle.h"
34 #include "plugins/ets/runtime/ets_platform_types.h"
35
36 // NOLINTBEGIN(readability-identifier-naming, readability-redundant-declaration)
37 // CC-OFFNXT(G.FMT.10-CPP) project code style
38 napi_status __attribute__((weak)) napi_get_ets_implements(napi_env env, napi_value jsValue, napi_value *result);
39 // NOLINTEND(readability-identifier-naming, readability-redundant-declaration)
40
41 namespace ark::ets::interop::js::ets_proxy {
42
43 class JSRefConvertEtsProxy : public JSRefConvert {
44 public:
JSRefConvertEtsProxy(EtsClassWrapper * etsClassWrapper)45 explicit JSRefConvertEtsProxy(EtsClassWrapper *etsClassWrapper)
46 : JSRefConvert(this), etsClassWrapper_(etsClassWrapper)
47 {
48 }
49
WrapImpl(InteropCtx * ctx,EtsObject * etsObject)50 napi_value WrapImpl(InteropCtx *ctx, EtsObject *etsObject)
51 {
52 return etsClassWrapper_->Wrap(ctx, etsObject);
53 }
UnwrapImpl(InteropCtx * ctx,napi_value jsValue)54 EtsObject *UnwrapImpl(InteropCtx *ctx, napi_value jsValue)
55 {
56 return etsClassWrapper_->Unwrap(ctx, jsValue);
57 }
58
59 private:
60 EtsClassWrapper *etsClassWrapper_ {};
61 };
62
Wrap(InteropCtx * ctx,EtsObject * etsObject)63 napi_value EtsClassWrapper::Wrap(InteropCtx *ctx, EtsObject *etsObject)
64 {
65 CheckClassInitialized(etsClass_->GetRuntimeClass());
66
67 napi_env env = ctx->GetJSEnv();
68
69 ASSERT(etsObject != nullptr);
70 ASSERT(etsClass_ == etsObject->GetClass());
71
72 SharedReferenceStorage *storage = ctx->GetSharedRefStorage();
73 if (LIKELY(storage->HasReference(etsObject, env))) {
74 return storage->GetJsObject(etsObject, env);
75 }
76
77 napi_value jsValue;
78 // etsObject will be wrapped in jsValue in responce to jsCtor call
79 auto *coro = EtsCoroutine::GetCurrent();
80 [[maybe_unused]] EtsHandleScope scope(coro);
81 EtsHandle<EtsObject> handle(coro, etsObject);
82 ctx->SetPendingNewInstance(handle);
83 {
84 ScopedNativeCodeThread nativeScope(coro);
85 NAPI_CHECK_FATAL(napi_new_instance(env, GetJsCtor(env), 0, nullptr, &jsValue));
86 }
87
88 // NOTE(MockMockBlack, #IC59ZS): put proxy to SharedReferenceStorage more prettily
89 if (this->needProxy_) {
90 ASSERT(storage->HasReference(etsObject, env));
91 return storage->GetJsObject(etsObject, env);
92 }
93 return jsValue;
94 }
95
96 // Use UnwrapEtsProxy if you expect exactly a EtsProxy
Unwrap(InteropCtx * ctx,napi_value jsValue)97 EtsObject *EtsClassWrapper::Unwrap(InteropCtx *ctx, napi_value jsValue)
98 {
99 CheckClassInitialized(etsClass_->GetRuntimeClass());
100
101 napi_env env = ctx->GetJSEnv();
102
103 ASSERT(!IsUndefined(env, jsValue));
104
105 // Check if object has SharedReference
106 SharedReference *sharedRef = ctx->GetSharedRefStorage()->GetReference(env, jsValue);
107 if (LIKELY(sharedRef != nullptr)) {
108 EtsObject *etsObject = sharedRef->GetEtsObject();
109 if (UNLIKELY(!etsClass_->IsAssignableFrom(etsObject->GetClass()))) {
110 ThrowJSErrorNotAssignable(env, etsObject->GetClass(), etsClass_);
111 return nullptr;
112 }
113 return etsObject;
114 }
115
116 // Check if object is subtype of js builtin class
117 if (LIKELY(HasBuiltin())) {
118 ASSERT(jsBuiltinMatcher_ != nullptr);
119 auto res = jsBuiltinMatcher_(ctx, jsValue, false);
120 ASSERT(res != nullptr || ctx->SanityJSExceptionPending() || ctx->SanityETSExceptionPending());
121 return res;
122 }
123
124 InteropCtx::ThrowJSTypeError(env, std::string("Value is not assignable to ") + etsClass_->GetDescriptor());
125 return nullptr;
126 }
127
128 // Special method to ensure unwrapped object is not a JSProxy
UnwrapEtsProxy(InteropCtx * ctx,napi_value jsValue)129 EtsObject *EtsClassWrapper::UnwrapEtsProxy(InteropCtx *ctx, napi_value jsValue)
130 {
131 CheckClassInitialized(etsClass_->GetRuntimeClass());
132
133 napi_env env = ctx->GetJSEnv();
134
135 ASSERT(!IsNullOrUndefined(env, jsValue));
136
137 // Check if object has SharedReference
138 SharedReference *sharedRef = ctx->GetSharedRefStorage()->GetReference(env, jsValue);
139 if (LIKELY(sharedRef != nullptr)) {
140 EtsObject *etsObject = sharedRef->GetEtsObject();
141 if (UNLIKELY(!etsClass_->IsAssignableFrom(etsObject->GetClass()))) {
142 ThrowJSErrorNotAssignable(env, etsObject->GetClass(), etsClass_);
143 return nullptr;
144 }
145 if (UNLIKELY(!sharedRef->HasETSFlag())) {
146 InteropCtx::ThrowJSTypeError(env, std::string("JS object in context of EtsProxy of class ") +
147 etsClass_->GetDescriptor());
148 return nullptr;
149 }
150 return etsObject;
151 }
152 return nullptr;
153 }
154
ThrowJSErrorNotAssignable(napi_env env,EtsClass * fromKlass,EtsClass * toKlass)155 void EtsClassWrapper::ThrowJSErrorNotAssignable(napi_env env, EtsClass *fromKlass, EtsClass *toKlass)
156 {
157 const char *from = fromKlass->GetDescriptor();
158 const char *to = toKlass->GetDescriptor();
159 InteropCtx::ThrowJSTypeError(env, std::string(from) + " is not assignable to " + to);
160 }
161
CreateJSBuiltinProxy(InteropCtx * ctx,napi_value jsValue)162 EtsObject *EtsClassWrapper::CreateJSBuiltinProxy(InteropCtx *ctx, napi_value jsValue)
163 {
164 ASSERT(jsproxyWrapper_ != nullptr);
165 auto *storage = ctx->GetSharedRefStorage();
166 ASSERT(storage->GetReference(ctx->GetJSEnv(), jsValue) == nullptr);
167
168 EtsObject *etsObject = EtsObject::Create(jsproxyWrapper_->GetProxyClass());
169 if (UNLIKELY(etsObject == nullptr)) {
170 ctx->ForwardEtsException(EtsCoroutine::GetCurrent());
171 return nullptr;
172 }
173
174 SharedReference *sharedRef = storage->CreateJSObjectRefwithWrap(ctx, etsObject, jsValue);
175 if (UNLIKELY(sharedRef == nullptr)) {
176 ASSERT(InteropCtx::SanityJSExceptionPending());
177 return nullptr;
178 }
179 return sharedRef->GetEtsObject(); // fetch again after gc
180 }
181
182 /*static*/
CreateJSRefConvertEtsProxy(InteropCtx * ctx,Class * klass)183 std::unique_ptr<JSRefConvert> EtsClassWrapper::CreateJSRefConvertEtsProxy(InteropCtx *ctx, Class *klass)
184 {
185 ASSERT(!klass->IsInterface());
186 EtsClass *etsClass = EtsClass::FromRuntimeClass(klass);
187 EtsClassWrapper *wrapper = EtsClassWrapper::Get(ctx, etsClass);
188 if (UNLIKELY(wrapper == nullptr)) {
189 return nullptr;
190 }
191 ASSERT(wrapper->etsClass_ == etsClass);
192 return std::make_unique<JSRefConvertEtsProxy>(wrapper);
193 }
194
195 class JSRefConvertJSProxy : public JSRefConvert {
196 public:
JSRefConvertJSProxy()197 explicit JSRefConvertJSProxy() : JSRefConvert(this) {}
198
WrapImpl(InteropCtx * ctx,EtsObject * etsObject)199 napi_value WrapImpl(InteropCtx *ctx, EtsObject *etsObject)
200 {
201 SharedReferenceStorage *storage = ctx->GetSharedRefStorage();
202 INTEROP_FATAL_IF(!storage->HasReference(etsObject, ctx->GetJSEnv()));
203 return storage->GetJsObject(etsObject, ctx->GetJSEnv());
204 }
205
UnwrapImpl(InteropCtx * ctx,napi_value jsValue)206 EtsObject *UnwrapImpl(InteropCtx *ctx, [[maybe_unused]] napi_value jsValue)
207 {
208 ctx->Fatal("Unwrap called on JSProxy class");
209 return nullptr;
210 }
211 };
212
213 /*static*/
CreateJSRefConvertJSProxy(InteropCtx * ctx,Class * klass)214 std::unique_ptr<JSRefConvert> EtsClassWrapper::CreateJSRefConvertJSProxy([[maybe_unused]] InteropCtx *ctx,
215 [[maybe_unused]] Class *klass)
216 {
217 ASSERT(js_proxy::JSProxy::IsProxyClass(klass));
218 return std::make_unique<JSRefConvertJSProxy>();
219 }
220
221 class JSRefConvertInterface : public JSRefConvert {
222 public:
JSRefConvertInterface(Class * klass)223 explicit JSRefConvertInterface(Class *klass) : JSRefConvert(this), klass_(klass)
224 {
225 ASSERT(klass->IsInterface());
226 }
227
WrapImpl(InteropCtx * ctx,EtsObject * etsObject)228 napi_value WrapImpl(InteropCtx *ctx, EtsObject *etsObject)
229 {
230 auto realConverter = JSRefConvertResolve(ctx, etsObject->GetClass()->GetRuntimeClass());
231 if (realConverter == nullptr) {
232 InteropFatal("Cannot get ref converter for object");
233 }
234 return realConverter->Wrap(ctx, etsObject);
235 }
236
UnwrapImpl(InteropCtx * ctx,napi_value jsValue)237 EtsObject *UnwrapImpl(InteropCtx *ctx, napi_value jsValue)
238 {
239 auto *coro = EtsCoroutine::GetCurrent();
240 napi_env env = ctx->GetJSEnv();
241 SharedReference *sharedRef = ctx->GetSharedRefStorage()->GetReference(env, jsValue);
242 if (LIKELY(sharedRef != nullptr)) {
243 EtsObject *etsObject = sharedRef->GetEtsObject();
244 return etsObject;
245 }
246 if (IsStdClass(klass_)) {
247 auto *objectConverter =
248 ctx->GetEtsClassWrappersCache()->Lookup(EtsClass::FromRuntimeClass(ctx->GetObjectClass()));
249 auto *ret = objectConverter->Unwrap(ctx, jsValue);
250 ASSERT(ret != nullptr);
251 if (!ret->IsInstanceOf(EtsClass::FromRuntimeClass(klass_))) {
252 ctx->ThrowJSTypeError(ctx->GetJSEnv(), "object of type " +
253 ret->GetClass()->GetRuntimeClass()->GetName() +
254 " is not assignable to " + klass_->GetName());
255 return nullptr;
256 }
257 return ret;
258 }
259
260 napi_value result;
261 NAPI_CHECK_FATAL(napi_get_ets_implements(env, jsValue, &result));
262 if (GetValueType(env, result) != napi_string) {
263 ctx->ThrowJSTypeError(ctx->GetJSEnv(), std::string("object is not a type of Interface: ") +
264 utf::Mutf8AsCString(klass_->GetDescriptor()));
265 return nullptr;
266 }
267 auto interfaceName = GetString(env, result);
268 auto proxy = ctx->GetInterfaceProxyInstance(interfaceName);
269 if (proxy == nullptr) {
270 auto interfaces = GetInterfaceClass(ctx, interfaceName);
271 proxy = js_proxy::JSProxy::CreateInterfaceProxy(interfaces, interfaceName);
272 ctx->SetInterfaceProxyInstance(interfaceName, proxy);
273 }
274 LocalObjectHandle<EtsObject> etsObject(coro, EtsObject::Create(proxy->GetProxyClass()));
275 if (UNLIKELY(etsObject.GetPtr() == nullptr)) {
276 ctx->ThrowJSTypeError(ctx->GetJSEnv(),
277 "Interface Proxy EtsObject create failed, interfaceList: " + interfaceName);
278 return nullptr;
279 }
280 sharedRef = ctx->GetSharedRefStorage()->CreateHybridObjectRef(ctx, etsObject.GetPtr(), jsValue);
281 if (UNLIKELY(sharedRef == nullptr)) {
282 ASSERT(InteropCtx::SanityJSExceptionPending());
283 return nullptr;
284 }
285 return etsObject.GetPtr();
286 }
287
288 protected:
289 template <typename D>
JSRefConvertInterface(Class * klass,D * instance)290 explicit JSRefConvertInterface(Class *klass, D *instance) : JSRefConvert(instance), klass_(klass)
291 {
292 ASSERT(klass->IsInterface());
293 }
294
GetInterfaceClass(InteropCtx * ctx,std::string & interfaces)295 PandaSet<Class *> GetInterfaceClass(InteropCtx *ctx, std::string &interfaces)
296 {
297 PandaSet<Class *> interfaceList;
298 std::istringstream iss {interfaces};
299 std::string descriptor;
300 auto *coro = EtsCoroutine::GetCurrent();
301 ASSERT(coro != nullptr);
302 while (std::getline(iss, descriptor, ',')) {
303 auto interfaceCls =
304 coro->GetPandaVM()->GetClassLinker()->GetClass(descriptor.data(), true, ctx->LinkerCtx());
305 ASSERT(interfaceCls != nullptr);
306 interfaceList.insert(interfaceCls->GetRuntimeClass());
307 }
308 return interfaceList;
309 }
310
GetKlass()311 Class *GetKlass()
312 {
313 return klass_;
314 }
315
316 private:
317 Class *klass_;
318 };
319
320 class JSRefConvertInterfaceIterator : public JSRefConvertInterface {
321 public:
JSRefConvertInterfaceIterator(Class * klass)322 explicit JSRefConvertInterfaceIterator(Class *klass) : JSRefConvertInterface(klass, this)
323 {
324 ASSERT(klass->IsInterface());
325 }
326
UnwrapImpl(InteropCtx * ctx,napi_value jsValue)327 EtsObject *UnwrapImpl(InteropCtx *ctx, napi_value jsValue)
328 {
329 auto objectConverter =
330 ctx->GetEtsClassWrappersCache()->Lookup(EtsClass::FromRuntimeClass(ctx->GetObjectClass()));
331 ASSERT(objectConverter != nullptr);
332 auto ret = objectConverter->Unwrap(ctx, jsValue);
333
334 std::array args = {Value {ret->GetCoreType()}};
335 auto *method = EtsClass::FromRuntimeClass(ctx->GetJSRuntimeClass())->GetStaticMethod("CreateIterator", nullptr);
336 auto *coro = EtsCoroutine::GetCurrent();
337 auto resObject = method->GetPandaMethod()->Invoke(coro, args.data());
338 ret = EtsObject::FromCoreType(resObject.GetAs<ObjectHeader *>());
339 if (!ret->IsInstanceOf(EtsClass::FromRuntimeClass(GetKlass()))) {
340 ctx->ThrowJSTypeError(ctx->GetJSEnv(), "object of type " + ret->GetClass()->GetRuntimeClass()->GetName() +
341 " is not a assignable to " + GetKlass()->GetName());
342 return nullptr;
343 }
344 return ret;
345 }
346 };
347
348 /*static*/
CreateJSRefConvertEtsInterface(InteropCtx * ctx,Class * klass)349 std::unique_ptr<JSRefConvert> EtsClassWrapper::CreateJSRefConvertEtsInterface([[maybe_unused]] InteropCtx *ctx,
350 Class *klass)
351 {
352 if (klass->GetName() == INTERFACE_ITERABLE_NAME) {
353 return std::make_unique<JSRefConvertInterfaceIterator>(klass);
354 }
355 return std::make_unique<JSRefConvertInterface>(klass);
356 }
357
GetMethod(const std::string & name) const358 EtsMethodSet *EtsClassWrapper::GetMethod(const std::string &name) const
359 {
360 for (const auto &item : etsMethods_) {
361 if (name == item->GetName()) {
362 return item.get();
363 }
364 }
365 return nullptr;
366 }
367
368 /*static*/
Get(InteropCtx * ctx,EtsClass * etsClass)369 EtsClassWrapper *EtsClassWrapper::Get(InteropCtx *ctx, EtsClass *etsClass)
370 {
371 EtsClassWrappersCache *cache = ctx->GetEtsClassWrappersCache();
372
373 ASSERT(etsClass != nullptr);
374 EtsClassWrapper *etsClassWrapper = cache->Lookup(etsClass);
375 if (LIKELY(etsClassWrapper != nullptr)) {
376 return etsClassWrapper;
377 }
378
379 ASSERT(!etsClass->IsPrimitive() && etsClass->GetComponentType() == nullptr);
380 ASSERT(ctx->GetRefConvertCache()->Lookup(etsClass->GetRuntimeClass()) == nullptr);
381
382 if (IsStdClass(etsClass) && !etsClass->IsInterface() && !etsClass->IsEtsEnum() &&
383 !IsSubClassOfError(etsClass)) { // NOTE(gogabr): temporary ugly workaround for Function... interfaces
384 return nullptr;
385 }
386 ASSERT(!js_proxy::JSProxy::IsProxyClass((etsClass->GetRuntimeClass())));
387
388 std::unique_ptr<EtsClassWrapper> wrapper = EtsClassWrapper::Create(ctx, etsClass);
389 if (UNLIKELY(wrapper == nullptr)) {
390 return nullptr;
391 }
392 return cache->Insert(etsClass, std::move(wrapper));
393 }
394
SetupHierarchy(InteropCtx * ctx,const char * jsBuiltinName)395 bool EtsClassWrapper::SetupHierarchy(InteropCtx *ctx, const char *jsBuiltinName)
396 {
397 ASSERT(etsClass_->GetBase() != etsClass_);
398 if (etsClass_->GetBase() != nullptr) {
399 baseWrapper_ = EtsClassWrapper::Get(ctx, etsClass_->GetBase());
400 if (baseWrapper_ == nullptr) {
401 return false;
402 }
403 }
404
405 if (jsBuiltinName != nullptr && std::string(jsBuiltinName) != "Object") {
406 auto env = ctx->GetJSEnv();
407 napi_value jsBuiltinCtor;
408 NAPI_CHECK_FATAL(napi_get_named_property(env, GetGlobal(env), jsBuiltinName, &jsBuiltinCtor));
409 NAPI_CHECK_FATAL(napi_create_reference(env, jsBuiltinCtor, 1, &jsBuiltinCtorRef_));
410 }
411 return true;
412 }
413
SetBaseWrapperMethods(napi_env env,const EtsClassWrapper::MethodsVec & methods)414 void EtsClassWrapper::SetBaseWrapperMethods(napi_env env, const EtsClassWrapper::MethodsVec &methods)
415 {
416 for (auto method : methods) {
417 const std::string methodName(method->GetName());
418 if (methodName == GET_INDEX_METHOD || methodName == SET_INDEX_METHOD) {
419 SetUpMimicHandler(env);
420 }
421 auto baseClassWrapper = baseWrapper_;
422 while (nullptr != baseClassWrapper) {
423 EtsMethodSet *baseMethodSet = baseClassWrapper->GetMethod(methodName);
424 if (nullptr != baseMethodSet) {
425 method->SetBaseMethodSet(baseMethodSet);
426 break;
427 }
428 baseClassWrapper = baseClassWrapper->baseWrapper_;
429 }
430 }
431 }
432
CalculateProperties(const OverloadsMap * overloads)433 std::pair<EtsClassWrapper::FieldsVec, EtsClassWrapper::MethodsVec> EtsClassWrapper::CalculateProperties(
434 const OverloadsMap *overloads)
435 {
436 auto fatalNoMethod = [](Class *klass, const uint8_t *name, const char *signature) {
437 InteropCtx::Fatal(std::string("No method ") + utf::Mutf8AsCString(name) + " " + signature + " in " +
438 klass->GetName());
439 };
440
441 auto klass = etsClass_->GetRuntimeClass();
442 PropsMap props;
443
444 // Collect fields
445 for (auto &f : klass->GetFields()) {
446 if (f.IsPublic()) {
447 props.insert({f.GetName().data, &f});
448 }
449 }
450 // Select preferred overloads
451 if (overloads != nullptr) {
452 for (auto &[name, signaturePair] : *overloads) {
453 Method *method = etsClass_->GetDirectMethod(name, signaturePair.first)->GetPandaMethod();
454 if (UNLIKELY(method == nullptr)) {
455 fatalNoMethod(klass, name, signaturePair.first);
456 }
457 auto etsMethodSet = std::make_unique<EtsMethodSet>(EtsMethodSet::Create(method));
458 auto it = props.insert({method->GetName().data, etsMethodSet.get()});
459 if (!it.second && std::holds_alternative<EtsMethodSet *>(it.first->second)) {
460 // Possible correct method overloading: merge to existing entry
461 auto addedMethods = std::get<EtsMethodSet *>(it.first->second);
462 addedMethods->MergeWith(*etsMethodSet.get());
463 } else {
464 etsMethods_.push_back(std::move(etsMethodSet));
465 }
466 }
467 }
468
469 CollectClassMethods(&props, overloads);
470 if (etsClass_ != PlatformTypes()->coreObject) {
471 UpdatePropsWithBaseClasses(&props);
472 }
473
474 return CalculateFieldsAndMethods(props);
475 }
476
CollectConstructors(EtsClassWrapper::PropsMap * props)477 void EtsClassWrapper::CollectConstructors(EtsClassWrapper::PropsMap *props)
478 {
479 auto objCtors = etsClass_->GetConstructors();
480 // Assuming that ETS StdLib guarantee that Object has the only one ctor
481 ASSERT(objCtors.size() == 1);
482 auto ctor = objCtors[0]->GetPandaMethod();
483 auto etsMethodSet = std::make_unique<EtsMethodSet>(EtsMethodSet::Create(ctor));
484 props->insert({ctor->GetName().data, etsMethodSet.get()});
485 etsMethods_.push_back(std::move(etsMethodSet));
486 }
487
CollectClassMethods(EtsClassWrapper::PropsMap * props,const OverloadsMap * overloads)488 void EtsClassWrapper::CollectClassMethods(EtsClassWrapper::PropsMap *props, const OverloadsMap *overloads)
489 {
490 auto fatalMethodOverloaded = [](Method *method) {
491 for (auto &m : method->GetClass()->GetMethods()) {
492 if (utf::IsEqual(m.GetName().data, method->GetName().data)) {
493 INTEROP_LOG(ERROR) << "overload: " << EtsMethod::FromRuntimeMethod(&m)->GetMethodSignature(true);
494 }
495 }
496 InteropCtx::Fatal(std::string("Method ") + utf::Mutf8AsCString(method->GetName().data) + " of class " +
497 utf::Mutf8AsCString(method->GetClass()->GetDescriptor()) + " is overloaded");
498 };
499
500 auto klass = etsClass_->GetRuntimeClass();
501 for (auto &m : klass->GetMethods()) {
502 if (m.IsPrivate()) {
503 continue;
504 }
505 if (overloads != nullptr) {
506 if (HasOverloadsMethod(overloads, &m)) {
507 continue;
508 }
509 }
510 auto methodSet = std::make_unique<EtsMethodSet>(EtsMethodSet::Create(&m));
511 auto it = props->insert({m.GetName().data, methodSet.get()});
512 if (!it.second && !std::holds_alternative<EtsMethodSet *>(it.first->second)) {
513 // Method overloads non-method field
514 fatalMethodOverloaded(&m);
515 }
516 if (!it.second && std::holds_alternative<EtsMethodSet *>(it.first->second)) {
517 // Possible correct method overloading: merge to existing entry
518 auto addedMethods = std::get<EtsMethodSet *>(it.first->second);
519 addedMethods->MergeWith(*methodSet.get());
520 } else {
521 etsMethods_.push_back(std::move(methodSet));
522 }
523 }
524 }
525
HasOverloadsMethod(const OverloadsMap * overloads,Method * m)526 bool EtsClassWrapper::HasOverloadsMethod(const OverloadsMap *overloads, Method *m)
527 {
528 auto name = m->GetName().data;
529 auto range = overloads->equal_range(name);
530 EtsMethod *etsMethod = EtsMethod::FromRuntimeMethod(m);
531 for (auto iter = range.first; iter != range.second; ++iter) {
532 if (iter->second.second ==
533 etsMethod->GetNumArgs() - static_cast<unsigned int>(etsMethod->GetPandaMethod()->HasVarArgs())) {
534 return true;
535 }
536 }
537 return false;
538 }
539
UpdatePropsWithBaseClasses(EtsClassWrapper::PropsMap * props)540 void EtsClassWrapper::UpdatePropsWithBaseClasses(EtsClassWrapper::PropsMap *props)
541 {
542 auto hasSquashedProtoOhosImpl = [](EtsClassWrapper *wclass) {
543 ASSERT(wclass->HasBuiltin() || wclass->baseWrapper_ != nullptr);
544 return wclass->HasBuiltin() || wclass->baseWrapper_->HasBuiltin();
545 };
546
547 auto hasSquashedProtoOtherImpl = [](EtsClassWrapper *wclass) {
548 // NOTE(vpukhov): some napi implementations add explicit receiver checks in call handler,
549 // thus method inheritance via prototype chain wont work
550 (void)wclass;
551 return true;
552 };
553
554 #if defined(PANDA_TARGET_OHOS) || defined(PANDA_JS_ETS_HYBRID_MODE)
555 auto hasSquashedProto = hasSquashedProtoOhosImpl;
556 (void)hasSquashedProtoOtherImpl;
557 #else
558 auto hasSquashedProto = hasSquashedProtoOtherImpl;
559 (void)hasSquashedProtoOhosImpl;
560 #endif
561
562 if (hasSquashedProto(this)) {
563 // Copy properties of base classes if we have to split prototype chain
564 for (auto wclass = baseWrapper_; wclass != nullptr && (wclass->etsClass_ != PlatformTypes()->coreObject);
565 wclass = wclass->baseWrapper_) {
566 for (auto &wfield : wclass->GetFields()) {
567 Field *field = wfield.GetField();
568 props->insert({field->GetName().data, field});
569 }
570 for (auto &link : wclass->GetMethods()) {
571 EtsMethodSet *methodSet = link.IsResolved() ? link.GetResolved()->GetMethodSet() : link.GetUnresolved();
572 props->insert({utf::CStringAsMutf8(methodSet->GetName()), methodSet});
573 }
574
575 if (hasSquashedProto(wclass)) {
576 break;
577 }
578 }
579 }
580 }
581
CalculateFieldsAndMethods(const PropsMap & props)582 std::pair<EtsClassWrapper::FieldsVec, EtsClassWrapper::MethodsVec> EtsClassWrapper::CalculateFieldsAndMethods(
583 const PropsMap &props)
584 {
585 std::vector<EtsMethodSet *> methods;
586 std::vector<Field *> fields;
587
588 for (auto &[n, p] : props) {
589 if (std::holds_alternative<EtsMethodSet *>(p)) {
590 auto method = std::get<EtsMethodSet *>(p);
591 if (method->IsConstructor() && !method->IsStatic()) {
592 etsCtorLink_ = LazyEtsMethodWrapperLink(method);
593 }
594 if (!method->IsConstructor()) {
595 methods.push_back(method);
596 }
597 } else if (std::holds_alternative<Field *>(p)) {
598 fields.push_back(std::get<Field *>(p));
599 } else {
600 UNREACHABLE();
601 }
602 }
603
604 return {fields, methods};
605 }
606
BuildJSProperties(napi_env & env,Span<Field * > fields,Span<EtsMethodSet * > methods)607 std::vector<napi_property_descriptor> EtsClassWrapper::BuildJSProperties(napi_env &env, Span<Field *> fields,
608 Span<EtsMethodSet *> methods)
609 {
610 std::vector<napi_property_descriptor> jsProps;
611 jsProps.reserve(fields.size() + methods.size() + 1);
612
613 // Process fields
614 numFields_ = fields.size();
615 // NOLINTNEXTLINE(modernize-avoid-c-arrays)
616 etsFieldWrappers_ = std::make_unique<EtsFieldWrapper[]>(numFields_);
617 Span<EtsFieldWrapper> etsFieldWrappers(etsFieldWrappers_.get(), numFields_);
618 size_t fieldIdx = 0;
619
620 for (Field *field : fields) {
621 auto wfield = &etsFieldWrappers[fieldIdx++];
622 if (field->IsStatic()) {
623 EtsClassWrapper *fieldWclass = LookupBaseWrapper(EtsClass::FromRuntimeClass(field->GetClass()));
624 ASSERT(fieldWclass != nullptr);
625 jsProps.emplace_back(wfield->MakeStaticProperty(fieldWclass, field));
626 } else {
627 jsProps.emplace_back(wfield->MakeInstanceProperty(this, field));
628 }
629 }
630
631 // Process methods
632 numMethods_ = methods.size();
633 // NOLINTNEXTLINE(modernize-avoid-c-arrays)
634 etsMethodWrappers_ = std::make_unique<LazyEtsMethodWrapperLink[]>(numMethods_);
635 Span<LazyEtsMethodWrapperLink> etsMethodWrappers(etsMethodWrappers_.get(), numMethods_);
636 size_t methodIdx = 0;
637 GetterSetterPropsMap propMap;
638 for (auto &method : methods) {
639 ASSERT(!method->IsConstructor());
640 auto lazyLink = &etsMethodWrappers[methodIdx++];
641 lazyLink->Set(method);
642 jsProps.emplace_back(EtsMethodWrapper::MakeNapiProperty(method, lazyLink));
643 if (strcmp(method->GetName(), ITERATOR_METHOD) == 0) {
644 auto iterator = EtsClassWrapper::GetGlobalSymbolIterator(env);
645 jsProps.emplace_back(EtsMethodWrapper::MakeNapiIteratorProperty(iterator, method, lazyLink));
646 }
647 if (method->IsGetter() || method->IsSetter()) {
648 BuildGetterSetterFieldProperties(propMap, method);
649 }
650 }
651 jsProps.reserve(jsProps.size() + propMap.size());
652 for (auto &item : propMap) {
653 jsProps.emplace_back(item.second);
654 }
655
656 return jsProps;
657 }
658
BuildGetterSetterFieldProperties(GetterSetterPropsMap & propMap,EtsMethodSet * method)659 void EtsClassWrapper::BuildGetterSetterFieldProperties(GetterSetterPropsMap &propMap, EtsMethodSet *method)
660 {
661 auto ptr = reinterpret_cast<uintptr_t>(method->GetName());
662 const char *fieldName = reinterpret_cast<char *>(ptr + strlen(SETTER_BEGIN));
663 const std::string key(fieldName);
664 auto result = propMap.find(key);
665 if (result != propMap.end()) {
666 EtsMethodWrapper::AttachGetterSetterToProperty(method, result->second);
667 } else {
668 napi_property_descriptor prop {};
669 auto fieldWrapper = std::make_unique<EtsFieldWrapper>(this);
670 prop.utf8name = fieldName;
671 prop.attributes = method->IsStatic() ? EtsClassWrapper::STATIC_FIELD_ATTR : EtsClassWrapper::FIELD_ATTR;
672 prop.data = fieldWrapper.get();
673 EtsMethodWrapper::AttachGetterSetterToProperty(method, prop);
674 propMap.insert({key, prop});
675 getterSetterFieldWrappers_.push_back(std::move(fieldWrapper));
676 }
677 }
678
679 /* static */
GetGlobalSymbolIterator(napi_env & env)680 napi_value EtsClassWrapper::GetGlobalSymbolIterator(napi_env &env)
681 {
682 napi_value global;
683 napi_value symbol;
684 napi_value iterator;
685 NAPI_CHECK_FATAL(napi_get_global(env, &global));
686 NAPI_CHECK_FATAL(napi_get_named_property(env, global, "Symbol", &symbol));
687 NAPI_CHECK_FATAL(napi_get_named_property(env, symbol, "iterator", &iterator));
688
689 return iterator;
690 }
691
LookupBaseWrapper(EtsClass * klass)692 EtsClassWrapper *EtsClassWrapper::LookupBaseWrapper(EtsClass *klass)
693 {
694 for (auto wclass = this; wclass != nullptr; wclass = wclass->baseWrapper_) {
695 if (wclass->etsClass_ == klass) {
696 return wclass;
697 }
698 }
699 return nullptr;
700 }
701
DoSetPrototype(napi_env env,napi_value obj,napi_value proto)702 static void DoSetPrototype(napi_env env, napi_value obj, napi_value proto)
703 {
704 napi_value builtinObject;
705 napi_value setprotoFn;
706 NAPI_CHECK_FATAL(napi_get_named_property(env, GetGlobal(env), "Object", &builtinObject));
707 NAPI_CHECK_FATAL(napi_get_named_property(env, builtinObject, "setPrototypeOf", &setprotoFn));
708
709 std::array args = {obj, proto};
710 NAPI_CHECK_FATAL(NapiCallFunction(env, builtinObject, setprotoFn, args.size(), args.data(), nullptr));
711 }
712
SetNullPrototype(napi_env env,napi_value jsCtor)713 static void SetNullPrototype(napi_env env, napi_value jsCtor)
714 {
715 napi_value prot;
716 NAPI_CHECK_FATAL(napi_get_named_property(env, jsCtor, "prototype", &prot));
717
718 auto nullProto = GetNull(env);
719 DoSetPrototype(env, jsCtor, nullProto);
720 DoSetPrototype(env, prot, nullProto);
721 }
722
SimulateJSInheritance(napi_env env,napi_value jsCtor,napi_value jsBaseCtor)723 static void SimulateJSInheritance(napi_env env, napi_value jsCtor, napi_value jsBaseCtor)
724 {
725 napi_value cprototype;
726 napi_value baseCprototype;
727 NAPI_CHECK_FATAL(napi_get_named_property(env, jsCtor, "prototype", &cprototype));
728 NAPI_CHECK_FATAL(napi_get_named_property(env, jsBaseCtor, "prototype", &baseCprototype));
729
730 DoSetPrototype(env, jsCtor, jsBaseCtor);
731 DoSetPrototype(env, cprototype, baseCprototype);
732 }
733
734 /*static*/
Create(InteropCtx * ctx,EtsClass * etsClass,const char * jsBuiltinName,const OverloadsMap * overloads)735 std::unique_ptr<EtsClassWrapper> EtsClassWrapper::Create(InteropCtx *ctx, EtsClass *etsClass, const char *jsBuiltinName,
736 const OverloadsMap *overloads)
737 {
738 auto env = ctx->GetJSEnv();
739
740 // CC-OFFNXT(G.RES.09) private constructor
741 // NOLINTNEXTLINE(readability-identifier-naming)
742 auto _this = std::unique_ptr<EtsClassWrapper>(new EtsClassWrapper(etsClass));
743 if (!_this->SetupHierarchy(ctx, jsBuiltinName)) {
744 return nullptr;
745 }
746
747 auto [fields, methods] = _this->CalculateProperties(overloads);
748 _this->SetBaseWrapperMethods(env, methods);
749
750 auto jsProps = _this->BuildJSProperties(env, {fields.data(), fields.size()}, {methods.data(), methods.size()});
751
752 // NOTE(vpukhov): fatal no-public-fields check when escompat adopt accessors
753 if (_this->HasBuiltin() && !fields.empty()) {
754 INTEROP_LOG(ERROR) << "built-in class " << etsClass->GetDescriptor() << " has field properties";
755 }
756 if (_this->HasBuiltin() && etsClass->IsFinal()) {
757 INTEROP_LOG(FATAL) << "built-in class " << etsClass->GetDescriptor() << " is final";
758 }
759 // NOTE(vpukhov): forbid "true" ets-field overriding in js-derived class, as it cannot be proxied back
760 if (!etsClass->IsFinal()) {
761 auto ungroupedMethods = CollectAllPandaMethods(methods.begin(), methods.end());
762 _this->jsproxyWrapper_ = ctx->GetJsProxyInstance(etsClass);
763 if (_this->jsproxyWrapper_ == nullptr) {
764 // NOTE(konstanting): we assume that the method list stays the same for every proxied class
765 _this->jsproxyWrapper_ =
766 js_proxy::JSProxy::CreateBuiltinProxy(etsClass, {ungroupedMethods.data(), ungroupedMethods.size()});
767 ctx->SetJsProxyInstance(etsClass, _this->jsproxyWrapper_);
768 }
769 }
770 napi_value jsCtor {};
771 NAPI_CHECK_FATAL(napi_define_class(env, etsClass->GetDescriptor(), NAPI_AUTO_LENGTH,
772 EtsClassWrapper::JSCtorCallback, _this.get(), jsProps.size(), jsProps.data(),
773 &jsCtor));
774
775 if (etsClass == PlatformTypes()->coreObject) {
776 SetNullPrototype(env, jsCtor);
777
778 NAPI_CHECK_FATAL(napi_create_reference(env, jsCtor, 1, &_this->jsCtorRef_));
779 NAPI_CHECK_FATAL(napi_create_reference(env, jsCtor, 1, &_this->jsBuiltinCtorRef_));
780 NAPI_CHECK_FATAL(napi_object_seal(env, jsCtor));
781
782 return _this;
783 }
784
785 auto base = _this->baseWrapper_;
786 napi_value fakeSuper = _this->HasBuiltin() ? _this->GetBuiltin(env)
787 : (base->HasBuiltin() ? base->GetBuiltin(env) : base->GetJsCtor(env));
788
789 SimulateJSInheritance(env, jsCtor, fakeSuper);
790 NAPI_CHECK_FATAL(napi_object_seal(env, jsCtor));
791 NAPI_CHECK_FATAL(napi_create_reference(env, jsCtor, 1, &_this->jsCtorRef_));
792
793 return _this;
794 }
795
SetUpMimicHandler(napi_env env)796 void EtsClassWrapper::SetUpMimicHandler(napi_env env)
797 {
798 this->needProxy_ = true;
799
800 napi_value global;
801 NAPI_CHECK_FATAL(napi_get_global(env, &global));
802
803 napi_value jsProxyCtorRef;
804 NAPI_CHECK_FATAL(napi_get_named_property(env, global, "Proxy", &jsProxyCtorRef));
805 NAPI_CHECK_FATAL(napi_create_reference(env, jsProxyCtorRef, 1, &this->jsProxyCtorRef_));
806
807 napi_value jsProxyHandlerRef;
808 NAPI_CHECK_FATAL(napi_create_object(env, &jsProxyHandlerRef));
809 NAPI_CHECK_FATAL(napi_create_reference(env, jsProxyHandlerRef, 1, &this->jsProxyHandlerRef_));
810
811 napi_value getHandlerFunc;
812 NAPI_CHECK_FATAL(napi_create_function(env, nullptr, NAPI_AUTO_LENGTH, EtsClassWrapper::MimicGetHandler, nullptr,
813 &getHandlerFunc));
814 napi_value setHandlerFunc;
815 NAPI_CHECK_FATAL(napi_create_function(env, nullptr, NAPI_AUTO_LENGTH, EtsClassWrapper::MimicSetHandler, nullptr,
816 &setHandlerFunc));
817
818 NAPI_CHECK_FATAL(napi_set_named_property(env, jsProxyHandlerRef, "get", getHandlerFunc));
819 NAPI_CHECK_FATAL(napi_set_named_property(env, jsProxyHandlerRef, "set", setHandlerFunc));
820 }
821
822 /*static*/
CreateProxy(napi_env env,napi_value jsCtor,EtsClassWrapper * thisWrapper)823 napi_value EtsClassWrapper::CreateProxy(napi_env env, napi_value jsCtor, EtsClassWrapper *thisWrapper)
824 {
825 napi_value jsProxyHandlerRef;
826 NAPI_CHECK_FATAL(napi_get_reference_value(env, thisWrapper->jsProxyHandlerRef_, &jsProxyHandlerRef));
827 napi_value jsProxyCtorRef;
828 NAPI_CHECK_FATAL(napi_get_reference_value(env, thisWrapper->jsProxyCtorRef_, &jsProxyCtorRef));
829
830 napi_value proxyObj;
831 std::vector<napi_value> args = {jsCtor, jsProxyHandlerRef};
832
833 NAPI_CHECK_FATAL(napi_new_instance(env, jsProxyCtorRef, args.size(), args.data(), &proxyObj));
834 return proxyObj;
835 }
836
837 /*static*/
MimicGetHandler(napi_env env,napi_callback_info info)838 napi_value EtsClassWrapper::MimicGetHandler(napi_env env, napi_callback_info info)
839 {
840 ASSERT_SCOPED_NATIVE_CODE();
841 auto coro = EtsCoroutine::GetCurrent();
842 auto ctx = InteropCtx::Current(coro);
843 INTEROP_CODE_SCOPE_JS(coro);
844
845 size_t argc;
846 NAPI_CHECK_FATAL(napi_get_cb_info(env, info, &argc, nullptr, nullptr, nullptr));
847 auto jsArgs = ctx->GetTempArgs<napi_value>(argc);
848 NAPI_CHECK_FATAL(napi_get_cb_info(env, info, &argc, jsArgs->data(), nullptr, nullptr));
849
850 napi_value target = jsArgs[0];
851 napi_value property = jsArgs[1];
852
853 if (GetValueType(env, property) == napi_number) {
854 ScopedManagedCodeThread managedCode(coro);
855 ets_proxy::SharedReferenceStorage *storage = ctx->GetSharedRefStorage();
856 ASSERT(storage != nullptr);
857 ets_proxy::SharedReference *sharedRef = storage->GetReference(env, jsArgs[0]);
858 if (sharedRef == nullptr) {
859 InteropFatal("MimicGetHandler sharedRef is empty");
860 }
861
862 auto *etsThis = sharedRef->GetEtsObject();
863 ASSERT(etsThis != nullptr);
864 EtsMethod *method = etsThis->GetClass()->GetInstanceMethod(GET_INDEX_METHOD, nullptr);
865 ASSERT(method != nullptr);
866
867 Span sp(jsArgs->begin(), jsArgs->end());
868 const size_t startIndex = 1;
869 const size_t argSize = 1;
870 return CallETSInstance(coro, ctx, method->GetPandaMethod(), sp.SubSpan(startIndex, argSize), etsThis);
871 }
872
873 napi_value result;
874 NAPI_CHECK_FATAL(napi_get_property(env, target, property, &result));
875 return result;
876 }
877
878 /*static*/
MimicSetHandler(napi_env env,napi_callback_info info)879 napi_value EtsClassWrapper::MimicSetHandler(napi_env env, napi_callback_info info)
880 {
881 ASSERT_SCOPED_NATIVE_CODE();
882 auto coro = EtsCoroutine::GetCurrent();
883 auto ctx = InteropCtx::Current(coro);
884 INTEROP_CODE_SCOPE_JS(coro);
885
886 size_t argc;
887 NAPI_CHECK_FATAL(napi_get_cb_info(env, info, &argc, nullptr, nullptr, nullptr));
888 auto jsArgs = ctx->GetTempArgs<napi_value>(argc);
889 NAPI_CHECK_FATAL(napi_get_cb_info(env, info, &argc, jsArgs->data(), nullptr, nullptr));
890
891 ScopedManagedCodeThread managedCode(coro);
892 ets_proxy::SharedReferenceStorage *storage = ctx->GetSharedRefStorage();
893 ASSERT(storage != nullptr);
894 ets_proxy::SharedReference *sharedRef = storage->GetReference(env, jsArgs[0]);
895 if (sharedRef == nullptr) {
896 InteropFatal("MimicSetHandler sharedRef is empty");
897 }
898
899 auto *etsThis = sharedRef->GetEtsObject();
900 ASSERT(etsThis != nullptr);
901 EtsMethod *method = etsThis->GetClass()->GetInstanceMethod(SET_INDEX_METHOD, nullptr);
902 ASSERT(method != nullptr);
903
904 Span sp(jsArgs->begin(), jsArgs->end());
905
906 const size_t startIndex = 1;
907 const size_t argSize = 2;
908 CallETSInstance(coro, ctx, method->GetPandaMethod(), sp.SubSpan(startIndex, argSize), etsThis);
909
910 napi_value trueValue;
911 NAPI_CHECK_FATAL(napi_get_boolean(env, true, &trueValue));
912 return trueValue;
913 }
914
915 /*static*/
JSCtorCallback(napi_env env,napi_callback_info cinfo)916 napi_value EtsClassWrapper::JSCtorCallback(napi_env env, napi_callback_info cinfo)
917 {
918 EtsCoroutine *coro = EtsCoroutine::GetCurrent();
919 InteropCtx *ctx = InteropCtx::Current(coro);
920 INTEROP_CODE_SCOPE_JS(coro);
921
922 napi_value jsThis;
923 size_t argc;
924 void *data;
925 NAPI_CHECK_FATAL(napi_get_cb_info(env, cinfo, &argc, nullptr, &jsThis, &data));
926 auto etsClassWrapper = reinterpret_cast<EtsClassWrapper *>(data);
927
928 ScopedManagedCodeThread managedScope(coro);
929 EtsObject *etsObject = ctx->AcquirePendingNewInstance();
930
931 if (LIKELY(etsObject != nullptr)) {
932 // proxy $get and $set
933 napi_value jsObject = nullptr;
934 if (etsClassWrapper->needProxy_) {
935 jsObject = CreateProxy(env, jsThis, etsClassWrapper);
936 } else {
937 jsObject = jsThis;
938 }
939
940 // Create shared reference for existing ets object
941 SharedReferenceStorage *storage = ctx->GetSharedRefStorage();
942 if (UNLIKELY(!storage->CreateETSObjectRef(ctx, etsObject, jsObject))) {
943 ASSERT(InteropCtx::SanityJSExceptionPending());
944 return nullptr;
945 }
946 NAPI_CHECK_FATAL(napi_object_seal(env, jsThis));
947 return nullptr;
948 }
949
950 if (!etsClassWrapper->etsCtorLink_.IsResolved() && etsClassWrapper->etsCtorLink_.GetUnresolved() == nullptr) {
951 InteropCtx::ThrowJSError(env,
952 etsClassWrapper->GetEtsClass()->GetRuntimeClass()->GetName() + " has no constructor");
953 return nullptr;
954 }
955
956 auto jsArgs = ctx->GetTempArgs<napi_value>(argc);
957 NAPI_CHECK_FATAL(napi_get_cb_info(env, cinfo, &argc, jsArgs->data(), nullptr, nullptr));
958
959 napi_value jsNewtarget;
960 NAPI_CHECK_FATAL(napi_get_new_target(env, cinfo, &jsNewtarget));
961
962 // create new object and wrap it
963 if (UNLIKELY(!etsClassWrapper->CreateAndWrap(env, jsNewtarget, jsThis, *jsArgs))) {
964 ASSERT(InteropCtx::SanityJSExceptionPending());
965 return nullptr;
966 }
967
968 // NOTE(ivagin): JS constructor is not required to return 'this', but ArkUI NAPI requires it
969 return jsThis;
970 }
971
CreateAndWrap(napi_env env,napi_value jsNewtarget,napi_value jsThis,Span<napi_value> jsArgs)972 bool EtsClassWrapper::CreateAndWrap(napi_env env, napi_value jsNewtarget, napi_value jsThis, Span<napi_value> jsArgs)
973 {
974 EtsCoroutine *coro = EtsCoroutine::GetCurrent();
975 InteropCtx *ctx = InteropCtx::Current(coro);
976
977 if (UNLIKELY(!CheckClassInitialized<true>(etsClass_->GetRuntimeClass()))) {
978 ctx->ForwardEtsException(coro);
979 return false;
980 }
981
982 bool notExtensible;
983 NAPI_CHECK_FATAL(napi_strict_equals(env, jsNewtarget, GetJsCtor(env), ¬Extensible));
984
985 EtsClass *instanceClass {};
986
987 if (LIKELY(notExtensible)) {
988 #if !defined(PANDA_TARGET_OHOS) && !defined(PANDA_JS_ETS_HYBRID_MODE)
989 // In case of OHOS sealed object can't be wrapped, therefore seal it after wrapping
990 NAPI_CHECK_FATAL(napi_object_seal(env, jsThis));
991 #endif // PANDA_TARGET_OHOS
992 instanceClass = etsClass_;
993 } else {
994 if (UNLIKELY(jsproxyWrapper_ == nullptr)) {
995 ctx->ThrowJSTypeError(env, std::string("Proxy for ") + etsClass_->GetDescriptor() + " is not extensible");
996 return false;
997 }
998 instanceClass = jsproxyWrapper_->GetProxyClass();
999 }
1000
1001 LocalObjectHandle<EtsObject> etsObject(coro, EtsObject::Create(instanceClass));
1002 if (UNLIKELY(etsObject.GetPtr() == nullptr)) {
1003 return false;
1004 }
1005
1006 // NOTE(MockMockBlack, #IC59ZS): put proxy to SharedReferenceStorage more prettily
1007 SharedReference *sharedRef;
1008 if (LIKELY(notExtensible)) {
1009 sharedRef = ctx->GetSharedRefStorage()->CreateETSObjectRef(ctx, etsObject.GetPtr(), jsThis);
1010 #if defined(PANDA_TARGET_OHOS) || defined(PANDA_JS_ETS_HYBRID_MODE)
1011 // In case of OHOS sealed object can't be wrapped, therefore seal it after wrapping
1012 NAPI_CHECK_FATAL(napi_object_seal(env, jsThis));
1013 #endif // PANDA_TARGET_OHOS
1014 } else {
1015 sharedRef = ctx->GetSharedRefStorage()->CreateHybridObjectRef(ctx, etsObject.GetPtr(), jsThis);
1016 }
1017 if (UNLIKELY(sharedRef == nullptr)) {
1018 ASSERT(InteropCtx::SanityJSExceptionPending());
1019 return false;
1020 }
1021
1022 EtsMethodWrapper *ctorWrapper = EtsMethodWrapper::ResolveLazyLink(ctx, etsCtorLink_);
1023 ASSERT(ctorWrapper != nullptr);
1024 EtsMethod *ctorMethod = ctorWrapper->GetEtsMethod(jsArgs.Size());
1025 ASSERT(ctorMethod->IsInstanceConstructor());
1026
1027 napi_value callRes = CallETSInstance(coro, ctx, ctorMethod->GetPandaMethod(), jsArgs, etsObject.GetPtr());
1028 return callRes != nullptr;
1029 }
1030
1031 } // namespace ark::ets::interop::js::ets_proxy
1032