node-api: implement external strings

Introduce APIs that allow for the creation of JavaScript strings without
copying the underlying native string into the engine. The APIs fall back
to regular string creation if the engine's external string APIs are
unavailable. In this case, an optional boolean out-parameter indicates
that the string was copied, and the optional finalizer is called if
given.

PR-URL: https://github.com/nodejs/node/pull/48339
Fixes: https://github.com/nodejs/node/issues/48198
Reviewed-By: Daeyeon Jeong <daeyeon.dev@gmail.com>
Signed-off-by: Gabriel Schulhof <gabrielschulhof@gmail.com>
This commit is contained in:
Gabriel Schulhof 2023-06-02 16:03:55 -07:00 committed by Gabriel Schulhof
parent ac0853c4ee
commit 60d9aed307
11 changed files with 724 additions and 187 deletions

1
benchmark/napi/string/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
build/

View File

@ -0,0 +1,56 @@
#include <assert.h>
#define NAPI_EXPERIMENTAL
#include <node_api.h>
#define NAPI_CALL(call) \
do { \
napi_status status = call; \
assert(status == napi_ok && #call " failed"); \
} while (0);
#define EXPORT_FUNC(env, exports, name, func) \
do { \
napi_value js_func; \
NAPI_CALL(napi_create_function( \
(env), (name), NAPI_AUTO_LENGTH, (func), NULL, &js_func)); \
NAPI_CALL(napi_set_named_property((env), (exports), (name), js_func)); \
} while (0);
const char* one_byte_string = "The Quick Brown Fox Jumped Over The Lazy Dog.";
const char16_t* two_byte_string =
u"The Quick Brown Fox Jumped Over The Lazy Dog.";
#define DECLARE_BINDING(CapName, lowercase_name, var_name) \
static napi_value CreateString##CapName(napi_env env, \
napi_callback_info info) { \
size_t argc = 4; \
napi_value argv[4]; \
uint32_t n; \
uint32_t index; \
napi_handle_scope scope; \
napi_value js_string; \
\
NAPI_CALL(napi_get_cb_info(env, info, &argc, argv, NULL, NULL)); \
NAPI_CALL(napi_get_value_uint32(env, argv[0], &n)); \
NAPI_CALL(napi_open_handle_scope(env, &scope)); \
NAPI_CALL(napi_call_function(env, argv[1], argv[2], 0, NULL, NULL)); \
for (index = 0; index < n; index++) { \
NAPI_CALL(napi_create_string_##lowercase_name( \
env, (var_name), NAPI_AUTO_LENGTH, &js_string)); \
} \
NAPI_CALL(napi_call_function(env, argv[1], argv[3], 1, &argv[0], NULL)); \
NAPI_CALL(napi_close_handle_scope(env, scope)); \
\
return NULL; \
}
DECLARE_BINDING(Latin1, latin1, one_byte_string)
DECLARE_BINDING(Utf8, utf8, one_byte_string)
DECLARE_BINDING(Utf16, utf16, two_byte_string)
NAPI_MODULE_INIT() {
EXPORT_FUNC(env, exports, "createStringLatin1", CreateStringLatin1);
EXPORT_FUNC(env, exports, "createStringUtf8", CreateStringUtf8);
EXPORT_FUNC(env, exports, "createStringUtf16", CreateStringUtf16);
return exports;
}

View File

@ -0,0 +1,8 @@
{
'targets': [
{
'target_name': 'binding',
'sources': [ 'binding.c' ]
}
]
}

View File

@ -0,0 +1,19 @@
'use strict';
const common = require('../../common.js');
let binding;
try {
binding = require(`./build/${common.buildType}/binding`);
} catch {
console.error(`${__filename}: Binding failed to load`);
process.exit(0);
}
const bench = common.createBenchmark(main, {
n: [1e5, 1e6, 1e7],
stringType: ['Latin1', 'Utf8', 'Utf16'],
});
function main({ n, stringType }) {
binding[`createString${stringType}`](n, bench, bench.start, bench.end);
}

View File

@ -801,7 +801,7 @@ napiVersion: 1
Function pointer type for add-on provided functions that allow the user to be
notified when externally-owned data is ready to be cleaned up because the
object with which it was associated with, has been garbage-collected. The user
object with which it was associated with has been garbage-collected. The user
must provide a function satisfying the following signature which would get
called upon the object's collection. Currently, `napi_finalize` can be used for
finding out when objects that have external data are collected.
@ -819,6 +819,11 @@ Since these functions may be called while the JavaScript engine is in a state
where it cannot execute JavaScript code, some Node-API calls may return
`napi_pending_exception` even when there is no exception pending.
In the case of [`node_api_create_external_string_latin1`][] and
[`node_api_create_external_string_utf16`][] the `env` parameter may be null,
because external strings can be collected during the latter part of environment
shutdown.
Change History:
* experimental (`NAPI_EXPERIMENTAL` is defined):
@ -2886,6 +2891,56 @@ string. The native string is copied.
The JavaScript `string` type is described in
[Section 6.1.4][] of the ECMAScript Language Specification.
#### `node_api_create_external_string_latin1`
<!-- YAML
added: REPLACEME
-->
> Stability: 1 - Experimental
```c
napi_status
node_api_create_external_string_latin1(napi_env env,
char* str,
size_t length,
napi_finalize finalize_callback,
void* finalize_hint,
napi_value* result,
bool* copied);
```
* `[in] env`: The environment that the API is invoked under.
* `[in] str`: Character buffer representing an ISO-8859-1-encoded string.
* `[in] length`: The length of the string in bytes, or `NAPI_AUTO_LENGTH` if it
is null-terminated.
* `[in] finalize_callback`: The function to call when the string is being
collected. The function will be called with the following parameters:
* `[in] env`: The environment in which the add-on is running. This value
may be null if the string is being collected as part of the termination
of the worker or the main Node.js instance.
* `[in] data`: This is the value `str` as a `void*` pointer.
* `[in] finalize_hint`: This is the value `finalize_hint` that was given
to the API.
[`napi_finalize`][] provides more details.
This parameter is optional. Passing a null value means that the add-on
doesn't need to be notified when the corresponding JavaScript string is
collected.
* `[in] finalize_hint`: Optional hint to pass to the finalize callback during
collection.
* `[out] result`: A `napi_value` representing a JavaScript `string`.
* `[out] copied`: Whether the string was copied. If it was, the finalizer will
already have been invoked to destroy `str`.
Returns `napi_ok` if the API succeeded.
This API creates a JavaScript `string` value from an ISO-8859-1-encoded C
string. The native string may not be copied and must thus exist for the entire
life cycle of the JavaScript value.
The JavaScript `string` type is described in
[Section 6.1.4][] of the ECMAScript Language Specification.
#### `napi_create_string_utf16`
<!-- YAML
@ -2914,6 +2969,56 @@ The native string is copied.
The JavaScript `string` type is described in
[Section 6.1.4][] of the ECMAScript Language Specification.
#### `node_api_create_external_string_utf16`
<!-- YAML
added: REPLACEME
-->
> Stability: 1 - Experimental
```c
napi_status
node_api_create_external_string_utf16(napi_env env,
char16_t* str,
size_t length,
napi_finalize finalize_callback,
void* finalize_hint,
napi_value* result,
bool* copied);
```
* `[in] env`: The environment that the API is invoked under.
* `[in] str`: Character buffer representing a UTF16-LE-encoded string.
* `[in] length`: The length of the string in two-byte code units, or
`NAPI_AUTO_LENGTH` if it is null-terminated.
* `[in] finalize_callback`: The function to call when the string is being
collected. The function will be called with the following parameters:
* `[in] env`: The environment in which the add-on is running. This value
may be null if the string is being collected as part of the termination
of the worker or the main Node.js instance.
* `[in] data`: This is the value `str` as a `void*` pointer.
* `[in] finalize_hint`: This is the value `finalize_hint` that was given
to the API.
[`napi_finalize`][] provides more details.
This parameter is optional. Passing a null value means that the add-on
doesn't need to be notified when the corresponding JavaScript string is
collected.
* `[in] finalize_hint`: Optional hint to pass to the finalize callback during
collection.
* `[out] result`: A `napi_value` representing a JavaScript `string`.
* `[out] copied`: Whether the string was copied. If it was, the finalizer will
already have been invoked to destroy `str`.
Returns `napi_ok` if the API succeeded.
This API creates a JavaScript `string` value from a UTF16-LE-encoded C string.
The native string may not be copied and must thus exist for the entire life
cycle of the JavaScript value.
The JavaScript `string` type is described in
[Section 6.1.4][] of the ECMAScript Language Specification.
#### `napi_create_string_utf8`
<!-- YAML
@ -6476,6 +6581,8 @@ the add-on's file name during loading.
[`napi_wrap`]: #napi_wrap
[`node-addon-api`]: https://github.com/nodejs/node-addon-api
[`node_api.h`]: https://github.com/nodejs/node/blob/HEAD/src/node_api.h
[`node_api_create_external_string_latin1`]: #node_api_create_external_string_latin1
[`node_api_create_external_string_utf16`]: #node_api_create_external_string_utf16
[`node_api_create_syntax_error`]: #node_api_create_syntax_error
[`node_api_throw_syntax_error`]: #node_api_throw_syntax_error
[`process.release`]: process.md#processrelease

View File

@ -92,6 +92,24 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_create_string_utf16(napi_env env,
const char16_t* str,
size_t length,
napi_value* result);
#ifdef NAPI_EXPERIMENTAL
NAPI_EXTERN napi_status NAPI_CDECL
node_api_create_external_string_latin1(napi_env env,
char* str,
size_t length,
napi_finalize finalize_callback,
void* finalize_hint,
napi_value* result,
bool* copied);
NAPI_EXTERN napi_status NAPI_CDECL
node_api_create_external_string_utf16(napi_env env,
char16_t* str,
size_t length,
napi_finalize finalize_callback,
void* finalize_hint,
napi_value* result,
bool* copied);
#endif // NAPI_EXPERIMENTAL
NAPI_EXTERN napi_status NAPI_CDECL napi_create_symbol(napi_env env,
napi_value description,
napi_value* result);

View File

@ -61,6 +61,133 @@ namespace v8impl {
namespace {
template <typename CCharType, typename StringMaker>
napi_status NewString(napi_env env,
const CCharType* str,
size_t length,
napi_value* result,
StringMaker string_maker) {
CHECK_ENV(env);
if (length > 0) CHECK_ARG(env, str);
CHECK_ARG(env, result);
RETURN_STATUS_IF_FALSE(
env, (length == NAPI_AUTO_LENGTH) || length <= INT_MAX, napi_invalid_arg);
auto isolate = env->isolate;
auto str_maybe = string_maker(isolate);
CHECK_MAYBE_EMPTY(env, str_maybe, napi_generic_failure);
*result = v8impl::JsValueFromV8LocalValue(str_maybe.ToLocalChecked());
return napi_clear_last_error(env);
}
template <typename CharType, typename CreateAPI, typename StringMaker>
napi_status NewExternalString(napi_env env,
CharType* str,
size_t length,
napi_finalize finalize_callback,
void* finalize_hint,
napi_value* result,
bool* copied,
CreateAPI create_api,
StringMaker string_maker) {
napi_status status;
#if defined(V8_ENABLE_SANDBOX)
status = create_api(env, str, length, result);
if (status == napi_ok) {
if (copied != nullptr) {
*copied = true;
}
if (finalize_callback) {
env->CallFinalizer(
finalize_callback, static_cast<CharType*>(str), finalize_hint);
}
}
#else
status = NewString(env, str, length, result, string_maker);
if (status == napi_ok && copied != nullptr) {
*copied = false;
}
#endif // V8_ENABLE_SANDBOX
return status;
}
class TrackedStringResource : public Finalizer, RefTracker {
public:
TrackedStringResource(napi_env env,
napi_finalize finalize_callback,
void* data,
void* finalize_hint)
: Finalizer(env, finalize_callback, data, finalize_hint) {
Link(finalize_callback == nullptr ? &env->reflist
: &env->finalizing_reflist);
}
protected:
// The only time Finalize() gets called before Dispose() is if the
// environment is dying. Finalize() expects that the item will be unlinked,
// so we do it here. V8 will still call Dispose() on us later, so we don't do
// any deleting here. We just null out env_ to avoid passing a stale pointer
// to the user's finalizer when V8 does finally call Dispose().
void Finalize() override {
Unlink();
env_ = nullptr;
}
~TrackedStringResource() {
if (finalize_callback_ == nullptr) return;
if (env_ == nullptr) {
// The environment is dead. Call the finalizer directly.
finalize_callback_(nullptr, finalize_data_, finalize_hint_);
} else {
// The environment is still alive. Let's remove ourselves from its list
// of references and call the user's finalizer.
Unlink();
env_->CallFinalizer(finalize_callback_, finalize_data_, finalize_hint_);
}
}
};
class ExternalOneByteStringResource
: public v8::String::ExternalOneByteStringResource,
TrackedStringResource {
public:
ExternalOneByteStringResource(napi_env env,
char* string,
const size_t length,
napi_finalize finalize_callback,
void* finalize_hint)
: TrackedStringResource(env, finalize_callback, string, finalize_hint),
string_(string),
length_(length) {}
const char* data() const override { return string_; }
size_t length() const override { return length_; }
private:
const char* string_;
const size_t length_;
};
class ExternalStringResource : public v8::String::ExternalStringResource,
TrackedStringResource {
public:
ExternalStringResource(napi_env env,
char16_t* string,
const size_t length,
napi_finalize finalize_callback,
void* finalize_hint)
: TrackedStringResource(env, finalize_callback, string, finalize_hint),
string_(reinterpret_cast<uint16_t*>(string)),
length_(length) {}
const uint16_t* data() const override { return string_; }
size_t length() const override { return length_; }
private:
const uint16_t* string_;
const size_t length_;
};
inline napi_status V8NameFromPropertyDescriptor(
napi_env env,
const napi_property_descriptor* p,
@ -1389,62 +1516,88 @@ napi_status NAPI_CDECL napi_create_string_latin1(napi_env env,
const char* str,
size_t length,
napi_value* result) {
CHECK_ENV(env);
if (length > 0) CHECK_ARG(env, str);
CHECK_ARG(env, result);
RETURN_STATUS_IF_FALSE(
env, (length == NAPI_AUTO_LENGTH) || length <= INT_MAX, napi_invalid_arg);
auto isolate = env->isolate;
auto str_maybe =
v8::String::NewFromOneByte(isolate,
reinterpret_cast<const uint8_t*>(str),
v8::NewStringType::kNormal,
length);
CHECK_MAYBE_EMPTY(env, str_maybe, napi_generic_failure);
*result = v8impl::JsValueFromV8LocalValue(str_maybe.ToLocalChecked());
return napi_clear_last_error(env);
return v8impl::NewString(env, str, length, result, [&](v8::Isolate* isolate) {
return v8::String::NewFromOneByte(isolate,
reinterpret_cast<const uint8_t*>(str),
v8::NewStringType::kNormal,
length);
});
}
napi_status NAPI_CDECL napi_create_string_utf8(napi_env env,
const char* str,
size_t length,
napi_value* result) {
CHECK_ENV(env);
if (length > 0) CHECK_ARG(env, str);
CHECK_ARG(env, result);
RETURN_STATUS_IF_FALSE(
env, (length == NAPI_AUTO_LENGTH) || length <= INT_MAX, napi_invalid_arg);
auto isolate = env->isolate;
auto str_maybe = v8::String::NewFromUtf8(
isolate, str, v8::NewStringType::kNormal, static_cast<int>(length));
CHECK_MAYBE_EMPTY(env, str_maybe, napi_generic_failure);
*result = v8impl::JsValueFromV8LocalValue(str_maybe.ToLocalChecked());
return napi_clear_last_error(env);
return v8impl::NewString(env, str, length, result, [&](v8::Isolate* isolate) {
return v8::String::NewFromUtf8(
isolate, str, v8::NewStringType::kNormal, static_cast<int>(length));
});
}
napi_status NAPI_CDECL napi_create_string_utf16(napi_env env,
const char16_t* str,
size_t length,
napi_value* result) {
CHECK_ENV(env);
if (length > 0) CHECK_ARG(env, str);
CHECK_ARG(env, result);
RETURN_STATUS_IF_FALSE(
env, (length == NAPI_AUTO_LENGTH) || length <= INT_MAX, napi_invalid_arg);
return v8impl::NewString(env, str, length, result, [&](v8::Isolate* isolate) {
return v8::String::NewFromTwoByte(isolate,
reinterpret_cast<const uint16_t*>(str),
v8::NewStringType::kNormal,
length);
});
}
auto isolate = env->isolate;
auto str_maybe =
v8::String::NewFromTwoByte(isolate,
reinterpret_cast<const uint16_t*>(str),
v8::NewStringType::kNormal,
length);
CHECK_MAYBE_EMPTY(env, str_maybe, napi_generic_failure);
napi_status NAPI_CDECL
node_api_create_external_string_latin1(napi_env env,
char* str,
size_t length,
napi_finalize finalize_callback,
void* finalize_hint,
napi_value* result,
bool* copied) {
return v8impl::NewExternalString(
env,
str,
length,
finalize_callback,
finalize_hint,
result,
copied,
napi_create_string_latin1,
[&](v8::Isolate* isolate) {
if (length == NAPI_AUTO_LENGTH) {
length = (std::string_view(str)).length();
}
auto resource = new v8impl::ExternalOneByteStringResource(
env, str, length, finalize_callback, finalize_hint);
return v8::String::NewExternalOneByte(isolate, resource);
});
}
*result = v8impl::JsValueFromV8LocalValue(str_maybe.ToLocalChecked());
return napi_clear_last_error(env);
napi_status NAPI_CDECL
node_api_create_external_string_utf16(napi_env env,
char16_t* str,
size_t length,
napi_finalize finalize_callback,
void* finalize_hint,
napi_value* result,
bool* copied) {
return v8impl::NewExternalString(
env,
str,
length,
finalize_callback,
finalize_hint,
result,
copied,
napi_create_string_utf16,
[&](v8::Isolate* isolate) {
if (length == NAPI_AUTO_LENGTH) {
length = (std::u16string_view(str)).length();
}
auto resource = new v8impl::ExternalStringResource(
env, str, length, finalize_callback, finalize_hint);
return v8::String::NewExternalTwoByte(isolate, resource);
});
}
napi_status NAPI_CDECL napi_create_double(napi_env env,

View File

@ -56,6 +56,17 @@
#define NODE_API_CALL_RETURN_VOID(env, the_call) \
NODE_API_CALL_BASE(env, the_call, NODE_API_RETVAL_NOTHING)
#define NODE_API_CHECK_STATUS(the_call) \
do { \
napi_status status = (the_call); \
if (status != napi_ok) { \
return status; \
} \
} while (0)
#define NODE_API_ASSERT_STATUS(env, assertion, message) \
NODE_API_ASSERT_BASE(env, assertion, message, napi_generic_failure)
#define DECLARE_NODE_API_PROPERTY(name, func) \
{ (name), NULL, (func), NULL, NULL, NULL, napi_default, NULL }

View File

@ -9,6 +9,13 @@ 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.Utf16Length(empty), 0);
assert.strictEqual(test_string.Utf8Length(empty), 0);
@ -16,6 +23,13 @@ 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));
@ -26,6 +40,13 @@ 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));
@ -36,6 +57,13 @@ 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));
@ -46,6 +74,13 @@ 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));
@ -56,6 +91,13 @@ 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));
@ -65,6 +107,10 @@ assert.strictEqual(test_string.Utf8Length(str5), 126);
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.Utf16Length(str6), 5);

View File

@ -1,106 +1,254 @@
#include <limits.h> // INT_MAX
#include <stdlib.h>
#include <string.h>
#define NAPI_EXPERIMENTAL
#include <js_native_api.h>
#include "../common.h"
#include "test_null.h"
static napi_value TestLatin1(napi_env env, napi_callback_info info) {
size_t argc = 1;
napi_value args[1];
NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL));
enum length_type { actual_length, auto_length };
NODE_API_ASSERT(env, argc >= 1, "Wrong number of arguments");
static napi_status validate_and_retrieve_single_string_arg(
napi_env env, napi_callback_info info, napi_value* arg) {
size_t argc = 1;
NODE_API_CHECK_STATUS(napi_get_cb_info(env, info, &argc, arg, NULL, NULL));
NODE_API_ASSERT_STATUS(env, argc >= 1, "Wrong number of arguments");
napi_valuetype valuetype;
NODE_API_CALL(env, napi_typeof(env, args[0], &valuetype));
NODE_API_CHECK_STATUS(napi_typeof(env, *arg, &valuetype));
NODE_API_ASSERT(env, valuetype == napi_string,
"Wrong type of argment. Expects a string.");
NODE_API_ASSERT_STATUS(env,
valuetype == napi_string,
"Wrong type of argment. Expects a string.");
return napi_ok;
}
// These help us factor out code that is common between the bindings.
typedef napi_status (*OneByteCreateAPI)(napi_env,
const char*,
size_t,
napi_value*);
typedef napi_status (*OneByteGetAPI)(
napi_env, napi_value, char*, size_t, size_t*);
typedef napi_status (*TwoByteCreateAPI)(napi_env,
const char16_t*,
size_t,
napi_value*);
typedef napi_status (*TwoByteGetAPI)(
napi_env, napi_value, char16_t*, size_t, size_t*);
// Test passing back the one-byte string we got from JS.
static napi_value TestOneByteImpl(napi_env env,
napi_callback_info info,
OneByteGetAPI get_api,
OneByteCreateAPI create_api,
enum length_type length_mode) {
napi_value args[1];
NODE_API_CALL(env, validate_and_retrieve_single_string_arg(env, info, args));
char buffer[128];
size_t buffer_size = 128;
size_t copied;
NODE_API_CALL(env,
napi_get_value_string_latin1(env, args[0], buffer, buffer_size, &copied));
NODE_API_CALL(env, get_api(env, args[0], buffer, buffer_size, &copied));
napi_value output;
NODE_API_CALL(env, napi_create_string_latin1(env, buffer, copied, &output));
if (length_mode == auto_length) {
copied = NAPI_AUTO_LENGTH;
}
NODE_API_CALL(env, create_api(env, buffer, copied, &output));
return output;
}
static napi_value TestUtf8(napi_env env, napi_callback_info info) {
size_t argc = 1;
// Test passing back the two-byte string we got from JS.
static napi_value TestTwoByteImpl(napi_env env,
napi_callback_info info,
TwoByteGetAPI get_api,
TwoByteCreateAPI create_api,
enum length_type length_mode) {
napi_value args[1];
NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL));
NODE_API_ASSERT(env, argc >= 1, "Wrong number of arguments");
napi_valuetype valuetype;
NODE_API_CALL(env, napi_typeof(env, args[0], &valuetype));
NODE_API_ASSERT(env, valuetype == napi_string,
"Wrong type of argment. Expects a string.");
char buffer[128];
size_t buffer_size = 128;
size_t copied;
NODE_API_CALL(env,
napi_get_value_string_utf8(env, args[0], buffer, buffer_size, &copied));
napi_value output;
NODE_API_CALL(env, napi_create_string_utf8(env, buffer, copied, &output));
return output;
}
static napi_value TestUtf16(napi_env env, napi_callback_info info) {
size_t argc = 1;
napi_value args[1];
NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL));
NODE_API_ASSERT(env, argc >= 1, "Wrong number of arguments");
napi_valuetype valuetype;
NODE_API_CALL(env, napi_typeof(env, args[0], &valuetype));
NODE_API_ASSERT(env, valuetype == napi_string,
"Wrong type of argment. Expects a string.");
NODE_API_CALL(env, validate_and_retrieve_single_string_arg(env, info, args));
char16_t buffer[128];
size_t buffer_size = 128;
size_t copied;
NODE_API_CALL(env,
napi_get_value_string_utf16(env, args[0], buffer, buffer_size, &copied));
NODE_API_CALL(env, get_api(env, args[0], buffer, buffer_size, &copied));
napi_value output;
NODE_API_CALL(env, napi_create_string_utf16(env, buffer, copied, &output));
if (length_mode == auto_length) {
copied = NAPI_AUTO_LENGTH;
}
NODE_API_CALL(env, create_api(env, buffer, copied, &output));
return output;
}
static napi_value
TestLatin1Insufficient(napi_env env, napi_callback_info info) {
size_t argc = 1;
static void free_string(napi_env env, void* data, void* hint) {
free(data);
}
static napi_status create_external_latin1(napi_env env,
const char* string,
size_t length,
napi_value* result) {
napi_status status;
// Initialize to true, because that is the value we don't want.
bool copied = true;
char* string_copy;
const size_t actual_length =
(length == NAPI_AUTO_LENGTH ? strlen(string) : length);
const size_t length_bytes = (actual_length + 1) * sizeof(*string_copy);
string_copy = malloc(length_bytes);
memcpy(string_copy, string, length_bytes);
string_copy[actual_length] = 0;
status = node_api_create_external_string_latin1(
env, string_copy, length, free_string, NULL, result, &copied);
// We do not want the string to be copied.
if (copied) {
return napi_generic_failure;
}
if (status != napi_ok) {
free(string_copy);
return status;
}
return napi_ok;
}
// strlen for char16_t. Needed in case we're copying a string of length
// NAPI_AUTO_LENGTH.
static size_t strlen16(const char16_t* string) {
for (const char16_t* iter = string;; iter++) {
if (*iter == 0) {
return iter - string;
}
}
// We should never get here.
abort();
}
static napi_status create_external_utf16(napi_env env,
const char16_t* string,
size_t length,
napi_value* result) {
napi_status status;
// Initialize to true, because that is the value we don't want.
bool copied = true;
char16_t* string_copy;
const size_t actual_length =
(length == NAPI_AUTO_LENGTH ? strlen16(string) : length);
const size_t length_bytes = (actual_length + 1) * sizeof(*string_copy);
string_copy = malloc(length_bytes);
memcpy(string_copy, string, length_bytes);
string_copy[actual_length] = 0;
status = node_api_create_external_string_utf16(
env, string_copy, length, free_string, NULL, result, &copied);
if (status != napi_ok) {
free(string_copy);
return status;
}
return napi_ok;
}
static napi_value TestLatin1(napi_env env, napi_callback_info info) {
return TestOneByteImpl(env,
info,
napi_get_value_string_latin1,
napi_create_string_latin1,
actual_length);
}
static napi_value TestUtf8(napi_env env, napi_callback_info info) {
return TestOneByteImpl(env,
info,
napi_get_value_string_utf8,
napi_create_string_utf8,
actual_length);
}
static napi_value TestUtf16(napi_env env, napi_callback_info info) {
return TestTwoByteImpl(env,
info,
napi_get_value_string_utf16,
napi_create_string_utf16,
actual_length);
}
static napi_value TestLatin1AutoLength(napi_env env, napi_callback_info info) {
return TestOneByteImpl(env,
info,
napi_get_value_string_latin1,
napi_create_string_latin1,
auto_length);
}
static napi_value TestUtf8AutoLength(napi_env env, napi_callback_info info) {
return TestOneByteImpl(env,
info,
napi_get_value_string_utf8,
napi_create_string_utf8,
auto_length);
}
static napi_value TestUtf16AutoLength(napi_env env, napi_callback_info info) {
return TestTwoByteImpl(env,
info,
napi_get_value_string_utf16,
napi_create_string_utf16,
auto_length);
}
static napi_value TestLatin1External(napi_env env, napi_callback_info info) {
return TestOneByteImpl(env,
info,
napi_get_value_string_latin1,
create_external_latin1,
actual_length);
}
static napi_value TestUtf16External(napi_env env, napi_callback_info info) {
return TestTwoByteImpl(env,
info,
napi_get_value_string_utf16,
create_external_utf16,
actual_length);
}
static napi_value TestLatin1ExternalAutoLength(napi_env env,
napi_callback_info info) {
return TestOneByteImpl(env,
info,
napi_get_value_string_latin1,
create_external_latin1,
auto_length);
}
static napi_value TestUtf16ExternalAutoLength(napi_env env,
napi_callback_info info) {
return TestTwoByteImpl(env,
info,
napi_get_value_string_utf16,
create_external_utf16,
auto_length);
}
static napi_value TestLatin1Insufficient(napi_env env,
napi_callback_info info) {
napi_value args[1];
NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL));
NODE_API_ASSERT(env, argc >= 1, "Wrong number of arguments");
napi_valuetype valuetype;
NODE_API_CALL(env, napi_typeof(env, args[0], &valuetype));
NODE_API_ASSERT(env, valuetype == napi_string,
"Wrong type of argment. Expects a string.");
NODE_API_CALL(env, validate_and_retrieve_single_string_arg(env, info, args));
char buffer[4];
size_t buffer_size = 4;
size_t copied;
NODE_API_CALL(env,
NODE_API_CALL(
env,
napi_get_value_string_latin1(env, args[0], buffer, buffer_size, &copied));
napi_value output;
@ -110,23 +258,15 @@ TestLatin1Insufficient(napi_env env, napi_callback_info info) {
}
static napi_value TestUtf8Insufficient(napi_env env, napi_callback_info info) {
size_t argc = 1;
napi_value args[1];
NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL));
NODE_API_ASSERT(env, argc >= 1, "Wrong number of arguments");
napi_valuetype valuetype;
NODE_API_CALL(env, napi_typeof(env, args[0], &valuetype));
NODE_API_ASSERT(env, valuetype == napi_string,
"Wrong type of argment. Expects a string.");
NODE_API_CALL(env, validate_and_retrieve_single_string_arg(env, info, args));
char buffer[4];
size_t buffer_size = 4;
size_t copied;
NODE_API_CALL(env,
NODE_API_CALL(
env,
napi_get_value_string_utf8(env, args[0], buffer, buffer_size, &copied));
napi_value output;
@ -136,23 +276,15 @@ static napi_value TestUtf8Insufficient(napi_env env, napi_callback_info info) {
}
static napi_value TestUtf16Insufficient(napi_env env, napi_callback_info info) {
size_t argc = 1;
napi_value args[1];
NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL));
NODE_API_ASSERT(env, argc >= 1, "Wrong number of arguments");
napi_valuetype valuetype;
NODE_API_CALL(env, napi_typeof(env, args[0], &valuetype));
NODE_API_ASSERT(env, valuetype == napi_string,
"Wrong type of argment. Expects a string.");
NODE_API_CALL(env, validate_and_retrieve_single_string_arg(env, info, args));
char16_t buffer[4];
size_t buffer_size = 4;
size_t copied;
NODE_API_CALL(env,
NODE_API_CALL(
env,
napi_get_value_string_utf16(env, args[0], buffer, buffer_size, &copied));
napi_value output;
@ -162,20 +294,12 @@ static napi_value TestUtf16Insufficient(napi_env env, napi_callback_info info) {
}
static napi_value Utf16Length(napi_env env, napi_callback_info info) {
size_t argc = 1;
napi_value args[1];
NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL));
NODE_API_ASSERT(env, argc >= 1, "Wrong number of arguments");
napi_valuetype valuetype;
NODE_API_CALL(env, napi_typeof(env, args[0], &valuetype));
NODE_API_ASSERT(env, valuetype == napi_string,
"Wrong type of argment. Expects a string.");
NODE_API_CALL(env, validate_and_retrieve_single_string_arg(env, info, args));
size_t length;
NODE_API_CALL(env, napi_get_value_string_utf16(env, args[0], NULL, 0, &length));
NODE_API_CALL(env,
napi_get_value_string_utf16(env, args[0], NULL, 0, &length));
napi_value output;
NODE_API_CALL(env, napi_create_uint32(env, (uint32_t)length, &output));
@ -184,21 +308,12 @@ static napi_value Utf16Length(napi_env env, napi_callback_info info) {
}
static napi_value Utf8Length(napi_env env, napi_callback_info info) {
size_t argc = 1;
napi_value args[1];
NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL));
NODE_API_ASSERT(env, argc >= 1, "Wrong number of arguments");
napi_valuetype valuetype;
NODE_API_CALL(env, napi_typeof(env, args[0], &valuetype));
NODE_API_ASSERT(env, valuetype == napi_string,
"Wrong type of argment. Expects a string.");
NODE_API_CALL(env, validate_and_retrieve_single_string_arg(env, info, args));
size_t length;
NODE_API_CALL(env,
napi_get_value_string_utf8(env, args[0], NULL, 0, &length));
napi_get_value_string_utf8(env, args[0], NULL, 0, &length));
napi_value output;
NODE_API_CALL(env, napi_create_uint32(env, (uint32_t)length, &output));
@ -209,8 +324,8 @@ static napi_value Utf8Length(napi_env env, napi_callback_info info) {
static napi_value TestLargeUtf8(napi_env env, napi_callback_info info) {
napi_value output;
if (SIZE_MAX > INT_MAX) {
NODE_API_CALL(env,
napi_create_string_utf8(env, "", ((size_t)INT_MAX) + 1, &output));
NODE_API_CALL(
env, napi_create_string_utf8(env, "", ((size_t)INT_MAX) + 1, &output));
} else {
// just throw the expected error as there is nothing to test
// in this case since we can't overflow
@ -223,7 +338,8 @@ static napi_value TestLargeUtf8(napi_env env, napi_callback_info info) {
static napi_value TestLargeLatin1(napi_env env, napi_callback_info info) {
napi_value output;
if (SIZE_MAX > INT_MAX) {
NODE_API_CALL(env,
NODE_API_CALL(
env,
napi_create_string_latin1(env, "", ((size_t)INT_MAX) + 1, &output));
} else {
// just throw the expected error as there is nothing to test
@ -237,7 +353,8 @@ static napi_value TestLargeLatin1(napi_env env, napi_callback_info info) {
static napi_value TestLargeUtf16(napi_env env, napi_callback_info info) {
napi_value output;
if (SIZE_MAX > INT_MAX) {
NODE_API_CALL(env,
NODE_API_CALL(
env,
napi_create_string_utf16(
env, ((const char16_t*)""), ((size_t)INT_MAX) + 1, &output));
} else {
@ -256,10 +373,10 @@ static napi_value TestMemoryCorruption(napi_env env, napi_callback_info info) {
NODE_API_ASSERT(env, argc == 1, "Wrong number of arguments");
char buf[10] = { 0 };
char buf[10] = {0};
NODE_API_CALL(env, napi_get_value_string_utf8(env, args[0], buf, 0, NULL));
char zero[10] = { 0 };
char zero[10] = {0};
if (memcmp(buf, zero, sizeof(buf)) != 0) {
NODE_API_CALL(env, napi_throw_error(env, NULL, "Buffer overwritten"));
}
@ -270,24 +387,36 @@ static napi_value TestMemoryCorruption(napi_env env, napi_callback_info info) {
EXTERN_C_START
napi_value Init(napi_env env, napi_value exports) {
napi_property_descriptor properties[] = {
DECLARE_NODE_API_PROPERTY("TestLatin1", TestLatin1),
DECLARE_NODE_API_PROPERTY("TestLatin1Insufficient", TestLatin1Insufficient),
DECLARE_NODE_API_PROPERTY("TestUtf8", TestUtf8),
DECLARE_NODE_API_PROPERTY("TestUtf8Insufficient", TestUtf8Insufficient),
DECLARE_NODE_API_PROPERTY("TestUtf16", TestUtf16),
DECLARE_NODE_API_PROPERTY("TestUtf16Insufficient", TestUtf16Insufficient),
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("TestLatin1", TestLatin1),
DECLARE_NODE_API_PROPERTY("TestLatin1AutoLength", TestLatin1AutoLength),
DECLARE_NODE_API_PROPERTY("TestLatin1External", TestLatin1External),
DECLARE_NODE_API_PROPERTY("TestLatin1ExternalAutoLength",
TestLatin1ExternalAutoLength),
DECLARE_NODE_API_PROPERTY("TestLatin1Insufficient",
TestLatin1Insufficient),
DECLARE_NODE_API_PROPERTY("TestUtf8", TestUtf8),
DECLARE_NODE_API_PROPERTY("TestUtf8AutoLength", TestUtf8AutoLength),
DECLARE_NODE_API_PROPERTY("TestUtf8Insufficient", TestUtf8Insufficient),
DECLARE_NODE_API_PROPERTY("TestUtf16", TestUtf16),
DECLARE_NODE_API_PROPERTY("TestUtf16AutoLength", TestUtf16AutoLength),
DECLARE_NODE_API_PROPERTY("TestUtf16External", TestUtf16External),
DECLARE_NODE_API_PROPERTY("TestUtf16ExternalAutoLength",
TestUtf16ExternalAutoLength),
DECLARE_NODE_API_PROPERTY("TestUtf16Insufficient", TestUtf16Insufficient),
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),
};
init_test_null(env, exports);
NODE_API_CALL(env, napi_define_properties(
env, exports, sizeof(properties) / sizeof(*properties), properties));
NODE_API_CALL(
env,
napi_define_properties(
env, exports, sizeof(properties) / sizeof(*properties), properties));
return exports;
}

View File

@ -2,17 +2,6 @@
#include "../../js-native-api/common.h"
#include "stdlib.h"
#define NODE_API_ASSERT_STATUS(env, assertion, message) \
NODE_API_ASSERT_BASE(env, assertion, message, napi_generic_failure)
#define NODE_API_CHECK_STATUS(env, the_call) \
do { \
napi_status status = (the_call); \
if (status != napi_ok) { \
return status; \
} \
} while (0)
static uint32_t finalizeCount = 0;
static void FreeData(napi_env env, void* data, void* hint) {
@ -29,7 +18,7 @@ static napi_status GetArgValue(napi_env env,
napi_value* argValue) {
size_t argc = 1;
NODE_API_CHECK_STATUS(
env, napi_get_cb_info(env, info, &argc, argValue, NULL, NULL));
napi_get_cb_info(env, info, &argc, argValue, NULL, NULL));
NODE_API_ASSERT_STATUS(env, argc == 1, "Expects one arg.");
return napi_ok;
@ -39,10 +28,10 @@ static napi_status GetArgValueAsIndex(napi_env env,
napi_callback_info info,
uint32_t* index) {
napi_value argValue;
NODE_API_CHECK_STATUS(env, GetArgValue(env, info, &argValue));
NODE_API_CHECK_STATUS(GetArgValue(env, info, &argValue));
napi_valuetype valueType;
NODE_API_CHECK_STATUS(env, napi_typeof(env, argValue, &valueType));
NODE_API_CHECK_STATUS(napi_typeof(env, argValue, &valueType));
NODE_API_ASSERT_STATUS(
env, valueType == napi_number, "Argument must be a number.");
@ -53,10 +42,10 @@ static napi_status GetRef(napi_env env,
napi_callback_info info,
napi_ref* ref) {
uint32_t index;
NODE_API_CHECK_STATUS(env, GetArgValueAsIndex(env, info, &index));
NODE_API_CHECK_STATUS(GetArgValueAsIndex(env, info, &index));
napi_ref* refValues;
NODE_API_CHECK_STATUS(env, napi_get_instance_data(env, (void**)&refValues));
NODE_API_CHECK_STATUS(napi_get_instance_data(env, (void**)&refValues));
NODE_API_ASSERT_STATUS(env, refValues != NULL, "Cannot get instance data.");
*ref = refValues[index];