• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (c) 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/call/call.h"
17 #include "plugins/ets/runtime/interop_js/call/arg_convertors.h"
18 #include "plugins/ets/runtime/interop_js/call/proto_reader.h"
19 #include "plugins/ets/runtime/interop_js/code_scopes.h"
20 
21 namespace ark::ets::interop::js {
22 
23 class CallETSHandler {
24 public:
CallETSHandler(EtsCoroutine * coro,InteropCtx * ctx,Method * method,Span<napi_value> jsargv,EtsObject * thisObj)25     ALWAYS_INLINE CallETSHandler(EtsCoroutine *coro, InteropCtx *ctx, Method *method, Span<napi_value> jsargv,
26                                  EtsObject *thisObj)
27         : coro_(coro),
28           ctx_(ctx),
29           protoReader_(method, ctx_->GetClassLinker(), ctx_->LinkerCtx()),
30           thisObj_(thisObj),
31           jsargv_(jsargv)
32     {
33     }
34 
35     template <bool IS_STATIC>
HandleImpl(EtsCoroutine * coro,InteropCtx * ctx,Method * method,Span<napi_value> jsargv,EtsObject * thisObj)36     static ALWAYS_INLINE napi_value HandleImpl(EtsCoroutine *coro, InteropCtx *ctx, Method *method,
37                                                Span<napi_value> jsargv, EtsObject *thisObj)
38     {
39         CallETSHandler st(coro, ctx, method, jsargv, thisObj);
40         return st.HandleImpl<IS_STATIC>();
41     }
42 
43     ~CallETSHandler() = default;
44 
45 private:
46     template <bool IS_STATIC>
47     ALWAYS_INLINE bool ConvertArgs(Span<Value> etsArgs);
48     ALWAYS_INLINE ObjectHeader **ConvertRestParams(Span<napi_value> restArgs);
49 
50     ALWAYS_INLINE bool CheckNumArgs(size_t numArgs) const;
51 
52     template <bool IS_STATIC>
53     napi_value HandleImpl();
54 
ForwardException(InteropCtx * ctx,EtsCoroutine * coro)55     static napi_value __attribute__((noinline)) ForwardException(InteropCtx *ctx, EtsCoroutine *coro)
56     {
57         if (coro->HasPendingException()) {
58             ctx->ForwardEtsException(coro);
59         }
60         ASSERT(ctx->SanityJSExceptionPending());
61         return nullptr;
62     }
63 
64     NO_COPY_SEMANTIC(CallETSHandler);
65     NO_MOVE_SEMANTIC(CallETSHandler);
66 
67     EtsCoroutine *const coro_;
68     InteropCtx *const ctx_;
69 
70     ProtoReader protoReader_;
71 
72     EtsObject *thisObj_;
73     Span<napi_value> jsargv_;
74 };
75 
76 template <bool IS_STATIC>
ConvertArgs(Span<Value> etsArgs)77 ALWAYS_INLINE inline bool CallETSHandler::ConvertArgs(Span<Value> etsArgs)
78 {
79     HandleScope<ObjectHeader *> etsHandleScope(coro_);
80     auto const createRoot = [coro = coro_](ObjectHeader *val) {
81         return reinterpret_cast<ObjectHeader **>(VMHandle<ObjectHeader>(coro, val).GetAddress());
82     };
83 
84     ObjectHeader **thisObjRoot = IS_STATIC ? nullptr : createRoot(thisObj_->GetCoreType());
85 
86     using ArgValueBox = std::variant<uint64_t, ObjectHeader **>;
87     auto const numArgs = protoReader_.GetMethod()->GetNumArgs() - !IS_STATIC;
88     auto const numNonRest = numArgs - protoReader_.GetMethod()->HasVarArgs();
89     auto etsBoxedArgs = ctx_->GetTempArgs<ArgValueBox>(numArgs);
90 
91     // Convert and store in root if necessary
92     for (uint32_t argIdx = 0; argIdx < numNonRest; ++argIdx, protoReader_.Advance()) {
93         auto jsVal = jsargv_[argIdx];
94         auto store = [&etsBoxedArgs, &argIdx, createRoot](auto val) {
95             if constexpr (std::is_pointer_v<decltype(val)> || std::is_null_pointer_v<decltype(val)>) {
96                 etsBoxedArgs[argIdx] = createRoot(val);
97             } else {
98                 etsBoxedArgs[argIdx] = static_cast<uint64_t>(val);
99             }
100         };
101         if (UNLIKELY(!ConvertArgToEts(ctx_, protoReader_, store, jsVal))) {
102             return false;
103         }
104     }
105 
106     if (protoReader_.GetMethod()->HasVarArgs()) {
107         const auto restIdx = numArgs - 1;
108         etsBoxedArgs[restIdx] = ConvertRestParams(jsargv_.SubSpan(restIdx));
109     }
110 
111     // Unbox values
112     if constexpr (!IS_STATIC) {
113         etsArgs[0] = Value(*thisObjRoot);
114     }
115     static constexpr size_t ETS_ARGS_DISP = IS_STATIC ? 0 : 1;
116 
117     for (size_t i = 0; i < numArgs; ++i) {
118         ArgValueBox &box = etsBoxedArgs[i];
119         if (std::holds_alternative<ObjectHeader **>(box)) {
120             ObjectHeader **slot = std::get<1>(box);
121             etsArgs[ETS_ARGS_DISP + i] = Value(slot != nullptr ? *slot : nullptr);
122         } else {
123             etsArgs[ETS_ARGS_DISP + i] = Value(std::get<0>(box));
124         }
125     }
126     return true;
127 }
128 
ConvertRestParams(Span<napi_value> restArgs)129 ObjectHeader **CallETSHandler::ConvertRestParams(Span<napi_value> restArgs)
130 {
131     ASSERT(protoReader_.GetType().IsReference());
132     ASSERT(protoReader_.GetClass()->IsArrayClass());
133 
134     ObjectHeader **restParamsSlot = PackRestParameters(coro_, ctx_, protoReader_, restArgs);
135     ASSERT(restParamsSlot != nullptr);
136 
137     return restParamsSlot;
138 }
139 
CheckNumArgs(size_t numArgs) const140 bool CallETSHandler::CheckNumArgs(size_t numArgs) const
141 {
142     const auto method = protoReader_.GetMethod();
143     bool const hasRestParams = method->HasVarArgs();
144     ASSERT((hasRestParams && numArgs > 0) || !hasRestParams);
145 
146     if ((hasRestParams && (numArgs - 1) > jsargv_.size()) || (!hasRestParams && numArgs != jsargv_.size())) {
147         std::string msg = "CallEtsFunction: wrong argc, ets_argc=" + std::to_string(numArgs) +
148                           " js_argc=" + std::to_string(jsargv_.size()) + " ets_method='" +
149                           std::string(method->GetFullName(true)) + "'";
150         InteropCtx::ThrowJSTypeError(ctx_->GetJSEnv(), msg);
151         return false;
152     }
153     return true;
154 }
155 
156 template <bool IS_STATIC>
HandleImpl()157 napi_value CallETSHandler::HandleImpl()
158 {
159     ASSERT_MANAGED_CODE();
160     auto method = protoReader_.GetMethod();
161 
162     protoReader_.Advance();  // skip return type
163     ASSERT(method->IsStatic() == IS_STATIC);
164     ASSERT(IS_STATIC == (thisObj_ == nullptr));
165 
166     auto const numArgs = method->GetNumArgs() - (IS_STATIC ? 0 : 1);
167     if (UNLIKELY(!CheckNumArgs(numArgs))) {
168         return ForwardException(ctx_, coro_);
169     }
170 
171     auto etsArgs = ctx_->GetTempArgs<Value>(method->GetNumArgs());
172     if (UNLIKELY(!ConvertArgs<IS_STATIC>(*etsArgs))) {
173         return ForwardException(ctx_, coro_);
174     }
175 
176     Value etsRes = method->Invoke(coro_, etsArgs->data());
177     if (UNLIKELY(coro_->HasPendingException())) {
178         return ForwardException(ctx_, coro_);
179     }
180 
181     protoReader_.Reset();
182     napi_value jsRes;
183     auto readVal = [&etsRes](auto typeTag) { return etsRes.GetAs<typename decltype(typeTag)::type>(); };
184     if (UNLIKELY(!ConvertArgToJS(ctx_, protoReader_, &jsRes, readVal))) {
185         return ForwardException(ctx_, coro_);
186     }
187     return jsRes;
188 }
189 
CallETSInstance(EtsCoroutine * coro,InteropCtx * ctx,Method * method,Span<napi_value> jsargv,EtsObject * thisObj)190 napi_value CallETSInstance(EtsCoroutine *coro, InteropCtx *ctx, Method *method, Span<napi_value> jsargv,
191                            EtsObject *thisObj)
192 {
193     return CallETSHandler::HandleImpl<false>(coro, ctx, method, jsargv, thisObj);
194 }
CallETSStatic(EtsCoroutine * coro,InteropCtx * ctx,Method * method,Span<napi_value> jsargv)195 napi_value CallETSStatic(EtsCoroutine *coro, InteropCtx *ctx, Method *method, Span<napi_value> jsargv)
196 {
197     return CallETSHandler::HandleImpl<true>(coro, ctx, method, jsargv, nullptr);
198 }
199 
ResolveEntryPoint(InteropCtx * ctx,std::string_view entryPoint)200 Expected<Method *, char const *> ResolveEntryPoint(InteropCtx *ctx, std::string_view entryPoint)
201 {
202     uint8_t const *classDescriptor;
203     uint8_t const *methodName;
204     PandaString complexClassName;
205 
206     if (auto packageSep = entryPoint.rfind('.'); packageSep != PandaString::npos) {
207         complexClassName = 'L' + PandaString(entryPoint.substr(0, packageSep + 1)) + "ETSGLOBAL;";
208         std::replace(complexClassName.begin(), complexClassName.end(), '.', '/');
209         classDescriptor = utf::CStringAsMutf8(complexClassName.data());
210         methodName = utf::CStringAsMutf8(&entryPoint.at(packageSep + 1));
211     } else {
212         classDescriptor = utf::CStringAsMutf8("LETSGLOBAL;");
213         methodName = utf::CStringAsMutf8(entryPoint.data());
214     }
215 
216     Class *cls = ctx->GetClassLinker()->GetClass(classDescriptor, true, ctx->LinkerCtx());
217     if (UNLIKELY(cls == nullptr)) {
218         return Unexpected("Cannot find class");
219     }
220 
221     Method *method = cls->GetDirectMethod(methodName);
222     if (UNLIKELY(method == nullptr)) {
223         return Unexpected("Cannot find method");
224     }
225     return method;
226 }
227 
228 }  // namespace ark::ets::interop::js
229