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