• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (c) 2023-2024 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #include "plugins/ets/runtime/interop_js/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