• 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     PandaEtsVM *vm = coro->GetPandaVM();
123     EtsClassLinker *etsClassLinker = vm->GetClassLinker();
124     refstor_ = vm->GetGlobalObjectStorage();
125     classLinker_ = Runtime::GetCurrent()->GetClassLinker();
126     linkerCtx_ = etsClassLinker->GetEtsClassLinkerExtension()->GetBootContext();
127 
128     auto *jobQueue = Runtime::GetCurrent()->GetInternalAllocator()->New<JsJobQueue>();
129     vm->InitJobQueue(jobQueue);
130 
131     CacheClasses(etsClassLinker);
132 
133     RegisterBuiltinJSRefConvertors(this);
134 
135     {
136         auto method = EtsClass::FromRuntimeClass(jsRuntimeClass_)->GetMethod("createFinalizationRegistry");
137         ASSERT(method != nullptr);
138         auto res = method->GetPandaMethod()->Invoke(coro, nullptr);
139         ASSERT(!coro->HasPendingException());
140         auto queue = EtsObject::FromCoreType(res.GetAs<ObjectHeader *>());
141         jsvalueFregistryRef_ = Refstor()->Add(queue->GetCoreType(), mem::Reference::ObjectType::GLOBAL);
142         ASSERT(jsvalueFregistryRef_ != nullptr);
143 
144         jsvalueFregistryRegister_ = queue->GetClass()
145                                         ->GetMethod("register", "Lstd/core/Object;Lstd/core/Object;Lstd/core/Object;:V")
146                                         ->GetPandaMethod();
147         ASSERT(jsvalueFregistryRegister_ != nullptr);
148     }
149 
150     {
151         EtsClass *promiseInteropClass =
152             EtsClass::FromRuntimeClass(CacheClass(etsClassLinker, "Lstd/interop/js/PromiseInterop;"));
153         ASSERT(promiseInteropClass != nullptr);
154         promiseInteropConnectMethod_ =
155             promiseInteropClass->GetMethod("connectPromise", "Lstd/core/Promise;J:V")->GetPandaMethod();
156         ASSERT(promiseInteropConnectMethod_ != nullptr);
157     }
158 
159     etsProxyRefStorage_ = ets_proxy::SharedReferenceStorage::Create();
160     ASSERT(etsProxyRefStorage_.get() != nullptr);
161 
162     // Set InteropCtx::DestroyLocalScopeForTopFrame to VM for call it it deoptimization and exception handlers
163     vm->SetClearInteropHandleScopesFunction([this](Frame *frame) { this->DestroyLocalScopeForTopFrame(frame); });
164     vm->SetDestroyExternalDataFunction(Destroy);
165 }
166 
CacheClasses(EtsClassLinker * etsClassLinker)167 void InteropCtx::CacheClasses(EtsClassLinker *etsClassLinker)
168 {
169     jsRuntimeClass_ = CacheClass(etsClassLinker, descriptors::JS_RUNTIME);
170     jsValueClass_ = CacheClass(etsClassLinker, descriptors::JS_VALUE);
171     jsErrorClass_ = CacheClass(etsClassLinker, descriptors::JS_ERROR);
172     objectClass_ = CacheClass(etsClassLinker, descriptors::OBJECT);
173     stringClass_ = CacheClass(etsClassLinker, descriptors::STRING);
174     undefinedClass_ = CacheClass(etsClassLinker, descriptors::INTERNAL_UNDEFINED);
175     promiseClass_ = CacheClass(etsClassLinker, descriptors::PROMISE);
176     errorClass_ = CacheClass(etsClassLinker, descriptors::ERROR);
177     exceptionClass_ = CacheClass(etsClassLinker, descriptors::EXCEPTION);
178     typeClass_ = CacheClass(etsClassLinker, descriptors::TYPE);
179 
180     boxIntClass_ = CacheClass(etsClassLinker, descriptors::BOX_INT);
181     boxLongClass_ = CacheClass(etsClassLinker, descriptors::BOX_LONG);
182 
183     arrayClass_ = CacheClass(etsClassLinker, descriptors::ARRAY);
184     arraybufClass_ = CacheClass(etsClassLinker, descriptors::ARRAY_BUFFER);
185 
186     for (auto descr : FUNCTION_INTERFACE_DESCRIPTORS) {
187         functionalInterfaces_.insert(CacheClass(etsClassLinker, descr));
188     }
189 }
190 
CreateETSCoreJSError(EtsCoroutine * coro,JSValue * jsvalue)191 EtsObject *InteropCtx::CreateETSCoreJSError(EtsCoroutine *coro, JSValue *jsvalue)
192 {
193     [[maybe_unused]] HandleScope<ObjectHeader *> scope(coro);
194     VMHandle<ObjectHeader> jsvalueHandle(coro, jsvalue->GetCoreType());
195 
196     Method::Proto proto(Method::Proto::ShortyVector {panda_file::Type(panda_file::Type::TypeId::VOID),
197                                                      panda_file::Type(panda_file::Type::TypeId::REFERENCE)},
198                         Method::Proto::RefTypeVector {utf::Mutf8AsCString(GetJSValueClass()->GetDescriptor())});
199     auto ctorName = utf::CStringAsMutf8(panda_file_items::CTOR.data());
200     auto ctor = GetJSErrorClass()->GetDirectMethod(ctorName, proto);
201     ASSERT(ctor != nullptr);
202 
203     auto excObj = ObjectHeader::Create(coro, GetJSErrorClass());
204     if (UNLIKELY(excObj == nullptr)) {
205         return nullptr;
206     }
207     VMHandle<ObjectHeader> excHandle(coro, excObj);
208 
209     std::array<Value, 2U> args {Value(excHandle.GetPtr()), Value(jsvalueHandle.GetPtr())};
210     ctor->InvokeVoid(coro, args.data());
211     auto res = EtsObject::FromCoreType(excHandle.GetPtr());
212     if (UNLIKELY(coro->HasPendingException())) {
213         return nullptr;
214     }
215     return res;
216 }
217 
ThrowETSError(EtsCoroutine * coro,napi_value val)218 void InteropCtx::ThrowETSError(EtsCoroutine *coro, napi_value val)
219 {
220     auto ctx = Current(coro);
221 
222     if (coro->IsUsePreAllocObj()) {
223         coro->SetUsePreAllocObj(false);
224         coro->SetException(coro->GetVM()->GetOOMErrorObject());
225         return;
226     }
227     ASSERT(!coro->HasPendingException());
228 
229     if (IsNullOrUndefined(ctx->GetJSEnv(), val)) {
230         ctx->ThrowETSError(coro, "interop/js throws undefined/null");
231         return;
232     }
233 
234     // To catch `TypeError` or `UserError extends TypeError`
235     // 1. Frontend puts catch(compat/TypeError) { <instanceof-rethrow? if UserError expected> }
236     //    Where js.UserError will be wrapped into compat/TypeError
237     //    NOTE(vpukhov): compat: add intrinsic to obtain JSValue from compat/ instances
238 
239     auto objRefconv = JSRefConvertResolve(ctx, ctx->GetObjectClass());
240     LocalObjectHandle<EtsObject> etsObj(coro, objRefconv->Unwrap(ctx, val));
241     if (UNLIKELY(etsObj.GetPtr() == nullptr)) {
242         INTEROP_LOG(INFO) << "Something went wrong while unwrapping pending js exception";
243         ASSERT(ctx->SanityETSExceptionPending());
244         return;
245     }
246 
247     auto klass = etsObj->GetClass()->GetRuntimeClass();
248     if (LIKELY(ctx->GetErrorClass()->IsAssignableFrom(klass) || ctx->GetExceptionClass()->IsAssignableFrom(klass))) {
249         coro->SetException(etsObj->GetCoreType());
250         return;
251     }
252 
253     // NOTE(vpukhov): should throw a special error (JSError?) with cause set
254     auto exc = JSConvertJSError::Unwrap(ctx, ctx->GetJSEnv(), val);
255     if (LIKELY(exc.has_value())) {
256         ASSERT(exc != nullptr);
257         coro->SetException(exc.value()->GetCoreType());
258     }  // otherwise exception is already set
259 }
260 
ThrowETSError(EtsCoroutine * coro,const char * msg)261 void InteropCtx::ThrowETSError(EtsCoroutine *coro, const char *msg)
262 {
263     ASSERT(!coro->HasPendingException());
264     ets::ThrowEtsException(coro, panda_file_items::class_descriptors::ERROR, msg);
265 }
266 
ThrowJSError(napi_env env,const std::string & msg)267 void InteropCtx::ThrowJSError(napi_env env, const std::string &msg)
268 {
269     INTEROP_LOG(INFO) << "ThrowJSError: " << msg;
270     ASSERT(!NapiIsExceptionPending(env));
271     NAPI_CHECK_FATAL(napi_throw_error(env, nullptr, msg.c_str()));
272 }
273 
ThrowJSTypeError(napi_env env,const std::string & msg)274 void InteropCtx::ThrowJSTypeError(napi_env env, const std::string &msg)
275 {
276     INTEROP_LOG(INFO) << "ThrowJSTypeError: " << msg;
277     ASSERT(!NapiIsExceptionPending(env));
278     NAPI_CHECK_FATAL(napi_throw_type_error(env, nullptr, msg.c_str()));
279 }
280 
ThrowJSValue(napi_env env,napi_value val)281 void InteropCtx::ThrowJSValue(napi_env env, napi_value val)
282 {
283     INTEROP_LOG(INFO) << "ThrowJSValue";
284     ASSERT(!NapiIsExceptionPending(env));
285     NAPI_CHECK_FATAL(napi_throw(env, val));
286 }
287 
ForwardEtsException(EtsCoroutine * coro)288 void InteropCtx::ForwardEtsException(EtsCoroutine *coro)
289 {
290     auto env = GetJSEnv();
291     ASSERT(coro->HasPendingException());
292     LocalObjectHandle<ObjectHeader> exc(coro, coro->GetException());
293     coro->ClearException();
294 
295     auto klass = exc->ClassAddr<Class>();
296     ASSERT(GetErrorClass()->IsAssignableFrom(klass) || GetExceptionClass()->IsAssignableFrom(klass));
297     JSRefConvert *refconv = JSRefConvertResolve<true>(this, klass);
298     if (UNLIKELY(refconv == nullptr)) {
299         INTEROP_LOG(INFO) << "Exception thrown while forwarding ets exception: " << klass->GetDescriptor();
300         return;
301     }
302     napi_value res = refconv->Wrap(this, EtsObject::FromCoreType(exc.GetPtr()));
303     if (UNLIKELY(res == nullptr)) {
304         return;
305     }
306     ThrowJSValue(env, res);
307 }
308 
ForwardJSException(EtsCoroutine * coro)309 void InteropCtx::ForwardJSException(EtsCoroutine *coro)
310 {
311     auto env = GetJSEnv();
312     napi_value excval;
313     ASSERT(NapiIsExceptionPending(env));
314     NAPI_CHECK_FATAL(napi_get_and_clear_last_exception(env, &excval));
315     ThrowETSError(coro, excval);
316 }
317 
JSConvertTypeCheckFailed(const char * typeName)318 void JSConvertTypeCheckFailed(const char *typeName)
319 {
320     auto ctx = InteropCtx::Current();
321     auto env = ctx->GetJSEnv();
322     InteropCtx::ThrowJSTypeError(env, typeName + std::string(" expected"));
323 }
324 
GetErrorStack(napi_env env,napi_value jsErr)325 static std::optional<std::string> GetErrorStack(napi_env env, napi_value jsErr)
326 {
327     bool isError;
328     if (napi_ok != napi_is_error(env, jsErr, &isError)) {
329         return {};
330     }
331     if (!isError) {
332         return "not an Error instance";
333     }
334     napi_value jsStk;
335     if (napi_ok != napi_get_named_property(env, jsErr, "stack", &jsStk)) {
336         return {};
337     }
338     size_t length;
339     if (napi_ok != napi_get_value_string_utf8(env, jsStk, nullptr, 0, &length)) {
340         return {};
341     }
342     std::string value;
343     value.resize(length);
344     // +1 for NULL terminated string!!!
345     if (napi_ok != napi_get_value_string_utf8(env, jsStk, value.data(), value.size() + 1, &length)) {
346         return {};
347     }
348     return value;
349 }
350 
NapiTryDumpStack(napi_env env)351 static std::optional<std::string> NapiTryDumpStack(napi_env env)
352 {
353     bool isPending;
354     if (napi_ok != napi_is_exception_pending(env, &isPending)) {
355         return {};
356     }
357 
358     std::string pendingErrorMsg;
359     if (isPending) {
360         napi_value valuePending;
361         if (napi_ok != napi_get_and_clear_last_exception(env, &valuePending)) {
362             return {};
363         }
364         auto resStk = GetErrorStack(env, valuePending);
365         if (resStk.has_value()) {
366             pendingErrorMsg = "\nWith pending exception:\n" + resStk.value();
367         } else {
368             pendingErrorMsg = "\nFailed to stringify pending exception";
369         }
370     }
371 
372     std::string stacktraceMsg;
373     {
374         napi_value jsDummyStr;
375         if (napi_ok !=
376             napi_create_string_utf8(env, "probe-stacktrace-not-actual-error", NAPI_AUTO_LENGTH, &jsDummyStr)) {
377             return {};
378         }
379         napi_value jsErr;
380         auto rc = napi_create_error(env, nullptr, jsDummyStr, &jsErr);
381         if (napi_ok != rc) {
382             return {};
383         }
384         auto resStk = GetErrorStack(env, jsErr);
385         stacktraceMsg = resStk.has_value() ? resStk.value() : "failed to stringify probe exception";
386     }
387 
388     return stacktraceMsg + pendingErrorMsg;
389 }
390 
Fatal(const char * msg)391 [[noreturn]] void InteropCtx::Fatal(const char *msg)
392 {
393     INTEROP_LOG(ERROR) << "InteropCtx::Fatal: " << msg;
394 
395     auto coro = EtsCoroutine::GetCurrent();
396     auto ctx = InteropCtx::Current(coro);
397 
398     INTEROP_LOG(ERROR) << "======================== ETS stack ============================";
399     auto istk = ctx->interopStk_.GetRecords();
400     auto istkIt = istk.rbegin();
401 
402     auto printIstkFrames = [&istkIt, &istk](void *fp) {
403         while (istkIt != istk.rend() && fp == istkIt->etsFrame) {
404             INTEROP_LOG(ERROR) << "<interop> " << (istkIt->descr != nullptr ? istkIt->descr : "unknown");
405             istkIt++;
406         }
407     };
408 
409     for (auto stack = StackWalker::Create(coro); stack.HasFrame(); stack.NextFrame()) {
410         printIstkFrames(istkIt->etsFrame);
411         Method *method = stack.GetMethod();
412         INTEROP_LOG(ERROR) << method->GetClass()->GetName() << "." << method->GetName().data << " at "
413                            << method->GetLineNumberAndSourceFile(stack.GetBytecodePc());
414     }
415     ASSERT(istkIt == istk.rend() || istkIt->etsFrame == nullptr);
416     printIstkFrames(nullptr);
417 
418     auto env = ctx->jsEnv_;
419     INTEROP_LOG(ERROR) << (env != nullptr ? "<ets-entrypoint>" : "current js env is nullptr!");
420 
421     if (coro->HasPendingException()) {
422         auto exc = EtsObject::FromCoreType(coro->GetException());
423         INTEROP_LOG(ERROR) << "With pending exception: " << exc->GetClass()->GetDescriptor();
424     }
425 
426     if (env != nullptr) {
427         INTEROP_LOG(ERROR) << "======================== JS stack =============================";
428         std::optional<std::string> jsStk = NapiTryDumpStack(env);
429         if (jsStk.has_value()) {
430             INTEROP_LOG(ERROR) << jsStk.value();
431         } else {
432             INTEROP_LOG(ERROR) << "JS stack print failed";
433         }
434     }
435 
436     INTEROP_LOG(ERROR) << "======================== Native stack =========================";
437     PrintStack(Logger::Message(Logger::Level::ERROR, Logger::Component::ETS_INTEROP_JS, false).GetStream());
438     std::abort();
439 }
440 
Init(EtsCoroutine * coro,napi_env env)441 void InteropCtx::Init(EtsCoroutine *coro, napi_env env)
442 {
443     // Initialize InteropCtx in VM ExternalData
444     new (InteropCtx::Current(coro)) InteropCtx(coro, env);
445 }
446 
447 }  // namespace ark::ets::interop::js
448