mirror of
https://github.com/oven-sh/bun
synced 2026-02-10 10:58:56 +00:00
382 lines
13 KiB
C++
382 lines
13 KiB
C++
#include "standalone_tests.h"
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <iostream>
|
|
#include <string>
|
|
|
|
#include "utils.h"
|
|
|
|
namespace napitests {
|
|
|
|
// https://github.com/oven-sh/bun/issues/7685
|
|
static napi_value test_issue_7685(const Napi::CallbackInfo &info) {
|
|
Napi::Env env(info.Env());
|
|
Napi::HandleScope scope(env);
|
|
// info[0] is a function to run the GC
|
|
NODE_API_ASSERT(env, info[1].IsNumber());
|
|
NODE_API_ASSERT(env, info[2].IsNumber());
|
|
NODE_API_ASSERT(env, info[3].IsNumber());
|
|
NODE_API_ASSERT(env, info[4].IsNumber());
|
|
NODE_API_ASSERT(env, info[5].IsNumber());
|
|
NODE_API_ASSERT(env, info[6].IsNumber());
|
|
NODE_API_ASSERT(env, info[7].IsNumber());
|
|
NODE_API_ASSERT(env, info[8].IsNumber());
|
|
return ok(env);
|
|
}
|
|
|
|
static napi_threadsafe_function tsfn_11949 = nullptr;
|
|
|
|
static void test_issue_11949_callback(napi_env env, napi_value js_callback,
|
|
void *opaque_context, void *opaque_data) {
|
|
int *context = reinterpret_cast<int *>(opaque_context);
|
|
int *data = reinterpret_cast<int *>(opaque_data);
|
|
printf("data = %d, context = %d\n", *data, *context);
|
|
delete context;
|
|
delete data;
|
|
napi_unref_threadsafe_function(env, tsfn_11949);
|
|
tsfn_11949 = nullptr;
|
|
}
|
|
|
|
// https://github.com/oven-sh/bun/issues/11949
|
|
static napi_value test_issue_11949(const Napi::CallbackInfo &info) {
|
|
Napi::Env env = info.Env();
|
|
Napi::HandleScope scope(env);
|
|
napi_value name = Napi::String::New(env, "TSFN");
|
|
|
|
int *context = new int(42);
|
|
int *data = new int(1234);
|
|
|
|
NODE_API_CALL(env,
|
|
napi_create_threadsafe_function(
|
|
env, /* JavaScript function */ nullptr,
|
|
/* async resource */ nullptr, name,
|
|
/* max queue size (unlimited) */ 0,
|
|
/* initial thread count */ 1, /* finalize data */ nullptr,
|
|
/* finalize callback */ nullptr, context,
|
|
&test_issue_11949_callback, &tsfn_11949));
|
|
NODE_API_CALL(env, napi_call_threadsafe_function(tsfn_11949, data,
|
|
napi_tsfn_nonblocking));
|
|
return env.Undefined();
|
|
}
|
|
|
|
static void noop_callback(napi_env env, napi_value js_callback, void *context,
|
|
void *data) {}
|
|
|
|
static napi_value test_napi_threadsafe_function_does_not_hang_after_finalize(
|
|
const Napi::CallbackInfo &info) {
|
|
|
|
Napi::Env env = info.Env();
|
|
|
|
napi_value resource_name = Napi::String::New(env, "simple");
|
|
|
|
napi_threadsafe_function cb;
|
|
NODE_API_CALL(env,
|
|
napi_create_threadsafe_function(
|
|
env, /* JavaScript function */ nullptr,
|
|
/* async resource */ nullptr, resource_name,
|
|
/* max queue size (unlimited) */ 0,
|
|
/* initial thread count */ 1, /* finalize data */ nullptr,
|
|
/* finalize callback */ nullptr, /* context */ nullptr,
|
|
&noop_callback, &cb));
|
|
|
|
NODE_API_CALL(env, napi_release_threadsafe_function(cb, napi_tsfn_release));
|
|
printf("success!\n");
|
|
return env.Undefined();
|
|
}
|
|
|
|
static napi_value
|
|
test_napi_get_value_string_utf8_with_buffer(const Napi::CallbackInfo &info) {
|
|
Napi::Env env = info.Env();
|
|
|
|
// info[0] is a function to run the GC
|
|
napi_value string_js = info[1];
|
|
// get how many chars we need to copy
|
|
size_t len = info[2].As<Napi::Number>().Uint32Value();
|
|
|
|
if (len == 424242) {
|
|
len = NAPI_AUTO_LENGTH;
|
|
} else {
|
|
NODE_API_ASSERT(env, len <= 29);
|
|
}
|
|
|
|
size_t copied;
|
|
const size_t BUF_SIZE = 30;
|
|
char buf[BUF_SIZE];
|
|
memset(buf, '*', BUF_SIZE);
|
|
buf[BUF_SIZE - 1] = '\0';
|
|
|
|
NODE_API_CALL(env,
|
|
napi_get_value_string_utf8(env, string_js, buf, len, &copied));
|
|
|
|
std::cout << "Chars to copy: " << len << std::endl;
|
|
std::cout << "Copied chars: " << copied << std::endl;
|
|
std::cout << "Buffer: ";
|
|
for (size_t i = 0; i < BUF_SIZE; i++) {
|
|
std::cout << (int)buf[i] << ", ";
|
|
}
|
|
std::cout << std::endl;
|
|
std::cout << "Value str: " << buf << std::endl;
|
|
return ok(env);
|
|
}
|
|
|
|
static napi_value
|
|
test_napi_handle_scope_string(const Napi::CallbackInfo &info) {
|
|
// this is mostly a copy of test_handle_scope_gc from
|
|
// test/v8/v8-module/main.cpp -- see comments there for explanation
|
|
Napi::Env env = info.Env();
|
|
|
|
constexpr size_t num_small_strings = 10000;
|
|
|
|
auto *small_strings = new napi_value[num_small_strings];
|
|
|
|
for (size_t i = 0; i < num_small_strings; i++) {
|
|
std::string cpp_str = std::to_string(i);
|
|
NODE_API_CALL(env,
|
|
napi_create_string_utf8(env, cpp_str.c_str(), cpp_str.size(),
|
|
&small_strings[i]));
|
|
}
|
|
|
|
run_gc(info);
|
|
|
|
for (size_t j = 0; j < num_small_strings; j++) {
|
|
char buf[16];
|
|
size_t result;
|
|
NODE_API_CALL(env, napi_get_value_string_utf8(env, small_strings[j], buf,
|
|
sizeof buf, &result));
|
|
NODE_API_ASSERT(env, atoi(buf) == (int)j);
|
|
}
|
|
|
|
delete[] small_strings;
|
|
return ok(env);
|
|
}
|
|
|
|
static napi_value
|
|
test_napi_handle_scope_bigint(const Napi::CallbackInfo &info) {
|
|
// this is mostly a copy of test_handle_scope_gc from
|
|
// test/v8/v8-module/main.cpp -- see comments there for explanation
|
|
Napi::Env env = info.Env();
|
|
|
|
constexpr size_t num_small_ints = 10000;
|
|
constexpr size_t small_int_size = 100;
|
|
|
|
auto *small_ints = new napi_value[num_small_ints];
|
|
|
|
for (size_t i = 0; i < num_small_ints; i++) {
|
|
std::array<uint64_t, small_int_size> words;
|
|
words.fill(i + 1);
|
|
NODE_API_CALL(env, napi_create_bigint_words(env, 0, small_int_size,
|
|
words.data(), &small_ints[i]));
|
|
}
|
|
|
|
run_gc(info);
|
|
|
|
for (size_t j = 0; j < num_small_ints; j++) {
|
|
std::array<uint64_t, small_int_size> words;
|
|
int sign;
|
|
size_t word_count = words.size();
|
|
NODE_API_CALL(env, napi_get_value_bigint_words(env, small_ints[j], &sign,
|
|
&word_count, words.data()));
|
|
printf("%d, %zu\n", sign, word_count);
|
|
NODE_API_ASSERT(env, sign == 0 && word_count == words.size());
|
|
NODE_API_ASSERT(env,
|
|
std::all_of(words.begin(), words.end(),
|
|
[j](const uint64_t &w) { return w == j + 1; }));
|
|
}
|
|
|
|
delete[] small_ints;
|
|
return ok(env);
|
|
}
|
|
|
|
static napi_value test_napi_delete_property(const Napi::CallbackInfo &info) {
|
|
Napi::Env env = info.Env();
|
|
|
|
// info[0] is a function to run the GC
|
|
napi_value object = info[1];
|
|
napi_valuetype type = get_typeof(env, object);
|
|
NODE_API_ASSERT(env, type == napi_object);
|
|
|
|
napi_value key = Napi::String::New(env, "foo");
|
|
|
|
napi_value non_configurable_key = Napi::String::New(env, "bar");
|
|
|
|
napi_value val;
|
|
NODE_API_CALL(env, napi_create_int32(env, 42, &val));
|
|
|
|
bool delete_result;
|
|
NODE_API_CALL(env, napi_delete_property(env, object, non_configurable_key,
|
|
&delete_result));
|
|
NODE_API_ASSERT(env, delete_result == false);
|
|
|
|
NODE_API_CALL(env, napi_delete_property(env, object, key, &delete_result));
|
|
NODE_API_ASSERT(env, delete_result == true);
|
|
|
|
bool has_property;
|
|
NODE_API_CALL(env, napi_has_property(env, object, key, &has_property));
|
|
NODE_API_ASSERT(env, has_property == false);
|
|
|
|
return ok(env);
|
|
}
|
|
|
|
// Returns false if any napi function failed
|
|
static bool store_escaped_handle(napi_env env, napi_value *out,
|
|
const char *str) {
|
|
// Allocate these values on the heap so they cannot be seen by stack scanning
|
|
// after this function returns. An earlier version tried putting them on the
|
|
// stack and using volatile stores to set them to nullptr, but that wasn't
|
|
// effective when the NAPI module was built in release mode as extra copies of
|
|
// the pointers would still be left in uninitialized stack memory.
|
|
napi_escapable_handle_scope *ehs = new napi_escapable_handle_scope;
|
|
napi_value *s = new napi_value;
|
|
napi_value *escaped = new napi_value;
|
|
NODE_API_CALL_CUSTOM_RETURN(env, false,
|
|
napi_open_escapable_handle_scope(env, ehs));
|
|
NODE_API_CALL_CUSTOM_RETURN(
|
|
env, false, napi_create_string_utf8(env, str, NAPI_AUTO_LENGTH, s));
|
|
NODE_API_CALL_CUSTOM_RETURN(env, false,
|
|
napi_escape_handle(env, *ehs, *s, escaped));
|
|
// can't call a second time
|
|
NODE_API_ASSERT_CUSTOM_RETURN(env, false,
|
|
napi_escape_handle(env, *ehs, *s, escaped) ==
|
|
napi_escape_called_twice);
|
|
NODE_API_CALL_CUSTOM_RETURN(env, false,
|
|
napi_close_escapable_handle_scope(env, *ehs));
|
|
*out = *escaped;
|
|
|
|
delete escaped;
|
|
delete s;
|
|
delete ehs;
|
|
return true;
|
|
}
|
|
|
|
static napi_value
|
|
test_napi_escapable_handle_scope(const Napi::CallbackInfo &info) {
|
|
Napi::Env env = info.Env();
|
|
|
|
// allocate space for a napi_value on the heap
|
|
// use store_escaped_handle to put the value into it
|
|
// trigger GC
|
|
// the napi_value should still be valid even though it can't be found on the
|
|
// stack, because it escaped into the current handle scope
|
|
|
|
constexpr const char *str = "this is a long string meow meow meow";
|
|
|
|
napi_value *hidden = new napi_value;
|
|
NODE_API_ASSERT(env, store_escaped_handle(env, hidden, str));
|
|
|
|
run_gc(info);
|
|
|
|
char buf[64];
|
|
size_t len;
|
|
NODE_API_CALL(
|
|
env, napi_get_value_string_utf8(env, *hidden, buf, sizeof(buf), &len));
|
|
NODE_API_ASSERT(env, len == strlen(str));
|
|
NODE_API_ASSERT(env, strcmp(buf, str) == 0);
|
|
|
|
delete hidden;
|
|
return ok(env);
|
|
}
|
|
|
|
static napi_value
|
|
test_napi_handle_scope_nesting(const Napi::CallbackInfo &info) {
|
|
Napi::Env env = info.Env();
|
|
constexpr const char *str = "this is a long string meow meow meow";
|
|
|
|
// Create an outer handle scope, hidden on the heap (the one created in
|
|
// NAPIFunction::call is still on the stack
|
|
napi_handle_scope *outer_hs = new napi_handle_scope;
|
|
NODE_API_CALL(env, napi_open_handle_scope(env, outer_hs));
|
|
|
|
// Make a handle in the outer scope, on the heap so stack scanning can't see
|
|
// it
|
|
napi_value *outer_scope_handle = new napi_value;
|
|
NODE_API_CALL(env, napi_create_string_utf8(env, str, NAPI_AUTO_LENGTH,
|
|
outer_scope_handle));
|
|
|
|
// Make a new handle scope on the heap so that the outer handle scope isn't
|
|
// active anymore
|
|
napi_handle_scope *inner_hs = new napi_handle_scope;
|
|
NODE_API_CALL(env, napi_open_handle_scope(env, inner_hs));
|
|
|
|
// Force GC
|
|
run_gc(info);
|
|
|
|
// Try to read our first handle. Did the outer handle scope get
|
|
// collected now that it's not on the global object? The inner handle scope
|
|
// should be keeping it alive even though it's not on the stack.
|
|
char buf[64];
|
|
size_t len;
|
|
NODE_API_CALL(env, napi_get_value_string_utf8(env, *outer_scope_handle, buf,
|
|
sizeof(buf), &len));
|
|
NODE_API_ASSERT(env, len == strlen(str));
|
|
NODE_API_ASSERT(env, strcmp(buf, str) == 0);
|
|
|
|
// Clean up
|
|
NODE_API_CALL(env, napi_close_handle_scope(env, *inner_hs));
|
|
delete inner_hs;
|
|
NODE_API_CALL(env, napi_close_handle_scope(env, *outer_hs));
|
|
delete outer_hs;
|
|
delete outer_scope_handle;
|
|
return ok(env);
|
|
}
|
|
|
|
// call this with a bunch (>10) of string arguments representing increasing
|
|
// decimal numbers. ensures that the runtime does not let these arguments be
|
|
// freed.
|
|
//
|
|
// test_napi_handle_scope_many_args(() => gc(), '1', '2', '3', ...)
|
|
static napi_value
|
|
test_napi_handle_scope_many_args(const Napi::CallbackInfo &info) {
|
|
Napi::Env env = info.Env();
|
|
run_gc(info);
|
|
// now if bun is broken a bunch of our args are dead, because node-addon-api
|
|
// uses a heap array for >6 args
|
|
for (size_t i = 1; i < info.Length(); i++) {
|
|
Napi::String s = info[i].As<Napi::String>();
|
|
NODE_API_ASSERT(env, s.Utf8Value() == std::to_string(i));
|
|
}
|
|
return env.Undefined();
|
|
}
|
|
|
|
static napi_value test_napi_ref(const Napi::CallbackInfo &info) {
|
|
napi_env env = info.Env();
|
|
|
|
napi_value object;
|
|
NODE_API_CALL(env, napi_create_object(env, &object));
|
|
|
|
napi_ref ref;
|
|
NODE_API_CALL(env, napi_create_reference(env, object, 0, &ref));
|
|
|
|
napi_value from_ref;
|
|
NODE_API_CALL(env, napi_get_reference_value(env, ref, &from_ref));
|
|
NODE_API_ASSERT(env, from_ref != nullptr);
|
|
napi_valuetype typeof_result = get_typeof(env, from_ref);
|
|
NODE_API_ASSERT(env, typeof_result == napi_object);
|
|
return ok(env);
|
|
}
|
|
|
|
static napi_value test_napi_run_script(const Napi::CallbackInfo &info) {
|
|
napi_value ret = nullptr;
|
|
// info[0] is the GC callback
|
|
(void)napi_run_script(info.Env(), info[1], &ret);
|
|
return ret;
|
|
}
|
|
|
|
void register_standalone_tests(Napi::Env env, Napi::Object exports) {
|
|
REGISTER_FUNCTION(env, exports, test_issue_7685);
|
|
REGISTER_FUNCTION(env, exports, test_issue_11949);
|
|
REGISTER_FUNCTION(env, exports, test_napi_get_value_string_utf8_with_buffer);
|
|
REGISTER_FUNCTION(env, exports,
|
|
test_napi_threadsafe_function_does_not_hang_after_finalize);
|
|
REGISTER_FUNCTION(env, exports, test_napi_handle_scope_string);
|
|
REGISTER_FUNCTION(env, exports, test_napi_handle_scope_bigint);
|
|
REGISTER_FUNCTION(env, exports, test_napi_delete_property);
|
|
REGISTER_FUNCTION(env, exports, test_napi_escapable_handle_scope);
|
|
REGISTER_FUNCTION(env, exports, test_napi_handle_scope_nesting);
|
|
REGISTER_FUNCTION(env, exports, test_napi_handle_scope_many_args);
|
|
REGISTER_FUNCTION(env, exports, test_napi_ref);
|
|
REGISTER_FUNCTION(env, exports, test_napi_run_script);
|
|
}
|
|
|
|
} // namespace napitests
|