#include "standalone_tests.h" #include #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(opaque_context); int *data = reinterpret_cast(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().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 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 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(); 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