diff --git a/native_engine/impl/ark/ark_native_engine.cpp b/native_engine/impl/ark/ark_native_engine.cpp index d2998813bf7e1f31ac5bef9d95651534992457e7..b0fa0bef2e3d6f27ca4409210d633a9ce24a6df2 100644 --- a/native_engine/impl/ark/ark_native_engine.cpp +++ b/native_engine/impl/ark/ark_native_engine.cpp @@ -181,6 +181,146 @@ void FunctionSetContainerId(napi_env env, panda::Local &value } #endif +static Local NapiNativeCreateFunction(napi_env env, const char* name, + NapiNativeCallback cb, void* value) +{ + auto engine = reinterpret_cast(env); + auto vm = const_cast(engine->GetEcmaVm()); + NapiFunctionInfo* funcInfo = NapiFunctionInfo::CreateNewInstance(); + if (funcInfo == nullptr) { + HILOG_ERROR("funcInfo is nullptr"); + return JSValueRef::Undefined(vm); + } + funcInfo->callback = cb; + funcInfo->data = value; + funcInfo->env = env; +#ifdef ENABLE_CONTAINER_SCOPE + if (engine->IsContainerScopeEnabled()) { + funcInfo->scopeId = OHOS::Ace::ContainerScope::CurrentId(); + } +#endif + + Local context = engine->GetContext(); + Local fn = panda::FunctionRef::NewConcurrentWithName(vm, context, ArkNativeFunctionCallBack, + CommonDeleter, name, + reinterpret_cast(funcInfo), true); + return fn; +} + +static Local NapiInitAttrValFromProp(napi_env env, const NapiPropertyDescriptor &property, + bool &writable, Local *curKey) +{ + const EcmaVM *vm = reinterpret_cast(env)->GetEcmaVm(); + Local val = panda::JSValueRef::Undefined(vm); + std::string fullName(""); + if (property.getter != nullptr || property.setter != nullptr) { + Local localGetter = panda::JSValueRef::Undefined(vm); + Local localSetter = panda::JSValueRef::Undefined(vm); + if (property.getter != nullptr) { + fullName += "getter"; + localGetter = NapiNativeCreateFunction(env, fullName.c_str(), property.getter, property.data); + } + if (property.setter != nullptr) { + fullName += "setter"; + localSetter = NapiNativeCreateFunction(env, fullName.c_str(), property.setter, property.data); + } + val = panda::ObjectRef::CreateAccessorData(vm, localGetter, localSetter); + writable = false; // the default writable of getter and setter is 'false' + } else if (property.method != nullptr) { + if (property.utf8name != nullptr) { + fullName += property.utf8name; + } else { + fullName += (*curKey)->IsString(vm) ? + Local(*curKey)->ToString(vm) : + Local(*curKey)->GetDescription(vm)->ToString(vm); + } + val = NapiNativeCreateFunction(env, fullName.c_str(), property.method, property.data); + } else { + val = LocalValueFromJsValue(property.value); + } + return val; +} + +static size_t NapiGetKeysAndAttrsFromProps(napi_env env, size_t propertyCount, const NapiPropertyDescriptor *properties, + Local *keys, PropertyAttribute *attrs) +{ + auto vm = reinterpret_cast(env)->GetEcmaVm(); + size_t curNonStaticPropIdx = propertyCount - 1; // 1: last index of array is 'lenght - 1'. + size_t curStaticPropIdx = 0; + for (size_t i = 0; i < propertyCount; ++i) { + const NapiPropertyDescriptor &property = properties[i]; + Local *curKey; + PropertyAttribute *curAttr; + // static property index start at 0, and non-static property index start at 'propertyCount - 1'. + if (properties[i].attributes & NATIVE_STATIC) { + curKey = &keys[curStaticPropIdx]; + curAttr = &attrs[curStaticPropIdx]; + ++curStaticPropIdx; + } else { + curKey = &keys[curNonStaticPropIdx]; + curAttr = &attrs[curNonStaticPropIdx]; + --curNonStaticPropIdx; + } + if (property.utf8name != nullptr) { + *curKey = panda::StringRef::NewFromUtf8(vm, property.utf8name); + } else { + *curKey = LocalValueFromJsValue(property.name); + } + bool writable = (property.attributes & NATIVE_WRITABLE) != 0; + bool enumable = (property.attributes & NATIVE_ENUMERABLE) != 0; + bool configable = (property.attributes & NATIVE_CONFIGURABLE) != 0; + Local val = NapiInitAttrValFromProp(env, property, writable, curKey); + // ~PropertyAttribute() was called in NewConcurrentClassFunctionWithName + new (curAttr) PropertyAttribute(val, writable, enumable, configable); + } + return curStaticPropIdx; +} + +static Local NapiCreateClassFunction(napi_env env, std::string &className, + NapiFunctionInfo* funcInfo, size_t propCount, + const NapiPropertyDescriptor* properties) +{ + const EcmaVM *vm = reinterpret_cast(env)->GetEcmaVm(); + NativeEngine* engine = reinterpret_cast(env); + Local context = engine->GetContext(); + Local fn; + if (propCount == 0) { + fn = panda::FunctionRef::NewConcurrentClassFunctionWithName(vm, context, ArkNativeFunctionCallBack, + CommonDeleter, className.c_str(), + reinterpret_cast(funcInfo), true); + } else if (propCount <= panda::ObjectRef::MAX_PROPERTIES_ON_STACK) { + Local keys[panda::ObjectRef::MAX_PROPERTIES_ON_STACK]; + PropertyAttribute attrs[panda::ObjectRef::MAX_PROPERTIES_ON_STACK]; + size_t staticPropCount = NapiGetKeysAndAttrsFromProps(env, propCount, properties, &keys[0], &attrs[0]); + fn = panda::FunctionRef::NewConcurrentClassFunctionWithName(vm, context, ArkNativeFunctionCallBack, + CommonDeleter, className.c_str(), + reinterpret_cast(funcInfo), true, propCount, + staticPropCount, &keys[0], &attrs[0]); + } else { + Local *keys = + reinterpret_cast *>(malloc(sizeof(Local) * propCount)); + PropertyAttribute *attrs = reinterpret_cast(malloc(sizeof(PropertyAttribute) * propCount)); + if (attrs != nullptr && keys != nullptr) { + size_t staticPropCount = NapiGetKeysAndAttrsFromProps(env, propCount, properties, keys, attrs); + fn = panda::FunctionRef::NewConcurrentClassFunctionWithName(vm, context, ArkNativeFunctionCallBack, + CommonDeleter, className.c_str(), + reinterpret_cast(funcInfo), true, + propCount, staticPropCount, keys, attrs); + } else { + fn = panda::JSValueRef::Undefined(vm); + napi_throw_error(env, nullptr, "malloc failed in napi_define_class"); + } + + if (keys != nullptr) { + free(keys); + } + if (attrs != nullptr) { + free(attrs); + } + } + return fn; +} + panda::Local NapiDefineClass(napi_env env, const char* name, NapiNativeCallback callback, void* data, const NapiPropertyDescriptor* properties, size_t length) { @@ -198,34 +338,23 @@ panda::Local NapiDefineClass(napi_env env, const char* name, funcInfo->callback = callback; funcInfo->data = data; funcInfo->env = env; - NativeEngine* engine = reinterpret_cast(env); #ifdef ENABLE_CONTAINER_SCOPE + NativeEngine* engine = reinterpret_cast(env); if (engine->IsContainerScopeEnabled()) { funcInfo->scopeId = OHOS::Ace::ContainerScope::CurrentId(); } #endif - Local context = engine->GetContext(); - Local fn = panda::FunctionRef::NewConcurrentClassFunctionWithName(vm, context, - ArkNativeFunctionCallBack, CommonDeleter, className.c_str(), reinterpret_cast(funcInfo), true); + Local fn = NapiCreateClassFunction(env, className, funcInfo, length, properties); - if (length == 0) { - return fn; + Local excep = JSNApi::GetUncaughtException(vm); + if (!excep.IsNull()) { + HILOG_DEBUG("ArkNativeObject::NapiDefineClass occur Exception"); + JSNApi::GetAndClearUncaughtException(vm); } +#ifdef ENABLE_HITRACE Local classPrototype = fn->GetFunctionPrototype(vm); - Local fnObj = Local(fn); - for (size_t i = 0; i < length; ++i) { - if (properties[i].attributes & NATIVE_STATIC) { - NapiDefineProperty(env, fnObj, properties[i]); - } else { - if (classPrototype->IsUndefined()) { - HILOG_ERROR("ArkNativeEngineImpl::Class's prototype is null"); - continue; - } - reinterpret_cast(env)->SetModuleName(classPrototype, className); - NapiDefineProperty(env, classPrototype, properties[i]); - } - } - + reinterpret_cast(env)->SetModuleName(classPrototype, className); +#endif return fn; } @@ -1082,32 +1211,6 @@ panda::JSValueRef ArkNativeFunctionCallBack(JsiRuntimeCallInfo *runtimeInfo) return **localRet; } -static Local NapiNativeCreateFunction(napi_env env, const char* name, - NapiNativeCallback cb, void* value) -{ - auto engine = reinterpret_cast(env); - auto vm = const_cast(engine->GetEcmaVm()); - NapiFunctionInfo* funcInfo = NapiFunctionInfo::CreateNewInstance(); - if (funcInfo == nullptr) { - HILOG_ERROR("funcInfo is nullptr"); - return JSValueRef::Undefined(vm); - } - funcInfo->callback = cb; - funcInfo->data = value; - funcInfo->env = env; -#ifdef ENABLE_CONTAINER_SCOPE - if (engine->IsContainerScopeEnabled()) { - funcInfo->scopeId = OHOS::Ace::ContainerScope::CurrentId(); - } -#endif - - Local context = engine->GetContext(); - Local fn = panda::FunctionRef::NewConcurrentWithName(vm, context, ArkNativeFunctionCallBack, - CommonDeleter, name, - reinterpret_cast(funcInfo), true); - return fn; -} - static Local GetProperty(EcmaVM* vm, Local &obj, const char* name) { Local key = StringRef::NewFromUtf8(vm, name); diff --git a/native_engine/impl/ark/ark_native_engine.h b/native_engine/impl/ark/ark_native_engine.h index f67b4b787e2dc1c48cf55677aea37c1363a6b544..f0c87375f74c6fac2872c82e4e5d55e74fbcf01f 100644 --- a/native_engine/impl/ark/ark_native_engine.h +++ b/native_engine/impl/ark/ark_native_engine.h @@ -92,7 +92,6 @@ panda::Local NapiCreateObjectWithProperties(napi_env env, size const napi_property_descriptor *properties, Local *keys, panda::PropertyAttribute *attrs); - void CommonDeleter(void *env, void *externalPointer, void *data); enum class ForceExpandState : int32_t { diff --git a/test/unittest/test_napi.cpp b/test/unittest/test_napi.cpp index 5892ee8fa693c1e3254c1e6d99d4b5650488bc26..1de028d8f1d0f90e4794a6cf9039d89d88cacf1b 100644 --- a/test/unittest/test_napi.cpp +++ b/test/unittest/test_napi.cpp @@ -24,9 +24,11 @@ #include "gtest/gtest.h" #include "hilog/log.h" +#include "ecmascript/napi/include/jsnapi_expo.h" #include "napi/native_common.h" #include "napi/native_node_api.h" #include "native_create_env.h" +#include "native_utils.h" #include "securec.h" #include "test.h" @@ -7575,6 +7577,126 @@ HWTEST_F(NapiBasicTest, NapiDefineClassTest004, testing::ext::TestSize.Level1) ASSERT_EQ(status, napi_invalid_arg); } +HWTEST_F(NapiBasicTest, NapiDefineClassTest005, testing::ext::TestSize.Level1) +{ + napi_env env = reinterpret_cast(engine_); + napi_value result; + napi_status status = napi_define_class( + env, "TestClass", NAPI_AUTO_LENGTH, + [](napi_env env, napi_callback_info info) -> napi_value { + napi_value thisVar = nullptr; + napi_get_cb_info(env, info, nullptr, nullptr, &thisVar, nullptr); + return thisVar; + }, + nullptr, 0, nullptr, &result); + + const EcmaVM* vm = reinterpret_cast(engine_)->GetEcmaVm(); + Local classFunc = LocalValueFromJsValue(result); + Local className = classFunc->GetName(vm); + ASSERT_EQ(className->ToString(vm), "TestClass"); + ASSERT_EQ(status, napi_ok); +} + +HWTEST_F(NapiBasicTest, NapiDefineClassTest006, testing::ext::TestSize.Level1) +{ + napi_env env = reinterpret_cast(engine_); + napi_property_descriptor properties[10]; + const std::string keyNames[10] = { "key0", "key1", "key2", "key3", "key4", "key5", "key6", "key7", "key8", "key9" }; + const int expectedVals[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + int defaultAttributes = napi_default | napi_writable | napi_configurable; + for (size_t i = 0; i < 10; ++i) { + properties[i].utf8name = keyNames[i].c_str(); + properties[i].name = nullptr; + properties[i].method = nullptr; + properties[i].getter = nullptr; + properties[i].setter = nullptr; + if (i <= 5) { + properties[i].attributes = static_cast(defaultAttributes | napi_static); + } else { + properties[i].attributes = static_cast(defaultAttributes); + } + napi_create_int32(env, expectedVals[i], &(properties[i].value)); + } + + napi_value result; + napi_status status = napi_define_class( + env, "TestClass", NAPI_AUTO_LENGTH, + [](napi_env env, napi_callback_info info) -> napi_value { + napi_value thisVar = nullptr; + napi_get_cb_info(env, info, nullptr, nullptr, &thisVar, nullptr); + return thisVar; + }, + nullptr, 10, &properties[0], &result); + + const EcmaVM* vm = reinterpret_cast(engine_)->GetEcmaVm(); + Local classFunc = LocalValueFromJsValue(result); + Local clsFuncProto = classFunc->GetFunctionPrototype(vm); + for (size_t i = 0; i < 10; ++i) { + Local curProp; + if (i <= 5) { + curProp = classFunc->Get(vm, properties[i].utf8name); + } else { + curProp = clsFuncProto->Get(vm, properties[i].utf8name); + } + bool tmpBool = true; + ASSERT_EQ(curProp->GetValueInt32(tmpBool), expectedVals[i]); + } + ASSERT_EQ(status, napi_ok); +} + +static napi_value NativeCallBackForTest(napi_env env, napi_callback_info info) +{ + return nullptr; +} + +HWTEST_F(NapiBasicTest, NapiDefineClassTest007, testing::ext::TestSize.Level1) +{ + napi_env env = reinterpret_cast(engine_); + napi_property_descriptor properties[panda::ObjectRef::MAX_PROPERTIES_ON_STACK + 1]; + const std::string keyNames[10] = { "key0", "key1", "key2", "key3", "key4", "key5", "key6", "key7", "key8", "key9" }; + const std::string lastKeys[4] = { "lastKey0", "lastKey1", "lastKey2", "lastKey3" }; + int expectedVals[panda::ObjectRef::MAX_PROPERTIES_ON_STACK + 1]; + for (size_t i = 0; i < panda::ObjectRef::MAX_PROPERTIES_ON_STACK + 1; ++i) { + expectedVals[i] = i; + } + int defaultAttributes = napi_default | napi_writable | napi_configurable | napi_static; + for (size_t i = 0; i < panda::ObjectRef::MAX_PROPERTIES_ON_STACK + 1; ++i) { + if (i >= panda::ObjectRef::MAX_PROPERTIES_ON_STACK - 3) { + properties[i].utf8name = lastKeys[i - (panda::ObjectRef::MAX_PROPERTIES_ON_STACK - 3)].c_str(); + } else { + properties[i].utf8name = keyNames[i % 10].c_str(); + } + properties[i].name = nullptr; + properties[i].method = nullptr; + properties[i].getter = nullptr; + properties[i].setter = nullptr; + properties[i].attributes = static_cast(defaultAttributes); + napi_create_int32(env, expectedVals[i], &(properties[i].value)); + } + properties[panda::ObjectRef::MAX_PROPERTIES_ON_STACK - 1].getter = NativeCallBackForTest; + properties[panda::ObjectRef::MAX_PROPERTIES_ON_STACK - 2].setter = NativeCallBackForTest; + properties[panda::ObjectRef::MAX_PROPERTIES_ON_STACK - 3].method = NativeCallBackForTest; + + napi_value result; + napi_status status = napi_define_class( + env, "TestClass", NAPI_AUTO_LENGTH, + [](napi_env env, napi_callback_info info) -> napi_value { + napi_value thisVar = nullptr; + napi_get_cb_info(env, info, nullptr, nullptr, &thisVar, nullptr); + return thisVar; + }, + nullptr, panda::ObjectRef::MAX_PROPERTIES_ON_STACK + 1, &properties[0], &result); + + const EcmaVM* vm = reinterpret_cast(engine_)->GetEcmaVm(); + Local classFunc = LocalValueFromJsValue(result); + Local curProp = + classFunc->Get(vm, properties[panda::ObjectRef::MAX_PROPERTIES_ON_STACK].utf8name); + bool tmpBool = true; + ASSERT_EQ(curProp->GetValueInt32(tmpBool), expectedVals[panda::ObjectRef::MAX_PROPERTIES_ON_STACK]); + + ASSERT_EQ(status, napi_ok); +} + HWTEST_F(NapiBasicTest, NapiWrapTest001, testing::ext::TestSize.Level1) { napi_env env = reinterpret_cast(engine_);