• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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