From 416fa06e992ed1f17ae0bbd760914ddcae3f9397 Mon Sep 17 00:00:00 2001 From: Toyo Li Date: Sat, 14 Sep 2024 22:04:38 +0800 Subject: [PATCH] add support for UTF-8 and Latin-1 property keys (#127) --- packages/emnapi/include/node/js_native_api.h | 4 + packages/emnapi/src/value/convert2napi.ts | 14 ++ packages/test/string/binding.c | 55 ++++++ packages/test/string/test.js | 183 +++++++------------ 4 files changed, 138 insertions(+), 118 deletions(-) diff --git a/packages/emnapi/include/node/js_native_api.h b/packages/emnapi/include/node/js_native_api.h index d17afa0a..b2a66839 100644 --- a/packages/emnapi/include/node/js_native_api.h +++ b/packages/emnapi/include/node/js_native_api.h @@ -120,6 +120,10 @@ node_api_create_external_string_utf16(napi_env env, #ifdef NAPI_EXPERIMENTAL #define NODE_API_EXPERIMENTAL_HAS_PROPERTY_KEYS +NAPI_EXTERN napi_status NAPI_CDECL node_api_create_property_key_latin1( + napi_env env, const char* str, size_t length, napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL node_api_create_property_key_utf8( + napi_env env, const char* str, size_t length, napi_value* result); NAPI_EXTERN napi_status NAPI_CDECL node_api_create_property_key_utf16( napi_env env, const char16_t* str, size_t length, napi_value* result); #endif // NAPI_EXPERIMENTAL diff --git a/packages/emnapi/src/value/convert2napi.ts b/packages/emnapi/src/value/convert2napi.ts index a3284021..52961982 100644 --- a/packages/emnapi/src/value/convert2napi.ts +++ b/packages/emnapi/src/value/convert2napi.ts @@ -165,6 +165,20 @@ export function node_api_create_external_string_utf16 ( ) } +/** + * @__sig ipppp + */ +export function node_api_create_property_key_latin1 (env: napi_env, str: const_char16_t_p, length: size_t, result: Pointer): napi_status { + return napi_create_string_latin1(env, str, length, result) +} + +/** + * @__sig ipppp + */ +export function node_api_create_property_key_utf8 (env: napi_env, str: const_char16_t_p, length: size_t, result: Pointer): napi_status { + return napi_create_string_utf8(env, str, length, result) +} + /** * @__sig ipppp */ diff --git a/packages/test/string/binding.c b/packages/test/string/binding.c index 588cbe7a..314c278b 100644 --- a/packages/test/string/binding.c +++ b/packages/test/string/binding.c @@ -328,6 +328,40 @@ static napi_value TestUtf16Insufficient(napi_env env, napi_callback_info info) { return output; } +static napi_value TestPropertyKeyLatin1(napi_env env, napi_callback_info info) { + return TestOneByteImpl(env, + info, + napi_get_value_string_latin1, + node_api_create_property_key_latin1, + actual_length); +} + +static napi_value TestPropertyKeyLatin1AutoLength(napi_env env, + napi_callback_info info) { + return TestOneByteImpl(env, + info, + napi_get_value_string_latin1, + node_api_create_property_key_latin1, + auto_length); +} + +static napi_value TestPropertyKeyUtf8(napi_env env, napi_callback_info info) { + return TestOneByteImpl(env, + info, + napi_get_value_string_utf8, + node_api_create_property_key_utf8, + actual_length); +} + +static napi_value TestPropertyKeyUtf8AutoLength(napi_env env, + napi_callback_info info) { + return TestOneByteImpl(env, + info, + napi_get_value_string_utf8, + node_api_create_property_key_utf8, + auto_length); +} + static napi_value TestPropertyKeyUtf16(napi_env env, napi_callback_info info) { return TestTwoByteImpl(env, info, @@ -345,6 +379,20 @@ static napi_value TestPropertyKeyUtf16AutoLength(napi_env env, auto_length); } +static napi_value Latin1Length(napi_env env, napi_callback_info info) { + napi_value args[1]; + NODE_API_CALL(env, validate_and_retrieve_single_string_arg(env, info, args)); + + size_t length; + NODE_API_CALL(env, + napi_get_value_string_latin1(env, args[0], NULL, 0, &length)); + + napi_value output; + NODE_API_CALL(env, napi_create_uint32(env, (uint32_t)length, &output)); + + return output; +} + static napi_value Utf16Length(napi_env env, napi_callback_info info) { napi_value args[1]; NODE_API_CALL(env, validate_and_retrieve_single_string_arg(env, info, args)); @@ -479,12 +527,19 @@ napi_value Init(napi_env env, napi_value exports) { DECLARE_NODE_API_PROPERTY("TestUtf16ExternalAutoLength", TestUtf16ExternalAutoLength), DECLARE_NODE_API_PROPERTY("TestUtf16Insufficient", TestUtf16Insufficient), + DECLARE_NODE_API_PROPERTY("Latin1Length", Latin1Length), DECLARE_NODE_API_PROPERTY("Utf16Length", Utf16Length), DECLARE_NODE_API_PROPERTY("Utf8Length", Utf8Length), DECLARE_NODE_API_PROPERTY("TestLargeUtf8", TestLargeUtf8), DECLARE_NODE_API_PROPERTY("TestLargeLatin1", TestLargeLatin1), DECLARE_NODE_API_PROPERTY("TestLargeUtf16", TestLargeUtf16), DECLARE_NODE_API_PROPERTY("TestMemoryCorruption", TestMemoryCorruption), + DECLARE_NODE_API_PROPERTY("TestPropertyKeyLatin1", TestPropertyKeyLatin1), + DECLARE_NODE_API_PROPERTY("TestPropertyKeyLatin1AutoLength", + TestPropertyKeyLatin1AutoLength), + DECLARE_NODE_API_PROPERTY("TestPropertyKeyUtf8", TestPropertyKeyUtf8), + DECLARE_NODE_API_PROPERTY("TestPropertyKeyUtf8AutoLength", + TestPropertyKeyUtf8AutoLength), DECLARE_NODE_API_PROPERTY("TestPropertyKeyUtf16", TestPropertyKeyUtf16), DECLARE_NODE_API_PROPERTY("TestPropertyKeyUtf16AutoLength", TestPropertyKeyUtf16AutoLength), diff --git a/packages/test/string/test.js b/packages/test/string/test.js index 93499655..de5311e9 100644 --- a/packages/test/string/test.js +++ b/packages/test/string/test.js @@ -3,130 +3,77 @@ const assert = require('assert') module.exports = function test (test_string) { - const empty = '' - assert.strictEqual(test_string.TestLatin1(empty), empty) - assert.strictEqual(test_string.TestUtf8(empty), empty) - assert.strictEqual(test_string.TestUtf16(empty), empty) - assert.strictEqual(test_string.TestLatin1AutoLength(empty), empty) - assert.strictEqual(test_string.TestUtf8AutoLength(empty), empty) - assert.strictEqual(test_string.TestUtf16AutoLength(empty), empty) - assert.strictEqual(test_string.TestLatin1External(empty), empty) - assert.strictEqual(test_string.TestUtf16External(empty), empty) - assert.strictEqual(test_string.TestLatin1ExternalAutoLength(empty), empty) - assert.strictEqual(test_string.TestUtf16ExternalAutoLength(empty), empty) - assert.strictEqual(test_string.TestPropertyKeyUtf16(empty), empty) - assert.strictEqual(test_string.TestPropertyKeyUtf16AutoLength(empty), empty) - assert.strictEqual(test_string.Utf16Length(empty), 0) - assert.strictEqual(test_string.Utf8Length(empty), 0) + // The insufficient buffer test case allocates a buffer of size 4, including + // the null terminator. + const kInsufficientIdx = 3 - const str1 = 'hello world' - assert.strictEqual(test_string.TestLatin1(str1), str1) - assert.strictEqual(test_string.TestUtf8(str1), str1) - assert.strictEqual(test_string.TestUtf16(str1), str1) - assert.strictEqual(test_string.TestLatin1AutoLength(str1), str1) - assert.strictEqual(test_string.TestUtf8AutoLength(str1), str1) - assert.strictEqual(test_string.TestUtf16AutoLength(str1), str1) - assert.strictEqual(test_string.TestLatin1External(str1), str1) - assert.strictEqual(test_string.TestUtf16External(str1), str1) - assert.strictEqual(test_string.TestLatin1ExternalAutoLength(str1), str1) - assert.strictEqual(test_string.TestUtf16ExternalAutoLength(str1), str1) - assert.strictEqual(test_string.TestLatin1Insufficient(str1), str1.slice(0, 3)) - assert.strictEqual(test_string.TestUtf8Insufficient(str1), str1.slice(0, 3)) - assert.strictEqual(test_string.TestUtf16Insufficient(str1), str1.slice(0, 3)) - assert.strictEqual(test_string.TestPropertyKeyUtf16(str1), str1) - assert.strictEqual(test_string.TestPropertyKeyUtf16AutoLength(str1), str1) - assert.strictEqual(test_string.Utf16Length(str1), 11) - assert.strictEqual(test_string.Utf8Length(str1), 11) + const asciiCases = [ + '', + 'hello world', + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789', + '?!@#$%^&*()_+-=[]{}/.,<>\'"\\' + ] - const str2 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' - assert.strictEqual(test_string.TestLatin1(str2), str2) - assert.strictEqual(test_string.TestUtf8(str2), str2) - assert.strictEqual(test_string.TestUtf16(str2), str2) - assert.strictEqual(test_string.TestLatin1AutoLength(str2), str2) - assert.strictEqual(test_string.TestUtf8AutoLength(str2), str2) - assert.strictEqual(test_string.TestUtf16AutoLength(str2), str2) - assert.strictEqual(test_string.TestLatin1External(str2), str2) - assert.strictEqual(test_string.TestUtf16External(str2), str2) - assert.strictEqual(test_string.TestLatin1ExternalAutoLength(str2), str2) - assert.strictEqual(test_string.TestUtf16ExternalAutoLength(str2), str2) - assert.strictEqual(test_string.TestLatin1Insufficient(str2), str2.slice(0, 3)) - assert.strictEqual(test_string.TestUtf8Insufficient(str2), str2.slice(0, 3)) - assert.strictEqual(test_string.TestUtf16Insufficient(str2), str2.slice(0, 3)) - assert.strictEqual(test_string.TestPropertyKeyUtf16(str2), str2) - assert.strictEqual(test_string.TestPropertyKeyUtf16AutoLength(str2), str2) - assert.strictEqual(test_string.Utf16Length(str2), 62) - assert.strictEqual(test_string.Utf8Length(str2), 62) + const latin1Cases = [ + { + str: '¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿', + utf8Length: 62, + utf8InsufficientIdx: 1 + }, + { + str: 'ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþ', + utf8Length: 126, + utf8InsufficientIdx: 1 + } + ] - const str3 = '?!@#$%^&*()_+-=[]{}/.,<>\'"\\' - assert.strictEqual(test_string.TestLatin1(str3), str3) - assert.strictEqual(test_string.TestUtf8(str3), str3) - assert.strictEqual(test_string.TestUtf16(str3), str3) - assert.strictEqual(test_string.TestLatin1AutoLength(str3), str3) - assert.strictEqual(test_string.TestUtf8AutoLength(str3), str3) - assert.strictEqual(test_string.TestUtf16AutoLength(str3), str3) - assert.strictEqual(test_string.TestLatin1External(str3), str3) - assert.strictEqual(test_string.TestUtf16External(str3), str3) - assert.strictEqual(test_string.TestLatin1ExternalAutoLength(str3), str3) - assert.strictEqual(test_string.TestUtf16ExternalAutoLength(str3), str3) - assert.strictEqual(test_string.TestLatin1Insufficient(str3), str3.slice(0, 3)) - assert.strictEqual(test_string.TestUtf8Insufficient(str3), str3.slice(0, 3)) - assert.strictEqual(test_string.TestUtf16Insufficient(str3), str3.slice(0, 3)) - assert.strictEqual(test_string.TestPropertyKeyUtf16(str3), str3) - assert.strictEqual(test_string.TestPropertyKeyUtf16AutoLength(str3), str3) - assert.strictEqual(test_string.Utf16Length(str3), 27) - assert.strictEqual(test_string.Utf8Length(str3), 27) + const unicodeCases = [ + { + str: '\u{2003}\u{2101}\u{2001}\u{202}\u{2011}', + utf8Length: 14, + utf8InsufficientIdx: 1 + } + ] - const str4 = '¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿' - assert.strictEqual(test_string.TestLatin1(str4), str4) - assert.strictEqual(test_string.TestUtf8(str4), str4) - assert.strictEqual(test_string.TestUtf16(str4), str4) - assert.strictEqual(test_string.TestLatin1AutoLength(str4), str4) - assert.strictEqual(test_string.TestUtf8AutoLength(str4), str4) - assert.strictEqual(test_string.TestUtf16AutoLength(str4), str4) - assert.strictEqual(test_string.TestLatin1External(str4), str4) - assert.strictEqual(test_string.TestUtf16External(str4), str4) - assert.strictEqual(test_string.TestLatin1ExternalAutoLength(str4), str4) - assert.strictEqual(test_string.TestUtf16ExternalAutoLength(str4), str4) - assert.strictEqual(test_string.TestLatin1Insufficient(str4), str4.slice(0, 3)) - assert.strictEqual(test_string.TestUtf8Insufficient(str4), str4.slice(0, 1)) - assert.strictEqual(test_string.TestUtf16Insufficient(str4), str4.slice(0, 3)) - assert.strictEqual(test_string.TestPropertyKeyUtf16(str4), str4) - assert.strictEqual(test_string.TestPropertyKeyUtf16AutoLength(str4), str4) - assert.strictEqual(test_string.Utf16Length(str4), 31) - assert.strictEqual(test_string.Utf8Length(str4), 62) + function testLatin1Cases (str) { + assert.strictEqual(test_string.TestLatin1(str), str) + assert.strictEqual(test_string.TestLatin1AutoLength(str), str) + assert.strictEqual(test_string.TestLatin1External(str), str) + assert.strictEqual(test_string.TestLatin1ExternalAutoLength(str), str) + assert.strictEqual(test_string.TestPropertyKeyLatin1(str), str) + assert.strictEqual(test_string.TestPropertyKeyLatin1AutoLength(str), str) + assert.strictEqual(test_string.Latin1Length(str), str.length) - const str5 = 'ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþ' - assert.strictEqual(test_string.TestLatin1(str5), str5) - assert.strictEqual(test_string.TestUtf8(str5), str5) - assert.strictEqual(test_string.TestUtf16(str5), str5) - assert.strictEqual(test_string.TestLatin1AutoLength(str5), str5) - assert.strictEqual(test_string.TestUtf8AutoLength(str5), str5) - assert.strictEqual(test_string.TestUtf16AutoLength(str5), str5) - assert.strictEqual(test_string.TestLatin1External(str5), str5) - assert.strictEqual(test_string.TestUtf16External(str5), str5) - assert.strictEqual(test_string.TestLatin1ExternalAutoLength(str5), str5) - assert.strictEqual(test_string.TestUtf16ExternalAutoLength(str5), str5) - assert.strictEqual(test_string.TestLatin1Insufficient(str5), str5.slice(0, 3)) - assert.strictEqual(test_string.TestUtf8Insufficient(str5), str5.slice(0, 1)) - assert.strictEqual(test_string.TestUtf16Insufficient(str5), str5.slice(0, 3)) - assert.strictEqual(test_string.TestPropertyKeyUtf16(str5), str5) - assert.strictEqual(test_string.TestPropertyKeyUtf16AutoLength(str5), str5) - assert.strictEqual(test_string.Utf16Length(str5), 63) - assert.strictEqual(test_string.Utf8Length(str5), 126) + if (str !== '') { + assert.strictEqual(test_string.TestLatin1Insufficient(str), str.slice(0, kInsufficientIdx)) + } + } - const str6 = '\u{2003}\u{2101}\u{2001}\u{202}\u{2011}' - assert.strictEqual(test_string.TestUtf8(str6), str6) - assert.strictEqual(test_string.TestUtf16(str6), str6) - assert.strictEqual(test_string.TestUtf8AutoLength(str6), str6) - assert.strictEqual(test_string.TestUtf16AutoLength(str6), str6) - assert.strictEqual(test_string.TestUtf16External(str6), str6) - assert.strictEqual(test_string.TestUtf16ExternalAutoLength(str6), str6) - assert.strictEqual(test_string.TestUtf8Insufficient(str6), str6.slice(0, 1)) - assert.strictEqual(test_string.TestUtf16Insufficient(str6), str6.slice(0, 3)) - assert.strictEqual(test_string.TestPropertyKeyUtf16(str6), str6) - assert.strictEqual(test_string.TestPropertyKeyUtf16AutoLength(str6), str6) - assert.strictEqual(test_string.Utf16Length(str6), 5) - assert.strictEqual(test_string.Utf8Length(str6), 14) + function testUnicodeCases (str, utf8Length, utf8InsufficientIdx) { + assert.strictEqual(test_string.TestUtf8(str), str) + assert.strictEqual(test_string.TestUtf16(str), str) + assert.strictEqual(test_string.TestUtf8AutoLength(str), str) + assert.strictEqual(test_string.TestUtf16AutoLength(str), str) + assert.strictEqual(test_string.TestUtf16External(str), str) + assert.strictEqual(test_string.TestUtf16ExternalAutoLength(str), str) + assert.strictEqual(test_string.TestPropertyKeyUtf8(str), str) + assert.strictEqual(test_string.TestPropertyKeyUtf8AutoLength(str), str) + assert.strictEqual(test_string.TestPropertyKeyUtf16(str), str) + assert.strictEqual(test_string.TestPropertyKeyUtf16AutoLength(str), str) + assert.strictEqual(test_string.Utf8Length(str), utf8Length) + assert.strictEqual(test_string.Utf16Length(str), str.length) + + if (str !== '') { + assert.strictEqual(test_string.TestUtf8Insufficient(str), str.slice(0, utf8InsufficientIdx)) + assert.strictEqual(test_string.TestUtf16Insufficient(str), str.slice(0, kInsufficientIdx)) + } + } + + asciiCases.forEach(testLatin1Cases) + asciiCases.forEach((str) => testUnicodeCases(str, str.length, kInsufficientIdx)) + latin1Cases.forEach((it) => testLatin1Cases(it.str)) + latin1Cases.forEach((it) => testUnicodeCases(it.str, it.utf8Length, it.utf8InsufficientIdx)) + unicodeCases.forEach((it) => testUnicodeCases(it.str, it.utf8Length, it.utf8InsufficientIdx)) assert.throws(() => { test_string.TestLargeUtf8()