1 /**
2 * Copyright (c) 2021-2022 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/js_value.h"
17 #include "plugins/ets/runtime/interop_js/napi_env_scope.h"
18 #include "plugins/ets/runtime/interop_js/interop_common.h"
19 #include "plugins/ets/runtime/interop_js/ets_type_visitor-inl.h"
20 #include "plugins/ets/runtime/ets_vm.h"
21 #include "plugins/ets/runtime/types/ets_promise.h"
22 #include "runtime/handle_scope-inl.h"
23
24 namespace panda::ets::interop::js {
25
ToObjRoot(uintptr_t ptr)26 static inline EtsConvertorRef::ObjRoot ToObjRoot(uintptr_t ptr)
27 {
28 return reinterpret_cast<EtsConvertorRef::ObjRoot>(ptr);
29 }
30
31 class JsToEtsConvertor final : public EtsTypeVisitor {
32 using Base = EtsTypeVisitor;
33
34 public:
JsToEtsConvertor(napi_env env,napi_value jsValue,EtsConvertorRef::ValVariant * dataPtr)35 JsToEtsConvertor(napi_env env, napi_value jsValue, EtsConvertorRef::ValVariant *dataPtr)
36 : loc_(dataPtr), env_(env), jsValue_(jsValue)
37 {
38 }
JsToEtsConvertor(napi_env env,napi_value jsValue,EtsConvertorRef::ObjRoot etsObj,size_t etsOffs)39 JsToEtsConvertor(napi_env env, napi_value jsValue, EtsConvertorRef::ObjRoot etsObj, size_t etsOffs)
40 : loc_(etsObj, etsOffs), env_(env), jsValue_(jsValue)
41 {
42 }
43
VisitU1()44 void VisitU1() override
45 {
46 TYPEVIS_CHECK_ERROR(GetValueType(env_, jsValue_) == napi_boolean, "bool expected");
47 bool val;
48 TYPEVIS_NAPI_CHECK(napi_get_value_bool(env_, jsValue_, &val));
49 loc_.StorePrimitive(static_cast<bool>(val));
50 }
51
52 template <typename T>
VisitIntType()53 inline void VisitIntType()
54 {
55 TYPEVIS_CHECK_ERROR(GetValueType(env_, jsValue_) == napi_number, "number expected");
56 int64_t val;
57 TYPEVIS_NAPI_CHECK(napi_get_value_int64(env_, jsValue_, &val));
58 loc_.StorePrimitive(static_cast<T>(val));
59 }
60
VisitI8()61 void VisitI8() override
62 {
63 VisitIntType<int8_t>();
64 }
65
VisitI16()66 void VisitI16() override
67 {
68 VisitIntType<int16_t>();
69 }
70
VisitU16()71 void VisitU16() override
72 {
73 VisitIntType<uint16_t>();
74 }
75
VisitI32()76 void VisitI32() override
77 {
78 VisitIntType<int32_t>();
79 }
80
VisitI64()81 void VisitI64() override
82 {
83 VisitIntType<int64_t>();
84 }
85
VisitF32()86 void VisitF32() override
87 {
88 TYPEVIS_CHECK_ERROR(GetValueType(env_, jsValue_) == napi_number, "number expected");
89 double val;
90 TYPEVIS_NAPI_CHECK(napi_get_value_double(env_, jsValue_, &val));
91 loc_.StorePrimitive(static_cast<float>(val));
92 }
93
VisitF64()94 void VisitF64() override
95 {
96 TYPEVIS_CHECK_ERROR(GetValueType(env_, jsValue_) == napi_number, "number expected");
97 double val;
98 TYPEVIS_NAPI_CHECK(napi_get_value_double(env_, jsValue_, &val));
99 loc_.StorePrimitive(val);
100 }
101
VisitString(panda::Class * klass)102 void VisitString([[maybe_unused]] panda::Class *klass) override
103 {
104 TYPEVIS_CHECK_ERROR(GetValueType(env_, jsValue_) == napi_string, "string expected");
105
106 size_t length;
107 TYPEVIS_NAPI_CHECK(napi_get_value_string_utf8(env_, jsValue_, nullptr, 0, &length));
108
109 std::string value;
110 value.resize(length + 1);
111 size_t result;
112 TYPEVIS_NAPI_CHECK(napi_get_value_string_utf8(env_, jsValue_, &value[0], value.size(), &result));
113
114 auto coro = EtsCoroutine::GetCurrent();
115 auto vm = coro->GetVM();
116 auto str = panda::coretypes::String::CreateFromMUtf8(reinterpret_cast<uint8_t const *>(value.data()),
117 vm->GetLanguageContext(), vm);
118 loc_.StoreReference(ToObjRoot(panda::VMHandle<panda::ObjectHeader>(coro, str).GetAddress()));
119 }
120
VisitArray(panda::Class * klass)121 void VisitArray(panda::Class *klass) override
122 {
123 {
124 bool isArray;
125 TYPEVIS_NAPI_CHECK(napi_is_array(env_, jsValue_, &isArray));
126 TYPEVIS_CHECK_ERROR(isArray, "array expected");
127 }
128 auto coro = EtsCoroutine::GetCurrent();
129
130 NapiScope jsHandleScope(env_);
131
132 uint32_t len;
133 TYPEVIS_NAPI_CHECK(napi_get_array_length(env_, jsValue_, &len));
134
135 panda::VMHandle<panda::ObjectHeader> etsArr(coro, panda::coretypes::Array::Create(klass, len));
136
137 panda::HandleScope<panda::ObjectHeader *> etsHandleScope(coro);
138 auto *subType = klass->GetComponentType();
139 size_t elemSz = panda::Class::GetTypeSize(subType->GetType());
140 constexpr auto ELEMS_OFFS = panda::coretypes::Array::GetDataOffset();
141 for (size_t idx = 0; idx < len; ++idx) {
142 napi_value jsElem;
143 TYPEVIS_NAPI_CHECK(napi_get_element(env_, jsValue_, idx, &jsElem));
144 JsToEtsConvertor subVis(env_, jsElem, ToObjRoot(etsArr.GetAddress()), ELEMS_OFFS + elemSz * idx);
145 subVis.VisitClass(subType);
146 TYPEVIS_CHECK_FORWARD_ERROR(subVis.Error());
147 }
148 loc_.StoreReference(ToObjRoot(etsArr.GetAddress()));
149 }
150
VisitFieldReference(const panda::Field * field,panda::Class * klass)151 void VisitFieldReference(const panda::Field *field, panda::Class *klass) override
152 {
153 auto fname = reinterpret_cast<const char *>(field->GetName().data);
154 napi_value subVal;
155 TYPEVIS_NAPI_CHECK(napi_get_named_property(env_, jsValue_, fname, &subVal));
156 auto subVis = JsToEtsConvertor(env_, subVal, owner_, field->GetOffset());
157 subVis.VisitReference(klass);
158 TYPEVIS_CHECK_FORWARD_ERROR(subVis.Error());
159 }
160
VisitFieldPrimitive(const panda::Field * field,panda::panda_file::Type type)161 void VisitFieldPrimitive(const panda::Field *field, panda::panda_file::Type type) override
162 {
163 auto fname = reinterpret_cast<const char *>(field->GetName().data);
164 napi_value subVal;
165 TYPEVIS_NAPI_CHECK(napi_get_named_property(env_, jsValue_, fname, &subVal));
166 auto subVis = JsToEtsConvertor(env_, subVal, owner_, field->GetOffset());
167 subVis.VisitPrimitive(type);
168 TYPEVIS_CHECK_FORWARD_ERROR(subVis.Error());
169 }
170
VisitObject(panda::Class * klass)171 void VisitObject(panda::Class *klass) override
172 {
173 ASSERT(klass != InteropCtx::Current()->GetJSValueClass());
174 TYPEVIS_CHECK_ERROR(GetValueType(env_, jsValue_) == napi_object, "object expected");
175 auto coro = EtsCoroutine::GetCurrent();
176
177 NapiScope jsHandleScope(env_);
178 {
179 auto etsObj = panda::ObjectHeader::Create(coro, klass);
180 owner_ = ToObjRoot(panda::VMHandle<panda::ObjectHeader>(coro, etsObj).GetAddress());
181 }
182
183 panda::HandleScope<panda::ObjectHeader *> etsHandleScope(coro);
184 Base::VisitObject(klass);
185 TYPEVIS_ABRUPT_ON_ERROR();
186 loc_.StoreReference(owner_);
187 }
188
189 private:
190 EtsConvertorRef loc_;
191 EtsConvertorRef::ObjRoot owner_ = nullptr;
192
193 napi_env env_ {};
194 napi_value jsValue_ {};
195 };
196
197 class EtsToJsConvertor final : public EtsTypeVisitor {
198 using Base = EtsTypeVisitor;
199
200 public:
EtsToJsConvertor(napi_env env,EtsConvertorRef::ValVariant * dataPtr)201 EtsToJsConvertor(napi_env env, EtsConvertorRef::ValVariant *dataPtr) : loc_(dataPtr), env_(env) {}
EtsToJsConvertor(napi_env env,EtsConvertorRef::ObjRoot etsObj,size_t etsOffs)202 EtsToJsConvertor(napi_env env, EtsConvertorRef::ObjRoot etsObj, size_t etsOffs) : loc_(etsObj, etsOffs), env_(env)
203 {
204 }
205
VisitU1()206 void VisitU1() override
207 {
208 bool etsVal = loc_.LoadPrimitive<bool>();
209 TYPEVIS_NAPI_CHECK(napi_get_boolean(env_, etsVal, &jsValue_));
210 }
211
212 template <typename T>
VisitIntType()213 inline void VisitIntType()
214 {
215 auto etsVal = static_cast<int64_t>(loc_.LoadPrimitive<T>());
216 TYPEVIS_NAPI_CHECK(napi_create_int64(env_, etsVal, &jsValue_));
217 }
218
VisitI8()219 void VisitI8() override
220 {
221 VisitIntType<int8_t>();
222 }
223
VisitI16()224 void VisitI16() override
225 {
226 VisitIntType<int16_t>();
227 }
228
VisitU16()229 void VisitU16() override
230 {
231 VisitIntType<uint16_t>();
232 }
233
VisitI32()234 void VisitI32() override
235 {
236 VisitIntType<int32_t>();
237 }
238
VisitI64()239 void VisitI64() override
240 {
241 VisitIntType<int64_t>();
242 }
243
VisitF32()244 void VisitF32() override
245 {
246 auto etsVal = static_cast<double>(loc_.LoadPrimitive<float>());
247 TYPEVIS_NAPI_CHECK(napi_create_double(env_, etsVal, &jsValue_));
248 }
249
VisitF64()250 void VisitF64() override
251 {
252 auto etsVal = loc_.LoadPrimitive<double>();
253 TYPEVIS_NAPI_CHECK(napi_create_double(env_, etsVal, &jsValue_));
254 }
255
VisitString(panda::Class * klass)256 void VisitString([[maybe_unused]] panda::Class *klass) override
257 {
258 auto etsStr = static_cast<panda::coretypes::String *>(loc_.LoadReference());
259 TYPEVIS_NAPI_CHECK(napi_create_string_utf8(env_, reinterpret_cast<const char *>(etsStr->GetDataMUtf8()),
260 etsStr->GetMUtf8Length() - 1, &jsValue_));
261 }
262
VisitArray(panda::Class * klass)263 void VisitArray(panda::Class *klass) override
264 {
265 auto coro = EtsCoroutine::GetCurrent();
266
267 panda::HandleScope<panda::ObjectHeader *> etsHandleScope(coro);
268 auto etsArr = panda::VMHandle<panda::coretypes::Array>(coro, loc_.LoadReference());
269 auto len = etsArr->GetLength();
270
271 TYPEVIS_NAPI_CHECK(napi_create_array_with_length(env_, len, &jsValue_));
272
273 NapiScope jsHandleScope(env_);
274 auto *subType = klass->GetComponentType();
275 size_t elemSz = panda::Class::GetTypeSize(subType->GetType());
276 auto constexpr ELEMS_OFFS = panda::coretypes::Array::GetDataOffset();
277 for (size_t idx = 0; idx < len; ++idx) {
278 EtsToJsConvertor subVis(env_, ToObjRoot(etsArr.GetAddress()), ELEMS_OFFS + elemSz * idx);
279 subVis.VisitClass(subType);
280 TYPEVIS_CHECK_FORWARD_ERROR(subVis.Error());
281 TYPEVIS_NAPI_CHECK(napi_set_element(env_, jsValue_, idx, subVis.jsValue_));
282 }
283 }
284
VisitFieldReference(const panda::Field * field,panda::Class * klass)285 void VisitFieldReference(const panda::Field *field, panda::Class *klass) override
286 {
287 auto subVis = EtsToJsConvertor(env_, owner_, field->GetOffset());
288 subVis.VisitReference(klass);
289 TYPEVIS_CHECK_FORWARD_ERROR(subVis.Error());
290 auto fname = reinterpret_cast<const char *>(field->GetName().data);
291 napi_set_named_property(env_, jsValue_, fname, subVis.jsValue_);
292 }
293
VisitFieldPrimitive(const panda::Field * field,panda::panda_file::Type type)294 void VisitFieldPrimitive(const panda::Field *field, panda::panda_file::Type type) override
295 {
296 auto subVis = EtsToJsConvertor(env_, owner_, field->GetOffset());
297 subVis.VisitPrimitive(type);
298 TYPEVIS_CHECK_FORWARD_ERROR(subVis.Error());
299 auto fname = reinterpret_cast<const char *>(field->GetName().data);
300 napi_set_named_property(env_, jsValue_, fname, subVis.jsValue_);
301 }
302
VisitObject(panda::Class * klass)303 void VisitObject(panda::Class *klass) override
304 {
305 ASSERT(klass != InteropCtx::Current()->GetJSValueClass());
306 auto coro = EtsCoroutine::GetCurrent();
307 if (klass == InteropCtx::Current(coro)->GetPromiseClass()) {
308 VisitPromise();
309 return;
310 }
311
312 panda::HandleScope<panda::ObjectHeader *> etsHandleScope(coro);
313 {
314 auto etsObj = loc_.LoadReference();
315 owner_ = ToObjRoot(panda::VMHandle<panda::ObjectHeader>(coro, etsObj).GetAddress());
316 }
317
318 TYPEVIS_NAPI_CHECK(napi_create_object(env_, &jsValue_));
319
320 NapiScope jsHandleScope(env_);
321 Base::VisitObject(klass);
322 }
323
VisitPromise()324 void VisitPromise()
325 {
326 napi_deferred deferred;
327 TYPEVIS_NAPI_CHECK(napi_create_promise(env_, &deferred, &jsValue_));
328 auto *coro = EtsCoroutine::GetCurrent();
329 [[maybe_unused]] HandleScope<panda::ObjectHeader *> handleScope(coro);
330 VMHandle<EtsPromise> promise(coro, reinterpret_cast<EtsPromise *>(loc_.LoadReference()));
331 if (promise->GetState() != EtsPromise::STATE_PENDING) {
332 VMHandle<EtsObject> value(coro, promise->GetValue(coro)->GetCoreType());
333 napi_value completionValue;
334 if (value.GetPtr() == nullptr) {
335 napi_get_null(env_, &completionValue);
336 } else {
337 EtsConvertorRef::ObjRoot v = ToObjRoot(VMHandle<ObjectHeader>(coro, value->GetCoreType()).GetAddress());
338 EtsConvertorRef::ValVariant valVar(v);
339 EtsToJsConvertor valueConv(env_, &valVar);
340 valueConv.VisitReference(value->GetClass()->GetRuntimeClass());
341 TYPEVIS_CHECK_FORWARD_ERROR(valueConv.Error());
342 completionValue = valueConv.GetResult();
343 }
344 if (promise->IsResolved()) {
345 TYPEVIS_NAPI_CHECK(napi_resolve_deferred(env_, deferred, completionValue));
346 } else {
347 TYPEVIS_NAPI_CHECK(napi_reject_deferred(env_, deferred, completionValue));
348 }
349 } else {
350 UNREACHABLE();
351 }
352 }
353
GetResult()354 napi_value GetResult()
355 {
356 return jsValue_;
357 }
358
359 private:
360 EtsConvertorRef loc_;
361 EtsConvertorRef::ObjRoot owner_ {};
362
363 napi_env env_ {};
364 napi_value jsValue_ {};
365 };
366
367 class JsToEtsArgsConvertor final : public EtsMethodVisitor {
368 public:
JsToEtsArgsConvertor(panda::Method * method,napi_env env,napi_value * args,uint32_t numArgs,uint32_t argsStart)369 JsToEtsArgsConvertor(panda::Method *method, napi_env env, napi_value *args, uint32_t numArgs, uint32_t argsStart)
370 : EtsMethodVisitor(method), env_(env), jsargs_(args), numArgs_(numArgs), argsStart_(argsStart)
371 {
372 ASSERT(numArgs_ == method->GetNumArgs());
373 etsargs_.resize(numArgs_);
374 }
375
GetResult()376 std::vector<panda::Value> GetResult()
377 {
378 std::vector<panda::Value> res;
379 for (auto const &e : etsargs_) {
380 if (std::holds_alternative<panda::Value>(e)) {
381 res.emplace_back(std::get<panda::Value>(e));
382 } else {
383 res.emplace_back(*std::get<EtsConvertorRef::ObjRoot>(e));
384 }
385 }
386 return res;
387 }
388
Process()389 void Process()
390 {
391 VisitMethod();
392 }
393
394 private:
VisitReturn(panda::panda_file::Type type)395 void VisitReturn([[maybe_unused]] panda::panda_file::Type type) override
396 {
397 // do nothing
398 }
VisitReturn(panda::Class * klass)399 void VisitReturn([[maybe_unused]] panda::Class *klass) override
400 {
401 // do nothing
402 }
VisitArgument(uint32_t idx,panda::panda_file::Type type)403 void VisitArgument(uint32_t idx, panda::panda_file::Type type) override
404 {
405 // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
406 JsToEtsConvertor conv(env_, jsargs_[idx + argsStart_], &etsargs_[idx]);
407 conv.VisitPrimitive(type);
408 TYPEVIS_CHECK_FORWARD_ERROR(conv.Error());
409 }
VisitArgument(uint32_t idx,panda::Class * klass)410 void VisitArgument(uint32_t idx, panda::Class *klass) override
411 {
412 // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
413 JsToEtsConvertor conv(env_, jsargs_[idx + argsStart_], &etsargs_[idx]);
414 conv.VisitReference(klass);
415 TYPEVIS_CHECK_FORWARD_ERROR(conv.Error());
416 }
417
418 napi_env env_;
419 napi_value *jsargs_;
420 uint32_t numArgs_;
421 uint32_t argsStart_;
422 std::vector<EtsConvertorRef::ValVariant> etsargs_;
423 };
424
425 class EtsArgsClassesCollector final : protected EtsMethodVisitor {
426 public:
EtsArgsClassesCollector(panda::Method * method)427 explicit EtsArgsClassesCollector(panda::Method *method) : EtsMethodVisitor(method) {}
428
GetResult(std::unordered_set<panda::Class * > & to)429 void GetResult(std::unordered_set<panda::Class *> &to)
430 {
431 VisitMethod();
432 to = std::move(set_);
433 }
434
435 private:
VisitReturn(panda::panda_file::Type type)436 void VisitReturn([[maybe_unused]] panda::panda_file::Type type) override
437 {
438 // do nothing
439 }
VisitReturn(panda::Class * klass)440 void VisitReturn(panda::Class *klass) override
441 {
442 AddClass(klass);
443 }
VisitArgument(uint32_t idx,panda::panda_file::Type type)444 void VisitArgument([[maybe_unused]] uint32_t idx, [[maybe_unused]] panda::panda_file::Type type) override
445 {
446 // do nothing
447 }
VisitArgument(uint32_t idx,panda::Class * klass)448 void VisitArgument([[maybe_unused]] uint32_t idx, panda::Class *klass) override
449 {
450 AddClass(klass);
451 }
452
AddClass(panda::Class * klass)453 void AddClass(panda::Class *klass)
454 {
455 set_.insert(klass);
456 }
457
458 std::unordered_set<panda::Class *> set_;
459 };
460
461 class EtsClassesRecursionChecker final : public EtsTypeVisitor {
462 using Base = EtsTypeVisitor;
463
464 struct DFSData {
DFSDatapanda::ets::interop::js::EtsClassesRecursionChecker::DFSData465 explicit DFSData(panda::Class *cls) : klass(cls) {}
466
467 // NOLINTNEXTLINE(misc-non-private-member-variables-in-classes)
468 panda::Class *klass = nullptr;
469
470 enum : uint8_t {
471 MARK_W = 0,
472 MARK_G = 1,
473 MARK_B = 2,
474 } mark = MARK_W; // NOLINT(misc-non-private-member-variables-in-classes)
475 };
476
477 public:
CheckClasses(panda::Method * method)478 static bool CheckClasses(panda::Method *method)
479 {
480 std::unordered_set<panda::Class *> classSet;
481 EtsArgsClassesCollector collector(method);
482 collector.GetResult(classSet);
483
484 std::unordered_map<panda::Class *, DFSData> dfsData;
485 for (auto const &e : classSet) {
486 dfsData.insert(std::make_pair(e, DFSData(e)));
487 }
488
489 bool hasLoops = false;
490 EtsClassesRecursionChecker checker(hasLoops, dfsData);
491 for (auto const &e : dfsData) {
492 checker.VisitClass(e.second.klass);
493 if (hasLoops) {
494 break;
495 }
496 }
497 return !hasLoops;
498 }
499
500 private:
EtsClassesRecursionChecker(bool & loopFound,std::unordered_map<panda::Class *,DFSData> & dfs)501 EtsClassesRecursionChecker(bool &loopFound, std::unordered_map<panda::Class *, DFSData> &dfs)
502 : loopFound_(loopFound), dfs_(dfs)
503 {
504 }
505
VisitU1()506 void VisitU1() override {}
VisitI8()507 void VisitI8() override {}
VisitU16()508 void VisitU16() override {}
VisitI16()509 void VisitI16() override {}
VisitI32()510 void VisitI32() override {}
VisitI64()511 void VisitI64() override {}
VisitF32()512 void VisitF32() override {}
VisitF64()513 void VisitF64() override {}
514
VisitPrimitive(const panda::panda_file::Type type)515 void VisitPrimitive([[maybe_unused]] const panda::panda_file::Type type) override {}
516
VisitString(panda::Class * klass)517 void VisitString([[maybe_unused]] panda::Class *klass) override {}
518
VisitArray(panda::Class * klass)519 void VisitArray(panda::Class *klass) override
520 {
521 VisitClass(klass->GetComponentType());
522 }
523
VisitFieldPrimitive(const panda::Field * field,panda::panda_file::Type type)524 void VisitFieldPrimitive([[maybe_unused]] const panda::Field *field,
525 [[maybe_unused]] panda::panda_file::Type type) override
526 {
527 }
528
VisitFieldReference(const panda::Field * field,panda::Class * klass)529 void VisitFieldReference([[maybe_unused]] const panda::Field *field, panda::Class *klass) override
530 {
531 VisitReference(klass);
532 }
533
VisitReference(panda::Class * klass)534 void VisitReference(panda::Class *klass) override
535 {
536 auto str = klass->GetName();
537 auto &data = Lookup(klass);
538 if (data.mark == DFSData::MARK_B) {
539 return;
540 }
541 if (data.mark == DFSData::MARK_G) {
542 loopFound_ = true;
543 return;
544 }
545 data.mark = DFSData::MARK_G;
546 Base::VisitReference(klass);
547 data.mark = DFSData::MARK_B;
548 }
549
Lookup(panda::Class * klass)550 DFSData &Lookup(panda::Class *klass)
551 {
552 auto it = dfs_.find(klass);
553 if (it == dfs_.end()) {
554 auto x = dfs_.insert(std::make_pair(klass, DFSData(klass)));
555 return x.first->second;
556 }
557 return it->second;
558 }
559
560 bool &loopFound_;
561 std::unordered_map<panda::Class *, DFSData> &dfs_;
562 };
563
564 class EtsToJsRetConvertor final : public EtsMethodVisitor {
565 public:
EtsToJsRetConvertor(panda::Method * method,napi_env env,panda::Value ret)566 EtsToJsRetConvertor(panda::Method *method, napi_env env, panda::Value ret) : EtsMethodVisitor(method), env_(env)
567 {
568 if (ret.IsReference()) {
569 auto coro = EtsCoroutine::GetCurrent();
570 ret_ = ToObjRoot(panda::VMHandle<panda::ObjectHeader>(coro, *ret.GetGCRoot()).GetAddress());
571 } else {
572 ret_ = ret;
573 }
574 }
575
Process()576 void Process()
577 {
578 VisitMethod();
579 }
580
GetResult()581 napi_value GetResult()
582 {
583 return jsRet_;
584 }
585
586 private:
VisitReturn(panda::panda_file::Type type)587 void VisitReturn(panda::panda_file::Type type) override
588 {
589 if (type.GetId() == panda_file::Type::TypeId::VOID) {
590 // Skip 'void' type because it doesn't require conversion
591 return;
592 }
593
594 EtsToJsConvertor conv(env_, &ret_);
595 conv.VisitPrimitive(type);
596 TYPEVIS_CHECK_FORWARD_ERROR(conv.Error());
597 jsRet_ = conv.GetResult();
598 }
VisitReturn(panda::Class * klass)599 void VisitReturn(panda::Class *klass) override
600 {
601 EtsToJsConvertor conv(env_, &ret_);
602 conv.VisitReference(klass);
603 TYPEVIS_CHECK_FORWARD_ERROR(conv.Error());
604 jsRet_ = conv.GetResult();
605 }
VisitArgument(uint32_t idx,panda::panda_file::Type type)606 void VisitArgument([[maybe_unused]] uint32_t idx, [[maybe_unused]] panda::panda_file::Type type) override
607 {
608 // do nothing
609 }
VisitArgument(uint32_t idx,panda::Class * klass)610 void VisitArgument([[maybe_unused]] uint32_t idx, [[maybe_unused]] panda::Class *klass) override
611 {
612 // do nothing
613 }
614
615 napi_env env_ {};
616 EtsConvertorRef::ValVariant ret_;
617 napi_value jsRet_ {};
618 };
619
InvokeEtsMethodImpl(napi_env env,napi_value * jsargv,uint32_t jsargc,bool doClscheck)620 napi_value InvokeEtsMethodImpl(napi_env env, napi_value *jsargv, uint32_t jsargc, bool doClscheck)
621 {
622 auto coro = EtsCoroutine::GetCurrent();
623 [[maybe_unused]] EtsJSNapiEnvScope scope(InteropCtx::Current(coro), env);
624
625 if (jsargc < 1) {
626 InteropCtx::ThrowJSError(env, "InvokeEtsMethod: method name required");
627 return nullptr;
628 }
629
630 // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
631 if (GetValueType(env, jsargv[0]) != napi_string) {
632 InteropCtx::ThrowJSError(env, "InvokeEtsMethod: method name is not a string");
633 return nullptr;
634 }
635 // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
636 auto methodName = std::string("ETSGLOBAL::") + GetString(env, jsargv[0]);
637 INTEROP_LOG(INFO) << "InvokeEtsMethod: method name: " << methodName.c_str();
638
639 auto methodRes = panda::Runtime::GetCurrent()->ResolveEntryPoint(methodName);
640 if (!methodRes) {
641 InteropCtx::ThrowJSError(env, "InvokeEtsMethod: can't resolve method " + methodName);
642 return nullptr;
643 }
644 auto method = methodRes.Value();
645
646 auto numArgs = method->GetNumArgs();
647 if (numArgs != jsargc - 1) {
648 InteropCtx::ThrowJSError(env, std::string("InvokeEtsMethod: wrong argc"));
649 return nullptr;
650 }
651
652 ScopedManagedCodeThread managedScope(coro);
653
654 if (doClscheck && !EtsClassesRecursionChecker::CheckClasses(method)) {
655 InteropCtx::ThrowJSError(env, "InvokeEtsMethod: loops possible in args objects");
656 return nullptr;
657 }
658
659 std::vector<panda::Value> args;
660 {
661 JsToEtsArgsConvertor js2ets(method, env, jsargv, jsargc - 1, 1);
662
663 NapiScope jsHandleScope(env);
664 panda::HandleScope<panda::ObjectHeader *> etsHandleScope(coro);
665
666 auto begin = std::chrono::steady_clock::now();
667 js2ets.Process();
668 if (js2ets.Error()) {
669 InteropCtx::ThrowJSError(env, std::string("InvokeEtsMethod: js2ets error: ") + js2ets.Error().value());
670 return nullptr;
671 }
672 args = js2ets.GetResult();
673 auto end = std::chrono::steady_clock::now();
674 int64_t t = std::chrono::duration_cast<std::chrono::microseconds>(end - begin).count();
675 INTEROP_LOG(INFO) << "InvokeEtsMethod: js2ets elapsed time: " << t << "us";
676 }
677
678 panda::Value etsRes = method->Invoke(coro, args.data());
679 if (UNLIKELY(coro->HasPendingException())) {
680 NapiScope jsHandleScope(env);
681 panda::HandleScope<panda::ObjectHeader *> etsHandleScope(coro);
682
683 auto exc = coro->GetException();
684 auto klass = exc->ClassAddr<panda::Class>();
685 auto data =
686 EtsConvertorRef::ValVariant(ToObjRoot(panda::VMHandle<panda::ObjectHeader>(coro, exc).GetAddress()));
687 coro->ClearException();
688
689 EtsToJsConvertor ets2js(env, &data);
690 ets2js.VisitClass(klass);
691 if (ets2js.Error()) {
692 InteropCtx::ThrowJSError(env,
693 std::string("InvokeEtsMethod: ets2js error while converting pending exception: " +
694 ets2js.Error().value()));
695 return nullptr;
696 }
697 InteropCtx::ThrowJSValue(env, ets2js.GetResult());
698 return nullptr;
699 }
700
701 napi_value jsRes;
702 {
703 NapiEscapableScope jsHandleScope(env);
704 panda::HandleScope<panda::ObjectHeader *> etsHandleScope(coro);
705
706 auto begin = std::chrono::steady_clock::now();
707 EtsToJsRetConvertor ets2js(method, env, etsRes);
708 ets2js.Process();
709 if (ets2js.Error()) {
710 InteropCtx::ThrowJSError(env, std::string("InvokeEtsMethod: ets2js error: ") + ets2js.Error().value());
711 return nullptr;
712 }
713 jsRes = ets2js.GetResult();
714 auto end = std::chrono::steady_clock::now();
715 int64_t t = std::chrono::duration_cast<std::chrono::microseconds>(end - begin).count();
716 INTEROP_LOG(INFO) << "InvokeEtsMethod: ets2js elapsed time: " << t << "us";
717
718 // Check that the method has a return value
719 panda_file::Type retType = method->GetProto().GetReturnType();
720 if (retType.GetId() != panda_file::Type::TypeId::VOID) {
721 ASSERT(jsRes != nullptr);
722 jsHandleScope.Escape(jsRes);
723 } else {
724 ASSERT(jsRes == nullptr);
725 }
726 }
727 return jsRes;
728 }
729
730 } // namespace panda::ets::interop::js
731