mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 15:08:46 +00:00
651 lines
19 KiB
C++
651 lines
19 KiB
C++
/*
|
|
Dummy plugin which counts the occurrences of the word "foo" in the source code,
|
|
replacing it with "boo".
|
|
|
|
It stores the number of occurrences in the External struct.
|
|
*/
|
|
#include <atomic>
|
|
#include <bun-native-bundler-plugin-api/bundler_plugin.h>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <node_api.h>
|
|
|
|
#ifdef _WIN32
|
|
#define BUN_PLUGIN_EXPORT __declspec(dllexport)
|
|
#else
|
|
#define BUN_PLUGIN_EXPORT
|
|
#include <signal.h>
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
extern "C" BUN_PLUGIN_EXPORT const char *BUN_PLUGIN_NAME = "native_plugin_test";
|
|
|
|
struct External {
|
|
std::atomic<size_t> foo_count;
|
|
std::atomic<size_t> bar_count;
|
|
std::atomic<size_t> baz_count;
|
|
|
|
// For testing logging error logic
|
|
std::atomic<bool> throws_an_error;
|
|
// For testing crash reporting
|
|
std::atomic<bool> simulate_crash;
|
|
|
|
std::atomic<size_t> compilation_ctx_freed_count;
|
|
};
|
|
|
|
struct CompilationCtx {
|
|
const char *source_ptr;
|
|
size_t source_len;
|
|
std::atomic<size_t> *free_counter;
|
|
};
|
|
|
|
CompilationCtx *compilation_ctx_new(const char *source_ptr, size_t source_len,
|
|
std::atomic<size_t> *free_counter) {
|
|
CompilationCtx *ctx = new CompilationCtx;
|
|
ctx->source_ptr = source_ptr;
|
|
ctx->source_len = source_len;
|
|
ctx->free_counter = free_counter;
|
|
return ctx;
|
|
}
|
|
|
|
void compilation_ctx_free(CompilationCtx *ctx) {
|
|
printf("Freed compilation ctx!\n");
|
|
if (ctx->free_counter != nullptr) {
|
|
ctx->free_counter->fetch_add(1);
|
|
}
|
|
free((void *)ctx->source_ptr);
|
|
delete ctx;
|
|
}
|
|
|
|
void log_error(const OnBeforeParseArguments *args,
|
|
const OnBeforeParseResult *result, BunLogLevel level,
|
|
const char *message, size_t message_len) {
|
|
BunLogOptions options;
|
|
options.message_ptr = (uint8_t *)message;
|
|
options.message_len = message_len;
|
|
options.path_ptr = args->path_ptr;
|
|
options.path_len = args->path_len;
|
|
options.source_line_text_ptr = nullptr;
|
|
options.source_line_text_len = 0;
|
|
options.level = (int8_t)level;
|
|
options.line = 0;
|
|
options.lineEnd = 0;
|
|
options.column = 0;
|
|
options.columnEnd = 0;
|
|
(result->log)(args, &options);
|
|
}
|
|
|
|
extern "C" BUN_PLUGIN_EXPORT void
|
|
plugin_impl_with_needle(const OnBeforeParseArguments *args,
|
|
OnBeforeParseResult *result, const char *needle) {
|
|
if (args->__struct_size < sizeof(OnBeforeParseArguments)) {
|
|
const char *msg = "This plugin is built for a newer version of Bun than "
|
|
"the one currently running.";
|
|
log_error(args, result, BUN_LOG_LEVEL_ERROR, msg, strlen(msg));
|
|
return;
|
|
}
|
|
|
|
if (args->external) {
|
|
External *external = (External *)args->external;
|
|
if (external->throws_an_error.load()) {
|
|
log_error(args, result, BUN_LOG_LEVEL_ERROR, "Throwing an error",
|
|
sizeof("Throwing an error") - 1);
|
|
return;
|
|
} else if (external->simulate_crash.load()) {
|
|
#ifndef _WIN32
|
|
raise(SIGSEGV);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
int fetch_result = result->fetchSourceCode(args, result);
|
|
if (fetch_result != 0) {
|
|
exit(1);
|
|
}
|
|
|
|
size_t needle_len = strlen(needle);
|
|
|
|
int needle_count = 0;
|
|
|
|
const char *end = (const char *)result->source_ptr + result->source_len;
|
|
|
|
char *cursor = (char *)strstr((const char *)result->source_ptr, needle);
|
|
while (cursor != nullptr) {
|
|
needle_count++;
|
|
cursor += needle_len;
|
|
if (cursor + needle_len < end) {
|
|
cursor = (char *)strstr((const char *)cursor, needle);
|
|
} else
|
|
break;
|
|
}
|
|
|
|
if (needle_count > 0) {
|
|
char *new_source = (char *)malloc(result->source_len);
|
|
if (new_source == nullptr) {
|
|
exit(1);
|
|
}
|
|
memcpy(new_source, result->source_ptr, result->source_len);
|
|
cursor = strstr(new_source, needle);
|
|
while (cursor != nullptr) {
|
|
cursor[0] = 'q';
|
|
cursor += 3;
|
|
if (cursor + 3 < end) {
|
|
cursor = (char *)strstr((const char *)cursor, needle);
|
|
} else
|
|
break;
|
|
}
|
|
std::atomic<size_t> *free_counter = nullptr;
|
|
if (args->external) {
|
|
External *external = (External *)args->external;
|
|
std::atomic<size_t> *needle_atomic_value = nullptr;
|
|
if (strcmp(needle, "foo") == 0) {
|
|
needle_atomic_value = &external->foo_count;
|
|
} else if (strcmp(needle, "bar") == 0) {
|
|
needle_atomic_value = &external->bar_count;
|
|
} else if (strcmp(needle, "baz") == 0) {
|
|
needle_atomic_value = &external->baz_count;
|
|
}
|
|
needle_atomic_value->fetch_add(needle_count);
|
|
free_counter = &external->compilation_ctx_freed_count;
|
|
}
|
|
result->source_ptr = (uint8_t *)new_source;
|
|
result->source_len = result->source_len;
|
|
result->plugin_source_code_context =
|
|
compilation_ctx_new(new_source, result->source_len, free_counter);
|
|
result->free_plugin_source_code_context =
|
|
(void (*)(void *))compilation_ctx_free;
|
|
} else {
|
|
result->source_ptr = nullptr;
|
|
result->source_len = 0;
|
|
result->loader = 0;
|
|
}
|
|
}
|
|
|
|
extern "C" BUN_PLUGIN_EXPORT void
|
|
plugin_impl(const OnBeforeParseArguments *args, OnBeforeParseResult *result) {
|
|
plugin_impl_with_needle(args, result, "foo");
|
|
}
|
|
|
|
extern "C" BUN_PLUGIN_EXPORT void
|
|
plugin_impl_bar(const OnBeforeParseArguments *args,
|
|
OnBeforeParseResult *result) {
|
|
plugin_impl_with_needle(args, result, "bar");
|
|
}
|
|
|
|
extern "C" BUN_PLUGIN_EXPORT void
|
|
plugin_impl_baz(const OnBeforeParseArguments *args,
|
|
OnBeforeParseResult *result) {
|
|
plugin_impl_with_needle(args, result, "baz");
|
|
}
|
|
|
|
extern "C" void finalizer(napi_env env, void *data, void *hint) {
|
|
External *external = (External *)data;
|
|
if (external != nullptr) {
|
|
delete external;
|
|
}
|
|
}
|
|
|
|
napi_value create_external(napi_env env, napi_callback_info info) {
|
|
napi_status status;
|
|
|
|
// Allocate the External struct
|
|
External *external = new External();
|
|
if (external == nullptr) {
|
|
napi_throw_error(env, nullptr, "Failed to allocate memory");
|
|
return nullptr;
|
|
}
|
|
|
|
external->foo_count = 0;
|
|
external->compilation_ctx_freed_count = 0;
|
|
|
|
// Create the external wrapper
|
|
napi_value result;
|
|
status = napi_create_external(env, external, finalizer, nullptr, &result);
|
|
if (status != napi_ok) {
|
|
delete external;
|
|
napi_throw_error(env, nullptr, "Failed to create external");
|
|
return nullptr;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
napi_value set_will_crash(napi_env env, napi_callback_info info) {
|
|
napi_status status;
|
|
External *external;
|
|
|
|
size_t argc = 2;
|
|
napi_value args[2];
|
|
status = napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
|
|
if (status != napi_ok) {
|
|
napi_throw_error(env, nullptr, "Failed to parse arguments");
|
|
return nullptr;
|
|
}
|
|
|
|
if (argc < 1) {
|
|
napi_throw_error(env, nullptr, "Wrong number of arguments");
|
|
return nullptr;
|
|
}
|
|
|
|
status = napi_get_value_external(env, args[0], (void **)&external);
|
|
if (status != napi_ok) {
|
|
napi_throw_error(env, nullptr, "Failed to get external");
|
|
return nullptr;
|
|
}
|
|
|
|
bool throws;
|
|
status = napi_get_value_bool(env, args[1], &throws);
|
|
if (status != napi_ok) {
|
|
napi_throw_error(env, nullptr, "Failed to get boolean value");
|
|
return nullptr;
|
|
}
|
|
|
|
external->simulate_crash.store(throws);
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
napi_value set_throws_errors(napi_env env, napi_callback_info info) {
|
|
napi_status status;
|
|
External *external;
|
|
|
|
size_t argc = 2;
|
|
napi_value args[2];
|
|
status = napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
|
|
if (status != napi_ok) {
|
|
napi_throw_error(env, nullptr, "Failed to parse arguments");
|
|
return nullptr;
|
|
}
|
|
|
|
if (argc < 1) {
|
|
napi_throw_error(env, nullptr, "Wrong number of arguments");
|
|
return nullptr;
|
|
}
|
|
|
|
status = napi_get_value_external(env, args[0], (void **)&external);
|
|
if (status != napi_ok) {
|
|
napi_throw_error(env, nullptr, "Failed to get external");
|
|
return nullptr;
|
|
}
|
|
|
|
bool throws;
|
|
status = napi_get_value_bool(env, args[1], &throws);
|
|
if (status != napi_ok) {
|
|
napi_throw_error(env, nullptr, "Failed to get boolean value");
|
|
return nullptr;
|
|
}
|
|
|
|
external->throws_an_error.store(throws);
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
napi_value get_compilation_ctx_freed_count(napi_env env,
|
|
napi_callback_info info) {
|
|
napi_status status;
|
|
External *external;
|
|
|
|
size_t argc = 1;
|
|
napi_value args[1];
|
|
status = napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
|
|
if (status != napi_ok) {
|
|
napi_throw_error(env, nullptr, "Failed to parse arguments");
|
|
return nullptr;
|
|
}
|
|
|
|
if (argc < 1) {
|
|
napi_throw_error(env, nullptr, "Wrong number of arguments");
|
|
return nullptr;
|
|
}
|
|
|
|
status = napi_get_value_external(env, args[0], (void **)&external);
|
|
if (status != napi_ok) {
|
|
napi_throw_error(env, nullptr, "Failed to get external");
|
|
return nullptr;
|
|
}
|
|
|
|
napi_value result;
|
|
status = napi_create_int32(env, external->compilation_ctx_freed_count.load(),
|
|
&result);
|
|
if (status != napi_ok) {
|
|
napi_throw_error(env, nullptr, "Failed to create array");
|
|
return nullptr;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
napi_value get_foo_count(napi_env env, napi_callback_info info) {
|
|
napi_status status;
|
|
External *external;
|
|
|
|
size_t argc = 1;
|
|
napi_value args[1];
|
|
status = napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
|
|
if (status != napi_ok) {
|
|
napi_throw_error(env, nullptr, "Failed to parse arguments");
|
|
return nullptr;
|
|
}
|
|
|
|
if (argc < 1) {
|
|
napi_throw_error(env, nullptr, "Wrong number of arguments");
|
|
return nullptr;
|
|
}
|
|
|
|
status = napi_get_value_external(env, args[0], (void **)&external);
|
|
if (status != napi_ok) {
|
|
napi_throw_error(env, nullptr, "Failed to get external");
|
|
return nullptr;
|
|
}
|
|
|
|
size_t foo_count = external->foo_count.load();
|
|
if (foo_count > INT32_MAX) {
|
|
napi_throw_error(env, nullptr,
|
|
"Too many foos! This probably means undefined memory or "
|
|
"heap corruption.");
|
|
return nullptr;
|
|
}
|
|
|
|
napi_value result;
|
|
status = napi_create_int32(env, foo_count, &result);
|
|
if (status != napi_ok) {
|
|
napi_throw_error(env, nullptr, "Failed to create array");
|
|
return nullptr;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
napi_value get_bar_count(napi_env env, napi_callback_info info) {
|
|
napi_status status;
|
|
External *external;
|
|
|
|
size_t argc = 1;
|
|
napi_value args[1];
|
|
status = napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
|
|
if (status != napi_ok) {
|
|
napi_throw_error(env, nullptr, "Failed to parse arguments");
|
|
return nullptr;
|
|
}
|
|
|
|
if (argc < 1) {
|
|
napi_throw_error(env, nullptr, "Wrong number of arguments");
|
|
return nullptr;
|
|
}
|
|
|
|
status = napi_get_value_external(env, args[0], (void **)&external);
|
|
if (status != napi_ok) {
|
|
napi_throw_error(env, nullptr, "Failed to get external");
|
|
return nullptr;
|
|
}
|
|
|
|
size_t bar_count = external->bar_count.load();
|
|
if (bar_count > INT32_MAX) {
|
|
napi_throw_error(env, nullptr,
|
|
"Too many bars! This probably means undefined memory or "
|
|
"heap corruption.");
|
|
return nullptr;
|
|
}
|
|
|
|
napi_value result;
|
|
status = napi_create_int32(env, bar_count, &result);
|
|
if (status != napi_ok) {
|
|
napi_throw_error(env, nullptr, "Failed to create array");
|
|
return nullptr;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
napi_value get_baz_count(napi_env env, napi_callback_info info) {
|
|
napi_status status;
|
|
External *external;
|
|
|
|
size_t argc = 1;
|
|
napi_value args[1];
|
|
status = napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
|
|
if (status != napi_ok) {
|
|
napi_throw_error(env, nullptr, "Failed to parse arguments");
|
|
return nullptr;
|
|
}
|
|
|
|
if (argc < 1) {
|
|
napi_throw_error(env, nullptr, "Wrong number of arguments");
|
|
return nullptr;
|
|
}
|
|
|
|
status = napi_get_value_external(env, args[0], (void **)&external);
|
|
if (status != napi_ok) {
|
|
napi_throw_error(env, nullptr, "Failed to get external");
|
|
return nullptr;
|
|
}
|
|
|
|
size_t baz_count = external->baz_count.load();
|
|
if (baz_count > INT32_MAX) {
|
|
napi_throw_error(env, nullptr,
|
|
"Too many bazs! This probably means undefined memory or "
|
|
"heap corruption.");
|
|
return nullptr;
|
|
}
|
|
|
|
napi_value result;
|
|
status = napi_create_int32(env, baz_count, &result);
|
|
if (status != napi_ok) {
|
|
napi_throw_error(env, nullptr, "Failed to create array");
|
|
return nullptr;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
napi_value Init(napi_env env, napi_value exports) {
|
|
napi_status status;
|
|
napi_value fn_get_foo_count;
|
|
napi_value fn_get_bar_count;
|
|
napi_value fn_get_baz_count;
|
|
|
|
napi_value fn_get_compilation_ctx_freed_count;
|
|
napi_value fn_create_external;
|
|
napi_value fn_set_throws_errors;
|
|
napi_value fn_set_will_crash;
|
|
|
|
// Register get_foo_count function
|
|
status = napi_create_function(env, nullptr, 0, get_foo_count, nullptr,
|
|
&fn_get_foo_count);
|
|
if (status != napi_ok) {
|
|
napi_throw_error(env, nullptr, "Failed to create get_names function");
|
|
return nullptr;
|
|
}
|
|
status =
|
|
napi_set_named_property(env, exports, "getFooCount", fn_get_foo_count);
|
|
if (status != napi_ok) {
|
|
napi_throw_error(env, nullptr,
|
|
"Failed to add get_names function to exports");
|
|
return nullptr;
|
|
}
|
|
|
|
// Register get_bar_count function
|
|
status = napi_create_function(env, nullptr, 0, get_bar_count, nullptr,
|
|
&fn_get_bar_count);
|
|
if (status != napi_ok) {
|
|
napi_throw_error(env, nullptr, "Failed to create get_names function");
|
|
return nullptr;
|
|
}
|
|
status =
|
|
napi_set_named_property(env, exports, "getBarCount", fn_get_bar_count);
|
|
if (status != napi_ok) {
|
|
napi_throw_error(env, nullptr,
|
|
"Failed to add get_names function to exports");
|
|
return nullptr;
|
|
}
|
|
|
|
// Register get_baz_count function
|
|
status = napi_create_function(env, nullptr, 0, get_baz_count, nullptr,
|
|
&fn_get_baz_count);
|
|
if (status != napi_ok) {
|
|
napi_throw_error(env, nullptr, "Failed to create get_names function");
|
|
return nullptr;
|
|
}
|
|
status =
|
|
napi_set_named_property(env, exports, "getBazCount", fn_get_baz_count);
|
|
if (status != napi_ok) {
|
|
napi_throw_error(env, nullptr,
|
|
"Failed to add get_names function to exports");
|
|
return nullptr;
|
|
}
|
|
|
|
// Register get_compilation_ctx_freed_count function
|
|
status =
|
|
napi_create_function(env, nullptr, 0, get_compilation_ctx_freed_count,
|
|
nullptr, &fn_get_compilation_ctx_freed_count);
|
|
if (status != napi_ok) {
|
|
napi_throw_error(
|
|
env, nullptr,
|
|
"Failed to create get_compilation_ctx_freed_count function");
|
|
return nullptr;
|
|
}
|
|
status = napi_set_named_property(env, exports, "getCompilationCtxFreedCount",
|
|
fn_get_compilation_ctx_freed_count);
|
|
if (status != napi_ok) {
|
|
napi_throw_error(
|
|
env, nullptr,
|
|
"Failed to add get_compilation_ctx_freed_count function to exports");
|
|
return nullptr;
|
|
}
|
|
|
|
// Register set_throws_errors function
|
|
status = napi_create_function(env, nullptr, 0, set_throws_errors, nullptr,
|
|
&fn_set_throws_errors);
|
|
if (status != napi_ok) {
|
|
napi_throw_error(env, nullptr,
|
|
"Failed to create set_throws_errors function");
|
|
return nullptr;
|
|
}
|
|
status = napi_set_named_property(env, exports, "setThrowsErrors",
|
|
fn_set_throws_errors);
|
|
if (status != napi_ok) {
|
|
napi_throw_error(env, nullptr,
|
|
"Failed to add set_throws_errors function to exports");
|
|
return nullptr;
|
|
}
|
|
|
|
// Register set_will_crash function
|
|
status = napi_create_function(env, nullptr, 0, set_will_crash, nullptr,
|
|
&fn_set_will_crash);
|
|
if (status != napi_ok) {
|
|
napi_throw_error(env, nullptr, "Failed to create set_will_crash function");
|
|
return nullptr;
|
|
}
|
|
status =
|
|
napi_set_named_property(env, exports, "setWillCrash", fn_set_will_crash);
|
|
if (status != napi_ok) {
|
|
napi_throw_error(env, nullptr,
|
|
"Failed to add set_will_crash function to exports");
|
|
return nullptr;
|
|
}
|
|
|
|
// Register create_external function
|
|
status = napi_create_function(env, nullptr, 0, create_external, nullptr,
|
|
&fn_create_external);
|
|
if (status != napi_ok) {
|
|
napi_throw_error(env, nullptr, "Failed to create create_external function");
|
|
return nullptr;
|
|
}
|
|
status = napi_set_named_property(env, exports, "createExternal",
|
|
fn_create_external);
|
|
if (status != napi_ok) {
|
|
napi_throw_error(env, nullptr,
|
|
"Failed to add create_external function to exports");
|
|
return nullptr;
|
|
}
|
|
// this should be the same as returning `exports`, but it would crash in
|
|
// previous versions of Bun
|
|
return nullptr;
|
|
}
|
|
|
|
struct NewOnBeforeParseArguments {
|
|
size_t __struct_size;
|
|
void *bun;
|
|
const uint8_t *path_ptr;
|
|
size_t path_len;
|
|
const uint8_t *namespace_ptr;
|
|
size_t namespace_len;
|
|
uint8_t default_loader;
|
|
void *external;
|
|
size_t new_field_one;
|
|
size_t new_field_two;
|
|
size_t new_field_three;
|
|
};
|
|
|
|
struct NewOnBeforeParseResult {
|
|
size_t __struct_size;
|
|
uint8_t *source_ptr;
|
|
size_t source_len;
|
|
uint8_t loader;
|
|
int (*fetchSourceCode)(const NewOnBeforeParseArguments *args,
|
|
struct NewOnBeforeParseResult *result);
|
|
void *plugin_source_code_context;
|
|
void (*free_plugin_source_code_context)(void *ctx);
|
|
void (*log)(const NewOnBeforeParseArguments *args, BunLogOptions *options);
|
|
size_t new_field_one;
|
|
size_t new_field_two;
|
|
size_t new_field_three;
|
|
};
|
|
|
|
void new_log_error(const NewOnBeforeParseArguments *args,
|
|
const NewOnBeforeParseResult *result, BunLogLevel level,
|
|
const char *message, size_t message_len) {
|
|
BunLogOptions options;
|
|
options.message_ptr = (uint8_t *)message;
|
|
options.message_len = message_len;
|
|
options.path_ptr = args->path_ptr;
|
|
options.path_len = args->path_len;
|
|
options.source_line_text_ptr = nullptr;
|
|
options.source_line_text_len = 0;
|
|
options.level = (int8_t)level;
|
|
options.line = 0;
|
|
options.lineEnd = 0;
|
|
options.column = 0;
|
|
options.columnEnd = 0;
|
|
(result->log)(args, &options);
|
|
}
|
|
|
|
extern "C" BUN_PLUGIN_EXPORT void
|
|
incompatible_version_plugin_impl(const NewOnBeforeParseArguments *args,
|
|
NewOnBeforeParseResult *result) {
|
|
if (args->__struct_size < sizeof(NewOnBeforeParseArguments)) {
|
|
const char *msg = "This plugin is built for a newer version of Bun than "
|
|
"the one currently running.";
|
|
new_log_error(args, result, BUN_LOG_LEVEL_ERROR, msg, strlen(msg));
|
|
return;
|
|
}
|
|
|
|
if (result->__struct_size < sizeof(NewOnBeforeParseResult)) {
|
|
const char *msg = "This plugin is built for a newer version of Bun than "
|
|
"the one currently running.";
|
|
new_log_error(args, result, BUN_LOG_LEVEL_ERROR, msg, strlen(msg));
|
|
return;
|
|
}
|
|
}
|
|
|
|
struct RandomUserContext {
|
|
const char *foo;
|
|
size_t bar;
|
|
};
|
|
|
|
extern "C" BUN_PLUGIN_EXPORT void random_user_context_free(void *ptr) {
|
|
free(ptr);
|
|
}
|
|
|
|
extern "C" BUN_PLUGIN_EXPORT void
|
|
plugin_impl_bad_free_function_pointer(const OnBeforeParseArguments *args,
|
|
OnBeforeParseResult *result) {
|
|
|
|
// Intentionally not setting the context here:
|
|
// result->plugin_source_code_context = ctx;
|
|
result->free_plugin_source_code_context = random_user_context_free;
|
|
}
|
|
|
|
NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)
|