From 17c8e3c96addeb595405e5e647eb63d0c79e8554 Mon Sep 17 00:00:00 2001 From: dingbihan Date: Thu, 22 May 2025 22:08:32 +0800 Subject: [PATCH 1/2] Get Ets Module Issue: https://gitee.com/openharmony/arkcompiler_runtime_core/issues/ICA6L7 Signed-off-by: dingbihan Change-Id: Ie6721731c4f2d9e611c875010dbd3c5ffa19ba8f --- .../ets/runtime/ets_panda_file_items.h | 1 + static_core/plugins/ets/runtime/ets_utils.cpp | 43 ++++++ static_core/plugins/ets/runtime/ets_utils.h | 6 +- .../interop_js/ets_proxy/ets_proxy.cpp | 130 +++++++++++++++++- .../runtime/interop_js/ets_proxy/ets_proxy.h | 1 + .../ets/runtime/interop_js/ets_vm_plugin.cpp | 17 +++ .../ets/runtime/interop_js/interop_common.h | 2 + .../napi_impl/detail/enumerate_napi.h | 1 + .../interop_js/napi_impl/napi_impl.cpp | 8 ++ .../tests/dynamicimport/CMakeLists.txt | 14 ++ .../dynamicimport/ets_to_ts/CMakeLists.txt | 18 +++ .../ets_to_ts/dynamic-import.cpp | 28 ++++ .../dynamicimport/ets_to_ts/dynamic-import.ts | 114 +++++++++++++++ .../dynamicimport/ets_to_ts/export12.ets | 25 ++++ .../tests/getEtsModule/CMakeLists.txt | 14 ++ .../getEtsModule/ets_to_ts/CMakeLists.txt | 22 +++ .../getEtsModule/ets_to_ts/entry_module.ets | 16 +++ .../getEtsModule/ets_to_ts/entry_module2.ets | 16 +++ .../getEtsModule/ets_to_ts/get_module.cpp | 28 ++++ .../getEtsModule/ets_to_ts/get_module.ets | 32 +++++ .../getEtsModule/ets_to_ts/test_get_module.ts | 45 ++++++ 21 files changed, 576 insertions(+), 5 deletions(-) create mode 100644 static_core/plugins/ets/tests/interop_js/tests/dynamicimport/CMakeLists.txt create mode 100644 static_core/plugins/ets/tests/interop_js/tests/dynamicimport/ets_to_ts/CMakeLists.txt create mode 100644 static_core/plugins/ets/tests/interop_js/tests/dynamicimport/ets_to_ts/dynamic-import.cpp create mode 100644 static_core/plugins/ets/tests/interop_js/tests/dynamicimport/ets_to_ts/dynamic-import.ts create mode 100644 static_core/plugins/ets/tests/interop_js/tests/dynamicimport/ets_to_ts/export12.ets create mode 100644 static_core/plugins/ets/tests/interop_js/tests/getEtsModule/CMakeLists.txt create mode 100644 static_core/plugins/ets/tests/interop_js/tests/getEtsModule/ets_to_ts/CMakeLists.txt create mode 100644 static_core/plugins/ets/tests/interop_js/tests/getEtsModule/ets_to_ts/entry_module.ets create mode 100644 static_core/plugins/ets/tests/interop_js/tests/getEtsModule/ets_to_ts/entry_module2.ets create mode 100644 static_core/plugins/ets/tests/interop_js/tests/getEtsModule/ets_to_ts/get_module.cpp create mode 100644 static_core/plugins/ets/tests/interop_js/tests/getEtsModule/ets_to_ts/get_module.ets create mode 100644 static_core/plugins/ets/tests/interop_js/tests/getEtsModule/ets_to_ts/test_get_module.ts diff --git a/static_core/plugins/ets/runtime/ets_panda_file_items.h b/static_core/plugins/ets/runtime/ets_panda_file_items.h index 4bf9ab0ff1..c84b6e8e3e 100644 --- a/static_core/plugins/ets/runtime/ets_panda_file_items.h +++ b/static_core/plugins/ets/runtime/ets_panda_file_items.h @@ -208,6 +208,7 @@ static constexpr std::string_view ANI_UNSAFE_DIRECT = "Lstd/a // Module annotation class static constexpr std::string_view ANNOTATION_MODULE = "Lets/annotation/Module;"; +static constexpr std::string_view ANNOTATION_MODULE_EXPORTED = "exported"; // Interface object literal annotation class static constexpr std::string_view INTERFACE_OBJ_LITERAL = "Lstd/annotations/InterfaceObjectLiteral;"; diff --git a/static_core/plugins/ets/runtime/ets_utils.cpp b/static_core/plugins/ets/runtime/ets_utils.cpp index ec626c0a3c..00922377e9 100644 --- a/static_core/plugins/ets/runtime/ets_utils.cpp +++ b/static_core/plugins/ets/runtime/ets_utils.cpp @@ -83,4 +83,47 @@ EtsField *ManglingUtils::GetFieldIDByDisplayName(EtsClass *klass, const PandaStr return field; } + +static void ExtractClassDescriptorsFromArray(const panda_file::File *pfile, const panda_file::ArrayValue &classesArray, + std::vector &outDescriptors) +{ + const uint32_t valCount = classesArray.GetCount(); + outDescriptors.resize(valCount); + for (uint32_t j = 0; j < valCount; j++) { + auto entityId = classesArray.Get(j); + panda_file::ClassDataAccessor classData(*pfile, entityId); + outDescriptors[j] = utf::Mutf8AsCString(classData.GetDescriptor()); + } +} + +// NOLINT(clang-analyzer-core) // false positive: this is a function, not a global var +bool GetExportedClassDescriptorsFromModule(ark::ets::EtsClass *etsGlobalClass, std::vector &outDescriptors) +{ + ASSERT(etsGlobalClass != nullptr); + + const auto *runtimeClass = etsGlobalClass->GetRuntimeClass(); + const panda_file::File *pfile = runtimeClass->GetPandaFile(); + panda_file::ClassDataAccessor cda(*pfile, runtimeClass->GetFileId()); + + bool found = false; + cda.EnumerateAnnotation(panda_file_items::class_descriptors::ANNOTATION_MODULE.data(), + [&outDescriptors, &found, pfile](panda_file::AnnotationDataAccessor &annotationAccessor) { + const uint32_t count = annotationAccessor.GetCount(); + for (uint32_t i = 0; i < count; i++) { + auto elem = annotationAccessor.GetElement(i); + std::string nameStr = + utf::Mutf8AsCString(pfile->GetStringData(elem.GetNameId()).data); + if (nameStr == panda_file_items::class_descriptors::ANNOTATION_MODULE_EXPORTED) { + auto classesArray = elem.GetArrayValue(); + ExtractClassDescriptorsFromArray(pfile, classesArray, outDescriptors); + found = true; + break; + } + } + return true; + }); + + return found; +} + } // namespace ark::ets diff --git a/static_core/plugins/ets/runtime/ets_utils.h b/static_core/plugins/ets/runtime/ets_utils.h index e809ed75da..4f42041732 100644 --- a/static_core/plugins/ets/runtime/ets_utils.h +++ b/static_core/plugins/ets/runtime/ets_utils.h @@ -53,6 +53,10 @@ private: ManglingUtils() = default; ~ManglingUtils() = default; }; + +PANDA_PUBLIC_API bool GetExportedClassDescriptorsFromModule(EtsClass *etsGlobalClass, + std::vector &outDescriptors); + } // namespace ark::ets -#endif // PANDA_PLUGINS_ETS_RUNTIME_ETS_UTILS_H +#endif // PANDA_PLUGINS_ETS_RUNTIME_ETS_UTILS_H \ No newline at end of file diff --git a/static_core/plugins/ets/runtime/interop_js/ets_proxy/ets_proxy.cpp b/static_core/plugins/ets/runtime/interop_js/ets_proxy/ets_proxy.cpp index 01836e7f9b..65fff7d1ea 100644 --- a/static_core/plugins/ets/runtime/interop_js/ets_proxy/ets_proxy.cpp +++ b/static_core/plugins/ets/runtime/interop_js/ets_proxy/ets_proxy.cpp @@ -15,6 +15,7 @@ #include "plugins/ets/runtime/interop_js/ets_proxy/ets_proxy.h" +#include "interfaces/inner_api/napi/native_node_api.h" #include "plugins/ets/runtime/ets_panda_file_items.h" #include "plugins/ets/runtime/ets_utils.h" #include "plugins/ets/runtime/interop_js/code_scopes.h" @@ -56,16 +57,14 @@ napi_value GetETSFunction(napi_env env, std::string_view packageName, std::strin return jsMethod; } -napi_value GetETSClass(napi_env env, std::string_view classDescriptor) +napi_value GetETSClassImpl(napi_env env, std::string_view classDescriptor) { EtsCoroutine *coro = EtsCoroutine::GetCurrent(); InteropCtx *ctx = InteropCtx::Current(coro); - INTEROP_CODE_SCOPE_JS(coro); - ScopedManagedCodeThread managedScope(coro); EtsClass *etsKlass = coro->GetPandaVM()->GetClassLinker()->GetClass(classDescriptor.data(), true, ctx->LinkerCtx()); if (UNLIKELY(etsKlass == nullptr)) { - InteropCtx::ThrowJSError(env, "GetETSClass: unresolved klass " + std::string(classDescriptor)); + InteropCtx::ThrowJSError(env, "GetETSClassImpl: unresolved klass " + std::string(classDescriptor)); return nullptr; } @@ -109,4 +108,127 @@ napi_value GetETSInstance(napi_env env, std::string_view classDescriptor) return nullptr; } +napi_value GetETSClass(napi_env env, std::string_view classDescriptor) +{ + EtsCoroutine *coro = EtsCoroutine::GetCurrent(); + INTEROP_CODE_SCOPE_JS(coro); + ScopedManagedCodeThread managedScope(coro); + + return GetETSClassImpl(env, classDescriptor); +} + +static void FillExportedClasses(napi_env env, EtsClass *globalClass, napi_value moduleObject) +{ + std::vector exportedClasses; + if (!GetExportedClassDescriptorsFromModule(globalClass, exportedClasses)) { + return; + } + + EtsCoroutine *coro = EtsCoroutine::GetCurrent(); + InteropCtx *ctx = InteropCtx::Current(coro); + ClassLinkerContext *ctxForLoad = globalClass->GetLoadContext(); + auto *classLinker = coro->GetPandaVM()->GetClassLinker(); + + for (const std::string &clsDesc : exportedClasses) { + EtsClass *exportedKlass = classLinker->GetClass(clsDesc.c_str(), true, ctxForLoad); + if (exportedKlass == nullptr) { + LOG(WARNING, INTEROP) << "Failed to resolve exported class: " << clsDesc; + continue; + } + + EtsClassWrapper *wrapper = EtsClassWrapper::Get(ctx, exportedKlass); + if (wrapper == nullptr) { + LOG(WARNING, INTEROP) << "Failed to get wrapper for exported class: " << clsDesc; + continue; + } + + napi_value clsProxy = wrapper->GetJsCtor(env); + + std::string simpleName = clsDesc.substr(clsDesc.find_last_of('/') + 1); + if (!simpleName.empty() && simpleName.back() == ';') { + simpleName.pop_back(); + } + + NAPI_CHECK_FATAL(napi_set_named_property(env, moduleObject, simpleName.c_str(), clsProxy)); + } +} + +static void CopyNamedProperties(napi_env env, napi_value from, napi_value to) +{ + auto *coro = EtsCoroutine::GetCurrent(); + ScopedNativeCodeThread etsNativeScope(coro); + napi_value keys; + NAPI_CHECK_FATAL(napi_get_property_names(env, from, &keys)); + uint32_t len; + NAPI_CHECK_FATAL(napi_get_array_length(env, keys, &len)); + for (uint32_t i = 0; i < len; i++) { + napi_value key; + NAPI_CHECK_FATAL(napi_get_element(env, keys, i, &key)); + napi_value val; + NAPI_CHECK_FATAL(napi_get_property(env, from, key, &val)); + NAPI_CHECK_FATAL(napi_set_property(env, to, key, val)); + } +} + +static void ProcessModuleRecursive(napi_env env, EtsClass *globalClass, napi_value moduleObject, + std::unordered_set &visitedModules) +{ + if (visitedModules.count(globalClass) > 0) { + return; + } + visitedModules.insert(globalClass); + + EtsCoroutine *coro = EtsCoroutine::GetCurrent(); + InteropCtx *ctx = InteropCtx::Current(coro); + + napi_value globalProxy = EtsClassWrapper::Get(ctx, globalClass)->GetJsCtor(env); + FillExportedClasses(env, globalClass, moduleObject); + CopyNamedProperties(env, globalProxy, moduleObject); + + std::vector exportedClasses; + if (!GetExportedClassDescriptorsFromModule(globalClass, exportedClasses)) { + return; + } + + auto *classLinker = coro->GetPandaVM()->GetClassLinker(); + auto *ctxForLoad = globalClass->GetLoadContext(); + + for (const auto &clsDesc : exportedClasses) { + EtsClass *exportedKlass = classLinker->GetClass(clsDesc.c_str(), true, ctxForLoad); + if (exportedKlass == nullptr) { + continue; + } + + if (exportedKlass->IsModule()) { + ProcessModuleRecursive(env, exportedKlass, moduleObject, visitedModules); + } + } +} + +napi_value GetETSModule(napi_env env, const std::string &moduleName) +{ + EtsCoroutine *coro = EtsCoroutine::GetCurrent(); + if (coro == nullptr) { + napi_value val; + NAPI_CHECK_FATAL(napi_get_hole(env, &val)); + return val; + } + ScopedManagedCodeThread managedScope(coro); + InteropCtx *ctx = InteropCtx::Current(coro); + + std::string descriptor = "L" + moduleName + "/ETSGLOBAL;"; + EtsClass *globalClass = coro->GetPandaVM()->GetClassLinker()->GetClass(descriptor.c_str(), true, ctx->LinkerCtx()); + if (globalClass == nullptr) { + InteropCtx::ThrowJSError(env, "Failed to resolve module class: " + descriptor); + return nullptr; + } + + napi_value moduleObject; + NAPI_CHECK_FATAL(napi_create_object(env, &moduleObject)); + + std::unordered_set visitedModules; + ProcessModuleRecursive(env, globalClass, moduleObject, visitedModules); + return moduleObject; +} + } // namespace ark::ets::interop::js::ets_proxy diff --git a/static_core/plugins/ets/runtime/interop_js/ets_proxy/ets_proxy.h b/static_core/plugins/ets/runtime/interop_js/ets_proxy/ets_proxy.h index 4f9f0b2992..c938fc499f 100644 --- a/static_core/plugins/ets/runtime/interop_js/ets_proxy/ets_proxy.h +++ b/static_core/plugins/ets/runtime/interop_js/ets_proxy/ets_proxy.h @@ -26,6 +26,7 @@ namespace ark::ets::interop::js::ets_proxy { PANDA_PUBLIC_API napi_value GetETSFunction(napi_env env, std::string_view packageName, std::string_view methodName); PANDA_PUBLIC_API napi_value GetETSClass(napi_env env, std::string_view classDescriptor); PANDA_PUBLIC_API napi_value GetETSInstance(napi_env env, std::string_view classDescriptor); +PANDA_PUBLIC_API napi_value GetETSModule(napi_env env, const std::string &moduleName); } // namespace ark::ets::interop::js::ets_proxy diff --git a/static_core/plugins/ets/runtime/interop_js/ets_vm_plugin.cpp b/static_core/plugins/ets/runtime/interop_js/ets_vm_plugin.cpp index 75e1fa85f0..6d982607d3 100644 --- a/static_core/plugins/ets/runtime/interop_js/ets_vm_plugin.cpp +++ b/static_core/plugins/ets/runtime/interop_js/ets_vm_plugin.cpp @@ -26,6 +26,8 @@ #include "compiler_options.h" #include "compiler/compiler_logger.h" #include "interop_js/napi_impl/napi_impl.h" +#include "plugins/ets/runtime/ets_utils.h" +#include "runtime/include/runtime.h" #include "os/thread.h" @@ -124,6 +126,20 @@ static napi_value GetEtsInstance(napi_env env, napi_callback_info info) return ets_proxy::GetETSInstance(env, classDescriptor); } +static napi_value GetEtsModule(napi_env env, napi_callback_info info) +{ + size_t argc = 1; + std::array argv {}; + NAPI_CHECK_FATAL(napi_get_cb_info(env, info, &argc, argv.data(), nullptr, nullptr)); + if (argc != 1) { + InteropCtx::ThrowJSError(env, "GetEtsModule: expects exactly one argument (module name)"); + return nullptr; + } + + std::string moduleName = GetString(env, argv[0]); + return ets_proxy::GetETSModule(env, moduleName); +} + static std::optional> GetArgStrings(napi_env env, napi_value options, bool needFakeArgv0 = true) { @@ -318,6 +334,7 @@ static napi_value Init(napi_env env, napi_value exports) napi_property_descriptor {"getFunction", 0, GetEtsFunction, 0, 0, 0, napi_enumerable, 0}, napi_property_descriptor {"getClass", 0, GetEtsClass, 0, 0, 0, napi_enumerable, 0}, napi_property_descriptor {"getInstance", 0, GetEtsInstance, 0, 0, 0, napi_enumerable, 0}, + napi_property_descriptor {"getModule", 0, GetEtsModule, 0, 0, 0, napi_enumerable, 0}, }; NAPI_CHECK_FATAL(napi_define_properties(env, exports, desc.size(), desc.data())); diff --git a/static_core/plugins/ets/runtime/interop_js/interop_common.h b/static_core/plugins/ets/runtime/interop_js/interop_common.h index ba0348b707..3a7cbec136 100644 --- a/static_core/plugins/ets/runtime/interop_js/interop_common.h +++ b/static_core/plugins/ets/runtime/interop_js/interop_common.h @@ -36,6 +36,8 @@ napi_wrap_with_xref(napi_env env, napi_value js_object, void *native_object, nap napi_status __attribute__((weak)) // CC-OFF(G.FMT.07) project code style napi_xref_unwrap(napi_env env, napi_value js_object, void **result); napi_status __attribute__((weak)) // CC-OFF(G.FMT.07) project code style +napi_get_hole(napi_env env, napi_value *result); +napi_status __attribute__((weak)) // CC-OFF(G.FMT.07) project code style napi_create_xref(napi_env env, napi_value value, uint32_t initial_refcount, napi_ref *result); napi_status __attribute__((weak)) // CC-OFF(G.FMT.07) project code style napi_register_appstate_callback(napi_env env, void (*f)(int a1, int64_t a2)); diff --git a/static_core/plugins/ets/runtime/interop_js/napi_impl/detail/enumerate_napi.h b/static_core/plugins/ets/runtime/interop_js/napi_impl/detail/enumerate_napi.h index 57656c2b41..79a38fb21e 100644 --- a/static_core/plugins/ets/runtime/interop_js/napi_impl/detail/enumerate_napi.h +++ b/static_core/plugins/ets/runtime/interop_js/napi_impl/detail/enumerate_napi.h @@ -136,6 +136,7 @@ arraybuffer, size_t, byte_offset, napi_value *, result) \ FN_MACRO(napi_create_dataview, napi_env, env, size_t, length, napi_value, arraybuffer, size_t, byte_offset, \ napi_value *, result) \ + FN_MACRO(napi_get_property_names, napi_env, env, napi_value, object, napi_value *, result) \ FN_MACRO(napi_create_object, napi_env, env, napi_value *, result) // NOLINTEND(cppcoreguidelines-macro-usage) diff --git a/static_core/plugins/ets/runtime/interop_js/napi_impl/napi_impl.cpp b/static_core/plugins/ets/runtime/interop_js/napi_impl/napi_impl.cpp index 59a26dc5cd..5ae01f2df6 100644 --- a/static_core/plugins/ets/runtime/interop_js/napi_impl/napi_impl.cpp +++ b/static_core/plugins/ets/runtime/interop_js/napi_impl/napi_impl.cpp @@ -42,6 +42,14 @@ napi_xref_unwrap([[maybe_unused]] napi_env env, [[maybe_unused]] napi_value js_o return napi_ok; } +napi_status __attribute__((weak)) // CC-OFF(G.FMT.10) project code style +napi_get_hole([[maybe_unused]] napi_env env, [[maybe_unused]] napi_value *result) +{ + INTEROP_LOG(FATAL) << "ETS_INTEROP_GTEST_PLUGIN: " << __func__ + << " is implemented in later versions of OHOS, please update." << std::endl; + return napi_ok; +} + napi_status __attribute__((weak)) // CC-OFF(G.FMT.10) project code style napi_create_xref([[maybe_unused]] napi_env env, [[maybe_unused]] napi_value value, [[maybe_unused]] uint32_t initial_refcount, [[maybe_unused]] napi_ref *result) diff --git a/static_core/plugins/ets/tests/interop_js/tests/dynamicimport/CMakeLists.txt b/static_core/plugins/ets/tests/interop_js/tests/dynamicimport/CMakeLists.txt new file mode 100644 index 0000000000..eb93546617 --- /dev/null +++ b/static_core/plugins/ets/tests/interop_js/tests/dynamicimport/CMakeLists.txt @@ -0,0 +1,14 @@ +# Copyright (c) 2025 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. + +add_subdirectory(ets_to_ts) diff --git a/static_core/plugins/ets/tests/interop_js/tests/dynamicimport/ets_to_ts/CMakeLists.txt b/static_core/plugins/ets/tests/interop_js/tests/dynamicimport/ets_to_ts/CMakeLists.txt new file mode 100644 index 0000000000..d3a1a4d968 --- /dev/null +++ b/static_core/plugins/ets/tests/interop_js/tests/dynamicimport/ets_to_ts/CMakeLists.txt @@ -0,0 +1,18 @@ +# Copyright (c) 2025 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. + +panda_ets_interop_js_gtest(ets_interop_js__dynamic_import + CPP_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/dynamic-import.cpp + ETS_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/export12.ets + TS_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/dynamic-import.ts +) diff --git a/static_core/plugins/ets/tests/interop_js/tests/dynamicimport/ets_to_ts/dynamic-import.cpp b/static_core/plugins/ets/tests/interop_js/tests/dynamicimport/ets_to_ts/dynamic-import.cpp new file mode 100644 index 0000000000..3a8dc1a943 --- /dev/null +++ b/static_core/plugins/ets/tests/interop_js/tests/dynamicimport/ets_to_ts/dynamic-import.cpp @@ -0,0 +1,28 @@ +/** + * Copyright (c) 2025 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. + */ + +#include +#include "ets_interop_js_gtest.h" + +namespace ark::ets::interop::js::testing { + +class DynamicImportTest : public EtsInteropTest {}; + +TEST_F(DynamicImportTest, CheckDynamicImport) +{ + ASSERT_TRUE(RunJsTestSuite("dynamic-import.ts")); +} + +} // namespace ark::ets::interop::js::testing diff --git a/static_core/plugins/ets/tests/interop_js/tests/dynamicimport/ets_to_ts/dynamic-import.ts b/static_core/plugins/ets/tests/interop_js/tests/dynamicimport/ets_to_ts/dynamic-import.ts new file mode 100644 index 0000000000..53b991a92c --- /dev/null +++ b/static_core/plugins/ets/tests/interop_js/tests/dynamicimport/ets_to_ts/dynamic-import.ts @@ -0,0 +1,114 @@ +/** + * Copyright (c) 2025 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. + */ + +let path = 'export12'; //1.2 +import(path) + .then((value) => { + // getProperty + ASSERT_TRUE(value !== undefined); + ASSERT_TRUE(typeof value.ClassA === 'function'); + + let ClassA = value.ClassA; + let a = new ClassA(); + ASSERT_TRUE(typeof value.foo === 'function' && value.foo() === 'this is 1.2 foo ets'); + ASSERT_TRUE(value.a === 'this is 1.2 classA ets'); + + // deleteProperty + try { + delete value.ClassA; + } catch (error) { + ASSERT_TRUE(error.toString() == 'TypeError: Cannot delete property'); + let ClassA = value.ClassA; + let a = new ClassA(); + ASSERT_TRUE(typeof a.foo === 'function' && value.foo() === 'this is 1.2 foo ets'); + } + + // deleteProperty + try { + delete value.a; + } catch (error) { + ASSERT_TRUE(error.toString() == 'TypeError: Cannot delete property'); + ASSERT_TRUE(value.a === 'this is 1.2 classA ets'); + } + + // deleteProperty + try { + delete value.foo; + } catch (error) { + ASSERT_TRUE(error.toString() == 'TypeError: Cannot delete property'); + ASSERT_TRUE(typeof value.foo === 'function' && value.foo() === 'this is 1.2 foo ets'); + } + + // ownPropertyKeys + /** + ClassA + length + prototype + name + a + main + foo + _$init$_ + */ + let keys = Reflect.ownKeys(value); + ASSERT_TRUE(keys.length == 8); + + // SetProperty + try { + value['ClassA'] = {}; + } catch (error) { + ASSERT_TRUE(error.toString() == 'TypeError: Cannot assign to read only property of Object Module'); + let ClassA = value.ClassA; + let a = new ClassA(); + ASSERT_TRUE(typeof a.foo === 'function' && a.foo() === 'this is 1.2 classA ets'); + } + + // SetProperty + try { + value['a'] = {}; + } catch (error) { + ASSERT_TRUE(error.toString() == 'TypeError: Cannot assign to read only property of Object Module'); + ASSERT_TRUE(value.a === 'this is 1.2 classA ets'); + } + + // SetProperty + try { + value['foo'] = {}; + } catch (error) { + ASSERT_TRUE(error.toString() == 'TypeError: Cannot assign to read only property of Object Module'); + ASSERT_TRUE(typeof value.foo === 'function' && value.foo() === 'this is 1.2 foo ets'); + } + + // DefineOwnProperty true + { + ASSERT_TRUE(Reflect.defineProperty(value, 'ClassA', {})); + let ClassA = value.ClassA; + let a = new ClassA(); + ASSERT_TRUE(typeof a.foo === 'function' && a.foo() === 'this is 1.2 classA ets'); + } + { + ASSERT_TRUE( + !Reflect.defineProperty(value, 'ClassA', { + value: 1, + }) + ); + let ClassA = value.ClassA; + let a = new ClassA(); + ASSERT_TRUE(typeof a.foo === 'function' && a.foo() === 'this is 1.2 classA ets'); + } + }) + .catch((error) => { + print(error.toString()); + }); diff --git a/static_core/plugins/ets/tests/interop_js/tests/dynamicimport/ets_to_ts/export12.ets b/static_core/plugins/ets/tests/interop_js/tests/dynamicimport/ets_to_ts/export12.ets new file mode 100644 index 0000000000..800bffea04 --- /dev/null +++ b/static_core/plugins/ets/tests/interop_js/tests/dynamicimport/ets_to_ts/export12.ets @@ -0,0 +1,25 @@ +/** + * Copyright (c) 2025 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. + */ + +export class ClassA { + foo() { + return 'this is 1.2 classA ets'; + } +} + +export let a = 'this is 1.2 classA ets'; +export function foo() { + return 'this is 1.2 foo ets'; +} diff --git a/static_core/plugins/ets/tests/interop_js/tests/getEtsModule/CMakeLists.txt b/static_core/plugins/ets/tests/interop_js/tests/getEtsModule/CMakeLists.txt new file mode 100644 index 0000000000..eb93546617 --- /dev/null +++ b/static_core/plugins/ets/tests/interop_js/tests/getEtsModule/CMakeLists.txt @@ -0,0 +1,14 @@ +# Copyright (c) 2025 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. + +add_subdirectory(ets_to_ts) diff --git a/static_core/plugins/ets/tests/interop_js/tests/getEtsModule/ets_to_ts/CMakeLists.txt b/static_core/plugins/ets/tests/interop_js/tests/getEtsModule/ets_to_ts/CMakeLists.txt new file mode 100644 index 0000000000..37c4d5595b --- /dev/null +++ b/static_core/plugins/ets/tests/interop_js/tests/getEtsModule/ets_to_ts/CMakeLists.txt @@ -0,0 +1,22 @@ +# Copyright (c) 2025 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. + +panda_ets_interop_js_gtest(ets_interop_js__get_module_exports + PACKAGE_NAME get_module + CPP_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/get_module.cpp + ETS_SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/get_module.ets + ${CMAKE_CURRENT_SOURCE_DIR}/entry_module.ets + ${CMAKE_CURRENT_SOURCE_DIR}/entry_module2.ets + TS_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/test_get_module.ts +) \ No newline at end of file diff --git a/static_core/plugins/ets/tests/interop_js/tests/getEtsModule/ets_to_ts/entry_module.ets b/static_core/plugins/ets/tests/interop_js/tests/getEtsModule/ets_to_ts/entry_module.ets new file mode 100644 index 0000000000..3e357af57d --- /dev/null +++ b/static_core/plugins/ets/tests/interop_js/tests/getEtsModule/ets_to_ts/entry_module.ets @@ -0,0 +1,16 @@ +/** + * Copyright (c) 2025 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. + */ + +export { A, b, c } from './get_module'; diff --git a/static_core/plugins/ets/tests/interop_js/tests/getEtsModule/ets_to_ts/entry_module2.ets b/static_core/plugins/ets/tests/interop_js/tests/getEtsModule/ets_to_ts/entry_module2.ets new file mode 100644 index 0000000000..2d2da6d300 --- /dev/null +++ b/static_core/plugins/ets/tests/interop_js/tests/getEtsModule/ets_to_ts/entry_module2.ets @@ -0,0 +1,16 @@ +/** + * Copyright (c) 2025 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. + */ + +export * from './get_module'; diff --git a/static_core/plugins/ets/tests/interop_js/tests/getEtsModule/ets_to_ts/get_module.cpp b/static_core/plugins/ets/tests/interop_js/tests/getEtsModule/ets_to_ts/get_module.cpp new file mode 100644 index 0000000000..5398dade6c --- /dev/null +++ b/static_core/plugins/ets/tests/interop_js/tests/getEtsModule/ets_to_ts/get_module.cpp @@ -0,0 +1,28 @@ +/** + * Copyright (c) 2025 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. + */ + +#include +#include "ets_interop_js_gtest.h" + +namespace ark::ets::interop::js::testing { + +class GetModuleTest : public EtsInteropTest {}; + +TEST_F(GetModuleTest, CheckGetModuleExports) +{ + ASSERT_TRUE(RunJsTestSuite("test_get_module.ts")); +} + +} // namespace ark::ets::interop::js::testing diff --git a/static_core/plugins/ets/tests/interop_js/tests/getEtsModule/ets_to_ts/get_module.ets b/static_core/plugins/ets/tests/interop_js/tests/getEtsModule/ets_to_ts/get_module.ets new file mode 100644 index 0000000000..dc53432662 --- /dev/null +++ b/static_core/plugins/ets/tests/interop_js/tests/getEtsModule/ets_to_ts/get_module.ets @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2025 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. + */ + +export class A { + value(): number { + return 42; + } +} + +export let b: number = 100; + +export function c(): string { + return 'hello from c'; +} + +class D { + hidden(): boolean { + return false; + } +} \ No newline at end of file diff --git a/static_core/plugins/ets/tests/interop_js/tests/getEtsModule/ets_to_ts/test_get_module.ts b/static_core/plugins/ets/tests/interop_js/tests/getEtsModule/ets_to_ts/test_get_module.ts new file mode 100644 index 0000000000..48a14e232d --- /dev/null +++ b/static_core/plugins/ets/tests/interop_js/tests/getEtsModule/ets_to_ts/test_get_module.ts @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2025 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. + */ + + +const etsVm = globalThis.gtest.etsVm; + +const mod = etsVm.getModule('get_module'); +ASSERT_TRUE(mod !== undefined); +ASSERT_TRUE(typeof mod.b === 'number' && mod.b === 100); +ASSERT_TRUE(typeof mod.c === 'function' && mod.c() === 'hello from c'); +ASSERT_TRUE(typeof mod.A === 'function'); +const a1 = new mod.A(); +ASSERT_TRUE(typeof a1.value === 'function' && a1.value() === 42); +ASSERT_TRUE(mod.D === undefined); + +const entry = etsVm.getModule('entry_module'); +ASSERT_TRUE(entry !== undefined); +ASSERT_TRUE(typeof entry.b === 'number' && entry.b === 100); +ASSERT_TRUE(typeof entry.c === 'function' && entry.c() === 'hello from c'); +ASSERT_TRUE(typeof entry.A === 'function'); +const a2 = new entry.A(); +ASSERT_TRUE(typeof a2.value === 'function' && a2.value() === 42); +ASSERT_TRUE(entry.D === undefined); + + +const entry2 = etsVm.getModule('entry_module2'); +ASSERT_TRUE(entry2 !== undefined); +ASSERT_TRUE(typeof entry2.b === 'number' && entry2.b === 100); +ASSERT_TRUE(typeof entry2.c === 'function' && entry2.c() === 'hello from c'); +ASSERT_TRUE(typeof entry2.A === 'function'); +const a3 = new entry2.A(); +ASSERT_TRUE(typeof a3.value === 'function' && a3.value() === 42); +ASSERT_TRUE(entry2.D === undefined); \ No newline at end of file -- Gitee From f72f5b4344802ebae0e3bd025b9d3d8e8e0f4d44 Mon Sep 17 00:00:00 2001 From: zhaoziming Date: Fri, 6 Jun 2025 23:23:04 +0800 Subject: [PATCH 2/2] Add error report when static runtime is not created Change-Id: I00002212f2dacbc12471dc9b5de04ac9573d71b5 --- .../ets/runtime/interop_js/ets_proxy/ets_proxy.cpp | 4 +--- .../plugins/ets/runtime/interop_js/interop_common.h | 2 -- .../ets/runtime/interop_js/interop_context.cpp | 13 +++++++++++++ .../ets/runtime/interop_js/interop_context.h | 1 + .../interop_js/napi_impl/detail/enumerate_napi.h | 1 + .../ets/runtime/interop_js/napi_impl/napi_impl.cpp | 8 -------- 6 files changed, 16 insertions(+), 13 deletions(-) diff --git a/static_core/plugins/ets/runtime/interop_js/ets_proxy/ets_proxy.cpp b/static_core/plugins/ets/runtime/interop_js/ets_proxy/ets_proxy.cpp index 65fff7d1ea..b7831868c0 100644 --- a/static_core/plugins/ets/runtime/interop_js/ets_proxy/ets_proxy.cpp +++ b/static_core/plugins/ets/runtime/interop_js/ets_proxy/ets_proxy.cpp @@ -209,9 +209,7 @@ napi_value GetETSModule(napi_env env, const std::string &moduleName) { EtsCoroutine *coro = EtsCoroutine::GetCurrent(); if (coro == nullptr) { - napi_value val; - NAPI_CHECK_FATAL(napi_get_hole(env, &val)); - return val; + return InteropCtx::CreateJSTypeError(env, "Static context not loaded", ""); } ScopedManagedCodeThread managedScope(coro); InteropCtx *ctx = InteropCtx::Current(coro); diff --git a/static_core/plugins/ets/runtime/interop_js/interop_common.h b/static_core/plugins/ets/runtime/interop_js/interop_common.h index 3a7cbec136..ba0348b707 100644 --- a/static_core/plugins/ets/runtime/interop_js/interop_common.h +++ b/static_core/plugins/ets/runtime/interop_js/interop_common.h @@ -36,8 +36,6 @@ napi_wrap_with_xref(napi_env env, napi_value js_object, void *native_object, nap napi_status __attribute__((weak)) // CC-OFF(G.FMT.07) project code style napi_xref_unwrap(napi_env env, napi_value js_object, void **result); napi_status __attribute__((weak)) // CC-OFF(G.FMT.07) project code style -napi_get_hole(napi_env env, napi_value *result); -napi_status __attribute__((weak)) // CC-OFF(G.FMT.07) project code style napi_create_xref(napi_env env, napi_value value, uint32_t initial_refcount, napi_ref *result); napi_status __attribute__((weak)) // CC-OFF(G.FMT.07) project code style napi_register_appstate_callback(napi_env env, void (*f)(int a1, int64_t a2)); diff --git a/static_core/plugins/ets/runtime/interop_js/interop_context.cpp b/static_core/plugins/ets/runtime/interop_js/interop_context.cpp index b5b567229f..bb8fbce863 100644 --- a/static_core/plugins/ets/runtime/interop_js/interop_context.cpp +++ b/static_core/plugins/ets/runtime/interop_js/interop_context.cpp @@ -541,6 +541,19 @@ void InteropCtx::ThrowJSValue(napi_env env, napi_value val) #endif } +napi_value InteropCtx::CreateJSTypeError(napi_env env, const std::string &code, const std::string &msg) +{ + INTEROP_LOG(INFO) << "CreateJSTypeError: code: " << code << ", msg: " << msg; + ASSERT(!NapiIsExceptionPending(env)); + napi_value errorCode; + NAPI_CHECK_FATAL(napi_create_string_utf8(env, code.data(), code.size(), &errorCode)); + napi_value errorMessage; + NAPI_CHECK_FATAL(napi_create_string_utf8(env, msg.data(), msg.size(), &errorMessage)); + napi_value error; + NAPI_CHECK_FATAL(napi_create_type_error(env, errorCode, errorMessage, &error)); + return error; +} + void InteropCtx::InitializeDefaultLinkerCtxIfNeeded(EtsRuntimeLinker *linker) { os::memory::LockHolder lock(InteropCtx::SharedEtsVmState::mutex_); diff --git a/static_core/plugins/ets/runtime/interop_js/interop_context.h b/static_core/plugins/ets/runtime/interop_js/interop_context.h index 34a9430eb5..8827c966c1 100644 --- a/static_core/plugins/ets/runtime/interop_js/interop_context.h +++ b/static_core/plugins/ets/runtime/interop_js/interop_context.h @@ -427,6 +427,7 @@ public: PANDA_PUBLIC_API static void ThrowJSError(napi_env env, const std::string &msg); static void ThrowJSTypeError(napi_env env, const std::string &msg); static void ThrowJSValue(napi_env env, napi_value val); + static napi_value CreateJSTypeError(napi_env env, const std::string &code, const std::string &msg); static void InitializeDefaultLinkerCtxIfNeeded(EtsRuntimeLinker *linker); static void SetDefaultLinkerContext(EtsRuntimeLinker *linker); diff --git a/static_core/plugins/ets/runtime/interop_js/napi_impl/detail/enumerate_napi.h b/static_core/plugins/ets/runtime/interop_js/napi_impl/detail/enumerate_napi.h index 79a38fb21e..5ec368a0b7 100644 --- a/static_core/plugins/ets/runtime/interop_js/napi_impl/detail/enumerate_napi.h +++ b/static_core/plugins/ets/runtime/interop_js/napi_impl/detail/enumerate_napi.h @@ -96,6 +96,7 @@ FN_MACRO(napi_get_value_bool, napi_env, env, napi_value, value, bool *, result) \ FN_MACRO(napi_get_named_property, napi_env, env, napi_value, object, const char *, utf8name, napi_value *, result) \ FN_MACRO(napi_create_error, napi_env, env, napi_value, code, napi_value, msg, napi_value *, result) \ + FN_MACRO(napi_create_type_error, napi_env, env, napi_value, code, napi_value, msg, napi_value *, result) \ FN_MACRO(napi_coerce_to_string, napi_env, env, napi_value, value, napi_value *, result) \ FN_MACRO(napi_create_int64, napi_env, env, int64_t, value, napi_value *, result) \ FN_MACRO(napi_reference_ref, napi_env, env, napi_ref, ref, uint32_t *, result) \ diff --git a/static_core/plugins/ets/runtime/interop_js/napi_impl/napi_impl.cpp b/static_core/plugins/ets/runtime/interop_js/napi_impl/napi_impl.cpp index 5ae01f2df6..59a26dc5cd 100644 --- a/static_core/plugins/ets/runtime/interop_js/napi_impl/napi_impl.cpp +++ b/static_core/plugins/ets/runtime/interop_js/napi_impl/napi_impl.cpp @@ -42,14 +42,6 @@ napi_xref_unwrap([[maybe_unused]] napi_env env, [[maybe_unused]] napi_value js_o return napi_ok; } -napi_status __attribute__((weak)) // CC-OFF(G.FMT.10) project code style -napi_get_hole([[maybe_unused]] napi_env env, [[maybe_unused]] napi_value *result) -{ - INTEROP_LOG(FATAL) << "ETS_INTEROP_GTEST_PLUGIN: " << __func__ - << " is implemented in later versions of OHOS, please update." << std::endl; - return napi_ok; -} - napi_status __attribute__((weak)) // CC-OFF(G.FMT.10) project code style napi_create_xref([[maybe_unused]] napi_env env, [[maybe_unused]] napi_value value, [[maybe_unused]] uint32_t initial_refcount, [[maybe_unused]] napi_ref *result) -- Gitee