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), ¬Extensible));
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