From 7e3e2d572302aa793af84463f289f419fdc757c2 Mon Sep 17 00:00:00 2001 From: Vsevolod Pukhov Date: Tue, 27 Jun 2023 20:17:58 +0300 Subject: [PATCH] interop_js: Lazy class init in convertors, improve Error forwarding * Add lazy class loading and initialization for reftype convertors * Proper exceptions forwarding in interop calls * Replace interop Exceptions with Errors * Add hybrid vm stacktrace for InteropCtx::Fatal * Add handling for ets->js convertors failures and possible OOMs Signed-off-by: Vsevolod Pukhov --- plugins/ets/runtime/ets_panda_file_items.h | 11 +- .../ets_proxy/ets_class_wrapper.cpp | 30 ++- .../interop_js/ets_proxy/ets_class_wrapper.h | 4 + .../ets_proxy/ets_method_wrapper.cpp | 10 +- .../runtime/interop_js/ets_proxy/ets_proxy.h | 1 + .../interop_js/ets_proxy/wrappers_cache.h | 1 - .../ets/runtime/interop_js/ets_vm_plugin.cpp | 6 + .../runtime/interop_js/interop_context.cpp | 157 ++++++++--- .../ets/runtime/interop_js/interop_context.h | 61 ++++- .../interop_js/intrinsics_api_impl.cpp | 6 +- plugins/ets/runtime/interop_js/js_convert.h | 35 ++- .../ets/runtime/interop_js/js_refconvert.cpp | 246 +++--------------- .../ets/runtime/interop_js/js_refconvert.h | 35 ++- .../runtime/interop_js/js_refconvert_array.h | 228 ++++++++++++++++ plugins/ets/runtime/interop_js/js_value.cpp | 28 +- plugins/ets/runtime/interop_js/js_value.h | 23 +- .../ets/runtime/interop_js/js_value_call.cpp | 188 +++++++------ .../ets/runtime/interop_js/js_value_call.h | 13 +- .../ets/runtime/interop_js/ts2ets_common.h | 29 ++- .../ets/runtime/interop_js/ts2ets_copy.cpp | 6 +- .../ets/runtime/interop_js/ts2ets_tstype.cpp | 30 +-- plugins/ets/stdlib/std/core/Error.ets | 9 + .../js/{JSException.ets => JSError.ets} | 2 +- .../test_intrins/frontend_test_intrins.ets | 108 +++++--- .../tests/test_intrins/test_intrins.cpp | 7 + .../tests/test_intrins/test_intrins.ets | 120 ++++++--- .../tests/test_intrins/test_intrins.js | 7 +- runtime/mem/local_object_handle.h | 7 + runtime/mem/vm_handle-inl.h | 40 +++ runtime/mem/vm_handle.h | 10 +- 30 files changed, 964 insertions(+), 494 deletions(-) create mode 100644 plugins/ets/runtime/interop_js/js_refconvert_array.h rename plugins/ets/stdlib/std/interop/js/{JSException.ets => JSError.ets} (95%) create mode 100644 runtime/mem/vm_handle-inl.h diff --git a/plugins/ets/runtime/ets_panda_file_items.h b/plugins/ets/runtime/ets_panda_file_items.h index 8e476737b..aa9bf0d6d 100644 --- a/plugins/ets/runtime/ets_panda_file_items.h +++ b/plugins/ets/runtime/ets_panda_file_items.h @@ -25,9 +25,13 @@ namespace class_descriptors { // Base classes static constexpr std::string_view ASYNC = "Lets/coroutine/Async;"; +static constexpr std::string_view ERROR = "Lstd/core/Error;"; +static constexpr std::string_view EXCEPTION = "Lstd/core/Exception;"; static constexpr std::string_view CLASS = "Lstd/core/Class;"; static constexpr std::string_view OBJECT = "Lstd/core/Object;"; +static constexpr std::string_view PROMISE = "Lstd/core/Promise;"; static constexpr std::string_view STRING = "Lstd/core/String;"; +static constexpr std::string_view TYPE = "Lstd/core/Type;"; // Arrays of base classes static constexpr std::string_view CLASS_ARRAY = "[Lstd/core/Class;"; @@ -57,7 +61,6 @@ static constexpr std::string_view UNSUPPORTED_OPERATION_EXCEPTION = "Lstd/c // Error classes static constexpr std::string_view CLASS_CIRCULARITY_ERROR = "Lstd/core/ClassCircularityError;"; -static constexpr std::string_view ERROR = "Lstd/core/Error;"; static constexpr std::string_view EXCEPTION_IN_INITIALIZER_ERROR = "Lstd/core/ExceptionInInitializerError;"; static constexpr std::string_view INCOMPATIBLE_CLASS_CHANGE_ERROR = "Lstd/core/IncompatibleClassChangeError;"; static constexpr std::string_view INSTANTIATION_ERROR = "Lstd/core/InstantiationError;"; @@ -67,6 +70,12 @@ static constexpr std::string_view NO_SUCH_METHOD_ERROR = "Lstd/c static constexpr std::string_view OUT_OF_MEMORY_ERROR = "Lstd/core/OutOfMemoryError;"; static constexpr std::string_view RANGE_ERROR = "Lstd/core/RangeError;"; static constexpr std::string_view VERIFY_ERROR = "Lstd/core/VerifyError;"; + +// interop/js +static constexpr std::string_view JS_RUNTIME = "Lstd/interop/js/JSRuntime;"; +static constexpr std::string_view JS_VALUE = "Lstd/interop/js/JSValue;"; +static constexpr std::string_view JS_ERROR = "Lstd/interop/js/JSError;"; + } // namespace class_descriptors static constexpr std::string_view CCTOR = ""; diff --git a/plugins/ets/runtime/interop_js/ets_proxy/ets_class_wrapper.cpp b/plugins/ets/runtime/interop_js/ets_proxy/ets_class_wrapper.cpp index 34ab9d16f..8b59455b3 100644 --- a/plugins/ets/runtime/interop_js/ets_proxy/ets_class_wrapper.cpp +++ b/plugins/ets/runtime/interop_js/ets_proxy/ets_class_wrapper.cpp @@ -25,19 +25,19 @@ namespace panda::ets::interop::js::ets_proxy { -class JSRefConvertObj : public JSRefConvert { +class JSRefConvertEtsProxy : public JSRefConvert { public: - explicit JSRefConvertObj(EtsClassWrapper *ets_class_wrapper) + explicit JSRefConvertEtsProxy(EtsClassWrapper *ets_class_wrapper) : JSRefConvert(this), ets_class_wrapper_(ets_class_wrapper) { } - napi_value WrapImpl(InteropCtx *ctx, EtsObject *ets_object) const + napi_value WrapImpl(InteropCtx *ctx, EtsObject *ets_object) { return JSConvertOBJECT::Wrap(ctx, ctx->GetJSEnv(), ets_class_wrapper_, ets_object); } - EtsObject *UnwrapImpl(InteropCtx *ctx, napi_value js_value) const + EtsObject *UnwrapImpl(InteropCtx *ctx, napi_value js_value) { napi_env env = ctx->GetJSEnv(); std::optional ets_object = JSConvertOBJECT::UnWrap(ctx, env, ets_class_wrapper_, js_value); @@ -51,6 +51,16 @@ private: EtsClassWrapper *ets_class_wrapper_ {}; }; +/*static*/ +std::unique_ptr EtsClassWrapper::CreateRefConv(napi_env env, Class *klass) +{ + EtsClassWrapper *proxy_wrapper = EtsClassWrapper::Get(env, EtsClass::FromRuntimeClass(klass)); + if (UNLIKELY(proxy_wrapper == nullptr)) { + return nullptr; + } + return std::make_unique(proxy_wrapper); +} + /*static*/ EtsClassWrapper *EtsClassWrapper::Get(napi_env env, EtsClass *ets_class) { @@ -63,16 +73,12 @@ EtsClassWrapper *EtsClassWrapper::Get(napi_env env, EtsClass *ets_class) return ets_class_wrapper; } + // TODO(vpukhov): trigger Create exactly when we need it! std::unique_ptr ets_class_wrapper_u = EtsClassWrapper::Create(env, ets_class); if (UNLIKELY(ets_class_wrapper_u.get() == nullptr)) { return nullptr; } - ets_class_wrapper = cache->Insert(ets_class, std::move(ets_class_wrapper_u)); - - InteropCtx *ctx = InteropCtx::Current(); - ctx->GetRefConvertCache()->Insert(ets_class->GetRuntimeClass(), - std::make_unique(ets_class_wrapper)); - return ets_class_wrapper; + return cache->Insert(ets_class, std::move(ets_class_wrapper_u)); } /*static*/ @@ -119,7 +125,7 @@ std::unique_ptr EtsClassWrapper::Create(napi_env env, EtsClass for (auto &method : methods) { auto name = method.GetName().data; auto it = names.insert(name); - NAPI_FATAL_IF(!it.second); // no overloading + INTEROP_FATAL_IF(!it.second); // no overloading in subset if (UNLIKELY(method.IsInstanceConstructor())) { _this->ets_ctor_wrapper_ = LazyEtsMethodWrapperLink(&method); continue; @@ -219,7 +225,7 @@ UniqueEtsObjectReference EtsClassWrapper::CreateEtsObject(napi_env env, napi_cal Method *method = ets_ctor_wrapper->GetEtsMethod()->GetPandaMethod(); ASSERT(method->IsStatic() == false); ASSERT(method->IsInstanceConstructor() == true); - EtsCallImpl(coro, ctx, method, {js_args.data(), js_args.size()}, ets_object.GetPtr()); + EtsCallImplInstance(coro, ctx, method, {js_args.data(), js_args.size()}, ets_object.GetPtr()); return nullptr; } diff --git a/plugins/ets/runtime/interop_js/ets_proxy/ets_class_wrapper.h b/plugins/ets/runtime/interop_js/ets_proxy/ets_class_wrapper.h index cb9a28053..27e7f51c8 100644 --- a/plugins/ets/runtime/interop_js/ets_proxy/ets_class_wrapper.h +++ b/plugins/ets/runtime/interop_js/ets_proxy/ets_class_wrapper.h @@ -21,6 +21,7 @@ #include "plugins/ets/runtime/interop_js/ets_proxy/ets_method_wrapper.h" #include "plugins/ets/runtime/interop_js/ets_proxy/ets_object_reference.h" #include "plugins/ets/runtime/interop_js/ets_proxy/lazy_ets_class_wrapper_link.h" +#include "plugins/ets/runtime/interop_js/js_refconvert.h" #include "plugins/ets/runtime/interop_js/ts2ets_common.h" #include @@ -34,6 +35,7 @@ namespace panda::ets::interop::js::ets_proxy { using EtsClassWrappersCache = WrappersCache; +// TODO(vpukhov): Split this class into JSRefConvert-derived and GetEtsClass-related parts class EtsClassWrapper { public: // clang-format off @@ -48,6 +50,8 @@ public: static EtsClassWrapper *Get(napi_env env, EtsClass *ets_class); + static std::unique_ptr CreateRefConv(napi_env env, Class *klass); + static inline EtsClassWrapper *ResolveLazyLink(napi_env env, /*in/out*/ LazyEtsClassWrapperLink &lazy_link) { if (LIKELY(lazy_link.IsResolved())) { diff --git a/plugins/ets/runtime/interop_js/ets_proxy/ets_method_wrapper.cpp b/plugins/ets/runtime/interop_js/ets_proxy/ets_method_wrapper.cpp index 03fb668a5..8d664fc72 100644 --- a/plugins/ets/runtime/interop_js/ets_proxy/ets_method_wrapper.cpp +++ b/plugins/ets/runtime/interop_js/ets_proxy/ets_method_wrapper.cpp @@ -22,6 +22,8 @@ #include "plugins/ets/runtime/interop_js/js_value_call.h" #include "plugins/ets/runtime/interop_js/napi_env_scope.h" +#include "runtime/mem/vm_handle-inl.h" + namespace panda::ets::interop::js::ets_proxy { /*static*/ @@ -100,20 +102,20 @@ napi_value EtsMethodWrapper::EtsMethodCallHandler(napi_env env, napi_callback_in Method *method = _this->ets_method_->GetPandaMethod(); EtsClass *ets_class = _this->ets_method_->GetClass(); + + ScopedManagedCodeThread managed_scope(coro); if constexpr (IS_STATIC) { - ScopedManagedCodeThread managed_scope(coro); if (UNLIKELY(!coro->GetPandaVM()->GetClassLinker()->InitializeClass(coro, ets_class))) { ctx->ForwardEtsException(coro); return nullptr; } - return EtsCallImpl(coro, ctx, method, {js_args.data(), js_args.size()}, nullptr); + return EtsCallImplStatic(coro, ctx, method, {js_args.data(), js_args.size()}); } EtsClassWrapper *ets_class_wrapper = EtsClassWrapper::Get(env, ets_class); - ScopedManagedCodeThread managed_scope(coro); std::optional this_ets_object = JSConvertOBJECT::UnWrap(ctx, env, ets_class_wrapper, js_this); ASSERT(this_ets_object.has_value()); - return EtsCallImpl(coro, ctx, method, {js_args.data(), js_args.size()}, this_ets_object.value()); + return EtsCallImplInstance(coro, ctx, method, {js_args.data(), js_args.size()}, this_ets_object.value()); } // Explicit instantiation diff --git a/plugins/ets/runtime/interop_js/ets_proxy/ets_proxy.h b/plugins/ets/runtime/interop_js/ets_proxy/ets_proxy.h index e119b8b1c..7bc58f81a 100644 --- a/plugins/ets/runtime/interop_js/ets_proxy/ets_proxy.h +++ b/plugins/ets/runtime/interop_js/ets_proxy/ets_proxy.h @@ -21,6 +21,7 @@ namespace panda::ets::interop::js::ets_proxy { +// TODO(vpukhov): Make these functions a general absraction for JSRefConvert napi_value GetETSFunction(napi_env env, std::string_view class_descriptor, std::string_view method_name); napi_value GetETSClass(napi_env env, std::string_view class_descriptor); diff --git a/plugins/ets/runtime/interop_js/ets_proxy/wrappers_cache.h b/plugins/ets/runtime/interop_js/ets_proxy/wrappers_cache.h index 477700661..c98aeeeab 100644 --- a/plugins/ets/runtime/interop_js/ets_proxy/wrappers_cache.h +++ b/plugins/ets/runtime/interop_js/ets_proxy/wrappers_cache.h @@ -42,7 +42,6 @@ public: Wrapper *Insert(Key key, std::unique_ptr &&wrapper) { - ASSERT(Lookup(key) == nullptr); auto [it, inserted] = cache_.insert_or_assign(key, std::move(wrapper)); ASSERT(inserted); return it->second.get(); diff --git a/plugins/ets/runtime/interop_js/ets_vm_plugin.cpp b/plugins/ets/runtime/interop_js/ets_vm_plugin.cpp index c0214c30a..f2a6af1cd 100644 --- a/plugins/ets/runtime/interop_js/ets_vm_plugin.cpp +++ b/plugins/ets/runtime/interop_js/ets_vm_plugin.cpp @@ -49,6 +49,11 @@ static napi_value Version(napi_env env, [[maybe_unused]] napi_callback_info info return result; } +static napi_value Fatal([[maybe_unused]] napi_env env, [[maybe_unused]] napi_callback_info info) +{ + InteropCtx::Fatal("etsVm.Fatal"); +} + static napi_value GetEtsFunction(napi_env env, napi_callback_info info) { size_t js_argc = 0; @@ -282,6 +287,7 @@ static napi_value Init(napi_env env, napi_value exports) { const std::array desc = { napi_property_descriptor {"version", 0, Version, 0, 0, 0, napi_enumerable, 0}, + napi_property_descriptor {"fatal", 0, Fatal, 0, 0, 0, napi_enumerable, 0}, napi_property_descriptor {"call", 0, Call, 0, 0, 0, napi_enumerable, 0}, napi_property_descriptor {"callWithCopy", 0, CallWithCopy, 0, 0, 0, napi_enumerable, 0}, napi_property_descriptor {"createEtsRuntime", 0, CreateEtsRuntime, 0, 0, 0, napi_enumerable, 0}, diff --git a/plugins/ets/runtime/interop_js/interop_context.cpp b/plugins/ets/runtime/interop_js/interop_context.cpp index 49165b530..e61647ed4 100644 --- a/plugins/ets/runtime/interop_js/interop_context.cpp +++ b/plugins/ets/runtime/interop_js/interop_context.cpp @@ -22,6 +22,7 @@ #include "plugins/ets/runtime/interop_js/ts2ets_common.h" #include "plugins/ets/runtime/types/ets_method.h" #include "runtime/include/runtime.h" +#include "runtime/mem/local_object_handle.h" namespace panda::ets::interop::js { @@ -36,16 +37,22 @@ InteropCtx::InteropCtx(EtsCoroutine *coro) auto *job_queue = Runtime::GetCurrent()->GetInternalAllocator()->New(); vm->InitJobQueue(job_queue); - auto cache_class = [&](const char *descriptor) { - auto klass = ets_class_linker->GetClass(descriptor)->GetRuntimeClass(); + auto cache_class = [&](std::string_view descriptor) { + auto klass = ets_class_linker->GetClass(descriptor.data())->GetRuntimeClass(); ASSERT(klass != nullptr); return klass; }; - jsruntime_class_ = cache_class("Lstd/interop/js/JSRuntime;"); - jsvalue_class_ = cache_class("Lstd/interop/js/JSValue;"); - jsexception_class_ = cache_class("Lstd/interop/js/JSException;"); - string_class_ = cache_class("Lstd/core/String;"); - promise_class_ = cache_class("Lstd/core/Promise;"); + + namespace descriptors = panda_file_items::class_descriptors; + + jsruntime_class_ = cache_class(descriptors::JS_RUNTIME); + jsvalue_class_ = cache_class(descriptors::JS_VALUE); + jserror_class_ = cache_class(descriptors::JS_ERROR); + string_class_ = cache_class(descriptors::STRING); + promise_class_ = cache_class(descriptors::PROMISE); + error_class_ = cache_class(descriptors::ERROR); + exception_class_ = cache_class(descriptors::EXCEPTION); + type_class_ = cache_class(descriptors::TYPE); RegisterBuiltinJSRefConvertors(this); @@ -65,19 +72,19 @@ InteropCtx::InteropCtx(EtsCoroutine *coro) } } -EtsObject *InteropCtx::CreateJSException(EtsCoroutine *coro, JSValue *jsvalue) +EtsObject *InteropCtx::CreateETSCoreJSError(EtsCoroutine *coro, JSValue *jsvalue) { [[maybe_unused]] HandleScope scope(coro); VMHandle jsvalue_handle(coro, jsvalue->GetCoreType()); Method::Proto proto(Method::Proto::ShortyVector {panda_file::Type(panda_file::Type::TypeId::VOID), panda_file::Type(panda_file::Type::TypeId::REFERENCE)}, - Method::Proto::RefTypeVector {utf::Mutf8AsCString(JSValueClass()->GetDescriptor())}); + Method::Proto::RefTypeVector {utf::Mutf8AsCString(GetJSValueClass()->GetDescriptor())}); auto ctor_name = utf::CStringAsMutf8(panda_file_items::CTOR.data()); - auto ctor = JSExceptionClass()->GetDirectMethod(ctor_name, proto); + auto ctor = GetJSErrorClass()->GetDirectMethod(ctor_name, proto); ASSERT(ctor != nullptr); - auto exc_obj = ObjectHeader::Create(coro, JSExceptionClass()); + auto exc_obj = ObjectHeader::Create(coro, GetJSErrorClass()); if (UNLIKELY(exc_obj == nullptr)) { return nullptr; } @@ -92,7 +99,7 @@ EtsObject *InteropCtx::CreateJSException(EtsCoroutine *coro, JSValue *jsvalue) return res; } -void InteropCtx::ThrowETSException(EtsCoroutine *coro, napi_value val) +void InteropCtx::ThrowETSError(EtsCoroutine *coro, napi_value val) { auto ctx = Current(coro); @@ -103,72 +110,148 @@ void InteropCtx::ThrowETSException(EtsCoroutine *coro, napi_value val) } ASSERT(!coro->HasPendingException()); - auto exc = JSConvertJSException::Unwrap(ctx, ctx->GetJSEnv(), val); + // Conversion to more strict type (JSValue, Error, TypeError...) is done explicitly by frontend! + // TODO(vpukhov): create core/Error with dynamic/static implementations to merge in single catch clause + // TODO(vpukhov): unwrap ets_proxy immediately + auto exc = JSConvertJSError::Unwrap(ctx, ctx->GetJSEnv(), val); if (LIKELY(exc.has_value())) { ASSERT(exc != nullptr); coro->SetException(exc.value()->GetCoreType()); } // otherwise exception is already set } -void InteropCtx::ThrowETSException(EtsCoroutine *coro, const char *msg) +void InteropCtx::ThrowETSError(EtsCoroutine *coro, const char *msg) { ASSERT(!coro->HasPendingException()); - ets::ThrowEtsException(coro, "Lstd/interop/js/JSException;", msg); + ets::ThrowEtsException(coro, panda_file_items::class_descriptors::ERROR, msg); } void InteropCtx::ThrowJSError(napi_env env, const std::string &msg) { INTEROP_LOG(INFO) << "ThrowJSError: " << msg; - bool pending; - NAPI_CHECK_FATAL(napi_is_exception_pending(env, &pending)); - if (!pending) { - napi_throw_error(env, nullptr, msg.c_str()); - } + ASSERT(!NapiIsExceptionPending(env)); + NAPI_CHECK_FATAL(napi_throw_error(env, nullptr, msg.c_str())); +} + +void InteropCtx::ThrowJSTypeError(napi_env env, const std::string &msg) +{ + INTEROP_LOG(INFO) << "ThrowJSError: " << msg; + ASSERT(!NapiIsExceptionPending(env)); + NAPI_CHECK_FATAL(napi_throw_type_error(env, nullptr, msg.c_str())); } -void InteropCtx::ThrowJSException(napi_env env, napi_value val) +void InteropCtx::ThrowJSValue(napi_env env, napi_value val) { - INTEROP_LOG(INFO) << "ThrowJSException"; - napi_throw(env, val); + INTEROP_LOG(INFO) << "ThrowJSValue"; + ASSERT(!NapiIsExceptionPending(env)); + NAPI_CHECK_FATAL(napi_throw(env, val)); } void InteropCtx::ForwardEtsException(EtsCoroutine *coro) { auto env = GetJSEnv(); ASSERT(coro->HasPendingException()); - auto exc = EtsObject::FromCoreType(coro->GetException()); + LocalObjectHandle exc(coro, coro->GetException()); coro->ClearException(); - auto klass = exc->GetClass(); - if (LIKELY(klass->GetRuntimeClass() == JSExceptionClass())) { - napi_value js_exc = JSConvertJSException::Wrap(env, exc); - ThrowJSException(env, js_exc); + + auto klass = exc->ClassAddr(); + ASSERT(GetErrorClass()->IsAssignableFrom(klass)); + JSRefConvert *refconv = JSRefConvertResolve(this, klass); + if (UNLIKELY(refconv == nullptr)) { + INTEROP_LOG(INFO) << "Exception thrown while forwarding ets exception: " << klass->GetDescriptor(); return; } - // TODO(vpukhov): put proxy-object instead - ThrowJSError(env, klass->GetDescriptor()); + napi_value res = refconv->Wrap(this, EtsObject::FromCoreType(exc.GetPtr())); + if (UNLIKELY(res == nullptr)) { + return; + } + ThrowJSValue(env, res); } void InteropCtx::ForwardJSException(EtsCoroutine *coro) { + auto env = GetJSEnv(); napi_value excval; - NAPI_CHECK_FATAL(napi_get_and_clear_last_exception(GetJSEnv(), &excval)); - ThrowETSException(coro, excval); + ASSERT(NapiIsExceptionPending(env)); + NAPI_CHECK_FATAL(napi_get_and_clear_last_exception(env, &excval)); + ThrowETSError(coro, excval); } void JSConvertTypeCheckFailed(const char *type_name) { auto ctx = InteropCtx::Current(); auto env = ctx->GetJSEnv(); - std::string str = type_name + std::string(" expected"); - napi_value js_val; - NAPI_CHECK_FATAL(napi_create_string_utf8(env, str.data(), str.length(), &js_val)); - InteropCtx::ThrowJSException(env, js_val); + InteropCtx::ThrowJSTypeError(env, type_name + std::string(" expected")); +} + +static std::optional NapiTryGetStack(napi_env env) +{ + napi_value js_err; + if (NapiIsExceptionPending(env)) { + napi_value pending; + if (napi_ok != napi_get_and_clear_last_exception(env, &pending)) { + return {}; + } + } + napi_value js_dummy_str; + if (napi_ok != napi_create_string_utf8(env, "probe-stacktrace", NAPI_AUTO_LENGTH, &js_dummy_str)) { + return {}; + } + auto rc = napi_create_error(env, nullptr, js_dummy_str, &js_err); + if (napi_ok != rc) { + return {}; + } + napi_value js_stk; + if (napi_ok != napi_get_named_property(env, js_err, "stack", &js_stk)) { + return {}; + } + size_t length; + if (napi_ok != napi_get_value_string_utf8(env, js_stk, nullptr, 0, &length)) { + return {}; + } + std::string value; + value.resize(length); + // +1 for NULL terminated string!!! + if (napi_ok != napi_get_value_string_utf8(env, js_stk, value.data(), value.size() + 1, &length)) { + return {}; + } + return value; } [[noreturn]] void InteropCtx::Fatal(const char *msg) { INTEROP_LOG(ERROR) << "InteropCtx::Fatal: " << msg; - napi_fatal_error("ets::interop::js", NAPI_AUTO_LENGTH, msg, NAPI_AUTO_LENGTH); + + auto coro = EtsCoroutine::GetCurrent(); + auto ctx = InteropCtx::Current(coro); + + INTEROP_LOG(ERROR) << "====================== ETS stack begin ========================"; + auto &istk = ctx->GetInteropFrames(); + auto istk_it = istk.rbegin(); + + for (auto stack = StackWalker::Create(coro); stack.HasFrame(); stack.NextFrame()) { + if (istk_it != istk.rend() && stack.GetFp() == *istk_it) { + INTEROP_LOG(ERROR) << ""; + istk_it++; + } + + Method *method = stack.GetMethod(); + INTEROP_LOG(ERROR) << method->GetClass()->GetName() << "." << method->GetName().data << " at " + << method->GetLineNumberAndSourceFile(stack.GetBytecodePc()); + } + INTEROP_LOG(ERROR) << "====================== ETS stack end =========================="; + + auto env = ctx->GetJSEnv(); + std::optional js_stk = NapiTryGetStack(env); + if (js_stk.has_value()) { + INTEROP_LOG(ERROR) << "====================== JS stack begin ========================="; + INTEROP_LOG(ERROR) << js_stk.value(); + INTEROP_LOG(ERROR) << "====================== JS stack end ==========================="; + } else { + INTEROP_LOG(ERROR) << "JS stack print failed"; + } + + PrintStack(Logger::Message(Logger::Level::ERROR, Logger::Component::ETS_INTEROP_JS, false).GetStream()); std::abort(); } diff --git a/plugins/ets/runtime/interop_js/interop_context.h b/plugins/ets/runtime/interop_js/interop_context.h index 75e3021e4..1cdb1b94d 100644 --- a/plugins/ets/runtime/interop_js/interop_context.h +++ b/plugins/ets/runtime/interop_js/interop_context.h @@ -166,19 +166,24 @@ public: return args; } + std::vector &GetInteropFrames() + { + return interop_frames_; + } + JSRefConvertCache *GetRefConvertCache() { return &refconvert_cache_; } - Class *JSValueClass() const + Class *GetJSValueClass() const { return jsvalue_class_; } - Class *JSExceptionClass() const + Class *GetJSErrorClass() const { - return jsexception_class_; + return jserror_class_; } Class *GetStringClass() const @@ -191,21 +196,52 @@ public: return promise_class_; } - EtsObject *CreateJSException(EtsCoroutine *coro, JSValue *jsvalue); + Class *GetErrorClass() const + { + return error_class_; + } + + Class *GetExceptionClass() const + { + return exception_class_; + } - static void ThrowETSException(EtsCoroutine *coro, napi_value val); - static void ThrowETSException(EtsCoroutine *coro, const char *msg); - static void ThrowETSException(EtsCoroutine *coro, const std::string &msg) + Class *GetTypeClass() const { - ThrowETSException(coro, msg.c_str()); + return type_class_; + } + + EtsObject *CreateETSCoreJSError(EtsCoroutine *coro, JSValue *jsvalue); + + static void ThrowETSError(EtsCoroutine *coro, napi_value val); + static void ThrowETSError(EtsCoroutine *coro, const char *msg); + static void ThrowETSError(EtsCoroutine *coro, const std::string &msg) + { + ThrowETSError(coro, msg.c_str()); } static void ThrowJSError(napi_env env, const std::string &msg); - static void ThrowJSException(napi_env env, napi_value val); + static void ThrowJSTypeError(napi_env env, const std::string &msg); + static void ThrowJSValue(napi_env env, napi_value val); void ForwardEtsException(EtsCoroutine *coro); void ForwardJSException(EtsCoroutine *coro); + static bool SanityETSExceptionPending() + { + auto coro = EtsCoroutine::GetCurrent(); + auto env = InteropCtx::Current(coro)->GetJSEnv(); + return coro->HasPendingException() && !NapiIsExceptionPending(env); + } + + static bool SanityJSExceptionPending() + { + auto coro = EtsCoroutine::GetCurrent(); + auto env = InteropCtx::Current(coro)->GetJSEnv(); + return !coro->HasPendingException() && NapiIsExceptionPending(env); + } + + // Die and print execution stacks [[noreturn]] static void Fatal(const char *msg); [[noreturn]] static void Fatal(const std::string &msg) { @@ -250,13 +286,18 @@ private: // Temporary storages for arg convertors, std::tuple...> std::tuple, std::vector, std::vector> tmp_args_; + std::vector interop_frames_ {}; + JSRefConvertCache refconvert_cache_; Class *jsruntime_class_ {}; Class *jsvalue_class_ {}; - Class *jsexception_class_ {}; + Class *jserror_class_ {}; Class *string_class_ {}; Class *promise_class_ {}; + Class *error_class_ {}; + Class *exception_class_ {}; + Class *type_class_ {}; Method *jsvalue_fqueue_register_ {}; diff --git a/plugins/ets/runtime/interop_js/intrinsics_api_impl.cpp b/plugins/ets/runtime/interop_js/intrinsics_api_impl.cpp index 7d010ea07..bbe5ebc5e 100644 --- a/plugins/ets/runtime/interop_js/intrinsics_api_impl.cpp +++ b/plugins/ets/runtime/interop_js/intrinsics_api_impl.cpp @@ -230,7 +230,7 @@ static JSValue *JSRuntimeLoadModule(EtsString *module) NAPI_CHECK_FATAL(napi_get_global(env, &js_glob)); NAPI_CHECK_FATAL(napi_get_named_property(env, js_glob, func.data(), &require_fn)); - NAPI_FATAL_IF(GetValueType(env, require_fn) != napi_function); + INTEROP_FATAL_IF(GetValueType(env, require_fn) != napi_function); napi_value mod_obj; { napi_value js_name; @@ -247,9 +247,9 @@ static JSValue *JSRuntimeLoadModule(EtsString *module) std::abort(); } - NAPI_FATAL_IF(status != napi_ok); + INTEROP_FATAL_IF(status != napi_ok); } - NAPI_FATAL_IF(IsUndefined(env, mod_obj)); + INTEROP_FATAL_IF(IsUndefined(env, mod_obj)); return JSValue::CreateRefValue(coro, ctx, mod_obj, napi_object); } diff --git a/plugins/ets/runtime/interop_js/js_convert.h b/plugins/ets/runtime/interop_js/js_convert.h index 700f38443..89131394e 100644 --- a/plugins/ets/runtime/interop_js/js_convert.h +++ b/plugins/ets/runtime/interop_js/js_convert.h @@ -62,22 +62,26 @@ struct JSConvertBase { JSConvertTypeCheckFailed(Impl::type_name); } - // Convert ets->js, returns nullptr if failed + // Convert ets->js, returns nullptr if failed, throws JS exceptions static napi_value Wrap(napi_env env, cpptype ets_val) { if constexpr (IS_REFTYPE) { ASSERT(ets_val != nullptr); } - return Impl::WrapImpl(env, ets_val); + auto res = Impl::WrapImpl(env, ets_val); + ASSERT(res != nullptr || InteropCtx::SanityJSExceptionPending()); + return res; } - // Convert js->ets, returns nullopt if failed + // Convert js->ets, returns nullopt if failed, throws ETS/JS exceptions static std::optional Unwrap(InteropCtx *ctx, napi_env env, napi_value js_val) { if constexpr (IS_REFTYPE) { ASSERT(!IsNullOrUndefined(env, js_val)); } - return Impl::UnwrapImpl(ctx, env, js_val); + auto res = Impl::UnwrapImpl(ctx, env, js_val); + ASSERT(res.has_value() || InteropCtx::SanityJSExceptionPending() || InteropCtx::SanityETSExceptionPending()); + return res; } static napi_value WrapWithNullCheck(napi_env env, cpptype ets_val) @@ -87,7 +91,9 @@ struct JSConvertBase { return GetNull(env); } } - return Impl::WrapImpl(env, ets_val); + auto res = Impl::WrapImpl(env, ets_val); + ASSERT(res != nullptr || InteropCtx::SanityJSExceptionPending()); + return res; } static std::optional UnwrapWithNullCheck(InteropCtx *ctx, napi_env env, napi_value js_val) @@ -97,7 +103,9 @@ struct JSConvertBase { return nullptr; } } - return Impl::UnwrapImpl(ctx, env, js_val); + auto res = Impl::UnwrapImpl(ctx, env, js_val); + ASSERT(res.has_value() || InteropCtx::SanityJSExceptionPending() || InteropCtx::SanityETSExceptionPending()); + return res; } }; @@ -247,31 +255,32 @@ JSCONVERT_UNWRAP(JSValue) return JSValue::Create(EtsCoroutine::GetCurrent(), ctx, js_val); } -JSCONVERT_DEFINE_TYPE(JSException, EtsObject *) // TODO(vpukhov): associate with js Error? -JSCONVERT_WRAP(JSException) +// JSError convertors are supposed to box JSValue objects, do not treat them in any other way +JSCONVERT_DEFINE_TYPE(JSError, EtsObject *) +JSCONVERT_WRAP(JSError) { auto coro = EtsCoroutine::GetCurrent(); auto ctx = InteropCtx::Current(coro); auto klass = ets_val->GetClass(); - NAPI_FATAL_IF(klass->GetRuntimeClass() != ctx->JSExceptionClass()); + INTEROP_FATAL_IF(klass->GetRuntimeClass() != ctx->GetJSErrorClass()); - // TODO(vpukhov): remove call after adding a mirror-class for JSException + // TODO(vpukhov): remove call after adding a mirror-class for JSError auto method = klass->GetMethod("getValue"); ASSERT(method != nullptr); std::array args = {Value(ets_val->GetCoreType())}; auto val = JSValue::FromCoreType(method->GetPandaMethod()->Invoke(coro, args.data()).GetAs()); - NAPI_FATAL_IF(val == nullptr); + INTEROP_FATAL_IF(val == nullptr); return val->GetNapiValue(env); } -JSCONVERT_UNWRAP(JSException) +JSCONVERT_UNWRAP(JSError) { auto coro = EtsCoroutine::GetCurrent(); auto value = JSValue::Create(coro, ctx, js_val); if (UNLIKELY(value == nullptr)) { return {}; } - auto res = ctx->CreateJSException(coro, value); + auto res = ctx->CreateETSCoreJSError(coro, value); if (UNLIKELY(res == nullptr)) { return {}; } diff --git a/plugins/ets/runtime/interop_js/js_refconvert.cpp b/plugins/ets/runtime/interop_js/js_refconvert.cpp index 17768479e..996df000e 100644 --- a/plugins/ets/runtime/interop_js/js_refconvert.cpp +++ b/plugins/ets/runtime/interop_js/js_refconvert.cpp @@ -20,6 +20,8 @@ #include "plugins/ets/runtime/interop_js/js_convert.h" #include "runtime/mem/local_object_handle.h" +#include "plugins/ets/runtime/interop_js/js_refconvert_array.h" + namespace panda::ets::interop::js { // JSRefConvert adapter for builtin reference types @@ -28,13 +30,13 @@ class JSRefConvertBuiltin : public JSRefConvert { public: JSRefConvertBuiltin() : JSRefConvert(this) {} - napi_value WrapImpl(InteropCtx *ctx, EtsObject *obj) const + napi_value WrapImpl(InteropCtx *ctx, EtsObject *obj) { using ObjType = std::remove_pointer_t; return Conv::Wrap(ctx->GetJSEnv(), FromEtsObject(obj)); } - EtsObject *UnwrapImpl(InteropCtx *ctx, napi_value js_value) const + EtsObject *UnwrapImpl(InteropCtx *ctx, napi_value js_value) { auto res = Conv::Unwrap(ctx, ctx->GetJSEnv(), js_value); if (!res) { @@ -44,253 +46,73 @@ public: } }; -// JSRefConvert adapter for builtin[] types template -class JSRefConvertBuiltinArray : public JSRefConvert { -public: - explicit JSRefConvertBuiltinArray(Class *klass) : JSRefConvert(this), klass_(klass) {} - -private: - using elem_cpptype = typename Conv::cpptype; // NOLINT(readability-identifier-naming) - - static elem_cpptype GetElem(coretypes::Array *arr, size_t idx) - { - if constexpr (Conv::IS_REFTYPE) { - auto elem = EtsObject::FromCoreType(arr->Get(idx)); - return FromEtsObject>(elem); - } else { - return arr->Get(idx); - } - } - - static void SetElem(coretypes::Array *arr, size_t idx, elem_cpptype value) - { - if constexpr (Conv::IS_REFTYPE) { - arr->Set(idx, AsEtsObject(value)->GetCoreType()); - } else { - arr->Set(idx, value); - } - } - -public: - napi_value WrapImpl(InteropCtx *ctx, EtsObject *obj) const - { - auto env = ctx->GetJSEnv(); - - auto ets_arr = static_cast(obj->GetCoreType()); - auto len = ets_arr->GetLength(); - - NapiEscapableScope js_handle_scope(env); - napi_value js_arr; - NAPI_CHECK_FATAL(napi_create_array_with_length(env, len, &js_arr)); - - for (size_t idx = 0; idx < len; ++idx) { - elem_cpptype ets_elem = GetElem(ets_arr, idx); - auto js_elem = Conv::WrapWithNullCheck(env, ets_elem); - if (UNLIKELY(js_elem == nullptr)) { - return nullptr; - } - napi_status rc = napi_set_element(env, js_arr, idx, js_elem); - if (UNLIKELY(NapiThrownGeneric(rc))) { - return nullptr; - } - } - js_handle_scope.Escape(js_arr); - return js_arr; - } - - EtsObject *UnwrapImpl(InteropCtx *ctx, napi_value js_arr) const - { - auto coro = EtsCoroutine::GetCurrent(); - auto env = ctx->GetJSEnv(); - { - bool is_array; - NAPI_CHECK_FATAL(napi_is_array(env, js_arr, &is_array)); - if (UNLIKELY(!is_array)) { - JSConvertTypeCheckFailed("array"); - return nullptr; - } - } - - uint32_t len; - napi_status rc = napi_get_array_length(env, js_arr, &len); - if (UNLIKELY(NapiThrownGeneric(rc))) { - return nullptr; - } - - // TODO(vpukhov): elide handles for primitive arrays - LocalObjectHandle ets_arr(coro, coretypes::Array::Create(klass_, len)); - NapiScope js_handle_scope(env); - - for (size_t idx = 0; idx < len; ++idx) { - napi_value js_elem; - rc = napi_get_element(env, js_arr, idx, &js_elem); - if (UNLIKELY(NapiThrownGeneric(rc))) { - return nullptr; - } - auto res = Conv::UnwrapWithNullCheck(ctx, env, js_elem); - if (UNLIKELY(!res)) { - return nullptr; - } - SetElem(ets_arr.GetPtr(), idx, res.value()); - } - - return EtsObject::FromCoreType(ets_arr.GetPtr()); - } - -private: - Class *klass_ {}; -}; - -// JSRefConvert adapter for reference[] types -class JSRefConvertReftypeArray : public JSRefConvert { -public: - JSRefConvertReftypeArray(Class *klass, JSRefConvert *elem_conv) - : JSRefConvert(this), klass_(klass), elem_conv_(elem_conv) - { - } - - napi_value WrapImpl(InteropCtx *ctx, EtsObject *obj) const - { - auto coro = EtsCoroutine::GetCurrent(); - auto env = ctx->GetJSEnv(); - - LocalObjectHandle ets_arr(coro, obj->GetCoreType()); - auto len = ets_arr->GetLength(); - - NapiEscapableScope js_handle_scope(env); - napi_value js_arr; - NAPI_CHECK_FATAL(napi_create_array_with_length(env, len, &js_arr)); - - for (size_t idx = 0; idx < len; ++idx) { - EtsObject *ets_elem = EtsObject::FromCoreType(ets_arr->Get(idx)); - napi_value js_elem; - if (LIKELY(ets_elem != nullptr)) { - js_elem = elem_conv_->Wrap(ctx, ets_elem); - if (UNLIKELY(js_elem == nullptr)) { - return nullptr; - } - } else { - js_elem = GetNull(env); - } - napi_status rc = napi_set_element(env, js_arr, idx, js_elem); - if (UNLIKELY(NapiThrownGeneric(rc))) { - return nullptr; - } - } - js_handle_scope.Escape(js_arr); - return js_arr; - } - - EtsObject *UnwrapImpl(InteropCtx *ctx, napi_value js_arr) const - { - auto coro = EtsCoroutine::GetCurrent(); - auto env = ctx->GetJSEnv(); - { - bool is_array; - NAPI_CHECK_FATAL(napi_is_array(env, js_arr, &is_array)); - if (UNLIKELY(!is_array)) { - JSConvertTypeCheckFailed("array"); - return nullptr; - } - } - - uint32_t len; - napi_status rc = napi_get_array_length(env, js_arr, &len); - if (UNLIKELY(NapiThrownGeneric(rc))) { - return nullptr; - } - - LocalObjectHandle ets_arr(coro, coretypes::Array::Create(klass_, len)); - NapiScope js_handle_scope(env); - - for (size_t idx = 0; idx < len; ++idx) { - napi_value js_elem; - rc = napi_get_element(env, js_arr, idx, &js_elem); - if (UNLIKELY(NapiThrownGeneric(rc))) { - return nullptr; - } - if (LIKELY(!IsNullOrUndefined(env, js_elem))) { - EtsObject *ets_elem = elem_conv_->Unwrap(ctx, js_elem); - if (UNLIKELY(ets_elem == nullptr)) { - return nullptr; - } - ets_arr->Set(idx, ets_elem->GetCoreType()); - } - } - - return EtsObject::FromCoreType(ets_arr.GetPtr()); - } - -private: - static constexpr auto ELEM_SIZE = ClassHelper::OBJECT_POINTER_SIZE; - - Class *klass_ {}; - JSRefConvert *elem_conv_ {}; -}; +static inline void RegisterBuiltinRefConvertor(JSRefConvertCache *cache, Class *klass) +{ + [[maybe_unused]] bool res = CheckClassInitialized(klass); + ASSERT(res); + cache->Insert(klass, std::unique_ptr(new JSRefConvertBuiltin())); +} static std::unique_ptr JSRefConvertCreateImpl(InteropCtx *ctx, Class *klass) { + INTEROP_FATAL_IF(klass->IsClassClass()); + std::string descriptor(utf::Mutf8AsCString(klass->GetDescriptor())); if (descriptor.rfind("Lstd/", 0) == 0) { - NapiFatal("Convertor for " + descriptor + " requested, must add placeholder"); + if (UNLIKELY(ctx->GetExceptionClass()->IsAssignableFrom(klass))) { + ctx->Fatal(descriptor + " is Exception, Exceptions do not belong to subset"); + } + ctx->Fatal("Convertor for " + descriptor + " requested, must add or forbid"); } if (klass->IsArrayClass()) { auto type = klass->GetComponentType()->GetType().GetId(); - NAPI_FATAL_IF(type != panda_file::Type::TypeId::REFERENCE); // Unhandled primitive array - JSRefConvert *elem_conv = JSRefConvertResolve(ctx, klass->GetComponentType()); - if (UNLIKELY(elem_conv == nullptr)) { - return nullptr; + if (type != panda_file::Type::TypeId::REFERENCE) { + ctx->Fatal(std::string("Unhandled primitive array: ") + utf::Mutf8AsCString(klass->GetDescriptor())); } - return std::unique_ptr(new JSRefConvertReftypeArray(klass, elem_conv)); + return std::unique_ptr(new JSRefConvertReftypeArray(klass)); } - // TODO(vpukhov): ets_proxy - return nullptr; + return ets_proxy::EtsClassWrapper::CreateRefConv(ctx->GetJSEnv(), klass); } +template JSRefConvert *JSRefConvertCreate(InteropCtx *ctx, Class *klass) { + if (!CheckClassInitialized(klass)) { + ASSERT(EtsCoroutine::GetCurrent()->HasPendingException()); + return nullptr; + } auto conv = JSRefConvertCreateImpl(ctx, klass); if (UNLIKELY(conv == nullptr)) { - NapiFatal(std::string("Can't create convertor for ") + utf::Mutf8AsCString(klass->GetDescriptor())); + ctx->Fatal(std::string("Can't create convertor for ") + utf::Mutf8AsCString(klass->GetDescriptor())); } return ctx->GetRefConvertCache()->Insert(klass, std::move(conv)); } -template -static inline void RegisterBuiltinRefConvertor(JSRefConvertCache *cache, Class *klass) -{ - cache->Insert(klass, std::unique_ptr(new JSRefConvertBuiltin())); -} - -template -static inline void RegisterBuiltinArrayConvertor(JSRefConvertCache *cache, EtsClassLinkerExtension *ext) -{ - auto aklass = ext->GetClassRoot(CLASS_ROOT); - cache->Insert(aklass, std::unique_ptr(new JSRefConvertBuiltinArray(aklass))); -} +template JSRefConvert *JSRefConvertCreate(InteropCtx *ctx, Class *klass); +template JSRefConvert *JSRefConvertCreate(InteropCtx *ctx, Class *klass); void RegisterBuiltinJSRefConvertors(InteropCtx *ctx) { auto cache = ctx->GetRefConvertCache(); - RegisterBuiltinRefConvertor(cache, ctx->JSValueClass()); - RegisterBuiltinRefConvertor(cache, ctx->JSExceptionClass()); - RegisterBuiltinRefConvertor(cache, ctx->GetStringClass()); - RegisterBuiltinRefConvertor(cache, ctx->GetPromiseClass()); - auto coro = EtsCoroutine::GetCurrent(); PandaEtsVM *vm = coro->GetPandaVM(); EtsClassLinkerExtension *linker_ext = vm->GetClassLinker()->GetEtsClassLinkerExtension(); + RegisterBuiltinRefConvertor(cache, ctx->GetJSValueClass()); + RegisterBuiltinRefConvertor(cache, ctx->GetJSErrorClass()); + RegisterBuiltinRefConvertor(cache, ctx->GetStringClass()); + RegisterBuiltinRefConvertor(cache, ctx->GetPromiseClass()); + RegisterBuiltinArrayConvertor(cache, linker_ext); RegisterBuiltinArrayConvertor(cache, linker_ext); RegisterBuiltinArrayConvertor(cache, linker_ext); RegisterBuiltinArrayConvertor(cache, linker_ext); RegisterBuiltinArrayConvertor(cache, linker_ext); RegisterBuiltinArrayConvertor(cache, linker_ext); - // TODO(vpukhov): jsvalue[], currently uses JSRefConvertArrayRef + // TODO(vpukhov): jsvalue[] specialization, currently uses JSRefConvertArrayRef } } // namespace panda::ets::interop::js diff --git a/plugins/ets/runtime/interop_js/js_refconvert.h b/plugins/ets/runtime/interop_js/js_refconvert.h index 06faf52fe..3ddf28bab 100644 --- a/plugins/ets/runtime/interop_js/js_refconvert.h +++ b/plugins/ets/runtime/interop_js/js_refconvert.h @@ -17,6 +17,7 @@ #define PANDA_PLUGINS_ETS_RUNTIME_INTEROP_JS_JS_REFCONVERT_H_ #include "plugins/ets/runtime/ets_coroutine.h" +#include "plugins/ets/runtime/ets_vm.h" #include "plugins/ets/runtime/interop_js/ts2ets_common.h" #include "libpandabase/macros.h" #include @@ -34,15 +35,15 @@ inline napi_env JSEnvFromInteropCtx(InteropCtx *ctx); // Conversion interface for some panda::Class objects class JSRefConvert { public: - // Returns nullptr if failed - napi_value Wrap(InteropCtx *ctx, EtsObject *obj) const + // Convert ets->js, returns nullptr if failed, throws JS exceptions + napi_value Wrap(InteropCtx *ctx, EtsObject *obj) { ASSERT(obj != nullptr); return (this->*(this->wrap_))(ctx, obj); } - // Returns nullptr if failed - EtsObject *Unwrap(InteropCtx *ctx, napi_value js_value) const + // Convert js->ets, returns nullopt if failed, throws ETS/JS exceptions + EtsObject *Unwrap(InteropCtx *ctx, napi_value js_value) { ASSERT(!IsNullOrUndefined(JSEnvFromInteropCtx(ctx), js_value)); return (this->*(this->unwrap_))(ctx, js_value); @@ -84,6 +85,8 @@ public: JSRefConvert *Insert(Class *klass, std::unique_ptr value) { + ASSERT(value != nullptr); + ASSERT(klass->IsInitialized()); auto owned_value = value.get(); auto [it, inserted] = cache_.insert_or_assign(klass, std::move(value)); ASSERT(inserted); @@ -129,9 +132,11 @@ private: void RegisterBuiltinJSRefConvertors(InteropCtx *ctx); // Try to create JSRefConvert for nonexisting cache entry -JSRefConvert *JSRefConvertCreate(InteropCtx *ctx, Class *klass); +template +extern JSRefConvert *JSRefConvertCreate(InteropCtx *ctx, Class *klass); // Find or create JSRefConvert for some Class +template inline JSRefConvert *JSRefConvertResolve(InteropCtx *ctx, Class *klass) { JSRefConvertCache *cache = RefConvertCacheFromInteropCtx(ctx); @@ -139,7 +144,25 @@ inline JSRefConvert *JSRefConvertResolve(InteropCtx *ctx, Class *klass) if (LIKELY(conv != nullptr)) { return conv; } - return JSRefConvertCreate(ctx, klass); + return JSRefConvertCreate(ctx, klass); +} + +template +inline bool CheckClassInitialized(Class *klass) +{ + if constexpr (ALLOW_INIT) { + if (UNLIKELY(!klass->IsInitialized())) { + auto coro = EtsCoroutine::GetCurrent(); + auto class_linker = coro->GetPandaVM()->GetClassLinker(); + if (!class_linker->InitializeClass(coro, EtsClass::FromRuntimeClass(klass))) { + INTEROP_LOG(ERROR) << "Class " << klass->GetDescriptor() << " cannot be initialized"; + return false; + } + } + } else { + INTEROP_FATAL_IF(!klass->IsInitialized()); + } + return true; } } // namespace panda::ets::interop::js diff --git a/plugins/ets/runtime/interop_js/js_refconvert_array.h b/plugins/ets/runtime/interop_js/js_refconvert_array.h new file mode 100644 index 000000000..a2329d9ad --- /dev/null +++ b/plugins/ets/runtime/interop_js/js_refconvert_array.h @@ -0,0 +1,228 @@ +/** + * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PANDA_PLUGINS_ETS_RUNTIME_INTEROP_JS_JS_REFCONVERT_ARRAY_H_ +#define PANDA_PLUGINS_ETS_RUNTIME_INTEROP_JS_JS_REFCONVERT_ARRAY_H_ + +#include "plugins/ets/runtime/ets_class_linker_extension.h" +#include "plugins/ets/runtime/interop_js/interop_context.h" +#include "plugins/ets/runtime/interop_js/ts2ets_common.h" +#include "plugins/ets/runtime/interop_js/js_refconvert.h" +#include "plugins/ets/runtime/interop_js/js_convert.h" +#include "runtime/mem/local_object_handle.h" + +namespace panda::ets::interop::js { + +// JSRefConvert adapter for builtin[] types +template +class JSRefConvertBuiltinArray : public JSRefConvert { +public: + explicit JSRefConvertBuiltinArray(Class *klass) : JSRefConvert(this), klass_(klass) {} + +private: + using ElemCpptype = typename Conv::cpptype; + + static ElemCpptype GetElem(coretypes::Array *arr, size_t idx) + { + if constexpr (Conv::IS_REFTYPE) { + auto elem = EtsObject::FromCoreType(arr->Get(idx)); + return FromEtsObject>(elem); + } else { + return arr->Get(idx); + } + } + + static void SetElem(coretypes::Array *arr, size_t idx, ElemCpptype value) + { + if constexpr (Conv::IS_REFTYPE) { + arr->Set(idx, AsEtsObject(value)->GetCoreType()); + } else { + arr->Set(idx, value); + } + } + +public: + napi_value WrapImpl(InteropCtx *ctx, EtsObject *obj) + { + auto env = ctx->GetJSEnv(); + + auto ets_arr = static_cast(obj->GetCoreType()); + auto len = ets_arr->GetLength(); + + NapiEscapableScope js_handle_scope(env); + napi_value js_arr; + NAPI_CHECK_FATAL(napi_create_array_with_length(env, len, &js_arr)); + + for (size_t idx = 0; idx < len; ++idx) { + ElemCpptype ets_elem = GetElem(ets_arr, idx); + auto js_elem = Conv::WrapWithNullCheck(env, ets_elem); + if (UNLIKELY(js_elem == nullptr)) { + return nullptr; + } + napi_status rc = napi_set_element(env, js_arr, idx, js_elem); + if (UNLIKELY(NapiThrownGeneric(rc))) { + return nullptr; + } + } + js_handle_scope.Escape(js_arr); + return js_arr; + } + + EtsObject *UnwrapImpl(InteropCtx *ctx, napi_value js_arr) + { + auto coro = EtsCoroutine::GetCurrent(); + auto env = ctx->GetJSEnv(); + { + bool is_array; + NAPI_CHECK_FATAL(napi_is_array(env, js_arr, &is_array)); + if (UNLIKELY(!is_array)) { + JSConvertTypeCheckFailed("array"); + return nullptr; + } + } + + uint32_t len; + napi_status rc = napi_get_array_length(env, js_arr, &len); + if (UNLIKELY(NapiThrownGeneric(rc))) { + return nullptr; + } + + // TODO(vpukhov): elide handles for primitive arrays + LocalObjectHandle ets_arr(coro, coretypes::Array::Create(klass_, len)); + NapiScope js_handle_scope(env); + + for (size_t idx = 0; idx < len; ++idx) { + napi_value js_elem; + rc = napi_get_element(env, js_arr, idx, &js_elem); + if (UNLIKELY(NapiThrownGeneric(rc))) { + return nullptr; + } + auto res = Conv::UnwrapWithNullCheck(ctx, env, js_elem); + if (UNLIKELY(!res)) { + return nullptr; + } + SetElem(ets_arr.GetPtr(), idx, res.value()); + } + + return EtsObject::FromCoreType(ets_arr.GetPtr()); + } + +private: + Class *klass_ {}; +}; + +template +static inline void RegisterBuiltinArrayConvertor(JSRefConvertCache *cache, EtsClassLinkerExtension *ext) +{ + auto aklass = ext->GetClassRoot(CLASS_ROOT); + cache->Insert(aklass, std::unique_ptr(new JSRefConvertBuiltinArray(aklass))); +} + +// JSRefConvert adapter for reference[] types +class JSRefConvertReftypeArray : public JSRefConvert { +public: + explicit JSRefConvertReftypeArray(Class *klass) : JSRefConvert(this), klass_(klass) {} + + napi_value WrapImpl(InteropCtx *ctx, EtsObject *obj) + { + auto coro = EtsCoroutine::GetCurrent(); + auto env = ctx->GetJSEnv(); + + LocalObjectHandle ets_arr(coro, obj->GetCoreType()); + auto len = ets_arr->GetLength(); + + NapiEscapableScope js_handle_scope(env); + napi_value js_arr; + NAPI_CHECK_FATAL(napi_create_array_with_length(env, len, &js_arr)); + + for (size_t idx = 0; idx < len; ++idx) { + EtsObject *ets_elem = EtsObject::FromCoreType(ets_arr->Get(idx)); + napi_value js_elem; + if (LIKELY(ets_elem != nullptr)) { + if (UNLIKELY(elem_conv_ == nullptr)) { + elem_conv_ = JSRefConvertResolve(ctx, klass_->GetComponentType()); + } + js_elem = elem_conv_->Wrap(ctx, ets_elem); + if (UNLIKELY(js_elem == nullptr)) { + return nullptr; + } + } else { + js_elem = GetNull(env); + } + napi_status rc = napi_set_element(env, js_arr, idx, js_elem); + if (UNLIKELY(NapiThrownGeneric(rc))) { + return nullptr; + } + } + js_handle_scope.Escape(js_arr); + return js_arr; + } + + EtsObject *UnwrapImpl(InteropCtx *ctx, napi_value js_arr) + { + auto coro = EtsCoroutine::GetCurrent(); + auto env = ctx->GetJSEnv(); + { + bool is_array; + NAPI_CHECK_FATAL(napi_is_array(env, js_arr, &is_array)); + if (UNLIKELY(!is_array)) { + JSConvertTypeCheckFailed("array"); + return nullptr; + } + } + + uint32_t len; + napi_status rc = napi_get_array_length(env, js_arr, &len); + if (UNLIKELY(NapiThrownGeneric(rc))) { + return nullptr; + } + + LocalObjectHandle ets_arr(coro, coretypes::Array::Create(klass_, len)); + NapiScope js_handle_scope(env); + + for (size_t idx = 0; idx < len; ++idx) { + napi_value js_elem; + rc = napi_get_element(env, js_arr, idx, &js_elem); + if (UNLIKELY(NapiThrownGeneric(rc))) { + return nullptr; + } + if (LIKELY(!IsNullOrUndefined(env, js_elem))) { + if (UNLIKELY(elem_conv_ == nullptr)) { + elem_conv_ = JSRefConvertResolve(ctx, klass_->GetComponentType()); + if (UNLIKELY(elem_conv_ == nullptr)) { + return nullptr; + } + } + EtsObject *ets_elem = elem_conv_->Unwrap(ctx, js_elem); + if (UNLIKELY(ets_elem == nullptr)) { + return nullptr; + } + ets_arr->Set(idx, ets_elem->GetCoreType()); + } + } + + return EtsObject::FromCoreType(ets_arr.GetPtr()); + } + +private: + static constexpr auto ELEM_SIZE = ClassHelper::OBJECT_POINTER_SIZE; + + Class *klass_ {}; + JSRefConvert *elem_conv_ {}; // TODO(vpukhov): Add tagged link and overload JSRefConvertResolve +}; + +} // namespace panda::ets::interop::js + +#endif // !PANDA_PLUGINS_ETS_RUNTIME_INTEROP_JS_JS_REFCONVERT_ARRAY_H_ diff --git a/plugins/ets/runtime/interop_js/js_value.cpp b/plugins/ets/runtime/interop_js/js_value.cpp index e212d23e1..5f0c43f4a 100644 --- a/plugins/ets/runtime/interop_js/js_value.cpp +++ b/plugins/ets/runtime/interop_js/js_value.cpp @@ -29,6 +29,10 @@ namespace panda::ets::interop::js { LocalObjectHandle handle(coro, js_value); JSValue *mirror = AllocUndefined(coro, ctx); + if (UNLIKELY(mirror == nullptr)) { + JSRuntimeFinalizationQueueCallback(handle.GetPtr()); + return nullptr; + } mirror->type_ = handle->type_; mirror->data_ = handle->data_; @@ -77,6 +81,9 @@ JSValue *JSValue::Create(EtsCoroutine *coro, InteropCtx *ctx, napi_value nvalue) napi_valuetype js_type = GetValueType(env, nvalue); auto jsvalue = AllocUndefined(coro, ctx); + if (UNLIKELY(jsvalue == nullptr)) { + return nullptr; + } switch (js_type) { case napi_undefined: { @@ -102,17 +109,17 @@ JSValue *JSValue::Create(EtsCoroutine *coro, InteropCtx *ctx, napi_value nvalue) case napi_string: { auto cached_str = ctx->GetStringStor()->Get(interop::js::GetString(env, nvalue)); jsvalue->SetString(cached_str); - jsvalue = JSValue::AttachFinalizer(EtsCoroutine::GetCurrent(), jsvalue); - return jsvalue; + return JSValue::AttachFinalizer(EtsCoroutine::GetCurrent(), jsvalue); } case napi_symbol: [[fallthrough]]; case napi_object: [[fallthrough]]; - case napi_function: { + case napi_function: + [[fallthrough]]; + case napi_external: { jsvalue->SetRefValue(env, nvalue, js_type); - jsvalue = JSValue::AttachFinalizer(EtsCoroutine::GetCurrent(), jsvalue); - return jsvalue; + return JSValue::AttachFinalizer(EtsCoroutine::GetCurrent(), jsvalue); } default: { InteropCtx::Fatal("Unsupported JSValue.Type: " + std::to_string(js_type)); @@ -125,8 +132,8 @@ napi_value JSValue::GetNapiValue(napi_env env) { napi_value js_value {}; - auto type = GetType(); - switch (GetType()) { + auto js_type = GetType(); + switch (js_type) { case napi_undefined: { NAPI_ASSERT_OK(napi_get_undefined(env, &js_value)); return js_value; @@ -152,12 +159,13 @@ napi_value JSValue::GetNapiValue(napi_env env) [[fallthrough]]; case napi_object: [[fallthrough]]; - case napi_function: { + case napi_function: + [[fallthrough]]; + case napi_external: { return GetRefValue(env); } default: { - InteropCtx::Fatal("Unsupported JSValue.Type: " + std::to_string(type)); - return nullptr; + InteropCtx::Fatal("Unsupported JSValue.Type: " + std::to_string(js_type)); } } UNREACHABLE(); diff --git a/plugins/ets/runtime/interop_js/js_value.h b/plugins/ets/runtime/interop_js/js_value.h index 5f0ba7ff8..f3132ab9c 100644 --- a/plugins/ets/runtime/interop_js/js_value.h +++ b/plugins/ets/runtime/interop_js/js_value.h @@ -35,7 +35,7 @@ class JSValue : private EtsObject { public: static JSValue *FromEtsObject(EtsObject *ets_object) { - ASSERT(ets_object->GetClass() == EtsClass::FromRuntimeClass(InteropCtx::Current()->JSValueClass())); + ASSERT(ets_object->GetClass() == EtsClass::FromRuntimeClass(InteropCtx::Current()->GetJSValueClass())); return static_cast(ets_object); } @@ -74,6 +74,9 @@ public: static JSValue *CreateNull(EtsCoroutine *coro, InteropCtx *ctx) { auto jsvalue = AllocUndefined(coro, ctx); + if (UNLIKELY(jsvalue == nullptr)) { + return nullptr; + } jsvalue->SetNull(); return jsvalue; } @@ -81,6 +84,9 @@ public: static JSValue *CreateBoolean(EtsCoroutine *coro, InteropCtx *ctx, bool value) { auto jsvalue = AllocUndefined(coro, ctx); + if (UNLIKELY(jsvalue == nullptr)) { + return nullptr; + } jsvalue->SetBoolean(value); return jsvalue; } @@ -88,6 +94,9 @@ public: static JSValue *CreateNumber(EtsCoroutine *coro, InteropCtx *ctx, double value) { auto jsvalue = AllocUndefined(coro, ctx); + if (UNLIKELY(jsvalue == nullptr)) { + return nullptr; + } jsvalue->SetNumber(value); return jsvalue; } @@ -95,6 +104,9 @@ public: static JSValue *CreateString(EtsCoroutine *coro, InteropCtx *ctx, std::string &&value) { auto jsvalue = AllocUndefined(coro, ctx); + if (UNLIKELY(jsvalue == nullptr)) { + return nullptr; + } jsvalue->SetString(ctx->GetStringStor()->Get(std::move(value))); return JSValue::AttachFinalizer(EtsCoroutine::GetCurrent(), jsvalue); } @@ -102,6 +114,9 @@ public: static JSValue *CreateRefValue(EtsCoroutine *coro, InteropCtx *ctx, napi_value value, napi_valuetype type) { auto jsvalue = AllocUndefined(coro, ctx); + if (UNLIKELY(jsvalue == nullptr)) { + return nullptr; + } jsvalue->SetRefValue(ctx->GetJSEnv(), value, type); return JSValue::AttachFinalizer(EtsCoroutine::GetCurrent(), jsvalue); } @@ -185,8 +200,10 @@ private: { JSValue *js_value; { - auto obj = ObjectHeader::Create(coro, ctx->JSValueClass()); - NAPI_FATAL_IF(obj == nullptr); // TODO(vpukhov): forward OOM exception + auto obj = ObjectHeader::Create(coro, ctx->GetJSValueClass()); + if (UNLIKELY(!obj)) { + return nullptr; + } js_value = FromCoreType(obj); } static_assert(napi_undefined == 0); // zero-initialized diff --git a/plugins/ets/runtime/interop_js/js_value_call.cpp b/plugins/ets/runtime/interop_js/js_value_call.cpp index 61002261f..964165562 100644 --- a/plugins/ets/runtime/interop_js/js_value_call.cpp +++ b/plugins/ets/runtime/interop_js/js_value_call.cpp @@ -24,8 +24,11 @@ #include "runtime/include/class_linker-inl.h" #include "runtime/handle_scope-inl.h" +#include "runtime/mem/vm_handle-inl.h" + namespace panda::ets::interop::js { +// Convert js->ets, throws ETS/JS exceptions template [[nodiscard]] static ALWAYS_INLINE inline bool ConvertNapiVal(InteropCtx *ctx, FClsResolv &resolve_ref_cls, FStorePrim &store_prim, FStoreRef &store_ref, @@ -84,20 +87,20 @@ template } auto klass = resolve_ref_cls(); // start fastpath - if (klass == ctx->JSValueClass()) { + if (klass == ctx->GetJSValueClass()) { return unwrap_val(helpers::TypeIdentity()); } if (klass == ctx->GetStringClass()) { return unwrap_val(helpers::TypeIdentity()); } // start slowpath - auto refconv = JSRefConvertResolve(ctx, klass); - ObjectHeader *res = refconv->Unwrap(ctx, js_val)->GetCoreType(); - if (UNLIKELY(res == nullptr)) { + auto refconv = JSRefConvertResolve(ctx, klass); + if (UNLIKELY(refconv == nullptr)) { return false; } + ObjectHeader *res = refconv->Unwrap(ctx, js_val)->GetCoreType(); store_ref(res); - return true; + return res != nullptr; } default: { ctx->Fatal(std::string("ConvertNapiVal: unsupported typeid ") + @@ -107,29 +110,35 @@ template UNREACHABLE(); } +// Convert ets->js, throws JS exceptions template -static ALWAYS_INLINE inline void ConvertEtsVal(InteropCtx *ctx, [[maybe_unused]] FClsResolv &resolve_ref_cls, - FStore &store_res, panda_file::Type type, FRead &read_val) +[[nodiscard]] static ALWAYS_INLINE inline bool ConvertEtsVal(InteropCtx *ctx, + [[maybe_unused]] FClsResolv &resolve_ref_cls, + FStore &store_res, panda_file::Type type, FRead &read_val) { auto env = ctx->GetJSEnv(); - auto wrap_prim = [&](auto conv_tag) { + auto wrap_prim = [&](auto conv_tag) -> bool { using Convertor = typename decltype(conv_tag)::type; // conv_tag acts as lambda template parameter using cpptype = typename Convertor::cpptype; // NOLINT(readability-identifier-naming) - store_res(Convertor::Wrap(env, read_val(helpers::TypeIdentity()))); + napi_value res = Convertor::Wrap(env, read_val(helpers::TypeIdentity())); + store_res(res); + return res != nullptr; }; - auto wrap_ref = [&](auto conv_tag, ObjectHeader *ref) -> void { + auto wrap_ref = [&](auto conv_tag, ObjectHeader *ref) -> bool { using Convertor = typename decltype(conv_tag)::type; // conv_tag acts as lambda template parameter using cpptype = typename Convertor::cpptype; // NOLINT(readability-identifier-naming) cpptype value = std::remove_pointer_t::FromEtsObject(EtsObject::FromCoreType(ref)); - store_res(Convertor::Wrap(env, value)); + napi_value res = Convertor::Wrap(env, value); + store_res(res); + return res != nullptr; }; switch (type.GetId()) { case panda_file::Type::TypeId::VOID: { store_res(nullptr); - return; + return true; } case panda_file::Type::TypeId::U1: { return wrap_prim(helpers::TypeIdentity()); @@ -159,12 +168,12 @@ static ALWAYS_INLINE inline void ConvertEtsVal(InteropCtx *ctx, [[maybe_unused]] ObjectHeader *ref = read_val(helpers::TypeIdentity()); if (UNLIKELY(ref == nullptr)) { store_res(GetNull(env)); - return; + return true; } auto klass = ref->template ClassAddr(); ASSERT(klass == resolve_ref_cls()); // TODO(vpukhov): inheritance // start fastpath - if (klass == ctx->JSValueClass()) { + if (klass == ctx->GetJSValueClass()) { return wrap_ref(helpers::TypeIdentity(), ref); } if (klass == ctx->GetStringClass()) { @@ -173,20 +182,11 @@ static ALWAYS_INLINE inline void ConvertEtsVal(InteropCtx *ctx, [[maybe_unused]] // start slowpath auto refconv = JSRefConvertResolve(ctx, klass); if (UNLIKELY(refconv == nullptr)) { - // NOTE: - // Create ets_proxy::EtsClassWrapper for klass that automatically adds to RefConvertCache - EtsClass *ets_class = EtsClass::FromRuntimeClass(klass); - ets_proxy::EtsClassWrapper *ets_class_wrapper = ets_proxy::EtsClassWrapper::Get(env, ets_class); - if (UNLIKELY(ets_class_wrapper == nullptr)) { - INTEROP_LOG(FATAL) << "ConvertEtsVal: Cannot create EtsClassWrapper for class: " - << ets_class->GetDescriptor(); - } - - refconv = JSRefConvertResolve(ctx, klass); - ASSERT(refconv != nullptr); + return false; } - store_res(refconv->Wrap(ctx, EtsObject::FromCoreType(ref))); - return; + auto res = refconv->Wrap(ctx, EtsObject::FromCoreType(ref)); + store_res(res); + return res != nullptr; } default: { ctx->Fatal(std::string("ConvertEtsVal: unsupported typeid ") + @@ -200,7 +200,7 @@ using ArgValueBox = std::variant; template napi_value EtsCallImpl(EtsCoroutine *coro, InteropCtx *ctx, Method *method, Span jsargv, - EtsObject *this_ets_object) + EtsObject *this_obj) { ASSERT_MANAGED_CODE(); @@ -215,6 +215,9 @@ napi_value EtsCallImpl(EtsCoroutine *coro, InteropCtx *ctx, Method *method, Span ASSERT(klass != nullptr); return klass; }; + auto load_ref_cls = [&](uint32_t idx) { + return class_linker->GetClass(*pf, pda.GetReferenceType(idx), ctx->LinkerCtx()); + }; panda_file::ShortyIterator it(method->GetShorty()); auto ref_arg_idx = static_cast(!(*it++).IsPrimitive()); // skip retval @@ -227,23 +230,31 @@ napi_value EtsCallImpl(EtsCoroutine *coro, InteropCtx *ctx, Method *method, Span { HandleScope ets_handle_scope(coro); + VMHandle this_obj_handle {}; + if constexpr (!IS_STATIC) { + this_obj_handle = VMHandle(coro, this_obj->GetCoreType()); + } else { + (void)this_obj; + ASSERT(this_obj == nullptr); + } + auto &ets_boxed_args = ctx->GetTempArgs(num_args); // Convert and box in VMHandle if necessary for (uint32_t arg_idx = 0; arg_idx < num_args; ++arg_idx, it.IncrementWithoutCheck()) { panda_file::Type type = *it; auto js_val = jsargv[arg_idx]; - auto cls_resolver = [&]() { return resolve_ref_cls(ref_arg_idx++); }; + auto cls_resolver = [&]() { return load_ref_cls(ref_arg_idx++); }; auto store_prim = [&](uint64_t val) { ets_boxed_args[arg_idx] = val; }; auto store_ref = [&](ObjectHeader *obj) { - ObjectHeader **ref = nullptr; - if (obj != nullptr) { - uintptr_t addr = VMHandle(coro, obj).GetAddress(); - ref = reinterpret_cast(addr); - } - ets_boxed_args[arg_idx] = ref; + uintptr_t addr = VMHandle(coro, obj).GetAddress(); + ets_boxed_args[arg_idx] = reinterpret_cast(addr); }; if (UNLIKELY(!ConvertNapiVal(ctx, cls_resolver, store_prim, store_ref, type, js_val))) { + if (coro->HasPendingException()) { + ctx->ForwardEtsException(coro); + } + ctx->SanityJSExceptionPending(); INTEROP_LOG(DEBUG) << "EtsCall: exit with pending exception"; return nullptr; } @@ -251,24 +262,17 @@ napi_value EtsCallImpl(EtsCoroutine *coro, InteropCtx *ctx, Method *method, Span // Unbox VMHandles for (size_t i = 0; i < num_args; ++i) { - auto const &box = ets_boxed_args[i]; + ArgValueBox &box = ets_boxed_args[i]; if (std::holds_alternative(box)) { - ObjectHeader **ref = std::get<1>(box); - ObjectHeader *obj = nullptr; - if (ref != nullptr) { - obj = *ref; - } - ets_args[ETS_ARGS_DISP + i] = Value(obj); + ObjectHeader **slot = std::get<1>(box); + ets_args[ETS_ARGS_DISP + i] = Value(slot != nullptr ? *slot : nullptr); } else { ets_args[ETS_ARGS_DISP + i] = Value(std::get<0>(box)); } } - } - if constexpr (!IS_STATIC) { - ets_args[0] = Value(this_ets_object->GetCoreType()); - } else { - (void)this_ets_object; - ASSERT(this_ets_object == nullptr); + if constexpr (!IS_STATIC) { + ets_args[0] = Value(this_obj_handle.GetPtr()); + } } Value ets_res = method->Invoke(coro, ets_args.data()); @@ -285,16 +289,19 @@ napi_value EtsCallImpl(EtsCoroutine *coro, InteropCtx *ctx, Method *method, Span auto cls_resolver = [&]() { return resolve_ref_cls(0); }; auto store_res = [&](napi_value res) { js_res = res; }; auto read_val = [&](auto type_tag) { return ets_res.GetAs(); }; - ConvertEtsVal(ctx, cls_resolver, store_res, type, read_val); + if (UNLIKELY(!ConvertEtsVal(ctx, cls_resolver, store_res, type, read_val))) { + ctx->SanityJSExceptionPending(); + return nullptr; + } } return js_res; } // Explicit instantiation template napi_value EtsCallImpl(EtsCoroutine *coro, InteropCtx *ctx, Method *method, Span jsargv, - EtsObject *this_ets_object); + EtsObject *this_obj); template napi_value EtsCallImpl(EtsCoroutine *coro, InteropCtx *ctx, Method *method, Span jsargv, - EtsObject *this_ets_object); + EtsObject *this_obj); napi_value CallEtsFunctionImpl(napi_env env, Span jsargv) { @@ -330,7 +337,7 @@ napi_value CallEtsFunctionImpl(napi_env env, Span jsargv) } ScopedManagedCodeThread managed_scope(coro); - auto js_ret = EtsCallImpl(coro, ctx, method, jsargv.SubSpan(1), nullptr); + auto js_ret = EtsCallImplStatic(coro, ctx, method, jsargv.SubSpan(1)); INTEROP_LOG(DEBUG) << "CallEtsFunction: exit"; return js_ret; } @@ -351,8 +358,10 @@ napi_value EtsLambdaProxyInvoke(napi_env env, napi_callback_info cbinfo) auto ets_ref = static_cast(data); ASSERT(ets_ref != nullptr); - auto ets_obj = EtsObject::FromCoreType(ctx->Refstor()->Get(ets_ref)); - auto method = ets_obj->GetClass()->GetMethod("invoke"); + + ScopedManagedCodeThread managed_scope(coro); + auto ets_this = EtsObject::FromCoreType(ctx->Refstor()->Get(ets_ref)); + auto method = ets_this->GetClass()->GetMethod("invoke"); ASSERT(method != nullptr); if (UNLIKELY(argc != method->GetNumArgs() - 1)) { @@ -360,16 +369,14 @@ napi_value EtsLambdaProxyInvoke(napi_env env, napi_callback_info cbinfo) return nullptr; } - ScopedManagedCodeThread managed_scope(coro); auto js_args_span = Span(js_args.data(), js_args.size()); - auto this_ets_object = EtsObject::FromCoreType(ctx->Refstor()->Get(ets_ref)); - auto js_ret = EtsCallImpl(coro, ctx, method->GetPandaMethod(), js_args_span, this_ets_object); + auto js_ret = EtsCallImplInstance(coro, ctx, method->GetPandaMethod(), js_args_span, ets_this); INTEROP_LOG(DEBUG) << "EtsProxyInvoke: exit"; return js_ret; } template -static void WalkQualifiedName(std::string_view name, F const &f) +static bool WalkQualifiedName(std::string_view name, F const &f) { for (const char *p = name.data(); *p == DELIM;) { // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) @@ -377,29 +384,37 @@ static void WalkQualifiedName(std::string_view name, F const &f) while (*e != '\0' && *e != DELIM) { e++; } - f(std::string(p, ToUintPtr(e) - ToUintPtr(p))); + std::string substr(p, ToUintPtr(e) - ToUintPtr(p)); + if (UNLIKELY(!f(substr))) { + return false; + } p = e; } + return true; } -static inline std::pair ResolveQualifiedJSCall(napi_env env, napi_value js_val, - coretypes::String *qname_str) +static inline std::optional> ResolveQualifiedJSCall(napi_env env, napi_value js_val, + coretypes::String *qname_str) { napi_value js_this {}; + ASSERT(qname_str->IsMUtf8()); auto qname = std::string_view(utf::Mutf8AsCString(qname_str->GetDataMUtf8()), qname_str->GetMUtf8Length()); - auto resolve_name = [&](const std::string &name) { + auto resolve_name = [&](const std::string &name) -> bool { js_this = js_val; INTEROP_LOG(DEBUG) << "JSValueJSCall: resolve name: " << name; napi_status rc = napi_get_named_property(env, js_val, name.c_str(), &js_val); - if (UNLIKELY(NapiThrownGeneric(rc))) { - NapiFatal("Qualified name resolution throws"); // TODO(vpukhov): handle different failure types + if (UNLIKELY(rc == napi_object_expected || NapiThrownGeneric(rc))) { + ASSERT(NapiIsExceptionPending(env)); + return false; } + return true; }; js_this = js_val; - WalkQualifiedName(qname, resolve_name); - - return {js_this, js_val}; + if (UNLIKELY(!WalkQualifiedName(qname, resolve_name))) { + return std::nullopt; + } + return std::make_pair(js_this, js_val); } template @@ -419,6 +434,9 @@ static ALWAYS_INLINE inline uint64_t JSValueJSCallImpl(Method *method, uint8_t * ASSERT(klass != nullptr); return klass; }; + auto load_ref_cls = [&](uint32_t idx) { + return class_linker->GetClass(*pf, pda.GetReferenceType(idx), ctx->LinkerCtx()); + }; Span in_gpr_args(args, arch::ExtArchTraits::GP_ARG_NUM_BYTES); Span in_fpr_args(in_gpr_args.end(), arch::ExtArchTraits::FP_ARG_NUM_BYTES); @@ -446,7 +464,17 @@ static ALWAYS_INLINE inline uint64_t JSValueJSCallImpl(Method *method, uint8_t * napi_value js_val = JSConvertJSValue::Wrap(env, arg_reader.Read()); auto qname_str = arg_reader.Read(); ASSERT(qname_str->ClassAddr()->IsStringClass()); - std::tie(js_this, js_fn) = ResolveQualifiedJSCall(env, js_val, qname_str); + auto res = ResolveQualifiedJSCall(env, js_val, qname_str); + if (UNLIKELY(!res.has_value())) { + ctx->ForwardJSException(coro); + return 0; + } + std::tie(js_this, js_fn) = res.value(); + } + if (UNLIKELY(GetValueType(env, js_fn) != napi_function)) { + ctx->ThrowJSTypeError(env, "call target is not a function"); + ctx->ForwardJSException(coro); + return 0; } auto &jsargs = ctx->GetTempArgs(num_args); @@ -455,22 +483,29 @@ static ALWAYS_INLINE inline uint64_t JSValueJSCallImpl(Method *method, uint8_t * auto cls_resolver = [&]() { return resolve_ref_cls(ref_arg_idx++); }; auto store_res = [&](napi_value res) { jsargs[arg_idx] = res; }; auto read_val = [&](auto type_tag) { return arg_reader.Read(); }; - ConvertEtsVal(ctx, cls_resolver, store_res, *it, read_val); + if (UNLIKELY(!ConvertEtsVal(ctx, cls_resolver, store_res, *it, read_val))) { + ctx->ForwardJSException(coro); + return 0; + } } napi_value js_ret; napi_status js_status; { + ctx->GetInteropFrames().push_back(coro->GetCurrentFrame()); ScopedNativeCodeThread native_scope(coro); + if constexpr (IS_NEWCALL) { js_status = napi_new_instance(env, js_fn, jsargs.size(), jsargs.data(), &js_ret); } else { js_status = napi_call_function(env, js_this, js_fn, jsargs.size(), jsargs.data(), &js_ret); } + + ctx->GetInteropFrames().pop_back(); } if (UNLIKELY(js_status != napi_ok)) { - NAPI_FATAL_IF(js_status != napi_pending_exception); + INTEROP_FATAL_IF(js_status != napi_pending_exception); ctx->ForwardJSException(coro); INTEROP_LOG(DEBUG) << "JSValueJSCall: exit with pending exception"; return 0; @@ -479,7 +514,7 @@ static ALWAYS_INLINE inline uint64_t JSValueJSCallImpl(Method *method, uint8_t * Value ets_ret; if constexpr (IS_NEWCALL) { - NAPI_FATAL_IF(resolve_ref_cls(0) != ctx->JSValueClass()); + INTEROP_FATAL_IF(resolve_ref_cls(0) != ctx->GetJSValueClass()); auto res = JSConvertJSValue::Unwrap(ctx, env, js_ret); if (!res.has_value()) { ctx->Fatal("newcall result unwrap failed, but shouldnt"); @@ -487,11 +522,14 @@ static ALWAYS_INLINE inline uint64_t JSValueJSCallImpl(Method *method, uint8_t * ets_ret = Value(res.value()->GetCoreType()); } else { panda_file::Type type = method->GetReturnType(); - auto cls_resolver = [&]() { return resolve_ref_cls(0); }; + auto cls_resolver = [&]() { return load_ref_cls(0); }; auto store_prim = [&](uint64_t val) { ets_ret = Value(val); }; auto store_ref = [&](ObjectHeader *obj) { ets_ret = Value(obj); }; if (UNLIKELY(!ConvertNapiVal(ctx, cls_resolver, store_prim, store_ref, type, js_ret))) { - ctx->ForwardJSException(coro); + if (NapiIsExceptionPending(env)) { + ctx->ForwardJSException(coro); + } + ctx->SanityETSExceptionPending(); INTEROP_LOG(DEBUG) << "JSValueJSCall: exit with pending exception"; return 0; } @@ -529,7 +567,7 @@ static void InitJSCallSignatures(coretypes::String *cls_str) const char *class_descriptor = utf::Mutf8AsCString(cls_str->GetDataMUtf8()); EtsClass *ets_class = coro->GetPandaVM()->GetClassLinker()->GetClass(class_descriptor); - NAPI_FATAL_IF(ets_class == nullptr); + INTEROP_FATAL_IF(ets_class == nullptr); auto klass = ets_class->GetRuntimeClass(); INTEROP_LOG(DEBUG) << "Bind bridge call methods for " << utf::Mutf8AsCString(klass->GetDescriptor()); @@ -554,7 +592,7 @@ static void InitJSCallSignatures(coretypes::String *cls_str) if (cls1->IsStringClass()) { method_ep = reinterpret_cast(JSValueJSCallBridge); } else { - ASSERT(cls1 == ctx->JSValueClass()); + ASSERT(cls1 == ctx->GetJSValueClass()); method_ep = reinterpret_cast(JSValueJSCallByValueBridge); } } diff --git a/plugins/ets/runtime/interop_js/js_value_call.h b/plugins/ets/runtime/interop_js/js_value_call.h index 94645525d..5975de31c 100644 --- a/plugins/ets/runtime/interop_js/js_value_call.h +++ b/plugins/ets/runtime/interop_js/js_value_call.h @@ -44,7 +44,18 @@ uint8_t JSRuntimeInitJSNewClass(EtsString *cls_str); template napi_value EtsCallImpl(EtsCoroutine *coro, InteropCtx *ctx, Method *method, Span jsargv, - EtsObject *this_ets_object); + EtsObject *this_obj); + +inline napi_value EtsCallImplInstance(EtsCoroutine *coro, InteropCtx *ctx, Method *method, Span jsargv, + EtsObject *this_obj) +{ + return EtsCallImpl(coro, ctx, method, jsargv, this_obj); +} + +inline napi_value EtsCallImplStatic(EtsCoroutine *coro, InteropCtx *ctx, Method *method, Span jsargv) +{ + return EtsCallImpl(coro, ctx, method, jsargv, nullptr); +} } // namespace panda::ets::interop::js diff --git a/plugins/ets/runtime/interop_js/ts2ets_common.h b/plugins/ets/runtime/interop_js/ts2ets_common.h index 271e97eb6..6d4b83fe8 100644 --- a/plugins/ets/runtime/interop_js/ts2ets_common.h +++ b/plugins/ets/runtime/interop_js/ts2ets_common.h @@ -84,19 +84,20 @@ namespace panda::ets::interop::js { [[noreturn]] void NapiFatal(const char *message); [[noreturn]] void NapiFatal(const std::string &message); +// Alternative for ASSERT(!expr) with interop stacktraces, enabled in NDEBUG // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) -#define NAPI_FATAL_IF(expr) \ - do { \ - bool _expr = (expr); \ - if (UNLIKELY(_expr)) { \ - NapiFatal(#expr); \ - UNREACHABLE(); \ - } \ +#define INTEROP_FATAL_IF(expr) \ + do { \ + bool _expr = (expr); \ + if (UNLIKELY(_expr)) { \ + NapiFatal("INTEROP_FATAL: " #expr); \ + UNREACHABLE(); \ + } \ } while (0) #if !defined(NDEBUG) // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) -#define NAPI_ASSERT_OK(expr) NAPI_FATAL_IF(expr) +#define NAPI_ASSERT_OK(expr) INTEROP_FATAL_IF(expr) #else // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) #define NAPI_ASSERT_OK(expr) \ @@ -105,14 +106,15 @@ namespace panda::ets::interop::js { } while (0) #endif +// Assertion for napi_* calls success, enabled in NDEBUG // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) -#define NAPI_CHECK_FATAL(status) NAPI_FATAL_IF((status) != napi_ok) +#define NAPI_CHECK_FATAL(status) INTEROP_FATAL_IF((status) != napi_ok) // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) #define TYPEVIS_NAPI_CHECK(expr) TYPEVIS_CHECK_ERROR((expr) == napi_ok, #expr) inline bool NapiThrownGeneric(napi_status rc) { - NAPI_FATAL_IF(rc != napi_ok && rc != napi_generic_failure); + INTEROP_FATAL_IF(rc != napi_ok && rc != napi_generic_failure); return rc == napi_generic_failure; } @@ -238,6 +240,13 @@ inline std::string GetString(napi_env env, napi_value js_val) return value; } +inline bool NapiIsExceptionPending(napi_env env) +{ + bool pending; + NAPI_CHECK_FATAL(napi_is_exception_pending(env, &pending)); + return pending; +} + } // namespace panda::ets::interop::js #endif // !PANDA_PLUGINS_ETS_RUNTIME_TS2ETS_TS2ETS_COMMON_H_ diff --git a/plugins/ets/runtime/interop_js/ts2ets_copy.cpp b/plugins/ets/runtime/interop_js/ts2ets_copy.cpp index 8134ee863..a7ec60434 100644 --- a/plugins/ets/runtime/interop_js/ts2ets_copy.cpp +++ b/plugins/ets/runtime/interop_js/ts2ets_copy.cpp @@ -170,7 +170,7 @@ public: void VisitObject(panda::Class *klass) override { - ASSERT(klass != InteropCtx::Current()->JSValueClass()); + ASSERT(klass != InteropCtx::Current()->GetJSValueClass()); TYPEVIS_CHECK_ERROR(GetValueType(env_, js_value_) == napi_object, "object expected"); auto coro = EtsCoroutine::GetCurrent(); @@ -303,7 +303,7 @@ public: void VisitObject(panda::Class *klass) override { - ASSERT(klass != InteropCtx::Current()->JSValueClass()); + ASSERT(klass != InteropCtx::Current()->GetJSValueClass()); auto coro = EtsCoroutine::GetCurrent(); if (klass == InteropCtx::Current(coro)->GetPromiseClass()) { VisitPromise(); @@ -700,7 +700,7 @@ napi_value InvokeEtsMethodImpl(napi_env env, napi_value *jsargv, uint32_t jsargc ets2js.Error().value())); return nullptr; } - InteropCtx::ThrowJSException(env, ets2js.GetResult()); + InteropCtx::ThrowJSValue(env, ets2js.GetResult()); return nullptr; } diff --git a/plugins/ets/runtime/interop_js/ts2ets_tstype.cpp b/plugins/ets/runtime/interop_js/ts2ets_tstype.cpp index 0ffabddcf..1d1d6d2f3 100644 --- a/plugins/ets/runtime/interop_js/ts2ets_tstype.cpp +++ b/plugins/ets/runtime/interop_js/ts2ets_tstype.cpp @@ -62,7 +62,7 @@ static napi_ref TSModuleRequire(napi_env env, const std::string &name) napi_value js_val; NAPI_CHECK_FATAL(napi_get_global(env, &js_glob)); NAPI_CHECK_FATAL(napi_get_named_property(env, js_glob, name.c_str(), &js_val)); - NAPI_FATAL_IF(IsUndefined(env, js_val)); + INTEROP_FATAL_IF(IsUndefined(env, js_val)); napi_ref js_ref; NAPI_CHECK_FATAL(napi_create_reference(env, js_val, 1, &js_ref)); return js_ref; @@ -76,7 +76,7 @@ static napi_ref TSNapiModuleRequire(napi_env env, const std::string &name) napi_value require_fn; NAPI_CHECK_FATAL(napi_get_global(env, &js_glob)); NAPI_CHECK_FATAL(napi_get_named_property(env, js_glob, "requireNapi", &require_fn)); - NAPI_FATAL_IF(IsUndefined(env, require_fn)); + INTEROP_FATAL_IF(IsUndefined(env, require_fn)); napi_value mod_obj; { napi_value js_name; @@ -84,7 +84,7 @@ static napi_ref TSNapiModuleRequire(napi_env env, const std::string &name) std::array args = {js_name}; NAPI_CHECK_FATAL(napi_call_function(env, nullptr, require_fn, args.size(), args.data(), &mod_obj)); } - NAPI_FATAL_IF(IsUndefined(env, mod_obj)); + INTEROP_FATAL_IF(IsUndefined(env, mod_obj)); napi_ref js_ref; NAPI_CHECK_FATAL(napi_create_reference(env, mod_obj, 1, &js_ref)); return js_ref; @@ -124,9 +124,9 @@ TSTypeNamespace *TSTypeNamespace::FromClass(Class *klass) { auto klass_obj = coretypes::Class::FromRuntimeClass(klass); auto field = klass->GetStaticFieldByName(utf::CStringAsMutf8(TSTypeNamespace::TSTYPE_DATA_FIELD)); - NAPI_FATAL_IF(field == nullptr); + INTEROP_FATAL_IF(field == nullptr); auto data = reinterpret_cast(klass_obj->GetFieldPrimitive(*field)); - NAPI_FATAL_IF(data == nullptr); + INTEROP_FATAL_IF(data == nullptr); return data; } @@ -184,7 +184,7 @@ napi_value TSTypeNamespace::ResolveValue(napi_env env) NAPI_CHECK_FATAL(napi_get_reference_value(env, toplevel_, &cur_obj)); // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) TSTYPE_DLOG_A("get toplevel: %s", klass_->GetName().c_str()); - NAPI_FATAL_IF(IsUndefined(env, cur_obj)); + INTEROP_FATAL_IF(IsUndefined(env, cur_obj)); for (auto const &e : resolv_) { napi_value prop; @@ -209,7 +209,7 @@ static uint64_t TSTypeCCtor([[maybe_unused]] Method *method, coretypes::String * auto env = ctx->GetJSEnv(); auto ets_class_linker = coro->GetPandaVM()->GetClassLinker(); auto tklass = ctx->LinkerCtx()->FindClass(descriptor->GetDataMUtf8()); - NAPI_FATAL_IF(tklass == nullptr); + INTEROP_FATAL_IF(tklass == nullptr); // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) TSTYPE_DLOG_A("TSTypeCCtor: %s", tklass->GetName().c_str()); @@ -221,7 +221,7 @@ static uint64_t TSTypeCCtor([[maybe_unused]] Method *method, coretypes::String * ns = TSTypeNamespace::Create(tklass, TSNapiModuleRequire(env, tklass->GetName())); } else { auto eklass = ets_class_linker->GetClass(eklass_name)->GetRuntimeClass(); - NAPI_FATAL_IF(eklass == nullptr); + INTEROP_FATAL_IF(eklass == nullptr); ets_class_linker->InitializeClass(coro, EtsClass::FromRuntimeClass(eklass)); ns = TSTypeNamespace::Create(tklass, TSTypeNamespace::FromClass(eklass)); } @@ -261,7 +261,7 @@ ALWAYS_INLINE inline uint64_t TSTypeCall(Method *method, uint8_t *args, uint8_t return klass; }; - NAPI_FATAL_IF(method->IsStatic() != IS_STATIC); + INTEROP_FATAL_IF(method->IsStatic() != IS_STATIC); auto const num_args = method->GetNumArgs() - (IS_STATIC ? 0 : 1); napi_value js_ret {}; @@ -273,11 +273,11 @@ ALWAYS_INLINE inline uint64_t TSTypeCall(Method *method, uint8_t *args, uint8_t napi_value js_this; if constexpr (IS_STATIC) { js_this = TSTypeNamespace::FromBoundMethod(method)->ResolveValue(env); - NAPI_FATAL_IF(js_this == nullptr); + INTEROP_FATAL_IF(js_this == nullptr); } else { auto value = arg_reader.Read(); auto klass = method->GetClass(); - NAPI_FATAL_IF(klass->GetBase() != ctx->JSValueClass()); + INTEROP_FATAL_IF(klass->GetBase() != ctx->GetJSValueClass()); js_this = JSConvertJSValue::Wrap(env, JSValue::FromEtsObject(EtsObject::FromCoreType(value))); ++it; // this } @@ -303,7 +303,7 @@ ALWAYS_INLINE inline uint64_t TSTypeCall(Method *method, uint8_t *args, uint8_t js_val = JSConvertString::Wrap(env, EtsString::FromEtsObject(EtsObject::FromCoreType(value))); break; } - if (klass == ctx->JSValueClass() || klass->GetBase() == ctx->JSValueClass()) { + if (klass == ctx->GetJSValueClass() || klass->GetBase() == ctx->GetJSValueClass()) { js_val = JSConvertJSValue::Wrap(env, JSValue::FromEtsObject(EtsObject::FromCoreType(value))); break; } @@ -322,7 +322,7 @@ ALWAYS_INLINE inline uint64_t TSTypeCall(Method *method, uint8_t *args, uint8_t // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) TSTYPE_DLOG_A("get method %s", method_name); NAPI_CHECK_FATAL(napi_get_named_property(env, js_this, method_name, &js_fn)); - NAPI_FATAL_IF(IsUndefined(env, js_fn)); + INTEROP_FATAL_IF(IsUndefined(env, js_fn)); NAPI_CHECK_FATAL(napi_call_function(env, js_this, js_fn, jsargs.size(), jsargs.data(), &js_ret)); } @@ -351,7 +351,7 @@ ALWAYS_INLINE inline uint64_t TSTypeCall(Method *method, uint8_t *args, uint8_t ets_ret = panda::Value(res.value()->GetCoreType()); break; } - if (klass == ctx->JSValueClass() || klass->GetBase() == ctx->JSValueClass()) { + if (klass == ctx->GetJSValueClass() || klass->GetBase() == ctx->GetJSValueClass()) { if (UNLIKELY(!klass->IsInitialized())) { class_linker->InitializeClass(coro, klass); } @@ -379,7 +379,7 @@ static typename T::cpptype TSTypeGetter([[maybe_unused]] panda::Method *method, // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) auto fname = utf::Mutf8AsCString(method->GetName().data + METHOD_PREFIX_LEN); auto res = JSValueGetByName(ctx, jsvalue, fname); - NAPI_FATAL_IF(!res); + INTEROP_FATAL_IF(!res); return res.value(); } diff --git a/plugins/ets/stdlib/std/core/Error.ets b/plugins/ets/stdlib/std/core/Error.ets index 04a72cfbf..d435e1261 100644 --- a/plugins/ets/stdlib/std/core/Error.ets +++ b/plugins/ets/stdlib/std/core/Error.ets @@ -95,4 +95,13 @@ export class Error { return this.cause; } } + + /** + * Returns the message of this error + * + * @returns String - the message + */ + public getMessage(): String { + return this.msg; + } } diff --git a/plugins/ets/stdlib/std/interop/js/JSException.ets b/plugins/ets/stdlib/std/interop/js/JSError.ets similarity index 95% rename from plugins/ets/stdlib/std/interop/js/JSException.ets rename to plugins/ets/stdlib/std/interop/js/JSError.ets index 2fb44e716..aadc95a4c 100644 --- a/plugins/ets/stdlib/std/interop/js/JSException.ets +++ b/plugins/ets/stdlib/std/interop/js/JSError.ets @@ -15,7 +15,7 @@ package std.interop.js; -export final class JSException extends Exception { +export final class JSError extends Error { private value: JSValue; constructor() { diff --git a/plugins/ets/tests/interop_js/tests/test_intrins/frontend_test_intrins.ets b/plugins/ets/tests/interop_js/tests/test_intrins/frontend_test_intrins.ets index 687549df1..882943c95 100644 --- a/plugins/ets/tests/interop_js/tests/test_intrins/frontend_test_intrins.ets +++ b/plugins/ets/tests/interop_js/tests/test_intrins/frontend_test_intrins.ets @@ -24,16 +24,23 @@ import { TestProxy, StringifyArgs, GetProp, - Nop + Nop, + Nonexisting } from "/plugins/ets/tests/interop_js/tests/test_intrins/test_intrins.js"; function vstringify(val: JSValue): String { return StringifyValue(val) as String; } -function unreachable(): Exception { +function unreachable(): Error { Log("unreachable reached!"); - return new Exception("unreachable"); + return new Error("unreachable"); } +function throwmsg(msg: String): boolean { + throw new Error(msg); +} +class BadClass { + static _init: boolean = throwmsg("error from BadClass static ctor"); +}; function js_sum_wrapper_num_num(a: double, b: double): double { return Sum(a, b); @@ -97,7 +104,7 @@ function test_string_ops(): boolean { function test_builtin_array_any(): boolean { let v: JSValue[] = [JSRuntime.getUndefined(), JSRuntime.newJSValueDouble(1.23), JSRuntime.newJSValueString("foo"), JSRuntime.createObject()]; let r = "object:,1.23,foo,[object Object]"; - // v = Identity(Identity(v)) as JSValue[]; // TODO(vpukhov): trigger cast, apply to other tests + // v = Identity(Identity(v)) as JSValue[]; // TODO(vpukhov): jscast let v1 = Identity(Identity(v)); return r == vstringify(Identity(v1)); } @@ -136,6 +143,10 @@ function test_builtin_array_instanceof(): boolean { let ctor = JSRuntime.getGlobal().Array; return JSRuntime.instanceOf(Identity(v), ctor); } +function test_init_array_component(): boolean { + // TODO(vpukhov): relies on jscast BadClass[] + return true; +} function test_named_access(): boolean { let v: double = 12.34 @@ -190,33 +201,31 @@ function test_lambda_proxy_recursive(): boolean { return res == "123,foo"; } -function test_exception_forwarding_fromjs(): boolean throws { +function test_exception_forwarding_fromjs(): boolean { try { ThrowValue(123.321); - throw unreachable(); - } catch (e: JSException) { + } catch (e: JSError) { return "number:123.321" == vstringify(e.getValue()); } throw unreachable(); } -function test_exception_forwarding_fromets(): boolean throws { - let etsfn: (v: JSValue) => void throws = (v: JSValue): void throws => { - throw new JSException(v); +function test_exception_forwarding_fromets(): boolean { + let etsfn: (v: JSValue) => void = (v: JSValue): void => { + throw new JSError(v); } let jsfn = JSRuntime.__createLambdaProxy(etsfn as Object); try { ApplyArgs(jsfn, 12345.6); - throw unreachable(); - } catch (e: JSException) { + } catch (e: JSError) { return "number:12345.6" == vstringify(e.getValue()); } throw unreachable(); } -function test_exception_forwarding_recursive(): boolean throws { - let etsfn: (v: JSValue) => void throws = (v: JSValue): void throws => { +function test_exception_forwarding_recursive(): boolean { + let etsfn: (v: JSValue) => void = (v: JSValue): void => { ThrowValue(123.456); throw unreachable(); } @@ -224,68 +233,91 @@ function test_exception_forwarding_recursive(): boolean throws { try { ApplyArgs(jsfn, 123.456); - throw unreachable(); - } catch (e: JSException) { + } catch (e: JSError) { return "number:123.456" == vstringify(e.getValue()); } throw unreachable(); } -function test_typecheck_getprop(): boolean throws { +function test_typecheck_getprop(): boolean { let obj = JSRuntime.createObject(); obj.prop = 1.23; try { obj.prop as String; - throw unreachable(); - } catch (e: JSException) { - return "string:String expected" == vstringify(e.getValue()); + } catch (e: JSError) { + return "object:TypeError: String expected" == vstringify(e.getValue()); } throw unreachable(); } -function test_typecheck_jscall(): boolean throws { +function test_typecheck_jscall(): boolean { try { Identity(123) as String; - throw unreachable(); - } catch (e: JSException) { - return "string:String expected" == vstringify(e.getValue()); + } catch (e: JSError) { + return "object:TypeError: String expected" == vstringify(e.getValue()); } throw unreachable(); } -function test_typecheck_etscall(): boolean throws { +function test_typecheck_etscall(): boolean { try { - let etsfn: (x: String) => String throws = (x: String): String throws => { + let etsfn: (x: String) => String = (x: String): String => { throw unreachable(); } let jsfn = JSRuntime.__createLambdaProxy(etsfn as Object); ApplyArgs(jsfn, 123); - throw unreachable(); - } catch (e: JSException) { - return "string:String expected" == vstringify(e.getValue()); + } catch (e: JSError) { + return "object:TypeError: String expected" == vstringify(e.getValue()); } throw unreachable(); } -function test_get_throws(): boolean throws { +function test_get_throws(): boolean { let obj = new TestProxy(); try { - obj.foo as String; - throw unreachable(); - } catch (e: JSException) { - return "string:get exception" == vstringify(e.getValue()); + obj.foo as double; + } catch (e: JSError) { + return "object:Error: get exception" == vstringify(e.getValue()); } throw unreachable(); } -function test_set_throws(): boolean throws { +function test_set_throws(): boolean { let obj = new TestProxy(); try { obj.foo = 123; throw unreachable(); - } catch (e: JSException) { - return "string:set exception" == vstringify(e.getValue()); + } catch (e: JSError) { + return "object:Error: set exception" == vstringify(e.getValue()); + } + throw unreachable(); +} + +function test_jscall_resolution_throws1(): boolean { + try { + Nonexisting(); + } catch (e: JSError) { + return "object:TypeError: call target is not a function" == vstringify(e.getValue()); + } + throw unreachable(); +} + +function test_jscall_resolution_throws2(): boolean { + try { + Nonexisting.Nonexisting(); + } catch (e: JSError) { + return "object:TypeError: Cannot convert undefined or null to object" == vstringify(e.getValue()); + } + throw unreachable(); +} + +function test_jscall_resolution_throws3(): boolean { + let obj = new TestProxy(); + try { + obj.foo(); + } catch (e: JSError) { + return "object:Error: get exception" == vstringify(e.getValue()); } throw unreachable(); } @@ -300,7 +332,7 @@ function gc_jsruntime_cleanup(): void { } } -function test_finalizers(): boolean throws { +function test_finalizers(): boolean { let workload = 128; let repeats = 4; diff --git a/plugins/ets/tests/interop_js/tests/test_intrins/test_intrins.cpp b/plugins/ets/tests/interop_js/tests/test_intrins/test_intrins.cpp index c5fa03533..9f77c049d 100644 --- a/plugins/ets/tests/interop_js/tests/test_intrins/test_intrins.cpp +++ b/plugins/ets/tests/interop_js/tests/test_intrins/test_intrins.cpp @@ -72,6 +72,7 @@ TEST_F(EtsInteropJsIntrinsTest, test_builtin_array_convertors) ASSERT_EQ(true, CallEtsMethod("test_builtin_array_string")); ASSERT_EQ(true, CallEtsMethod("test_builtin_array_multidim")); ASSERT_EQ(true, CallEtsMethod("test_builtin_array_instanceof")); + ASSERT_EQ(true, CallEtsMethod("test_init_array_component")); } TEST_F(EtsInteropJsIntrinsTest, test_named_access) @@ -118,6 +119,9 @@ TEST_F(EtsInteropJsIntrinsTest, test_exception_forwarding) ASSERT_EQ(true, CallEtsMethod("test_exception_forwarding_fromjs")); ASSERT_EQ(true, CallEtsMethod("test_exception_forwarding_fromets")); ASSERT_EQ(true, CallEtsMethod("test_exception_forwarding_recursive")); + + // TODO(vpukhov): ets_proxy exceptions forwarding + // ASSERT_EQ(true, CallEtsMethod("test_core_error_forwarding")); } TEST_F(EtsInteropJsIntrinsTest, test_typechecks) @@ -133,6 +137,9 @@ TEST_F(EtsInteropJsIntrinsTest, test_accessor_throws) LoadJSTestMoudle(); ASSERT_EQ(true, CallEtsMethod("test_get_throws")); ASSERT_EQ(true, CallEtsMethod("test_set_throws")); + ASSERT_EQ(true, CallEtsMethod("test_jscall_resolution_throws1")); + ASSERT_EQ(true, CallEtsMethod("test_jscall_resolution_throws2")); + ASSERT_EQ(true, CallEtsMethod("test_jscall_resolution_throws3")); } TEST_F(EtsInteropJsIntrinsTest, test_finalizers) diff --git a/plugins/ets/tests/interop_js/tests/test_intrins/test_intrins.ets b/plugins/ets/tests/interop_js/tests/test_intrins/test_intrins.ets index 2ee0a32d5..3b266c6f7 100644 --- a/plugins/ets/tests/interop_js/tests/test_intrins/test_intrins.ets +++ b/plugins/ets/tests/interop_js/tests/test_intrins/test_intrins.ets @@ -25,6 +25,7 @@ class jscall { // Remove after JSValue cast fix: signature type suffixes + native static void$(top: JSValue, qname: String): void; native static void$a(top: JSValue, qname: String, v0: JSValue): void; native static void$an(top: JSValue, qname: String, a0: JSValue, a1: double): void; native static void$n(top: JSValue, qname: String, v0: double): void; @@ -67,6 +68,8 @@ class jscall { native static AAn$a(top: JSValue, qname: String, v0: JSValue): double[][]; native static As$a(top: JSValue, qname: String, v0: JSValue): String[]; + native static ALBadClass$a(top: JSValue, qname: String, v0: JSValue): BadClass[]; + native static num$vn(fn: JSValue, rec: JSValue, a0: double): double; }; @@ -85,10 +88,16 @@ function vlog(val: JSValue): void { function vlog(val: String): void { jscall.void$s(jsvars.m, ".Log", val); } -function unreachable(): Exception { +function unreachable(): Error { vlog("unreachable reached!"); - return new Exception("unreachable"); + return new Error("unreachable"); +} +function throwmsg(msg: String): boolean { + throw new Error(msg); } +class BadClass { + static _init: boolean = throwmsg("error from BadClass static ctor"); +}; function js_sum_wrapper_num_num(a: double, b: double): double { return jscall.num$nn(jsvars.m, ".Sum", a, b); @@ -190,6 +199,11 @@ function test_builtin_array_instanceof(): boolean { let ctor = JSRuntime.getPropertyJSValue(jsvars.global, "Array"); return JSRuntime.instanceOf(jscall.any$Ai(jsvars.m, ".Identity", v), ctor); } +function test_init_array_component(): boolean { + let v: JSValue[] = [JSRuntime.getNull()]; + let x = jscall.ALBadClass$a(jsvars.m, ".Identity", jscall.any$Aa(jsvars.m, ".Identity", v)); + return true; +} function test_named_access(): boolean { let v: double = 12.34 @@ -245,33 +259,31 @@ function test_lambda_proxy_recursive(): boolean { return res == "123,foo"; } -function test_exception_forwarding_fromjs(): boolean throws { +function test_exception_forwarding_fromjs(): boolean { try { jscall.void$n(jsvars.m, ".ThrowValue", 123.321); - throw unreachable(); - } catch (e: JSException) { + } catch (e: JSError) { return "number:123.321" == vstringify(e.getValue()); } throw unreachable(); } -function test_exception_forwarding_fromets(): boolean throws { - let etsfn: (v: JSValue) => void throws = (v: JSValue): void throws => { - throw new JSException(v); +function test_exception_forwarding_fromets(): boolean { + let etsfn: (v: JSValue) => void = (v: JSValue): void => { + throw new JSError(v); } let jsfn = JSRuntime.__createLambdaProxy(etsfn as Object); try { jscall.void$an(jsvars.m, ".ApplyArgs", jsfn, 12345.6); - throw unreachable(); - } catch (e: JSException) { + } catch (e: JSError) { return "number:12345.6" == vstringify(e.getValue()); } throw unreachable(); } -function test_exception_forwarding_recursive(): boolean throws { - let etsfn: (v: JSValue) => void throws = (v: JSValue): void throws => { +function test_exception_forwarding_recursive(): boolean { + let etsfn: (v: JSValue) => void = (v: JSValue): void => { jscall.void$n(jsvars.m, ".ThrowValue", 123.456); throw unreachable(); } @@ -279,68 +291,104 @@ function test_exception_forwarding_recursive(): boolean throws { try { jscall.void$an(jsvars.m, ".ApplyArgs", jsfn, 123.456); - throw unreachable(); - } catch (e: JSException) { + } catch (e: JSError) { return "number:123.456" == vstringify(e.getValue()); } throw unreachable(); } -function test_typecheck_getprop(): boolean throws { +function test_core_error_forwarding(): boolean { + let etsfn: () => void = (): void => { + throw new Error("funny message"); + } + let jsfn = JSRuntime.__createLambdaProxy(etsfn as Object); + + try { + jscall.void$a(jsvars.m, ".ApplyArgs", jsfn); + } catch (e: Error) { + return e.getMessage() == "funny message"; + } + throw unreachable(); +} + +function test_typecheck_getprop(): boolean { let obj = JSRuntime.createObject(); JSRuntime.setPropertyDouble(obj, "prop", 1.23); try { JSRuntime.getPropertyString(obj, "prop"); - throw unreachable(); - } catch (e: JSException) { - return "string:String expected" == vstringify(e.getValue()); + } catch (e: JSError) { + return "object:TypeError: String expected" == vstringify(e.getValue()); } throw unreachable(); } -function test_typecheck_jscall(): boolean throws { +function test_typecheck_jscall(): boolean { try { jscall.str$n(jsvars.m, ".Identity", 123); - throw unreachable(); - } catch (e: JSException) { - return "string:String expected" == vstringify(e.getValue()); + } catch (e: JSError) { + return "object:TypeError: String expected" == vstringify(e.getValue()); } throw unreachable(); } -function test_typecheck_etscall(): boolean throws { +function test_typecheck_etscall(): boolean { try { - let etsfn: (x: String) => String throws = (x: String): String throws => { + let etsfn: (x: String) => String = (x: String): String => { throw unreachable(); } let jsfn = JSRuntime.__createLambdaProxy(etsfn as Object); jscall.num$an(jsvars.m, ".ApplyArgs", jsfn, 123); - throw unreachable(); - } catch (e: JSException) { - return "string:String expected" == vstringify(e.getValue()); + } catch (e: JSError) { + return "object:TypeError: String expected" == vstringify(e.getValue()); } throw unreachable(); } -function test_get_throws(): boolean throws { +function test_get_throws(): boolean { let obj = jsnew.$(jsvars.m, ".TestProxy"); try { JSRuntime.getPropertyDouble(obj, "foo"); - throw unreachable(); - } catch (e: JSException) { - return "string:get exception" == vstringify(e.getValue()); + } catch (e: JSError) { + return "object:Error: get exception" == vstringify(e.getValue()); } throw unreachable(); } -function test_set_throws(): boolean throws { +function test_set_throws(): boolean { let obj = jsnew.$(jsvars.m, ".TestProxy"); try { JSRuntime.setPropertyDouble(obj, "foo", 123); - throw unreachable(); - } catch (e: JSException) { - return "string:set exception" == vstringify(e.getValue()); + } catch (e: JSError) { + return "object:Error: set exception" == vstringify(e.getValue()); + } + throw unreachable(); +} + +function test_jscall_resolution_throws1(): boolean { + try { + jscall.void$(jsvars.m, ".Nonexisting"); + } catch (e: JSError) { + return "object:TypeError: call target is not a function" == vstringify(e.getValue()); + } + throw unreachable(); +} + +function test_jscall_resolution_throws2(): boolean { + try { + jscall.void$(jsvars.m, ".Nonexisting.Nonexsiting"); + } catch (e: JSError) { + return "object:TypeError: Cannot convert undefined or null to object" == vstringify(e.getValue()); + } + throw unreachable(); +} + +function test_jscall_resolution_throws3(): boolean { + let obj = jsnew.$(jsvars.m, ".TestProxy"); + try { + jscall.void$(obj, ".foo"); + } catch (e: JSError) { + return "object:Error: get exception" == vstringify(e.getValue()); } throw unreachable(); } @@ -355,7 +403,7 @@ function gc_jsruntime_cleanup(): void { } } -function test_finalizers(): boolean throws { +function test_finalizers(): boolean { let workload = 128; let repeats = 4; diff --git a/plugins/ets/tests/interop_js/tests/test_intrins/test_intrins.js b/plugins/ets/tests/interop_js/tests/test_intrins/test_intrins.js index 9286dbba9..7d7dda7ba 100644 --- a/plugins/ets/tests/interop_js/tests/test_intrins/test_intrins.js +++ b/plugins/ets/tests/interop_js/tests/test_intrins/test_intrins.js @@ -14,6 +14,9 @@ */ "use strict"; +let etsVm = gtest.etsVm; +exports.etsVm = etsVm; + exports.Nop = function () { } exports.Identity = function (v) { return v } exports.StringifyValue = function (v) { return typeof v + ":" + v } @@ -29,7 +32,7 @@ exports.Car = function (v) { this.color = v } exports.TestProxy = function () { Object.defineProperty(this, "foo", { - get: function () { throw "get exception" }, - set: function () { throw "set exception" } + get: function () { throw Error("get exception") }, + set: function () { throw Error("set exception") } }); } diff --git a/runtime/mem/local_object_handle.h b/runtime/mem/local_object_handle.h index a456c898a..3618dbf3e 100644 --- a/runtime/mem/local_object_handle.h +++ b/runtime/mem/local_object_handle.h @@ -31,6 +31,8 @@ public: thread_->PushLocalObject(&root_); } + // Disabled when T is ObjectHeader, delayed instantiation + template >> explicit LocalObjectHandle(ManagedThread *thread, T *object) : LocalObjectHandle(thread, reinterpret_cast(object)) { @@ -56,6 +58,11 @@ public: return GetPtr(); } + inline uintptr_t GetAddress() const + { + return ToUintPtr(&root_); + } + private: ObjectHeader *root_ {}; ManagedThread *thread_ {}; diff --git a/runtime/mem/vm_handle-inl.h b/runtime/mem/vm_handle-inl.h new file mode 100644 index 000000000..fdfb84d8c --- /dev/null +++ b/runtime/mem/vm_handle-inl.h @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef RUNTIME_MEM_OBJECT_VMHANDLE_INL_H +#define RUNTIME_MEM_OBJECT_VMHANDLE_INL_H + +#include "runtime/mem/vm_handle.h" +#include "runtime/mem/local_object_handle.h" +#include "runtime/mem/refstorage/global_object_storage.h" + +namespace panda { + +template +template +inline VMHandle::VMHandle(const LocalObjectHandle

&other) : HandleBase(other.GetAddress()) +{ +} + +template +inline VMHandle::VMHandle(mem::GlobalObjectStorage *global_storage, mem::Reference *reference) + : HandleBase(global_storage->GetAddressForRef(reference)) +{ + ASSERT(reference != nullptr); +} + +} // namespace panda + +#endif // RUNTIME_MEM_OBJECT_VMHANDLE_INL_H diff --git a/runtime/mem/vm_handle.h b/runtime/mem/vm_handle.h index fb496b8dc..3a9946584 100644 --- a/runtime/mem/vm_handle.h +++ b/runtime/mem/vm_handle.h @@ -25,6 +25,9 @@ namespace panda { using TaggedType = coretypes::TaggedType; using TaggedValue = coretypes::TaggedValue; +template +class LocalObjectHandle; + // VMHandle should be used in language-agnostic part of runtime template class VMHandle : public HandleBase { @@ -42,9 +45,14 @@ public: } } + template >> + inline explicit VMHandle(const LocalObjectHandle

&other); + + inline explicit VMHandle(mem::GlobalObjectStorage *global_storage, mem::Reference *reference); + ~VMHandle() = default; - NO_COPY_SEMANTIC(VMHandle); + DEFAULT_COPY_SEMANTIC(VMHandle); DEFAULT_NOEXCEPT_MOVE_SEMANTIC(VMHandle); T *GetPtr() const -- Gitee