• 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/napi_env_scope.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 panda::ets::interop::js {
29 
InteropCtx(EtsCoroutine * coro,napi_env env)30 InteropCtx::InteropCtx(EtsCoroutine *coro, napi_env env)
31 {
32     EtsJSNapiEnvScope envscope(this, env);
33 
34     PandaEtsVM *vm = coro->GetPandaVM();
35     EtsClassLinker *etsClassLinker = vm->GetClassLinker();
36     refstor_ = vm->GetGlobalObjectStorage();
37     linkerCtx_ = etsClassLinker->GetEtsClassLinkerExtension()->GetBootContext();
38 
39     JSRuntimeIntrinsicsSetIntrinsicsAPI(GetIntrinsicsAPI());
40     auto *jobQueue = Runtime::GetCurrent()->GetInternalAllocator()->New<JsJobQueue>();
41     vm->InitJobQueue(jobQueue);
42 
43     auto cacheClass = [&etsClassLinker](std::string_view descriptor) {
44         auto klass = etsClassLinker->GetClass(descriptor.data())->GetRuntimeClass();
45         ASSERT(klass != nullptr);
46         return klass;
47     };
48 
49     namespace descriptors = panda_file_items::class_descriptors;
50 
51     jsRuntimeClass_ = cacheClass(descriptors::JS_RUNTIME);
52     jsValueClass_ = cacheClass(descriptors::JS_VALUE);
53     jsErrorClass_ = cacheClass(descriptors::JS_ERROR);
54     objectClass_ = cacheClass(descriptors::OBJECT);
55     stringClass_ = cacheClass(descriptors::STRING);
56     voidClass_ = cacheClass(descriptors::VOID);
57     undefinedClass_ = cacheClass(descriptors::INTERNAL_UNDEFINED);
58     promiseClass_ = cacheClass(descriptors::PROMISE);
59     errorClass_ = cacheClass(descriptors::ERROR);
60     exceptionClass_ = cacheClass(descriptors::EXCEPTION);
61     typeClass_ = cacheClass(descriptors::TYPE);
62 
63     boxIntClass_ = cacheClass(descriptors::BOX_INT);
64     boxLongClass_ = cacheClass(descriptors::BOX_LONG);
65 
66     arrayClass_ = cacheClass(descriptors::ARRAY);
67     arraybufClass_ = cacheClass(descriptors::ARRAY_BUFFER);
68 
69     RegisterBuiltinJSRefConvertors(this);
70 
71     {
72         auto method = EtsClass::FromRuntimeClass(jsRuntimeClass_)->GetMethod("createFinalizationQueue");
73         ASSERT(method != nullptr);
74         auto res = method->GetPandaMethod()->Invoke(coro, nullptr);
75         ASSERT(!coro->HasPendingException());
76         auto queue = EtsObject::FromCoreType(res.GetAs<ObjectHeader *>());
77         jsvalueFqueueRef_ = Refstor()->Add(queue->GetCoreType(), mem::Reference::ObjectType::GLOBAL);
78         ASSERT(jsvalueFqueueRef_ != nullptr);
79 
80         jsvalueFqueueRegister_ =
81             queue->GetClass()
82                 ->GetMethod("register", "Lstd/core/Object;Lstd/core/Object;Lstd/core/Object;:Lstd/core/void;")
83                 ->GetPandaMethod();
84         ASSERT(jsvalueFqueueRegister_ != nullptr);
85     }
86 
87     etsProxyRefStorage_ = ets_proxy::SharedReferenceStorage::Create();
88     ASSERT(etsProxyRefStorage_.get() != nullptr);
89 
90     // Set InteropCtx::DestroyLocalScopeForTopFrame to VM for call it it deoptimization and exception handlers
91     coro->GetPandaVM()->SetClearInteropHandleScopesFunction(
92         [this](Frame *frame) { this->DestroyLocalScopeForTopFrame(frame); });
93 }
94 
CreateETSCoreJSError(EtsCoroutine * coro,JSValue * jsvalue)95 EtsObject *InteropCtx::CreateETSCoreJSError(EtsCoroutine *coro, JSValue *jsvalue)
96 {
97     [[maybe_unused]] HandleScope<ObjectHeader *> scope(coro);
98     VMHandle<ObjectHeader> jsvalueHandle(coro, jsvalue->GetCoreType());
99 
100     Method::Proto proto(Method::Proto::ShortyVector {panda_file::Type(panda_file::Type::TypeId::VOID),
101                                                      panda_file::Type(panda_file::Type::TypeId::REFERENCE)},
102                         Method::Proto::RefTypeVector {utf::Mutf8AsCString(GetJSValueClass()->GetDescriptor())});
103     auto ctorName = utf::CStringAsMutf8(panda_file_items::CTOR.data());
104     auto ctor = GetJSErrorClass()->GetDirectMethod(ctorName, proto);
105     ASSERT(ctor != nullptr);
106 
107     auto excObj = ObjectHeader::Create(coro, GetJSErrorClass());
108     if (UNLIKELY(excObj == nullptr)) {
109         return nullptr;
110     }
111     VMHandle<ObjectHeader> excHandle(coro, excObj);
112 
113     std::array<Value, 2U> args {Value(excHandle.GetPtr()), Value(jsvalueHandle.GetPtr())};
114     ctor->InvokeVoid(coro, args.data());
115     auto res = EtsObject::FromCoreType(excHandle.GetPtr());
116     if (UNLIKELY(coro->HasPendingException())) {
117         return nullptr;
118     }
119     return res;
120 }
121 
ThrowETSError(EtsCoroutine * coro,napi_value val)122 void InteropCtx::ThrowETSError(EtsCoroutine *coro, napi_value val)
123 {
124     auto ctx = Current(coro);
125 
126     if (coro->IsUsePreAllocObj()) {
127         coro->SetUsePreAllocObj(false);
128         coro->SetException(coro->GetVM()->GetOOMErrorObject());
129         return;
130     }
131     ASSERT(!coro->HasPendingException());
132 
133     if (IsNullOrUndefined(ctx->GetJSEnv(), val)) {
134         ctx->ThrowETSError(coro, "interop/js throws undefined/null");
135         return;
136     }
137 
138     // To catch `TypeError` or `UserError extends TypeError`
139     // 1. Frontend puts catch(compat/TypeError) { <instanceof-rethrow? if UserError expected> }
140     //    Where js.UserError will be wrapped into compat/TypeError
141     //    NOTE(vpukhov): compat: add intrinsic to obtain JSValue from compat/ instances
142 
143     auto objRefconv = JSRefConvertResolve(ctx, ctx->GetObjectClass());
144     LocalObjectHandle<EtsObject> etsObj(coro, objRefconv->Unwrap(ctx, val));
145     if (UNLIKELY(etsObj.GetPtr() == nullptr)) {
146         INTEROP_LOG(INFO) << "Something went wrong while unwrapping pending js exception";
147         ASSERT(ctx->SanityETSExceptionPending());
148         return;
149     }
150 
151     auto klass = etsObj->GetClass()->GetRuntimeClass();
152     if (LIKELY(ctx->GetErrorClass()->IsAssignableFrom(klass) || ctx->GetExceptionClass()->IsAssignableFrom(klass))) {
153         coro->SetException(etsObj->GetCoreType());
154         return;
155     }
156 
157     // NOTE(vpukhov): should throw a special error (JSError?) with cause set
158     auto exc = JSConvertJSError::Unwrap(ctx, ctx->GetJSEnv(), val);
159     if (LIKELY(exc.has_value())) {
160         ASSERT(exc != nullptr);
161         coro->SetException(exc.value()->GetCoreType());
162     }  // otherwise exception is already set
163 }
164 
ThrowETSError(EtsCoroutine * coro,const char * msg)165 void InteropCtx::ThrowETSError(EtsCoroutine *coro, const char *msg)
166 {
167     ASSERT(!coro->HasPendingException());
168     ets::ThrowEtsException(coro, panda_file_items::class_descriptors::ERROR, msg);
169 }
170 
ThrowJSError(napi_env env,const std::string & msg)171 void InteropCtx::ThrowJSError(napi_env env, const std::string &msg)
172 {
173     INTEROP_LOG(INFO) << "ThrowJSError: " << msg;
174     ASSERT(!NapiIsExceptionPending(env));
175     NAPI_CHECK_FATAL(napi_throw_error(env, nullptr, msg.c_str()));
176 }
177 
ThrowJSTypeError(napi_env env,const std::string & msg)178 void InteropCtx::ThrowJSTypeError(napi_env env, const std::string &msg)
179 {
180     INTEROP_LOG(INFO) << "ThrowJSTypeError: " << msg;
181     ASSERT(!NapiIsExceptionPending(env));
182     NAPI_CHECK_FATAL(napi_throw_type_error(env, nullptr, msg.c_str()));
183 }
184 
ThrowJSValue(napi_env env,napi_value val)185 void InteropCtx::ThrowJSValue(napi_env env, napi_value val)
186 {
187     INTEROP_LOG(INFO) << "ThrowJSValue";
188     ASSERT(!NapiIsExceptionPending(env));
189     NAPI_CHECK_FATAL(napi_throw(env, val));
190 }
191 
ForwardEtsException(EtsCoroutine * coro)192 void InteropCtx::ForwardEtsException(EtsCoroutine *coro)
193 {
194     auto env = GetJSEnv();
195     ASSERT(coro->HasPendingException());
196     LocalObjectHandle<ObjectHeader> exc(coro, coro->GetException());
197     coro->ClearException();
198 
199     auto klass = exc->ClassAddr<Class>();
200     ASSERT(GetErrorClass()->IsAssignableFrom(klass) || GetExceptionClass()->IsAssignableFrom(klass));
201     JSRefConvert *refconv = JSRefConvertResolve<true>(this, klass);
202     if (UNLIKELY(refconv == nullptr)) {
203         INTEROP_LOG(INFO) << "Exception thrown while forwarding ets exception: " << klass->GetDescriptor();
204         return;
205     }
206     napi_value res = refconv->Wrap(this, EtsObject::FromCoreType(exc.GetPtr()));
207     if (UNLIKELY(res == nullptr)) {
208         return;
209     }
210     ThrowJSValue(env, res);
211 }
212 
ForwardJSException(EtsCoroutine * coro)213 void InteropCtx::ForwardJSException(EtsCoroutine *coro)
214 {
215     auto env = GetJSEnv();
216     napi_value excval;
217     ASSERT(NapiIsExceptionPending(env));
218     NAPI_CHECK_FATAL(napi_get_and_clear_last_exception(env, &excval));
219     ThrowETSError(coro, excval);
220 }
221 
JSConvertTypeCheckFailed(const char * typeName)222 void JSConvertTypeCheckFailed(const char *typeName)
223 {
224     auto ctx = InteropCtx::Current();
225     auto env = ctx->GetJSEnv();
226     InteropCtx::ThrowJSTypeError(env, typeName + std::string(" expected"));
227 }
228 
GetErrorStack(napi_env env,napi_value jsErr)229 static std::optional<std::string> GetErrorStack(napi_env env, napi_value jsErr)
230 {
231     bool isError;
232     if (napi_ok != napi_is_error(env, jsErr, &isError)) {
233         return {};
234     }
235     if (!isError) {
236         return "not an Error instance";
237     }
238     napi_value jsStk;
239     if (napi_ok != napi_get_named_property(env, jsErr, "stack", &jsStk)) {
240         return {};
241     }
242     size_t length;
243     if (napi_ok != napi_get_value_string_utf8(env, jsStk, nullptr, 0, &length)) {
244         return {};
245     }
246     std::string value;
247     value.resize(length);
248     // +1 for NULL terminated string!!!
249     if (napi_ok != napi_get_value_string_utf8(env, jsStk, value.data(), value.size() + 1, &length)) {
250         return {};
251     }
252     return value;
253 }
254 
NapiTryDumpStack(napi_env env)255 static std::optional<std::string> NapiTryDumpStack(napi_env env)
256 {
257     bool isPending;
258     if (napi_ok != napi_is_exception_pending(env, &isPending)) {
259         return {};
260     }
261 
262     std::string pendingErrorMsg;
263     if (isPending) {
264         napi_value valuePending;
265         if (napi_ok != napi_get_and_clear_last_exception(env, &valuePending)) {
266             return {};
267         }
268         auto resStk = GetErrorStack(env, valuePending);
269         if (resStk.has_value()) {
270             pendingErrorMsg = "\nWith pending exception:\n" + resStk.value();
271         } else {
272             pendingErrorMsg = "\nFailed to stringify pending exception";
273         }
274     }
275 
276     std::string stacktraceMsg;
277     {
278         napi_value jsDummyStr;
279         if (napi_ok !=
280             napi_create_string_utf8(env, "probe-stacktrace-not-actual-error", NAPI_AUTO_LENGTH, &jsDummyStr)) {
281             return {};
282         }
283         napi_value jsErr;
284         auto rc = napi_create_error(env, nullptr, jsDummyStr, &jsErr);
285         if (napi_ok != rc) {
286             return {};
287         }
288         auto resStk = GetErrorStack(env, jsErr);
289         stacktraceMsg = resStk.has_value() ? resStk.value() : "failed to stringify probe exception";
290     }
291 
292     return stacktraceMsg + pendingErrorMsg;
293 }
294 
Fatal(const char * msg)295 [[noreturn]] void InteropCtx::Fatal(const char *msg)
296 {
297     INTEROP_LOG(ERROR) << "InteropCtx::Fatal: " << msg;
298 
299     auto coro = EtsCoroutine::GetCurrent();
300     auto ctx = InteropCtx::Current(coro);
301 
302     INTEROP_LOG(ERROR) << "======================== ETS stack ============================";
303     auto &istk = ctx->GetInteropFrames();
304     auto istkIt = istk.rbegin();
305 
306     for (auto stack = StackWalker::Create(coro); stack.HasFrame(); stack.NextFrame()) {
307         while (istkIt != istk.rend() && stack.GetFp() == istkIt->etsFrame) {
308             INTEROP_LOG(ERROR) << (istkIt->toJs ? "<ets-to-js>" : "<js-to-ets>");
309             istkIt++;
310         }
311 
312         Method *method = stack.GetMethod();
313         INTEROP_LOG(ERROR) << method->GetClass()->GetName() << "." << method->GetName().data << " at "
314                            << method->GetLineNumberAndSourceFile(stack.GetBytecodePc());
315     }
316     ASSERT(istkIt == istk.rend() || istkIt->etsFrame == nullptr);
317 
318     auto env = ctx->jsEnv_;
319     INTEROP_LOG(ERROR) << (env != nullptr ? "<ets-entrypoint>" : "current js env is nullptr!");
320 
321     if (coro->HasPendingException()) {
322         auto exc = EtsObject::FromCoreType(coro->GetException());
323         INTEROP_LOG(ERROR) << "With pending exception: " << exc->GetClass()->GetDescriptor();
324     }
325 
326     if (env != nullptr) {
327         INTEROP_LOG(ERROR) << "======================== JS stack =============================";
328         std::optional<std::string> jsStk = NapiTryDumpStack(env);
329         if (jsStk.has_value()) {
330             INTEROP_LOG(ERROR) << jsStk.value();
331         } else {
332             INTEROP_LOG(ERROR) << "JS stack print failed";
333         }
334     }
335 
336     INTEROP_LOG(ERROR) << "======================== Native stack =========================";
337     PrintStack(Logger::Message(Logger::Level::ERROR, Logger::Component::ETS_INTEROP_JS, false).GetStream());
338     std::abort();
339 }
340 
341 }  // namespace panda::ets::interop::js
342