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