• 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/interop_context.h"
17 
18 #include "plugins/ets/runtime/ets_exceptions.h"
19 #include "plugins/ets/runtime/ets_class_linker_extension.h"
20 #include "plugins/ets/runtime/ets_vm.h"
21 #include "plugins/ets/runtime/interop_js/js_convert.h"
22 #include "plugins/ets/runtime/interop_js/interop_common.h"
23 #include "plugins/ets/runtime/interop_js/code_scopes.h"
24 #include "plugins/ets/runtime/types/ets_method.h"
25 #include "runtime/include/runtime.h"
26 #include "runtime/mem/local_object_handle.h"
27 
28 namespace ark::ets::interop::js {
29 
30 namespace descriptors = panda_file_items::class_descriptors;
31 
32 // NOLINTNEXTLINE(modernize-avoid-c-arrays)
33 static constexpr std::string_view const FUNCTION_INTERFACE_DESCRIPTORS[] = {
34     descriptors::FUNCTION0,  descriptors::FUNCTION1,  descriptors::FUNCTION2,  descriptors::FUNCTION3,
35     descriptors::FUNCTION4,  descriptors::FUNCTION5,  descriptors::FUNCTION6,  descriptors::FUNCTION7,
36     descriptors::FUNCTION8,  descriptors::FUNCTION9,  descriptors::FUNCTION10, descriptors::FUNCTION11,
37     descriptors::FUNCTION12, descriptors::FUNCTION13, descriptors::FUNCTION14, descriptors::FUNCTION15,
38     descriptors::FUNCTION16, descriptors::FUNCTIONN,
39 };
40 
CacheClass(EtsClassLinker * etsClassLinker,std::string_view descriptor)41 static Class *CacheClass(EtsClassLinker *etsClassLinker, std::string_view descriptor)
42 {
43     auto klass = etsClassLinker->GetClass(descriptor.data())->GetRuntimeClass();
44     ASSERT(klass != nullptr);
45     return klass;
46 }
47 
LoadDynamicCallClass(Class * klass)48 void ConstStringStorage::LoadDynamicCallClass(Class *klass)
49 {
50     if (klass == lastLoadedClass_) {
51         return;
52     }
53     lastLoadedClass_ = klass;
54     auto fields = klass->GetStaticFields();
55     ASSERT(fields.Size() == 1);
56     auto startFrom = klass->GetFieldPrimitive<uint32_t>(fields[0]);
57     napi_value jsArr;
58     auto *env = Ctx()->GetJSEnv();
59     if (jsStringBufferRef_ == nullptr) {
60         jsArr = InitBuffer(Ctx()->GetStringBufferSize());
61     } else {
62         jsArr = GetReferenceValue(env, jsStringBufferRef_);
63         if (startFrom >= stringBuffer_.size()) {
64             stringBuffer_.resize(Ctx()->GetStringBufferSize());
65         } else if (stringBuffer_[startFrom] != nullptr) {
66             return;
67         }
68     }
69 
70     auto *pf = klass->GetPandaFile();
71     panda_file::ClassDataAccessor cda(*pf, klass->GetFileId());
72     [[maybe_unused]] auto annotationFound = cda.EnumerateAnnotation(
73         "Lets/annotation/DynamicCall;", [this, pf, startFrom, jsArr, env](panda_file::AnnotationDataAccessor &ada) {
74             for (uint32_t i = 0; i < ada.GetCount(); i++) {
75                 auto adae = ada.GetElement(i);
76                 auto *elemName = pf->GetStringData(adae.GetNameId()).data;
77                 if (!utf::IsEqual(utf::CStringAsMutf8("value"), elemName)) {
78                     continue;
79                 }
80                 auto arr = adae.GetArrayValue();
81                 auto count = arr.GetCount();
82                 for (uint32_t j = 0, bufferIndex = startFrom; j < count; j++, bufferIndex++) {
83                     panda_file::File::EntityId stringId(arr.Get<uint32_t>(j));
84                     auto data = pf->GetStringData(stringId);
85                     napi_value jsStr;
86                     NAPI_CHECK_FATAL(
87                         napi_create_string_utf8(env, utf::Mutf8AsCString(data.data), data.utf16Length, &jsStr));
88                     ASSERT(stringBuffer_[bufferIndex] == nullptr);
89                     napi_set_element(env, jsArr, bufferIndex, jsStr);
90                     stringBuffer_[bufferIndex] = jsStr;
91                 }
92                 return true;
93             }
94             UNREACHABLE();
95         });
96     ASSERT(annotationFound.has_value());
97 }
98 
GetConstantPool()99 napi_value ConstStringStorage::GetConstantPool()
100 {
101     return GetReferenceValue(Ctx()->GetJSEnv(), jsStringBufferRef_);
102 }
103 
InitBuffer(size_t length)104 napi_value ConstStringStorage::InitBuffer(size_t length)
105 {
106     napi_value jsArr;
107     NAPI_CHECK_FATAL(napi_create_array_with_length(Ctx()->GetJSEnv(), length, &jsArr));
108     NAPI_CHECK_FATAL(napi_create_reference(Ctx()->GetJSEnv(), jsArr, 1, &jsStringBufferRef_));
109     stringBuffer_.resize(Ctx()->GetStringBufferSize());
110     return jsArr;
111 }
112 
Ctx()113 InteropCtx *ConstStringStorage::Ctx()
114 {
115     return InteropCtx::FromConstStringStorage(this);
116 }
117 
InteropCtx(EtsCoroutine * coro,napi_env env)118 InteropCtx::InteropCtx(EtsCoroutine *coro, napi_env env)
119 {
120     JSNapiEnvScope envscope(this, env);
121 
122     jsEnvForEventLoopCallbacks_ = env;
123     PandaEtsVM *vm = coro->GetPandaVM();
124     EtsClassLinker *etsClassLinker = vm->GetClassLinker();
125     refstor_ = vm->GetGlobalObjectStorage();
126     classLinker_ = Runtime::GetCurrent()->GetClassLinker();
127     linkerCtx_ = etsClassLinker->GetEtsClassLinkerExtension()->GetBootContext();
128 
129     auto *jobQueue = Runtime::GetCurrent()->GetInternalAllocator()->New<JsJobQueue>();
130     vm->InitJobQueue(jobQueue);
131 
132     CacheClasses(etsClassLinker);
133 
134     RegisterBuiltinJSRefConvertors(this);
135 
136     {
137         auto method = EtsClass::FromRuntimeClass(jsRuntimeClass_)->GetMethod("createFinalizationRegistry");
138         ASSERT(method != nullptr);
139         auto res = method->GetPandaMethod()->Invoke(coro, nullptr);
140         ASSERT(!coro->HasPendingException());
141         auto queue = EtsObject::FromCoreType(res.GetAs<ObjectHeader *>());
142         jsvalueFregistryRef_ = Refstor()->Add(queue->GetCoreType(), mem::Reference::ObjectType::GLOBAL);
143         ASSERT(jsvalueFregistryRef_ != nullptr);
144 
145         jsvalueFregistryRegister_ = queue->GetClass()
146                                         ->GetMethod("register", "Lstd/core/Object;Lstd/core/Object;Lstd/core/Object;:V")
147                                         ->GetPandaMethod();
148         ASSERT(jsvalueFregistryRegister_ != nullptr);
149     }
150 
151     {
152         EtsClass *promiseInteropClass =
153             EtsClass::FromRuntimeClass(CacheClass(etsClassLinker, "Lstd/interop/js/PromiseInterop;"));
154         ASSERT(promiseInteropClass != nullptr);
155         promiseInteropConnectMethod_ =
156             promiseInteropClass->GetMethod("connectPromise", "Lstd/core/Promise;J:V")->GetPandaMethod();
157         ASSERT(promiseInteropConnectMethod_ != nullptr);
158     }
159 
160     etsProxyRefStorage_ = ets_proxy::SharedReferenceStorage::Create();
161     ASSERT(etsProxyRefStorage_.get() != nullptr);
162 
163     // Set InteropCtx::DestroyLocalScopeForTopFrame to VM for call it it deoptimization and exception handlers
164     vm->SetClearInteropHandleScopesFunction([this](Frame *frame) { this->DestroyLocalScopeForTopFrame(frame); });
165     vm->SetDestroyExternalDataFunction(Destroy);
166 }
167 
CacheClasses(EtsClassLinker * etsClassLinker)168 void InteropCtx::CacheClasses(EtsClassLinker *etsClassLinker)
169 {
170     jsRuntimeClass_ = CacheClass(etsClassLinker, descriptors::JS_RUNTIME);
171     jsValueClass_ = CacheClass(etsClassLinker, descriptors::JS_VALUE);
172     jsErrorClass_ = CacheClass(etsClassLinker, descriptors::JS_ERROR);
173     objectClass_ = CacheClass(etsClassLinker, descriptors::OBJECT);
174     stringClass_ = CacheClass(etsClassLinker, descriptors::STRING);
175     bigintClass_ = CacheClass(etsClassLinker, descriptors::BIG_INT);
176     undefinedClass_ = CacheClass(etsClassLinker, descriptors::INTERNAL_UNDEFINED);
177     promiseClass_ = CacheClass(etsClassLinker, descriptors::PROMISE);
178     errorClass_ = CacheClass(etsClassLinker, descriptors::ERROR);
179     exceptionClass_ = CacheClass(etsClassLinker, descriptors::EXCEPTION);
180     typeClass_ = CacheClass(etsClassLinker, descriptors::TYPE);
181 
182     boxIntClass_ = CacheClass(etsClassLinker, descriptors::BOX_INT);
183     boxLongClass_ = CacheClass(etsClassLinker, descriptors::BOX_LONG);
184 
185     arrayClass_ = CacheClass(etsClassLinker, descriptors::ARRAY);
186     arraybufClass_ = CacheClass(etsClassLinker, descriptors::ARRAY_BUFFER);
187 
188     arrayAsListIntClass_ = CacheClass(etsClassLinker, descriptors::ARRAY_AS_LIST_INT);
189 
190     for (auto descr : FUNCTION_INTERFACE_DESCRIPTORS) {
191         functionalInterfaces_.insert(CacheClass(etsClassLinker, descr));
192     }
193 }
194 
CreateETSCoreJSError(EtsCoroutine * coro,JSValue * jsvalue)195 EtsObject *InteropCtx::CreateETSCoreJSError(EtsCoroutine *coro, JSValue *jsvalue)
196 {
197     [[maybe_unused]] HandleScope<ObjectHeader *> scope(coro);
198     VMHandle<ObjectHeader> jsvalueHandle(coro, jsvalue->GetCoreType());
199 
200     Method::Proto proto(Method::Proto::ShortyVector {panda_file::Type(panda_file::Type::TypeId::VOID),
201                                                      panda_file::Type(panda_file::Type::TypeId::REFERENCE)},
202                         Method::Proto::RefTypeVector {utf::Mutf8AsCString(GetJSValueClass()->GetDescriptor())});
203     auto ctorName = utf::CStringAsMutf8(panda_file_items::CTOR.data());
204     auto ctor = GetJSErrorClass()->GetDirectMethod(ctorName, proto);
205     ASSERT(ctor != nullptr);
206 
207     auto excObj = ObjectHeader::Create(coro, GetJSErrorClass());
208     if (UNLIKELY(excObj == nullptr)) {
209         return nullptr;
210     }
211     VMHandle<ObjectHeader> excHandle(coro, excObj);
212 
213     std::array<Value, 2U> args {Value(excHandle.GetPtr()), Value(jsvalueHandle.GetPtr())};
214     ctor->InvokeVoid(coro, args.data());
215     auto res = EtsObject::FromCoreType(excHandle.GetPtr());
216     if (UNLIKELY(coro->HasPendingException())) {
217         return nullptr;
218     }
219     return res;
220 }
221 
ThrowETSError(EtsCoroutine * coro,napi_value val)222 void InteropCtx::ThrowETSError(EtsCoroutine *coro, napi_value val)
223 {
224     auto ctx = Current(coro);
225 
226     if (coro->IsUsePreAllocObj()) {
227         coro->SetUsePreAllocObj(false);
228         coro->SetException(coro->GetVM()->GetOOMErrorObject());
229         return;
230     }
231     ASSERT(!coro->HasPendingException());
232 
233     if (IsNullOrUndefined(ctx->GetJSEnv(), val)) {
234         ctx->ThrowETSError(coro, "interop/js throws undefined/null");
235         return;
236     }
237 
238     // To catch `TypeError` or `UserError extends TypeError`
239     // 1. Frontend puts catch(compat/TypeError) { <instanceof-rethrow? if UserError expected> }
240     //    Where js.UserError will be wrapped into compat/TypeError
241     //    NOTE(vpukhov): compat: add intrinsic to obtain JSValue from compat/ instances
242 
243     auto objRefconv = JSRefConvertResolve(ctx, ctx->GetObjectClass());
244     LocalObjectHandle<EtsObject> etsObj(coro, objRefconv->Unwrap(ctx, val));
245     if (UNLIKELY(etsObj.GetPtr() == nullptr)) {
246         INTEROP_LOG(INFO) << "Something went wrong while unwrapping pending js exception";
247         ASSERT(ctx->SanityETSExceptionPending());
248         return;
249     }
250 
251     auto klass = etsObj->GetClass()->GetRuntimeClass();
252     if (LIKELY(ctx->GetErrorClass()->IsAssignableFrom(klass) || ctx->GetExceptionClass()->IsAssignableFrom(klass))) {
253         coro->SetException(etsObj->GetCoreType());
254         return;
255     }
256 
257     // NOTE(vpukhov): should throw a special error (JSError?) with cause set
258     auto exc = JSConvertJSError::Unwrap(ctx, ctx->GetJSEnv(), val);
259     if (LIKELY(exc.has_value())) {
260         ASSERT(exc != nullptr);
261         coro->SetException(exc.value()->GetCoreType());
262     }  // otherwise exception is already set
263 }
264 
ThrowETSError(EtsCoroutine * coro,const char * msg)265 void InteropCtx::ThrowETSError(EtsCoroutine *coro, const char *msg)
266 {
267     ASSERT(!coro->HasPendingException());
268     ets::ThrowEtsException(coro, panda_file_items::class_descriptors::ERROR, msg);
269 }
270 
ThrowJSError(napi_env env,const std::string & msg)271 void InteropCtx::ThrowJSError(napi_env env, const std::string &msg)
272 {
273     INTEROP_LOG(INFO) << "ThrowJSError: " << msg;
274     ASSERT(!NapiIsExceptionPending(env));
275     NAPI_CHECK_FATAL(napi_throw_error(env, nullptr, msg.c_str()));
276 }
277 
ThrowJSTypeError(napi_env env,const std::string & msg)278 void InteropCtx::ThrowJSTypeError(napi_env env, const std::string &msg)
279 {
280     INTEROP_LOG(INFO) << "ThrowJSTypeError: " << msg;
281     ASSERT(!NapiIsExceptionPending(env));
282     NAPI_CHECK_FATAL(napi_throw_type_error(env, nullptr, msg.c_str()));
283 }
284 
ThrowJSValue(napi_env env,napi_value val)285 void InteropCtx::ThrowJSValue(napi_env env, napi_value val)
286 {
287     INTEROP_LOG(INFO) << "ThrowJSValue";
288     ASSERT(!NapiIsExceptionPending(env));
289     NAPI_CHECK_FATAL(napi_throw(env, val));
290 }
291 
ForwardEtsException(EtsCoroutine * coro)292 void InteropCtx::ForwardEtsException(EtsCoroutine *coro)
293 {
294     auto env = GetJSEnv();
295     ASSERT(coro->HasPendingException());
296     LocalObjectHandle<ObjectHeader> exc(coro, coro->GetException());
297     coro->ClearException();
298 
299     auto klass = exc->ClassAddr<Class>();
300     ASSERT(GetErrorClass()->IsAssignableFrom(klass) || GetExceptionClass()->IsAssignableFrom(klass));
301     JSRefConvert *refconv = JSRefConvertResolve<true>(this, klass);
302     if (UNLIKELY(refconv == nullptr)) {
303         INTEROP_LOG(INFO) << "Exception thrown while forwarding ets exception: " << klass->GetDescriptor();
304         return;
305     }
306     napi_value res = refconv->Wrap(this, EtsObject::FromCoreType(exc.GetPtr()));
307     if (UNLIKELY(res == nullptr)) {
308         return;
309     }
310     ThrowJSValue(env, res);
311 }
312 
ForwardJSException(EtsCoroutine * coro)313 void InteropCtx::ForwardJSException(EtsCoroutine *coro)
314 {
315     auto env = GetJSEnv();
316     napi_value excval;
317     ASSERT(NapiIsExceptionPending(env));
318     NAPI_CHECK_FATAL(napi_get_and_clear_last_exception(env, &excval));
319     ThrowETSError(coro, excval);
320 }
321 
JSConvertTypeCheckFailed(const char * typeName)322 void JSConvertTypeCheckFailed(const char *typeName)
323 {
324     auto ctx = InteropCtx::Current();
325     auto env = ctx->GetJSEnv();
326     InteropCtx::ThrowJSTypeError(env, typeName + std::string(" expected"));
327 }
328 
GetErrorStack(napi_env env,napi_value jsErr)329 static std::optional<std::string> GetErrorStack(napi_env env, napi_value jsErr)
330 {
331     bool isError;
332     if (napi_ok != napi_is_error(env, jsErr, &isError)) {
333         return {};
334     }
335     if (!isError) {
336         return "not an Error instance";
337     }
338     napi_value jsStk;
339     if (napi_ok != napi_get_named_property(env, jsErr, "stack", &jsStk)) {
340         return {};
341     }
342     size_t length;
343     if (napi_ok != napi_get_value_string_utf8(env, jsStk, nullptr, 0, &length)) {
344         return {};
345     }
346     std::string value;
347     value.resize(length);
348     // +1 for NULL terminated string!!!
349     if (napi_ok != napi_get_value_string_utf8(env, jsStk, value.data(), value.size() + 1, &length)) {
350         return {};
351     }
352     return value;
353 }
354 
NapiTryDumpStack(napi_env env)355 static std::optional<std::string> NapiTryDumpStack(napi_env env)
356 {
357     bool isPending;
358     if (napi_ok != napi_is_exception_pending(env, &isPending)) {
359         return {};
360     }
361 
362     std::string pendingErrorMsg;
363     if (isPending) {
364         napi_value valuePending;
365         if (napi_ok != napi_get_and_clear_last_exception(env, &valuePending)) {
366             return {};
367         }
368         auto resStk = GetErrorStack(env, valuePending);
369         if (resStk.has_value()) {
370             pendingErrorMsg = "\nWith pending exception:\n" + resStk.value();
371         } else {
372             pendingErrorMsg = "\nFailed to stringify pending exception";
373         }
374     }
375 
376     std::string stacktraceMsg;
377     {
378         napi_value jsDummyStr;
379         if (napi_ok !=
380             napi_create_string_utf8(env, "probe-stacktrace-not-actual-error", NAPI_AUTO_LENGTH, &jsDummyStr)) {
381             return {};
382         }
383         napi_value jsErr;
384         auto rc = napi_create_error(env, nullptr, jsDummyStr, &jsErr);
385         if (napi_ok != rc) {
386             return {};
387         }
388         auto resStk = GetErrorStack(env, jsErr);
389         stacktraceMsg = resStk.has_value() ? resStk.value() : "failed to stringify probe exception";
390     }
391 
392     return stacktraceMsg + pendingErrorMsg;
393 }
394 
Fatal(const char * msg)395 [[noreturn]] void InteropCtx::Fatal(const char *msg)
396 {
397     INTEROP_LOG(ERROR) << "InteropCtx::Fatal: " << msg;
398 
399     auto coro = EtsCoroutine::GetCurrent();
400     auto ctx = InteropCtx::Current(coro);
401 
402     INTEROP_LOG(ERROR) << "======================== ETS stack ============================";
403     auto istk = ctx->interopStk_.GetRecords();
404     auto istkIt = istk.rbegin();
405 
406     auto printIstkFrames = [&istkIt, &istk](void *fp) {
407         while (istkIt != istk.rend() && fp == istkIt->etsFrame) {
408             INTEROP_LOG(ERROR) << "<interop> " << (istkIt->descr != nullptr ? istkIt->descr : "unknown");
409             istkIt++;
410         }
411     };
412 
413     for (auto stack = StackWalker::Create(coro); stack.HasFrame(); stack.NextFrame()) {
414         printIstkFrames(istkIt->etsFrame);
415         Method *method = stack.GetMethod();
416         INTEROP_LOG(ERROR) << method->GetClass()->GetName() << "." << method->GetName().data << " at "
417                            << method->GetLineNumberAndSourceFile(stack.GetBytecodePc());
418     }
419     ASSERT(istkIt == istk.rend() || istkIt->etsFrame == nullptr);
420     printIstkFrames(nullptr);
421 
422     auto env = ctx->jsEnv_;
423     INTEROP_LOG(ERROR) << (env != nullptr ? "<ets-entrypoint>" : "current js env is nullptr!");
424 
425     if (coro->HasPendingException()) {
426         auto exc = EtsObject::FromCoreType(coro->GetException());
427         INTEROP_LOG(ERROR) << "With pending exception: " << exc->GetClass()->GetDescriptor();
428     }
429 
430     if (env != nullptr) {
431         INTEROP_LOG(ERROR) << "======================== JS stack =============================";
432         std::optional<std::string> jsStk = NapiTryDumpStack(env);
433         if (jsStk.has_value()) {
434             INTEROP_LOG(ERROR) << jsStk.value();
435         } else {
436             INTEROP_LOG(ERROR) << "JS stack print failed";
437         }
438     }
439 
440     INTEROP_LOG(ERROR) << "======================== Native stack =========================";
441     PrintStack(Logger::Message(Logger::Level::ERROR, Logger::Component::ETS_INTEROP_JS, false).GetStream());
442     std::abort();
443 }
444 
Init(EtsCoroutine * coro,napi_env env)445 void InteropCtx::Init(EtsCoroutine *coro, napi_env env)
446 {
447     // Initialize InteropCtx in VM ExternalData
448     new (InteropCtx::Current(coro)) InteropCtx(coro, env);
449 }
450 
451 }  // namespace ark::ets::interop::js
452