diff --git a/build.rs b/build.rs index 809bf52..24cb7d1 100644 --- a/build.rs +++ b/build.rs @@ -51,8 +51,6 @@ fn main() { println!("cargo:rerun-if-changed=include"); println!("cargo:rerun-if-changed=lib/src"); println!("cargo:rerun-if-changed=v8/src"); - println!("cargo:rerun-if-changed=src/edge_napi_embedder_hooks.cc"); - println!("cargo:rerun-if-changed=src/edge_napi_embedder_hooks.h"); println!("cargo:rerun-if-env-changed=V8_INCLUDE_DIR"); println!("cargo:rerun-if-env-changed=V8_LIB_DIR"); println!("cargo:rerun-if-env-changed=V8_DEFINES"); @@ -100,7 +98,6 @@ fn main() { .include(napi_lib_src.to_str().unwrap()) .include(napi_v8_src.to_str().unwrap()) .file("src/napi_bridge_init.cc") - .file("src/edge_napi_embedder_hooks.cc") .file(napi_lib_src.join("napi_error_state.cc").to_str().unwrap()) .file( napi_lib_src diff --git a/include/unofficial_napi.h b/include/unofficial_napi.h index 8e99b1a..39a4f35 100644 --- a/include/unofficial_napi.h +++ b/include/unofficial_napi.h @@ -11,85 +11,135 @@ extern "C" { struct uv_loop_s; -// Unofficial/test-only helper APIs for creating and releasing an env scope. +// Provider-owned resources used during environment construction and teardown. +// Their representations are private to the provider; callers may only move +// them through the operations that name the corresponding handle type. +typedef struct unofficial_napi_env_owner__* unofficial_napi_env_owner; +typedef struct unofficial_napi_guest_heap__* unofficial_napi_guest_heap; + +enum { UNOFFICIAL_NAPI_ENV_CREATE_OPTIONS_VERSION = 1 }; + +enum { UNOFFICIAL_NAPI_RUNTIME_OPTIONS_VERSION = 1 }; + +// Configuration for the provider's process-global JavaScript runtime. This +// must be applied before creating an environment. Repeating an identical +// configuration is idempotent; a conflicting configuration is rejected. +// Pointed-to strings are observed only for the duration of the call. typedef struct { + uint32_t size; + uint32_t version; + const char* engine_flags; + size_t engine_flags_length; +} unofficial_napi_runtime_options; + +NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_configure_runtime( + const unofficial_napi_runtime_options* options_or_null); + +// All provider configuration needed before engine/isolate creation. The +// descriptor is observed only for the duration of unofficial_napi_create_env; +// pointed-to strings remain owned by the caller. `size` and `version` make +// additions ABI-compatible. Process-global configuration is deliberately +// excluded and belongs to unofficial_napi_configure_runtime. +typedef struct { + uint32_t size; + uint32_t version; + uint64_t total_memory; + uint64_t constrained_memory; size_t max_young_generation_size_in_bytes; size_t max_old_generation_size_in_bytes; size_t code_range_size_in_bytes; void* stack_limit; - /* Opaque guest-heap context (see napi_host_guest_heap_alloc). When set, the + /* Opaque guest-heap resource (see napi_host_guest_heap_alloc). When set, the * env's array-buffer allocator places every backing store in the guest's - * linear memory from isolate birth. Ownership transfers to the env: it is - * released exactly once via napi_host_guest_heap_release (by the allocator - * destructor, or on env-creation failure). */ - void* guest_heap_ctx; + * linear memory from isolate birth. Ownership transfers only after size and + * version validation proves that this field is present, before validating + * any other argument. It is then released exactly once via + * napi_host_guest_heap_release (by the allocator destructor, or on any later + * env-creation failure). */ + unofficial_napi_guest_heap guest_heap; } unofficial_napi_env_create_options; -typedef struct { - uint64_t total_memory; - uint64_t constrained_memory; -} unofficial_napi_embedder_memory_info; - -typedef napi_status (*unofficial_napi_embedder_memory_info_callback)( - void* target, - unofficial_napi_embedder_memory_info* info_out); -typedef napi_status (*unofficial_napi_embedder_shutdown_pump_callback)( +typedef void (*unofficial_napi_context_token_callback)(napi_env env, + void* token, + void* data); +typedef void (*unofficial_napi_foreground_task_callback)(napi_env env, + void* data); +typedef void (*unofficial_napi_foreground_task_cleanup)(napi_env env, + void* data); +typedef napi_status (*unofficial_napi_enqueue_foreground_task_callback)( void* target, - void* handle); + unofficial_napi_foreground_task_callback callback, + void* data, + unofficial_napi_foreground_task_cleanup cleanup, + uint64_t delay_millis); +typedef void (*unofficial_napi_fatal_error_callback)(napi_env env, + const char* location, + const char* message); +typedef void (*unofficial_napi_oom_error_callback)(napi_env env, + const char* location, + bool is_heap_oom, + const char* detail); +typedef enum { + unofficial_napi_env_hook_context_token_assign = 1u << 0, + unofficial_napi_env_hook_context_token_unassign = 1u << 1, + unofficial_napi_env_hook_enqueue_foreground_task = 1u << 2, + unofficial_napi_env_hook_fatal_error = 1u << 3, + unofficial_napi_env_hook_oom_error = 1u << 4, +} unofficial_napi_env_hook; + +enum { UNOFFICIAL_NAPI_ENV_HOOKS_VERSION = 1 }; + +// Immutable embedder callbacks attached as one complete environment state +// transition. `size` and `version` make additions ABI-compatible. `data` is +// owned by the embedder and must remain valid until the environment is released. typedef struct { - unofficial_napi_embedder_memory_info_callback memory_info_callback; - void* memory_info_target; - unofficial_napi_embedder_shutdown_pump_callback shutdown_pump_callback; - void* shutdown_pump_target; -} unofficial_napi_embedder_hooks; - -NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_create_env(int32_t module_api_version, - napi_env* env_out, - void** scope_out); -NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_create_env_with_options( + uint32_t size; + uint32_t version; + void* data; + unofficial_napi_context_token_callback context_token_assign_callback; + unofficial_napi_context_token_callback context_token_unassign_callback; + unofficial_napi_enqueue_foreground_task_callback enqueue_foreground_task_callback; + unofficial_napi_fatal_error_callback fatal_error_callback; + unofficial_napi_oom_error_callback oom_error_callback; +} unofficial_napi_env_hooks; + +static inline uint64_t unofficial_napi_env_hooks_requested( + const unofficial_napi_env_hooks* hooks) { + if (hooks == NULL) return 0; + uint64_t requested = 0; + if (hooks->context_token_assign_callback != NULL) + requested |= unofficial_napi_env_hook_context_token_assign; + if (hooks->context_token_unassign_callback != NULL) + requested |= unofficial_napi_env_hook_context_token_unassign; + if (hooks->enqueue_foreground_task_callback != NULL) + requested |= unofficial_napi_env_hook_enqueue_foreground_task; + if (hooks->fatal_error_callback != NULL) + requested |= unofficial_napi_env_hook_fatal_error; + if (hooks->oom_error_callback != NULL) + requested |= unofficial_napi_env_hook_oom_error; + return requested; +} + +NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_create_env( int32_t module_api_version, - const unofficial_napi_env_create_options* options, + const unofficial_napi_env_create_options* options_or_null, napi_env* env_out, - void** scope_out); -NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_set_embedder_hooks( - const unofficial_napi_embedder_hooks* hooks); -NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_set_edge_environment(napi_env env, void* environment); -NAPI_EXTENSION_WASMER_EXTERN void* unofficial_napi_get_edge_environment(napi_env env); -typedef void (*unofficial_napi_env_cleanup_callback)(napi_env env, void* data); -NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_set_env_cleanup_callback( - napi_env env, - unofficial_napi_env_cleanup_callback callback, - void* data); -typedef void (*unofficial_napi_env_destroy_callback)(napi_env env, void* data); -NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_set_env_destroy_callback( + unofficial_napi_env_owner* owner_out); +NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_attach_env( napi_env env, - unofficial_napi_env_destroy_callback callback, - void* data); -typedef void (*unofficial_napi_context_token_callback)(napi_env env, - void* token, - void* data); -NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_set_context_token_callbacks( - napi_env env, - unofficial_napi_context_token_callback assign_callback, - unofficial_napi_context_token_callback unassign_callback, - void* data); -NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_destroy_env_instance(napi_env env); -NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_release_env(void* scope); -NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_release_env_with_loop( - void* scope, - struct uv_loop_s* loop); -NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_low_memory_notification(napi_env env); -NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_set_flags_from_string( - const char* flags, - size_t length); + const unofficial_napi_env_hooks* hooks, + uint64_t* accepted_hooks_out); +NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_release_env( + unofficial_napi_env_owner owner, + struct uv_loop_s* loop_or_null); + +NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_collect_garbage( + napi_env env); NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_set_prepare_stack_trace_callback( napi_env env, napi_value callback); -// Unofficial/test-only helper. Requests a full GC cycle for testing. -NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_request_gc_for_testing(napi_env env); - typedef enum unofficial_napi_event_loop_checkpoint_mode { // Drain promise/microtask work without admitting a host task turn. unofficial_napi_event_loop_checkpoint_microtasks = 0, @@ -168,8 +218,6 @@ NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_terminate_execution(nap // used when embedder code intentionally stops a worker but still needs the // current JS stack to unwind normally. NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_cancel_terminate_execution(napi_env env); -NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_set_pending_exception(napi_env env, - napi_value error); typedef void (*unofficial_napi_interrupt_callback)(napi_env env, void* data); @@ -181,26 +229,6 @@ NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_request_interrupt( unofficial_napi_interrupt_callback callback, void* data); -typedef void (*unofficial_napi_foreground_task_callback)(napi_env env, - void* data); -typedef void (*unofficial_napi_foreground_task_cleanup)(napi_env env, - void* data); -typedef napi_status (*unofficial_napi_enqueue_foreground_task_callback)( - void* target, - unofficial_napi_foreground_task_callback callback, - void* data, - unofficial_napi_foreground_task_cleanup cleanup, - uint64_t delay_millis); - -// Installs the embedder-owned foreground task queue hook for a single env. -// Engine backends use this to forward engine-originated foreground work into -// the embedder-owned main-thread queue. Queue ownership and drain policy stay -// outside the backend. -NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_set_enqueue_foreground_task_callback( - napi_env env, - unofficial_napi_enqueue_foreground_task_callback callback, - void* target); - // Unofficial helper. Enqueues a JS function into V8 microtask queue. NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_enqueue_microtask(napi_env env, napi_value callback); @@ -217,34 +245,22 @@ NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_set_promise_hooks(napi_ napi_value after, napi_value resolve); -typedef void (*unofficial_napi_fatal_error_callback)(napi_env env, - const char* location, - const char* message); -typedef void (*unofficial_napi_oom_error_callback)(napi_env env, - const char* location, - bool is_heap_oom, - const char* detail); typedef size_t (*unofficial_napi_near_heap_limit_callback)( napi_env env, void* data, size_t current_heap_limit, size_t initial_heap_limit); -// Unofficial helpers for embedder-native fatal/OOM handling. -// These callbacks run from the engine's fatal error hooks. -NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_set_fatal_error_callbacks( - napi_env env, - unofficial_napi_fatal_error_callback fatal_callback, - unofficial_napi_oom_error_callback oom_callback); -NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_set_near_heap_limit_callback( +// Atomically configures the environment's one near-heap-limit callback slot. +// A non-null callback installs or replaces the slot and requires +// `restored_heap_limit` to be zero. A null callback removes the slot, requires +// `data` to be null, and restores the provider heap limit to the supplied +// value. +NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_configure_near_heap_limit_callback( napi_env env, - unofficial_napi_near_heap_limit_callback callback, - void* data); -NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_remove_near_heap_limit_callback( - napi_env env, - size_t heap_limit); -NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_set_stack_limit(napi_env env, void* stack_limit); - + unofficial_napi_near_heap_limit_callback callback_or_null, + void* data, + size_t restored_heap_limit); // Unofficial helpers used by util/options parity work in edge. // These expose engine-specific data that is not available in the public N-API. NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_get_promise_details(napi_env env, @@ -253,46 +269,43 @@ NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_get_promise_details(nap napi_value* result_out, bool* has_result_out); +typedef enum { + unofficial_napi_error_metadata_current = 0, + unofficial_napi_error_metadata_take_preserved = 1, + unofficial_napi_error_metadata_positions_only = 2, + // Return only the provider stack's "Thrown at" text. Unlike `current`, this + // mode must not request source-map formatting or invoke embedder JavaScript. + unofficial_napi_error_metadata_thrown_at_only = 3, +} unofficial_napi_error_metadata_mode; + typedef struct { napi_value source_line; napi_value script_resource_name; + napi_value stderr_line; + napi_value thrown_at; int32_t line_number; int32_t start_column; int32_t end_column; -} unofficial_napi_error_source_positions; +} unofficial_napi_error_metadata; // Unofficial helpers for Node-style exception/message parity. // These expose engine message/source metadata that is not available in the // public Node-API. -NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_get_error_source_positions( +NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_get_error_metadata( napi_env env, napi_value error, - unofficial_napi_error_source_positions* out); + unofficial_napi_error_metadata_mode mode, + unofficial_napi_error_metadata* out); // Preserve the current engine-generated source arrow/message for an Error // object so later rethrows do not overwrite it with the rethrow callsite. NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_preserve_error_source_message( napi_env env, napi_value error); -NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_set_source_maps_enabled( - napi_env env, - bool enabled); -NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_set_get_source_map_error_source_callback( +NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_configure_source_maps( napi_env env, + bool enabled, napi_value callback); -NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_get_error_source_line_for_stderr( - napi_env env, - napi_value error, - napi_value* result_out); -NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_get_error_thrown_at( - napi_env env, - napi_value error, - napi_value* result_out); -NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_take_preserved_error_formatting( - napi_env env, - napi_value error, - napi_value* source_line_out, - napi_value* thrown_at_out); // Unofficial helper used by module_wrap parity paths to tell the runtime's // PromiseReject callback machinery that a rejected promise is being handled @@ -314,12 +327,6 @@ NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_preview_entries(napi_en NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_get_call_sites(napi_env env, uint32_t frames, napi_value* callsites_out); -NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_get_current_stack_trace(napi_env env, - uint32_t frames, - napi_value* callsites_out); - -NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_get_caller_location(napi_env env, - napi_value* location_out); NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_arraybuffer_view_has_buffer(napi_env env, napi_value value, @@ -329,15 +336,6 @@ NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_get_constructor_name(na napi_value value, napi_value* name_out); -// Unofficial helper for Node's internalBinding('util').getOwnNonIndexProperties. -// Returns the target's own property names while skipping indexed elements at the -// engine level, matching Node's use of IndexFilter::kSkipIndices. -NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_get_own_non_index_properties( - napi_env env, - napi_value value, - uint32_t filter_bits, - napi_value* result_out); - // Unofficial helper for Node's internalBinding('util').privateSymbols. // Returns a JS-visible private symbol value backed by the engine's hidden // private property machinery. @@ -352,37 +350,34 @@ NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_create_private_symbol(n NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_structured_clone( napi_env env, napi_value value, + napi_value transfer_list_or_null, napi_value* result_out); -NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_structured_clone_with_transfer( - napi_env env, - napi_value value, - napi_value transfer_list, - napi_value* result_out); +// Opaque provider-owned message which may cross N-API environments. A message +// is consumed by message_take on both success and failure, or explicitly +// destroyed with message_drop while it is still queued. +typedef struct unofficial_napi_message__* unofficial_napi_message; -// Unofficial helpers for env-agnostic message payload queues. -// The returned opaque payload must be released with -// unofficial_napi_release_serialized_value(). -NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_serialize_value( +NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_message_create( napi_env env, napi_value value, - void** payload_out); + unofficial_napi_message* message_out); -NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_deserialize_value( +NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_message_take( napi_env env, - void* payload, + unofficial_napi_message message, napi_value* result_out); -NAPI_EXTENSION_WASMER_EXTERN void unofficial_napi_release_serialized_value(void* payload); +NAPI_EXTENSION_WASMER_EXTERN void unofficial_napi_message_drop(unofficial_napi_message message); -// Unofficial helper for Node-style process.memoryUsage() parity. -// Returns V8 heap statistics plus allocator-tracked ArrayBuffer memory. -NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_get_process_memory_info( +// Returns own property keys while filtering indexed elements inside the +// provider. This bulk operation avoids materializing every array index and +// crossing the guest/host boundary once per key. +NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_get_own_non_index_properties( napi_env env, - double* heap_total_out, - double* heap_used_out, - double* external_out, - double* array_buffers_out); + napi_value value, + uint32_t filter_bits, + napi_value* result_out); // Unofficial helper for Node's internalBinding('v8').getHashSeed(). NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_get_hash_seed(napi_env env, @@ -390,7 +385,31 @@ NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_get_hash_seed(napi_env #define UNOFFICIAL_NAPI_HEAP_SPACE_NAME_MAX_LENGTH 64 +typedef enum { + unofficial_napi_heap_stat_total_heap_size = 1u << 0, + unofficial_napi_heap_stat_total_heap_size_executable = 1u << 1, + unofficial_napi_heap_stat_total_physical_size = 1u << 2, + unofficial_napi_heap_stat_total_available_size = 1u << 3, + unofficial_napi_heap_stat_used_heap_size = 1u << 4, + unofficial_napi_heap_stat_heap_size_limit = 1u << 5, + unofficial_napi_heap_stat_does_zap_garbage = 1u << 6, + unofficial_napi_heap_stat_malloced_memory = 1u << 7, + unofficial_napi_heap_stat_peak_malloced_memory = 1u << 8, + unofficial_napi_heap_stat_number_of_native_contexts = 1u << 9, + unofficial_napi_heap_stat_number_of_detached_contexts = 1u << 10, + unofficial_napi_heap_stat_total_global_handles_size = 1u << 11, + unofficial_napi_heap_stat_used_global_handles_size = 1u << 12, + unofficial_napi_heap_stat_external_memory = 1u << 13, + unofficial_napi_heap_stat_array_buffer_memory = 1u << 14, + unofficial_napi_heap_stat_all = (1u << 15) - 1, +} unofficial_napi_heap_stat_field; + +enum { UNOFFICIAL_NAPI_HEAP_STATISTICS_VERSION = 1 }; + typedef struct { + uint32_t size; + uint32_t version; + uint64_t valid_fields; uint64_t total_heap_size; uint64_t total_heap_size_executable; uint64_t total_physical_size; @@ -405,8 +424,73 @@ typedef struct { uint64_t total_global_handles_size; uint64_t used_global_handles_size; uint64_t external_memory; + uint64_t array_buffer_memory; } unofficial_napi_heap_statistics; +static inline void unofficial_napi_heap_statistics_init( + unofficial_napi_heap_statistics* stats) { + if (stats == NULL) return; +#ifdef __cplusplus + *stats = {}; +#else + *stats = (unofficial_napi_heap_statistics){0}; +#endif + stats->size = sizeof(*stats); + stats->version = UNOFFICIAL_NAPI_HEAP_STATISTICS_VERSION; +} + +// Providers identify measured fields through `valid_fields`; unsupported +// fields have no provider-neutral meaning. Consumers that expose a dense +// Node-compatible object should normalize once at the boundary instead of +// accidentally treating provider scratch values as observations. +static inline void unofficial_napi_heap_statistics_normalize( + unofficial_napi_heap_statistics* stats) { + if (stats == NULL) return; +#define UNOFFICIAL_NAPI_NORMALIZE_HEAP_FIELD(bit, field) \ + do { \ + if ((stats->valid_fields & (bit)) == 0) { \ + stats->field = 0; \ + } \ + } while (0) + UNOFFICIAL_NAPI_NORMALIZE_HEAP_FIELD( + unofficial_napi_heap_stat_total_heap_size, total_heap_size); + UNOFFICIAL_NAPI_NORMALIZE_HEAP_FIELD( + unofficial_napi_heap_stat_total_heap_size_executable, + total_heap_size_executable); + UNOFFICIAL_NAPI_NORMALIZE_HEAP_FIELD( + unofficial_napi_heap_stat_total_physical_size, total_physical_size); + UNOFFICIAL_NAPI_NORMALIZE_HEAP_FIELD( + unofficial_napi_heap_stat_total_available_size, total_available_size); + UNOFFICIAL_NAPI_NORMALIZE_HEAP_FIELD( + unofficial_napi_heap_stat_used_heap_size, used_heap_size); + UNOFFICIAL_NAPI_NORMALIZE_HEAP_FIELD( + unofficial_napi_heap_stat_heap_size_limit, heap_size_limit); + UNOFFICIAL_NAPI_NORMALIZE_HEAP_FIELD( + unofficial_napi_heap_stat_does_zap_garbage, does_zap_garbage); + UNOFFICIAL_NAPI_NORMALIZE_HEAP_FIELD( + unofficial_napi_heap_stat_malloced_memory, malloced_memory); + UNOFFICIAL_NAPI_NORMALIZE_HEAP_FIELD( + unofficial_napi_heap_stat_peak_malloced_memory, peak_malloced_memory); + UNOFFICIAL_NAPI_NORMALIZE_HEAP_FIELD( + unofficial_napi_heap_stat_number_of_native_contexts, + number_of_native_contexts); + UNOFFICIAL_NAPI_NORMALIZE_HEAP_FIELD( + unofficial_napi_heap_stat_number_of_detached_contexts, + number_of_detached_contexts); + UNOFFICIAL_NAPI_NORMALIZE_HEAP_FIELD( + unofficial_napi_heap_stat_total_global_handles_size, + total_global_handles_size); + UNOFFICIAL_NAPI_NORMALIZE_HEAP_FIELD( + unofficial_napi_heap_stat_used_global_handles_size, + used_global_handles_size); + UNOFFICIAL_NAPI_NORMALIZE_HEAP_FIELD( + unofficial_napi_heap_stat_external_memory, external_memory); + UNOFFICIAL_NAPI_NORMALIZE_HEAP_FIELD( + unofficial_napi_heap_stat_array_buffer_memory, array_buffer_memory); + stats->valid_fields &= unofficial_napi_heap_stat_all; +#undef UNOFFICIAL_NAPI_NORMALIZE_HEAP_FIELD +} + typedef struct { char space_name[UNOFFICIAL_NAPI_HEAP_SPACE_NAME_MAX_LENGTH]; uint64_t space_size; @@ -422,10 +506,17 @@ typedef struct { uint64_t cpu_profiler_metadata_size; } unofficial_napi_heap_code_statistics; +typedef struct unofficial_napi_profile__* unofficial_napi_profile; + typedef enum { - unofficial_napi_cpu_profile_start_ok = 0, - unofficial_napi_cpu_profile_start_too_many = 1, -} unofficial_napi_cpu_profile_start_result; + unofficial_napi_profile_cpu = 1, + unofficial_napi_profile_heap = 2, +} unofficial_napi_profile_kind; + +typedef enum { + unofficial_napi_profile_start_ok = 0, + unofficial_napi_profile_start_busy = 1, +} unofficial_napi_profile_start_result; typedef struct { bool expose_internals; @@ -436,51 +527,44 @@ NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_get_heap_statistics( napi_env env, unofficial_napi_heap_statistics* stats_out); -NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_get_heap_space_count( - napi_env env, - uint32_t* count_out); - +// Takes one provider snapshot, writes at most `capacity` entries, and reports +// the snapshot's full entry count through `count_out`. A null `stats_out` is +// valid only when `capacity` is zero, allowing callers to query capacity. NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_get_heap_space_statistics( napi_env env, - uint32_t space_index, - unofficial_napi_heap_space_statistics* stats_out); + unofficial_napi_heap_space_statistics* stats_out, + uint32_t capacity, + uint32_t* count_out); NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_get_heap_code_statistics( napi_env env, unofficial_napi_heap_code_statistics* stats_out); -// Unofficial helpers for worker-thread profiling/snapshot support. -// These must be called on the target env's engine thread, typically from -// unofficial_napi_request_interrupt(). -NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_start_cpu_profile( +// Unofficial helpers for worker-thread profiling/snapshot support. These must +// be called on the target env's engine thread, typically from +// unofficial_napi_request_interrupt(). A successful start returns one opaque, +// env-owned session. Stop leaves the session active when it returns +// napi_cannot_run_js, so the caller can retry from the target engine thread. +// Once stop accepts the thread/env preconditions it consumes the session even +// if producing the final result fails. Diagnostic JSON is returned as UTF-8 +// bytes in a Uint8Array whose backing store is outside the inspected heap. +// Sessions still active at env teardown are stopped and released by the +// provider. +NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_profile_start( napi_env env, - unofficial_napi_cpu_profile_start_result* result_out, - uint32_t* profile_id_out); + unofficial_napi_profile_kind kind, + unofficial_napi_profile_start_result* result_out, + unofficial_napi_profile* profile_out); -NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_stop_cpu_profile( +NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_profile_stop( napi_env env, - uint32_t profile_id, - bool* found_out, - char** json_out, - size_t* json_len_out); - -NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_start_heap_profile( - napi_env env, - bool* started_out); - -NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_stop_heap_profile( - napi_env env, - bool* found_out, - char** json_out, - size_t* json_len_out); + unofficial_napi_profile profile, + napi_value* utf8_json_out); NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_take_heap_snapshot( napi_env env, const unofficial_napi_heap_snapshot_options* options, - char** json_out, - size_t* json_len_out); - -NAPI_EXTENSION_WASMER_EXTERN void unofficial_napi_free_buffer(void* data); + napi_value* utf8_json_out); // Unofficial helpers for Node's async_context_frame parity. These expose the // engine continuation-preserved embedder data used by AsyncContextFrame. @@ -513,14 +597,46 @@ NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_contextify_make_context napi_value host_defined_option_id, napi_value* result_out); -// A JS source for compile/eval APIs: exactly one of `text` (a JS string) or -// `bytecode` (an opaque handle from unofficial_napi_bytecode_compile / -// unofficial_napi_bytecode_deserialize) is set. +// Provider-owned compiled JavaScript. The handle is bound to its creating +// environment and must be released explicitly. +typedef struct unofficial_napi_bytecode__* unofficial_napi_bytecode; + +// A tagged JS source for compile/eval APIs. Providers reject descriptors whose +// selected field is null or whose unselected field is non-null. +typedef enum { + unofficial_napi_js_source_text = 0, + unofficial_napi_js_source_bytecode = 1, +} unofficial_napi_js_source_kind; + typedef struct unofficial_napi_js_source { + int32_t kind; napi_value text; - void* bytecode; + unofficial_napi_bytecode bytecode; } unofficial_napi_js_source; +static inline bool unofficial_napi_js_source_is_valid( + const unofficial_napi_js_source* source) { + return source != NULL && + ((source->kind == unofficial_napi_js_source_text && + source->text != NULL && source->bytecode == NULL) || + (source->kind == unofficial_napi_js_source_bytecode && + source->text == NULL && source->bytecode != NULL)); +} + +static inline unofficial_napi_js_source unofficial_napi_js_source_from_text( + napi_value text) { + unofficial_napi_js_source source = { + unofficial_napi_js_source_text, text, NULL}; + return source; +} + +static inline unofficial_napi_js_source unofficial_napi_js_source_from_bytecode( + unofficial_napi_bytecode bytecode) { + unofficial_napi_js_source source = { + unofficial_napi_js_source_bytecode, NULL, bytecode}; + return source; +} + // Compile shape of a bytecode artifact; bytecode is only usable by APIs that // compile the same shape. typedef enum { @@ -529,64 +645,53 @@ typedef enum { unofficial_napi_bytecode_shape_module = 2, // ES module } unofficial_napi_bytecode_shape; -// Eagerly compiles source text into bytecode. The returned handle holds the -// live compiled artifact plus serializable engine bytes, and retains the -// text/filename/shape/params (V8 code caches are consumed against the -// original source). On compile FAILURE with a non-module shape, -// *can_parse_as_module_out (nullable) reports whether the source parses as an -// ES module — the detect-module signal — while the compile exception stays -// pending. -// host_defined_option_id (Symbol or nullish) is baked into the compiled -// artifact's origin so consumers with the same symbol can reuse it directly; -// a consumer with different options re-consumes the serialized bytes instead. -NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_bytecode_compile( - napi_env env, - napi_value source_text, - napi_value filename, - int32_t shape, - napi_value params_or_undefined, - napi_value host_defined_option_id, - int32_t line_offset, - int32_t column_offset, - void** bytecode_out, - bool* can_parse_as_module_out); - -// Deserializes engine bytes (e.g. a sidecar payload) into a live compiled -// artifact. source_text is the source the bytes were produced from. On -// stale/incompatible bytes the call succeeds with *bytecode_out = NULL and -// *rejected_out = true; the caller falls back to compiling the text. -// Payloads are fully self-validating against (shape, source_text, params, -// filename) — V8 natively via CachedData; QuickJS via the provider's QJSB -// header written at serialize time (which also guards payload integrity, -// since JS_ReadObject is not hardened). A stored filename hash of 0 is -// "unenforced" (vm.SourceTextModule#createCachedData writes 0 because Node -// numbers vm module identifiers). So untrusted cachedData can be handed -// straight in: a mismatch on any of those dimensions is reported as rejected -// without the caller needing its own validation wrapper. - -NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_bytecode_deserialize( - napi_env env, - const uint8_t* bytes, - size_t byte_length, - napi_value source_text, - napi_value filename, - int32_t shape, - napi_value params_or_undefined, - napi_value host_defined_option_id, - void** bytecode_out, - bool* rejected_out); +// Versioned input for opening a compiled artifact. When `has_cache` is nonzero, +// the provider first validates the supplied bytes against every compile input. +// `cache_policy` determines whether a rejected cache is atomically replaced +// by compiling source or reported without the discarded fallback compile. +typedef enum { + unofficial_napi_bytecode_cache_compile_on_reject = 0, + unofficial_napi_bytecode_cache_validate_only = 1, +} unofficial_napi_bytecode_cache_policy; +#define UNOFFICIAL_NAPI_BYTECODE_OPEN_OPTIONS_VERSION 1u +typedef struct unofficial_napi_bytecode_open_options { + uint32_t size; + uint32_t version; + napi_value source_text; + napi_value filename; + int32_t shape; + napi_value params_or_undefined; + napi_value host_defined_option_id; + int32_t line_offset; + int32_t column_offset; + const uint8_t* cache_bytes; + size_t cache_byte_length; + uint8_t has_cache; + uint8_t cache_policy; +} unofficial_napi_bytecode_open_options; + +typedef struct unofficial_napi_bytecode_open_result { + unofficial_napi_bytecode bytecode; + uint8_t cache_rejected; + uint8_t can_parse_as_module; +} unofficial_napi_bytecode_open_result; + +NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_bytecode_open( + napi_env env, + const unofficial_napi_bytecode_open_options* options, + unofficial_napi_bytecode_open_result* result); // Engine bytes for persistence, as a Uint8Array (V8: raw CachedData; // QuickJS: self-validating QJSB header [shape, source, params, filename, // payload hashes] + JS_WriteObject output). NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_bytecode_serialize( napi_env env, - void* bytecode, + unofficial_napi_bytecode bytecode, napi_value* buffer_out); NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_bytecode_release( napi_env env, - void* bytecode); + unofficial_napi_bytecode bytecode); NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_contextify_run_script( napi_env env, @@ -602,10 +707,6 @@ NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_contextify_run_script( napi_value host_defined_option_id, napi_value* result_out); -NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_contextify_dispose_context( - napi_env env, - napi_value sandbox_or_context_global); - NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_contextify_compile_function( napi_env env, const unofficial_napi_js_source* source, @@ -618,14 +719,6 @@ NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_contextify_compile_func napi_value host_defined_option_id, napi_value* result_out); -NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_contextify_compile_function_for_cjs_loader( - napi_env env, - const unofficial_napi_js_source* source, - napi_value filename, - bool is_sea_main, - bool should_detect_module, - napi_value* result_out); - NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_contextify_contains_module_syntax( napi_env env, napi_value code, @@ -634,144 +727,190 @@ NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_contextify_contains_mod bool cjs_var_in_scope, bool* result_out); -NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_contextify_start_sigint_watchdog( - napi_env env, - bool* result_out); +// Unofficial helpers for implementing internalBinding('module_wrap') on embedders. +// These keep V8 module objects behind an opaque native handle so bindings stay N-API only. +typedef struct unofficial_napi_module__* unofficial_napi_module; -NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_contextify_stop_sigint_watchdog( - napi_env env, - bool* had_pending_signal_out); +// Versioned, tagged transaction for creating either kind of module. Keeping +// creation behind one descriptor gives providers one validation boundary and +// lets the ABI grow without adding one import per module kind. +#define UNOFFICIAL_NAPI_MODULE_CREATE_OPTIONS_VERSION 1u -NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_contextify_watchdog_has_pending_sigint( - napi_env env, - bool* result_out); +typedef enum { + unofficial_napi_module_source_text = 1, + unofficial_napi_module_synthetic = 2, +} unofficial_napi_module_kind; -// Unofficial helpers for implementing internalBinding('module_wrap') on embedders. -// These keep V8 module objects behind an opaque native handle so bindings stay N-API only. -NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_module_wrap_create_source_text( - napi_env env, - napi_value wrapper, - napi_value url, - napi_value context_or_undefined, - const unofficial_napi_js_source* source, - int32_t line_offset, - int32_t column_offset, - napi_value host_defined_option_id, - void** handle_out); +typedef struct { + const unofficial_napi_js_source* source; + int32_t line_offset; + int32_t column_offset; + napi_value host_defined_option_id; +} unofficial_napi_source_text_module_options; -NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_module_wrap_create_synthetic( - napi_env env, - napi_value wrapper, - napi_value url, - napi_value context_or_undefined, - napi_value export_names, - napi_value synthetic_eval_steps, - void** handle_out); +typedef struct { + napi_value export_names; + napi_value synthetic_evaluation_steps; +} unofficial_napi_synthetic_module_options; -NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_module_wrap_destroy( +typedef struct { + size_t size; + uint32_t version; + unofficial_napi_module_kind kind; + napi_value wrapper; + napi_value url; + napi_value context_or_undefined; + union { + unofficial_napi_source_text_module_options source_text; + unofficial_napi_synthetic_module_options synthetic; + } payload; +} unofficial_napi_module_create_options; + +// Immutable metadata produced by the same transaction that creates a module. +// The layout is selected by options.version, so adding fields requires a new +// create-options version rather than another query function. +typedef struct { + unofficial_napi_module module; + napi_value module_requests; + bool has_top_level_await; +} unofficial_napi_module_create_result; + +NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_module_wrap_create( napi_env env, - void* handle); + const unofficial_napi_module_create_options* options, + unofficial_napi_module_create_result* result_out); -NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_module_wrap_get_module_requests( +NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_module_wrap_destroy( napi_env env, - void* handle, - napi_value* result_out); + unofficial_napi_module module); NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_module_wrap_link( napi_env env, - void* handle, + unofficial_napi_module module, size_t count, - void* const* linked_handles); + const unofficial_napi_module* linked_modules); NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_module_wrap_instantiate( napi_env env, - void* handle); + unofficial_napi_module module); NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_module_wrap_evaluate( napi_env env, - void* handle, + unofficial_napi_module module, int64_t timeout, bool break_on_sigint, napi_value* result_out); NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_module_wrap_evaluate_sync( napi_env env, - void* handle, + unofficial_napi_module module, napi_value filename, napi_value parent_filename, napi_value* result_out); NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_module_wrap_get_namespace( napi_env env, - void* handle, + unofficial_napi_module module, napi_value* result_out); -NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_module_wrap_get_status( +// Returns the requested fields from one observation of the provider-owned +// module record. At least one output must be non-null. Expensive fields are +// computed only when requested; has_async_graph is false before instantiation. +NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_module_wrap_get_state( napi_env env, - void* handle, - int32_t* status_out); - -NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_module_wrap_get_error( - napi_env env, - void* handle, - napi_value* result_out); - -NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_module_wrap_has_top_level_await( - napi_env env, - void* handle, - bool* result_out); - -NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_module_wrap_has_async_graph( - napi_env env, - void* handle, - bool* result_out); + unofficial_napi_module module, + int32_t* status_out, + napi_value* error_out, + bool* has_async_graph_out); NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_module_wrap_check_unsettled_top_level_await( napi_env env, - napi_value module_wrap, + unofficial_napi_module module, bool warnings, bool* settled_out); NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_module_wrap_set_export( napi_env env, - void* handle, + unofficial_napi_module module, napi_value export_name, napi_value export_value); NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_module_wrap_set_module_source_object( napi_env env, - void* handle, + unofficial_napi_module module, napi_value source_object); NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_module_wrap_get_module_source_object( napi_env env, - void* handle, + unofficial_napi_module module, napi_value* result_out); NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_module_wrap_create_cached_data( napi_env env, - void* handle, + unofficial_napi_module module, napi_value* result_out); -NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_module_wrap_set_import_module_dynamically_callback( - napi_env env, - napi_value callback); +#define UNOFFICIAL_NAPI_MODULE_HOOKS_VERSION 1u -NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_module_wrap_set_initialize_import_meta_object_callback( - napi_env env, - napi_value callback); +typedef struct { + size_t size; + uint32_t version; + napi_value import_module_dynamically; + napi_value initialize_import_meta_object; +} unofficial_napi_module_hooks; -NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_module_wrap_import_module_dynamically( +NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_module_wrap_set_hooks( napi_env env, - size_t argc, - napi_value* argv, - napi_value* result_out); + const unofficial_napi_module_hooks* hooks); NAPI_EXTENSION_WASMER_EXTERN napi_status unofficial_napi_module_wrap_create_required_module_facade( napi_env env, - void* handle, + unofficial_napi_module module, napi_value* result_out); +#include "unofficial_napi_wasm32_abi.h" + +#if defined(__wasm32__) +#if defined(__cplusplus) +static_assert(sizeof(unofficial_napi_env_create_options) == + sizeof(unofficial_napi_wasm32_env_create_options_v1)); +static_assert(sizeof(unofficial_napi_runtime_options) == + sizeof(unofficial_napi_wasm32_runtime_options_v1)); +static_assert(sizeof(unofficial_napi_env_hooks) == + sizeof(unofficial_napi_wasm32_env_hooks_v1)); +static_assert(sizeof(unofficial_napi_js_source) == + sizeof(unofficial_napi_wasm32_js_source)); +static_assert(sizeof(unofficial_napi_bytecode_open_options) == + sizeof(unofficial_napi_wasm32_bytecode_open_options_v1)); +static_assert(sizeof(unofficial_napi_module_create_options) == + sizeof(unofficial_napi_wasm32_module_create_options_v1)); +static_assert(sizeof(unofficial_napi_module_hooks) == + sizeof(unofficial_napi_wasm32_module_hooks_v1)); +#else +_Static_assert(sizeof(unofficial_napi_env_create_options) == + sizeof(unofficial_napi_wasm32_env_create_options_v1), + "public env-create layout differs from its wasm32 wire ABI"); +_Static_assert(sizeof(unofficial_napi_runtime_options) == + sizeof(unofficial_napi_wasm32_runtime_options_v1), + "public runtime-options layout differs from its wasm32 wire ABI"); +_Static_assert(sizeof(unofficial_napi_env_hooks) == + sizeof(unofficial_napi_wasm32_env_hooks_v1), + "public env-hooks layout differs from its wasm32 wire ABI"); +_Static_assert(sizeof(unofficial_napi_js_source) == + sizeof(unofficial_napi_wasm32_js_source), + "public JS-source layout differs from its wasm32 wire ABI"); +_Static_assert(sizeof(unofficial_napi_bytecode_open_options) == + sizeof(unofficial_napi_wasm32_bytecode_open_options_v1), + "public bytecode-open layout differs from its wasm32 wire ABI"); +_Static_assert(sizeof(unofficial_napi_module_create_options) == + sizeof(unofficial_napi_wasm32_module_create_options_v1), + "public module-create layout differs from its wasm32 wire ABI"); +_Static_assert(sizeof(unofficial_napi_module_hooks) == + sizeof(unofficial_napi_wasm32_module_hooks_v1), + "public module-hooks layout differs from its wasm32 wire ABI"); +#endif +#endif + #ifdef __cplusplus } // extern "C" #endif diff --git a/include/unofficial_napi_wasm32_abi.h b/include/unofficial_napi_wasm32_abi.h new file mode 100644 index 0000000..96508dc --- /dev/null +++ b/include/unofficial_napi_wasm32_abi.h @@ -0,0 +1,105 @@ +#ifndef UNOFFICIAL_NAPI_WASM32_ABI_H_ +#define UNOFFICIAL_NAPI_WASM32_ABI_H_ + +#include +#include + +// Fixed-width representations observed by Wasmer when Edge is compiled for +// wasm32. These are a wire contract, not native provider implementation types. +// Every pointer and size_t in the public C API is represented by one u32 here. +typedef struct { + uint32_t size; + uint32_t version; + uint32_t engine_flags; + uint32_t engine_flags_length; +} unofficial_napi_wasm32_runtime_options_v1; + +typedef struct { + uint32_t size; + uint32_t version; + uint64_t total_memory; + uint64_t constrained_memory; + uint32_t max_young_generation_size_in_bytes; + uint32_t max_old_generation_size_in_bytes; + uint32_t code_range_size_in_bytes; + uint32_t stack_limit; + uint32_t guest_heap; +} unofficial_napi_wasm32_env_create_options_v1; + +typedef struct { + uint32_t size; + uint32_t version; + uint32_t data; + uint32_t context_token_assign_callback; + uint32_t context_token_unassign_callback; + uint32_t enqueue_foreground_task_callback; + uint32_t fatal_error_callback; + uint32_t oom_error_callback; +} unofficial_napi_wasm32_env_hooks_v1; + +typedef struct { + int32_t kind; + uint32_t text; + uint32_t bytecode; +} unofficial_napi_wasm32_js_source; + +typedef struct { + uint32_t size; + uint32_t version; + uint32_t source_text; + uint32_t filename; + int32_t shape; + uint32_t params_or_undefined; + uint32_t host_defined_option_id; + int32_t line_offset; + int32_t column_offset; + uint32_t cache_bytes; + uint32_t cache_byte_length; + uint8_t has_cache; + uint8_t cache_policy; + uint8_t reserved[2]; +} unofficial_napi_wasm32_bytecode_open_options_v1; + +typedef struct { + uint32_t size; + uint32_t version; + int32_t kind; + uint32_t wrapper; + uint32_t url; + uint32_t context_or_undefined; + uint8_t payload[16]; +} unofficial_napi_wasm32_module_create_options_v1; + +typedef struct { + uint32_t size; + uint32_t version; + uint32_t import_module_dynamically; + uint32_t initialize_import_meta_object; +} unofficial_napi_wasm32_module_hooks_v1; + +#if defined(__cplusplus) +static_assert(sizeof(unofficial_napi_wasm32_runtime_options_v1) == 16); +static_assert(sizeof(unofficial_napi_wasm32_env_create_options_v1) == 48); +static_assert(sizeof(unofficial_napi_wasm32_env_hooks_v1) == 32); +static_assert(sizeof(unofficial_napi_wasm32_js_source) == 12); +static_assert(sizeof(unofficial_napi_wasm32_bytecode_open_options_v1) == 48); +static_assert(sizeof(unofficial_napi_wasm32_module_create_options_v1) == 40); +static_assert(sizeof(unofficial_napi_wasm32_module_hooks_v1) == 16); +#else +_Static_assert(sizeof(unofficial_napi_wasm32_runtime_options_v1) == 16, + "wasm32 runtime-options wire layout changed"); +_Static_assert(sizeof(unofficial_napi_wasm32_env_create_options_v1) == 48, + "wasm32 env-create wire layout changed"); +_Static_assert(sizeof(unofficial_napi_wasm32_env_hooks_v1) == 32, + "wasm32 env-hooks wire layout changed"); +_Static_assert(sizeof(unofficial_napi_wasm32_js_source) == 12, + "wasm32 JS-source wire layout changed"); +_Static_assert(sizeof(unofficial_napi_wasm32_bytecode_open_options_v1) == 48, + "wasm32 bytecode-open wire layout changed"); +_Static_assert(sizeof(unofficial_napi_wasm32_module_create_options_v1) == 40, + "wasm32 module-create wire layout changed"); +_Static_assert(sizeof(unofficial_napi_wasm32_module_hooks_v1) == 16, + "wasm32 module-hooks wire layout changed"); +#endif + +#endif // UNOFFICIAL_NAPI_WASM32_ABI_H_ diff --git a/quickjs/deps/quickjs b/quickjs/deps/quickjs index 9d5513a..ff1471c 160000 --- a/quickjs/deps/quickjs +++ b/quickjs/deps/quickjs @@ -1 +1 @@ -Subproject commit 9d5513a65693e4fc16f48975df59f6fa62f6a9b0 +Subproject commit ff1471cf525483ea1e5b8030d5ecf274ed00eb70 diff --git a/quickjs/src/internal/napi_bytecode.h b/quickjs/src/internal/napi_bytecode.h index 5d97e48..9f27d2c 100644 --- a/quickjs/src/internal/napi_bytecode.h +++ b/quickjs/src/internal/napi_bytecode.h @@ -153,6 +153,15 @@ namespace quickjs::detail // cjs_function -> the compiled function; module -> the module value. JSValue artifact = JS_UNDEFINED; }; + + inline napi_bytecode_record__ *napi_bytecode_record_from_source( + const unofficial_napi_js_source *source) + { + return source != nullptr && + source->kind == unofficial_napi_js_source_bytecode + ? reinterpret_cast(source->bytecode) + : nullptr; + } } #endif // NAPI_QUICKJS_INTERNAL_NAPI_BYTECODE_H_ diff --git a/quickjs/src/internal/napi_contextify.cc b/quickjs/src/internal/napi_contextify.cc index d4ac04c..7db6b3b 100644 --- a/quickjs/src/internal/napi_contextify.cc +++ b/quickjs/src/internal/napi_contextify.cc @@ -629,13 +629,27 @@ namespace quickjs::detail napi_util__::set_string_property(ctx_, exception, "stack", summary + "\n" + stack_text); } - napi_status napi_contextify__::get_error_source_positions( + napi_status napi_contextify__::get_error_metadata( napi_value error, - unofficial_napi_error_source_positions *out) + unofficial_napi_error_metadata_mode mode, + unofficial_napi_error_metadata *out) { if (!napi_util__::check_env(env_) || error == nullptr || out == nullptr) return napi_invalid_arg; std::memset(out, 0, sizeof(*out)); + if (mode == unofficial_napi_error_metadata_take_preserved) + return napi_ok; + if (mode != unofficial_napi_error_metadata_current && + mode != unofficial_napi_error_metadata_positions_only && + mode != unofficial_napi_error_metadata_thrown_at_only) + return napi_invalid_arg; + + // QuickJS does not currently expose a provider stack snapshot for an + // arbitrary caught value. The cheap query is nevertheless a valid, + // side-effect-free empty result and must not inspect user properties. + if (mode == unofficial_napi_error_metadata_thrown_at_only) + return napi_ok; + out->line_number = -1; out->start_column = -1; out->end_column = -1; @@ -645,7 +659,22 @@ namespace quickjs::detail return status; out->source_line = empty; out->script_resource_name = empty; - return napi_ok; + + if (mode == unofficial_napi_error_metadata_positions_only) + return napi_ok; + + JSValueConst raw_error = napi_quickjs_value_inner(env_, error); + if (!JS_IsObject(raw_error)) + return napi_util__::create_undefined(env_, &out->stderr_line); + JSValue value = JS_GetPropertyStr(ctx_, raw_error, "node:arrowMessage"); + if (JS_IsException(value)) + return napi_pending_exception; + if (JS_IsUndefined(value)) + { + JS_FreeValue(ctx_, value); + return napi_util__::create_undefined(env_, &out->stderr_line); + } + return napi_util__::wrap_owned(env_, value, &out->stderr_line); } napi_status napi_contextify__::preserve_error_source_message(napi_value error) @@ -656,15 +685,7 @@ namespace quickjs::detail return napi_ok; } - napi_status napi_contextify__::set_source_maps_enabled(bool enabled) - { - if (!napi_util__::check_env(env_)) - return napi_invalid_arg; - source_maps_enabled_ = enabled; - return napi_ok; - } - - napi_status napi_contextify__::set_get_source_map_error_source_callback(napi_value callback) + napi_status napi_contextify__::configure_source_maps(bool enabled, napi_value callback) { if (!napi_util__::check_env(env_)) return napi_invalid_arg; @@ -674,49 +695,13 @@ namespace quickjs::detail return napi_invalid_arg; } + source_maps_enabled_ = enabled; JS_FreeValue(ctx_, source_map_error_source_callback_); source_map_error_source_callback_ = callback == nullptr ? JS_UNDEFINED : JS_DupValue(ctx_, napi_quickjs_value_inner(env_, callback)); return napi_ok; } - napi_status napi_contextify__::get_error_source_line_for_stderr(napi_value error, - napi_value *result_out) - { - if (!napi_util__::check_env(env_) || error == nullptr || result_out == nullptr) - return napi_invalid_arg; - JSValue value = JS_GetPropertyStr(ctx_, napi_quickjs_value_inner(env_, error), "node:arrowMessage"); - if (JS_IsException(value)) - return napi_pending_exception; - if (JS_IsUndefined(value)) - { - JS_FreeValue(ctx_, value); - return napi_util__::create_undefined(env_, result_out); - } - return napi_util__::wrap_owned(env_, value, result_out); - } - - napi_status napi_contextify__::get_error_thrown_at(napi_value error, - napi_value *result_out) - { - (void)error; - if (!napi_util__::check_env(env_) || result_out == nullptr) - return napi_invalid_arg; - return napi_util__::create_undefined(env_, result_out); - } - - napi_status napi_contextify__::take_preserved_error_formatting(napi_value error, - napi_value *source_line_out, - napi_value *thrown_at_out) - { - if (!napi_util__::check_env(env_) || error == nullptr || source_line_out == nullptr || thrown_at_out == nullptr) - return napi_invalid_arg; - napi_status status = get_error_source_line_for_stderr(error, source_line_out); - if (status != napi_ok) - return status; - return napi_util__::create_undefined(env_, thrown_at_out); - } - napi_status napi_contextify__::make_context(napi_value sandbox_or_symbol, napi_value name, napi_value origin_or_undefined, @@ -778,10 +763,10 @@ namespace quickjs::detail (void)display_errors; (void)break_on_sigint; (void)break_on_first_line; - if (!napi_util__::check_env(env_) || source == nullptr || - (source->text == nullptr && source->bytecode == nullptr) || result_out == nullptr) + if (!napi_util__::check_env(env_) || + !unofficial_napi_js_source_is_valid(source) || result_out == nullptr) return napi_invalid_arg; - auto *bytecode_record = static_cast(source->bytecode); + auto *bytecode_record = napi_bytecode_record_from_source(source); if (bytecode_record != nullptr && bytecode_record->shape != unofficial_napi_bytecode_shape_script) return napi_invalid_arg; @@ -881,11 +866,11 @@ namespace quickjs::detail { (void)context_extensions_or_undefined; (void)host_defined_option_id; - if (!napi_util__::check_env(env_) || source == nullptr || - (source->text == nullptr && source->bytecode == nullptr) || result_out == nullptr) + if (!napi_util__::check_env(env_) || + !unofficial_napi_js_source_is_valid(source) || result_out == nullptr) return napi_invalid_arg; - auto *bytecode_record = static_cast(source->bytecode); + auto *bytecode_record = napi_bytecode_record_from_source(source); if (bytecode_record != nullptr && bytecode_record->shape != unofficial_napi_bytecode_shape_cjs_function) return napi_invalid_arg; @@ -1077,7 +1062,7 @@ namespace quickjs::detail napi_value host_defined_option_id, int32_t line_offset, int32_t column_offset, - void **bytecode_out, + unofficial_napi_bytecode *bytecode_out, bool *can_parse_as_module_out) { (void)host_defined_option_id; // QuickJS handles HDO at record registration. @@ -1155,7 +1140,7 @@ namespace quickjs::detail return napi_invalid_arg; } - *bytecode_out = record.release(); + *bytecode_out = reinterpret_cast(record.release()); return napi_ok; } @@ -1166,7 +1151,7 @@ namespace quickjs::detail int32_t shape, napi_value params_or_undefined, napi_value host_defined_option_id, - void **bytecode_out, + unofficial_napi_bytecode *bytecode_out, bool *rejected_out) { (void)host_defined_option_id; // QuickJS handles HDO at record registration. @@ -1244,15 +1229,15 @@ namespace quickjs::detail record->artifact = restored; } - *bytecode_out = record.release(); + *bytecode_out = reinterpret_cast(record.release()); return napi_ok; } - napi_status napi_contextify__::bytecode_serialize(void *bytecode, napi_value *buffer_out) + napi_status napi_contextify__::bytecode_serialize(unofficial_napi_bytecode bytecode, napi_value *buffer_out) { if (!napi_util__::check_env(env_) || bytecode == nullptr || buffer_out == nullptr) return napi_invalid_arg; - auto *record = static_cast(bytecode); + auto *record = reinterpret_cast(bytecode); std::vector persisted; if (!record->bytes.empty()) { @@ -1273,11 +1258,11 @@ namespace quickjs::detail return napi_create_typedarray(env_, napi_uint8_array, persisted.size(), arraybuffer, 0, buffer_out); } - napi_status napi_contextify__::bytecode_release(void *bytecode) + napi_status napi_contextify__::bytecode_release(unofficial_napi_bytecode bytecode) { if (bytecode == nullptr) return napi_invalid_arg; - auto *record = static_cast(bytecode); + auto *record = reinterpret_cast(bytecode); if (record->ctx != nullptr && !JS_IsUndefined(record->artifact)) JS_FreeValue(record->ctx, record->artifact); delete record; diff --git a/quickjs/src/internal/napi_contextify.h b/quickjs/src/internal/napi_contextify.h index 73e7e4e..9936fc2 100644 --- a/quickjs/src/internal/napi_contextify.h +++ b/quickjs/src/internal/napi_contextify.h @@ -25,18 +25,11 @@ namespace quickjs::detail napi_contextify__(const napi_contextify__ &) = delete; napi_contextify__ &operator=(const napi_contextify__ &) = delete; - napi_status get_error_source_positions(napi_value error, - unofficial_napi_error_source_positions *out); + napi_status get_error_metadata(napi_value error, + unofficial_napi_error_metadata_mode mode, + unofficial_napi_error_metadata *out); napi_status preserve_error_source_message(napi_value error); - napi_status set_source_maps_enabled(bool enabled); - napi_status set_get_source_map_error_source_callback(napi_value callback); - napi_status get_error_source_line_for_stderr(napi_value error, - napi_value *result_out); - napi_status get_error_thrown_at(napi_value error, - napi_value *result_out); - napi_status take_preserved_error_formatting(napi_value error, - napi_value *source_line_out, - napi_value *thrown_at_out); + napi_status configure_source_maps(bool enabled, napi_value callback); napi_status make_context(napi_value sandbox_or_symbol, napi_value name, @@ -79,7 +72,7 @@ namespace quickjs::detail napi_value host_defined_option_id, int32_t line_offset, int32_t column_offset, - void **bytecode_out, + unofficial_napi_bytecode *bytecode_out, bool *can_parse_as_module_out); napi_status bytecode_deserialize(const uint8_t *bytes, size_t byte_length, @@ -88,10 +81,10 @@ namespace quickjs::detail int32_t shape, napi_value params_or_undefined, napi_value host_defined_option_id, - void **bytecode_out, + unofficial_napi_bytecode *bytecode_out, bool *rejected_out); - napi_status bytecode_serialize(void *bytecode, napi_value *buffer_out); - napi_status bytecode_release(void *bytecode); + napi_status bytecode_serialize(unofficial_napi_bytecode bytecode, napi_value *buffer_out); + napi_status bytecode_release(unofficial_napi_bytecode bytecode); private: bool compile_trace_enabled() const; diff --git a/quickjs/src/internal/napi_env.h b/quickjs/src/internal/napi_env.h index ef98681..de2912b 100644 --- a/quickjs/src/internal/napi_env.h +++ b/quickjs/src/internal/napi_env.h @@ -3,6 +3,7 @@ #include "../../../include/js_native_api.h" #include "../../../include/node_api_types.h" +#include "../../../include/unofficial_napi.h" #include "napi_callback_info.h" #include "napi_buffer.h" #include "napi_contextify.h" @@ -113,6 +114,15 @@ struct napi_env__ quickjs::detail::napi_module_wrap__ &module_wrap(); const quickjs::detail::napi_module_wrap__ &module_wrap() const; + bool attach_embedder_hooks(const unofficial_napi_env_hooks &hooks) + { + if (embedder_hooks_attached_) + return false; + embedder_hooks_ = hooks; + embedder_hooks_attached_ = true; + return true; + } + private: void clear_refs_for_teardown(); @@ -164,6 +174,8 @@ struct napi_env__ // Env teardown state. bool torn_down_ = false; bool clearing_refs_for_teardown_ = false; + bool embedder_hooks_attached_ = false; + unofficial_napi_env_hooks embedder_hooks_{}; }; class napi_env_context_scope__ diff --git a/quickjs/src/internal/napi_module_wrap.cc b/quickjs/src/internal/napi_module_wrap.cc index eccec66..e87f340 100644 --- a/quickjs/src/internal/napi_module_wrap.cc +++ b/quickjs/src/internal/napi_module_wrap.cc @@ -170,9 +170,9 @@ void napi_module_wrap__::teardown() torn_down_ = true; } -napi_module_wrap__::record *napi_module_wrap__::find(void *handle) const +napi_module_wrap__::record *napi_module_wrap__::find(unofficial_napi_module module) const { - auto *entry = static_cast(handle); + auto *entry = reinterpret_cast(module); if (entry == nullptr) return nullptr; for (record *candidate : records_) @@ -451,23 +451,22 @@ napi_status napi_module_wrap__::create_source_text(napi_value wrapper, int32_t line_offset, int32_t column_offset, napi_value host_defined_option_id, - void **handle_out) + unofficial_napi_module *module_out) { (void)context_or_undefined; (void)column_offset; if (!napi_util__::check_value(env_, wrapper) || !napi_util__::check_value(env_, url) || - source == nullptr || - (source->text == nullptr && source->bytecode == nullptr) || - handle_out == nullptr) + !unofficial_napi_js_source_is_valid(source) || + module_out == nullptr) return napi_util__::invalid_arg(env_); - auto *bytecode_record = static_cast(source->bytecode); + auto *bytecode_record = napi_bytecode_record_from_source(source); if (bytecode_record != nullptr && bytecode_record->shape != unofficial_napi_bytecode_shape_module) return napi_util__::invalid_arg(env_); - *handle_out = nullptr; + *module_out = nullptr; std::string url_string = napi_util__::to_utf8(env_, url); std::string source_string = bytecode_record != nullptr ? bytecode_record->source_utf8 @@ -539,7 +538,7 @@ napi_status napi_module_wrap__::create_source_text(napi_value wrapper, JS_SetPropertyStr(ctx_, napi_quickjs_value_inner(env_, wrapper), "sourceURL", JS_NewString(ctx_, url_string.c_str())); JS_SetPropertyStr(ctx_, napi_quickjs_value_inner(env_, wrapper), "sourceMapURL", JS_UNDEFINED); - *handle_out = entry; + *module_out = reinterpret_cast(entry); return napi_ok; } @@ -548,17 +547,17 @@ napi_status napi_module_wrap__::create_synthetic(napi_value wrapper, napi_value context_or_undefined, napi_value export_names, napi_value synthetic_eval_steps, - void **handle_out) + unofficial_napi_module *module_out) { (void)context_or_undefined; if (!napi_util__::check_value(env_, wrapper) || !napi_util__::check_value(env_, url) || !napi_util__::check_value(env_, export_names) || !napi_util__::check_value(env_, synthetic_eval_steps) || - handle_out == nullptr) + module_out == nullptr) return napi_util__::invalid_arg(env_); - *handle_out = nullptr; + *module_out = nullptr; std::string url_string = napi_util__::to_utf8(env_, url); JSModuleDef *module = JS_NewCModule(ctx_, url_string.empty() ? "" : url_string.c_str(), @@ -593,22 +592,24 @@ napi_status napi_module_wrap__::create_synthetic(napi_value wrapper, entry->synthetic = true; records_.push_back(entry); - *handle_out = entry; + *module_out = reinterpret_cast(entry); return napi_ok; } -napi_status napi_module_wrap__::destroy(void *handle) +napi_status napi_module_wrap__::destroy(unofficial_napi_module module) { - record *entry = find(handle); + record *entry = find(module); if (entry != nullptr) free_record(entry); return napi_ok; } -napi_status napi_module_wrap__::get_module_requests(void *handle, napi_value *result_out) +napi_status napi_module_wrap__::get_creation_metadata(unofficial_napi_module module, + napi_value *requests_out, + bool *has_top_level_await_out) { - record *entry = find(handle); - if (entry == nullptr || result_out == nullptr) + record *entry = find(module); + if (entry == nullptr || requests_out == nullptr || has_top_level_await_out == nullptr) return napi_util__::invalid_arg(env_); JSValue array = JS_NewArray(ctx_); @@ -637,15 +638,16 @@ napi_status napi_module_wrap__::get_module_requests(void *handle, napi_value *re JS_DefinePropertyValueUint32(ctx_, array, static_cast(i), object, JS_PROP_C_W_E); } - return wrap_owned(array, result_out); + *has_top_level_await_out = JS_ModuleHasTopLevelAwait(ctx_, entry->module); + return wrap_owned(array, requests_out); } -napi_status napi_module_wrap__::link(void *handle, +napi_status napi_module_wrap__::link(unofficial_napi_module module, size_t count, - void *const *linked_handles) + const unofficial_napi_module *linked_modules) { - record *entry = find(handle); - if (entry == nullptr || (count > 0 && linked_handles == nullptr)) + record *entry = find(module); + if (entry == nullptr || (count > 0 && linked_modules == nullptr)) return napi_util__::invalid_arg(env_); if (count != entry->requests.size()) return throw_error("ERR_MODULE_LINK_MISMATCH", "Module link count mismatch"); @@ -657,7 +659,7 @@ napi_status napi_module_wrap__::link(void *handle, for (size_t i = 0; i < count; ++i) { - record *linked = find(linked_handles[i]); + record *linked = find(linked_modules[i]); if (linked == nullptr) return throw_error("ERR_VM_MODULE_LINK_FAILURE", "Linked module is invalid"); @@ -687,9 +689,9 @@ napi_status napi_module_wrap__::link(void *handle, return napi_ok; } -napi_status napi_module_wrap__::instantiate(void *handle) +napi_status napi_module_wrap__::instantiate(unofficial_napi_module module) { - record *entry = find(handle); + record *entry = find(module); if (entry == nullptr) return napi_util__::invalid_arg(env_); if (JS_LinkModule(ctx_, entry->module) < 0) @@ -697,14 +699,14 @@ napi_status napi_module_wrap__::instantiate(void *handle) return napi_ok; } -napi_status napi_module_wrap__::evaluate(void *handle, +napi_status napi_module_wrap__::evaluate(unofficial_napi_module module, int64_t timeout, bool break_on_sigint, napi_value *result_out) { (void)timeout; (void)break_on_sigint; - record *entry = find(handle); + record *entry = find(module); if (entry == nullptr || result_out == nullptr) return napi_util__::invalid_arg(env_); JSValue promise = JS_EvaluateModule(ctx_, entry->module); @@ -713,14 +715,14 @@ napi_status napi_module_wrap__::evaluate(void *handle, return wrap_owned(promise, result_out); } -napi_status napi_module_wrap__::evaluate_sync(void *handle, +napi_status napi_module_wrap__::evaluate_sync(unofficial_napi_module module, napi_value filename, napi_value parent_filename, napi_value *result_out) { (void)filename; (void)parent_filename; - record *entry = find(handle); + record *entry = find(module); if (entry == nullptr || result_out == nullptr) return napi_util__::invalid_arg(env_); @@ -732,7 +734,7 @@ napi_status napi_module_wrap__::evaluate_sync(void *handle, if (state == JS_PROMISE_FULFILLED) { JS_FreeValue(ctx_, promise); - return get_namespace(handle, result_out); + return get_namespace(module, result_out); } if (state == JS_PROMISE_REJECTED) { @@ -747,9 +749,10 @@ napi_status napi_module_wrap__::evaluate_sync(void *handle, "require() cannot be used on an ESM graph with top-level await"); } -napi_status napi_module_wrap__::get_namespace(void *handle, napi_value *result_out) +napi_status napi_module_wrap__::get_namespace(unofficial_napi_module module, + napi_value *result_out) { - record *entry = find(handle); + record *entry = find(module); if (entry == nullptr || result_out == nullptr) return napi_util__::invalid_arg(env_); JSValue ns = JS_GetModuleNamespace(ctx_, entry->module); @@ -758,90 +761,79 @@ napi_status napi_module_wrap__::get_namespace(void *handle, napi_value *result_o return wrap_owned(ns, result_out); } -napi_status napi_module_wrap__::get_status(void *handle, int32_t *status_out) +napi_status napi_module_wrap__::get_state(unofficial_napi_module module, + int32_t *status_out, + napi_value *error_out, + bool *has_async_graph_out) { - record *entry = find(handle); - if (entry == nullptr || status_out == nullptr) + record *entry = find(module); + if (entry == nullptr || + (status_out == nullptr && error_out == nullptr && + has_async_graph_out == nullptr)) return napi_util__::invalid_arg(env_); JSValue error = JS_GetModuleEvaluationException(ctx_, entry->module); const bool has_error = !JS_IsUndefined(error); - JS_FreeValue(ctx_, error); - if (has_error) + int32_t status = kUninstantiated; + if (status_out != nullptr || has_async_graph_out != nullptr) { - *status_out = kErrored; - return napi_ok; + if (has_error) + { + status = kErrored; + } + else + { + switch (JS_GetModuleStatus(ctx_, entry->module)) + { + case JS_MODULE_STATUS_UNLINKED: + status = kUninstantiated; + break; + case JS_MODULE_STATUS_LINKING: + status = kInstantiating; + break; + case JS_MODULE_STATUS_LINKED: + status = kInstantiated; + break; + case JS_MODULE_STATUS_EVALUATING: + case JS_MODULE_STATUS_EVALUATING_ASYNC: + status = kEvaluating; + break; + case JS_MODULE_STATUS_EVALUATED: + status = kEvaluated; + break; + } + } } - - switch (JS_GetModuleStatus(ctx_, entry->module)) + if (status_out != nullptr) + *status_out = status; + if (has_async_graph_out != nullptr) { - case JS_MODULE_STATUS_UNLINKED: - *status_out = kUninstantiated; - break; - case JS_MODULE_STATUS_LINKING: - *status_out = kInstantiating; - break; - case JS_MODULE_STATUS_LINKED: - *status_out = kInstantiated; - break; - case JS_MODULE_STATUS_EVALUATING: - case JS_MODULE_STATUS_EVALUATING_ASYNC: - *status_out = kEvaluating; - break; - case JS_MODULE_STATUS_EVALUATED: - *status_out = kEvaluated; - break; + *has_async_graph_out = + status >= kInstantiated && JS_ModuleHasAsyncGraph(ctx_, entry->module); } - return napi_ok; -} - -napi_status napi_module_wrap__::get_error(void *handle, napi_value *result_out) -{ - record *entry = find(handle); - if (entry == nullptr || result_out == nullptr) - return napi_util__::invalid_arg(env_); - JSValue error = JS_GetModuleEvaluationException(ctx_, entry->module); - return wrap_owned(error, result_out); -} + if (error_out != nullptr) + return wrap_owned(error, error_out); -napi_status napi_module_wrap__::has_top_level_await(void *handle, bool *result_out) -{ - record *entry = find(handle); - if (entry == nullptr || result_out == nullptr) - return napi_util__::invalid_arg(env_); - *result_out = JS_ModuleHasTopLevelAwait(ctx_, entry->module); - return napi_ok; -} - -napi_status napi_module_wrap__::has_async_graph(void *handle, bool *result_out) -{ - record *entry = find(handle); - if (entry == nullptr || result_out == nullptr) - return napi_util__::invalid_arg(env_); - JSModuleStatusEnum status = JS_GetModuleStatus(ctx_, entry->module); - if (status == JS_MODULE_STATUS_UNLINKED || status == JS_MODULE_STATUS_LINKING) - return throw_error("ERR_MODULE_NOT_INSTANTIATED", "Module has not been instantiated"); - *result_out = JS_ModuleHasAsyncGraph(ctx_, entry->module); + JS_FreeValue(ctx_, error); return napi_ok; } -napi_status napi_module_wrap__::check_unsettled_top_level_await(napi_value module_wrap, +napi_status napi_module_wrap__::check_unsettled_top_level_await(unofficial_napi_module module, bool warnings, bool *settled_out) { - (void)module_wrap; (void)warnings; - if (settled_out == nullptr) + if (find(module) == nullptr || settled_out == nullptr) return napi_util__::invalid_arg(env_); *settled_out = true; return napi_ok; } -napi_status napi_module_wrap__::set_export(void *handle, +napi_status napi_module_wrap__::set_export(unofficial_napi_module module, napi_value export_name, napi_value export_value) { - record *entry = find(handle); + record *entry = find(module); if (entry == nullptr || !napi_util__::check_value(env_, export_name) || !napi_util__::check_value(env_, export_value)) @@ -861,10 +853,10 @@ napi_status napi_module_wrap__::set_export(void *handle, return napi_ok; } -napi_status napi_module_wrap__::set_module_source_object(void *handle, +napi_status napi_module_wrap__::set_module_source_object(unofficial_napi_module module, napi_value source_object) { - record *entry = find(handle); + record *entry = find(module); if (entry == nullptr) return napi_util__::invalid_arg(env_); JS_FreeValue(ctx_, entry->source_object); @@ -872,10 +864,10 @@ napi_status napi_module_wrap__::set_module_source_object(void *handle, return napi_ok; } -napi_status napi_module_wrap__::get_module_source_object(void *handle, +napi_status napi_module_wrap__::get_module_source_object(unofficial_napi_module module, napi_value *result_out) { - record *entry = find(handle); + record *entry = find(module); if (entry == nullptr || result_out == nullptr) return napi_util__::invalid_arg(env_); if (JS_IsUndefined(entry->source_object)) @@ -883,7 +875,7 @@ napi_status napi_module_wrap__::get_module_source_object(void *handle, return wrap_owned(JS_DupValue(ctx_, entry->source_object), result_out); } -napi_status napi_module_wrap__::create_cached_data(void *handle, +napi_status napi_module_wrap__::create_cached_data(unofficial_napi_module module, napi_value *result_out) { if (result_out == nullptr) @@ -893,7 +885,7 @@ napi_status napi_module_wrap__::create_cached_data(void *handle, // An evaluated module's function bytecode is gone; fall back to an empty // buffer in that case, matching the V8 provider's empty-cache behavior. std::vector bytes; - record *entry = find(handle); + record *entry = find(module); if (entry != nullptr && !entry->synthetic && !JS_IsUndefined(entry->module_value)) { size_t serialized_size = 0; @@ -932,23 +924,27 @@ napi_status napi_module_wrap__::create_cached_data(void *handle, return napi_create_typedarray(env_, napi_uint8_array, bytes.size(), arraybuffer, 0, result_out); } -napi_status napi_module_wrap__::set_import_module_dynamically_callback(napi_value callback) +napi_status napi_module_wrap__::set_hooks(const unofficial_napi_module_hooks *hooks) { - JS_FreeValue(ctx_, import_module_dynamically_callback_); - import_module_dynamically_callback_ = - (!is_nullish(env_, callback) && JS_IsFunction(ctx_, napi_quickjs_value_inner(env_, callback))) - ? JS_DupValue(ctx_, napi_quickjs_value_inner(env_, callback)) + if (hooks == nullptr || hooks->size < sizeof(*hooks) || + hooks->version != UNOFFICIAL_NAPI_MODULE_HOOKS_VERSION) + return napi_util__::invalid_arg(env_); + + JSValue import_callback = + (!is_nullish(env_, hooks->import_module_dynamically) && + JS_IsFunction(ctx_, napi_quickjs_value_inner(env_, hooks->import_module_dynamically))) + ? JS_DupValue(ctx_, napi_quickjs_value_inner(env_, hooks->import_module_dynamically)) + : JS_UNDEFINED; + JSValue import_meta_callback = + (!is_nullish(env_, hooks->initialize_import_meta_object) && + JS_IsFunction(ctx_, napi_quickjs_value_inner(env_, hooks->initialize_import_meta_object))) + ? JS_DupValue(ctx_, napi_quickjs_value_inner(env_, hooks->initialize_import_meta_object)) : JS_UNDEFINED; - return napi_ok; -} -napi_status napi_module_wrap__::set_initialize_import_meta_object_callback(napi_value callback) -{ + JS_FreeValue(ctx_, import_module_dynamically_callback_); JS_FreeValue(ctx_, initialize_import_meta_callback_); - initialize_import_meta_callback_ = - (!is_nullish(env_, callback) && JS_IsFunction(ctx_, napi_quickjs_value_inner(env_, callback))) - ? JS_DupValue(ctx_, napi_quickjs_value_inner(env_, callback)) - : JS_UNDEFINED; + import_module_dynamically_callback_ = import_callback; + initialize_import_meta_callback_ = import_meta_callback; return napi_ok; } @@ -1010,10 +1006,10 @@ bool napi_module_wrap__::has_pending_provider_work() return !pending_dynamic_imports_.empty(); } -napi_status napi_module_wrap__::create_required_module_facade(void *handle, +napi_status napi_module_wrap__::create_required_module_facade(unofficial_napi_module module, napi_value *result_out) { - record *entry = find(handle); + record *entry = find(module); if (entry == nullptr || result_out == nullptr) return napi_util__::invalid_arg(env_); diff --git a/quickjs/src/internal/napi_module_wrap.h b/quickjs/src/internal/napi_module_wrap.h index b262886..81ebe5e 100644 --- a/quickjs/src/internal/napi_module_wrap.h +++ b/quickjs/src/internal/napi_module_wrap.h @@ -28,46 +28,54 @@ class napi_module_wrap__ int32_t line_offset, int32_t column_offset, napi_value host_defined_option_id, - void **handle_out); + unofficial_napi_module *module_out); napi_status create_synthetic(napi_value wrapper, napi_value url, napi_value context_or_undefined, napi_value export_names, napi_value synthetic_eval_steps, - void **handle_out); - napi_status destroy(void *handle); - napi_status get_module_requests(void *handle, napi_value *result_out); - napi_status link(void *handle, size_t count, void *const *linked_handles); - napi_status instantiate(void *handle); - napi_status evaluate(void *handle, + unofficial_napi_module *module_out); + napi_status destroy(unofficial_napi_module module); + napi_status get_creation_metadata(unofficial_napi_module module, + napi_value *requests_out, + bool *has_top_level_await_out); + napi_status link(unofficial_napi_module module, + size_t count, + const unofficial_napi_module *linked_modules); + napi_status instantiate(unofficial_napi_module module); + napi_status evaluate(unofficial_napi_module module, int64_t timeout, bool break_on_sigint, napi_value *result_out); - napi_status evaluate_sync(void *handle, + napi_status evaluate_sync(unofficial_napi_module module, napi_value filename, napi_value parent_filename, napi_value *result_out); - napi_status get_namespace(void *handle, napi_value *result_out); - napi_status get_status(void *handle, int32_t *status_out); - napi_status get_error(void *handle, napi_value *result_out); - napi_status has_top_level_await(void *handle, bool *result_out); - napi_status has_async_graph(void *handle, bool *result_out); - napi_status check_unsettled_top_level_await(napi_value module_wrap, + napi_status get_namespace(unofficial_napi_module module, + napi_value *result_out); + napi_status get_state(unofficial_napi_module module, + int32_t *status_out, + napi_value *error_out, + bool *has_async_graph_out); + napi_status check_unsettled_top_level_await(unofficial_napi_module module, bool warnings, bool *settled_out); - napi_status set_export(void *handle, + napi_status set_export(unofficial_napi_module module, napi_value export_name, napi_value export_value); - napi_status set_module_source_object(void *handle, napi_value source_object); - napi_status get_module_source_object(void *handle, napi_value *result_out); - napi_status create_cached_data(void *handle, napi_value *result_out); - napi_status set_import_module_dynamically_callback(napi_value callback); - napi_status set_initialize_import_meta_object_callback(napi_value callback); + napi_status set_module_source_object(unofficial_napi_module module, + napi_value source_object); + napi_status get_module_source_object(unofficial_napi_module module, + napi_value *result_out); + napi_status create_cached_data(unofficial_napi_module module, + napi_value *result_out); + napi_status set_hooks(const unofficial_napi_module_hooks *hooks); napi_status import_module_dynamically(size_t argc, napi_value const *argv, napi_value *result_out); bool has_pending_provider_work(); - napi_status create_required_module_facade(void *handle, napi_value *result_out); + napi_status create_required_module_facade(unofficial_napi_module module, + napi_value *result_out); void register_dynamic_import_referrer(napi_value referrer_name, napi_value host_defined_option_id); void unregister_dynamic_import_referrer(napi_value referrer_name, @@ -91,7 +99,7 @@ class napi_module_wrap__ struct record; struct script_referrer; - record *find(void *handle) const; + record *find(unofficial_napi_module module) const; record *find_by_module(JSModuleDef *module) const; record *find_by_wrapper(JSValueConst wrapper) const; void remove(record *entry); diff --git a/quickjs/src/internal/napi_util.cc b/quickjs/src/internal/napi_util.cc index 83be5f5..6f36f1e 100644 --- a/quickjs/src/internal/napi_util.cc +++ b/quickjs/src/internal/napi_util.cc @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -20,6 +21,43 @@ bool starts_with(std::string_view value, std::string_view prefix) return value.substr(0, prefix.size()) == prefix; } +int property_key_to_array_index(JSContext *ctx, JSValue key, uint32_t *index_out) +{ + if (JS_IsNumber(key)) + return JS_ToUint32(ctx, index_out, key) < 0 ? -1 : 1; + + if (!JS_IsString(key)) + return 0; + + size_t length = 0; + const char *text = JS_ToCStringLen(ctx, &length, key); + if (text == nullptr) + return -1; + + bool is_index = length != 0 && length <= 10 && + (length == 1 || text[0] != '0'); + uint64_t index = 0; + for (size_t i = 0; is_index && i < length; ++i) + { + const char ch = text[i]; + if (ch < '0' || ch > '9') + { + is_index = false; + break; + } + index = index * 10 + static_cast(ch - '0'); + } + JS_FreeCString(ctx, text); + + constexpr uint64_t kMaxArrayIndex = + static_cast(std::numeric_limits::max()) - 1; + if (!is_index || index > kMaxArrayIndex) + return 0; + + *index_out = static_cast(index); + return 1; +} + int hex_digit_value(char ch) { if (ch >= '0' && ch <= '9') @@ -613,6 +651,22 @@ napi_status napi_util__::get_property_names(napi_env env, else { key = JS_AtomToValue(ctx, t[i].atom); + if (!JS_IsException(key)) + { + uint32_t array_index = 0; + const int is_array_index = property_key_to_array_index(ctx, key, &array_index); + if (is_array_index < 0) + { + JS_FreeValue(ctx, key); + JS_FreePropertyEnum(ctx, t, count); + return return_pending_if_caught(env, "Failed to convert property name"); + } + if (is_array_index != 0 && !JS_IsNumber(key)) + { + JS_FreeValue(ctx, key); + key = JS_NewUint32(ctx, array_index); + } + } } if (JS_IsException(key)) { diff --git a/quickjs/src/unofficial_napi.cc b/quickjs/src/unofficial_napi.cc index 8f0615a..5e24c37 100644 --- a/quickjs/src/unofficial_napi.cc +++ b/quickjs/src/unofficial_napi.cc @@ -16,14 +16,25 @@ #include #include #include +#include #include #include #include using namespace quickjs::detail; +// QuickJS does not allocate ArrayBuffer backing stores from the guest heap, +// but create_env still owns a supplied context according to the common +// provider contract. The weak fallback keeps native embedders linkable. +extern "C" __attribute__((weak)) void napi_host_guest_heap_release( + void * /*ctx*/) {} + namespace { + std::mutex g_runtime_config_mutex; + bool g_runtime_configured = false; + std::string g_runtime_engine_flags; + struct UnofficialEnvScope { JSRuntime *rt = nullptr; @@ -43,12 +54,12 @@ namespace return napi_ok; } - napi_status ReleaseEnvScope(void *scope_ptr) + napi_status ReleaseEnvScope(unofficial_napi_env_owner owner) { - if (scope_ptr == nullptr) + if (owner == nullptr) return napi_invalid_arg; - auto *scope = static_cast(scope_ptr); + auto *scope = reinterpret_cast(owner); napi_status status = napi_ok; napi_env env_to_delete = nullptr; if (scope->env != nullptr) @@ -130,6 +141,31 @@ namespace extern "C" { + napi_status NAPI_CDECL unofficial_napi_configure_runtime( + const unofficial_napi_runtime_options *options) + { + if (options != nullptr && + (options->size < sizeof(unofficial_napi_runtime_options) || + options->version != UNOFFICIAL_NAPI_RUNTIME_OPTIONS_VERSION || + (options->engine_flags_length > 0 && options->engine_flags == nullptr))) + return napi_invalid_arg; + + const char *flags = options != nullptr && options->engine_flags_length > 0 + ? options->engine_flags + : ""; + const size_t flags_length = options != nullptr ? options->engine_flags_length : 0; + std::lock_guard lock(g_runtime_config_mutex); + if (g_runtime_configured) + return g_runtime_engine_flags.size() == flags_length && + (flags_length == 0 || + std::memcmp(g_runtime_engine_flags.data(), flags, flags_length) == 0) + ? napi_ok + : napi_invalid_arg; + g_runtime_engine_flags.assign(flags, flags_length); + g_runtime_configured = true; + return napi_ok; + } + napi_status NAPI_CDECL unofficial_napi_create_guest_backed_typedarray( napi_env env, napi_typedarray_type type, size_t length, void **data, napi_value *result) { @@ -193,21 +229,43 @@ extern "C" return napi_ok; } - napi_status NAPI_CDECL unofficial_napi_create_env(int32_t module_api_version, - napi_env *env_out, - void **scope_out) - { - return unofficial_napi_create_env_with_options(module_api_version, nullptr, env_out, scope_out); - } - - napi_status NAPI_CDECL unofficial_napi_create_env_with_options( + napi_status NAPI_CDECL unofficial_napi_create_env( int32_t module_api_version, const unofficial_napi_env_create_options *options, napi_env *env_out, - void **scope_out) + unofficial_napi_env_owner *owner_out) { - if (env_out == nullptr || scope_out == nullptr) + if (options != nullptr && + (options->size < sizeof(unofficial_napi_env_create_options) || + options->version != UNOFFICIAL_NAPI_ENV_CREATE_OPTIONS_VERSION)) + { + // A truncated descriptor cannot transfer ownership of a field it + // does not contain. + return napi_invalid_arg; + } + + unofficial_napi_guest_heap guest_heap = + options != nullptr ? options->guest_heap : nullptr; + if (env_out == nullptr || owner_out == nullptr) + { + if (guest_heap != nullptr) + napi_host_guest_heap_release(guest_heap); return napi_invalid_arg; + } + { + std::lock_guard lock(g_runtime_config_mutex); + if (!g_runtime_configured) + { + if (guest_heap != nullptr) + napi_host_guest_heap_release(guest_heap); + return napi_invalid_arg; + } + } + + // QuickJS does not allocate backing stores from the guest heap, so it + // consumes the transferred context immediately. + if (guest_heap != nullptr) + napi_host_guest_heap_release(guest_heap); auto rt = JS_NewRuntime(); if (rt == nullptr) @@ -244,79 +302,38 @@ extern "C" return (status == napi_ok) ? napi_generic_failure : status; } - *scope_out = reinterpret_cast(scope); + *owner_out = reinterpret_cast(scope); *env_out = scope->env; return napi_ok; } - napi_status NAPI_CDECL unofficial_napi_set_embedder_hooks( - const unofficial_napi_embedder_hooks *hooks) - { - (void)hooks; - return napi_ok; - } - - napi_status NAPI_CDECL unofficial_napi_set_edge_environment(napi_env env, void *environment) - { - (void)environment; - if (!napi_util__::check_env(env)) - return napi_invalid_arg; - return napi_ok; - } - - napi_status NAPI_CDECL unofficial_napi_set_env_cleanup_callback( + napi_status NAPI_CDECL unofficial_napi_attach_env( napi_env env, - unofficial_napi_env_cleanup_callback callback, - void *data) + const unofficial_napi_env_hooks *hooks, + uint64_t *accepted_hooks_out) { - (void)callback; - (void)data; - if (!napi_util__::check_env(env)) + if (!napi_util__::check_env(env) || hooks == nullptr || accepted_hooks_out == nullptr || + hooks->size < sizeof(unofficial_napi_env_hooks) || + hooks->version != UNOFFICIAL_NAPI_ENV_HOOKS_VERSION || + !env->attach_embedder_hooks(*hooks)) return napi_invalid_arg; + // QuickJS currently drives none of the remaining provider-owned hook + // classes. Attaching the transaction is valid, but no capability is + // advertised until the corresponding engine callback exists. + *accepted_hooks_out = 0; return napi_ok; } - napi_status NAPI_CDECL unofficial_napi_set_env_destroy_callback( - napi_env env, - unofficial_napi_env_destroy_callback callback, - void *data) - { - (void)callback; - (void)data; - if (!napi_util__::check_env(env)) - return napi_invalid_arg; - return napi_ok; - } - - napi_status NAPI_CDECL unofficial_napi_set_context_token_callbacks( - napi_env env, - unofficial_napi_context_token_callback assign_callback, - unofficial_napi_context_token_callback unassign_callback, - void *data) - { - (void)assign_callback; - (void)unassign_callback; - (void)data; - if (!napi_util__::check_env(env)) - return napi_invalid_arg; - return napi_ok; - } - - napi_status NAPI_CDECL unofficial_napi_release_env(void *scope) - { - return ReleaseEnvScope(scope); - } - - napi_status NAPI_CDECL unofficial_napi_release_env_with_loop( - void *scope, + napi_status NAPI_CDECL unofficial_napi_release_env( + unofficial_napi_env_owner owner, struct uv_loop_s *loop) { (void)loop; - return ReleaseEnvScope(scope); + return ReleaseEnvScope(owner); } - napi_status NAPI_CDECL unofficial_napi_low_memory_notification(napi_env env) + napi_status NAPI_CDECL unofficial_napi_collect_garbage(napi_env env) { if (!napi_util__::check_env(env)) return napi_invalid_arg; @@ -324,15 +341,6 @@ extern "C" return napi_ok; } - napi_status NAPI_CDECL unofficial_napi_set_flags_from_string( - const char *flags, - size_t length) - { - (void)flags; - (void)length; - return napi_ok; - } - napi_status NAPI_CDECL unofficial_napi_set_prepare_stack_trace_callback( napi_env env, napi_value callback) @@ -343,11 +351,6 @@ extern "C" return napi_ok; } - napi_status NAPI_CDECL unofficial_napi_request_gc_for_testing(napi_env env) - { - return unofficial_napi_low_memory_notification(env); - } - napi_status NAPI_CDECL unofficial_napi_event_loop_checkpoint( napi_env env, unofficial_napi_event_loop_checkpoint_mode mode, @@ -417,18 +420,6 @@ extern "C" return napi_ok; } - napi_status NAPI_CDECL unofficial_napi_set_enqueue_foreground_task_callback( - napi_env env, - unofficial_napi_enqueue_foreground_task_callback callback, - void *target) - { - (void)callback; - (void)target; - if (!napi_util__::check_env(env)) - return napi_invalid_arg; - return napi_ok; - } - napi_status NAPI_CDECL unofficial_napi_enqueue_microtask(napi_env env, napi_value callback) { if (!napi_util__::check_env(env) || !napi_util__::is_callable(env, callback)) @@ -467,45 +458,19 @@ extern "C" return napi_ok; } - napi_status NAPI_CDECL unofficial_napi_set_fatal_error_callbacks( + napi_status NAPI_CDECL unofficial_napi_configure_near_heap_limit_callback( napi_env env, - unofficial_napi_fatal_error_callback fatal_callback, - unofficial_napi_oom_error_callback oom_callback) + unofficial_napi_near_heap_limit_callback callback_or_null, + void *data, + size_t restored_heap_limit) { - (void)fatal_callback; - (void)oom_callback; - if (!napi_util__::check_env(env)) + if (!napi_util__::check_env(env) || + (callback_or_null != nullptr && restored_heap_limit != 0) || + (callback_or_null == nullptr && data != nullptr)) return napi_invalid_arg; - return napi_ok; - } - - napi_status NAPI_CDECL unofficial_napi_set_near_heap_limit_callback( - napi_env env, - unofficial_napi_near_heap_limit_callback callback, - void *data) - { - (void)callback; + (void)callback_or_null; (void)data; - if (!napi_util__::check_env(env)) - return napi_invalid_arg; - return napi_ok; - } - - napi_status NAPI_CDECL unofficial_napi_remove_near_heap_limit_callback( - napi_env env, - size_t heap_limit) - { - (void)heap_limit; - if (!napi_util__::check_env(env)) - return napi_invalid_arg; - return napi_ok; - } - - napi_status NAPI_CDECL unofficial_napi_set_stack_limit(napi_env env, void *stack_limit) - { - (void)stack_limit; - if (!napi_util__::check_env(env) || stack_limit == nullptr) - return napi_invalid_arg; + (void)restored_heap_limit; return napi_ok; } @@ -532,12 +497,13 @@ extern "C" return napi_ok; } - napi_status NAPI_CDECL unofficial_napi_get_error_source_positions( + napi_status NAPI_CDECL unofficial_napi_get_error_metadata( napi_env env, napi_value error, - unofficial_napi_error_source_positions *out) + unofficial_napi_error_metadata_mode mode, + unofficial_napi_error_metadata *out) { - return napi_util__::check_env(env) ? env->contextify().get_error_source_positions(error, out) : napi_invalid_arg; + return napi_util__::check_env(env) ? env->contextify().get_error_metadata(error, mode, out) : napi_invalid_arg; } napi_status NAPI_CDECL unofficial_napi_preserve_error_source_message( @@ -547,43 +513,12 @@ extern "C" return napi_util__::check_env(env) ? env->contextify().preserve_error_source_message(error) : napi_invalid_arg; } - napi_status NAPI_CDECL unofficial_napi_set_source_maps_enabled( - napi_env env, - bool enabled) - { - return napi_util__::check_env(env) ? env->contextify().set_source_maps_enabled(enabled) : napi_invalid_arg; - } - - napi_status NAPI_CDECL unofficial_napi_set_get_source_map_error_source_callback( + napi_status NAPI_CDECL unofficial_napi_configure_source_maps( napi_env env, + bool enabled, napi_value callback) { - return napi_util__::check_env(env) ? env->contextify().set_get_source_map_error_source_callback(callback) : napi_invalid_arg; - } - - napi_status NAPI_CDECL unofficial_napi_get_error_source_line_for_stderr( - napi_env env, - napi_value error, - napi_value *result_out) - { - return napi_util__::check_env(env) ? env->contextify().get_error_source_line_for_stderr(error, result_out) : napi_invalid_arg; - } - - napi_status NAPI_CDECL unofficial_napi_get_error_thrown_at( - napi_env env, - napi_value error, - napi_value *result_out) - { - return napi_util__::check_env(env) ? env->contextify().get_error_thrown_at(error, result_out) : napi_invalid_arg; - } - - napi_status NAPI_CDECL unofficial_napi_take_preserved_error_formatting( - napi_env env, - napi_value error, - napi_value *source_line_out, - napi_value *thrown_at_out) - { - return napi_util__::check_env(env) ? env->contextify().take_preserved_error_formatting(error, source_line_out, thrown_at_out) : napi_invalid_arg; + return napi_util__::check_env(env) ? env->contextify().configure_source_maps(enabled, callback) : napi_invalid_arg; } napi_status NAPI_CDECL unofficial_napi_mark_promise_as_handled( @@ -665,19 +600,6 @@ extern "C" return napi_callsite__::get_call_sites(env, frames, callsites_out); } - napi_status NAPI_CDECL unofficial_napi_get_current_stack_trace(napi_env env, - uint32_t frames, - napi_value *callsites_out) - { - return napi_callsite__::get_current_stack_trace(env, frames, callsites_out); - } - - napi_status NAPI_CDECL unofficial_napi_get_caller_location(napi_env env, - napi_value *location_out) - { - return napi_callsite__::get_caller_location(env, location_out); - } - napi_status NAPI_CDECL unofficial_napi_arraybuffer_view_has_buffer(napi_env env, napi_value value, bool *result_out) @@ -709,106 +631,6 @@ extern "C" return napi_util__::wrap_owned(env, name, name_out); } - napi_status NAPI_CDECL unofficial_napi_get_own_non_index_properties( - napi_env env, - napi_value value, - uint32_t filter_bits, - napi_value *result_out) - { - if (!napi_util__::check_env(env) || value == nullptr || result_out == nullptr) - return napi_invalid_arg; - JSContext *ctx = napi_util__::context(env); - JSValue obj = napi_quickjs_value_inner(env, value); - if (!JS_IsObject(obj)) - return napi_object_expected; - - auto is_array_index_atom = [&](JSAtom atom) -> bool - { - size_t len = 0; - const char *name = JS_AtomToCStringLen(ctx, &len, atom); - if (name == nullptr) - return false; - bool is_index = false; - if (len > 0 && len <= 10) - { - uint64_t index = 0; - is_index = true; - for (size_t i = 0; i < len; ++i) - { - if (name[i] < '0' || name[i] > '9') - { - is_index = false; - break; - } - if (i == 0 && len > 1 && name[i] == '0') - { - is_index = false; - break; - } - index = index * 10 + static_cast(name[i] - '0'); - } - if (is_index && index > 4294967294ULL) - is_index = false; - } - JS_FreeCString(ctx, name); - return is_index; - }; - - int gpn_flags = napi_util__::key_filter_to_gpn(static_cast(filter_bits)); - JSPropertyEnum *props = nullptr; - uint32_t prop_count = 0; - if (JS_GetOwnPropertyNames(ctx, &props, &prop_count, obj, gpn_flags) < 0) - return napi_util__::return_pending_if_caught(env, "Exception while getting property names"); - - JSValue out = JS_NewArray(ctx); - uint32_t out_idx = 0; - - for (uint32_t i = 0; i < prop_count; ++i) - { - if (is_array_index_atom(props[i].atom)) - continue; - - if ((filter_bits & (napi_key_writable | napi_key_configurable)) != 0) - { - JSPropertyDescriptor desc; - int has = JS_GetOwnProperty(ctx, &desc, obj, props[i].atom); - if (has < 0) - { - JS_FreePropertyEnum(ctx, props, prop_count); - JS_FreeValue(ctx, out); - return napi_util__::return_pending_if_caught(env, "Exception while filtering property names"); - } - if (has == 0) - continue; - - bool include = true; - if ((filter_bits & napi_key_writable) != 0 && (desc.flags & JS_PROP_WRITABLE) == 0) - include = false; - if ((filter_bits & napi_key_configurable) != 0 && (desc.flags & JS_PROP_CONFIGURABLE) == 0) - include = false; - - JS_FreeValue(ctx, desc.value); - JS_FreeValue(ctx, desc.getter); - JS_FreeValue(ctx, desc.setter); - - if (!include) - continue; - } - - JSValue key = JS_AtomToValue(ctx, props[i].atom); - if (JS_IsException(key)) - { - JS_FreePropertyEnum(ctx, props, prop_count); - JS_FreeValue(ctx, out); - return napi_util__::return_pending_if_caught(env, "Failed to convert property name"); - } - JS_SetPropertyUint32(ctx, out, out_idx++, key); - } - - JS_FreePropertyEnum(ctx, props, prop_count); - return napi_util__::wrap_owned(env, out, result_out); - } - napi_status NAPI_CDECL unofficial_napi_create_private_symbol(napi_env env, const char *utf8description, size_t length, @@ -876,14 +698,6 @@ extern "C" } napi_status NAPI_CDECL unofficial_napi_structured_clone( - napi_env env, - napi_value value, - napi_value *result_out) - { - return QuickJSStructuredClone(env, value, nullptr, result_out); - } - - napi_status NAPI_CDECL unofficial_napi_structured_clone_with_transfer( napi_env env, napi_value value, napi_value transfer_list_or_null, @@ -892,45 +706,143 @@ extern "C" return QuickJSStructuredClone(env, value, transfer_list_or_null, result_out); } - napi_status NAPI_CDECL unofficial_napi_serialize_value( + napi_status NAPI_CDECL unofficial_napi_message_create( napi_env env, napi_value value, - void **payload_out) + unofficial_napi_message *message_out) { - return napi_serdes__::serialize_value(env, value, payload_out); + return napi_serdes__::serialize_value( + env, value, reinterpret_cast(message_out)); } - napi_status NAPI_CDECL unofficial_napi_deserialize_value( + napi_status NAPI_CDECL unofficial_napi_message_take( napi_env env, - void *payload, + unofficial_napi_message message, napi_value *result_out) { - return napi_serdes__::deserialize_value(env, payload, result_out); + if (message == nullptr) + return napi_invalid_arg; + struct MessageGuard + { + void *payload; + ~MessageGuard() { napi_serdes__::release_serialized_value(payload); } + } guard{reinterpret_cast(message)}; + return napi_serdes__::deserialize_value(env, guard.payload, result_out); } - void NAPI_CDECL unofficial_napi_release_serialized_value(void *payload) + void NAPI_CDECL unofficial_napi_message_drop(unofficial_napi_message message) { - napi_serdes__::release_serialized_value(payload); + napi_serdes__::release_serialized_value(reinterpret_cast(message)); } - napi_status NAPI_CDECL unofficial_napi_get_process_memory_info( + napi_status NAPI_CDECL unofficial_napi_get_own_non_index_properties( napi_env env, - double *heap_total_out, - double *heap_used_out, - double *external_out, - double *array_buffers_out) + napi_value value, + uint32_t filter_bits, + napi_value *result_out) { - if (!napi_util__::check_env(env) || heap_total_out == nullptr || heap_used_out == nullptr || - external_out == nullptr || array_buffers_out == nullptr) + if (!napi_util__::check_env(env) || value == nullptr || result_out == nullptr) return napi_invalid_arg; + JSContext *ctx = napi_util__::context(env); + JSValue obj = napi_quickjs_value_inner(env, value); + if (!JS_IsObject(obj)) + return napi_object_expected; - JSMemoryUsage usage{}; - JS_ComputeMemoryUsage(napi_util__::runtime(env), &usage); - *heap_total_out = static_cast(usage.malloc_size); - *heap_used_out = static_cast(usage.memory_used_size); - *external_out = static_cast(usage.binary_object_size); - *array_buffers_out = static_cast(usage.binary_object_size); - return napi_ok; + auto string_is_array_index = [&](JSValue key) -> bool + { + size_t len = 0; + const char *name = JS_ToCStringLen(ctx, &len, key); + if (name == nullptr) + return false; + bool is_index = len > 0 && len <= 10; + uint64_t index = 0; + for (size_t i = 0; is_index && i < len; ++i) + { + if (name[i] < '0' || name[i] > '9' || + (i == 0 && len > 1 && name[i] == '0')) + { + is_index = false; + break; + } + index = index * 10 + static_cast(name[i] - '0'); + } + JS_FreeCString(ctx, name); + return is_index && index <= 4294967294ULL; + }; + + const int gpn_flags = napi_util__::key_filter_to_gpn( + static_cast(filter_bits)); + JSPropertyEnum *props = nullptr; + uint32_t prop_count = 0; + if (JS_GetOwnPropertyNames(ctx, &props, &prop_count, obj, gpn_flags) < 0) + return napi_util__::return_pending_if_caught( + env, "Exception while getting property names"); + + JSValue out = JS_NewArray(ctx); + uint32_t out_idx = 0; + for (uint32_t i = 0; i < prop_count; ++i) + { + JSValue key = JS_AtomToValue(ctx, props[i].atom); + if (JS_IsException(key)) + { + JS_FreePropertyEnum(ctx, props, prop_count); + JS_FreeValue(ctx, out); + return napi_util__::return_pending_if_caught( + env, "Failed to convert property name"); + } + + // Dense integer property atoms become numbers, avoiding a CString + // allocation for every element. Only string atoms that could be + // larger uint32 indices need parsing. + const bool is_index = JS_IsNumber(key) || + (JS_IsString(key) && string_is_array_index(key)); + if (is_index) + { + JS_FreeValue(ctx, key); + continue; + } + + if ((filter_bits & (napi_key_writable | napi_key_configurable)) != 0) + { + JSPropertyDescriptor desc; + int has = JS_GetOwnProperty(ctx, &desc, obj, props[i].atom); + if (has < 0) + { + JS_FreeValue(ctx, key); + JS_FreePropertyEnum(ctx, props, prop_count); + JS_FreeValue(ctx, out); + return napi_util__::return_pending_if_caught( + env, "Exception while filtering property names"); + } + if (has == 0) + { + JS_FreeValue(ctx, key); + continue; + } + + bool include = true; + if ((filter_bits & napi_key_writable) != 0 && + (desc.flags & JS_PROP_WRITABLE) == 0) + include = false; + if ((filter_bits & napi_key_configurable) != 0 && + (desc.flags & JS_PROP_CONFIGURABLE) == 0) + include = false; + + JS_FreeValue(ctx, desc.value); + JS_FreeValue(ctx, desc.getter); + JS_FreeValue(ctx, desc.setter); + if (!include) + { + JS_FreeValue(ctx, key); + continue; + } + } + + JS_SetPropertyUint32(ctx, out, out_idx++, key); + } + + JS_FreePropertyEnum(ctx, props, prop_count); + return napi_util__::wrap_owned(env, out, result_out); } napi_status NAPI_CDECL unofficial_napi_get_hash_seed(napi_env env, @@ -946,16 +858,25 @@ extern "C" napi_env env, unofficial_napi_heap_statistics *stats_out) { - if (!napi_util__::check_env(env) || stats_out == nullptr) + if (!napi_util__::check_env(env) || stats_out == nullptr || + stats_out->size < sizeof(*stats_out) || + stats_out->version != UNOFFICIAL_NAPI_HEAP_STATISTICS_VERSION) return napi_invalid_arg; + const uint32_t output_size = stats_out->size; std::memset(stats_out, 0, sizeof(*stats_out)); + stats_out->size = output_size; + stats_out->version = UNOFFICIAL_NAPI_HEAP_STATISTICS_VERSION; JSMemoryUsage usage{}; JS_ComputeMemoryUsage(napi_util__::runtime(env), &usage); stats_out->total_heap_size = static_cast(std::max(0, usage.malloc_size)); stats_out->used_heap_size = static_cast(std::max(0, usage.memory_used_size)); stats_out->malloced_memory = static_cast(std::max(0, usage.malloc_size)); + // QuickJS does not retain a historical allocator peak, but its current + // malloc size is still an honest lower bound and preserves the value + // exposed by the pre-consolidation process-memory operation. stats_out->peak_malloced_memory = stats_out->malloced_memory; stats_out->external_memory = static_cast(std::max(0, usage.binary_object_size)); + stats_out->array_buffer_memory = stats_out->external_memory; // heap_size_limit must never be reported as zero. Consumers read it as // "the ceiling this heap can grow to" and divide by it: Next.js uses @@ -975,28 +896,30 @@ extern "C" stats_out->heap_size_limit > stats_out->used_heap_size ? stats_out->heap_size_limit - stats_out->used_heap_size : 0; + stats_out->valid_fields = + unofficial_napi_heap_stat_total_heap_size | + unofficial_napi_heap_stat_total_available_size | + unofficial_napi_heap_stat_used_heap_size | + unofficial_napi_heap_stat_heap_size_limit | + unofficial_napi_heap_stat_malloced_memory | + unofficial_napi_heap_stat_peak_malloced_memory | + unofficial_napi_heap_stat_external_memory | + unofficial_napi_heap_stat_array_buffer_memory; return napi_ok; } - napi_status NAPI_CDECL unofficial_napi_get_heap_space_count( + napi_status NAPI_CDECL unofficial_napi_get_heap_space_statistics( napi_env env, + unofficial_napi_heap_space_statistics *stats_out, + uint32_t capacity, uint32_t *count_out) { - if (!napi_util__::check_env(env) || count_out == nullptr) + if (!napi_util__::check_env(env) || count_out == nullptr || + (capacity > 0 && stats_out == nullptr)) return napi_invalid_arg; *count_out = 1; - return napi_ok; - } - - napi_status NAPI_CDECL unofficial_napi_get_heap_space_statistics( - napi_env env, - uint32_t space_index, - unofficial_napi_heap_space_statistics *stats_out) - { - if (!napi_util__::check_env(env) || stats_out == nullptr) - return napi_invalid_arg; - if (space_index != 0) - return napi_invalid_arg; + if (capacity == 0) + return napi_ok; std::memset(stats_out, 0, sizeof(*stats_out)); std::strncpy(stats_out->space_name, "quickjs", sizeof(stats_out->space_name) - 1); JSMemoryUsage usage{}; @@ -1021,77 +944,44 @@ extern "C" return napi_ok; } - napi_status NAPI_CDECL unofficial_napi_start_cpu_profile( + napi_status NAPI_CDECL unofficial_napi_profile_start( napi_env env, - unofficial_napi_cpu_profile_start_result *result_out, - uint32_t *profile_id_out) + unofficial_napi_profile_kind kind, + unofficial_napi_profile_start_result *result_out, + unofficial_napi_profile *profile_out) { - if (!napi_util__::check_env(env) || result_out == nullptr || profile_id_out == nullptr) + if (!napi_util__::check_env(env) || result_out == nullptr || profile_out == nullptr || + (kind != unofficial_napi_profile_cpu && kind != unofficial_napi_profile_heap)) return napi_invalid_arg; - *result_out = unofficial_napi_cpu_profile_start_ok; - *profile_id_out = 1; + // Unsupported providers leave outputs untouched on failure. Out + // parameters describe only successful calls; they are not a second, + // contradictory status channel. return napi_generic_failure; } - napi_status NAPI_CDECL unofficial_napi_stop_cpu_profile( + napi_status NAPI_CDECL unofficial_napi_profile_stop( napi_env env, - uint32_t profile_id, - bool *found_out, - char **json_out, - size_t *json_len_out) + unofficial_napi_profile profile, + napi_value *json_out) { - (void)profile_id; - if (!napi_util__::check_env(env) || found_out == nullptr || json_out == nullptr || json_len_out == nullptr) + if (!napi_util__::check_env(env) || profile == nullptr || json_out == nullptr) return napi_invalid_arg; - *found_out = false; *json_out = nullptr; - *json_len_out = 0; - return napi_ok; - } - - napi_status NAPI_CDECL unofficial_napi_start_heap_profile( - napi_env env, - bool *started_out) - { - if (!napi_util__::check_env(env) || started_out == nullptr) - return napi_invalid_arg; - *started_out = false; return napi_generic_failure; } - napi_status NAPI_CDECL unofficial_napi_stop_heap_profile( - napi_env env, - bool *found_out, - char **json_out, - size_t *json_len_out) - { - if (!napi_util__::check_env(env) || found_out == nullptr || json_out == nullptr || json_len_out == nullptr) - return napi_invalid_arg; - *found_out = false; - *json_out = nullptr; - *json_len_out = 0; - return napi_ok; - } - napi_status NAPI_CDECL unofficial_napi_take_heap_snapshot( napi_env env, const unofficial_napi_heap_snapshot_options *options, - char **json_out, - size_t *json_len_out) + napi_value *json_out) { (void)options; - if (!napi_util__::check_env(env) || json_out == nullptr || json_len_out == nullptr) + if (!napi_util__::check_env(env) || json_out == nullptr) return napi_invalid_arg; *json_out = nullptr; - *json_len_out = 0; return napi_generic_failure; } - void NAPI_CDECL unofficial_napi_free_buffer(void *data) - { - std::free(data); - } - napi_status NAPI_CDECL unofficial_napi_get_continuation_preserved_embedder_data( napi_env env, napi_value *result_out) @@ -1236,76 +1126,78 @@ extern "C" : napi_invalid_arg; } - napi_status NAPI_CDECL unofficial_napi_bytecode_compile( + napi_status NAPI_CDECL unofficial_napi_bytecode_open( napi_env env, - napi_value source_text, - napi_value filename, - int32_t shape, - napi_value params_or_undefined, - napi_value host_defined_option_id, - int32_t line_offset, - int32_t column_offset, - void **bytecode_out, - bool *can_parse_as_module_out) + const unofficial_napi_bytecode_open_options *options, + unofficial_napi_bytecode_open_result *result) { - return napi_util__::check_env(env) ? env->contextify().bytecode_compile(source_text, - filename, - shape, - params_or_undefined, - host_defined_option_id, - line_offset, - column_offset, - bytecode_out, - can_parse_as_module_out) - : napi_invalid_arg; - } + if (!napi_util__::check_env(env) || options == nullptr || result == nullptr || + options->size < sizeof(*options) || + options->version != UNOFFICIAL_NAPI_BYTECODE_OPEN_OPTIONS_VERSION || + options->source_text == nullptr || options->filename == nullptr || + (options->cache_policy != unofficial_napi_bytecode_cache_compile_on_reject && + options->cache_policy != unofficial_napi_bytecode_cache_validate_only)) + return napi_invalid_arg; - napi_status NAPI_CDECL unofficial_napi_bytecode_deserialize( - napi_env env, - const uint8_t *bytes, - size_t byte_length, - napi_value source_text, - napi_value filename, - int32_t shape, - napi_value params_or_undefined, - napi_value host_defined_option_id, - void **bytecode_out, - bool *rejected_out) - { - return napi_util__::check_env(env) ? env->contextify().bytecode_deserialize(bytes, - byte_length, - source_text, - filename, - shape, - params_or_undefined, - host_defined_option_id, - bytecode_out, - rejected_out) - : napi_invalid_arg; + *result = {}; + if (options->has_cache != 0 && options->cache_bytes != nullptr && + options->cache_byte_length != 0) + { + bool rejected = false; + napi_status status = env->contextify().bytecode_deserialize(options->cache_bytes, + options->cache_byte_length, + options->source_text, + options->filename, + options->shape, + options->params_or_undefined, + options->host_defined_option_id, + &result->bytecode, + &rejected); + result->cache_rejected = rejected ? 1 : 0; + if (status != napi_ok) + return status; + if (result->bytecode != nullptr) + return napi_ok; + result->cache_rejected = 1; + } + else if (options->has_cache != 0) + { + result->cache_rejected = 1; + } + + if (result->cache_rejected != 0 && + options->cache_policy == unofficial_napi_bytecode_cache_validate_only) + return napi_ok; + bool can_parse_as_module = false; + napi_status status = env->contextify().bytecode_compile(options->source_text, + options->filename, + options->shape, + options->params_or_undefined, + options->host_defined_option_id, + options->line_offset, + options->column_offset, + &result->bytecode, + &can_parse_as_module); + result->can_parse_as_module = can_parse_as_module ? 1 : 0; + return status; } napi_status NAPI_CDECL unofficial_napi_bytecode_serialize( napi_env env, - void *bytecode, + unofficial_napi_bytecode bytecode, napi_value *buffer_out) { return napi_util__::check_env(env) ? env->contextify().bytecode_serialize(bytecode, buffer_out) : napi_invalid_arg; } - napi_status NAPI_CDECL unofficial_napi_bytecode_release(napi_env env, void *bytecode) + napi_status NAPI_CDECL unofficial_napi_bytecode_release( + napi_env env, unofficial_napi_bytecode bytecode) { return napi_util__::check_env(env) ? env->contextify().bytecode_release(bytecode) : napi_invalid_arg; } - napi_status NAPI_CDECL unofficial_napi_contextify_dispose_context( - napi_env env, - napi_value sandbox_or_context_global) - { - return napi_util__::check_env(env) ? env->contextify().dispose_context(sandbox_or_context_global) : napi_invalid_arg; - } - napi_status NAPI_CDECL unofficial_napi_contextify_compile_function( napi_env env, const unofficial_napi_js_source *source, @@ -1346,266 +1238,218 @@ extern "C" : napi_invalid_arg; } - napi_status NAPI_CDECL unofficial_napi_module_wrap_create_source_text( + napi_status NAPI_CDECL unofficial_napi_module_wrap_create( napi_env env, - napi_value wrapper, - napi_value url, - napi_value context_or_undefined, - const unofficial_napi_js_source *source, - int32_t line_offset, - int32_t column_offset, - napi_value host_defined_option_id, - void **handle_out) + const unofficial_napi_module_create_options *options, + unofficial_napi_module_create_result *result_out) { - if (!napi_util__::check_env(env) || handle_out == nullptr) + if (!napi_util__::check_env(env) || options == nullptr || + options->size < sizeof(*options) || + options->version != UNOFFICIAL_NAPI_MODULE_CREATE_OPTIONS_VERSION || + result_out == nullptr) return napi_invalid_arg; - return env->module_wrap().create_source_text(wrapper, - url, - context_or_undefined, - source, - line_offset, - column_offset, - host_defined_option_id, - handle_out); - } - - napi_status NAPI_CDECL unofficial_napi_module_wrap_create_synthetic( - napi_env env, - napi_value wrapper, - napi_value url, - napi_value context_or_undefined, - napi_value export_names, - napi_value synthetic_eval_steps, - void **handle_out) - { - if (!napi_util__::check_env(env) || handle_out == nullptr) - return napi_invalid_arg; - return env->module_wrap().create_synthetic(wrapper, - url, - context_or_undefined, - export_names, - synthetic_eval_steps, - handle_out); - } - - napi_status NAPI_CDECL unofficial_napi_module_wrap_destroy(napi_env env, void *handle) - { - if (!napi_util__::check_env(env)) + *result_out = {}; + napi_status status = napi_invalid_arg; + switch (options->kind) + { + case unofficial_napi_module_source_text: + status = env->module_wrap().create_source_text( + options->wrapper, + options->url, + options->context_or_undefined, + options->payload.source_text.source, + options->payload.source_text.line_offset, + options->payload.source_text.column_offset, + options->payload.source_text.host_defined_option_id, + &result_out->module); + break; + case unofficial_napi_module_synthetic: + status = env->module_wrap().create_synthetic( + options->wrapper, + options->url, + options->context_or_undefined, + options->payload.synthetic.export_names, + options->payload.synthetic.synthetic_evaluation_steps, + &result_out->module); + break; + default: return napi_invalid_arg; - return env->module_wrap().destroy(handle); + } + if (status != napi_ok) + return status; + status = env->module_wrap().get_creation_metadata( + result_out->module, + &result_out->module_requests, + &result_out->has_top_level_await); + if (status != napi_ok) + { + env->module_wrap().destroy(result_out->module); + *result_out = {}; + } + return status; } - napi_status NAPI_CDECL unofficial_napi_module_wrap_get_module_requests( + napi_status NAPI_CDECL unofficial_napi_module_wrap_destroy( napi_env env, - void *handle, - napi_value *result_out) + unofficial_napi_module module) { - if (!napi_util__::check_env(env) || result_out == nullptr) + if (!napi_util__::check_env(env)) return napi_invalid_arg; - return env->module_wrap().get_module_requests(handle, result_out); + return env->module_wrap().destroy(module); } napi_status NAPI_CDECL unofficial_napi_module_wrap_link( napi_env env, - void *handle, + unofficial_napi_module module, size_t count, - void *const *linked_handles) + const unofficial_napi_module *linked_modules) { - if (!napi_util__::check_env(env) || handle == nullptr) + if (!napi_util__::check_env(env) || module == nullptr) return napi_invalid_arg; - return env->module_wrap().link(handle, count, linked_handles); + return env->module_wrap().link(module, count, linked_modules); } - napi_status NAPI_CDECL unofficial_napi_module_wrap_instantiate(napi_env env, void *handle) + napi_status NAPI_CDECL unofficial_napi_module_wrap_instantiate( + napi_env env, + unofficial_napi_module module) { - if (!napi_util__::check_env(env) || handle == nullptr) + if (!napi_util__::check_env(env) || module == nullptr) return napi_invalid_arg; - return env->module_wrap().instantiate(handle); + return env->module_wrap().instantiate(module); } napi_status NAPI_CDECL unofficial_napi_module_wrap_evaluate( napi_env env, - void *handle, + unofficial_napi_module module, int64_t timeout, bool break_on_sigint, napi_value *result_out) { if (!napi_util__::check_env(env) || result_out == nullptr) return napi_invalid_arg; - if (handle == nullptr) + if (module == nullptr) return napi_invalid_arg; - return env->module_wrap().evaluate(handle, timeout, break_on_sigint, result_out); + return env->module_wrap().evaluate(module, timeout, break_on_sigint, result_out); } napi_status NAPI_CDECL unofficial_napi_module_wrap_evaluate_sync( napi_env env, - void *handle, + unofficial_napi_module module, napi_value filename, napi_value parent_filename, napi_value *result_out) { if (!napi_util__::check_env(env) || result_out == nullptr) return napi_invalid_arg; - if (handle == nullptr) + if (module == nullptr) return napi_invalid_arg; - return env->module_wrap().evaluate_sync(handle, filename, parent_filename, result_out); + return env->module_wrap().evaluate_sync(module, filename, parent_filename, result_out); } napi_status NAPI_CDECL unofficial_napi_module_wrap_get_namespace( napi_env env, - void *handle, + unofficial_napi_module module, napi_value *result_out) { if (!napi_util__::check_env(env) || result_out == nullptr) return napi_invalid_arg; - if (handle == nullptr) - return napi_invalid_arg; - return env->module_wrap().get_namespace(handle, result_out); - } - - napi_status NAPI_CDECL unofficial_napi_module_wrap_get_status( - napi_env env, - void *handle, - int32_t *status_out) - { - if (!napi_util__::check_env(env) || status_out == nullptr) - return napi_invalid_arg; - if (handle == nullptr) - return napi_invalid_arg; - return env->module_wrap().get_status(handle, status_out); - } - - napi_status NAPI_CDECL unofficial_napi_module_wrap_get_error( - napi_env env, - void *handle, - napi_value *result_out) - { - if (!napi_util__::check_env(env) || result_out == nullptr) - return napi_invalid_arg; - if (handle == nullptr) - return napi_invalid_arg; - return env->module_wrap().get_error(handle, result_out); - } - - napi_status NAPI_CDECL unofficial_napi_module_wrap_has_top_level_await( - napi_env env, - void *handle, - bool *result_out) - { - if (!napi_util__::check_env(env) || result_out == nullptr) - return napi_invalid_arg; - if (handle == nullptr) + if (module == nullptr) return napi_invalid_arg; - return env->module_wrap().has_top_level_await(handle, result_out); + return env->module_wrap().get_namespace(module, result_out); } - napi_status NAPI_CDECL unofficial_napi_module_wrap_has_async_graph( + napi_status NAPI_CDECL unofficial_napi_module_wrap_get_state( napi_env env, - void *handle, - bool *result_out) + unofficial_napi_module module, + int32_t *status_out, + napi_value *error_out, + bool *has_async_graph_out) { - if (!napi_util__::check_env(env) || result_out == nullptr) + if (!napi_util__::check_env(env) || + (status_out == nullptr && error_out == nullptr && + has_async_graph_out == nullptr)) return napi_invalid_arg; - if (handle == nullptr) + if (module == nullptr) return napi_invalid_arg; - return env->module_wrap().has_async_graph(handle, result_out); + return env->module_wrap().get_state( + module, status_out, error_out, has_async_graph_out); } napi_status NAPI_CDECL unofficial_napi_module_wrap_check_unsettled_top_level_await( napi_env env, - napi_value module_wrap, + unofficial_napi_module module, bool warnings, bool *settled_out) { - if (!napi_util__::check_env(env) || settled_out == nullptr) + if (!napi_util__::check_env(env) || module == nullptr || settled_out == nullptr) return napi_invalid_arg; - return env->module_wrap().check_unsettled_top_level_await(module_wrap, + return env->module_wrap().check_unsettled_top_level_await(module, warnings, settled_out); } napi_status NAPI_CDECL unofficial_napi_module_wrap_set_export( napi_env env, - void *handle, + unofficial_napi_module module, napi_value export_name, napi_value export_value) { - if (!napi_util__::check_env(env) || handle == nullptr) + if (!napi_util__::check_env(env) || module == nullptr) return napi_invalid_arg; - return env->module_wrap().set_export(handle, export_name, export_value); + return env->module_wrap().set_export(module, export_name, export_value); } napi_status NAPI_CDECL unofficial_napi_module_wrap_set_module_source_object( napi_env env, - void *handle, + unofficial_napi_module module, napi_value source_object) { - if (!napi_util__::check_env(env) || handle == nullptr) + if (!napi_util__::check_env(env) || module == nullptr) return napi_invalid_arg; - return env->module_wrap().set_module_source_object(handle, source_object); + return env->module_wrap().set_module_source_object(module, source_object); } napi_status NAPI_CDECL unofficial_napi_module_wrap_get_module_source_object( napi_env env, - void *handle, + unofficial_napi_module module, napi_value *result_out) { if (!napi_util__::check_env(env) || result_out == nullptr) return napi_invalid_arg; - if (handle == nullptr) + if (module == nullptr) return napi_invalid_arg; - return env->module_wrap().get_module_source_object(handle, result_out); + return env->module_wrap().get_module_source_object(module, result_out); } napi_status NAPI_CDECL unofficial_napi_module_wrap_create_cached_data( napi_env env, - void *handle, + unofficial_napi_module module, napi_value *result_out) { if (!napi_util__::check_env(env) || result_out == nullptr) return napi_invalid_arg; - return env->module_wrap().create_cached_data(handle, result_out); + return env->module_wrap().create_cached_data(module, result_out); } - napi_status NAPI_CDECL unofficial_napi_module_wrap_set_import_module_dynamically_callback( + napi_status NAPI_CDECL unofficial_napi_module_wrap_set_hooks( napi_env env, - napi_value callback) + const unofficial_napi_module_hooks *hooks) { - if (!napi_util__::check_env(env)) - return napi_invalid_arg; - return env->module_wrap().set_import_module_dynamically_callback(callback); - } - - napi_status NAPI_CDECL unofficial_napi_module_wrap_set_initialize_import_meta_object_callback( - napi_env env, - napi_value callback) - { - if (!napi_util__::check_env(env)) - return napi_invalid_arg; - return env->module_wrap().set_initialize_import_meta_object_callback(callback); - } - - napi_status NAPI_CDECL unofficial_napi_module_wrap_import_module_dynamically( - napi_env env, - size_t argc, - napi_value *argv, - napi_value *result_out) - { - if (!napi_util__::check_env(env) || result_out == nullptr) + if (!napi_util__::check_env(env) || hooks == nullptr) return napi_invalid_arg; - return env->module_wrap().import_module_dynamically(argc, argv, result_out); + return env->module_wrap().set_hooks(hooks); } napi_status NAPI_CDECL unofficial_napi_module_wrap_create_required_module_facade( napi_env env, - void *handle, + unofficial_napi_module module, napi_value *result_out) { if (!napi_util__::check_env(env) || result_out == nullptr) return napi_invalid_arg; - if (handle == nullptr) + if (module == nullptr) return napi_invalid_arg; - return env->module_wrap().create_required_module_facade(handle, result_out); + return env->module_wrap().create_required_module_facade(module, result_out); } napi_status NAPI_CDECL unofficial_napi_destroy_env_instance_for_testing(napi_env env) diff --git a/src/edge_napi_embedder_hooks.cc b/src/edge_napi_embedder_hooks.cc deleted file mode 100644 index df26986..0000000 --- a/src/edge_napi_embedder_hooks.cc +++ /dev/null @@ -1,48 +0,0 @@ -#include "edge_napi_embedder_hooks.h" - -#include -#include - -#if defined(__unix__) || defined(__APPLE__) -#include -#endif - -#include "unofficial_napi.h" - -namespace { - -uint64_t DetectTotalMemory() { -#if defined(_SC_PHYS_PAGES) - const long pages = sysconf(_SC_PHYS_PAGES); -#if defined(_SC_PAGE_SIZE) - const long page_size = sysconf(_SC_PAGE_SIZE); -#elif defined(_SC_PAGESIZE) - const long page_size = sysconf(_SC_PAGESIZE); -#else - const long page_size = 0; -#endif - if (pages > 0 && page_size > 0) { - return static_cast(pages) * static_cast(page_size); - } -#endif - return 0; -} - -napi_status GetEmbedderMemoryInfo(void* /*target*/, - unofficial_napi_embedder_memory_info* info_out) { - if (info_out == nullptr) return napi_invalid_arg; - info_out->total_memory = DetectTotalMemory(); - info_out->constrained_memory = 0; - return napi_ok; -} - -} // namespace - -void EdgeInstallNapiEmbedderHooks() { - static std::once_flag once; - std::call_once(once, []() { - unofficial_napi_embedder_hooks hooks{}; - hooks.memory_info_callback = GetEmbedderMemoryInfo; - (void)unofficial_napi_set_embedder_hooks(&hooks); - }); -} diff --git a/src/edge_napi_embedder_hooks.h b/src/edge_napi_embedder_hooks.h deleted file mode 100644 index e78c3bb..0000000 --- a/src/edge_napi_embedder_hooks.h +++ /dev/null @@ -1,6 +0,0 @@ -#ifndef EDGE_NAPI_EMBEDDER_HOOKS_H_ -#define EDGE_NAPI_EMBEDDER_HOOKS_H_ - -void EdgeInstallNapiEmbedderHooks(); - -#endif // EDGE_NAPI_EMBEDDER_HOOKS_H_ diff --git a/src/guest/abi.rs b/src/guest/abi.rs new file mode 100644 index 0000000..38d24ed --- /dev/null +++ b/src/guest/abi.rs @@ -0,0 +1,527 @@ +use wasmer::FunctionEnvMut; + +use crate::NapiEnv; + +use super::util::read_guest_bytes; + +#[repr(C)] +struct Wasm32VersionedHeader { + size: u32, + version: u32, +} + +#[repr(C)] +struct Wasm32RuntimeOptionsV1 { + size: u32, + version: u32, + engine_flags: u32, + engine_flags_length: u32, +} + +#[repr(C)] +struct Wasm32EnvCreateOptionsV1 { + size: u32, + version: u32, + total_memory: u64, + constrained_memory: u64, + max_young_generation_size_in_bytes: u32, + max_old_generation_size_in_bytes: u32, + code_range_size_in_bytes: u32, + stack_limit: u32, + guest_heap: u32, +} + +#[repr(C)] +struct Wasm32EnvHooksV1 { + size: u32, + version: u32, + data: u32, + context_token_assign_callback: u32, + context_token_unassign_callback: u32, + enqueue_foreground_task_callback: u32, + fatal_error_callback: u32, + oom_error_callback: u32, +} + +#[repr(C)] +struct Wasm32JsSource { + kind: i32, + text: u32, + bytecode: u32, +} + +#[repr(C)] +struct Wasm32BytecodeOpenOptionsV1 { + size: u32, + version: u32, + source_text: u32, + filename: u32, + shape: i32, + params_or_undefined: u32, + host_defined_option_id: u32, + line_offset: i32, + column_offset: i32, + cache_bytes: u32, + cache_byte_length: u32, + has_cache: u8, + cache_policy: u8, + reserved: [u8; 2], +} + +#[repr(C)] +struct Wasm32ModuleCreateOptionsV1 { + size: u32, + version: u32, + kind: i32, + wrapper: u32, + url: u32, + context_or_undefined: u32, + payload: [u8; 16], +} + +#[repr(C)] +struct Wasm32ModuleCreateJsPayload { + source: u32, + line_offset: i32, + column_offset: i32, + host_defined_option_id: u32, +} + +#[repr(C)] +struct Wasm32ModuleCreateSyntheticPayload { + export_names: u32, + synthetic_eval_steps: u32, + reserved: [u8; 8], +} + +#[repr(C)] +struct Wasm32ModuleHooksV1 { + size: u32, + version: u32, + import_module_dynamically: u32, + initialize_import_meta_object: u32, +} + +const JS_SOURCE_SIZE: usize = std::mem::size_of::(); +const RUNTIME_OPTIONS_SIZE: usize = std::mem::size_of::(); +const ENV_CREATE_PREFIX_SIZE: usize = std::mem::size_of::(); +const BYTECODE_OPEN_SIZE: usize = std::mem::size_of::(); +const MODULE_CREATE_PREFIX_SIZE: usize = std::mem::size_of::(); +const MODULE_HOOKS_SIZE: usize = std::mem::size_of::(); +const ENV_HOOKS_SIZE: usize = std::mem::size_of::(); + +const _: () = assert!(std::mem::size_of::() == 8); +const _: () = assert!(JS_SOURCE_SIZE == 12); +const _: () = assert!(RUNTIME_OPTIONS_SIZE == 16); +const _: () = assert!(ENV_CREATE_PREFIX_SIZE == 48); +const _: () = assert!(BYTECODE_OPEN_SIZE == 48); +const _: () = assert!(MODULE_CREATE_PREFIX_SIZE == 40); +const _: () = assert!(std::mem::size_of::() == 16); +const _: () = assert!(std::mem::size_of::() == 16); +const _: () = assert!(MODULE_HOOKS_SIZE == 16); +const _: () = assert!(ENV_HOOKS_SIZE == 32); + +fn u32_at(bytes: &[u8], offset: usize) -> Option { + Some(u32::from_le_bytes( + bytes.get(offset..offset.checked_add(4)?)?.try_into().ok()?, + )) +} + +fn i32_at(bytes: &[u8], offset: usize) -> Option { + Some(i32::from_le_bytes( + bytes.get(offset..offset.checked_add(4)?)?.try_into().ok()?, + )) +} + +fn read_versioned( + env: &mut FunctionEnvMut, + guest_ptr: i32, + prefix_size: usize, + version: u32, +) -> Option> { + if guest_ptr <= 0 { + return None; + } + let bytes = read_guest_bytes(env, guest_ptr, prefix_size)?; + if u32_at(&bytes, std::mem::offset_of!(Wasm32VersionedHeader, size))? < prefix_size as u32 + || u32_at(&bytes, std::mem::offset_of!(Wasm32VersionedHeader, version))? != version + { + return None; + } + Some(bytes) +} + +pub(crate) fn read_output_header( + env: &mut FunctionEnvMut, + guest_ptr: i32, + minimum_size: usize, + version: u32, +) -> Option { + let bytes = read_guest_bytes(env, guest_ptr, 8)?; + let size = u32_at(&bytes, std::mem::offset_of!(Wasm32VersionedHeader, size))?; + (size >= minimum_size as u32 + && u32_at(&bytes, std::mem::offset_of!(Wasm32VersionedHeader, version))? == version) + .then_some(size) +} + +pub(crate) struct EnvCreate { + pub total_memory: u64, + pub constrained_memory: u64, + pub max_young_generation_size_in_bytes: u32, + pub max_old_generation_size_in_bytes: u32, + pub code_range_size_in_bytes: u32, + pub stack_limit: u32, +} + +pub(crate) fn read_runtime_options( + env: &mut FunctionEnvMut, + guest_ptr: i32, +) -> Option> { + if guest_ptr == 0 { + return Some(Vec::new()); + } + let bytes = read_versioned(env, guest_ptr, RUNTIME_OPTIONS_SIZE, 1)?; + let flags_ptr = u32_at( + &bytes, + std::mem::offset_of!(Wasm32RuntimeOptionsV1, engine_flags), + )? as i32; + let flags_length = u32_at( + &bytes, + std::mem::offset_of!(Wasm32RuntimeOptionsV1, engine_flags_length), + )? as usize; + if flags_length == 0 { + return Some(Vec::new()); + } + if flags_ptr <= 0 { + return None; + } + read_guest_bytes(env, flags_ptr, flags_length) +} + +pub(crate) fn read_env_create( + env: &mut FunctionEnvMut, + guest_ptr: i32, +) -> Option { + let bytes = read_versioned(env, guest_ptr, ENV_CREATE_PREFIX_SIZE, 1)?; + // A raw guest pointer is not an unofficial_napi_guest_heap provider + // resource. Wasmer injects its own guest-heap owner after decoding, so a + // non-null wire field is rejected rather than silently discarded. + if u32_at( + &bytes, + std::mem::offset_of!(Wasm32EnvCreateOptionsV1, guest_heap), + )? != 0 + { + return None; + } + Some(EnvCreate { + total_memory: u64::from_le_bytes( + bytes + .get( + std::mem::offset_of!(Wasm32EnvCreateOptionsV1, total_memory) + ..std::mem::offset_of!(Wasm32EnvCreateOptionsV1, total_memory) + 8, + )? + .try_into() + .ok()?, + ), + constrained_memory: u64::from_le_bytes( + bytes + .get( + std::mem::offset_of!(Wasm32EnvCreateOptionsV1, constrained_memory) + ..std::mem::offset_of!(Wasm32EnvCreateOptionsV1, constrained_memory) + 8, + )? + .try_into() + .ok()?, + ), + max_young_generation_size_in_bytes: u32_at( + &bytes, + std::mem::offset_of!(Wasm32EnvCreateOptionsV1, max_young_generation_size_in_bytes), + )?, + max_old_generation_size_in_bytes: u32_at( + &bytes, + std::mem::offset_of!(Wasm32EnvCreateOptionsV1, max_old_generation_size_in_bytes), + )?, + code_range_size_in_bytes: u32_at( + &bytes, + std::mem::offset_of!(Wasm32EnvCreateOptionsV1, code_range_size_in_bytes), + )?, + stack_limit: u32_at( + &bytes, + std::mem::offset_of!(Wasm32EnvCreateOptionsV1, stack_limit), + )?, + }) +} + +pub(crate) fn read_js_source( + env: &mut FunctionEnvMut, + guest_ptr: i32, +) -> Option<(u32, u32)> { + let bytes = read_guest_bytes(env, guest_ptr, JS_SOURCE_SIZE)?; + match ( + i32_at(&bytes, std::mem::offset_of!(Wasm32JsSource, kind))?, + u32_at(&bytes, std::mem::offset_of!(Wasm32JsSource, text))?, + u32_at(&bytes, std::mem::offset_of!(Wasm32JsSource, bytecode))?, + ) { + (0, text, 0) if text != 0 => Some((text, 0)), + (1, 0, bytecode) if bytecode != 0 => Some((0, bytecode)), + _ => None, + } +} + +pub(crate) struct BytecodeOpen { + pub source_text: u32, + pub filename: u32, + pub shape: i32, + pub params_or_undefined: u32, + pub host_defined_option_id: u32, + pub line_offset: i32, + pub column_offset: i32, + pub cache: Vec, + pub has_cache: u8, + pub cache_policy: u8, +} + +pub(crate) fn read_bytecode_open( + env: &mut FunctionEnvMut, + guest_ptr: i32, +) -> Option { + let bytes = read_versioned(env, guest_ptr, BYTECODE_OPEN_SIZE, 1)?; + let cache_ptr = u32_at( + &bytes, + std::mem::offset_of!(Wasm32BytecodeOpenOptionsV1, cache_bytes), + )? as i32; + let cache_length = u32_at( + &bytes, + std::mem::offset_of!(Wasm32BytecodeOpenOptionsV1, cache_byte_length), + )? as usize; + let has_cache = *bytes.get(std::mem::offset_of!(Wasm32BytecodeOpenOptionsV1, has_cache))?; + let cache_policy = *bytes.get(std::mem::offset_of!( + Wasm32BytecodeOpenOptionsV1, + cache_policy + ))?; + let cache = if has_cache != 0 && cache_length != 0 { + if cache_ptr <= 0 { + return None; + } + read_guest_bytes(env, cache_ptr, cache_length)? + } else { + Vec::new() + }; + Some(BytecodeOpen { + source_text: u32_at( + &bytes, + std::mem::offset_of!(Wasm32BytecodeOpenOptionsV1, source_text), + )?, + filename: u32_at( + &bytes, + std::mem::offset_of!(Wasm32BytecodeOpenOptionsV1, filename), + )?, + shape: i32_at( + &bytes, + std::mem::offset_of!(Wasm32BytecodeOpenOptionsV1, shape), + )?, + params_or_undefined: u32_at( + &bytes, + std::mem::offset_of!(Wasm32BytecodeOpenOptionsV1, params_or_undefined), + )?, + host_defined_option_id: u32_at( + &bytes, + std::mem::offset_of!(Wasm32BytecodeOpenOptionsV1, host_defined_option_id), + )?, + line_offset: i32_at( + &bytes, + std::mem::offset_of!(Wasm32BytecodeOpenOptionsV1, line_offset), + )?, + column_offset: i32_at( + &bytes, + std::mem::offset_of!(Wasm32BytecodeOpenOptionsV1, column_offset), + )?, + cache, + has_cache, + cache_policy, + }) +} + +pub(crate) struct ModuleCreate { + pub kind: i32, + pub wrapper: u32, + pub url: u32, + pub context_or_undefined: u32, + pub source_text: u32, + pub source_bytecode: u32, + pub line_offset: i32, + pub column_offset: i32, + pub host_defined_option_id: u32, + pub export_names: u32, + pub synthetic_eval_steps: u32, +} + +pub(crate) fn read_module_create( + env: &mut FunctionEnvMut, + guest_ptr: i32, +) -> Option { + let bytes = read_versioned(env, guest_ptr, MODULE_CREATE_PREFIX_SIZE, 1)?; + let kind = i32_at( + &bytes, + std::mem::offset_of!(Wasm32ModuleCreateOptionsV1, kind), + )?; + let payload_offset = std::mem::offset_of!(Wasm32ModuleCreateOptionsV1, payload); + let ( + source_text, + source_bytecode, + line_offset, + column_offset, + host_defined_option_id, + export_names, + synthetic_eval_steps, + ) = match kind { + 1 => { + let source_offset = + payload_offset + std::mem::offset_of!(Wasm32ModuleCreateJsPayload, source); + let (text, bytecode) = read_js_source(env, u32_at(&bytes, source_offset)? as i32)?; + ( + text, + bytecode, + i32_at( + &bytes, + payload_offset + std::mem::offset_of!(Wasm32ModuleCreateJsPayload, line_offset), + )?, + i32_at( + &bytes, + payload_offset + + std::mem::offset_of!(Wasm32ModuleCreateJsPayload, column_offset), + )?, + u32_at( + &bytes, + payload_offset + + std::mem::offset_of!(Wasm32ModuleCreateJsPayload, host_defined_option_id), + )?, + 0, + 0, + ) + } + 2 => ( + 0, + 0, + 0, + 0, + 0, + u32_at( + &bytes, + payload_offset + + std::mem::offset_of!(Wasm32ModuleCreateSyntheticPayload, export_names), + )?, + u32_at( + &bytes, + payload_offset + + std::mem::offset_of!( + Wasm32ModuleCreateSyntheticPayload, + synthetic_eval_steps + ), + )?, + ), + _ => return None, + }; + Some(ModuleCreate { + kind, + wrapper: u32_at( + &bytes, + std::mem::offset_of!(Wasm32ModuleCreateOptionsV1, wrapper), + )?, + url: u32_at( + &bytes, + std::mem::offset_of!(Wasm32ModuleCreateOptionsV1, url), + )?, + context_or_undefined: u32_at( + &bytes, + std::mem::offset_of!(Wasm32ModuleCreateOptionsV1, context_or_undefined), + )?, + source_text, + source_bytecode, + line_offset, + column_offset, + host_defined_option_id, + export_names, + synthetic_eval_steps, + }) +} + +pub(crate) fn read_module_hooks( + env: &mut FunctionEnvMut, + guest_ptr: i32, +) -> Option<(u32, u32)> { + let bytes = read_versioned(env, guest_ptr, MODULE_HOOKS_SIZE, 1)?; + Some(( + u32_at( + &bytes, + std::mem::offset_of!(Wasm32ModuleHooksV1, import_module_dynamically), + )?, + u32_at( + &bytes, + std::mem::offset_of!(Wasm32ModuleHooksV1, initialize_import_meta_object), + )?, + )) +} + +pub(crate) struct EnvHooks { + pub fatal: u32, + pub oom: u32, + pub requested: u64, +} + +pub(crate) fn read_env_hooks( + env: &mut FunctionEnvMut, + guest_ptr: i32, +) -> Option { + let bytes = read_versioned(env, guest_ptr, ENV_HOOKS_SIZE, 1)?; + let hook_fields = [ + ( + Wasm32EnvHooksV1::context_token_assign_callback_offset(), + 1u64 << 0, + ), + ( + Wasm32EnvHooksV1::context_token_unassign_callback_offset(), + 1u64 << 1, + ), + ( + Wasm32EnvHooksV1::enqueue_foreground_task_callback_offset(), + 1u64 << 2, + ), + (Wasm32EnvHooksV1::fatal_error_callback_offset(), 1u64 << 3), + (Wasm32EnvHooksV1::oom_error_callback_offset(), 1u64 << 4), + ]; + let mut requested = 0; + for (offset, bit) in hook_fields { + if u32_at(&bytes, offset)? != 0 { + requested |= bit; + } + } + Some(EnvHooks { + fatal: u32_at( + &bytes, + std::mem::offset_of!(Wasm32EnvHooksV1, fatal_error_callback), + )?, + oom: u32_at( + &bytes, + std::mem::offset_of!(Wasm32EnvHooksV1, oom_error_callback), + )?, + requested, + }) +} + +impl Wasm32EnvHooksV1 { + const fn context_token_assign_callback_offset() -> usize { + std::mem::offset_of!(Self, context_token_assign_callback) + } + const fn context_token_unassign_callback_offset() -> usize { + std::mem::offset_of!(Self, context_token_unassign_callback) + } + const fn enqueue_foreground_task_callback_offset() -> usize { + std::mem::offset_of!(Self, enqueue_foreground_task_callback) + } + const fn fatal_error_callback_offset() -> usize { + std::mem::offset_of!(Self, fatal_error_callback) + } + const fn oom_error_callback_offset() -> usize { + std::mem::offset_of!(Self, oom_error_callback) + } +} diff --git a/src/guest/mod.rs b/src/guest/mod.rs index 03ed622..6299bb3 100644 --- a/src/guest/mod.rs +++ b/src/guest/mod.rs @@ -1,3 +1,4 @@ +pub(crate) mod abi; #[cfg(not(all(target_arch = "wasm32", feature = "js")))] pub mod callback; #[cfg(all(target_arch = "wasm32", feature = "js"))] diff --git a/src/guest/napi.rs b/src/guest/napi.rs index 8ec9d6c..e70c55f 100644 --- a/src/guest/napi.rs +++ b/src/guest/napi.rs @@ -4,7 +4,7 @@ // --- Init --- -use std::ffi::{CString, c_void}; +use std::ffi::CString; use wasmer::{AsStoreMut, Function, FunctionEnv, FunctionEnvMut, Imports, namespace}; use wasmer_wasix::WasiError; @@ -18,10 +18,14 @@ use crate::{ snapi::*, }; -use super::util::*; +use super::{abi, util::*}; fn guest_napi_wasm_init_env(mut env: FunctionEnvMut) -> i32 { let _ = unsafe { snapi_bridge_init() }; + // Legacy N-API modules without an embedder-owned runtime configuration use + // the provider default. If Edge configured flags first, this conflicting + // empty request is rejected without changing the existing runtime. + let _ = unsafe { snapi_bridge_unofficial_configure_runtime(std::ptr::null(), 0) }; if let Some(env_id) = env.data().default_napi_env_id { return env_id as i32; @@ -37,6 +41,8 @@ fn guest_napi_wasm_init_env(mut env: FunctionEnvMut) -> i32 { unsafe { snapi_bridge_unofficial_create_env_with_options( 8, + 0, + 0, reservation.max_young, reservation.max_old, reservation.code_range, @@ -68,21 +74,6 @@ fn guest_heap_alloc_ctx(env: &FunctionEnvMut) -> *const std::ffi::c_voi .unwrap_or(std::ptr::null()) } -fn guest_unofficial_napi_set_flags_from_string( - mut env: FunctionEnvMut, - flags_ptr: i32, - flags_len: i32, -) -> i32 { - if flags_ptr <= 0 || flags_len < 0 { - return 1; - } - let Some(flags_bytes) = read_guest_bytes(&mut env, flags_ptr, flags_len as usize) else { - return 1; - }; - let flags = CString::new(flags_bytes).unwrap_or_default(); - unsafe { snapi_bridge_unofficial_set_flags_from_string(flags.as_ptr(), flags_len as u32) } -} - /// Run a bridge call that may re-enter the guest via host V8 callbacks, then /// re-raise any guest instance exit that trapped out of one of those callbacks. /// @@ -110,22 +101,6 @@ fn snapi_env(env: &FunctionEnvMut, guest_env: i32) -> SnapiEnv { env.data().resolve_napi_env(guest_env) } -/// Reads an `unofficial_napi_js_source` ({ napi_value text; void* bytecode }) -/// from guest memory. The guest ABI passes the struct by pointer; both fields -/// are 32-bit in wasm32. -fn read_guest_js_source(env: &mut FunctionEnvMut, source_ptr: i32) -> (i32, i32) { - if source_ptr <= 0 { - return (0, 0); - } - match read_guest_bytes(env, source_ptr, 8) { - Some(bytes) if bytes.len() == 8 => ( - i32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]), - i32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]), - ), - _ => (0, 0), - } -} - fn write_guest_pod(env: &mut FunctionEnvMut, guest_ptr: i32, value: &T) -> bool { if guest_ptr <= 0 { return false; @@ -136,37 +111,21 @@ fn write_guest_pod(env: &mut FunctionEnvMut, guest_ptr: i32, value: write_guest_bytes(env, guest_ptr as u32, bytes) } -fn copy_host_buffer_to_guest( +fn write_guest_pod_slice( env: &mut FunctionEnvMut, - data_out_ptr: i32, - len_out_ptr: i32, - host_ptr: u64, - host_len: u32, -) -> i32 { - let mut guest_ptr = 0u32; - if host_ptr != 0 && host_len > 0 { - let host_slice = - unsafe { std::slice::from_raw_parts(host_ptr as *const u8, host_len as usize) }; - let Some(ptr) = allocate_guest_bytes(env, host_slice) else { - unsafe { snapi_bridge_unofficial_free_buffer(host_ptr as *mut c_void) }; - return 1; - }; - guest_ptr = ptr; - } - if data_out_ptr > 0 { - write_guest_u32(env, data_out_ptr as u32, guest_ptr); - } - if len_out_ptr > 0 { - write_guest_u32( - env, - len_out_ptr as u32, - if guest_ptr == 0 { 0 } else { host_len }, - ); + guest_ptr: i32, + values: &[T], +) -> bool { + if values.is_empty() { + return true; } - if host_ptr != 0 { - unsafe { snapi_bridge_unofficial_free_buffer(host_ptr as *mut c_void) }; + if guest_ptr <= 0 { + return false; } - 0 + let bytes = unsafe { + std::slice::from_raw_parts(values.as_ptr().cast::(), std::mem::size_of_val(values)) + }; + write_guest_bytes(env, guest_ptr as u32, bytes) } /// Resolve a value's backing-store data pointer to a guest address. @@ -233,55 +192,22 @@ fn foreign_store_warn() { } } -fn guest_unofficial_napi_create_env( +fn guest_unofficial_napi_configure_runtime( mut env: FunctionEnvMut, - module_api_version: i32, - env_out_ptr: i32, - scope_out_ptr: i32, + options_ptr: i32, ) -> i32 { - let reservation = match env.data().reserve_isolate(RequestedHeap::default()) { - Ok(reservation) => reservation, - Err(_) => return 1, + let Some(flags) = abi::read_runtime_options(&mut env, options_ptr) else { + return 1; }; - - let guest_heap_ctx = guest_heap_alloc_ctx(&env); - let mut snapi_env_state: SnapiEnv = std::ptr::null_mut(); - let status = if reservation.clamped { - unsafe { - snapi_bridge_unofficial_create_env_with_options( - module_api_version, - reservation.max_young, - reservation.max_old, - reservation.code_range, - 0, - guest_heap_ctx, - &mut snapi_env_state, - ) - } - } else { - unsafe { - snapi_bridge_unofficial_create_env( - module_api_version, - guest_heap_ctx, - &mut snapi_env_state, - ) - } + let Ok(flags) = CString::new(flags) else { + return 1; }; - if status != 0 { - env.data().abort_isolate(&reservation); - return status; - } - let (env_id, scope_id) = env.data_mut().commit_isolate(snapi_env_state, &reservation); - if env_out_ptr > 0 { - write_guest_u32(&mut env, env_out_ptr as u32, env_id); - } - if scope_out_ptr > 0 { - write_guest_u32(&mut env, scope_out_ptr as u32, scope_id); + unsafe { + snapi_bridge_unofficial_configure_runtime(flags.as_ptr(), flags.as_bytes().len() as u32) } - 0 } -fn guest_unofficial_napi_create_env_with_options( +fn guest_unofficial_napi_create_env( mut env: FunctionEnvMut, module_api_version: i32, options_ptr: i32, @@ -289,22 +215,26 @@ fn guest_unofficial_napi_create_env_with_options( scope_out_ptr: i32, ) -> i32 { let ( + total_memory, + constrained_memory, max_young_generation_size_in_bytes, max_old_generation_size_in_bytes, code_range_size_in_bytes, stack_limit, ) = if options_ptr > 0 { - let Some(bytes) = read_guest_bytes(&mut env, options_ptr, 16) else { + let Some(options) = abi::read_env_create(&mut env, options_ptr) else { return 1; }; ( - u32::from_le_bytes(bytes[0..4].try_into().unwrap()), - u32::from_le_bytes(bytes[4..8].try_into().unwrap()), - u32::from_le_bytes(bytes[8..12].try_into().unwrap()), - u32::from_le_bytes(bytes[12..16].try_into().unwrap()), + options.total_memory, + options.constrained_memory, + options.max_young_generation_size_in_bytes, + options.max_old_generation_size_in_bytes, + options.code_range_size_in_bytes, + options.stack_limit, ) } else { - (0, 0, 0, 0) + (0, 0, 0, 0, 0, 0) }; let requested = RequestedHeap { @@ -322,6 +252,8 @@ fn guest_unofficial_napi_create_env_with_options( let status = unsafe { snapi_bridge_unofficial_create_env_with_options( module_api_version, + total_memory, + constrained_memory, reservation.max_young, reservation.max_old, reservation.code_range, @@ -344,15 +276,7 @@ fn guest_unofficial_napi_create_env_with_options( 0 } -fn guest_unofficial_napi_release_env(mut env: FunctionEnvMut, scope_ptr: i32) -> i32 { - let scope_id = if scope_ptr > 0 { scope_ptr as u32 } else { 0 }; - let Some(snapi_env_state) = env.data_mut().unregister_napi_scope(scope_id) else { - return 1; - }; - unsafe { snapi_bridge_unofficial_release_env(snapi_env_state) } -} - -fn guest_unofficial_napi_release_env_with_loop( +fn guest_unofficial_napi_release_env( mut env: FunctionEnvMut, scope_ptr: i32, loop_ptr: i32, @@ -365,17 +289,37 @@ fn guest_unofficial_napi_release_env_with_loop( unsafe { snapi_bridge_unofficial_release_env_with_loop(snapi_env_state, loop_id) } } -fn guest_unofficial_napi_set_embedder_hooks(env: FunctionEnvMut, napi_env: i32) -> i32 { - let env_handle = snapi_env(&env, napi_env); - unsafe { snapi_bridge_unofficial_set_embedder_hooks(env_handle) } -} - -fn guest_unofficial_napi_low_memory_notification( - env: FunctionEnvMut, +fn guest_unofficial_napi_attach_env( + mut env: FunctionEnvMut, napi_env: i32, + hooks_ptr: i32, + accepted_hooks_ptr: i32, ) -> i32 { + let Some(hooks) = abi::read_env_hooks(&mut env, hooks_ptr) else { + return 1; + }; + if napi_env <= 0 || accepted_hooks_ptr <= 0 { + return 1; + } + let mut accepted_hooks = 0u64; + let status = unsafe { + snapi_bridge_unofficial_attach_env( + snapi_env(&env, napi_env), + hooks.fatal, + hooks.oom, + &mut accepted_hooks, + ) + }; + if status == 0 { + write_guest_u64(&mut env, accepted_hooks_ptr as u32, accepted_hooks); + debug_assert_eq!(accepted_hooks & !hooks.requested, 0); + } + status +} + +fn guest_unofficial_napi_collect_garbage(env: FunctionEnvMut, napi_env: i32) -> i32 { let env_handle = snapi_env(&env, napi_env); - unsafe { snapi_bridge_unofficial_low_memory_notification(env_handle) } + unsafe { snapi_bridge_unofficial_collect_garbage(env_handle) } } fn guest_unofficial_napi_event_loop_checkpoint( @@ -417,14 +361,6 @@ fn guest_unofficial_napi_create_uninitialized_arraybuffer( guest_napi_create_arraybuffer(env, napi_env, length, 0, result_ptr) } -fn guest_unofficial_napi_request_gc_for_testing( - env: FunctionEnvMut, - napi_env: i32, -) -> i32 { - let env_handle = snapi_env(&env, napi_env); - unsafe { snapi_bridge_unofficial_request_gc_for_testing(env_handle) } -} - fn guest_unofficial_napi_get_promise_details( mut env: FunctionEnvMut, napi_env: i32, @@ -541,42 +477,6 @@ fn guest_unofficial_napi_get_call_sites( status } -fn guest_unofficial_napi_get_current_stack_trace( - mut env: FunctionEnvMut, - napi_env: i32, - frames: i32, - callsites_ptr: i32, -) -> i32 { - let env_handle = snapi_env(&env, napi_env); - let mut callsites_id = 0u32; - let status = unsafe { - snapi_bridge_unofficial_get_current_stack_trace( - env_handle, - frames as u32, - &mut callsites_id, - ) - }; - if status == 0 && callsites_ptr > 0 { - write_guest_u32(&mut env, callsites_ptr as u32, callsites_id); - } - status -} - -fn guest_unofficial_napi_get_caller_location( - mut env: FunctionEnvMut, - napi_env: i32, - location_ptr: i32, -) -> i32 { - let env_handle = snapi_env(&env, napi_env); - let mut location_id = 0u32; - let status = - unsafe { snapi_bridge_unofficial_get_caller_location(env_handle, &mut location_id) }; - if status == 0 && location_ptr > 0 { - write_guest_u32(&mut env, location_ptr as u32, location_id); - } - status -} - fn guest_unofficial_napi_arraybuffer_view_has_buffer( mut env: FunctionEnvMut, napi_env: i32, @@ -690,36 +590,6 @@ fn guest_unofficial_napi_notify_datetime_configuration_change( unsafe { snapi_bridge_unofficial_notify_datetime_configuration_change(env_handle) } } -fn guest_unofficial_napi_set_enqueue_foreground_task_callback( - env: FunctionEnvMut, - napi_env: i32, - _callback: i32, - _target: i32, -) -> i32 { - let env_handle = snapi_env(&env, napi_env); - unsafe { snapi_bridge_unofficial_set_enqueue_foreground_task_callback(env_handle) } -} - -fn guest_unofficial_napi_set_fatal_error_callbacks( - env: FunctionEnvMut, - napi_env: i32, - fatal_callback: i32, - oom_callback: i32, -) -> i32 { - let env_handle = snapi_env(&env, napi_env); - let fatal_id = if fatal_callback > 0 { - fatal_callback as u32 - } else { - 0 - }; - let oom_id = if oom_callback > 0 { - oom_callback as u32 - } else { - 0 - }; - unsafe { snapi_bridge_unofficial_set_fatal_error_callbacks(env_handle, fatal_id, oom_id) } -} - fn guest_unofficial_napi_terminate_execution(env: FunctionEnvMut, napi_env: i32) -> i32 { let env_handle = snapi_env(&env, napi_env); unsafe { snapi_bridge_unofficial_terminate_execution(env_handle) } @@ -753,23 +623,6 @@ fn guest_unofficial_napi_request_interrupt( } fn guest_unofficial_napi_structured_clone( - mut env: FunctionEnvMut, - napi_env: i32, - value: i32, - result_ptr: i32, -) -> i32 { - let env_handle = snapi_env(&env, napi_env); - let value_id = if value > 0 { value as u32 } else { 0 }; - let mut out = 0u32; - let status = - unsafe { snapi_bridge_unofficial_structured_clone(env_handle, value_id, &mut out) }; - if status == 0 && result_ptr > 0 { - write_guest_u32(&mut env, result_ptr as u32, out); - } - status -} - -fn guest_unofficial_napi_structured_clone_with_transfer( mut env: FunctionEnvMut, napi_env: i32, value: i32, @@ -785,12 +638,7 @@ fn guest_unofficial_napi_structured_clone_with_transfer( }; let mut out = 0u32; let status = unsafe { - snapi_bridge_unofficial_structured_clone_with_transfer( - env_handle, - value_id, - transfer_list_id, - &mut out, - ) + snapi_bridge_unofficial_structured_clone(env_handle, value_id, transfer_list_id, &mut out) }; if status == 0 && result_ptr > 0 { write_guest_u32(&mut env, result_ptr as u32, out); @@ -798,31 +646,52 @@ fn guest_unofficial_napi_structured_clone_with_transfer( status } -fn guest_unofficial_napi_serialize_value( +fn guest_unofficial_napi_message_create( mut env: FunctionEnvMut, - _napi_env: i32, + napi_env: i32, value: i32, payload_out_ptr: i32, ) -> i32 { - if payload_out_ptr > 0 { - write_guest_u32(&mut env, payload_out_ptr as u32, value.max(0) as u32); + if payload_out_ptr <= 0 { + return 1; } - 0 + let env_handle = snapi_env(&env, napi_env); + let mut message = 0u32; + let status = unsafe { + snapi_bridge_unofficial_message_create(env_handle, value.max(0) as u32, &mut message) + }; + if status == 0 { + write_guest_u32(&mut env, payload_out_ptr as u32, message); + } + status } -fn guest_unofficial_napi_deserialize_value( +fn guest_unofficial_napi_message_take( mut env: FunctionEnvMut, - _napi_env: i32, + napi_env: i32, payload: i32, result_out_ptr: i32, ) -> i32 { - if result_out_ptr > 0 { - write_guest_u32(&mut env, result_out_ptr as u32, payload.max(0) as u32); + if payload <= 0 { + return 1; } - 0 + if result_out_ptr <= 0 { + unsafe { snapi_bridge_unofficial_message_drop(payload as u32) }; + return 1; + } + let env_handle = snapi_env(&env, napi_env); + let mut value = 0u32; + let status = + unsafe { snapi_bridge_unofficial_message_take(env_handle, payload as u32, &mut value) }; + if status == 0 { + write_guest_u32(&mut env, result_out_ptr as u32, value); + } + status } -fn guest_unofficial_napi_release_serialized_value(_env: FunctionEnvMut, _payload: i32) {} +fn guest_unofficial_napi_message_drop(_env: FunctionEnvMut, payload: i32) { + unsafe { snapi_bridge_unofficial_message_drop(payload.max(0) as u32) }; +} fn guest_unofficial_napi_enqueue_microtask( env: FunctionEnvMut, @@ -844,6 +713,32 @@ fn guest_unofficial_napi_set_promise_reject_callback( unsafe { snapi_bridge_unofficial_set_promise_reject_callback(env_handle, callback_id) } } +fn guest_unofficial_napi_get_own_non_index_properties( + mut env: FunctionEnvMut, + napi_env: i32, + value: i32, + filter_bits: i32, + result_out_ptr: i32, +) -> i32 { + if value <= 0 || result_out_ptr <= 0 { + return 1; + } + let env_handle = snapi_env(&env, napi_env); + let mut out = 0u32; + let status = unsafe { + snapi_bridge_unofficial_get_own_non_index_properties( + env_handle, + value as u32, + filter_bits.max(0) as u32, + &mut out, + ) + }; + if status == 0 { + write_guest_u32(&mut env, result_out_ptr as u32, out); + } + status +} + fn guest_unofficial_napi_set_promise_hooks( env: FunctionEnvMut, napi_env: i32, @@ -880,70 +775,6 @@ fn guest_unofficial_napi_set_promise_hooks( } } -fn guest_unofficial_napi_get_own_non_index_properties( - mut env: FunctionEnvMut, - napi_env: i32, - value: i32, - filter_bits: i32, - result_out_ptr: i32, -) -> i32 { - let env_handle = snapi_env(&env, napi_env); - let value_id = if value > 0 { value as u32 } else { 0 }; - let filter = if filter_bits > 0 { - filter_bits as u32 - } else { - 0 - }; - let mut out = 0u32; - let status = unsafe { - snapi_bridge_unofficial_get_own_non_index_properties(env_handle, value_id, filter, &mut out) - }; - if status == 0 && result_out_ptr > 0 { - write_guest_u32(&mut env, result_out_ptr as u32, out); - } - status -} - -fn guest_unofficial_napi_get_process_memory_info( - mut env: FunctionEnvMut, - napi_env: i32, - heap_total_out: i32, - heap_used_out: i32, - external_out: i32, - array_buffers_out: i32, -) -> i32 { - let env_handle = snapi_env(&env, napi_env); - let mut heap_total = 0.0f64; - let mut heap_used = 0.0f64; - let mut external = 0.0f64; - let mut array_buffers = 0.0f64; - let status = unsafe { - snapi_bridge_unofficial_get_process_memory_info( - env_handle, - &mut heap_total, - &mut heap_used, - &mut external, - &mut array_buffers, - ) - }; - if status != 0 { - return status; - } - if heap_total_out > 0 { - write_guest_f64(&mut env, heap_total_out as u32, heap_total); - } - if heap_used_out > 0 { - write_guest_f64(&mut env, heap_used_out as u32, heap_used); - } - if external_out > 0 { - write_guest_f64(&mut env, external_out as u32, external); - } - if array_buffers_out > 0 { - write_guest_f64(&mut env, array_buffers_out as u32, array_buffers); - } - 0 -} - fn guest_unofficial_napi_get_hash_seed( mut env: FunctionEnvMut, napi_env: i32, @@ -958,25 +789,31 @@ fn guest_unofficial_napi_get_hash_seed( status } -fn guest_unofficial_napi_get_error_source_positions( +fn guest_unofficial_napi_get_error_metadata( mut env: FunctionEnvMut, napi_env: i32, error: i32, - positions_ptr: i32, + mode: i32, + metadata_ptr: i32, ) -> i32 { let env_handle = snapi_env(&env, napi_env); let error_id = if error > 0 { error as u32 } else { 0 }; let mut source_line_id = 0u32; let mut script_resource_name_id = 0u32; + let mut stderr_line_id = 0u32; + let mut thrown_at_id = 0u32; let mut line_number = 0i32; let mut start_column = 0i32; let mut end_column = 0i32; let status = unsafe { - snapi_bridge_unofficial_get_error_source_positions( + snapi_bridge_unofficial_get_error_metadata( env_handle, error_id, + mode, &mut source_line_id, &mut script_resource_name_id, + &mut stderr_line_id, + &mut thrown_at_id, &mut line_number, &mut start_column, &mut end_column, @@ -985,105 +822,27 @@ fn guest_unofficial_napi_get_error_source_positions( if status != 0 { return status; } - if positions_ptr > 0 { - write_guest_u32(&mut env, positions_ptr as u32, source_line_id); - write_guest_u32(&mut env, positions_ptr as u32 + 4, script_resource_name_id); - write_guest_i32(&mut env, positions_ptr as u32 + 8, line_number); - write_guest_i32(&mut env, positions_ptr as u32 + 12, start_column); - write_guest_i32(&mut env, positions_ptr as u32 + 16, end_column); + if metadata_ptr > 0 { + write_guest_u32(&mut env, metadata_ptr as u32, source_line_id); + write_guest_u32(&mut env, metadata_ptr as u32 + 4, script_resource_name_id); + write_guest_u32(&mut env, metadata_ptr as u32 + 8, stderr_line_id); + write_guest_u32(&mut env, metadata_ptr as u32 + 12, thrown_at_id); + write_guest_i32(&mut env, metadata_ptr as u32 + 16, line_number); + write_guest_i32(&mut env, metadata_ptr as u32 + 20, start_column); + write_guest_i32(&mut env, metadata_ptr as u32 + 24, end_column); } 0 } -fn guest_unofficial_napi_set_source_maps_enabled( +fn guest_unofficial_napi_configure_source_maps( env: FunctionEnvMut, napi_env: i32, enabled: i32, -) -> i32 { - let env_handle = snapi_env(&env, napi_env); - unsafe { snapi_bridge_unofficial_set_source_maps_enabled(env_handle, enabled) } -} - -fn guest_unofficial_napi_set_get_source_map_error_source_callback( - env: FunctionEnvMut, - napi_env: i32, callback: i32, ) -> i32 { let env_handle = snapi_env(&env, napi_env); let callback_id = if callback > 0 { callback as u32 } else { 0 }; - unsafe { - snapi_bridge_unofficial_set_get_source_map_error_source_callback(env_handle, callback_id) - } -} - -fn guest_unofficial_napi_get_error_source_line_for_stderr( - mut env: FunctionEnvMut, - napi_env: i32, - error: i32, - result_ptr: i32, -) -> i32 { - let env_handle = snapi_env(&env, napi_env); - let error_id = if error > 0 { error as u32 } else { 0 }; - let mut result_id = 0u32; - let status = unsafe { - snapi_bridge_unofficial_get_error_source_line_for_stderr( - env_handle, - error_id, - &mut result_id, - ) - }; - if status == 0 && result_ptr > 0 { - write_guest_u32(&mut env, result_ptr as u32, result_id); - } - status -} - -fn guest_unofficial_napi_get_error_thrown_at( - mut env: FunctionEnvMut, - napi_env: i32, - error: i32, - result_ptr: i32, -) -> i32 { - let env_handle = snapi_env(&env, napi_env); - let error_id = if error > 0 { error as u32 } else { 0 }; - let mut result_id = 0u32; - let status = unsafe { - snapi_bridge_unofficial_get_error_thrown_at(env_handle, error_id, &mut result_id) - }; - if status == 0 && result_ptr > 0 { - write_guest_u32(&mut env, result_ptr as u32, result_id); - } - status -} - -fn guest_unofficial_napi_take_preserved_error_formatting( - mut env: FunctionEnvMut, - napi_env: i32, - error: i32, - source_line_ptr: i32, - thrown_at_ptr: i32, -) -> i32 { - let env_handle = snapi_env(&env, napi_env); - let error_id = if error > 0 { error as u32 } else { 0 }; - let mut source_line_id = 0u32; - let mut thrown_at_id = 0u32; - let status = unsafe { - snapi_bridge_unofficial_take_preserved_error_formatting( - env_handle, - error_id, - &mut source_line_id, - &mut thrown_at_id, - ) - }; - if status == 0 { - if source_line_ptr > 0 { - write_guest_u32(&mut env, source_line_ptr as u32, source_line_id); - } - if thrown_at_ptr > 0 { - write_guest_u32(&mut env, thrown_at_ptr as u32, thrown_at_id); - } - } - status + unsafe { snapi_bridge_unofficial_configure_source_maps(env_handle, enabled, callback_id) } } fn guest_unofficial_napi_preserve_error_source_message( @@ -1111,8 +870,19 @@ fn guest_unofficial_napi_get_heap_statistics( napi_env: i32, stats_ptr: i32, ) -> i32 { + let Some(size) = abi::read_output_header( + &mut env, + stats_ptr, + std::mem::size_of::(), + 1, + ) else { + return 1; + }; let env_handle = snapi_env(&env, napi_env); let mut stats = SnapiUnofficialHeapStatistics { + size, + version: 1, + valid_fields: 0, total_heap_size: 0, total_heap_size_executable: 0, total_physical_size: 0, @@ -1127,6 +897,7 @@ fn guest_unofficial_napi_get_heap_statistics( total_global_handles_size: 0, used_global_handles_size: 0, external_memory: 0, + array_buffer_memory: 0, }; let status = unsafe { snapi_bridge_unofficial_get_heap_statistics(env_handle, &mut stats) }; if status == 0 && stats_ptr > 0 && !write_guest_pod(&mut env, stats_ptr, &stats) { @@ -1135,43 +906,52 @@ fn guest_unofficial_napi_get_heap_statistics( status } -fn guest_unofficial_napi_get_heap_space_count( - mut env: FunctionEnvMut, - napi_env: i32, - count_ptr: i32, -) -> i32 { - let env_handle = snapi_env(&env, napi_env); - let mut count = 0u32; - let status = unsafe { snapi_bridge_unofficial_get_heap_space_count(env_handle, &mut count) }; - if status == 0 && count_ptr > 0 { - write_guest_u32(&mut env, count_ptr as u32, count); - } - status -} - fn guest_unofficial_napi_get_heap_space_statistics( mut env: FunctionEnvMut, napi_env: i32, - space_index: i32, stats_ptr: i32, + capacity: i32, + count_ptr: i32, ) -> i32 { + const MAX_HEAP_SPACE_CAPACITY: usize = 1024; + if capacity < 0 || count_ptr <= 0 || (capacity > 0 && stats_ptr <= 0) { + return 1; + } + let capacity = capacity as usize; + if capacity > MAX_HEAP_SPACE_CAPACITY { + return 1; + } let env_handle = snapi_env(&env, napi_env); - let mut stats = SnapiUnofficialHeapSpaceStatistics { - space_name: [0; 64], - space_size: 0, - space_used_size: 0, - space_available_size: 0, - physical_space_size: 0, - }; + let mut stats: Vec = (0..capacity) + .map(|_| SnapiUnofficialHeapSpaceStatistics { + space_name: [0; 64], + space_size: 0, + space_used_size: 0, + space_available_size: 0, + physical_space_size: 0, + }) + .collect(); + let mut count = 0u32; let status = unsafe { snapi_bridge_unofficial_get_heap_space_statistics( env_handle, - space_index.max(0) as u32, - &mut stats, + if stats.is_empty() { + std::ptr::null_mut() + } else { + stats.as_mut_ptr() + }, + capacity as u32, + &mut count, ) }; - if status == 0 && stats_ptr > 0 && !write_guest_pod(&mut env, stats_ptr, &stats) { - return 1; + if status == 0 { + if !write_guest_u32(&mut env, count_ptr as u32, count) { + return 1; + } + let written = capacity.min(count as usize); + if !write_guest_pod_slice(&mut env, stats_ptr, &stats[..written]) { + return 1; + } } status } @@ -1196,55 +976,28 @@ fn guest_unofficial_napi_get_heap_code_statistics( status } -fn guest_unofficial_napi_set_stack_limit( - _env: FunctionEnvMut, - _napi_env: i32, - _stack_limit: i32, -) -> i32 { - // The guest passes a Wasm linear-memory address here, which is not a native host stack - // address. Treat this as a no-op in the Wasm-hosted runner. - 0 -} - -fn guest_unofficial_napi_set_near_heap_limit_callback( +fn guest_unofficial_napi_configure_near_heap_limit_callback( _env: FunctionEnvMut, _napi_env: i32, _callback: i32, _data: i32, + _restored_heap_limit: i32, ) -> i32 { 0 } -fn guest_unofficial_napi_remove_near_heap_limit_callback( - _env: FunctionEnvMut, - _napi_env: i32, - _heap_limit: i32, -) -> i32 { - 0 -} - -fn guest_unofficial_napi_free_buffer(env: FunctionEnvMut, data: i32) { - // Buffers handed to the guest by the host (profile/snapshot JSON copies) - // come from the guest heap; the guest returns them here. Validation inside - // free_offset makes a bogus or double free a rejected no-op. - if data > 0 - && let Some(heap) = env.data().guest_heap.clone() - { - heap.free_offset(data as u32); - } -} - -fn guest_unofficial_napi_start_cpu_profile( +fn guest_unofficial_napi_profile_start( mut env: FunctionEnvMut, napi_env: i32, + kind: i32, result_ptr: i32, - profile_id_ptr: i32, + profile_ptr: i32, ) -> i32 { let env_handle = snapi_env(&env, napi_env); let mut result = 0i32; - let mut profile_id = 0u32; + let mut profile = 0u32; let status = unsafe { - snapi_bridge_unofficial_start_cpu_profile(env_handle, &mut result, &mut profile_id) + snapi_bridge_unofficial_profile_start(env_handle, kind, &mut result, &mut profile) }; if status != 0 { return status; @@ -1252,82 +1005,30 @@ fn guest_unofficial_napi_start_cpu_profile( if result_ptr > 0 { write_guest_i32(&mut env, result_ptr as u32, result); } - if profile_id_ptr > 0 { - write_guest_u32(&mut env, profile_id_ptr as u32, profile_id); + if profile_ptr > 0 { + write_guest_u32(&mut env, profile_ptr as u32, profile); } 0 } -fn guest_unofficial_napi_stop_cpu_profile( - mut env: FunctionEnvMut, - napi_env: i32, - profile_id: i32, - found_ptr: i32, - json_ptr: i32, - json_len_ptr: i32, -) -> i32 { - let env_handle = snapi_env(&env, napi_env); - let mut found = 0i32; - let mut host_ptr = 0u64; - let mut host_len = 0u32; - let status = unsafe { - snapi_bridge_unofficial_stop_cpu_profile( - env_handle, - profile_id.max(0) as u32, - &mut found, - &mut host_ptr, - &mut host_len, - ) - }; - if status != 0 { - return status; - } - if found_ptr > 0 { - write_guest_u8(&mut env, found_ptr as u32, (found != 0) as u8); - } - copy_host_buffer_to_guest(&mut env, json_ptr, json_len_ptr, host_ptr, host_len) -} - -fn guest_unofficial_napi_start_heap_profile( - mut env: FunctionEnvMut, - napi_env: i32, - started_ptr: i32, -) -> i32 { - let env_handle = snapi_env(&env, napi_env); - let mut started = 0i32; - let status = unsafe { snapi_bridge_unofficial_start_heap_profile(env_handle, &mut started) }; - if status == 0 && started_ptr > 0 { - write_guest_u8(&mut env, started_ptr as u32, (started != 0) as u8); - } - status -} - -fn guest_unofficial_napi_stop_heap_profile( +fn guest_unofficial_napi_profile_stop( mut env: FunctionEnvMut, napi_env: i32, - found_ptr: i32, + profile: i32, json_ptr: i32, - json_len_ptr: i32, ) -> i32 { let env_handle = snapi_env(&env, napi_env); - let mut found = 0i32; - let mut host_ptr = 0u64; - let mut host_len = 0u32; + let mut json = 0u32; let status = unsafe { - snapi_bridge_unofficial_stop_heap_profile( - env_handle, - &mut found, - &mut host_ptr, - &mut host_len, - ) + snapi_bridge_unofficial_profile_stop(env_handle, profile.max(0) as u32, &mut json) }; if status != 0 { return status; } - if found_ptr > 0 { - write_guest_u8(&mut env, found_ptr as u32, (found != 0) as u8); + if json_ptr > 0 && !write_guest_u32(&mut env, json_ptr as u32, json) { + return 1; } - copy_host_buffer_to_guest(&mut env, json_ptr, json_len_ptr, host_ptr, host_len) + 0 } fn guest_unofficial_napi_take_heap_snapshot( @@ -1335,7 +1036,6 @@ fn guest_unofficial_napi_take_heap_snapshot( napi_env: i32, options_ptr: i32, json_ptr: i32, - json_len_ptr: i32, ) -> i32 { let env_handle = snapi_env(&env, napi_env); let (expose_internals, expose_numeric_values) = if options_ptr > 0 { @@ -1346,21 +1046,22 @@ fn guest_unofficial_napi_take_heap_snapshot( } else { (0, 0) }; - let mut host_ptr = 0u64; - let mut host_len = 0u32; + let mut json = 0u32; let status = unsafe { snapi_bridge_unofficial_take_heap_snapshot( env_handle, expose_internals, expose_numeric_values, - &mut host_ptr, - &mut host_len, + &mut json, ) }; if status != 0 { return status; } - copy_host_buffer_to_guest(&mut env, json_ptr, json_len_ptr, host_ptr, host_len) + if json_ptr > 0 && !write_guest_u32(&mut env, json_ptr as u32, json) { + return 1; + } + 0 } fn guest_unofficial_napi_create_serdes_binding( @@ -1487,7 +1188,9 @@ fn guest_unofficial_napi_contextify_run_script( host_defined_option_id: i32, result_ptr: i32, ) -> i32 { - let (source_text, source_bytecode) = read_guest_js_source(&mut env, source); + let Some((source_text, source_bytecode)) = abi::read_js_source(&mut env, source) else { + return 1; + }; let env_handle = snapi_env(&env, napi_env); let mut result_id = 0u32; let status = unsafe { @@ -1498,16 +1201,8 @@ fn guest_unofficial_napi_contextify_run_script( } else { 0 }, - if source_text > 0 { - source_text as u32 - } else { - 0 - }, - if source_bytecode > 0 { - source_bytecode as u32 - } else { - 0 - }, + source_text, + source_bytecode, filename as u32, line_offset, column_offset, @@ -1529,20 +1224,6 @@ fn guest_unofficial_napi_contextify_run_script( status } -fn guest_unofficial_napi_contextify_dispose_context( - env: FunctionEnvMut, - napi_env: i32, - sandbox_or_context_global: i32, -) -> i32 { - let env_handle = snapi_env(&env, napi_env); - unsafe { - snapi_bridge_unofficial_contextify_dispose_context( - env_handle, - sandbox_or_context_global as u32, - ) - } -} - #[allow(clippy::too_many_arguments)] fn guest_unofficial_napi_contextify_compile_function( mut env: FunctionEnvMut, @@ -1557,115 +1238,29 @@ fn guest_unofficial_napi_contextify_compile_function( host_defined_option_id: i32, result_ptr: i32, ) -> i32 { - let (source_text, source_bytecode) = read_guest_js_source(&mut env, source); + let Some((source_text, source_bytecode)) = abi::read_js_source(&mut env, source) else { + return 1; + }; let env_handle = snapi_env(&env, napi_env); let mut result_id = 0u32; let status = unsafe { snapi_bridge_unofficial_contextify_compile_function( env_handle, - if source_text > 0 { - source_text as u32 - } else { - 0 - }, - if source_bytecode > 0 { - source_bytecode as u32 - } else { - 0 - }, + source_text, + source_bytecode, filename as u32, line_offset, column_offset, - if parsing_context_or_undefined > 0 { - parsing_context_or_undefined as u32 - } else { - 0 - }, - if context_extensions_or_undefined > 0 { - context_extensions_or_undefined as u32 - } else { - 0 - }, - if params_or_undefined > 0 { - params_or_undefined as u32 - } else { - 0 - }, - if host_defined_option_id > 0 { - host_defined_option_id as u32 - } else { - 0 - }, - &mut result_id, - ) - }; - if status == 0 && result_ptr > 0 { - write_guest_u32(&mut env, result_ptr as u32, result_id); - } - status -} - -#[allow(clippy::too_many_arguments)] -fn guest_unofficial_napi_contextify_compile_function_for_cjs_loader( - mut env: FunctionEnvMut, - napi_env: i32, - source: i32, - filename: i32, - is_sea_main: i32, - should_detect_module: i32, - result_ptr: i32, -) -> i32 { - let (source_text, source_bytecode) = read_guest_js_source(&mut env, source); - let env_handle = snapi_env(&env, napi_env); - let mut result_id = 0u32; - let status = unsafe { - snapi_bridge_unofficial_contextify_compile_function_for_cjs_loader( - env_handle, - if source_text > 0 { - source_text as u32 - } else { - 0 - }, - if source_bytecode > 0 { - source_bytecode as u32 - } else { - 0 - }, - filename as u32, - is_sea_main, - should_detect_module, - &mut result_id, - ) - }; - if status == 0 && result_ptr > 0 { - write_guest_u32(&mut env, result_ptr as u32, result_id); - } - status -} - -#[allow(clippy::too_many_arguments)] -fn guest_unofficial_napi_bytecode_compile( - mut env: FunctionEnvMut, - napi_env: i32, - source_text: i32, - filename: i32, - shape: i32, - params_or_undefined: i32, - host_defined_option_id: i32, - line_offset: i32, - column_offset: i32, - bytecode_ptr: i32, - can_parse_as_module_ptr: i32, -) -> i32 { - let env_handle = snapi_env(&env, napi_env); - let mut bytecode_id = 0u32; - let mut can_parse_as_module = 0u8; - let status = unsafe { - snapi_bridge_unofficial_bytecode_compile( - env_handle, - source_text as u32, - filename as u32, - shape, + if parsing_context_or_undefined > 0 { + parsing_context_or_undefined as u32 + } else { + 0 + }, + if context_extensions_or_undefined > 0 { + context_extensions_or_undefined as u32 + } else { + 0 + }, if params_or_undefined > 0 { params_or_undefined as u32 } else { @@ -1676,76 +1271,57 @@ fn guest_unofficial_napi_bytecode_compile( } else { 0 }, - line_offset, - column_offset, - &mut bytecode_id, - &mut can_parse_as_module, + &mut result_id, ) }; - if can_parse_as_module_ptr > 0 { - write_guest_u8( - &mut env, - can_parse_as_module_ptr as u32, - can_parse_as_module, - ); - } - if status == 0 && bytecode_ptr > 0 { - write_guest_u32(&mut env, bytecode_ptr as u32, bytecode_id); + if status == 0 && result_ptr > 0 { + write_guest_u32(&mut env, result_ptr as u32, result_id); } status } -#[allow(clippy::too_many_arguments)] -fn guest_unofficial_napi_bytecode_deserialize( +fn guest_unofficial_napi_bytecode_open( mut env: FunctionEnvMut, napi_env: i32, - bytes_ptr: i32, - byte_length: i32, - source_text: i32, - filename: i32, - shape: i32, - params_or_undefined: i32, - host_defined_option_id: i32, - bytecode_ptr: i32, - rejected_ptr: i32, + options_ptr: i32, + result_ptr: i32, ) -> i32 { - let env_handle = snapi_env(&env, napi_env); - if bytes_ptr <= 0 || byte_length <= 0 { - return 1; // napi_invalid_arg - } - let Some(bytes) = read_guest_bytes(&mut env, bytes_ptr, byte_length as usize) else { - return 1; // napi_invalid_arg + let Some(options) = abi::read_bytecode_open(&mut env, options_ptr) else { + return 1; }; + if result_ptr <= 0 { + return 1; + } + let env_handle = snapi_env(&env, napi_env); let mut bytecode_id = 0u32; - let mut rejected = 0u8; + let mut cache_rejected = 0u8; + let mut can_parse_as_module = 0u8; let status = unsafe { - snapi_bridge_unofficial_bytecode_deserialize( + snapi_bridge_unofficial_bytecode_open( env_handle, - bytes.as_ptr(), - bytes.len(), - source_text as u32, - filename as u32, - shape, - if params_or_undefined > 0 { - params_or_undefined as u32 - } else { - 0 - }, - if host_defined_option_id > 0 { - host_defined_option_id as u32 + options.source_text, + options.filename, + options.shape, + options.params_or_undefined, + options.host_defined_option_id, + options.line_offset, + options.column_offset, + if options.has_cache != 0 { + options.cache.as_ptr() } else { - 0 + std::ptr::null() }, + options.cache.len(), + options.has_cache, + options.cache_policy, &mut bytecode_id, - &mut rejected, + &mut cache_rejected, + &mut can_parse_as_module, ) }; - if rejected_ptr > 0 { - write_guest_u8(&mut env, rejected_ptr as u32, rejected); - } - if status == 0 && bytecode_ptr > 0 { - write_guest_u32(&mut env, bytecode_ptr as u32, bytecode_id); - } + write_guest_u32(&mut env, result_ptr as u32, bytecode_id); + write_guest_u8(&mut env, result_ptr as u32 + 4, cache_rejected); + write_guest_u8(&mut env, result_ptr as u32 + 5, can_parse_as_module); status } @@ -1775,140 +1351,45 @@ fn guest_unofficial_napi_bytecode_release( unsafe { snapi_bridge_unofficial_bytecode_release(env_handle, bytecode as u32) } } -fn guest_unofficial_napi_contextify_start_sigint_watchdog( - mut env: FunctionEnvMut, - napi_env: i32, - result_ptr: i32, -) -> i32 { - let env_handle = snapi_env(&env, napi_env); - let mut result = 0i32; - let status = unsafe { - snapi_bridge_unofficial_contextify_start_sigint_watchdog(env_handle, &mut result) - }; - if status == 0 && result_ptr > 0 { - write_guest_u8(&mut env, result_ptr as u32, (result != 0) as u8); - } - status -} - -fn guest_unofficial_napi_contextify_stop_sigint_watchdog( - mut env: FunctionEnvMut, - napi_env: i32, - had_pending_signal_ptr: i32, -) -> i32 { - let env_handle = snapi_env(&env, napi_env); - let mut had_pending_signal = 0i32; - let status = unsafe { - snapi_bridge_unofficial_contextify_stop_sigint_watchdog(env_handle, &mut had_pending_signal) - }; - if status == 0 && had_pending_signal_ptr > 0 { - write_guest_u8( - &mut env, - had_pending_signal_ptr as u32, - (had_pending_signal != 0) as u8, - ); - } - status -} - -fn guest_unofficial_napi_contextify_watchdog_has_pending_sigint( +fn guest_unofficial_napi_module_wrap_create( mut env: FunctionEnvMut, napi_env: i32, + options_ptr: i32, result_ptr: i32, ) -> i32 { - let env_handle = snapi_env(&env, napi_env); - let mut result = 0i32; - let status = unsafe { - snapi_bridge_unofficial_contextify_watchdog_has_pending_sigint(env_handle, &mut result) - }; - if status == 0 && result_ptr > 0 { - write_guest_u8(&mut env, result_ptr as u32, (result != 0) as u8); - } - status -} - -#[allow(clippy::too_many_arguments)] -fn guest_unofficial_napi_module_wrap_create_source_text( - mut env: FunctionEnvMut, - napi_env: i32, - wrapper: i32, - url: i32, - context_or_undefined: i32, - source: i32, - line_offset: i32, - column_offset: i32, - host_defined_option_id: i32, - handle_ptr: i32, -) -> i32 { - let (source_text, source_bytecode) = read_guest_js_source(&mut env, source); - let env_handle = snapi_env(&env, napi_env); - let mut handle_id = 0u32; - let status = unsafe { - snapi_bridge_unofficial_module_wrap_create_source_text( - env_handle, - wrapper as u32, - url as u32, - if context_or_undefined > 0 { - context_or_undefined as u32 - } else { - 0 - }, - if source_text > 0 { - source_text as u32 - } else { - 0 - }, - if source_bytecode > 0 { - source_bytecode as u32 - } else { - 0 - }, - line_offset, - column_offset, - if host_defined_option_id > 0 { - host_defined_option_id as u32 - } else { - 0 - }, - &mut handle_id, - ) + let Some(options) = abi::read_module_create(&mut env, options_ptr) else { + return 1; }; - if status == 0 && handle_ptr > 0 { - write_guest_u32(&mut env, handle_ptr as u32, handle_id); + if result_ptr <= 0 { + return 1; } - status -} - -#[allow(clippy::too_many_arguments)] -fn guest_unofficial_napi_module_wrap_create_synthetic( - mut env: FunctionEnvMut, - napi_env: i32, - wrapper: i32, - url: i32, - context_or_undefined: i32, - export_names: i32, - synthetic_eval_steps: i32, - handle_ptr: i32, -) -> i32 { let env_handle = snapi_env(&env, napi_env); let mut handle_id = 0u32; + let mut requests_id = 0u32; + let mut has_top_level_await = 0u8; let status = unsafe { - snapi_bridge_unofficial_module_wrap_create_synthetic( + snapi_bridge_unofficial_module_wrap_create( env_handle, - wrapper as u32, - url as u32, - if context_or_undefined > 0 { - context_or_undefined as u32 - } else { - 0 - }, - export_names as u32, - synthetic_eval_steps as u32, + options.kind, + options.wrapper, + options.url, + options.context_or_undefined, + options.source_text, + options.source_bytecode, + options.line_offset, + options.column_offset, + options.host_defined_option_id, + options.export_names, + options.synthetic_eval_steps, &mut handle_id, + &mut requests_id, + &mut has_top_level_await, ) }; - if status == 0 && handle_ptr > 0 { - write_guest_u32(&mut env, handle_ptr as u32, handle_id); + if status == 0 { + write_guest_u32(&mut env, result_ptr as u32, handle_id); + write_guest_u32(&mut env, result_ptr as u32 + 4, requests_id); + write_guest_bytes(&mut env, result_ptr as u32 + 8, &[has_top_level_await]); } status } @@ -1922,27 +1403,6 @@ fn guest_unofficial_napi_module_wrap_destroy( unsafe { snapi_bridge_unofficial_module_wrap_destroy(env_handle, handle as u32) } } -fn guest_unofficial_napi_module_wrap_get_module_requests( - mut env: FunctionEnvMut, - napi_env: i32, - handle: i32, - result_ptr: i32, -) -> i32 { - let env_handle = snapi_env(&env, napi_env); - let mut result_id = 0u32; - let status = unsafe { - snapi_bridge_unofficial_module_wrap_get_module_requests( - env_handle, - handle as u32, - &mut result_id, - ) - }; - if status == 0 && result_ptr > 0 { - write_guest_u32(&mut env, result_ptr as u32, result_id); - } - status -} - fn guest_unofficial_napi_module_wrap_link( mut env: FunctionEnvMut, napi_env: i32, @@ -2050,74 +1510,53 @@ fn guest_unofficial_napi_module_wrap_get_namespace( status } -fn guest_unofficial_napi_module_wrap_get_status( +fn guest_unofficial_napi_module_wrap_get_state( mut env: FunctionEnvMut, napi_env: i32, handle: i32, status_ptr: i32, + error_ptr: i32, + has_async_graph_ptr: i32, ) -> i32 { let env_handle = snapi_env(&env, napi_env); let mut status_val = 0i32; + let mut error_id = 0u32; + let mut has_async_graph = 0i32; let status = unsafe { - snapi_bridge_unofficial_module_wrap_get_status(env_handle, handle as u32, &mut status_val) - }; - if status == 0 && status_ptr > 0 { - write_guest_i32(&mut env, status_ptr as u32, status_val); - } - status -} - -fn guest_unofficial_napi_module_wrap_get_error( - mut env: FunctionEnvMut, - napi_env: i32, - handle: i32, - result_ptr: i32, -) -> i32 { - let env_handle = snapi_env(&env, napi_env); - let mut result_id = 0u32; - let status = unsafe { - snapi_bridge_unofficial_module_wrap_get_error(env_handle, handle as u32, &mut result_id) - }; - if status == 0 && result_ptr > 0 { - write_guest_u32(&mut env, result_ptr as u32, result_id); - } - status -} - -fn guest_unofficial_napi_module_wrap_has_top_level_await( - mut env: FunctionEnvMut, - napi_env: i32, - handle: i32, - result_ptr: i32, -) -> i32 { - let env_handle = snapi_env(&env, napi_env); - let mut result = 0i32; - let status = unsafe { - snapi_bridge_unofficial_module_wrap_has_top_level_await( + snapi_bridge_unofficial_module_wrap_get_state( env_handle, handle as u32, - &mut result, + if status_ptr > 0 { + &mut status_val + } else { + std::ptr::null_mut() + }, + if error_ptr > 0 { + &mut error_id + } else { + std::ptr::null_mut() + }, + if has_async_graph_ptr > 0 { + &mut has_async_graph + } else { + std::ptr::null_mut() + }, ) }; - if status == 0 && result_ptr > 0 { - write_guest_u8(&mut env, result_ptr as u32, (result != 0) as u8); - } - status -} - -fn guest_unofficial_napi_module_wrap_has_async_graph( - mut env: FunctionEnvMut, - napi_env: i32, - handle: i32, - result_ptr: i32, -) -> i32 { - let env_handle = snapi_env(&env, napi_env); - let mut result = 0i32; - let status = unsafe { - snapi_bridge_unofficial_module_wrap_has_async_graph(env_handle, handle as u32, &mut result) - }; - if status == 0 && result_ptr > 0 { - write_guest_u8(&mut env, result_ptr as u32, (result != 0) as u8); + if status == 0 { + if status_ptr > 0 { + write_guest_i32(&mut env, status_ptr as u32, status_val); + } + if error_ptr > 0 { + write_guest_u32(&mut env, error_ptr as u32, error_id); + } + if has_async_graph_ptr > 0 { + write_guest_u8( + &mut env, + has_async_graph_ptr as u32, + (has_async_graph != 0) as u8, + ); + } } status } @@ -2125,21 +1564,17 @@ fn guest_unofficial_napi_module_wrap_has_async_graph( fn guest_unofficial_napi_module_wrap_check_unsettled_top_level_await( mut env: FunctionEnvMut, napi_env: i32, - module_wrap: i32, + handle: i32, warnings: i32, settled_ptr: i32, ) -> i32 { let env_handle = snapi_env(&env, napi_env); - let module_wrap_id = if module_wrap > 0 { - module_wrap as u32 - } else { - 0 - }; + let handle_id = if handle > 0 { handle as u32 } else { 0 }; let mut settled = 0i32; let status = unsafe { snapi_bridge_unofficial_module_wrap_check_unsettled_top_level_await( env_handle, - module_wrap_id, + handle_id, warnings, &mut settled, ) @@ -2234,64 +1669,24 @@ fn guest_unofficial_napi_module_wrap_create_cached_data( status } -fn guest_unofficial_napi_module_wrap_set_import_module_dynamically_callback( - env: FunctionEnvMut, - napi_env: i32, - callback: i32, -) -> i32 { - let env_handle = snapi_env(&env, napi_env); - unsafe { - snapi_bridge_unofficial_module_wrap_set_import_module_dynamically_callback( - env_handle, - if callback > 0 { callback as u32 } else { 0 }, - ) - } -} - -fn guest_unofficial_napi_module_wrap_set_initialize_import_meta_object_callback( - env: FunctionEnvMut, - napi_env: i32, - callback: i32, -) -> i32 { - let env_handle = snapi_env(&env, napi_env); - unsafe { - snapi_bridge_unofficial_module_wrap_set_initialize_import_meta_object_callback( - env_handle, - if callback > 0 { callback as u32 } else { 0 }, - ) - } -} - -fn guest_unofficial_napi_module_wrap_import_module_dynamically( +fn guest_unofficial_napi_module_wrap_set_hooks( mut env: FunctionEnvMut, napi_env: i32, - argc: i32, - argv_ptr: i32, - result_ptr: i32, + hooks_ptr: i32, ) -> i32 { - let env_handle = snapi_env(&env, napi_env); - let argc_u = argc as u32; - let argv_ids = if argc_u > 0 { - let Some(ids) = read_guest_u32_array(&mut env, argv_ptr, argc_u as usize) else { - return 1; - }; - ids - } else { - Vec::new() + let Some((dynamic_import, initialize_import_meta)) = + abi::read_module_hooks(&mut env, hooks_ptr) + else { + return 1; }; - let mut result_id = 0u32; - let status = unsafe { - snapi_bridge_unofficial_module_wrap_import_module_dynamically( + let env_handle = snapi_env(&env, napi_env); + unsafe { + snapi_bridge_unofficial_module_wrap_set_hooks( env_handle, - argc_u, - argv_ids.as_ptr(), - &mut result_id, + dynamic_import, + initialize_import_meta, ) - }; - if status == 0 && result_ptr > 0 { - write_guest_u32(&mut env, result_ptr as u32, result_id); } - status } fn guest_unofficial_napi_module_wrap_create_required_module_facade( @@ -5581,104 +4976,72 @@ pub fn register_napi_imports( }; let napi_extension_wasmer_namespace = namespace! { - "unofficial_napi_set_flags_from_string" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_set_flags_from_string), + "unofficial_napi_configure_runtime" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_configure_runtime), "unofficial_napi_create_env" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_create_env), - "unofficial_napi_create_env_with_options" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_create_env_with_options), - "unofficial_napi_set_embedder_hooks" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_set_embedder_hooks), + "unofficial_napi_attach_env" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_attach_env), "unofficial_napi_release_env" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_release_env), - "unofficial_napi_release_env_with_loop" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_release_env_with_loop), - "unofficial_napi_low_memory_notification" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_low_memory_notification), + "unofficial_napi_collect_garbage" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_collect_garbage), "unofficial_napi_event_loop_checkpoint" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_event_loop_checkpoint), "unofficial_napi_create_uninitialized_arraybuffer" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_create_uninitialized_arraybuffer), "unofficial_napi_acquire_buffer_lease" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_acquire_buffer_lease), "unofficial_napi_release_buffer_lease" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_release_buffer_lease), "unofficial_napi_create_guest_backed_typedarray" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_create_guest_backed_typedarray), - "unofficial_napi_request_gc_for_testing" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_request_gc_for_testing), "unofficial_napi_set_prepare_stack_trace_callback" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_set_prepare_stack_trace_callback), "unofficial_napi_get_promise_details" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_get_promise_details), "unofficial_napi_get_proxy_details" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_get_proxy_details), "unofficial_napi_preview_entries" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_preview_entries), "unofficial_napi_get_call_sites" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_get_call_sites), - "unofficial_napi_get_current_stack_trace" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_get_current_stack_trace), - "unofficial_napi_get_caller_location" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_get_caller_location), "unofficial_napi_arraybuffer_view_has_buffer" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_arraybuffer_view_has_buffer), "unofficial_napi_get_constructor_name" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_get_constructor_name), "unofficial_napi_create_private_symbol" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_create_private_symbol), "unofficial_napi_get_continuation_preserved_embedder_data" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_get_continuation_preserved_embedder_data), "unofficial_napi_set_continuation_preserved_embedder_data" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_set_continuation_preserved_embedder_data), "unofficial_napi_notify_datetime_configuration_change" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_notify_datetime_configuration_change), - "unofficial_napi_set_enqueue_foreground_task_callback" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_set_enqueue_foreground_task_callback), - "unofficial_napi_set_fatal_error_callbacks" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_set_fatal_error_callbacks), "unofficial_napi_terminate_execution" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_terminate_execution), "unofficial_napi_cancel_terminate_execution" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_cancel_terminate_execution), "unofficial_napi_request_interrupt" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_request_interrupt), "unofficial_napi_structured_clone" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_structured_clone), - "unofficial_napi_structured_clone_with_transfer" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_structured_clone_with_transfer), - "unofficial_napi_serialize_value" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_serialize_value), - "unofficial_napi_deserialize_value" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_deserialize_value), - "unofficial_napi_release_serialized_value" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_release_serialized_value), + "unofficial_napi_message_create" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_message_create), + "unofficial_napi_message_take" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_message_take), + "unofficial_napi_message_drop" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_message_drop), "unofficial_napi_enqueue_microtask" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_enqueue_microtask), "unofficial_napi_set_promise_reject_callback" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_set_promise_reject_callback), - "unofficial_napi_set_promise_hooks" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_set_promise_hooks), "unofficial_napi_get_own_non_index_properties" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_get_own_non_index_properties), - "unofficial_napi_get_process_memory_info" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_get_process_memory_info), + "unofficial_napi_set_promise_hooks" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_set_promise_hooks), "unofficial_napi_get_hash_seed" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_get_hash_seed), - "unofficial_napi_get_error_source_positions" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_get_error_source_positions), - "unofficial_napi_set_source_maps_enabled" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_set_source_maps_enabled), - "unofficial_napi_set_get_source_map_error_source_callback" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_set_get_source_map_error_source_callback), - "unofficial_napi_get_error_source_line_for_stderr" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_get_error_source_line_for_stderr), - "unofficial_napi_get_error_thrown_at" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_get_error_thrown_at), - "unofficial_napi_take_preserved_error_formatting" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_take_preserved_error_formatting), + "unofficial_napi_get_error_metadata" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_get_error_metadata), + "unofficial_napi_configure_source_maps" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_configure_source_maps), "unofficial_napi_preserve_error_source_message" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_preserve_error_source_message), "unofficial_napi_mark_promise_as_handled" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_mark_promise_as_handled), "unofficial_napi_get_heap_statistics" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_get_heap_statistics), - "unofficial_napi_get_heap_space_count" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_get_heap_space_count), "unofficial_napi_get_heap_space_statistics" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_get_heap_space_statistics), "unofficial_napi_get_heap_code_statistics" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_get_heap_code_statistics), - "unofficial_napi_set_stack_limit" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_set_stack_limit), - "unofficial_napi_set_near_heap_limit_callback" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_set_near_heap_limit_callback), - "unofficial_napi_remove_near_heap_limit_callback" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_remove_near_heap_limit_callback), - "unofficial_napi_free_buffer" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_free_buffer), - "unofficial_napi_start_cpu_profile" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_start_cpu_profile), - "unofficial_napi_stop_cpu_profile" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_stop_cpu_profile), - "unofficial_napi_start_heap_profile" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_start_heap_profile), - "unofficial_napi_stop_heap_profile" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_stop_heap_profile), + "unofficial_napi_configure_near_heap_limit_callback" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_configure_near_heap_limit_callback), + "unofficial_napi_profile_start" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_profile_start), + "unofficial_napi_profile_stop" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_profile_stop), "unofficial_napi_take_heap_snapshot" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_take_heap_snapshot), "unofficial_napi_create_serdes_binding" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_create_serdes_binding), "unofficial_napi_contextify_contains_module_syntax" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_contextify_contains_module_syntax), "unofficial_napi_contextify_make_context" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_contextify_make_context), "unofficial_napi_contextify_run_script" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_contextify_run_script), - "unofficial_napi_contextify_dispose_context" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_contextify_dispose_context), "unofficial_napi_contextify_compile_function" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_contextify_compile_function), - "unofficial_napi_contextify_compile_function_for_cjs_loader" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_contextify_compile_function_for_cjs_loader), - "unofficial_napi_bytecode_compile" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_bytecode_compile), - "unofficial_napi_bytecode_deserialize" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_bytecode_deserialize), + "unofficial_napi_bytecode_open" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_bytecode_open), "unofficial_napi_bytecode_serialize" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_bytecode_serialize), "unofficial_napi_bytecode_release" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_bytecode_release), - "unofficial_napi_contextify_start_sigint_watchdog" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_contextify_start_sigint_watchdog), - "unofficial_napi_contextify_stop_sigint_watchdog" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_contextify_stop_sigint_watchdog), - "unofficial_napi_contextify_watchdog_has_pending_sigint" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_contextify_watchdog_has_pending_sigint), - "unofficial_napi_module_wrap_create_source_text" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_module_wrap_create_source_text), - "unofficial_napi_module_wrap_create_synthetic" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_module_wrap_create_synthetic), + "unofficial_napi_module_wrap_create" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_module_wrap_create), "unofficial_napi_module_wrap_destroy" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_module_wrap_destroy), - "unofficial_napi_module_wrap_get_module_requests" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_module_wrap_get_module_requests), "unofficial_napi_module_wrap_link" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_module_wrap_link), "unofficial_napi_module_wrap_instantiate" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_module_wrap_instantiate), "unofficial_napi_module_wrap_evaluate" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_module_wrap_evaluate), "unofficial_napi_module_wrap_evaluate_sync" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_module_wrap_evaluate_sync), "unofficial_napi_module_wrap_get_namespace" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_module_wrap_get_namespace), - "unofficial_napi_module_wrap_get_status" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_module_wrap_get_status), - "unofficial_napi_module_wrap_get_error" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_module_wrap_get_error), - "unofficial_napi_module_wrap_has_top_level_await" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_module_wrap_has_top_level_await), - "unofficial_napi_module_wrap_has_async_graph" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_module_wrap_has_async_graph), + "unofficial_napi_module_wrap_get_state" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_module_wrap_get_state), "unofficial_napi_module_wrap_check_unsettled_top_level_await" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_module_wrap_check_unsettled_top_level_await), "unofficial_napi_module_wrap_set_export" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_module_wrap_set_export), "unofficial_napi_module_wrap_set_module_source_object" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_module_wrap_set_module_source_object), "unofficial_napi_module_wrap_get_module_source_object" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_module_wrap_get_module_source_object), "unofficial_napi_module_wrap_create_cached_data" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_module_wrap_create_cached_data), - "unofficial_napi_module_wrap_set_import_module_dynamically_callback" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_module_wrap_set_import_module_dynamically_callback), - "unofficial_napi_module_wrap_set_initialize_import_meta_object_callback" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_module_wrap_set_initialize_import_meta_object_callback), - "unofficial_napi_module_wrap_import_module_dynamically" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_module_wrap_import_module_dynamically), + "unofficial_napi_module_wrap_set_hooks" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_module_wrap_set_hooks), "unofficial_napi_module_wrap_create_required_module_facade" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_module_wrap_create_required_module_facade), }; diff --git a/src/guest/napi_js.rs b/src/guest/napi_js.rs index 49acbe5..b52a22b 100644 --- a/src/guest/napi_js.rs +++ b/src/guest/napi_js.rs @@ -27,13 +27,14 @@ use crate::{ snapi::*, }; -use super::util::*; +use super::{abi, util::*}; const JS_BACKING_TOKEN_MARKER: u64 = 1 << 63; const JS_BACKING_TOKEN_OFFSET_MASK: u64 = u32::MAX as u64; fn guest_napi_wasm_init_env(mut env: FunctionEnvMut) -> i32 { let _ = unsafe { snapi_bridge_init() }; + let _ = unsafe { snapi_bridge_unofficial_configure_runtime(std::ptr::null(), 0) }; if let Some(env_id) = env.data().default_napi_env_id { return env_id as i32; @@ -57,21 +58,6 @@ fn guest_napi_wasm_init_env(mut env: FunctionEnvMut) -> i32 { env_id as i32 } -fn guest_unofficial_napi_set_flags_from_string( - mut env: FunctionEnvMut, - flags_ptr: i32, - flags_len: i32, -) -> i32 { - if flags_ptr <= 0 || flags_len < 0 { - return 1; - } - let Some(flags_bytes) = read_guest_bytes(&mut env, flags_ptr, flags_len as usize) else { - return 1; - }; - let flags = CString::new(flags_bytes).unwrap_or_default(); - unsafe { snapi_bridge_unofficial_set_flags_from_string(flags.as_ptr(), flags_len as u32) } -} - fn with_cb_context( env: &mut FunctionEnvMut, guest_env: i32, @@ -146,11 +132,6 @@ fn host_backing_store_token(env: &FunctionEnvMut, guest_env: i32, handl if status == 0 { token } else { 0 } } -fn read_guest_js_source(env: &mut FunctionEnvMut, source_ptr: i32) -> Option<(u32, u32)> { - let fields = read_guest_u32_array(env, source_ptr, 2)?; - Some((fields[0], fields[1])) -} - fn write_guest_pod(env: &mut FunctionEnvMut, guest_ptr: i32, value: &T) -> bool { if guest_ptr <= 0 { return false; @@ -161,37 +142,21 @@ fn write_guest_pod(env: &mut FunctionEnvMut, guest_ptr: i32, value: write_guest_bytes(env, guest_ptr as u32, bytes) } -fn copy_host_buffer_to_guest( +fn write_guest_pod_slice( env: &mut FunctionEnvMut, - data_out_ptr: i32, - len_out_ptr: i32, - host_ptr: u64, - host_len: u32, -) -> i32 { - let mut guest_ptr = 0u32; - if host_ptr != 0 && host_len > 0 { - let host_slice = - unsafe { std::slice::from_raw_parts(host_ptr as *const u8, host_len as usize) }; - let Some(ptr) = allocate_guest_bytes(env, host_slice) else { - unsafe { snapi_bridge_unofficial_free_buffer(host_ptr as *mut c_void) }; - return 1; - }; - guest_ptr = ptr; - } - if data_out_ptr > 0 { - write_guest_u32(env, data_out_ptr as u32, guest_ptr); - } - if len_out_ptr > 0 { - write_guest_u32( - env, - len_out_ptr as u32, - if guest_ptr == 0 { 0 } else { host_len }, - ); + guest_ptr: i32, + values: &[T], +) -> bool { + if values.is_empty() { + return true; } - if host_ptr != 0 { - unsafe { snapi_bridge_unofficial_free_buffer(host_ptr as *mut c_void) }; + if guest_ptr <= 0 { + return false; } - 0 + let bytes = unsafe { + std::slice::from_raw_parts(values.as_ptr().cast::(), std::mem::size_of_val(values)) + }; + write_guest_bytes(env, guest_ptr as u32, bytes) } fn remember_guest_backing_store( @@ -520,40 +485,22 @@ fn resolve_current_host_value_to_guest( Some(guest_ptr) } -fn guest_unofficial_napi_create_env( +fn guest_unofficial_napi_configure_runtime( mut env: FunctionEnvMut, - module_api_version: i32, - env_out_ptr: i32, - scope_out_ptr: i32, + options_ptr: i32, ) -> i32 { - let mut snapi_env_state: SnapiEnv = std::ptr::null_mut(); - let status = unsafe { - snapi_bridge_unofficial_create_env( - module_api_version, - std::ptr::null(), - &mut snapi_env_state, - ) + let Some(flags) = abi::read_runtime_options(&mut env, options_ptr) else { + return 1; }; - if status != 0 { - return status; - } - #[cfg(all(target_arch = "wasm32", feature = "js"))] - let async_env = env.as_async_mut(); - let (env_id, scope_id) = env.data_mut().register_napi_env(snapi_env_state); - #[cfg(all(target_arch = "wasm32", feature = "js"))] - if let Some(async_env) = async_env { - install_persistent_callback_state(&mut env, env_id, snapi_env_state, async_env); - } - if env_out_ptr > 0 { - write_guest_u32(&mut env, env_out_ptr as u32, env_id); - } - if scope_out_ptr > 0 { - write_guest_u32(&mut env, scope_out_ptr as u32, scope_id); + let Ok(flags) = CString::new(flags) else { + return 1; + }; + unsafe { + snapi_bridge_unofficial_configure_runtime(flags.as_ptr(), flags.as_bytes().len() as u32) } - 0 } -fn guest_unofficial_napi_create_env_with_options( +fn guest_unofficial_napi_create_env( mut env: FunctionEnvMut, module_api_version: i32, options_ptr: i32, @@ -561,28 +508,34 @@ fn guest_unofficial_napi_create_env_with_options( scope_out_ptr: i32, ) -> i32 { let ( + total_memory, + constrained_memory, max_young_generation_size_in_bytes, max_old_generation_size_in_bytes, code_range_size_in_bytes, stack_limit, ) = if options_ptr > 0 { - let Some(bytes) = read_guest_bytes(&mut env, options_ptr, 16) else { + let Some(options) = abi::read_env_create(&mut env, options_ptr) else { return 1; }; ( - u32::from_le_bytes(bytes[0..4].try_into().unwrap()), - u32::from_le_bytes(bytes[4..8].try_into().unwrap()), - u32::from_le_bytes(bytes[8..12].try_into().unwrap()), - u32::from_le_bytes(bytes[12..16].try_into().unwrap()), + options.total_memory, + options.constrained_memory, + options.max_young_generation_size_in_bytes, + options.max_old_generation_size_in_bytes, + options.code_range_size_in_bytes, + options.stack_limit, ) } else { - (0, 0, 0, 0) + (0, 0, 0, 0, 0, 0) }; let mut snapi_env_state: SnapiEnv = std::ptr::null_mut(); let status = unsafe { snapi_bridge_unofficial_create_env_with_options( module_api_version, + total_memory, + constrained_memory, max_young_generation_size_in_bytes, max_old_generation_size_in_bytes, code_range_size_in_bytes, @@ -632,19 +585,7 @@ fn drain_env_guest_finalizers(env: &mut FunctionEnvMut, scope_id: u32) 0 } -fn guest_unofficial_napi_release_env(mut env: FunctionEnvMut, scope_ptr: i32) -> i32 { - let scope_id = if scope_ptr > 0 { scope_ptr as u32 } else { 0 }; - let status = drain_env_guest_finalizers(&mut env, scope_id); - if status != 0 { - return status; - } - let Some(snapi_env_state) = env.data_mut().unregister_napi_scope(scope_id) else { - return 1; - }; - unsafe { snapi_bridge_unofficial_release_env(snapi_env_state) } -} - -fn guest_unofficial_napi_release_env_with_loop( +fn guest_unofficial_napi_release_env( mut env: FunctionEnvMut, scope_ptr: i32, loop_ptr: i32, @@ -661,17 +602,37 @@ fn guest_unofficial_napi_release_env_with_loop( unsafe { snapi_bridge_unofficial_release_env_with_loop(snapi_env_state, loop_id) } } -fn guest_unofficial_napi_set_embedder_hooks(env: FunctionEnvMut, napi_env: i32) -> i32 { - let env_handle = snapi_env(&env, napi_env); - unsafe { snapi_bridge_unofficial_set_embedder_hooks(env_handle) } -} - -fn guest_unofficial_napi_low_memory_notification( - env: FunctionEnvMut, +fn guest_unofficial_napi_attach_env( + mut env: FunctionEnvMut, napi_env: i32, + hooks_ptr: i32, + accepted_hooks_ptr: i32, ) -> i32 { + let Some(hooks) = abi::read_env_hooks(&mut env, hooks_ptr) else { + return 1; + }; + if napi_env <= 0 || accepted_hooks_ptr <= 0 { + return 1; + } + let mut accepted_hooks = 0u64; + let status = unsafe { + snapi_bridge_unofficial_attach_env( + snapi_env(&env, napi_env), + hooks.fatal, + hooks.oom, + &mut accepted_hooks, + ) + }; + if status == 0 { + write_guest_u64(&mut env, accepted_hooks_ptr as u32, accepted_hooks); + debug_assert_eq!(accepted_hooks & !hooks.requested, 0); + } + status +} + +fn guest_unofficial_napi_collect_garbage(env: FunctionEnvMut, napi_env: i32) -> i32 { let env_handle = snapi_env(&env, napi_env); - unsafe { snapi_bridge_unofficial_low_memory_notification(env_handle) } + unsafe { snapi_bridge_unofficial_collect_garbage(env_handle) } } fn provider_event_loop_checkpoint_sync( @@ -813,14 +774,6 @@ fn event_loop_checkpoint_import( Function::new_typed_with_env(store, fe, guest_unofficial_napi_event_loop_checkpoint_sync) } -fn guest_unofficial_napi_request_gc_for_testing( - env: FunctionEnvMut, - napi_env: i32, -) -> i32 { - let env_handle = snapi_env(&env, napi_env); - unsafe { snapi_bridge_unofficial_request_gc_for_testing(env_handle) } -} - fn guest_unofficial_napi_get_promise_details( mut env: FunctionEnvMut, napi_env: i32, @@ -937,42 +890,6 @@ fn guest_unofficial_napi_get_call_sites( status } -fn guest_unofficial_napi_get_current_stack_trace( - mut env: FunctionEnvMut, - napi_env: i32, - frames: i32, - callsites_ptr: i32, -) -> i32 { - let env_handle = snapi_env(&env, napi_env); - let mut callsites_id = 0u32; - let status = unsafe { - snapi_bridge_unofficial_get_current_stack_trace( - env_handle, - frames as u32, - &mut callsites_id, - ) - }; - if status == 0 && callsites_ptr > 0 { - write_guest_u32(&mut env, callsites_ptr as u32, callsites_id); - } - status -} - -fn guest_unofficial_napi_get_caller_location( - mut env: FunctionEnvMut, - napi_env: i32, - location_ptr: i32, -) -> i32 { - let env_handle = snapi_env(&env, napi_env); - let mut location_id = 0u32; - let status = - unsafe { snapi_bridge_unofficial_get_caller_location(env_handle, &mut location_id) }; - if status == 0 && location_ptr > 0 { - write_guest_u32(&mut env, location_ptr as u32, location_id); - } - status -} - fn guest_unofficial_napi_arraybuffer_view_has_buffer( mut env: FunctionEnvMut, napi_env: i32, @@ -1087,36 +1004,6 @@ fn guest_unofficial_napi_notify_datetime_configuration_change( unsafe { snapi_bridge_unofficial_notify_datetime_configuration_change(env_handle) } } -fn guest_unofficial_napi_set_enqueue_foreground_task_callback( - env: FunctionEnvMut, - napi_env: i32, - _callback: i32, - _target: i32, -) -> i32 { - let env_handle = snapi_env(&env, napi_env); - unsafe { snapi_bridge_unofficial_set_enqueue_foreground_task_callback(env_handle) } -} - -fn guest_unofficial_napi_set_fatal_error_callbacks( - env: FunctionEnvMut, - napi_env: i32, - fatal_callback: i32, - oom_callback: i32, -) -> i32 { - let env_handle = snapi_env(&env, napi_env); - let fatal_id = if fatal_callback > 0 { - fatal_callback as u32 - } else { - 0 - }; - let oom_id = if oom_callback > 0 { - oom_callback as u32 - } else { - 0 - }; - unsafe { snapi_bridge_unofficial_set_fatal_error_callbacks(env_handle, fatal_id, oom_id) } -} - fn guest_unofficial_napi_terminate_execution(env: FunctionEnvMut, napi_env: i32) -> i32 { let env_handle = snapi_env(&env, napi_env); unsafe { snapi_bridge_unofficial_terminate_execution(env_handle) } @@ -1150,23 +1037,6 @@ fn guest_unofficial_napi_request_interrupt( } fn guest_unofficial_napi_structured_clone( - mut env: FunctionEnvMut, - napi_env: i32, - value: i32, - result_ptr: i32, -) -> i32 { - let env_handle = snapi_env(&env, napi_env); - let value_id = if value > 0 { value as u32 } else { 0 }; - let mut out = 0u32; - let status = - unsafe { snapi_bridge_unofficial_structured_clone(env_handle, value_id, &mut out) }; - if status == 0 && result_ptr > 0 { - write_guest_u32(&mut env, result_ptr as u32, out); - } - status -} - -fn guest_unofficial_napi_structured_clone_with_transfer( mut env: FunctionEnvMut, napi_env: i32, value: i32, @@ -1182,12 +1052,7 @@ fn guest_unofficial_napi_structured_clone_with_transfer( }; let mut out = 0u32; let status = unsafe { - snapi_bridge_unofficial_structured_clone_with_transfer( - env_handle, - value_id, - transfer_list_id, - &mut out, - ) + snapi_bridge_unofficial_structured_clone(env_handle, value_id, transfer_list_id, &mut out) }; if status == 0 && result_ptr > 0 { write_guest_u32(&mut env, result_ptr as u32, out); @@ -1195,16 +1060,19 @@ fn guest_unofficial_napi_structured_clone_with_transfer( status } -fn guest_unofficial_napi_serialize_value( +fn guest_unofficial_napi_message_create( mut env: FunctionEnvMut, napi_env: i32, value: i32, payload_out_ptr: i32, ) -> i32 { + if payload_out_ptr <= 0 { + return 1; + } let env_handle = snapi_env(&env, napi_env); let mut payload = 0u32; let status = - unsafe { snapi_bridge_unofficial_serialize_value(env_handle, value as u32, &mut payload) }; + unsafe { snapi_bridge_unofficial_message_create(env_handle, value as u32, &mut payload) }; if status == 0 && payload_out_ptr > 0 { write_guest_u32(&mut env, payload_out_ptr as u32, payload); } @@ -1212,12 +1080,19 @@ fn guest_unofficial_napi_serialize_value( } #[cfg(all(target_arch = "wasm32", feature = "js"))] -async fn guest_unofficial_napi_deserialize_value_async( +async fn guest_unofficial_napi_message_take_async( mut env: AsyncFunctionEnvMut, napi_env: i32, payload: i32, result_out_ptr: i32, ) -> i32 { + if payload <= 0 { + return 1; + } + if result_out_ptr <= 0 { + unsafe { snapi_bridge_unofficial_message_drop(payload as u32) }; + return 1; + } let env_handle = { let env = env.read().await; env.data().resolve_napi_env(napi_env) @@ -1227,12 +1102,12 @@ async fn guest_unofficial_napi_deserialize_value_async( .await .is_err() { + unsafe { snapi_bridge_unofficial_message_drop(payload as u32) }; return (9, 0); } let mut value = 0u32; - let status = unsafe { - snapi_bridge_unofficial_deserialize_value(env_handle, payload as u32, &mut value) - }; + let status = + unsafe { snapi_bridge_unofficial_message_take(env_handle, payload as u32, &mut value) }; (status, value) }) .await; @@ -1245,17 +1120,23 @@ async fn guest_unofficial_napi_deserialize_value_async( } #[cfg(not(all(target_arch = "wasm32", feature = "js")))] -fn guest_unofficial_napi_deserialize_value_sync( +fn guest_unofficial_napi_message_take_sync( mut env: FunctionEnvMut, napi_env: i32, payload: i32, result_out_ptr: i32, ) -> i32 { + if payload <= 0 { + return 1; + } + if result_out_ptr <= 0 { + unsafe { snapi_bridge_unofficial_message_drop(payload as u32) }; + return 1; + } let env_handle = snapi_env(&env, napi_env); let mut value = 0u32; - let status = unsafe { - snapi_bridge_unofficial_deserialize_value(env_handle, payload as u32, &mut value) - }; + let status = + unsafe { snapi_bridge_unofficial_message_take(env_handle, payload as u32, &mut value) }; if status == 0 && result_out_ptr > 0 { write_guest_u32(&mut env, result_out_ptr as u32, value); } @@ -1263,17 +1144,17 @@ fn guest_unofficial_napi_deserialize_value_sync( } #[cfg(all(target_arch = "wasm32", feature = "js"))] -fn deserialize_value_import(store: &mut impl AsStoreMut, fe: &FunctionEnv) -> Function { - Function::new_typed_with_env_async(store, fe, guest_unofficial_napi_deserialize_value_async) +fn message_take_import(store: &mut impl AsStoreMut, fe: &FunctionEnv) -> Function { + Function::new_typed_with_env_async(store, fe, guest_unofficial_napi_message_take_async) } #[cfg(not(all(target_arch = "wasm32", feature = "js")))] -fn deserialize_value_import(store: &mut impl AsStoreMut, fe: &FunctionEnv) -> Function { - Function::new_typed_with_env(store, fe, guest_unofficial_napi_deserialize_value_sync) +fn message_take_import(store: &mut impl AsStoreMut, fe: &FunctionEnv) -> Function { + Function::new_typed_with_env(store, fe, guest_unofficial_napi_message_take_sync) } -fn guest_unofficial_napi_release_serialized_value(_env: FunctionEnvMut, payload: i32) { - unsafe { snapi_bridge_unofficial_release_serialized_value(payload as u32) }; +fn guest_unofficial_napi_message_drop(_env: FunctionEnvMut, payload: i32) { + unsafe { snapi_bridge_unofficial_message_drop(payload as u32) }; } fn guest_unofficial_napi_enqueue_microtask( @@ -1296,6 +1177,32 @@ fn guest_unofficial_napi_set_promise_reject_callback( unsafe { snapi_bridge_unofficial_set_promise_reject_callback(env_handle, callback_id) } } +fn guest_unofficial_napi_get_own_non_index_properties( + mut env: FunctionEnvMut, + napi_env: i32, + value: i32, + filter_bits: i32, + result_out_ptr: i32, +) -> i32 { + if value <= 0 || result_out_ptr <= 0 { + return 1; + } + let env_handle = snapi_env(&env, napi_env); + let mut out = 0u32; + let status = unsafe { + snapi_bridge_unofficial_get_own_non_index_properties( + env_handle, + value as u32, + filter_bits.max(0) as u32, + &mut out, + ) + }; + if status == 0 { + write_guest_u32(&mut env, result_out_ptr as u32, out); + } + status +} + fn guest_unofficial_napi_set_promise_hooks( env: FunctionEnvMut, napi_env: i32, @@ -1332,70 +1239,6 @@ fn guest_unofficial_napi_set_promise_hooks( } } -fn guest_unofficial_napi_get_own_non_index_properties( - mut env: FunctionEnvMut, - napi_env: i32, - value: i32, - filter_bits: i32, - result_out_ptr: i32, -) -> i32 { - let env_handle = snapi_env(&env, napi_env); - let value_id = if value > 0 { value as u32 } else { 0 }; - let filter = if filter_bits > 0 { - filter_bits as u32 - } else { - 0 - }; - let mut out = 0u32; - let status = unsafe { - snapi_bridge_unofficial_get_own_non_index_properties(env_handle, value_id, filter, &mut out) - }; - if status == 0 && result_out_ptr > 0 { - write_guest_u32(&mut env, result_out_ptr as u32, out); - } - status -} - -fn guest_unofficial_napi_get_process_memory_info( - mut env: FunctionEnvMut, - napi_env: i32, - heap_total_out: i32, - heap_used_out: i32, - external_out: i32, - array_buffers_out: i32, -) -> i32 { - let env_handle = snapi_env(&env, napi_env); - let mut heap_total = 0.0f64; - let mut heap_used = 0.0f64; - let mut external = 0.0f64; - let mut array_buffers = 0.0f64; - let status = unsafe { - snapi_bridge_unofficial_get_process_memory_info( - env_handle, - &mut heap_total, - &mut heap_used, - &mut external, - &mut array_buffers, - ) - }; - if status != 0 { - return status; - } - if heap_total_out > 0 { - write_guest_f64(&mut env, heap_total_out as u32, heap_total); - } - if heap_used_out > 0 { - write_guest_f64(&mut env, heap_used_out as u32, heap_used); - } - if external_out > 0 { - write_guest_f64(&mut env, external_out as u32, external); - } - if array_buffers_out > 0 { - write_guest_f64(&mut env, array_buffers_out as u32, array_buffers); - } - 0 -} - fn guest_unofficial_napi_get_hash_seed( mut env: FunctionEnvMut, napi_env: i32, @@ -1410,25 +1253,31 @@ fn guest_unofficial_napi_get_hash_seed( status } -fn guest_unofficial_napi_get_error_source_positions( +fn guest_unofficial_napi_get_error_metadata( mut env: FunctionEnvMut, napi_env: i32, error: i32, - positions_ptr: i32, + mode: i32, + metadata_ptr: i32, ) -> i32 { let env_handle = snapi_env(&env, napi_env); let error_id = if error > 0 { error as u32 } else { 0 }; let mut source_line_id = 0u32; let mut script_resource_name_id = 0u32; + let mut stderr_line_id = 0u32; + let mut thrown_at_id = 0u32; let mut line_number = 0i32; let mut start_column = 0i32; let mut end_column = 0i32; let status = unsafe { - snapi_bridge_unofficial_get_error_source_positions( + snapi_bridge_unofficial_get_error_metadata( env_handle, error_id, + mode, &mut source_line_id, &mut script_resource_name_id, + &mut stderr_line_id, + &mut thrown_at_id, &mut line_number, &mut start_column, &mut end_column, @@ -1437,105 +1286,27 @@ fn guest_unofficial_napi_get_error_source_positions( if status != 0 { return status; } - if positions_ptr > 0 { - write_guest_u32(&mut env, positions_ptr as u32, source_line_id); - write_guest_u32(&mut env, positions_ptr as u32 + 4, script_resource_name_id); - write_guest_i32(&mut env, positions_ptr as u32 + 8, line_number); - write_guest_i32(&mut env, positions_ptr as u32 + 12, start_column); - write_guest_i32(&mut env, positions_ptr as u32 + 16, end_column); + if metadata_ptr > 0 { + write_guest_u32(&mut env, metadata_ptr as u32, source_line_id); + write_guest_u32(&mut env, metadata_ptr as u32 + 4, script_resource_name_id); + write_guest_u32(&mut env, metadata_ptr as u32 + 8, stderr_line_id); + write_guest_u32(&mut env, metadata_ptr as u32 + 12, thrown_at_id); + write_guest_i32(&mut env, metadata_ptr as u32 + 16, line_number); + write_guest_i32(&mut env, metadata_ptr as u32 + 20, start_column); + write_guest_i32(&mut env, metadata_ptr as u32 + 24, end_column); } 0 } -fn guest_unofficial_napi_set_source_maps_enabled( +fn guest_unofficial_napi_configure_source_maps( env: FunctionEnvMut, napi_env: i32, enabled: i32, -) -> i32 { - let env_handle = snapi_env(&env, napi_env); - unsafe { snapi_bridge_unofficial_set_source_maps_enabled(env_handle, enabled) } -} - -fn guest_unofficial_napi_set_get_source_map_error_source_callback( - env: FunctionEnvMut, - napi_env: i32, callback: i32, ) -> i32 { let env_handle = snapi_env(&env, napi_env); let callback_id = if callback > 0 { callback as u32 } else { 0 }; - unsafe { - snapi_bridge_unofficial_set_get_source_map_error_source_callback(env_handle, callback_id) - } -} - -fn guest_unofficial_napi_get_error_source_line_for_stderr( - mut env: FunctionEnvMut, - napi_env: i32, - error: i32, - result_ptr: i32, -) -> i32 { - let env_handle = snapi_env(&env, napi_env); - let error_id = if error > 0 { error as u32 } else { 0 }; - let mut result_id = 0u32; - let status = unsafe { - snapi_bridge_unofficial_get_error_source_line_for_stderr( - env_handle, - error_id, - &mut result_id, - ) - }; - if status == 0 && result_ptr > 0 { - write_guest_u32(&mut env, result_ptr as u32, result_id); - } - status -} - -fn guest_unofficial_napi_get_error_thrown_at( - mut env: FunctionEnvMut, - napi_env: i32, - error: i32, - result_ptr: i32, -) -> i32 { - let env_handle = snapi_env(&env, napi_env); - let error_id = if error > 0 { error as u32 } else { 0 }; - let mut result_id = 0u32; - let status = unsafe { - snapi_bridge_unofficial_get_error_thrown_at(env_handle, error_id, &mut result_id) - }; - if status == 0 && result_ptr > 0 { - write_guest_u32(&mut env, result_ptr as u32, result_id); - } - status -} - -fn guest_unofficial_napi_take_preserved_error_formatting( - mut env: FunctionEnvMut, - napi_env: i32, - error: i32, - source_line_ptr: i32, - thrown_at_ptr: i32, -) -> i32 { - let env_handle = snapi_env(&env, napi_env); - let error_id = if error > 0 { error as u32 } else { 0 }; - let mut source_line_id = 0u32; - let mut thrown_at_id = 0u32; - let status = unsafe { - snapi_bridge_unofficial_take_preserved_error_formatting( - env_handle, - error_id, - &mut source_line_id, - &mut thrown_at_id, - ) - }; - if status == 0 { - if source_line_ptr > 0 { - write_guest_u32(&mut env, source_line_ptr as u32, source_line_id); - } - if thrown_at_ptr > 0 { - write_guest_u32(&mut env, thrown_at_ptr as u32, thrown_at_id); - } - } - status + unsafe { snapi_bridge_unofficial_configure_source_maps(env_handle, enabled, callback_id) } } fn guest_unofficial_napi_preserve_error_source_message( @@ -1563,8 +1334,19 @@ fn guest_unofficial_napi_get_heap_statistics( napi_env: i32, stats_ptr: i32, ) -> i32 { + let Some(size) = abi::read_output_header( + &mut env, + stats_ptr, + std::mem::size_of::(), + 1, + ) else { + return 1; + }; let env_handle = snapi_env(&env, napi_env); let mut stats = SnapiUnofficialHeapStatistics { + size, + version: 1, + valid_fields: 0, total_heap_size: 0, total_heap_size_executable: 0, total_physical_size: 0, @@ -1579,6 +1361,7 @@ fn guest_unofficial_napi_get_heap_statistics( total_global_handles_size: 0, used_global_handles_size: 0, external_memory: 0, + array_buffer_memory: 0, }; let status = unsafe { snapi_bridge_unofficial_get_heap_statistics(env_handle, &mut stats) }; if status == 0 && stats_ptr > 0 && !write_guest_pod(&mut env, stats_ptr, &stats) { @@ -1587,43 +1370,52 @@ fn guest_unofficial_napi_get_heap_statistics( status } -fn guest_unofficial_napi_get_heap_space_count( - mut env: FunctionEnvMut, - napi_env: i32, - count_ptr: i32, -) -> i32 { - let env_handle = snapi_env(&env, napi_env); - let mut count = 0u32; - let status = unsafe { snapi_bridge_unofficial_get_heap_space_count(env_handle, &mut count) }; - if status == 0 && count_ptr > 0 { - write_guest_u32(&mut env, count_ptr as u32, count); - } - status -} - fn guest_unofficial_napi_get_heap_space_statistics( mut env: FunctionEnvMut, napi_env: i32, - space_index: i32, stats_ptr: i32, + capacity: i32, + count_ptr: i32, ) -> i32 { + const MAX_HEAP_SPACE_CAPACITY: usize = 1024; + if capacity < 0 || count_ptr <= 0 || (capacity > 0 && stats_ptr <= 0) { + return 1; + } + let capacity = capacity as usize; + if capacity > MAX_HEAP_SPACE_CAPACITY { + return 1; + } let env_handle = snapi_env(&env, napi_env); - let mut stats = SnapiUnofficialHeapSpaceStatistics { - space_name: [0; 64], - space_size: 0, - space_used_size: 0, - space_available_size: 0, - physical_space_size: 0, - }; + let mut stats: Vec = (0..capacity) + .map(|_| SnapiUnofficialHeapSpaceStatistics { + space_name: [0; 64], + space_size: 0, + space_used_size: 0, + space_available_size: 0, + physical_space_size: 0, + }) + .collect(); + let mut count = 0u32; let status = unsafe { snapi_bridge_unofficial_get_heap_space_statistics( env_handle, - space_index.max(0) as u32, - &mut stats, + if stats.is_empty() { + std::ptr::null_mut() + } else { + stats.as_mut_ptr() + }, + capacity as u32, + &mut count, ) }; - if status == 0 && stats_ptr > 0 && !write_guest_pod(&mut env, stats_ptr, &stats) { - return 1; + if status == 0 { + if !write_guest_u32(&mut env, count_ptr as u32, count) { + return 1; + } + let written = capacity.min(count as usize); + if !write_guest_pod_slice(&mut env, stats_ptr, &stats[..written]) { + return 1; + } } status } @@ -1648,50 +1440,28 @@ fn guest_unofficial_napi_get_heap_code_statistics( status } -fn guest_unofficial_napi_set_stack_limit( - _env: FunctionEnvMut, - _napi_env: i32, - _stack_limit: i32, -) -> i32 { - // The guest passes a Wasm linear-memory address here, which is not a native host stack - // address. Treat this as a no-op in the Wasm-hosted runner. - 0 -} - -fn guest_unofficial_napi_set_near_heap_limit_callback( +fn guest_unofficial_napi_configure_near_heap_limit_callback( _env: FunctionEnvMut, _napi_env: i32, _callback: i32, _data: i32, + _restored_heap_limit: i32, ) -> i32 { 0 } -fn guest_unofficial_napi_remove_near_heap_limit_callback( - _env: FunctionEnvMut, - _napi_env: i32, - _heap_limit: i32, -) -> i32 { - 0 -} - -fn guest_unofficial_napi_free_buffer(mut env: FunctionEnvMut, data: i32) { - if data > 0 { - let _ = recycle_guest_allocation(&mut env, data as u32); - } -} - -fn guest_unofficial_napi_start_cpu_profile( +fn guest_unofficial_napi_profile_start( mut env: FunctionEnvMut, napi_env: i32, + kind: i32, result_ptr: i32, - profile_id_ptr: i32, + profile_ptr: i32, ) -> i32 { let env_handle = snapi_env(&env, napi_env); let mut result = 0i32; - let mut profile_id = 0u32; + let mut profile = 0u32; let status = unsafe { - snapi_bridge_unofficial_start_cpu_profile(env_handle, &mut result, &mut profile_id) + snapi_bridge_unofficial_profile_start(env_handle, kind, &mut result, &mut profile) }; if status != 0 { return status; @@ -1699,82 +1469,30 @@ fn guest_unofficial_napi_start_cpu_profile( if result_ptr > 0 { write_guest_i32(&mut env, result_ptr as u32, result); } - if profile_id_ptr > 0 { - write_guest_u32(&mut env, profile_id_ptr as u32, profile_id); + if profile_ptr > 0 { + write_guest_u32(&mut env, profile_ptr as u32, profile); } 0 } -fn guest_unofficial_napi_stop_cpu_profile( +fn guest_unofficial_napi_profile_stop( mut env: FunctionEnvMut, napi_env: i32, - profile_id: i32, - found_ptr: i32, + profile: i32, json_ptr: i32, - json_len_ptr: i32, ) -> i32 { let env_handle = snapi_env(&env, napi_env); - let mut found = 0i32; - let mut host_ptr = 0u64; - let mut host_len = 0u32; + let mut json = 0u32; let status = unsafe { - snapi_bridge_unofficial_stop_cpu_profile( - env_handle, - profile_id.max(0) as u32, - &mut found, - &mut host_ptr, - &mut host_len, - ) - }; - if status != 0 { - return status; - } - if found_ptr > 0 { - write_guest_u8(&mut env, found_ptr as u32, (found != 0) as u8); - } - copy_host_buffer_to_guest(&mut env, json_ptr, json_len_ptr, host_ptr, host_len) -} - -fn guest_unofficial_napi_start_heap_profile( - mut env: FunctionEnvMut, - napi_env: i32, - started_ptr: i32, -) -> i32 { - let env_handle = snapi_env(&env, napi_env); - let mut started = 0i32; - let status = unsafe { snapi_bridge_unofficial_start_heap_profile(env_handle, &mut started) }; - if status == 0 && started_ptr > 0 { - write_guest_u8(&mut env, started_ptr as u32, (started != 0) as u8); - } - status -} - -fn guest_unofficial_napi_stop_heap_profile( - mut env: FunctionEnvMut, - napi_env: i32, - found_ptr: i32, - json_ptr: i32, - json_len_ptr: i32, -) -> i32 { - let env_handle = snapi_env(&env, napi_env); - let mut found = 0i32; - let mut host_ptr = 0u64; - let mut host_len = 0u32; - let status = unsafe { - snapi_bridge_unofficial_stop_heap_profile( - env_handle, - &mut found, - &mut host_ptr, - &mut host_len, - ) + snapi_bridge_unofficial_profile_stop(env_handle, profile.max(0) as u32, &mut json) }; if status != 0 { return status; } - if found_ptr > 0 { - write_guest_u8(&mut env, found_ptr as u32, (found != 0) as u8); + if json_ptr > 0 && !write_guest_u32(&mut env, json_ptr as u32, json) { + return 1; } - copy_host_buffer_to_guest(&mut env, json_ptr, json_len_ptr, host_ptr, host_len) + 0 } fn guest_unofficial_napi_take_heap_snapshot( @@ -1782,7 +1500,6 @@ fn guest_unofficial_napi_take_heap_snapshot( napi_env: i32, options_ptr: i32, json_ptr: i32, - json_len_ptr: i32, ) -> i32 { let env_handle = snapi_env(&env, napi_env); let (expose_internals, expose_numeric_values) = if options_ptr > 0 { @@ -1793,21 +1510,22 @@ fn guest_unofficial_napi_take_heap_snapshot( } else { (0, 0) }; - let mut host_ptr = 0u64; - let mut host_len = 0u32; + let mut json = 0u32; let status = unsafe { snapi_bridge_unofficial_take_heap_snapshot( env_handle, expose_internals, expose_numeric_values, - &mut host_ptr, - &mut host_len, + &mut json, ) }; if status != 0 { return status; } - copy_host_buffer_to_guest(&mut env, json_ptr, json_len_ptr, host_ptr, host_len) + if json_ptr > 0 && !write_guest_u32(&mut env, json_ptr as u32, json) { + return 1; + } + 0 } fn guest_unofficial_napi_create_serdes_binding( @@ -1935,7 +1653,7 @@ fn guest_unofficial_napi_contextify_run_script( result_ptr: i32, ) -> i32 { let env_handle = snapi_env(&env, napi_env); - let Some((source_text, source_bytecode)) = read_guest_js_source(&mut env, source_ptr) else { + let Some((source_text, source_bytecode)) = abi::read_js_source(&mut env, source_ptr) else { return 1; }; let mut result_id = 0u32; @@ -1970,20 +1688,6 @@ fn guest_unofficial_napi_contextify_run_script( status } -fn guest_unofficial_napi_contextify_dispose_context( - env: FunctionEnvMut, - napi_env: i32, - sandbox_or_context_global: i32, -) -> i32 { - let env_handle = snapi_env(&env, napi_env); - unsafe { - snapi_bridge_unofficial_contextify_dispose_context( - env_handle, - sandbox_or_context_global as u32, - ) - } -} - #[allow(clippy::too_many_arguments)] fn guest_unofficial_napi_contextify_compile_function( mut env: FunctionEnvMut, @@ -1999,7 +1703,7 @@ fn guest_unofficial_napi_contextify_compile_function( result_ptr: i32, ) -> i32 { let env_handle = snapi_env(&env, napi_env); - let Some((source_text, source_bytecode)) = read_guest_js_source(&mut env, source_ptr) else { + let Some((source_text, source_bytecode)) = abi::read_js_source(&mut env, source_ptr) else { return 1; }; let mut result_id = 0u32; @@ -2011,90 +1715,16 @@ fn guest_unofficial_napi_contextify_compile_function( filename as u32, line_offset, column_offset, - if parsing_context_or_undefined > 0 { - parsing_context_or_undefined as u32 - } else { - 0 - }, - if context_extensions_or_undefined > 0 { - context_extensions_or_undefined as u32 - } else { - 0 - }, - if params_or_undefined > 0 { - params_or_undefined as u32 - } else { - 0 - }, - if host_defined_option_id > 0 { - host_defined_option_id as u32 - } else { - 0 - }, - &mut result_id, - ) - }; - if status == 0 && result_ptr > 0 { - write_guest_u32(&mut env, result_ptr as u32, result_id); - } - status -} - -#[allow(clippy::too_many_arguments)] -fn guest_unofficial_napi_contextify_compile_function_for_cjs_loader( - mut env: FunctionEnvMut, - napi_env: i32, - source_ptr: i32, - filename: i32, - is_sea_main: i32, - should_detect_module: i32, - result_ptr: i32, -) -> i32 { - let env_handle = snapi_env(&env, napi_env); - let Some((source_text, source_bytecode)) = read_guest_js_source(&mut env, source_ptr) else { - return 1; - }; - let mut result_id = 0u32; - let status = unsafe { - snapi_bridge_unofficial_contextify_compile_function_for_cjs_loader( - env_handle, - source_text, - source_bytecode, - filename as u32, - is_sea_main, - should_detect_module, - &mut result_id, - ) - }; - if status == 0 && result_ptr > 0 { - write_guest_u32(&mut env, result_ptr as u32, result_id); - } - status -} - -#[allow(clippy::too_many_arguments)] -fn guest_unofficial_napi_bytecode_compile( - mut env: FunctionEnvMut, - napi_env: i32, - source_text: i32, - filename: i32, - shape: i32, - params_or_undefined: i32, - host_defined_option_id: i32, - line_offset: i32, - column_offset: i32, - bytecode_ptr: i32, - can_parse_as_module_ptr: i32, -) -> i32 { - let env_handle = snapi_env(&env, napi_env); - let mut bytecode_id = 0u32; - let mut can_parse_as_module = 0u8; - let status = unsafe { - snapi_bridge_unofficial_bytecode_compile( - env_handle, - source_text as u32, - filename as u32, - shape, + if parsing_context_or_undefined > 0 { + parsing_context_or_undefined as u32 + } else { + 0 + }, + if context_extensions_or_undefined > 0 { + context_extensions_or_undefined as u32 + } else { + 0 + }, if params_or_undefined > 0 { params_or_undefined as u32 } else { @@ -2105,76 +1735,57 @@ fn guest_unofficial_napi_bytecode_compile( } else { 0 }, - line_offset, - column_offset, - &mut bytecode_id, - &mut can_parse_as_module, + &mut result_id, ) }; - if can_parse_as_module_ptr > 0 { - write_guest_u8( - &mut env, - can_parse_as_module_ptr as u32, - can_parse_as_module, - ); - } - if status == 0 && bytecode_ptr > 0 { - write_guest_u32(&mut env, bytecode_ptr as u32, bytecode_id); + if status == 0 && result_ptr > 0 { + write_guest_u32(&mut env, result_ptr as u32, result_id); } status } -#[allow(clippy::too_many_arguments)] -fn guest_unofficial_napi_bytecode_deserialize( +fn guest_unofficial_napi_bytecode_open( mut env: FunctionEnvMut, napi_env: i32, - bytes_ptr: i32, - byte_length: i32, - source_text: i32, - filename: i32, - shape: i32, - params_or_undefined: i32, - host_defined_option_id: i32, - bytecode_ptr: i32, - rejected_ptr: i32, + options_ptr: i32, + result_ptr: i32, ) -> i32 { - let env_handle = snapi_env(&env, napi_env); - if bytes_ptr <= 0 || byte_length <= 0 { - return 1; // napi_invalid_arg - } - let Some(bytes) = read_guest_bytes(&mut env, bytes_ptr, byte_length as usize) else { - return 1; // napi_invalid_arg + let Some(options) = abi::read_bytecode_open(&mut env, options_ptr) else { + return 1; }; + if result_ptr <= 0 { + return 1; + } + let env_handle = snapi_env(&env, napi_env); let mut bytecode_id = 0u32; - let mut rejected = 0u8; + let mut cache_rejected = 0u8; + let mut can_parse_as_module = 0u8; let status = unsafe { - snapi_bridge_unofficial_bytecode_deserialize( + snapi_bridge_unofficial_bytecode_open( env_handle, - bytes.as_ptr(), - bytes.len(), - source_text as u32, - filename as u32, - shape, - if params_or_undefined > 0 { - params_or_undefined as u32 - } else { - 0 - }, - if host_defined_option_id > 0 { - host_defined_option_id as u32 + options.source_text, + options.filename, + options.shape, + options.params_or_undefined, + options.host_defined_option_id, + options.line_offset, + options.column_offset, + if options.has_cache != 0 { + options.cache.as_ptr() } else { - 0 + std::ptr::null() }, + options.cache.len(), + options.has_cache, + options.cache_policy, &mut bytecode_id, - &mut rejected, + &mut cache_rejected, + &mut can_parse_as_module, ) }; - if rejected_ptr > 0 { - write_guest_u8(&mut env, rejected_ptr as u32, rejected); - } - if status == 0 && bytecode_ptr > 0 { - write_guest_u32(&mut env, bytecode_ptr as u32, bytecode_id); - } + write_guest_u32(&mut env, result_ptr as u32, bytecode_id); + write_guest_u8(&mut env, result_ptr as u32 + 4, cache_rejected); + write_guest_u8(&mut env, result_ptr as u32 + 5, can_parse_as_module); status } @@ -2204,134 +1815,45 @@ fn guest_unofficial_napi_bytecode_release( unsafe { snapi_bridge_unofficial_bytecode_release(env_handle, bytecode as u32) } } -fn guest_unofficial_napi_contextify_start_sigint_watchdog( - mut env: FunctionEnvMut, - napi_env: i32, - result_ptr: i32, -) -> i32 { - let env_handle = snapi_env(&env, napi_env); - let mut result = 0i32; - let status = unsafe { - snapi_bridge_unofficial_contextify_start_sigint_watchdog(env_handle, &mut result) - }; - if status == 0 && result_ptr > 0 { - write_guest_u8(&mut env, result_ptr as u32, (result != 0) as u8); - } - status -} - -fn guest_unofficial_napi_contextify_stop_sigint_watchdog( - mut env: FunctionEnvMut, - napi_env: i32, - had_pending_signal_ptr: i32, -) -> i32 { - let env_handle = snapi_env(&env, napi_env); - let mut had_pending_signal = 0i32; - let status = unsafe { - snapi_bridge_unofficial_contextify_stop_sigint_watchdog(env_handle, &mut had_pending_signal) - }; - if status == 0 && had_pending_signal_ptr > 0 { - write_guest_u8( - &mut env, - had_pending_signal_ptr as u32, - (had_pending_signal != 0) as u8, - ); - } - status -} - -fn guest_unofficial_napi_contextify_watchdog_has_pending_sigint( +fn guest_unofficial_napi_module_wrap_create( mut env: FunctionEnvMut, napi_env: i32, + options_ptr: i32, result_ptr: i32, ) -> i32 { - let env_handle = snapi_env(&env, napi_env); - let mut result = 0i32; - let status = unsafe { - snapi_bridge_unofficial_contextify_watchdog_has_pending_sigint(env_handle, &mut result) - }; - if status == 0 && result_ptr > 0 { - write_guest_u8(&mut env, result_ptr as u32, (result != 0) as u8); - } - status -} - -#[allow(clippy::too_many_arguments)] -fn guest_unofficial_napi_module_wrap_create_source_text( - mut env: FunctionEnvMut, - napi_env: i32, - wrapper: i32, - url: i32, - context_or_undefined: i32, - source_ptr: i32, - line_offset: i32, - column_offset: i32, - host_defined_option_id: i32, - handle_ptr: i32, -) -> i32 { - let env_handle = snapi_env(&env, napi_env); - let Some((source_text, source_bytecode)) = read_guest_js_source(&mut env, source_ptr) else { + let Some(options) = abi::read_module_create(&mut env, options_ptr) else { return 1; }; - let mut handle_id = 0u32; - let status = unsafe { - snapi_bridge_unofficial_module_wrap_create_source_text( - env_handle, - wrapper as u32, - url as u32, - if context_or_undefined > 0 { - context_or_undefined as u32 - } else { - 0 - }, - source_text, - source_bytecode, - line_offset, - column_offset, - if host_defined_option_id > 0 { - host_defined_option_id as u32 - } else { - 0 - }, - &mut handle_id, - ) - }; - if status == 0 && handle_ptr > 0 { - write_guest_u32(&mut env, handle_ptr as u32, handle_id); + if result_ptr <= 0 { + return 1; } - status -} - -#[allow(clippy::too_many_arguments)] -fn guest_unofficial_napi_module_wrap_create_synthetic( - mut env: FunctionEnvMut, - napi_env: i32, - wrapper: i32, - url: i32, - context_or_undefined: i32, - export_names: i32, - synthetic_eval_steps: i32, - handle_ptr: i32, -) -> i32 { let env_handle = snapi_env(&env, napi_env); let mut handle_id = 0u32; + let mut requests_id = 0u32; + let mut has_top_level_await = 0u8; let status = unsafe { - snapi_bridge_unofficial_module_wrap_create_synthetic( + snapi_bridge_unofficial_module_wrap_create( env_handle, - wrapper as u32, - url as u32, - if context_or_undefined > 0 { - context_or_undefined as u32 - } else { - 0 - }, - export_names as u32, - synthetic_eval_steps as u32, + options.kind, + options.wrapper, + options.url, + options.context_or_undefined, + options.source_text, + options.source_bytecode, + options.line_offset, + options.column_offset, + options.host_defined_option_id, + options.export_names, + options.synthetic_eval_steps, &mut handle_id, + &mut requests_id, + &mut has_top_level_await, ) }; - if status == 0 && handle_ptr > 0 { - write_guest_u32(&mut env, handle_ptr as u32, handle_id); + if status == 0 { + write_guest_u32(&mut env, result_ptr as u32, handle_id); + write_guest_u32(&mut env, result_ptr as u32 + 4, requests_id); + write_guest_bytes(&mut env, result_ptr as u32 + 8, &[has_top_level_await]); } status } @@ -2345,27 +1867,6 @@ fn guest_unofficial_napi_module_wrap_destroy( unsafe { snapi_bridge_unofficial_module_wrap_destroy(env_handle, handle as u32) } } -fn guest_unofficial_napi_module_wrap_get_module_requests( - mut env: FunctionEnvMut, - napi_env: i32, - handle: i32, - result_ptr: i32, -) -> i32 { - let env_handle = snapi_env(&env, napi_env); - let mut result_id = 0u32; - let status = unsafe { - snapi_bridge_unofficial_module_wrap_get_module_requests( - env_handle, - handle as u32, - &mut result_id, - ) - }; - if status == 0 && result_ptr > 0 { - write_guest_u32(&mut env, result_ptr as u32, result_id); - } - status -} - fn guest_unofficial_napi_module_wrap_link( mut env: FunctionEnvMut, napi_env: i32, @@ -2473,74 +1974,53 @@ fn guest_unofficial_napi_module_wrap_get_namespace( status } -fn guest_unofficial_napi_module_wrap_get_status( +fn guest_unofficial_napi_module_wrap_get_state( mut env: FunctionEnvMut, napi_env: i32, handle: i32, status_ptr: i32, + error_ptr: i32, + has_async_graph_ptr: i32, ) -> i32 { let env_handle = snapi_env(&env, napi_env); let mut status_val = 0i32; + let mut error_id = 0u32; + let mut has_async_graph = 0i32; let status = unsafe { - snapi_bridge_unofficial_module_wrap_get_status(env_handle, handle as u32, &mut status_val) - }; - if status == 0 && status_ptr > 0 { - write_guest_i32(&mut env, status_ptr as u32, status_val); - } - status -} - -fn guest_unofficial_napi_module_wrap_get_error( - mut env: FunctionEnvMut, - napi_env: i32, - handle: i32, - result_ptr: i32, -) -> i32 { - let env_handle = snapi_env(&env, napi_env); - let mut result_id = 0u32; - let status = unsafe { - snapi_bridge_unofficial_module_wrap_get_error(env_handle, handle as u32, &mut result_id) - }; - if status == 0 && result_ptr > 0 { - write_guest_u32(&mut env, result_ptr as u32, result_id); - } - status -} - -fn guest_unofficial_napi_module_wrap_has_top_level_await( - mut env: FunctionEnvMut, - napi_env: i32, - handle: i32, - result_ptr: i32, -) -> i32 { - let env_handle = snapi_env(&env, napi_env); - let mut result = 0i32; - let status = unsafe { - snapi_bridge_unofficial_module_wrap_has_top_level_await( + snapi_bridge_unofficial_module_wrap_get_state( env_handle, handle as u32, - &mut result, + if status_ptr > 0 { + &mut status_val + } else { + std::ptr::null_mut() + }, + if error_ptr > 0 { + &mut error_id + } else { + std::ptr::null_mut() + }, + if has_async_graph_ptr > 0 { + &mut has_async_graph + } else { + std::ptr::null_mut() + }, ) }; - if status == 0 && result_ptr > 0 { - write_guest_u8(&mut env, result_ptr as u32, (result != 0) as u8); - } - status -} - -fn guest_unofficial_napi_module_wrap_has_async_graph( - mut env: FunctionEnvMut, - napi_env: i32, - handle: i32, - result_ptr: i32, -) -> i32 { - let env_handle = snapi_env(&env, napi_env); - let mut result = 0i32; - let status = unsafe { - snapi_bridge_unofficial_module_wrap_has_async_graph(env_handle, handle as u32, &mut result) - }; - if status == 0 && result_ptr > 0 { - write_guest_u8(&mut env, result_ptr as u32, (result != 0) as u8); + if status == 0 { + if status_ptr > 0 { + write_guest_i32(&mut env, status_ptr as u32, status_val); + } + if error_ptr > 0 { + write_guest_u32(&mut env, error_ptr as u32, error_id); + } + if has_async_graph_ptr > 0 { + write_guest_u8( + &mut env, + has_async_graph_ptr as u32, + (has_async_graph != 0) as u8, + ); + } } status } @@ -2548,21 +2028,17 @@ fn guest_unofficial_napi_module_wrap_has_async_graph( fn guest_unofficial_napi_module_wrap_check_unsettled_top_level_await( mut env: FunctionEnvMut, napi_env: i32, - module_wrap: i32, + handle: i32, warnings: i32, settled_ptr: i32, ) -> i32 { let env_handle = snapi_env(&env, napi_env); - let module_wrap_id = if module_wrap > 0 { - module_wrap as u32 - } else { - 0 - }; + let handle_id = if handle > 0 { handle as u32 } else { 0 }; let mut settled = 0i32; let status = unsafe { snapi_bridge_unofficial_module_wrap_check_unsettled_top_level_await( env_handle, - module_wrap_id, + handle_id, warnings, &mut settled, ) @@ -2657,64 +2133,24 @@ fn guest_unofficial_napi_module_wrap_create_cached_data( status } -fn guest_unofficial_napi_module_wrap_set_import_module_dynamically_callback( - env: FunctionEnvMut, - napi_env: i32, - callback: i32, -) -> i32 { - let env_handle = snapi_env(&env, napi_env); - unsafe { - snapi_bridge_unofficial_module_wrap_set_import_module_dynamically_callback( - env_handle, - if callback > 0 { callback as u32 } else { 0 }, - ) - } -} - -fn guest_unofficial_napi_module_wrap_set_initialize_import_meta_object_callback( - env: FunctionEnvMut, - napi_env: i32, - callback: i32, -) -> i32 { - let env_handle = snapi_env(&env, napi_env); - unsafe { - snapi_bridge_unofficial_module_wrap_set_initialize_import_meta_object_callback( - env_handle, - if callback > 0 { callback as u32 } else { 0 }, - ) - } -} - -fn guest_unofficial_napi_module_wrap_import_module_dynamically( +fn guest_unofficial_napi_module_wrap_set_hooks( mut env: FunctionEnvMut, napi_env: i32, - argc: i32, - argv_ptr: i32, - result_ptr: i32, + hooks_ptr: i32, ) -> i32 { - let env_handle = snapi_env(&env, napi_env); - let argc_u = argc as u32; - let argv_ids = if argc_u > 0 { - let Some(ids) = read_guest_u32_array(&mut env, argv_ptr, argc_u as usize) else { - return 1; - }; - ids - } else { - Vec::new() + let Some((dynamic_import, initialize_import_meta)) = + abi::read_module_hooks(&mut env, hooks_ptr) + else { + return 1; }; - let mut result_id = 0u32; - let status = unsafe { - snapi_bridge_unofficial_module_wrap_import_module_dynamically( + let env_handle = snapi_env(&env, napi_env); + unsafe { + snapi_bridge_unofficial_module_wrap_set_hooks( env_handle, - argc_u, - argv_ids.as_ptr(), - &mut result_id, + dynamic_import, + initialize_import_meta, ) - }; - if status == 0 && result_ptr > 0 { - write_guest_u32(&mut env, result_ptr as u32, result_id); } - status } fn guest_unofficial_napi_module_wrap_create_required_module_facade( @@ -3972,7 +3408,7 @@ fn guest_napi_create_external_buffer( ) }; if host_data != 0 { - unsafe { snapi_bridge_unofficial_free_buffer(host_data as *mut c_void) }; + unsafe { snapi_bridge_free_buffer(host_data as *mut c_void) }; } if status != 0 { return status; @@ -4080,7 +3516,7 @@ fn guest_napi_get_arraybuffer_info( None }; if host_data_addr != 0 { - unsafe { snapi_bridge_unofficial_free_buffer(host_data_addr as *mut c_void) }; + unsafe { snapi_bridge_free_buffer(host_data_addr as *mut c_void) }; } if let Some(guest_data_ptr) = guest_data_ptr { write_guest_u32(&mut env, data_ptr as u32, guest_data_ptr); @@ -4317,7 +3753,7 @@ fn guest_napi_get_typedarray_info( } } if host_data_addr != 0 { - unsafe { snapi_bridge_unofficial_free_buffer(host_data_addr as *mut c_void) }; + unsafe { snapi_bridge_free_buffer(host_data_addr as *mut c_void) }; } if abp > 0 { write_guest_u32(&mut env, abp as u32, ab); @@ -4398,7 +3834,7 @@ fn guest_napi_get_dataview_info( None }; if host_data_addr != 0 { - unsafe { snapi_bridge_unofficial_free_buffer(host_data_addr as *mut c_void) }; + unsafe { snapi_bridge_free_buffer(host_data_addr as *mut c_void) }; } if let Some(guest_data_ptr) = guest_data_ptr { write_guest_u32(&mut env, dp as u32, guest_data_ptr); @@ -6523,104 +5959,72 @@ pub fn register_napi_imports( }; let napi_extension_wasmer_namespace = namespace! { - "unofficial_napi_set_flags_from_string" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_set_flags_from_string), + "unofficial_napi_configure_runtime" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_configure_runtime), "unofficial_napi_create_env" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_create_env), - "unofficial_napi_create_env_with_options" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_create_env_with_options), - "unofficial_napi_set_embedder_hooks" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_set_embedder_hooks), + "unofficial_napi_attach_env" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_attach_env), "unofficial_napi_release_env" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_release_env), - "unofficial_napi_release_env_with_loop" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_release_env_with_loop), - "unofficial_napi_low_memory_notification" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_low_memory_notification), + "unofficial_napi_collect_garbage" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_collect_garbage), "unofficial_napi_event_loop_checkpoint" => event_loop_checkpoint_import(store, fe), "unofficial_napi_create_uninitialized_arraybuffer" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_create_uninitialized_arraybuffer), "unofficial_napi_acquire_buffer_lease" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_acquire_buffer_lease), "unofficial_napi_release_buffer_lease" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_release_buffer_lease), "unofficial_napi_create_guest_backed_typedarray" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_create_guest_backed_typedarray), - "unofficial_napi_request_gc_for_testing" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_request_gc_for_testing), "unofficial_napi_set_prepare_stack_trace_callback" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_set_prepare_stack_trace_callback), "unofficial_napi_get_promise_details" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_get_promise_details), "unofficial_napi_get_proxy_details" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_get_proxy_details), "unofficial_napi_preview_entries" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_preview_entries), "unofficial_napi_get_call_sites" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_get_call_sites), - "unofficial_napi_get_current_stack_trace" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_get_current_stack_trace), - "unofficial_napi_get_caller_location" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_get_caller_location), "unofficial_napi_arraybuffer_view_has_buffer" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_arraybuffer_view_has_buffer), "unofficial_napi_get_constructor_name" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_get_constructor_name), "unofficial_napi_create_private_symbol" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_create_private_symbol), "unofficial_napi_get_continuation_preserved_embedder_data" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_get_continuation_preserved_embedder_data), "unofficial_napi_set_continuation_preserved_embedder_data" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_set_continuation_preserved_embedder_data), "unofficial_napi_notify_datetime_configuration_change" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_notify_datetime_configuration_change), - "unofficial_napi_set_enqueue_foreground_task_callback" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_set_enqueue_foreground_task_callback), - "unofficial_napi_set_fatal_error_callbacks" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_set_fatal_error_callbacks), "unofficial_napi_terminate_execution" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_terminate_execution), "unofficial_napi_cancel_terminate_execution" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_cancel_terminate_execution), "unofficial_napi_request_interrupt" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_request_interrupt), "unofficial_napi_structured_clone" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_structured_clone), - "unofficial_napi_structured_clone_with_transfer" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_structured_clone_with_transfer), - "unofficial_napi_serialize_value" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_serialize_value), - "unofficial_napi_deserialize_value" => deserialize_value_import(store, fe), - "unofficial_napi_release_serialized_value" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_release_serialized_value), + "unofficial_napi_message_create" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_message_create), + "unofficial_napi_message_take" => message_take_import(store, fe), + "unofficial_napi_message_drop" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_message_drop), "unofficial_napi_enqueue_microtask" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_enqueue_microtask), "unofficial_napi_set_promise_reject_callback" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_set_promise_reject_callback), - "unofficial_napi_set_promise_hooks" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_set_promise_hooks), "unofficial_napi_get_own_non_index_properties" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_get_own_non_index_properties), - "unofficial_napi_get_process_memory_info" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_get_process_memory_info), + "unofficial_napi_set_promise_hooks" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_set_promise_hooks), "unofficial_napi_get_hash_seed" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_get_hash_seed), - "unofficial_napi_get_error_source_positions" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_get_error_source_positions), - "unofficial_napi_set_source_maps_enabled" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_set_source_maps_enabled), - "unofficial_napi_set_get_source_map_error_source_callback" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_set_get_source_map_error_source_callback), - "unofficial_napi_get_error_source_line_for_stderr" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_get_error_source_line_for_stderr), - "unofficial_napi_get_error_thrown_at" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_get_error_thrown_at), - "unofficial_napi_take_preserved_error_formatting" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_take_preserved_error_formatting), + "unofficial_napi_get_error_metadata" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_get_error_metadata), + "unofficial_napi_configure_source_maps" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_configure_source_maps), "unofficial_napi_preserve_error_source_message" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_preserve_error_source_message), "unofficial_napi_mark_promise_as_handled" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_mark_promise_as_handled), "unofficial_napi_get_heap_statistics" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_get_heap_statistics), - "unofficial_napi_get_heap_space_count" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_get_heap_space_count), "unofficial_napi_get_heap_space_statistics" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_get_heap_space_statistics), "unofficial_napi_get_heap_code_statistics" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_get_heap_code_statistics), - "unofficial_napi_set_stack_limit" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_set_stack_limit), - "unofficial_napi_set_near_heap_limit_callback" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_set_near_heap_limit_callback), - "unofficial_napi_remove_near_heap_limit_callback" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_remove_near_heap_limit_callback), - "unofficial_napi_free_buffer" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_free_buffer), - "unofficial_napi_start_cpu_profile" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_start_cpu_profile), - "unofficial_napi_stop_cpu_profile" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_stop_cpu_profile), - "unofficial_napi_start_heap_profile" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_start_heap_profile), - "unofficial_napi_stop_heap_profile" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_stop_heap_profile), + "unofficial_napi_configure_near_heap_limit_callback" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_configure_near_heap_limit_callback), + "unofficial_napi_profile_start" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_profile_start), + "unofficial_napi_profile_stop" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_profile_stop), "unofficial_napi_take_heap_snapshot" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_take_heap_snapshot), "unofficial_napi_create_serdes_binding" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_create_serdes_binding), "unofficial_napi_contextify_contains_module_syntax" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_contextify_contains_module_syntax), "unofficial_napi_contextify_make_context" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_contextify_make_context), "unofficial_napi_contextify_run_script" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_contextify_run_script), - "unofficial_napi_contextify_dispose_context" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_contextify_dispose_context), "unofficial_napi_contextify_compile_function" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_contextify_compile_function), - "unofficial_napi_contextify_compile_function_for_cjs_loader" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_contextify_compile_function_for_cjs_loader), - "unofficial_napi_bytecode_compile" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_bytecode_compile), - "unofficial_napi_bytecode_deserialize" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_bytecode_deserialize), + "unofficial_napi_bytecode_open" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_bytecode_open), "unofficial_napi_bytecode_serialize" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_bytecode_serialize), "unofficial_napi_bytecode_release" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_bytecode_release), - "unofficial_napi_contextify_start_sigint_watchdog" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_contextify_start_sigint_watchdog), - "unofficial_napi_contextify_stop_sigint_watchdog" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_contextify_stop_sigint_watchdog), - "unofficial_napi_contextify_watchdog_has_pending_sigint" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_contextify_watchdog_has_pending_sigint), - "unofficial_napi_module_wrap_create_source_text" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_module_wrap_create_source_text), - "unofficial_napi_module_wrap_create_synthetic" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_module_wrap_create_synthetic), + "unofficial_napi_module_wrap_create" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_module_wrap_create), "unofficial_napi_module_wrap_destroy" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_module_wrap_destroy), - "unofficial_napi_module_wrap_get_module_requests" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_module_wrap_get_module_requests), "unofficial_napi_module_wrap_link" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_module_wrap_link), "unofficial_napi_module_wrap_instantiate" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_module_wrap_instantiate), "unofficial_napi_module_wrap_evaluate" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_module_wrap_evaluate), "unofficial_napi_module_wrap_evaluate_sync" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_module_wrap_evaluate_sync), "unofficial_napi_module_wrap_get_namespace" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_module_wrap_get_namespace), - "unofficial_napi_module_wrap_get_status" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_module_wrap_get_status), - "unofficial_napi_module_wrap_get_error" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_module_wrap_get_error), - "unofficial_napi_module_wrap_has_top_level_await" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_module_wrap_has_top_level_await), - "unofficial_napi_module_wrap_has_async_graph" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_module_wrap_has_async_graph), + "unofficial_napi_module_wrap_get_state" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_module_wrap_get_state), "unofficial_napi_module_wrap_check_unsettled_top_level_await" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_module_wrap_check_unsettled_top_level_await), "unofficial_napi_module_wrap_set_export" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_module_wrap_set_export), "unofficial_napi_module_wrap_set_module_source_object" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_module_wrap_set_module_source_object), "unofficial_napi_module_wrap_get_module_source_object" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_module_wrap_get_module_source_object), "unofficial_napi_module_wrap_create_cached_data" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_module_wrap_create_cached_data), - "unofficial_napi_module_wrap_set_import_module_dynamically_callback" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_module_wrap_set_import_module_dynamically_callback), - "unofficial_napi_module_wrap_set_initialize_import_meta_object_callback" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_module_wrap_set_initialize_import_meta_object_callback), - "unofficial_napi_module_wrap_import_module_dynamically" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_module_wrap_import_module_dynamically), + "unofficial_napi_module_wrap_set_hooks" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_module_wrap_set_hooks), "unofficial_napi_module_wrap_create_required_module_facade" => Function::new_typed_with_env(store, fe, guest_unofficial_napi_module_wrap_create_required_module_facade), }; diff --git a/src/guest/util.rs b/src/guest/util.rs index 15764c0..23ab76a 100644 --- a/src/guest/util.rs +++ b/src/guest/util.rs @@ -74,16 +74,6 @@ pub fn guest_data_size(env: &mut FunctionEnvMut) -> u64 { memory.view(&store).data_size() } -pub fn allocate_guest_bytes(env: &mut FunctionEnvMut, data: &[u8]) -> Option { - let heap = env.data().guest_heap.clone()?; - let guest_ptr = heap.alloc(data.len(), false)?; - if !write_guest_bytes(env, guest_ptr, data) { - heap.free_offset(guest_ptr); - return None; - } - Some(guest_ptr) -} - pub fn host_ptr_to_guest_ptr(env: &mut FunctionEnvMut, host_addr: u64) -> Option { let memory = env.data().memory.clone()?; let (_, store_ref) = env.data_and_store_mut(); diff --git a/src/guest_heap.rs b/src/guest_heap.rs index 13ab2ff..ee3c605 100644 --- a/src/guest_heap.rs +++ b/src/guest_heap.rs @@ -611,7 +611,7 @@ impl Drop for GuestHeap { use std::ffi::c_void; /// Context installed into a V8 array-buffer allocator -/// (`unofficial_napi_env_create_options.guest_heap_ctx`). +/// (`unofficial_napi_env_create_options.guest_heap`). pub(crate) struct GuestHeapCtx { base: usize, len: u64, diff --git a/src/napi_bridge_init.cc b/src/napi_bridge_init.cc index 12330f8..163bafd 100644 --- a/src/napi_bridge_init.cc +++ b/src/napi_bridge_init.cc @@ -25,7 +25,6 @@ #include "node_api.h" #include "unofficial_napi.h" #include "internal/napi_v8_env.h" -#include "edge_napi_embedder_hooks.h" namespace { @@ -62,6 +61,21 @@ struct HandleTable { const auto it = handles.find(id); return it != handles.end() ? it->second : nullptr; } + void* Take(uint32_t id) { + if (id == 0) return nullptr; + const auto it = handles.find(id); + if (it == handles.end()) return nullptr; + void* handle = it->second; + handles.erase(it); + return handle; + } + std::vector TakeAll() { + std::vector result; + result.reserve(handles.size()); + for (const auto& entry : handles) result.push_back(entry.second); + Reset(); + return result; + } void Remove(uint32_t id) { handles.erase(id); } void Reset() { handles.clear(); @@ -103,7 +117,7 @@ struct ScopeFrame { struct SnapiEnvState { napi_env env = nullptr; - void* scope = nullptr; + unofficial_napi_env_owner owner = nullptr; // Value slot table: maps generation-tagged u32 IDs to scope-bound raw // napi_values. A value id is valid until the scope frame that owns its slot @@ -132,6 +146,11 @@ struct SnapiEnvState { // Handle table for opaque bytecode handles (unofficial_napi_bytecode_*). HandleTable bytecode_handles; + // Provider-owned profiler sessions. The provider stops any sessions still + // active when its env is released; this table only translates pointer-sized + // host handles to the wasm32 IDs visible to the guest. + HandleTable profile_handles; + // Opaque provider-owned memory leases. Unlike value slots, these survive // handle-scope closure and retain their JavaScript values until released. HandleTable buffer_lease_handles; @@ -159,6 +178,10 @@ struct CallbackBinding { // Never destroyed: static destructors race concurrent env teardown at process // exit (see the runtime-globals comment in unofficial_napi.cc). std::unordered_set& g_envs = *new std::unordered_set(); +// Message resources cross environment boundaries, so their guest-visible IDs +// cannot belong to either the source or destination environment. The table is +// process-wide and each entry is removed atomically by take or drop. +HandleTable& g_message_handles = *new HandleTable(); CallbackBinding* RegisterCallbackBinding(SnapiEnvState* state, uint32_t reg_id) { if (state == nullptr || reg_id == 0) return nullptr; @@ -366,24 +389,24 @@ int FindFrameById(SnapiEnvState& state, uint32_t scope_id) { return -1; } -uint32_t StoreModuleWrapHandle(SnapiEnvState& state, void* handle) { - return state.module_wrap_handles.Store(handle); +uint32_t StoreModuleWrapHandle(SnapiEnvState& state, unofficial_napi_module module) { + return state.module_wrap_handles.Store(reinterpret_cast(module)); } -void* LoadModuleWrapHandle(SnapiEnvState& state, uint32_t id) { - return state.module_wrap_handles.Load(id); +unofficial_napi_module LoadModuleWrapHandle(SnapiEnvState& state, uint32_t id) { + return reinterpret_cast(state.module_wrap_handles.Load(id)); } void RemoveModuleWrapHandle(SnapiEnvState& state, uint32_t id) { state.module_wrap_handles.Remove(id); } -uint32_t StoreBytecodeHandle(SnapiEnvState& state, void* handle) { - return state.bytecode_handles.Store(handle); +uint32_t StoreBytecodeHandle(SnapiEnvState& state, unofficial_napi_bytecode handle) { + return state.bytecode_handles.Store(reinterpret_cast(handle)); } -void* LoadBytecodeHandle(SnapiEnvState& state, uint32_t id) { - return state.bytecode_handles.Load(id); +unofficial_napi_bytecode LoadBytecodeHandle(SnapiEnvState& state, uint32_t id) { + return reinterpret_cast(state.bytecode_handles.Load(id)); } void RemoveBytecodeHandle(SnapiEnvState& state, uint32_t id) { @@ -424,19 +447,21 @@ napi_status DisposeBridgeStateLocked(SnapiEnvState* state) { // explicitly — env teardown does not reclaim them. for (auto& entry : state->bytecode_handles.handles) { if (entry.second != nullptr) { - (void)unofficial_napi_bytecode_release(state->env, entry.second); + (void)unofficial_napi_bytecode_release( + state->env, reinterpret_cast(entry.second)); } } state->bytecode_handles.Reset(); + state->profile_handles.Reset(); state->active_callback_ctx.store(nullptr, std::memory_order_release); state->callback_invocations.clear(); state->next_callback_invocation_id = 1; state->cb_registry.clear(); state->next_cb_reg_id = 1; state->callback_bindings.clear(); - if (state->scope != nullptr) { - napi_status s = unofficial_napi_release_env(state->scope); - state->scope = nullptr; + if (state->owner != nullptr) { + napi_status s = unofficial_napi_release_env(state->owner, nullptr); + state->owner = nullptr; state->env = nullptr; g_envs.erase(state); delete state; @@ -2882,33 +2907,48 @@ extern "C" int snapi_bridge_unofficial_create_env(int32_t module_api_version, SnapiEnvState** env_out) { std::lock_guard lock(g_mu); napi_env env = nullptr; - void* scope = nullptr; + unofficial_napi_env_owner owner = nullptr; napi_status s; if (guest_heap_ctx != nullptr) { unofficial_napi_env_create_options options{}; - options.guest_heap_ctx = const_cast(guest_heap_ctx); - s = unofficial_napi_create_env_with_options(module_api_version, &options, - &env, &scope); + options.size = sizeof(options); + options.version = UNOFFICIAL_NAPI_ENV_CREATE_OPTIONS_VERSION; + options.guest_heap = reinterpret_cast( + const_cast(guest_heap_ctx)); + s = unofficial_napi_create_env(module_api_version, &options, &env, &owner); } else { - s = unofficial_napi_create_env(module_api_version, &env, &scope); + s = unofficial_napi_create_env(module_api_version, nullptr, &env, &owner); } if (s != napi_ok) return s; auto* state = new (std::nothrow) SnapiEnvState(); if (state == nullptr) { - (void)unofficial_napi_release_env(scope); + (void)unofficial_napi_release_env(owner, nullptr); return napi_generic_failure; } state->env = env; - state->scope = scope; + state->owner = owner; g_envs.insert(state); if (env_out != nullptr) *env_out = state; return napi_ok; } +extern "C" int snapi_bridge_unofficial_configure_runtime( + const char* engine_flags, + uint32_t engine_flags_length) { + unofficial_napi_runtime_options options{}; + options.size = sizeof(options); + options.version = UNOFFICIAL_NAPI_RUNTIME_OPTIONS_VERSION; + options.engine_flags = engine_flags; + options.engine_flags_length = engine_flags_length; + return unofficial_napi_configure_runtime(&options); +} + extern "C" int snapi_bridge_unofficial_create_env_with_options( int32_t module_api_version, + uint64_t total_memory, + uint64_t constrained_memory, uint32_t max_young_generation_size_in_bytes, uint32_t max_old_generation_size_in_bytes, uint32_t code_range_size_in_bytes, @@ -2921,7 +2961,13 @@ extern "C" int snapi_bridge_unofficial_create_env_with_options( max_young_generation_size_in_bytes > 0 || max_old_generation_size_in_bytes > 0 || code_range_size_in_bytes > 0 || + total_memory > 0 || + constrained_memory > 0 || guest_heap_ctx != nullptr; + options.size = sizeof(options); + options.version = UNOFFICIAL_NAPI_ENV_CREATE_OPTIONS_VERSION; + options.total_memory = total_memory; + options.constrained_memory = constrained_memory; options.max_young_generation_size_in_bytes = max_young_generation_size_in_bytes; options.max_old_generation_size_in_bytes = @@ -2930,21 +2976,21 @@ extern "C" int snapi_bridge_unofficial_create_env_with_options( // The guest-provided stack limit is a Wasm linear-memory address, not a // native stack address for the host thread running V8. options.stack_limit = nullptr; - options.guest_heap_ctx = const_cast(guest_heap_ctx); - + options.guest_heap = reinterpret_cast( + const_cast(guest_heap_ctx)); napi_env env = nullptr; - void* scope = nullptr; - napi_status s = unofficial_napi_create_env_with_options( - module_api_version, has_options ? &options : nullptr, &env, &scope); + unofficial_napi_env_owner owner = nullptr; + napi_status s = unofficial_napi_create_env( + module_api_version, has_options ? &options : nullptr, &env, &owner); if (s != napi_ok) return s; auto* state = new (std::nothrow) SnapiEnvState(); if (state == nullptr) { - (void)unofficial_napi_release_env(scope); + (void)unofficial_napi_release_env(owner, nullptr); return napi_generic_failure; } state->env = env; - state->scope = scope; + state->owner = owner; g_envs.insert(state); if (env_out != nullptr) *env_out = state; @@ -2973,8 +3019,11 @@ extern "C" int snapi_bridge_unofficial_set_host_near_heap_limit_callback( if (env_state == nullptr || env_state->env == nullptr) { return napi_invalid_arg; } - return unofficial_napi_set_near_heap_limit_callback( - env_state->env, HostNearHeapLimitTrampoline, const_cast(data)); + return unofficial_napi_configure_near_heap_limit_callback( + env_state->env, + HostNearHeapLimitTrampoline, + const_cast(data), + 0); } // Debug/diagnostics: live count of slot-table entries (leak assertions). @@ -3015,18 +3064,13 @@ extern "C" int snapi_bridge_unofficial_release_env_with_loop(SnapiEnvState* env_ return DisposeBridgeStateLocked(env_state); } -extern "C" int snapi_bridge_unofficial_set_flags_from_string(const char* flags, - uint32_t length) { - std::lock_guard lock(g_mu); - return unofficial_napi_set_flags_from_string(flags, static_cast(length)); -} - -extern "C" int snapi_bridge_unofficial_low_memory_notification(SnapiEnvState* env_state) { +extern "C" int snapi_bridge_unofficial_collect_garbage( + SnapiEnvState* env_state) { auto* bridge_state = RequireEnvState(env_state); if (bridge_state == nullptr) return napi_invalid_arg; napi_env env = bridge_state->env; std::lock_guard lock(g_mu); - return unofficial_napi_low_memory_notification(env); + return unofficial_napi_collect_garbage(env); } extern "C" int snapi_bridge_unofficial_event_loop_checkpoint( @@ -3050,14 +3094,6 @@ extern "C" int snapi_bridge_unofficial_event_loop_checkpoint( return status; } -extern "C" int snapi_bridge_unofficial_request_gc_for_testing(SnapiEnvState* env_state) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; - napi_env env = bridge_state->env; - std::lock_guard lock(g_mu); - return unofficial_napi_request_gc_for_testing(env); -} - extern "C" int snapi_bridge_unofficial_set_prepare_stack_trace_callback( SnapiEnvState* env_state, uint32_t callback_id) { @@ -3070,14 +3106,6 @@ extern "C" int snapi_bridge_unofficial_set_prepare_stack_trace_callback( return unofficial_napi_set_prepare_stack_trace_callback(env, callback); } -extern "C" int snapi_bridge_unofficial_set_embedder_hooks(SnapiEnvState* env_state) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; - std::lock_guard lock(g_mu); - EdgeInstallNapiEmbedderHooks(); - return napi_ok; -} - extern "C" int snapi_bridge_unofficial_get_promise_details(SnapiEnvState* env_state, uint32_t promise_id, int32_t* state_out, @@ -3151,34 +3179,6 @@ extern "C" int snapi_bridge_unofficial_get_call_sites(SnapiEnvState* env_state, return napi_ok; } -extern "C" int snapi_bridge_unofficial_get_current_stack_trace( - SnapiEnvState* env_state, - uint32_t frames, - uint32_t* callsites_out) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; - napi_env env = bridge_state->env; - std::lock_guard lock(g_mu); - napi_value callsites = nullptr; - napi_status s = unofficial_napi_get_current_stack_trace(env, frames, &callsites); - if (s != napi_ok) return s; - if (callsites_out != nullptr) *callsites_out = StoreValue(*bridge_state, callsites); - return napi_ok; -} - -extern "C" int snapi_bridge_unofficial_get_caller_location(SnapiEnvState* env_state, - uint32_t* location_out) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; - napi_env env = bridge_state->env; - std::lock_guard lock(g_mu); - napi_value location = nullptr; - napi_status s = unofficial_napi_get_caller_location(env, &location); - if (s != napi_ok) return s; - if (location_out != nullptr) *location_out = StoreValue(*bridge_state, location); - return napi_ok; -} - extern "C" int snapi_bridge_unofficial_arraybuffer_view_has_buffer(SnapiEnvState* env_state, uint32_t value_id, int* result_out) { @@ -3256,26 +3256,22 @@ extern "C" int snapi_bridge_unofficial_set_continuation_preserved_embedder_data( return unofficial_napi_set_continuation_preserved_embedder_data(env, value); } -extern "C" int snapi_bridge_unofficial_set_enqueue_foreground_task_callback( - SnapiEnvState* env_state) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; - napi_env env = bridge_state->env; - std::lock_guard lock(g_mu); - // Keep parity with the previous bridge behavior (no custom foreground task hook). - // The runtime drives provider work through the event-loop checkpoint. - return napi_ok; -} - -extern "C" int snapi_bridge_unofficial_set_fatal_error_callbacks( +extern "C" int snapi_bridge_unofficial_attach_env( SnapiEnvState* env_state, uint32_t /*fatal_callback_id*/, - uint32_t /*oom_callback_id*/) { + uint32_t /*oom_callback_id*/, + uint64_t* accepted_hooks_out) { auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; + if (bridge_state == nullptr || accepted_hooks_out == nullptr) return napi_invalid_arg; napi_env env = bridge_state->env; std::lock_guard lock(g_mu); - return unofficial_napi_set_fatal_error_callbacks(env, nullptr, nullptr); + // Guest callbacks cannot be installed as native provider function pointers. + // Guest lifecycle stays in the Wasmer bridge, while the provider receives + // the same single immutable attachment transition with empty native hooks. + unofficial_napi_env_hooks hooks{}; + hooks.size = sizeof(hooks); + hooks.version = UNOFFICIAL_NAPI_ENV_HOOKS_VERSION; + return unofficial_napi_attach_env(env, &hooks, accepted_hooks_out); } extern "C" int snapi_bridge_unofficial_terminate_execution(SnapiEnvState* env_state) { @@ -3335,6 +3331,25 @@ extern "C" int snapi_bridge_unofficial_set_promise_reject_callback(SnapiEnvState return unofficial_napi_set_promise_reject_callback(env, callback); } +extern "C" int snapi_bridge_unofficial_get_own_non_index_properties( + SnapiEnvState* env_state, + uint32_t value_id, + uint32_t filter_bits, + uint32_t* out_id) { + auto* bridge_state = RequireEnvState(env_state); + if (bridge_state == nullptr || out_id == nullptr) return napi_invalid_arg; + napi_env env = bridge_state->env; + std::lock_guard lock(g_mu); + napi_value value = LoadValue(*bridge_state, value_id); + if (value == nullptr) return napi_invalid_arg; + napi_value result = nullptr; + napi_status status = unofficial_napi_get_own_non_index_properties( + env, value, filter_bits, &result); + if (status != napi_ok) return status; + *out_id = StoreValue(*bridge_state, result); + return *out_id != 0 ? napi_ok : napi_generic_failure; +} + extern "C" int snapi_bridge_unofficial_set_promise_hooks( SnapiEnvState* env_state, uint32_t init_callback_id, @@ -3360,39 +3375,6 @@ extern "C" int snapi_bridge_unofficial_set_promise_hooks( return unofficial_napi_set_promise_hooks(env, init, before, after, resolve); } -extern "C" int snapi_bridge_unofficial_get_own_non_index_properties( - SnapiEnvState* env_state, - uint32_t value_id, - uint32_t filter_bits, - uint32_t* out_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; - napi_env env = bridge_state->env; - std::lock_guard lock(g_mu); - napi_value value = LoadValue(*bridge_state, value_id); - if (value == nullptr) return napi_invalid_arg; - napi_value result = nullptr; - napi_status s = - unofficial_napi_get_own_non_index_properties(env, value, filter_bits, &result); - if (s != napi_ok) return s; - if (out_id != nullptr) *out_id = StoreValue(*bridge_state, result); - return napi_ok; -} - -extern "C" int snapi_bridge_unofficial_get_process_memory_info( - SnapiEnvState* env_state, - double* heap_total_out, - double* heap_used_out, - double* external_out, - double* array_buffers_out) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; - napi_env env = bridge_state->env; - std::lock_guard lock(g_mu); - return unofficial_napi_get_process_memory_info( - env, heap_total_out, heap_used_out, external_out, array_buffers_out); -} - extern "C" int snapi_bridge_unofficial_get_hash_seed(SnapiEnvState* env_state, uint64_t* hash_seed_out) { auto* bridge_state = RequireEnvState(env_state); @@ -3406,11 +3388,14 @@ extern "C" int snapi_bridge_unofficial_get_hash_seed(SnapiEnvState* env_state, return unofficial_napi_get_hash_seed(env, hash_seed_out); } -extern "C" int snapi_bridge_unofficial_get_error_source_positions( +extern "C" int snapi_bridge_unofficial_get_error_metadata( SnapiEnvState* env_state, uint32_t error_id, + int mode, uint32_t* source_line_out, uint32_t* script_resource_name_out, + uint32_t* stderr_line_out, + uint32_t* thrown_at_out, int32_t* line_number_out, int32_t* start_column_out, int32_t* end_column_out) { @@ -3420,35 +3405,35 @@ extern "C" int snapi_bridge_unofficial_get_error_source_positions( std::lock_guard lock(g_mu); napi_value error = LoadValue(*bridge_state, error_id); if (error == nullptr) return napi_invalid_arg; - unofficial_napi_error_source_positions positions{}; - napi_status s = - unofficial_napi_get_error_source_positions(env, error, &positions); + unofficial_napi_error_metadata metadata{}; + napi_status s = unofficial_napi_get_error_metadata( + env, + error, + static_cast(mode), + &metadata); if (s != napi_ok) return s; if (source_line_out != nullptr) { - *source_line_out = StoreValue(*bridge_state, positions.source_line); + *source_line_out = StoreValue(*bridge_state, metadata.source_line); } if (script_resource_name_out != nullptr) { *script_resource_name_out = - StoreValue(*bridge_state, positions.script_resource_name); + StoreValue(*bridge_state, metadata.script_resource_name); + } + if (stderr_line_out != nullptr) { + *stderr_line_out = StoreValue(*bridge_state, metadata.stderr_line); } - if (line_number_out != nullptr) *line_number_out = positions.line_number; - if (start_column_out != nullptr) *start_column_out = positions.start_column; - if (end_column_out != nullptr) *end_column_out = positions.end_column; + if (thrown_at_out != nullptr) { + *thrown_at_out = StoreValue(*bridge_state, metadata.thrown_at); + } + if (line_number_out != nullptr) *line_number_out = metadata.line_number; + if (start_column_out != nullptr) *start_column_out = metadata.start_column; + if (end_column_out != nullptr) *end_column_out = metadata.end_column; return napi_ok; } -extern "C" int snapi_bridge_unofficial_set_source_maps_enabled( - SnapiEnvState* env_state, - int enabled) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; - napi_env env = bridge_state->env; - std::lock_guard lock(g_mu); - return unofficial_napi_set_source_maps_enabled(env, enabled != 0); -} - -extern "C" int snapi_bridge_unofficial_set_get_source_map_error_source_callback( +extern "C" int snapi_bridge_unofficial_configure_source_maps( SnapiEnvState* env_state, + int enabled, uint32_t callback_id) { auto* bridge_state = RequireEnvState(env_state); if (bridge_state == nullptr) return napi_invalid_arg; @@ -3456,62 +3441,7 @@ extern "C" int snapi_bridge_unofficial_set_get_source_map_error_source_callback( std::lock_guard lock(g_mu); napi_value callback = callback_id == 0 ? nullptr : LoadValue(*bridge_state, callback_id); if (callback_id != 0 && callback == nullptr) return napi_invalid_arg; - return unofficial_napi_set_get_source_map_error_source_callback(env, callback); -} - -extern "C" int snapi_bridge_unofficial_get_error_source_line_for_stderr( - SnapiEnvState* env_state, - uint32_t error_id, - uint32_t* result_out) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; - napi_env env = bridge_state->env; - std::lock_guard lock(g_mu); - napi_value error = LoadValue(*bridge_state, error_id); - if (error == nullptr) return napi_invalid_arg; - napi_value result = nullptr; - napi_status s = unofficial_napi_get_error_source_line_for_stderr(env, error, &result); - if (s != napi_ok) return s; - if (result_out != nullptr) *result_out = StoreValue(*bridge_state, result); - return napi_ok; -} - -extern "C" int snapi_bridge_unofficial_get_error_thrown_at( - SnapiEnvState* env_state, - uint32_t error_id, - uint32_t* result_out) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; - napi_env env = bridge_state->env; - std::lock_guard lock(g_mu); - napi_value error = LoadValue(*bridge_state, error_id); - if (error == nullptr) return napi_invalid_arg; - napi_value result = nullptr; - napi_status s = unofficial_napi_get_error_thrown_at(env, error, &result); - if (s != napi_ok) return s; - if (result_out != nullptr) *result_out = StoreValue(*bridge_state, result); - return napi_ok; -} - -extern "C" int snapi_bridge_unofficial_take_preserved_error_formatting( - SnapiEnvState* env_state, - uint32_t error_id, - uint32_t* source_line_out, - uint32_t* thrown_at_out) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; - napi_env env = bridge_state->env; - std::lock_guard lock(g_mu); - napi_value error = LoadValue(*bridge_state, error_id); - if (error == nullptr) return napi_invalid_arg; - napi_value source_line = nullptr; - napi_value thrown_at = nullptr; - napi_status s = unofficial_napi_take_preserved_error_formatting( - env, error, &source_line, &thrown_at); - if (s != napi_ok) return s; - if (source_line_out != nullptr) *source_line_out = StoreValue(*bridge_state, source_line); - if (thrown_at_out != nullptr) *thrown_at_out = StoreValue(*bridge_state, thrown_at); - return napi_ok; + return unofficial_napi_configure_source_maps(env, enabled != 0, callback); } extern "C" int snapi_bridge_unofficial_preserve_error_source_message( @@ -3552,32 +3482,19 @@ extern "C" int snapi_bridge_unofficial_get_heap_statistics( return unofficial_napi_get_heap_statistics(env, stats_out); } -extern "C" int snapi_bridge_unofficial_get_heap_space_count(SnapiEnvState* env_state, - uint32_t* count_out) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; - napi_env env = bridge_state->env; - std::lock_guard lock(g_mu); - if ( - count_out == nullptr) { - return napi_invalid_arg; - } - return unofficial_napi_get_heap_space_count(env, count_out); -} - extern "C" int snapi_bridge_unofficial_get_heap_space_statistics( SnapiEnvState* env_state, - uint32_t space_index, - unofficial_napi_heap_space_statistics* stats_out) { + unofficial_napi_heap_space_statistics* stats_out, + uint32_t capacity, + uint32_t* count_out) { auto* bridge_state = RequireEnvState(env_state); if (bridge_state == nullptr) return napi_invalid_arg; napi_env env = bridge_state->env; std::lock_guard lock(g_mu); - if ( - stats_out == nullptr) { + if (count_out == nullptr || (capacity > 0 && stats_out == nullptr)) { return napi_invalid_arg; } - return unofficial_napi_get_heap_space_statistics(env, space_index, stats_out); + return unofficial_napi_get_heap_space_statistics(env, stats_out, capacity, count_out); } extern "C" int snapi_bridge_unofficial_get_heap_code_statistics( @@ -3594,89 +3511,46 @@ extern "C" int snapi_bridge_unofficial_get_heap_code_statistics( return unofficial_napi_get_heap_code_statistics(env, stats_out); } -extern "C" int snapi_bridge_unofficial_start_cpu_profile(SnapiEnvState* env_state, - int32_t* result_out, - uint32_t* profile_id_out) { +extern "C" int snapi_bridge_unofficial_profile_start(SnapiEnvState* env_state, + int32_t kind, + int32_t* result_out, + uint32_t* profile_out) { auto* bridge_state = RequireEnvState(env_state); if (bridge_state == nullptr) return napi_invalid_arg; napi_env env = bridge_state->env; std::lock_guard lock(g_mu); - if ( - result_out == nullptr || profile_id_out == nullptr) { + if (result_out == nullptr || profile_out == nullptr) { return napi_invalid_arg; } - unofficial_napi_cpu_profile_start_result result = - unofficial_napi_cpu_profile_start_ok; - napi_status s = - unofficial_napi_start_cpu_profile(env, &result, profile_id_out); + unofficial_napi_profile_start_result result = unofficial_napi_profile_start_ok; + unofficial_napi_profile profile = nullptr; + napi_status s = unofficial_napi_profile_start( + env, static_cast(kind), &result, &profile); if (s != napi_ok) return s; *result_out = static_cast(result); + *profile_out = bridge_state->profile_handles.Store( + reinterpret_cast(profile)); return napi_ok; } -extern "C" int snapi_bridge_unofficial_stop_cpu_profile(SnapiEnvState* env_state, - uint32_t profile_id, - int* found_out, - uint64_t* json_out, - uint32_t* json_len_out) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; - napi_env env = bridge_state->env; - std::lock_guard lock(g_mu); - if ( - found_out == nullptr || json_out == nullptr || json_len_out == nullptr) { - return napi_invalid_arg; - } - bool found = false; - char* json = nullptr; - size_t json_len = 0; - napi_status s = unofficial_napi_stop_cpu_profile(env, profile_id, &found, &json, - &json_len); - if (s != napi_ok) return s; - *found_out = found ? 1 : 0; - *json_out = static_cast(reinterpret_cast(json)); - *json_len_out = static_cast(json_len); - return napi_ok; -} - -extern "C" int snapi_bridge_unofficial_start_heap_profile(SnapiEnvState* env_state, - int* started_out) { +extern "C" int snapi_bridge_unofficial_profile_stop(SnapiEnvState* env_state, + uint32_t profile_id, + uint32_t* json_out) { auto* bridge_state = RequireEnvState(env_state); if (bridge_state == nullptr) return napi_invalid_arg; napi_env env = bridge_state->env; std::lock_guard lock(g_mu); - if ( - started_out == nullptr) { - return napi_invalid_arg; - } - bool started = false; - napi_status s = unofficial_napi_start_heap_profile(env, &started); - if (s != napi_ok) return s; - *started_out = started ? 1 : 0; - return napi_ok; -} - -extern "C" int snapi_bridge_unofficial_stop_heap_profile(SnapiEnvState* env_state, - int* found_out, - uint64_t* json_out, - uint32_t* json_len_out) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; - napi_env env = bridge_state->env; - std::lock_guard lock(g_mu); - if ( - found_out == nullptr || json_out == nullptr || json_len_out == nullptr) { - return napi_invalid_arg; + if (json_out == nullptr) return napi_invalid_arg; + auto profile = reinterpret_cast( + bridge_state->profile_handles.Load(profile_id)); + if (profile == nullptr) return napi_invalid_arg; + napi_value json = nullptr; + napi_status s = unofficial_napi_profile_stop(env, profile, &json); + if (s != napi_cannot_run_js && s != napi_invalid_arg) { + (void)bridge_state->profile_handles.Take(profile_id); } - bool found = false; - char* json = nullptr; - size_t json_len = 0; - napi_status s = - unofficial_napi_stop_heap_profile(env, &found, &json, &json_len); if (s != napi_ok) return s; - *found_out = found ? 1 : 0; - *json_out = static_cast(reinterpret_cast(json)); - *json_len_out = static_cast(json_len); + *json_out = json != nullptr ? StoreValue(*bridge_state, json) : 0; return napi_ok; } @@ -3684,52 +3558,29 @@ extern "C" int snapi_bridge_unofficial_take_heap_snapshot( SnapiEnvState* env_state, int expose_internals, int expose_numeric_values, - uint64_t* json_out, - uint32_t* json_len_out) { + uint32_t* json_out) { auto* bridge_state = RequireEnvState(env_state); if (bridge_state == nullptr) return napi_invalid_arg; napi_env env = bridge_state->env; std::lock_guard lock(g_mu); - if ( - json_out == nullptr || json_len_out == nullptr) { + if (json_out == nullptr) { return napi_invalid_arg; } unofficial_napi_heap_snapshot_options options{}; options.expose_internals = expose_internals != 0; options.expose_numeric_values = expose_numeric_values != 0; - char* json = nullptr; - size_t json_len = 0; - napi_status s = - unofficial_napi_take_heap_snapshot(env, &options, &json, &json_len); + napi_value json = nullptr; + napi_status s = unofficial_napi_take_heap_snapshot(env, &options, &json); if (s != napi_ok) return s; - *json_out = static_cast(reinterpret_cast(json)); - *json_len_out = static_cast(json_len); + *json_out = json != nullptr ? StoreValue(*bridge_state, json) : 0; return napi_ok; } -extern "C" void snapi_bridge_unofficial_free_buffer(void* data) { - if (data != nullptr) { - unofficial_napi_free_buffer(data); - } -} - -extern "C" int snapi_bridge_unofficial_structured_clone(SnapiEnvState* env_state, - uint32_t value_id, - uint32_t* out_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; - napi_env env = bridge_state->env; - std::lock_guard lock(g_mu); - napi_value value = LoadValue(*bridge_state, value_id); - if (value == nullptr) return napi_invalid_arg; - napi_value result = nullptr; - napi_status s = unofficial_napi_structured_clone(env, value, &result); - if (s != napi_ok) return s; - if (out_id != nullptr) *out_id = StoreValue(*bridge_state, result); - return napi_ok; +extern "C" void snapi_bridge_free_buffer(void* data) { + std::free(data); } -extern "C" int snapi_bridge_unofficial_structured_clone_with_transfer( +extern "C" int snapi_bridge_unofficial_structured_clone( SnapiEnvState* env_state, uint32_t value_id, uint32_t transfer_list_id, @@ -3746,13 +3597,65 @@ extern "C" int snapi_bridge_unofficial_structured_clone_with_transfer( napi_value transfer_list = transfer_list_id == 0 ? nullptr : LoadValue(*bridge_state, transfer_list_id); napi_value result = nullptr; - napi_status s = unofficial_napi_structured_clone_with_transfer( - env, value, transfer_list, &result); + napi_status s = unofficial_napi_structured_clone(env, value, transfer_list, &result); if (s != napi_ok) return s; if (out_id != nullptr) *out_id = StoreValue(*bridge_state, result); return napi_ok; } +extern "C" int snapi_bridge_unofficial_message_create( + SnapiEnvState* env_state, + uint32_t value_id, + uint32_t* message_out) { + auto* bridge_state = RequireEnvState(env_state); + if (bridge_state == nullptr || message_out == nullptr) return napi_invalid_arg; + std::lock_guard lock(g_mu); + napi_value value = LoadValue(*bridge_state, value_id); + if (value == nullptr) return napi_invalid_arg; + unofficial_napi_message message = nullptr; + napi_status status = + unofficial_napi_message_create(bridge_state->env, value, &message); + if (status != napi_ok) return status; + const uint32_t message_id = + g_message_handles.Store(reinterpret_cast(message)); + if (message_id == 0) { + unofficial_napi_message_drop(message); + return napi_generic_failure; + } + *message_out = message_id; + return napi_ok; +} + +extern "C" int snapi_bridge_unofficial_message_take( + SnapiEnvState* env_state, + uint32_t message_id, + uint32_t* value_out) { + std::lock_guard lock(g_mu); + auto* message = reinterpret_cast( + g_message_handles.Take(message_id)); + if (message == nullptr) return napi_invalid_arg; + auto* bridge_state = RequireEnvState(env_state); + if (bridge_state == nullptr || value_out == nullptr) { + unofficial_napi_message_drop(message); + return napi_invalid_arg; + } + napi_value value = nullptr; + napi_status status = + unofficial_napi_message_take(bridge_state->env, message, &value); + if (status != napi_ok) return status; + const uint32_t value_id = StoreValue(*bridge_state, value); + if (value_id == 0) return napi_generic_failure; + *value_out = value_id; + return napi_ok; +} + +extern "C" void snapi_bridge_unofficial_message_drop(uint32_t message_id) { + std::lock_guard lock(g_mu); + auto* message = reinterpret_cast( + g_message_handles.Take(message_id)); + if (message != nullptr) unofficial_napi_message_drop(message); +} + extern "C" int snapi_bridge_unofficial_notify_datetime_configuration_change( SnapiEnvState* env_state) { auto* bridge_state = RequireEnvState(env_state); @@ -3857,14 +3760,17 @@ extern "C" int snapi_bridge_unofficial_contextify_run_script( std::lock_guard lock(g_mu); napi_value sandbox_or_null = sandbox_or_null_id == 0 ? nullptr : LoadValue(*bridge_state, sandbox_or_null_id); napi_value source_text = source_text_id == 0 ? nullptr : LoadValue(*bridge_state, source_text_id); - void* source_bytecode = LoadBytecodeHandle(*bridge_state, source_bytecode_id); + unofficial_napi_bytecode source_bytecode = + LoadBytecodeHandle(*bridge_state, source_bytecode_id); napi_value filename = LoadValue(*bridge_state, filename_id); napi_value host_defined_option = host_defined_option_id == 0 ? nullptr : LoadValue(*bridge_state, host_defined_option_id); if (sandbox_or_null_id != 0 && sandbox_or_null == nullptr) return napi_invalid_arg; if ((source_text == nullptr && source_bytecode == nullptr) || filename == nullptr) return napi_invalid_arg; if (host_defined_option_id != 0 && host_defined_option == nullptr) return napi_invalid_arg; - const unofficial_napi_js_source source{source_text, source_bytecode}; + const unofficial_napi_js_source source = source_text != nullptr + ? unofficial_napi_js_source_from_text(source_text) + : unofficial_napi_js_source_from_bytecode(source_bytecode); napi_value result = nullptr; napi_status s = unofficial_napi_contextify_run_script( env, @@ -3884,18 +3790,6 @@ extern "C" int snapi_bridge_unofficial_contextify_run_script( return napi_ok; } -extern "C" int snapi_bridge_unofficial_contextify_dispose_context( - SnapiEnvState* env_state, - uint32_t sandbox_or_context_global_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; - napi_env env = bridge_state->env; - std::lock_guard lock(g_mu); - napi_value sandbox_or_context_global = LoadValue(*bridge_state, sandbox_or_context_global_id); - if (sandbox_or_context_global == nullptr) return napi_invalid_arg; - return unofficial_napi_contextify_dispose_context(env, sandbox_or_context_global); -} - extern "C" int snapi_bridge_unofficial_contextify_compile_function( SnapiEnvState* env_state, uint32_t source_text_id, @@ -3913,7 +3807,8 @@ extern "C" int snapi_bridge_unofficial_contextify_compile_function( napi_env env = bridge_state->env; std::lock_guard lock(g_mu); napi_value source_text = source_text_id == 0 ? nullptr : LoadValue(*bridge_state, source_text_id); - void* source_bytecode = LoadBytecodeHandle(*bridge_state, source_bytecode_id); + unofficial_napi_bytecode source_bytecode = + LoadBytecodeHandle(*bridge_state, source_bytecode_id); napi_value filename = LoadValue(*bridge_state, filename_id); napi_value parsing_context = parsing_context_id == 0 ? nullptr : LoadValue(*bridge_state, parsing_context_id); napi_value context_extensions = @@ -3926,7 +3821,9 @@ extern "C" int snapi_bridge_unofficial_contextify_compile_function( if (context_extensions_id != 0 && context_extensions == nullptr) return napi_invalid_arg; if (params_id != 0 && params == nullptr) return napi_invalid_arg; if (host_defined_option_id != 0 && host_defined_option == nullptr) return napi_invalid_arg; - const unofficial_napi_js_source source{source_text, source_bytecode}; + const unofficial_napi_js_source source = source_text != nullptr + ? unofficial_napi_js_source_from_text(source_text) + : unofficial_napi_js_source_from_bytecode(source_bytecode); napi_value result = nullptr; napi_status s = unofficial_napi_contextify_compile_function( env, @@ -3944,32 +3841,7 @@ extern "C" int snapi_bridge_unofficial_contextify_compile_function( return napi_ok; } -extern "C" int snapi_bridge_unofficial_contextify_compile_function_for_cjs_loader( - SnapiEnvState* env_state, - uint32_t source_text_id, - uint32_t source_bytecode_id, - uint32_t filename_id, - int is_sea_main, - int should_detect_module, - uint32_t* result_out) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; - napi_env env = bridge_state->env; - std::lock_guard lock(g_mu); - napi_value source_text = source_text_id == 0 ? nullptr : LoadValue(*bridge_state, source_text_id); - void* source_bytecode = LoadBytecodeHandle(*bridge_state, source_bytecode_id); - napi_value filename = LoadValue(*bridge_state, filename_id); - if ((source_text == nullptr && source_bytecode == nullptr) || filename == nullptr) return napi_invalid_arg; - const unofficial_napi_js_source source{source_text, source_bytecode}; - napi_value result = nullptr; - napi_status s = unofficial_napi_contextify_compile_function_for_cjs_loader( - env, &source, filename, is_sea_main != 0, should_detect_module != 0, &result); - if (s != napi_ok) return s; - if (result_out != nullptr) *result_out = StoreValue(*bridge_state, result); - return napi_ok; -} - -extern "C" int snapi_bridge_unofficial_bytecode_compile( +extern "C" int snapi_bridge_unofficial_bytecode_open( SnapiEnvState* env_state, uint32_t source_text_id, uint32_t filename_id, @@ -3978,7 +3850,12 @@ extern "C" int snapi_bridge_unofficial_bytecode_compile( uint32_t host_defined_option_id, int32_t line_offset, int32_t column_offset, + const uint8_t* cache_bytes, + size_t cache_byte_length, + uint8_t has_cache, + uint8_t cache_policy, uint32_t* bytecode_out, + uint8_t* cache_rejected_out, uint8_t* can_parse_as_module_out) { auto* bridge_state = RequireEnvState(env_state); if (bridge_state == nullptr) return napi_invalid_arg; @@ -3992,50 +3869,30 @@ extern "C" int snapi_bridge_unofficial_bytecode_compile( if (source_text == nullptr || filename == nullptr) return napi_invalid_arg; if (params_id != 0 && params == nullptr) return napi_invalid_arg; if (host_defined_option_id != 0 && host_defined_option == nullptr) return napi_invalid_arg; - void* bytecode = nullptr; - bool can_parse_as_module = false; - napi_status s = unofficial_napi_bytecode_compile( - env, source_text, filename, shape, params, host_defined_option, line_offset, column_offset, - &bytecode, &can_parse_as_module); - if (can_parse_as_module_out != nullptr) *can_parse_as_module_out = can_parse_as_module ? 1 : 0; - if (s != napi_ok) return s; - if (bytecode_out != nullptr) *bytecode_out = StoreBytecodeHandle(*bridge_state, bytecode); - return napi_ok; -} - -extern "C" int snapi_bridge_unofficial_bytecode_deserialize( - SnapiEnvState* env_state, - const uint8_t* bytes, - size_t byte_length, - uint32_t source_text_id, - uint32_t filename_id, - int32_t shape, - uint32_t params_id, - uint32_t host_defined_option_id, - uint32_t* bytecode_out, - uint8_t* rejected_out) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; - napi_env env = bridge_state->env; - std::lock_guard lock(g_mu); - napi_value source_text = LoadValue(*bridge_state, source_text_id); - napi_value filename = LoadValue(*bridge_state, filename_id); - napi_value params = params_id == 0 ? nullptr : LoadValue(*bridge_state, params_id); - if (bytes == nullptr || byte_length == 0 || source_text == nullptr || filename == nullptr) { - return napi_invalid_arg; + unofficial_napi_bytecode_open_options options{}; + options.size = sizeof(options); + options.version = UNOFFICIAL_NAPI_BYTECODE_OPEN_OPTIONS_VERSION; + options.source_text = source_text; + options.filename = filename; + options.shape = shape; + options.params_or_undefined = params; + options.host_defined_option_id = host_defined_option; + options.line_offset = line_offset; + options.column_offset = column_offset; + options.cache_bytes = cache_bytes; + options.cache_byte_length = cache_byte_length; + options.has_cache = has_cache; + options.cache_policy = cache_policy; + unofficial_napi_bytecode_open_result result{}; + napi_status s = unofficial_napi_bytecode_open(env, &options, &result); + if (cache_rejected_out != nullptr) *cache_rejected_out = result.cache_rejected; + if (can_parse_as_module_out != nullptr) { + *can_parse_as_module_out = result.can_parse_as_module; } - if (params_id != 0 && params == nullptr) return napi_invalid_arg; - napi_value host_defined_option = - host_defined_option_id == 0 ? nullptr : LoadValue(*bridge_state, host_defined_option_id); - if (host_defined_option_id != 0 && host_defined_option == nullptr) return napi_invalid_arg; - void* bytecode = nullptr; - bool rejected = false; - napi_status s = unofficial_napi_bytecode_deserialize( - env, bytes, byte_length, source_text, filename, shape, params, host_defined_option, - &bytecode, &rejected); - if (rejected_out != nullptr) *rejected_out = rejected ? 1 : 0; if (s != napi_ok) return s; - if (bytecode_out != nullptr) *bytecode_out = StoreBytecodeHandle(*bridge_state, bytecode); + if (bytecode_out != nullptr) { + *bytecode_out = StoreBytecodeHandle(*bridge_state, result.bytecode); + } return napi_ok; } @@ -4047,7 +3904,7 @@ extern "C" int snapi_bridge_unofficial_bytecode_serialize( if (bridge_state == nullptr) return napi_invalid_arg; napi_env env = bridge_state->env; std::lock_guard lock(g_mu); - void* bytecode = LoadBytecodeHandle(*bridge_state, bytecode_id); + unofficial_napi_bytecode bytecode = LoadBytecodeHandle(*bridge_state, bytecode_id); if (bytecode == nullptr) return napi_invalid_arg; napi_value buffer = nullptr; napi_status s = unofficial_napi_bytecode_serialize(env, bytecode, &buffer); @@ -4063,61 +3920,16 @@ extern "C" int snapi_bridge_unofficial_bytecode_release( if (bridge_state == nullptr) return napi_invalid_arg; napi_env env = bridge_state->env; std::lock_guard lock(g_mu); - void* bytecode = LoadBytecodeHandle(*bridge_state, bytecode_id); + unofficial_napi_bytecode bytecode = LoadBytecodeHandle(*bridge_state, bytecode_id); if (bytecode == nullptr) return napi_invalid_arg; napi_status s = unofficial_napi_bytecode_release(env, bytecode); RemoveBytecodeHandle(*bridge_state, bytecode_id); return s; } -extern "C" int snapi_bridge_unofficial_contextify_start_sigint_watchdog( - SnapiEnvState* env_state, - int* result_out) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; - napi_env env = bridge_state->env; - std::lock_guard lock(g_mu); - if (result_out == nullptr) return napi_invalid_arg; - bool result = false; - napi_status s = unofficial_napi_contextify_start_sigint_watchdog(env, &result); - if (s != napi_ok) return s; - *result_out = result ? 1 : 0; - return napi_ok; -} - -extern "C" int snapi_bridge_unofficial_contextify_stop_sigint_watchdog( - SnapiEnvState* env_state, - int* had_pending_signal_out) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; - napi_env env = bridge_state->env; - std::lock_guard lock(g_mu); - if (had_pending_signal_out == nullptr) return napi_invalid_arg; - bool had_pending_signal = false; - napi_status s = - unofficial_napi_contextify_stop_sigint_watchdog(env, &had_pending_signal); - if (s != napi_ok) return s; - *had_pending_signal_out = had_pending_signal ? 1 : 0; - return napi_ok; -} - -extern "C" int snapi_bridge_unofficial_contextify_watchdog_has_pending_sigint( - SnapiEnvState* env_state, - int* result_out) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; - napi_env env = bridge_state->env; - std::lock_guard lock(g_mu); - if (result_out == nullptr) return napi_invalid_arg; - bool result = false; - napi_status s = unofficial_napi_contextify_watchdog_has_pending_sigint(env, &result); - if (s != napi_ok) return s; - *result_out = result ? 1 : 0; - return napi_ok; -} - -extern "C" int snapi_bridge_unofficial_module_wrap_create_source_text( +extern "C" int snapi_bridge_unofficial_module_wrap_create( SnapiEnvState* env_state, + int32_t kind, uint32_t wrapper_id, uint32_t url_id, uint32_t context_id, @@ -4126,7 +3938,11 @@ extern "C" int snapi_bridge_unofficial_module_wrap_create_source_text( int32_t line_offset, int32_t column_offset, uint32_t host_defined_option_id, - uint32_t* handle_out) { + uint32_t export_names_id, + uint32_t synthetic_eval_steps_id, + uint32_t* handle_out, + uint32_t* requests_out, + uint8_t* has_top_level_await_out) { auto* bridge_state = RequireEnvState(env_state); if (bridge_state == nullptr) return napi_invalid_arg; napi_env env = bridge_state->env; @@ -4135,51 +3951,71 @@ extern "C" int snapi_bridge_unofficial_module_wrap_create_source_text( napi_value url = LoadValue(*bridge_state, url_id); napi_value context = context_id == 0 ? nullptr : LoadValue(*bridge_state, context_id); napi_value source_text = source_text_id == 0 ? nullptr : LoadValue(*bridge_state, source_text_id); - void* source_bytecode = LoadBytecodeHandle(*bridge_state, source_bytecode_id); + unofficial_napi_bytecode source_bytecode = + LoadBytecodeHandle(*bridge_state, source_bytecode_id); napi_value host_defined_option = host_defined_option_id == 0 ? nullptr : LoadValue(*bridge_state, host_defined_option_id); - if (wrapper == nullptr || url == nullptr || - (source_text == nullptr && source_bytecode == nullptr)) { - return napi_invalid_arg; - } + napi_value export_names = + export_names_id == 0 ? nullptr : LoadValue(*bridge_state, export_names_id); + napi_value synthetic_eval_steps = synthetic_eval_steps_id == 0 + ? nullptr + : LoadValue(*bridge_state, synthetic_eval_steps_id); + if (wrapper == nullptr || url == nullptr) return napi_invalid_arg; if (context_id != 0 && context == nullptr) return napi_invalid_arg; if (host_defined_option_id != 0 && host_defined_option == nullptr) return napi_invalid_arg; - const unofficial_napi_js_source source{source_text, source_bytecode}; - void* handle = nullptr; - napi_status s = unofficial_napi_module_wrap_create_source_text( - env, wrapper, url, context, &source, line_offset, column_offset, host_defined_option, &handle); - if (s != napi_ok) return s; - if (handle_out != nullptr) *handle_out = StoreModuleWrapHandle(*bridge_state, handle); - return napi_ok; -} - -extern "C" int snapi_bridge_unofficial_module_wrap_create_synthetic( - SnapiEnvState* env_state, - uint32_t wrapper_id, - uint32_t url_id, - uint32_t context_id, - uint32_t export_names_id, - uint32_t synthetic_eval_steps_id, - uint32_t* handle_out) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; - napi_env env = bridge_state->env; - std::lock_guard lock(g_mu); - napi_value wrapper = LoadValue(*bridge_state, wrapper_id); - napi_value url = LoadValue(*bridge_state, url_id); - napi_value context = context_id == 0 ? nullptr : LoadValue(*bridge_state, context_id); - napi_value export_names = LoadValue(*bridge_state, export_names_id); - napi_value synthetic_eval_steps = LoadValue(*bridge_state, synthetic_eval_steps_id); - if (wrapper == nullptr || url == nullptr || export_names == nullptr || - synthetic_eval_steps == nullptr) { + if (export_names_id != 0 && export_names == nullptr) return napi_invalid_arg; + if (synthetic_eval_steps_id != 0 && synthetic_eval_steps == nullptr) { return napi_invalid_arg; } - if (context_id != 0 && context == nullptr) return napi_invalid_arg; - void* handle = nullptr; - napi_status s = unofficial_napi_module_wrap_create_synthetic( - env, wrapper, url, context, export_names, synthetic_eval_steps, &handle); + + unofficial_napi_js_source source{}; + unofficial_napi_module_create_options options{}; + options.size = sizeof(options); + options.version = UNOFFICIAL_NAPI_MODULE_CREATE_OPTIONS_VERSION; + options.kind = static_cast(kind); + options.wrapper = wrapper; + options.url = url; + options.context_or_undefined = context; + switch (options.kind) { + case unofficial_napi_module_source_text: + if (source_text == nullptr && source_bytecode == nullptr) return napi_invalid_arg; + source = source_text != nullptr + ? unofficial_napi_js_source_from_text(source_text) + : unofficial_napi_js_source_from_bytecode(source_bytecode); + options.payload.source_text.source = &source; + options.payload.source_text.line_offset = line_offset; + options.payload.source_text.column_offset = column_offset; + options.payload.source_text.host_defined_option_id = host_defined_option; + break; + case unofficial_napi_module_synthetic: + if (export_names == nullptr || synthetic_eval_steps == nullptr) { + return napi_invalid_arg; + } + options.payload.synthetic.export_names = export_names; + options.payload.synthetic.synthetic_evaluation_steps = synthetic_eval_steps; + break; + default: + return napi_invalid_arg; + } + + unofficial_napi_module_create_result result{}; + napi_status s = unofficial_napi_module_wrap_create(env, &options, &result); if (s != napi_ok) return s; - if (handle_out != nullptr) *handle_out = StoreModuleWrapHandle(*bridge_state, handle); + if (result.module == nullptr || result.module_requests == nullptr) { + if (result.module != nullptr) { + (void)unofficial_napi_module_wrap_destroy(env, result.module); + } + return napi_generic_failure; + } + if (handle_out != nullptr) { + *handle_out = StoreModuleWrapHandle(*bridge_state, result.module); + } + if (requests_out != nullptr) { + *requests_out = StoreValue(*bridge_state, result.module_requests); + } + if (has_top_level_await_out != nullptr) { + *has_top_level_await_out = result.has_top_level_await ? 1 : 0; + } return napi_ok; } @@ -4189,30 +4025,13 @@ extern "C" int snapi_bridge_unofficial_module_wrap_destroy(SnapiEnvState* env_st if (bridge_state == nullptr) return napi_invalid_arg; napi_env env = bridge_state->env; std::lock_guard lock(g_mu); - void* handle = LoadModuleWrapHandle(*bridge_state, handle_id); - if (handle == nullptr) return napi_invalid_arg; - napi_status s = unofficial_napi_module_wrap_destroy(env, handle); + unofficial_napi_module module = LoadModuleWrapHandle(*bridge_state, handle_id); + if (module == nullptr) return napi_invalid_arg; + napi_status s = unofficial_napi_module_wrap_destroy(env, module); if (s == napi_ok) RemoveModuleWrapHandle(*bridge_state, handle_id); return s; } -extern "C" int snapi_bridge_unofficial_module_wrap_get_module_requests( - SnapiEnvState* env_state, - uint32_t handle_id, - uint32_t* result_out) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; - napi_env env = bridge_state->env; - std::lock_guard lock(g_mu); - void* handle = LoadModuleWrapHandle(*bridge_state, handle_id); - if (handle == nullptr) return napi_invalid_arg; - napi_value result = nullptr; - napi_status s = unofficial_napi_module_wrap_get_module_requests(env, handle, &result); - if (s != napi_ok) return s; - if (result_out != nullptr) *result_out = StoreValue(*bridge_state, result); - return napi_ok; -} - extern "C" int snapi_bridge_unofficial_module_wrap_link(SnapiEnvState* env_state, uint32_t handle_id, uint32_t count, @@ -4221,16 +4040,19 @@ extern "C" int snapi_bridge_unofficial_module_wrap_link(SnapiEnvState* env_state if (bridge_state == nullptr) return napi_invalid_arg; napi_env env = bridge_state->env; std::lock_guard lock(g_mu); - void* handle = LoadModuleWrapHandle(*bridge_state, handle_id); - if (handle == nullptr) return napi_invalid_arg; - std::vector linked_handles(count, nullptr); + unofficial_napi_module module = LoadModuleWrapHandle(*bridge_state, handle_id); + if (module == nullptr) return napi_invalid_arg; + std::vector linked_modules(count, nullptr); for (uint32_t i = 0; i < count; ++i) { - void* linked = linked_handle_ids != nullptr ? LoadModuleWrapHandle(*bridge_state, linked_handle_ids[i]) : nullptr; + unofficial_napi_module linked = + linked_handle_ids != nullptr + ? LoadModuleWrapHandle(*bridge_state, linked_handle_ids[i]) + : nullptr; if (linked == nullptr) return napi_invalid_arg; - linked_handles[i] = linked; + linked_modules[i] = linked; } return unofficial_napi_module_wrap_link( - env, handle, count, count == 0 ? nullptr : linked_handles.data()); + env, module, count, count == 0 ? nullptr : linked_modules.data()); } extern "C" int snapi_bridge_unofficial_module_wrap_instantiate(SnapiEnvState* env_state, @@ -4239,9 +4061,9 @@ extern "C" int snapi_bridge_unofficial_module_wrap_instantiate(SnapiEnvState* en if (bridge_state == nullptr) return napi_invalid_arg; napi_env env = bridge_state->env; std::lock_guard lock(g_mu); - void* handle = LoadModuleWrapHandle(*bridge_state, handle_id); - if (handle == nullptr) return napi_invalid_arg; - return unofficial_napi_module_wrap_instantiate(env, handle); + unofficial_napi_module module = LoadModuleWrapHandle(*bridge_state, handle_id); + if (module == nullptr) return napi_invalid_arg; + return unofficial_napi_module_wrap_instantiate(env, module); } extern "C" int snapi_bridge_unofficial_module_wrap_evaluate(SnapiEnvState* env_state, @@ -4253,11 +4075,11 @@ extern "C" int snapi_bridge_unofficial_module_wrap_evaluate(SnapiEnvState* env_s if (bridge_state == nullptr) return napi_invalid_arg; napi_env env = bridge_state->env; std::lock_guard lock(g_mu); - void* handle = LoadModuleWrapHandle(*bridge_state, handle_id); - if (handle == nullptr) return napi_invalid_arg; + unofficial_napi_module module = LoadModuleWrapHandle(*bridge_state, handle_id); + if (module == nullptr) return napi_invalid_arg; napi_value result = nullptr; napi_status s = unofficial_napi_module_wrap_evaluate( - env, handle, timeout, break_on_sigint != 0, &result); + env, module, timeout, break_on_sigint != 0, &result); if (s != napi_ok) return s; if (result_out != nullptr) *result_out = StoreValue(*bridge_state, result); return napi_ok; @@ -4272,8 +4094,8 @@ extern "C" int snapi_bridge_unofficial_module_wrap_evaluate_sync(SnapiEnvState* if (bridge_state == nullptr) return napi_invalid_arg; napi_env env = bridge_state->env; std::lock_guard lock(g_mu); - void* handle = LoadModuleWrapHandle(*bridge_state, handle_id); - if (handle == nullptr) return napi_invalid_arg; + unofficial_napi_module module = LoadModuleWrapHandle(*bridge_state, handle_id); + if (module == nullptr) return napi_invalid_arg; napi_value filename = filename_id == 0 ? nullptr : LoadValue(*bridge_state, filename_id); napi_value parent_filename = parent_filename_id == 0 ? nullptr : LoadValue(*bridge_state, parent_filename_id); @@ -4283,7 +4105,7 @@ extern "C" int snapi_bridge_unofficial_module_wrap_evaluate_sync(SnapiEnvState* } napi_value result = nullptr; napi_status s = unofficial_napi_module_wrap_evaluate_sync( - env, handle, filename, parent_filename, &result); + env, module, filename, parent_filename, &result); if (s != napi_ok) return s; if (result_out != nullptr) *result_out = StoreValue(*bridge_state, result); return napi_ok; @@ -4296,95 +4118,63 @@ extern "C" int snapi_bridge_unofficial_module_wrap_get_namespace(SnapiEnvState* if (bridge_state == nullptr) return napi_invalid_arg; napi_env env = bridge_state->env; std::lock_guard lock(g_mu); - void* handle = LoadModuleWrapHandle(*bridge_state, handle_id); - if (handle == nullptr) return napi_invalid_arg; + unofficial_napi_module module = LoadModuleWrapHandle(*bridge_state, handle_id); + if (module == nullptr) return napi_invalid_arg; napi_value result = nullptr; - napi_status s = unofficial_napi_module_wrap_get_namespace(env, handle, &result); + napi_status s = unofficial_napi_module_wrap_get_namespace(env, module, &result); if (s != napi_ok) return s; if (result_out != nullptr) *result_out = StoreValue(*bridge_state, result); return napi_ok; } -extern "C" int snapi_bridge_unofficial_module_wrap_get_status(SnapiEnvState* env_state, - uint32_t handle_id, - int32_t* status_out) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; - napi_env env = bridge_state->env; - std::lock_guard lock(g_mu); - void* handle = LoadModuleWrapHandle(*bridge_state, handle_id); - if (handle == nullptr) return napi_invalid_arg; - return unofficial_napi_module_wrap_get_status(env, handle, status_out); -} - -extern "C" int snapi_bridge_unofficial_module_wrap_get_error(SnapiEnvState* env_state, - uint32_t handle_id, - uint32_t* result_out) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; - napi_env env = bridge_state->env; - std::lock_guard lock(g_mu); - void* handle = LoadModuleWrapHandle(*bridge_state, handle_id); - if (handle == nullptr) return napi_invalid_arg; - napi_value result = nullptr; - napi_status s = unofficial_napi_module_wrap_get_error(env, handle, &result); - if (s != napi_ok) return s; - if (result_out != nullptr) *result_out = StoreValue(*bridge_state, result); - return napi_ok; -} - -extern "C" int snapi_bridge_unofficial_module_wrap_has_top_level_await( +extern "C" int snapi_bridge_unofficial_module_wrap_get_state( SnapiEnvState* env_state, uint32_t handle_id, - int* result_out) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; - napi_env env = bridge_state->env; - std::lock_guard lock(g_mu); - void* handle = LoadModuleWrapHandle(*bridge_state, handle_id); - if (handle == nullptr) return napi_invalid_arg; - bool result = false; - napi_status s = unofficial_napi_module_wrap_has_top_level_await(env, handle, &result); - if (s != napi_ok) return s; - if (result_out != nullptr) *result_out = result ? 1 : 0; - return napi_ok; -} - -extern "C" int snapi_bridge_unofficial_module_wrap_has_async_graph(SnapiEnvState* env_state, - uint32_t handle_id, - int* result_out) { + int32_t* status_out, + uint32_t* error_out, + int* has_async_graph_out) { auto* bridge_state = RequireEnvState(env_state); if (bridge_state == nullptr) return napi_invalid_arg; napi_env env = bridge_state->env; std::lock_guard lock(g_mu); - void* handle = LoadModuleWrapHandle(*bridge_state, handle_id); - if (handle == nullptr) return napi_invalid_arg; - bool result = false; - napi_status s = unofficial_napi_module_wrap_has_async_graph(env, handle, &result); - if (s != napi_ok) return s; - if (result_out != nullptr) *result_out = result ? 1 : 0; + unofficial_napi_module module = LoadModuleWrapHandle(*bridge_state, handle_id); + if (module == nullptr) return napi_invalid_arg; + if (status_out == nullptr && error_out == nullptr && + has_async_graph_out == nullptr) { + return napi_invalid_arg; + } + napi_value error = nullptr; + bool has_async_graph = false; + napi_status s = unofficial_napi_module_wrap_get_state( + env, + module, + status_out, + error_out != nullptr ? &error : nullptr, + has_async_graph_out != nullptr ? &has_async_graph : nullptr); + if (s != napi_ok) return s; + if (error_out != nullptr) *error_out = StoreValue(*bridge_state, error); + if (has_async_graph_out != nullptr) { + *has_async_graph_out = has_async_graph ? 1 : 0; + } return napi_ok; } extern "C" int snapi_bridge_unofficial_module_wrap_check_unsettled_top_level_await( SnapiEnvState* env_state, - uint32_t module_wrap_id, + uint32_t handle_id, int warnings, int* settled_out) { auto* bridge_state = RequireEnvState(env_state); if (bridge_state == nullptr) return napi_invalid_arg; napi_env env = bridge_state->env; std::lock_guard lock(g_mu); - if ( - settled_out == nullptr) { - return napi_invalid_arg; - } - napi_value module_wrap = - module_wrap_id == 0 ? nullptr : LoadValue(*bridge_state, module_wrap_id); - if (module_wrap_id != 0 && module_wrap == nullptr) return napi_invalid_arg; + if (settled_out == nullptr) return napi_invalid_arg; + unofficial_napi_module module = + LoadModuleWrapHandle(*bridge_state, handle_id); + if (module == nullptr) return napi_invalid_arg; bool settled = true; napi_status s = unofficial_napi_module_wrap_check_unsettled_top_level_await( - env, module_wrap, warnings != 0, &settled); + env, module, warnings != 0, &settled); if (s != napi_ok) return s; *settled_out = settled ? 1 : 0; return napi_ok; @@ -4398,12 +4188,12 @@ extern "C" int snapi_bridge_unofficial_module_wrap_set_export(SnapiEnvState* env if (bridge_state == nullptr) return napi_invalid_arg; napi_env env = bridge_state->env; std::lock_guard lock(g_mu); - void* handle = LoadModuleWrapHandle(*bridge_state, handle_id); + unofficial_napi_module module = LoadModuleWrapHandle(*bridge_state, handle_id); napi_value export_name = LoadValue(*bridge_state, export_name_id); napi_value export_value = export_value_id == 0 ? nullptr : LoadValue(*bridge_state, export_value_id); - if (handle == nullptr || export_name == nullptr) return napi_invalid_arg; + if (module == nullptr || export_name == nullptr) return napi_invalid_arg; if (export_value_id != 0 && export_value == nullptr) return napi_invalid_arg; - return unofficial_napi_module_wrap_set_export(env, handle, export_name, export_value); + return unofficial_napi_module_wrap_set_export(env, module, export_name, export_value); } extern "C" int snapi_bridge_unofficial_module_wrap_set_module_source_object( @@ -4414,11 +4204,11 @@ extern "C" int snapi_bridge_unofficial_module_wrap_set_module_source_object( if (bridge_state == nullptr) return napi_invalid_arg; napi_env env = bridge_state->env; std::lock_guard lock(g_mu); - void* handle = LoadModuleWrapHandle(*bridge_state, handle_id); + unofficial_napi_module module = LoadModuleWrapHandle(*bridge_state, handle_id); napi_value source_object = source_object_id == 0 ? nullptr : LoadValue(*bridge_state, source_object_id); - if (handle == nullptr) return napi_invalid_arg; + if (module == nullptr) return napi_invalid_arg; if (source_object_id != 0 && source_object == nullptr) return napi_invalid_arg; - return unofficial_napi_module_wrap_set_module_source_object(env, handle, source_object); + return unofficial_napi_module_wrap_set_module_source_object(env, module, source_object); } extern "C" int snapi_bridge_unofficial_module_wrap_get_module_source_object( @@ -4429,10 +4219,10 @@ extern "C" int snapi_bridge_unofficial_module_wrap_get_module_source_object( if (bridge_state == nullptr) return napi_invalid_arg; napi_env env = bridge_state->env; std::lock_guard lock(g_mu); - void* handle = LoadModuleWrapHandle(*bridge_state, handle_id); - if (handle == nullptr) return napi_invalid_arg; + unofficial_napi_module module = LoadModuleWrapHandle(*bridge_state, handle_id); + if (module == nullptr) return napi_invalid_arg; napi_value result = nullptr; - napi_status s = unofficial_napi_module_wrap_get_module_source_object(env, handle, &result); + napi_status s = unofficial_napi_module_wrap_get_module_source_object(env, module, &result); if (s != napi_ok) return s; if (result_out != nullptr) *result_out = StoreValue(*bridge_state, result); return napi_ok; @@ -4446,61 +4236,40 @@ extern "C" int snapi_bridge_unofficial_module_wrap_create_cached_data( if (bridge_state == nullptr) return napi_invalid_arg; napi_env env = bridge_state->env; std::lock_guard lock(g_mu); - void* handle = LoadModuleWrapHandle(*bridge_state, handle_id); - if (handle == nullptr) return napi_invalid_arg; + unofficial_napi_module module = LoadModuleWrapHandle(*bridge_state, handle_id); + if (module == nullptr) return napi_invalid_arg; napi_value result = nullptr; - napi_status s = unofficial_napi_module_wrap_create_cached_data(env, handle, &result); + napi_status s = unofficial_napi_module_wrap_create_cached_data(env, module, &result); if (s != napi_ok) return s; if (result_out != nullptr) *result_out = StoreValue(*bridge_state, result); return napi_ok; } -extern "C" int snapi_bridge_unofficial_module_wrap_set_import_module_dynamically_callback( - SnapiEnvState* env_state, - uint32_t callback_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; - napi_env env = bridge_state->env; - std::lock_guard lock(g_mu); - napi_value callback = callback_id == 0 ? nullptr : LoadValue(*bridge_state, callback_id); - if (callback_id != 0 && callback == nullptr) return napi_invalid_arg; - return unofficial_napi_module_wrap_set_import_module_dynamically_callback(env, callback); -} - -extern "C" int -snapi_bridge_unofficial_module_wrap_set_initialize_import_meta_object_callback( - SnapiEnvState* env_state, - uint32_t callback_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; - napi_env env = bridge_state->env; - std::lock_guard lock(g_mu); - napi_value callback = callback_id == 0 ? nullptr : LoadValue(*bridge_state, callback_id); - if (callback_id != 0 && callback == nullptr) return napi_invalid_arg; - return unofficial_napi_module_wrap_set_initialize_import_meta_object_callback(env, callback); -} - -extern "C" int snapi_bridge_unofficial_module_wrap_import_module_dynamically( +extern "C" int snapi_bridge_unofficial_module_wrap_set_hooks( SnapiEnvState* env_state, - uint32_t argc, - const uint32_t* argv_ids, - uint32_t* result_out) { + uint32_t import_callback_id, + uint32_t import_meta_callback_id) { auto* bridge_state = RequireEnvState(env_state); if (bridge_state == nullptr) return napi_invalid_arg; napi_env env = bridge_state->env; std::lock_guard lock(g_mu); - std::vector argv(argc, nullptr); - for (uint32_t i = 0; i < argc; ++i) { - napi_value value = LoadValue(*bridge_state, argv_ids[i]); - if (value == nullptr) return napi_invalid_arg; - argv[i] = value; + napi_value import_callback = import_callback_id == 0 + ? nullptr + : LoadValue(*bridge_state, import_callback_id); + napi_value import_meta_callback = import_meta_callback_id == 0 + ? nullptr + : LoadValue(*bridge_state, import_meta_callback_id); + if ((import_callback_id != 0 && import_callback == nullptr) || + (import_meta_callback_id != 0 && import_meta_callback == nullptr)) { + return napi_invalid_arg; } - napi_value result = nullptr; - napi_status s = unofficial_napi_module_wrap_import_module_dynamically( - env, argc, argc == 0 ? nullptr : argv.data(), &result); - if (s != napi_ok) return s; - if (result_out != nullptr) *result_out = StoreValue(*bridge_state, result); - return napi_ok; + const unofficial_napi_module_hooks hooks = { + sizeof(unofficial_napi_module_hooks), + UNOFFICIAL_NAPI_MODULE_HOOKS_VERSION, + import_callback, + import_meta_callback, + }; + return unofficial_napi_module_wrap_set_hooks(env, &hooks); } extern "C" int snapi_bridge_unofficial_module_wrap_create_required_module_facade( @@ -4511,10 +4280,10 @@ extern "C" int snapi_bridge_unofficial_module_wrap_create_required_module_facade if (bridge_state == nullptr) return napi_invalid_arg; napi_env env = bridge_state->env; std::lock_guard lock(g_mu); - void* handle = LoadModuleWrapHandle(*bridge_state, handle_id); - if (handle == nullptr) return napi_invalid_arg; + unofficial_napi_module module = LoadModuleWrapHandle(*bridge_state, handle_id); + if (module == nullptr) return napi_invalid_arg; napi_value result = nullptr; - napi_status s = unofficial_napi_module_wrap_create_required_module_facade(env, handle, &result); + napi_status s = unofficial_napi_module_wrap_create_required_module_facade(env, module, &result); if (s != napi_ok) return s; if (result_out != nullptr) *result_out = StoreValue(*bridge_state, result); return napi_ok; @@ -4534,4 +4303,8 @@ extern "C" void snapi_bridge_dispose() { for (auto* env_state : env_states) { (void)DisposeBridgeStateLocked(env_state); } + for (void* handle : g_message_handles.TakeAll()) { + unofficial_napi_message_drop( + reinterpret_cast(handle)); + } } diff --git a/src/snapi.rs b/src/snapi.rs index 5829ebf..16cb812 100644 --- a/src/snapi.rs +++ b/src/snapi.rs @@ -13,6 +13,9 @@ pub type SnapiEnv = *mut SnapiEnvState; #[repr(C)] pub struct SnapiUnofficialHeapStatistics { + pub size: u32, + pub version: u32, + pub valid_fields: u64, pub total_heap_size: u64, pub total_heap_size_executable: u64, pub total_physical_size: u64, @@ -27,6 +30,7 @@ pub struct SnapiUnofficialHeapStatistics { pub total_global_handles_size: u64, pub used_global_handles_size: u64, pub external_memory: u64, + pub array_buffer_memory: u64, } #[repr(C)] @@ -48,7 +52,10 @@ pub struct SnapiUnofficialHeapCodeStatistics { unsafe extern "C" { pub fn snapi_bridge_init() -> i32; - pub fn snapi_bridge_unofficial_set_flags_from_string(flags: *const i8, length: u32) -> i32; + pub fn snapi_bridge_unofficial_configure_runtime( + engine_flags: *const i8, + engine_flags_length: u32, + ) -> i32; pub fn snapi_bridge_unofficial_create_env( module_api_version: i32, guest_heap_ctx: *const core::ffi::c_void, @@ -56,6 +63,8 @@ unsafe extern "C" { ) -> i32; pub fn snapi_bridge_unofficial_create_env_with_options( module_api_version: i32, + total_memory: u64, + constrained_memory: u64, max_young_generation_size_in_bytes: u32, max_old_generation_size_in_bytes: u32, code_range_size_in_bytes: u32, @@ -63,10 +72,9 @@ unsafe extern "C" { guest_heap_ctx: *const core::ffi::c_void, env_out: *mut SnapiEnv, ) -> i32; - pub fn snapi_bridge_unofficial_set_embedder_hooks(env: SnapiEnv) -> i32; pub fn snapi_bridge_unofficial_release_env(env: SnapiEnv) -> i32; pub fn snapi_bridge_unofficial_release_env_with_loop(env: SnapiEnv, loop_id: u32) -> i32; - pub fn snapi_bridge_unofficial_low_memory_notification(env: SnapiEnv) -> i32; + pub fn snapi_bridge_unofficial_collect_garbage(env: SnapiEnv) -> i32; pub fn snapi_bridge_unofficial_event_loop_checkpoint( env: SnapiEnv, mode: i32, @@ -88,7 +96,6 @@ unsafe extern "C" { guest_ptr_out: *mut u32, recyclable_out: *mut i32, ) -> i32; - pub fn snapi_bridge_unofficial_request_gc_for_testing(env: SnapiEnv) -> i32; pub fn snapi_bridge_unofficial_set_prepare_stack_trace_callback( env: SnapiEnv, callback_id: u32, @@ -107,8 +114,6 @@ unsafe extern "C" { after_callback_id: u32, resolve_callback_id: u32, ) -> i32; - #[allow(dead_code)] - pub fn snapi_bridge_unofficial_set_stack_limit(env: SnapiEnv, stack_limit: u32) -> i32; /// Register a host-owned near-heap-limit callback that charges V8 heap /// growth against the resource budget. `data` is an /// `*const crate::budget::EnvHeapCharge` passed opaquely to the callback. @@ -120,17 +125,6 @@ unsafe extern "C" { /// registrations) this env may hold, bounding host-side bookkeeping RSS. /// `limit == 0` means unlimited. pub fn snapi_bridge_unofficial_set_value_limit(env: SnapiEnv, limit: u64) -> i32; - #[allow(dead_code)] - pub fn snapi_bridge_unofficial_set_near_heap_limit_callback( - env: SnapiEnv, - callback_id: u32, - data: u32, - ) -> i32; - #[allow(dead_code)] - pub fn snapi_bridge_unofficial_remove_near_heap_limit_callback( - env: SnapiEnv, - heap_limit: u32, - ) -> i32; pub fn snapi_bridge_unofficial_get_promise_details( env: SnapiEnv, promise_id: u32, @@ -155,15 +149,6 @@ unsafe extern "C" { frames: u32, callsites_out: *mut u32, ) -> i32; - pub fn snapi_bridge_unofficial_get_current_stack_trace( - env: SnapiEnv, - frames: u32, - callsites_out: *mut u32, - ) -> i32; - pub fn snapi_bridge_unofficial_get_caller_location( - env: SnapiEnv, - location_out: *mut u32, - ) -> i32; pub fn snapi_bridge_unofficial_arraybuffer_view_has_buffer( env: SnapiEnv, value_id: u32, @@ -188,11 +173,11 @@ unsafe extern "C" { env: SnapiEnv, value_id: u32, ) -> i32; - pub fn snapi_bridge_unofficial_set_enqueue_foreground_task_callback(env: SnapiEnv) -> i32; - pub fn snapi_bridge_unofficial_set_fatal_error_callbacks( + pub fn snapi_bridge_unofficial_attach_env( env: SnapiEnv, fatal_callback_id: u32, oom_callback_id: u32, + accepted_hooks_out: *mut u64, ) -> i32; pub fn snapi_bridge_unofficial_terminate_execution(env: SnapiEnv) -> i32; pub fn snapi_bridge_unofficial_enqueue_microtask(env: SnapiEnv, callback_id: u32) -> i32; @@ -206,44 +191,24 @@ unsafe extern "C" { filter_bits: u32, out_id: *mut u32, ) -> i32; - pub fn snapi_bridge_unofficial_get_process_memory_info( - env: SnapiEnv, - heap_total_out: *mut f64, - heap_used_out: *mut f64, - external_out: *mut f64, - array_buffers_out: *mut f64, - ) -> i32; pub fn snapi_bridge_unofficial_get_hash_seed(env: SnapiEnv, hash_seed_out: *mut u64) -> i32; - pub fn snapi_bridge_unofficial_get_error_source_positions( + pub fn snapi_bridge_unofficial_get_error_metadata( env: SnapiEnv, error_id: u32, + mode: i32, source_line_out: *mut u32, script_resource_name_out: *mut u32, + stderr_line_out: *mut u32, + thrown_at_out: *mut u32, line_number_out: *mut i32, start_column_out: *mut i32, end_column_out: *mut i32, ) -> i32; - pub fn snapi_bridge_unofficial_set_source_maps_enabled(env: SnapiEnv, enabled: i32) -> i32; - pub fn snapi_bridge_unofficial_set_get_source_map_error_source_callback( + pub fn snapi_bridge_unofficial_configure_source_maps( env: SnapiEnv, + enabled: i32, callback_id: u32, ) -> i32; - pub fn snapi_bridge_unofficial_get_error_source_line_for_stderr( - env: SnapiEnv, - error_id: u32, - result_out: *mut u32, - ) -> i32; - pub fn snapi_bridge_unofficial_get_error_thrown_at( - env: SnapiEnv, - error_id: u32, - result_out: *mut u32, - ) -> i32; - pub fn snapi_bridge_unofficial_take_preserved_error_formatting( - env: SnapiEnv, - error_id: u32, - source_line_out: *mut u32, - thrown_at_out: *mut u32, - ) -> i32; pub fn snapi_bridge_unofficial_preserve_error_source_message( env: SnapiEnv, error_id: u32, @@ -253,65 +218,51 @@ unsafe extern "C" { env: SnapiEnv, stats_out: *mut SnapiUnofficialHeapStatistics, ) -> i32; - pub fn snapi_bridge_unofficial_get_heap_space_count(env: SnapiEnv, count_out: *mut u32) -> i32; pub fn snapi_bridge_unofficial_get_heap_space_statistics( env: SnapiEnv, - space_index: u32, stats_out: *mut SnapiUnofficialHeapSpaceStatistics, + capacity: u32, + count_out: *mut u32, ) -> i32; pub fn snapi_bridge_unofficial_get_heap_code_statistics( env: SnapiEnv, stats_out: *mut SnapiUnofficialHeapCodeStatistics, ) -> i32; - pub fn snapi_bridge_unofficial_start_cpu_profile( + pub fn snapi_bridge_unofficial_profile_start( env: SnapiEnv, + kind: i32, result_out: *mut i32, - profile_id_out: *mut u32, + profile_out: *mut u32, ) -> i32; - pub fn snapi_bridge_unofficial_stop_cpu_profile( + pub fn snapi_bridge_unofficial_profile_stop( env: SnapiEnv, - profile_id: u32, - found_out: *mut i32, - json_out: *mut u64, - json_len_out: *mut u32, - ) -> i32; - pub fn snapi_bridge_unofficial_start_heap_profile(env: SnapiEnv, started_out: *mut i32) -> i32; - pub fn snapi_bridge_unofficial_stop_heap_profile( - env: SnapiEnv, - found_out: *mut i32, - json_out: *mut u64, - json_len_out: *mut u32, + profile: u32, + json_out: *mut u32, ) -> i32; pub fn snapi_bridge_unofficial_take_heap_snapshot( env: SnapiEnv, expose_internals: i32, expose_numeric_values: i32, - json_out: *mut u64, - json_len_out: *mut u32, + json_out: *mut u32, ) -> i32; - pub fn snapi_bridge_unofficial_free_buffer(data: *mut c_void); + pub fn snapi_bridge_free_buffer(data: *mut c_void); pub fn snapi_bridge_unofficial_structured_clone( - env: SnapiEnv, - value_id: u32, - out_id: *mut u32, - ) -> i32; - pub fn snapi_bridge_unofficial_structured_clone_with_transfer( env: SnapiEnv, value_id: u32, transfer_list_id: u32, out_id: *mut u32, ) -> i32; - pub fn snapi_bridge_unofficial_serialize_value( + pub fn snapi_bridge_unofficial_message_create( env: SnapiEnv, value_id: u32, payload_out: *mut u32, ) -> i32; - pub fn snapi_bridge_unofficial_deserialize_value( + pub fn snapi_bridge_unofficial_message_take( env: SnapiEnv, payload: u32, value_out: *mut u32, ) -> i32; - pub fn snapi_bridge_unofficial_release_serialized_value(payload: u32); + pub fn snapi_bridge_unofficial_message_drop(payload: u32); pub fn snapi_bridge_unofficial_notify_datetime_configuration_change(env: SnapiEnv) -> i32; pub fn snapi_bridge_unofficial_create_serdes_binding(env: SnapiEnv, out_id: *mut u32) -> i32; pub fn snapi_bridge_unofficial_contextify_contains_module_syntax( @@ -348,10 +299,6 @@ unsafe extern "C" { host_defined_option_id: u32, result_out: *mut u32, ) -> i32; - pub fn snapi_bridge_unofficial_contextify_dispose_context( - env: SnapiEnv, - sandbox_or_context_global_id: u32, - ) -> i32; pub fn snapi_bridge_unofficial_contextify_compile_function( env: SnapiEnv, source_text_id: u32, @@ -365,16 +312,7 @@ unsafe extern "C" { host_defined_option_id: u32, result_out: *mut u32, ) -> i32; - pub fn snapi_bridge_unofficial_contextify_compile_function_for_cjs_loader( - env: SnapiEnv, - source_text_id: u32, - source_bytecode_id: u32, - filename_id: u32, - is_sea_main: i32, - should_detect_module: i32, - result_out: *mut u32, - ) -> i32; - pub fn snapi_bridge_unofficial_bytecode_compile( + pub fn snapi_bridge_unofficial_bytecode_open( env: SnapiEnv, source_text_id: u32, filename_id: u32, @@ -383,41 +321,23 @@ unsafe extern "C" { host_defined_option_id: u32, line_offset: i32, column_offset: i32, + cache_bytes: *const u8, + cache_byte_length: usize, + has_cache: u8, + cache_policy: u8, bytecode_out: *mut u32, + cache_rejected_out: *mut u8, can_parse_as_module_out: *mut u8, ) -> i32; - pub fn snapi_bridge_unofficial_bytecode_deserialize( - env: SnapiEnv, - bytes: *const u8, - byte_length: usize, - source_text_id: u32, - filename_id: u32, - shape: i32, - params_id: u32, - host_defined_option_id: u32, - bytecode_out: *mut u32, - rejected_out: *mut u8, - ) -> i32; pub fn snapi_bridge_unofficial_bytecode_serialize( env: SnapiEnv, bytecode_id: u32, buffer_out: *mut u32, ) -> i32; pub fn snapi_bridge_unofficial_bytecode_release(env: SnapiEnv, bytecode_id: u32) -> i32; - pub fn snapi_bridge_unofficial_contextify_start_sigint_watchdog( - env: SnapiEnv, - result_out: *mut i32, - ) -> i32; - pub fn snapi_bridge_unofficial_contextify_stop_sigint_watchdog( - env: SnapiEnv, - had_pending_signal_out: *mut i32, - ) -> i32; - pub fn snapi_bridge_unofficial_contextify_watchdog_has_pending_sigint( - env: SnapiEnv, - result_out: *mut i32, - ) -> i32; - pub fn snapi_bridge_unofficial_module_wrap_create_source_text( + pub fn snapi_bridge_unofficial_module_wrap_create( env: SnapiEnv, + kind: i32, wrapper_id: u32, url_id: u32, context_id: u32, @@ -426,23 +346,13 @@ unsafe extern "C" { line_offset: i32, column_offset: i32, host_defined_option_id: u32, - handle_out: *mut u32, - ) -> i32; - pub fn snapi_bridge_unofficial_module_wrap_create_synthetic( - env: SnapiEnv, - wrapper_id: u32, - url_id: u32, - context_id: u32, export_names_id: u32, synthetic_eval_steps_id: u32, handle_out: *mut u32, + requests_out: *mut u32, + has_top_level_await_out: *mut u8, ) -> i32; pub fn snapi_bridge_unofficial_module_wrap_destroy(env: SnapiEnv, handle_id: u32) -> i32; - pub fn snapi_bridge_unofficial_module_wrap_get_module_requests( - env: SnapiEnv, - handle_id: u32, - result_out: *mut u32, - ) -> i32; pub fn snapi_bridge_unofficial_module_wrap_link( env: SnapiEnv, handle_id: u32, @@ -469,29 +379,16 @@ unsafe extern "C" { handle_id: u32, result_out: *mut u32, ) -> i32; - pub fn snapi_bridge_unofficial_module_wrap_get_status( + pub fn snapi_bridge_unofficial_module_wrap_get_state( env: SnapiEnv, handle_id: u32, status_out: *mut i32, - ) -> i32; - pub fn snapi_bridge_unofficial_module_wrap_get_error( - env: SnapiEnv, - handle_id: u32, - result_out: *mut u32, - ) -> i32; - pub fn snapi_bridge_unofficial_module_wrap_has_top_level_await( - env: SnapiEnv, - handle_id: u32, - result_out: *mut i32, - ) -> i32; - pub fn snapi_bridge_unofficial_module_wrap_has_async_graph( - env: SnapiEnv, - handle_id: u32, - result_out: *mut i32, + error_out: *mut u32, + has_async_graph_out: *mut i32, ) -> i32; pub fn snapi_bridge_unofficial_module_wrap_check_unsettled_top_level_await( env: SnapiEnv, - module_wrap_id: u32, + handle_id: u32, warnings: i32, settled_out: *mut i32, ) -> i32; @@ -516,19 +413,10 @@ unsafe extern "C" { handle_id: u32, result_out: *mut u32, ) -> i32; - pub fn snapi_bridge_unofficial_module_wrap_set_import_module_dynamically_callback( - env: SnapiEnv, - callback_id: u32, - ) -> i32; - pub fn snapi_bridge_unofficial_module_wrap_set_initialize_import_meta_object_callback( + pub fn snapi_bridge_unofficial_module_wrap_set_hooks( env: SnapiEnv, - callback_id: u32, - ) -> i32; - pub fn snapi_bridge_unofficial_module_wrap_import_module_dynamically( - env: SnapiEnv, - argc: u32, - argv_ids: *const u32, - result_out: *mut u32, + import_callback_id: u32, + import_meta_callback_id: u32, ) -> i32; pub fn snapi_bridge_unofficial_module_wrap_create_required_module_facade( env: SnapiEnv, diff --git a/src/snapi_js.rs b/src/snapi_js.rs index 93e419b..5ea4084 100644 --- a/src/snapi_js.rs +++ b/src/snapi_js.rs @@ -13,7 +13,10 @@ use std::{ ptr, rc::Rc, slice, - sync::atomic::{AtomicU32, Ordering}, + sync::{ + Mutex, + atomic::{AtomicU32, Ordering}, + }, }; use js_sys::{ @@ -44,6 +47,11 @@ const SERIALIZED_MESSAGE_SEQUENCE_MASK: u32 = (1 << SERIALIZED_MESSAGE_SEQUENCE_ const SERIALIZED_MESSAGE_SCOPE_MAX: u32 = (1 << (32 - SERIALIZED_MESSAGE_SEQUENCE_BITS)) - 1; static NEXT_SERIALIZED_MESSAGE_SEQUENCE: AtomicU32 = AtomicU32::new(1); +// Runtime flags configure the provider, not an individual N-API environment +// or calling thread. Worker environments are created on guest pthreads, so a +// thread-local configuration would incorrectly make the process-global setup +// performed by the main thread invisible there. +static RUNTIME_ENGINE_FLAGS: Mutex>> = Mutex::new(None); fn next_serialized_message(scope: u32) -> Option { if scope > SERIALIZED_MESSAGE_SCOPE_MAX { @@ -429,6 +437,15 @@ function wasmerNapiCollectBindingNames(pattern, names) { wasmerNapiCollectBindingNames(pattern.argument, names); } } +export function wasmer_napi_validate_script(source, filename) { + wasmerNapiParse(source, { + ecmaVersion: 'latest', + sourceType: 'script', + allowHashBang: true, + locations: true, + sourceFile: filename, + }); +} export function wasmer_napi_compile_module(scope, source, filename) { const program = wasmerNapiParse(source, { ecmaVersion: 'latest', @@ -918,6 +935,8 @@ extern "C" { source: &str, filename: &str, ) -> Result; + #[wasm_bindgen(catch)] + fn wasmer_napi_validate_script(source: &str, filename: &str) -> Result<(), JsValue>; fn wasmer_napi_create_module_evaluation() -> Object; fn wasmer_napi_finish_module_evaluation( evaluation: &Object, @@ -1046,7 +1065,6 @@ struct SyntheticModule { } struct SourceTextModule { - wrapper: JsValue, url: String, requests: Array, linked_handles: Vec, @@ -1093,8 +1111,38 @@ struct HostJsEnv { backing_tokens: HashMap, type_tags: HashMap, next_module: u32, + next_bytecode: u32, + bytecodes: HashMap, synthetic_modules: HashMap, source_text_modules: HashMap, + embedder_hooks_attached: bool, +} + +struct HostJsBytecode { + source: JsValue, + shape: i32, +} + +fn host_js_source_for_shape( + state: &HostJsEnv, + source_text_id: u32, + source_bytecode_id: u32, + expected_shape: i32, +) -> Result { + if source_text_id != 0 { + return state + .get(source_text_id) + .and_then(JsValue::as_string) + .ok_or(NAPI_STRING_EXPECTED); + } + let bytecode = state + .bytecodes + .get(&source_bytecode_id) + .ok_or(NAPI_INVALID_ARG)?; + if bytecode.shape != expected_shape { + return Err(NAPI_INVALID_ARG); + } + bytecode.source.as_string().ok_or(NAPI_STRING_EXPECTED) } impl HostJsEnv { @@ -1140,8 +1188,11 @@ impl HostJsEnv { backing_tokens: HashMap::new(), type_tags: HashMap::new(), next_module: 1, + next_bytecode: 1, + bytecodes: HashMap::new(), synthetic_modules: HashMap::new(), source_text_modules: HashMap::new(), + embedder_hooks_attached: false, } } @@ -1394,6 +1445,34 @@ thread_local! { static ACTIVE_HOST_JS_ENV: Cell = const { Cell::new(0) }; } +#[unsafe(no_mangle)] +pub unsafe extern "C" fn snapi_bridge_unofficial_configure_runtime( + engine_flags: *const i8, + engine_flags_length: u32, +) -> i32 { + if engine_flags_length > 0 && engine_flags.is_null() { + return NAPI_INVALID_ARG; + } + let requested = if engine_flags_length == 0 { + Vec::new() + } else { + unsafe { + slice::from_raw_parts(engine_flags.cast::(), engine_flags_length as usize).to_vec() + } + }; + let Ok(mut configured) = RUNTIME_ENGINE_FLAGS.lock() else { + return NAPI_GENERIC_FAILURE; + }; + match configured.as_ref() { + Some(existing) if existing != &requested => NAPI_INVALID_ARG, + Some(_) => NAPI_OK, + None => { + *configured = Some(requested); + NAPI_OK + } + } +} + unsafe fn env_mut<'a>(env: SnapiEnv) -> Result<&'a mut HostJsEnv, i32> { if env.is_null() { return Err(NAPI_INVALID_ARG); @@ -1715,7 +1794,10 @@ pub unsafe extern "C" fn snapi_bridge_unofficial_create_env( _guest_heap_ctx: *const c_void, out: *mut SnapiEnv, ) -> i32 { - if out.is_null() { + let runtime_configured = RUNTIME_ENGINE_FLAGS + .lock() + .is_ok_and(|flags| flags.is_some()); + if out.is_null() || !runtime_configured { return NAPI_INVALID_ARG; } let env = Box::new(HostJsEnv::new()); @@ -1727,6 +1809,8 @@ pub unsafe extern "C" fn snapi_bridge_unofficial_create_env( #[unsafe(no_mangle)] pub unsafe extern "C" fn snapi_bridge_unofficial_create_env_with_options( version: i32, + _total_memory: u64, + _constrained_memory: u64, _young: u32, _old: u32, _code: u32, @@ -1771,7 +1855,7 @@ pub unsafe extern "C" fn snapi_bridge_unofficial_release_env_with_loop( } #[unsafe(no_mangle)] -pub unsafe extern "C" fn snapi_bridge_unofficial_free_buffer(data: *mut c_void) { +pub unsafe extern "C" fn snapi_bridge_free_buffer(data: *mut c_void) { if !data.is_null() { BUFFER_ALLOCS.with(|allocs| { allocs.borrow_mut().remove(&(data as usize)); @@ -3291,25 +3375,7 @@ pub unsafe extern "C" fn snapi_bridge_unofficial_event_loop_checkpoint( // until their host-JS semantics are implemented; keeping the symbols local // prevents wasm-bindgen output from acquiring raw C imports. #[unsafe(no_mangle)] -pub unsafe extern "C" fn snapi_bridge_unofficial_set_flags_from_string( - flags: *const i8, - length: u32, -) -> i32 { - let _ = (flags, length); - NAPI_GENERIC_FAILURE -} -#[unsafe(no_mangle)] -pub unsafe extern "C" fn snapi_bridge_unofficial_set_embedder_hooks(env: SnapiEnv) -> i32 { - let _ = env; - NAPI_GENERIC_FAILURE -} -#[unsafe(no_mangle)] -pub unsafe extern "C" fn snapi_bridge_unofficial_low_memory_notification(env: SnapiEnv) -> i32 { - let _ = env; - NAPI_GENERIC_FAILURE -} -#[unsafe(no_mangle)] -pub unsafe extern "C" fn snapi_bridge_unofficial_request_gc_for_testing(env: SnapiEnv) -> i32 { +pub unsafe extern "C" fn snapi_bridge_unofficial_collect_garbage(env: SnapiEnv) -> i32 { let _ = env; NAPI_GENERIC_FAILURE } @@ -3354,31 +3420,6 @@ pub unsafe extern "C" fn snapi_bridge_unofficial_set_promise_hooks( NAPI_GENERIC_FAILURE } #[unsafe(no_mangle)] -pub unsafe extern "C" fn snapi_bridge_unofficial_set_stack_limit( - env: SnapiEnv, - stack_limit: u32, -) -> i32 { - let _ = (env, stack_limit); - NAPI_GENERIC_FAILURE -} -#[unsafe(no_mangle)] -pub unsafe extern "C" fn snapi_bridge_unofficial_set_near_heap_limit_callback( - env: SnapiEnv, - callback_id: u32, - data: u32, -) -> i32 { - let _ = (env, callback_id, data); - NAPI_GENERIC_FAILURE -} -#[unsafe(no_mangle)] -pub unsafe extern "C" fn snapi_bridge_unofficial_remove_near_heap_limit_callback( - env: SnapiEnv, - heap_limit: u32, -) -> i32 { - let _ = (env, heap_limit); - NAPI_GENERIC_FAILURE -} -#[unsafe(no_mangle)] pub unsafe extern "C" fn snapi_bridge_unofficial_get_promise_details( env: SnapiEnv, promise_id: u32, @@ -3443,23 +3484,6 @@ pub unsafe extern "C" fn snapi_bridge_unofficial_get_call_sites( NAPI_GENERIC_FAILURE } #[unsafe(no_mangle)] -pub unsafe extern "C" fn snapi_bridge_unofficial_get_current_stack_trace( - env: SnapiEnv, - frames: u32, - callsites_out: *mut u32, -) -> i32 { - let _ = (env, frames, callsites_out); - NAPI_GENERIC_FAILURE -} -#[unsafe(no_mangle)] -pub unsafe extern "C" fn snapi_bridge_unofficial_get_caller_location( - env: SnapiEnv, - location_out: *mut u32, -) -> i32 { - let _ = (env, location_out); - NAPI_GENERIC_FAILURE -} -#[unsafe(no_mangle)] pub unsafe extern "C" fn snapi_bridge_unofficial_arraybuffer_view_has_buffer( env: SnapiEnv, value_id: u32, @@ -3511,25 +3535,25 @@ pub unsafe extern "C" fn snapi_bridge_unofficial_set_continuation_preserved_embe NAPI_GENERIC_FAILURE } #[unsafe(no_mangle)] -pub unsafe extern "C" fn snapi_bridge_unofficial_set_enqueue_foreground_task_callback( - env: SnapiEnv, -) -> i32 { - let _ = env; - // JavaScript-hosted execution already runs on the host event loop. EdgeJS - // installs this hook for engine-owned foreground tasks; there is no - // separate embedded-engine queue to bridge in this backend. - NAPI_OK -} -#[unsafe(no_mangle)] -pub unsafe extern "C" fn snapi_bridge_unofficial_set_fatal_error_callbacks( +pub unsafe extern "C" fn snapi_bridge_unofficial_attach_env( env: SnapiEnv, fatal_callback_id: u32, oom_callback_id: u32, + accepted_hooks_out: *mut u64, ) -> i32 { - let _ = (env, fatal_callback_id, oom_callback_id); - // The browser/Node host owns fatal JavaScript and OOM handling. There is - // no embedded engine whose fatal hooks can be replaced, so accepting the - // registration is the JS-backend equivalent of installing them. + let Ok(state) = (unsafe { env_mut(env) }) else { + return NAPI_INVALID_ARG; + }; + if state.embedder_hooks_attached || accepted_hooks_out.is_null() { + return NAPI_INVALID_ARG; + } + let _ = (fatal_callback_id, oom_callback_id); + // JavaScript-hosted execution already runs on the host event loop, and the + // browser/Node host owns fatal JavaScript and OOM handling. Accept the same + // single attachment transition while reporting that no guest callback can + // be invoked as a native provider function pointer. + state.embedder_hooks_attached = true; + unsafe { accepted_hooks_out.write(0) }; NAPI_OK } #[unsafe(no_mangle)] @@ -3641,23 +3665,6 @@ pub unsafe extern "C" fn snapi_bridge_unofficial_get_own_non_index_properties( unsafe { write(out_id, id) }.map_or_else(|error| error, |()| NAPI_OK) } #[unsafe(no_mangle)] -pub unsafe extern "C" fn snapi_bridge_unofficial_get_process_memory_info( - env: SnapiEnv, - heap_total_out: *mut f64, - heap_used_out: *mut f64, - external_out: *mut f64, - array_buffers_out: *mut f64, -) -> i32 { - let _ = ( - env, - heap_total_out, - heap_used_out, - external_out, - array_buffers_out, - ); - NAPI_GENERIC_FAILURE -} -#[unsafe(no_mangle)] pub unsafe extern "C" fn snapi_bridge_unofficial_get_hash_seed( env: SnapiEnv, hash_seed_out: *mut u64, @@ -3666,11 +3673,14 @@ pub unsafe extern "C" fn snapi_bridge_unofficial_get_hash_seed( NAPI_GENERIC_FAILURE } #[unsafe(no_mangle)] -pub unsafe extern "C" fn snapi_bridge_unofficial_get_error_source_positions( +pub unsafe extern "C" fn snapi_bridge_unofficial_get_error_metadata( env: SnapiEnv, error_id: u32, + mode: i32, source_line_out: *mut u32, script_resource_name_out: *mut u32, + stderr_line_out: *mut u32, + thrown_at_out: *mut u32, line_number_out: *mut i32, start_column_out: *mut i32, end_column_out: *mut i32, @@ -3678,8 +3688,11 @@ pub unsafe extern "C" fn snapi_bridge_unofficial_get_error_source_positions( let _ = ( env, error_id, + mode, source_line_out, script_resource_name_out, + stderr_line_out, + thrown_at_out, line_number_out, start_column_out, end_column_out, @@ -3687,47 +3700,12 @@ pub unsafe extern "C" fn snapi_bridge_unofficial_get_error_source_positions( NAPI_GENERIC_FAILURE } #[unsafe(no_mangle)] -pub unsafe extern "C" fn snapi_bridge_unofficial_set_source_maps_enabled( +pub unsafe extern "C" fn snapi_bridge_unofficial_configure_source_maps( env: SnapiEnv, enabled: i32, -) -> i32 { - let _ = (env, enabled); - NAPI_GENERIC_FAILURE -} -#[unsafe(no_mangle)] -pub unsafe extern "C" fn snapi_bridge_unofficial_set_get_source_map_error_source_callback( - env: SnapiEnv, callback_id: u32, ) -> i32 { - let _ = (env, callback_id); - NAPI_GENERIC_FAILURE -} -#[unsafe(no_mangle)] -pub unsafe extern "C" fn snapi_bridge_unofficial_get_error_source_line_for_stderr( - env: SnapiEnv, - error_id: u32, - result_out: *mut u32, -) -> i32 { - let _ = (env, error_id, result_out); - NAPI_GENERIC_FAILURE -} -#[unsafe(no_mangle)] -pub unsafe extern "C" fn snapi_bridge_unofficial_get_error_thrown_at( - env: SnapiEnv, - error_id: u32, - result_out: *mut u32, -) -> i32 { - let _ = (env, error_id, result_out); - NAPI_GENERIC_FAILURE -} -#[unsafe(no_mangle)] -pub unsafe extern "C" fn snapi_bridge_unofficial_take_preserved_error_formatting( - env: SnapiEnv, - error_id: u32, - source_line_out: *mut u32, - thrown_at_out: *mut u32, -) -> i32 { - let _ = (env, error_id, source_line_out, thrown_at_out); + let _ = (env, enabled, callback_id); NAPI_GENERIC_FAILURE } #[unsafe(no_mangle)] @@ -3746,75 +3724,163 @@ pub unsafe extern "C" fn snapi_bridge_unofficial_mark_promise_as_handled( let _ = (env, promise_id); NAPI_GENERIC_FAILURE } + +fn host_heap_sizes() -> (u64, u64, u64, bool) { + fn metric(memory: &JsValue, name: &str) -> u64 { + Reflect::get(memory, &JsValue::from_str(name)) + .ok() + .and_then(|value| value.as_f64()) + .filter(|value| value.is_finite() && *value >= 0.0) + .map(|value| value.min(u64::MAX as f64) as u64) + .unwrap_or(0) + } + + let global = js_sys::global(); + let memory = Reflect::get(&global, &JsValue::from_str("performance")) + .ok() + .and_then(|performance| Reflect::get(&performance, &JsValue::from_str("memory")).ok()) + .unwrap_or(JsValue::UNDEFINED); + let used = metric(&memory, "usedJSHeapSize"); + let total = metric(&memory, "totalJSHeapSize").max(used); + // `performance.memory` is Chromium-specific. When the host does not expose + // it, report an unknown-but-nonzero ceiling instead of zero: Node consumers + // interpret zero as an exhausted heap and may terminate healthy workers. + let limit = metric(&memory, "jsHeapSizeLimit") + .max(total) + .max(u64::from(u32::MAX)); + ( + total, + used, + limit, + !memory.is_undefined() && !memory.is_null(), + ) +} + #[unsafe(no_mangle)] pub unsafe extern "C" fn snapi_bridge_unofficial_get_heap_statistics( env: SnapiEnv, stats_out: *mut SnapiUnofficialHeapStatistics, ) -> i32 { - let _ = (env, stats_out); - NAPI_GENERIC_FAILURE -} -#[unsafe(no_mangle)] -pub unsafe extern "C" fn snapi_bridge_unofficial_get_heap_space_count( - env: SnapiEnv, - count_out: *mut u32, -) -> i32 { - let _ = (env, count_out); - NAPI_GENERIC_FAILURE + if stats_out.is_null() || (unsafe { env_mut(env) }).is_err() { + return NAPI_INVALID_ARG; + } + let requested = unsafe { &*stats_out }; + if requested.size < std::mem::size_of::() as u32 + || requested.version != 1 + { + return NAPI_INVALID_ARG; + } + let output_size = requested.size; + let (total, used, limit, measured) = host_heap_sizes(); + unsafe { + ptr::write( + stats_out, + SnapiUnofficialHeapStatistics { + size: output_size, + version: 1, + // The conservative ceiling is intentionally available even + // when Chromium withholds `performance.memory`. Only mark the + // host measurements valid when the host actually supplied + // them, but always expose the nonzero ceiling we computed. + valid_fields: (1 << 5) + | if measured { + (1 << 0) | (1 << 3) | (1 << 4) + } else { + 0 + }, + total_heap_size: total, + total_heap_size_executable: 0, + total_physical_size: 0, + total_available_size: limit.saturating_sub(used), + used_heap_size: used, + heap_size_limit: limit, + does_zap_garbage: 0, + malloced_memory: 0, + peak_malloced_memory: 0, + number_of_native_contexts: 0, + number_of_detached_contexts: 0, + total_global_handles_size: 0, + used_global_handles_size: 0, + external_memory: 0, + array_buffer_memory: 0, + }, + ); + } + NAPI_OK } #[unsafe(no_mangle)] pub unsafe extern "C" fn snapi_bridge_unofficial_get_heap_space_statistics( env: SnapiEnv, - space_index: u32, stats_out: *mut SnapiUnofficialHeapSpaceStatistics, + capacity: u32, + count_out: *mut u32, ) -> i32 { - let _ = (env, space_index, stats_out); - NAPI_GENERIC_FAILURE + if count_out.is_null() + || (capacity > 0 && stats_out.is_null()) + || (unsafe { env_mut(env) }).is_err() + { + return NAPI_INVALID_ARG; + } + unsafe { ptr::write(count_out, 1) }; + if capacity == 0 { + return NAPI_OK; + } + + let (total, used, limit, _) = host_heap_sizes(); + let mut space_name = [0; 64]; + space_name[.."host_js".len()].copy_from_slice(b"host_js"); + unsafe { + ptr::write( + stats_out, + SnapiUnofficialHeapSpaceStatistics { + space_name, + space_size: total, + space_used_size: used, + space_available_size: limit.saturating_sub(used), + physical_space_size: total, + }, + ); + } + NAPI_OK } #[unsafe(no_mangle)] pub unsafe extern "C" fn snapi_bridge_unofficial_get_heap_code_statistics( env: SnapiEnv, stats_out: *mut SnapiUnofficialHeapCodeStatistics, ) -> i32 { - let _ = (env, stats_out); - NAPI_GENERIC_FAILURE + if stats_out.is_null() || (unsafe { env_mut(env) }).is_err() { + return NAPI_INVALID_ARG; + } + unsafe { + ptr::write( + stats_out, + SnapiUnofficialHeapCodeStatistics { + code_and_metadata_size: 0, + bytecode_and_metadata_size: 0, + external_script_source_size: 0, + cpu_profiler_metadata_size: 0, + }, + ); + } + NAPI_OK } #[unsafe(no_mangle)] -pub unsafe extern "C" fn snapi_bridge_unofficial_start_cpu_profile( +pub unsafe extern "C" fn snapi_bridge_unofficial_profile_start( env: SnapiEnv, + kind: i32, result_out: *mut i32, - profile_id_out: *mut u32, -) -> i32 { - let _ = (env, result_out, profile_id_out); - NAPI_GENERIC_FAILURE -} -#[unsafe(no_mangle)] -pub unsafe extern "C" fn snapi_bridge_unofficial_stop_cpu_profile( - env: SnapiEnv, - profile_id: u32, - found_out: *mut i32, - json_out: *mut u64, - json_len_out: *mut u32, -) -> i32 { - let _ = (env, profile_id, found_out, json_out, json_len_out); - NAPI_GENERIC_FAILURE -} -#[unsafe(no_mangle)] -pub unsafe extern "C" fn snapi_bridge_unofficial_start_heap_profile( - env: SnapiEnv, - started_out: *mut i32, + profile_out: *mut u32, ) -> i32 { - let _ = (env, started_out); + let _ = (env, kind, result_out, profile_out); NAPI_GENERIC_FAILURE } #[unsafe(no_mangle)] -pub unsafe extern "C" fn snapi_bridge_unofficial_stop_heap_profile( +pub unsafe extern "C" fn snapi_bridge_unofficial_profile_stop( env: SnapiEnv, - found_out: *mut i32, - json_out: *mut u64, - json_len_out: *mut u32, + profile: u32, + json_out: *mut u32, ) -> i32 { - let _ = (env, found_out, json_out, json_len_out); + let _ = (env, profile, json_out); NAPI_GENERIC_FAILURE } #[unsafe(no_mangle)] @@ -3822,43 +3888,13 @@ pub unsafe extern "C" fn snapi_bridge_unofficial_take_heap_snapshot( env: SnapiEnv, expose_internals: i32, expose_numeric_values: i32, - json_out: *mut u64, - json_len_out: *mut u32, + json_out: *mut u32, ) -> i32 { - let _ = ( - env, - expose_internals, - expose_numeric_values, - json_out, - json_len_out, - ); + let _ = (env, expose_internals, expose_numeric_values, json_out); NAPI_GENERIC_FAILURE } #[unsafe(no_mangle)] pub unsafe extern "C" fn snapi_bridge_unofficial_structured_clone( - env: SnapiEnv, - value_id: u32, - out_id: *mut u32, -) -> i32 { - let Ok(state) = (unsafe { env_mut(env) }) else { - return NAPI_INVALID_ARG; - }; - let Some(value) = state.get(value_id).cloned() else { - return NAPI_INVALID_ARG; - }; - match wasmer_napi_structured_clone(&value, &JsValue::UNDEFINED) { - Ok(cloned) => { - let id = state.insert(cloned); - unsafe { write(out_id, id) }.map_or_else(|error| error, |()| NAPI_OK) - } - Err(error) => { - state.last_exception = Some(error); - NAPI_PENDING_EXCEPTION - } - } -} -#[unsafe(no_mangle)] -pub unsafe extern "C" fn snapi_bridge_unofficial_structured_clone_with_transfer( env: SnapiEnv, value_id: u32, transfer_list_id: u32, @@ -3890,7 +3926,7 @@ pub unsafe extern "C" fn snapi_bridge_unofficial_structured_clone_with_transfer( } #[unsafe(no_mangle)] -pub unsafe extern "C" fn snapi_bridge_unofficial_serialize_value( +pub unsafe extern "C" fn snapi_bridge_unofficial_message_create( env: SnapiEnv, value_id: u32, payload_out: *mut u32, @@ -3905,8 +3941,15 @@ pub unsafe extern "C" fn snapi_bridge_unofficial_serialize_value( return NAPI_GENERIC_FAILURE; }; match wasmer_napi_share_message(&value, payload) { - Ok(_) => unsafe { write(payload_out, payload) }.map_or_else(|error| error, |()| NAPI_OK), + Ok(_) => match unsafe { write(payload_out, payload) } { + Ok(()) => NAPI_OK, + Err(error) => { + wasmer_napi_release_message(payload); + error + } + }, Err(error) => { + wasmer_napi_release_message(payload); state.last_exception = Some(error); NAPI_PENDING_EXCEPTION } @@ -3914,15 +3957,18 @@ pub unsafe extern "C" fn snapi_bridge_unofficial_serialize_value( } #[unsafe(no_mangle)] -pub unsafe extern "C" fn snapi_bridge_unofficial_deserialize_value( +pub unsafe extern "C" fn snapi_bridge_unofficial_message_take( env: SnapiEnv, payload: u32, value_out: *mut u32, ) -> i32 { let Ok(state) = (unsafe { env_mut(env) }) else { + wasmer_napi_release_message(payload); return NAPI_INVALID_ARG; }; - match wasmer_napi_obtain_message(payload) { + let obtained = wasmer_napi_obtain_message(payload); + wasmer_napi_release_message(payload); + match obtained { Ok(value) => { let id = state.insert(value); unsafe { write(value_out, id) }.map_or_else(|error| error, |()| NAPI_OK) @@ -3935,7 +3981,7 @@ pub unsafe extern "C" fn snapi_bridge_unofficial_deserialize_value( } #[unsafe(no_mangle)] -pub extern "C" fn snapi_bridge_unofficial_release_serialized_value(payload: u32) { +pub extern "C" fn snapi_bridge_unofficial_message_drop(payload: u32) { wasmer_napi_release_message(payload); } #[unsafe(no_mangle)] @@ -4065,12 +4111,9 @@ pub unsafe extern "C" fn snapi_bridge_unofficial_contextify_run_script( let Ok(state) = (unsafe { env_mut(env) }) else { return NAPI_INVALID_ARG; }; - let Some(source) = state.get(source_text_id).and_then(JsValue::as_string) else { - return if source_bytecode_id != 0 { - NAPI_GENERIC_FAILURE - } else { - NAPI_STRING_EXPECTED - }; + let source = match host_js_source_for_shape(state, source_text_id, source_bytecode_id, 0) { + Ok(source) => source, + Err(status) => return status, }; let sandbox = state .get(sandbox_or_null_id) @@ -4088,14 +4131,6 @@ pub unsafe extern "C" fn snapi_bridge_unofficial_contextify_run_script( } } #[unsafe(no_mangle)] -pub unsafe extern "C" fn snapi_bridge_unofficial_contextify_dispose_context( - env: SnapiEnv, - sandbox_or_context_global_id: u32, -) -> i32 { - let _ = (env, sandbox_or_context_global_id); - NAPI_OK -} -#[unsafe(no_mangle)] pub unsafe extern "C" fn snapi_bridge_unofficial_contextify_compile_function( env: SnapiEnv, source_text_id: u32, @@ -4113,12 +4148,9 @@ pub unsafe extern "C" fn snapi_bridge_unofficial_contextify_compile_function( let Ok(state) = (unsafe { env_mut(env) }) else { return NAPI_INVALID_ARG; }; - let Some(source) = state.get(source_text_id).and_then(JsValue::as_string) else { - return if source_bytecode_id != 0 { - NAPI_GENERIC_FAILURE - } else { - NAPI_STRING_EXPECTED - }; + let source = match host_js_source_for_shape(state, source_text_id, source_bytecode_id, 1) { + Ok(source) => source, + Err(status) => return status, }; let params = Array::new(); if let Some(values) = state.get(params_id).filter(|value| Array::is_array(value)) { @@ -4189,122 +4221,104 @@ pub unsafe extern "C" fn snapi_bridge_unofficial_contextify_compile_function( } } #[unsafe(no_mangle)] -pub unsafe extern "C" fn snapi_bridge_unofficial_contextify_compile_function_for_cjs_loader( +pub unsafe extern "C" fn snapi_bridge_unofficial_bytecode_open( env: SnapiEnv, source_text_id: u32, - source_bytecode_id: u32, filename_id: u32, - is_sea_main: i32, - should_detect_module: i32, - result_out: *mut u32, + shape: i32, + params_id: u32, + host_defined_option_id: u32, + line_offset: i32, + column_offset: i32, + cache_bytes: *const u8, + cache_byte_length: usize, + has_cache: u8, + cache_policy: u8, + bytecode_out: *mut u32, + cache_rejected_out: *mut u8, + can_parse_as_module_out: *mut u8, ) -> i32 { - let _ = (is_sea_main, should_detect_module); + let _ = (host_defined_option_id, line_offset, column_offset); + let _ = (cache_bytes, cache_byte_length); let Ok(state) = (unsafe { env_mut(env) }) else { return NAPI_INVALID_ARG; }; - let Some(source) = state.get(source_text_id).and_then(JsValue::as_string) else { - return if source_bytecode_id != 0 { - NAPI_GENERIC_FAILURE - } else { - NAPI_STRING_EXPECTED - }; + let Some(source_value) = state.get(source_text_id).cloned() else { + return NAPI_INVALID_ARG; }; - let params = Array::new(); - for name in ["exports", "require", "module", "__filename", "__dirname"] { - params.push(&JsValue::from_str(name)); + let Some(source) = source_value.as_string() else { + return NAPI_STRING_EXPECTED; + }; + if cache_policy > 1 { + return NAPI_INVALID_ARG; + } + if has_cache != 0 && cache_policy == 1 { + let _ = unsafe { write(bytecode_out, 0) }; + let _ = unsafe { write(cache_rejected_out, 1) }; + let _ = unsafe { write(can_parse_as_module_out, 0) }; + return NAPI_OK; } let filename = state .get(filename_id) .and_then(JsValue::as_string) .unwrap_or_default(); + let params = Array::new(); + if params_id != 0 { + let Some(values) = state.get(params_id).filter(|value| Array::is_array(value)) else { + return NAPI_ARRAY_EXPECTED; + }; + for value in Array::from(values).iter() { + match js_string(&value) { + Ok(value) => { + params.push(&JsValue::from_str(&value)); + } + Err(error) => { + state.last_exception = Some(error); + return NAPI_PENDING_EXCEPTION; + } + } + } + } let scope = wasmer_napi_global_context_scope(&state.global_context); let context_extensions = Array::new(); - match wasmer_napi_compile_function(&scope, &context_extensions, ¶ms, &source, &filename) { - Ok(function) => { - let result = Object::new(); - let _ = Reflect::set( - &result, - &JsValue::from_str("cachedDataRejected"), - &JsValue::FALSE, - ); - let _ = Reflect::set( - &result, - &JsValue::from_str("canParseAsESM"), - &JsValue::FALSE, - ); - let _ = Reflect::set( - &result, - &JsValue::from_str("sourceMapURL"), - &JsValue::UNDEFINED, - ); - let source_url = state - .get(filename_id) - .cloned() - .unwrap_or(JsValue::UNDEFINED); - let _ = Reflect::set(&result, &JsValue::from_str("sourceURL"), &source_url); - let _ = Reflect::set(&result, &JsValue::from_str("function"), function.as_ref()); - let id = state.insert(result.into()); - unsafe { write(result_out, id) }.map_or_else(|err| err, |()| NAPI_OK) - } - Err(error) => { - state.last_exception = Some(error); - NAPI_PENDING_EXCEPTION - } + let compile_result = match shape { + 0 => wasmer_napi_validate_script(&source, &filename), + 1 => wasmer_napi_compile_function(&scope, &context_extensions, ¶ms, &source, &filename) + .map(|_| ()), + 2 => wasmer_napi_compile_module(&scope, &source, &filename).map(|_| ()), + _ => return NAPI_INVALID_ARG, + }; + if let Err(error) = compile_result { + let can_parse_as_module = + shape != 2 && wasmer_napi_compile_module(&scope, &source, &filename).is_ok(); + let _ = unsafe { write(cache_rejected_out, has_cache) }; + let _ = unsafe { write(can_parse_as_module_out, u8::from(can_parse_as_module)) }; + state.last_exception = Some(error); + return NAPI_PENDING_EXCEPTION; } -} -#[unsafe(no_mangle)] -pub unsafe extern "C" fn snapi_bridge_unofficial_bytecode_compile( - env: SnapiEnv, - source_text_id: u32, - filename_id: u32, - shape: i32, - params_id: u32, - host_defined_option_id: u32, - line_offset: i32, - column_offset: i32, - bytecode_out: *mut u32, - can_parse_as_module_out: *mut u8, -) -> i32 { - let _ = ( - env, - source_text_id, - filename_id, - shape, - params_id, - host_defined_option_id, - line_offset, - column_offset, - bytecode_out, - can_parse_as_module_out, - ); - NAPI_GENERIC_FAILURE -} -#[unsafe(no_mangle)] -pub unsafe extern "C" fn snapi_bridge_unofficial_bytecode_deserialize( - env: SnapiEnv, - bytes: *const u8, - byte_length: usize, - source_text_id: u32, - filename_id: u32, - shape: i32, - params_id: u32, - host_defined_option_id: u32, - bytecode_out: *mut u32, - rejected_out: *mut u8, -) -> i32 { - let _ = ( - env, - bytes, - byte_length, - source_text_id, - filename_id, - shape, - params_id, - host_defined_option_id, - bytecode_out, - rejected_out, + + let bytecode = state.next_bytecode.max(1); + state.next_bytecode = bytecode.wrapping_add(1).max(1); + state.bytecodes.insert( + bytecode, + HostJsBytecode { + source: source_value, + shape, + }, ); - NAPI_GENERIC_FAILURE + if let Err(error) = unsafe { write(bytecode_out, bytecode) } { + state.bytecodes.remove(&bytecode); + return error; + } + if let Err(error) = unsafe { write(cache_rejected_out, has_cache) } { + state.bytecodes.remove(&bytecode); + return error; + } + if let Err(error) = unsafe { write(can_parse_as_module_out, 0) } { + state.bytecodes.remove(&bytecode); + return error; + } + NAPI_OK } #[unsafe(no_mangle)] pub unsafe extern "C" fn snapi_bridge_unofficial_bytecode_serialize( @@ -4312,43 +4326,30 @@ pub unsafe extern "C" fn snapi_bridge_unofficial_bytecode_serialize( bytecode_id: u32, buffer_out: *mut u32, ) -> i32 { - let _ = (env, bytecode_id, buffer_out); - NAPI_GENERIC_FAILURE + let Ok(state) = (unsafe { env_mut(env) }) else { + return NAPI_INVALID_ARG; + }; + if !state.bytecodes.contains_key(&bytecode_id) { + return NAPI_INVALID_ARG; + } + let id = state.insert(Uint8Array::new_with_length(0).into()); + unsafe { write(buffer_out, id) }.map_or_else(|error| error, |()| NAPI_OK) } #[unsafe(no_mangle)] pub unsafe extern "C" fn snapi_bridge_unofficial_bytecode_release( env: SnapiEnv, bytecode_id: u32, ) -> i32 { - let _ = (env, bytecode_id); - NAPI_GENERIC_FAILURE -} -#[unsafe(no_mangle)] -pub unsafe extern "C" fn snapi_bridge_unofficial_contextify_start_sigint_watchdog( - env: SnapiEnv, - result_out: *mut i32, -) -> i32 { - let _ = (env, result_out); - NAPI_GENERIC_FAILURE -} -#[unsafe(no_mangle)] -pub unsafe extern "C" fn snapi_bridge_unofficial_contextify_stop_sigint_watchdog( - env: SnapiEnv, - had_pending_signal_out: *mut i32, -) -> i32 { - let _ = (env, had_pending_signal_out); - NAPI_GENERIC_FAILURE -} -#[unsafe(no_mangle)] -pub unsafe extern "C" fn snapi_bridge_unofficial_contextify_watchdog_has_pending_sigint( - env: SnapiEnv, - result_out: *mut i32, -) -> i32 { - let _ = (env, result_out); - NAPI_GENERIC_FAILURE + let Ok(state) = (unsafe { env_mut(env) }) else { + return NAPI_INVALID_ARG; + }; + if state.bytecodes.remove(&bytecode_id).is_some() { + NAPI_OK + } else { + NAPI_INVALID_ARG + } } -#[unsafe(no_mangle)] -pub unsafe extern "C" fn snapi_bridge_unofficial_module_wrap_create_source_text( +unsafe fn host_js_create_source_text_module( env: SnapiEnv, wrapper_id: u32, url_id: u32, @@ -4359,22 +4360,22 @@ pub unsafe extern "C" fn snapi_bridge_unofficial_module_wrap_create_source_text( column_offset: i32, host_defined_option_id: u32, handle_out: *mut u32, + requests_out: *mut u32, + has_top_level_await_out: *mut u8, ) -> i32 { let _ = (line_offset, column_offset, host_defined_option_id); let Ok(state) = (unsafe { env_mut(env) }) else { return NAPI_INVALID_ARG; }; - let Some(wrapper) = state.get(wrapper_id).cloned() else { + if state.get(wrapper_id).is_none() { return NAPI_INVALID_ARG; - }; + } let Some(url) = state.get(url_id).and_then(JsValue::as_string) else { return NAPI_STRING_EXPECTED; }; - if source_bytecode_id != 0 { - return NAPI_GENERIC_FAILURE; - } - let Some(source) = state.get(source_text_id).and_then(JsValue::as_string) else { - return NAPI_STRING_EXPECTED; + let source = match host_js_source_for_shape(state, source_text_id, source_bytecode_id, 2) { + Ok(source) => source, + Err(status) => return status, }; let scope = match evaluation_scope(state, context_id) { @@ -4424,10 +4425,10 @@ pub unsafe extern "C" fn snapi_bridge_unofficial_module_wrap_create_source_text( let handle = state.next_module; state.next_module = state.next_module.wrapping_add(1).max(1); + let requests_id = state.insert(requests.clone().into()); state.source_text_modules.insert( handle, SourceTextModule { - wrapper, url, requests, linked_handles: Vec::new(), @@ -4439,10 +4440,18 @@ pub unsafe extern "C" fn snapi_bridge_unofficial_module_wrap_create_source_text( evaluation: None, }, ); - unsafe { write(handle_out, handle) }.map_or_else(|error| error, |()| NAPI_OK) + for result in [ + unsafe { write(handle_out, handle) }, + unsafe { write(requests_out, requests_id) }, + unsafe { write(has_top_level_await_out, u8::from(has_top_level_await)) }, + ] { + if let Err(error) = result { + return error; + } + } + NAPI_OK } -#[unsafe(no_mangle)] -pub unsafe extern "C" fn snapi_bridge_unofficial_module_wrap_create_synthetic( +unsafe fn host_js_create_synthetic_module( env: SnapiEnv, wrapper_id: u32, url_id: u32, @@ -4450,6 +4459,8 @@ pub unsafe extern "C" fn snapi_bridge_unofficial_module_wrap_create_synthetic( export_names_id: u32, synthetic_eval_steps_id: u32, handle_out: *mut u32, + requests_out: *mut u32, + has_top_level_await_out: *mut u8, ) -> i32 { let _ = (url_id, context_id); let Ok(state) = (unsafe { env_mut(env) }) else { @@ -4500,38 +4511,81 @@ pub unsafe extern "C" fn snapi_bridge_unofficial_module_wrap_create_synthetic( evaluation: None, }, ); - unsafe { write(handle_out, handle) }.map_or_else(|error| error, |()| NAPI_OK) + let requests_id = state.insert(Array::new().into()); + for result in [ + unsafe { write(handle_out, handle) }, + unsafe { write(requests_out, requests_id) }, + unsafe { write(has_top_level_await_out, 0) }, + ] { + if let Err(error) = result { + return error; + } + } + NAPI_OK } #[unsafe(no_mangle)] -pub unsafe extern "C" fn snapi_bridge_unofficial_module_wrap_destroy( +#[allow(clippy::too_many_arguments)] +pub unsafe extern "C" fn snapi_bridge_unofficial_module_wrap_create( env: SnapiEnv, - handle_id: u32, + kind: i32, + wrapper_id: u32, + url_id: u32, + context_id: u32, + source_text_id: u32, + source_bytecode_id: u32, + line_offset: i32, + column_offset: i32, + host_defined_option_id: u32, + export_names_id: u32, + synthetic_eval_steps_id: u32, + handle_out: *mut u32, + requests_out: *mut u32, + has_top_level_await_out: *mut u8, ) -> i32 { - let Ok(state) = (unsafe { env_mut(env) }) else { - return NAPI_INVALID_ARG; - }; - state.synthetic_modules.remove(&handle_id); - state.source_text_modules.remove(&handle_id); - NAPI_OK + match kind { + 1 => unsafe { + host_js_create_source_text_module( + env, + wrapper_id, + url_id, + context_id, + source_text_id, + source_bytecode_id, + line_offset, + column_offset, + host_defined_option_id, + handle_out, + requests_out, + has_top_level_await_out, + ) + }, + 2 => unsafe { + host_js_create_synthetic_module( + env, + wrapper_id, + url_id, + context_id, + export_names_id, + synthetic_eval_steps_id, + handle_out, + requests_out, + has_top_level_await_out, + ) + }, + _ => NAPI_INVALID_ARG, + } } #[unsafe(no_mangle)] -pub unsafe extern "C" fn snapi_bridge_unofficial_module_wrap_get_module_requests( +pub unsafe extern "C" fn snapi_bridge_unofficial_module_wrap_destroy( env: SnapiEnv, handle_id: u32, - result_out: *mut u32, ) -> i32 { let Ok(state) = (unsafe { env_mut(env) }) else { return NAPI_INVALID_ARG; }; - let requests = if state.synthetic_modules.contains_key(&handle_id) { - Array::new() - } else if let Some(module) = state.source_text_modules.get(&handle_id) { - module.requests.clone() - } else { - return NAPI_INVALID_ARG; - }; - let id = state.insert(requests.into()); - unsafe { write(result_out, id) }.map_or_else(|error| error, |()| NAPI_OK) + state.synthetic_modules.remove(&handle_id); + state.source_text_modules.remove(&handle_id); + NAPI_OK } #[unsafe(no_mangle)] pub unsafe extern "C" fn snapi_bridge_unofficial_module_wrap_link( @@ -4890,67 +4944,6 @@ pub unsafe extern "C" fn snapi_bridge_unofficial_module_wrap_get_namespace( let id = state.insert(namespace.into()); unsafe { write(result_out, id) }.map_or_else(|error| error, |()| NAPI_OK) } -#[unsafe(no_mangle)] -pub unsafe extern "C" fn snapi_bridge_unofficial_module_wrap_get_status( - env: SnapiEnv, - handle_id: u32, - status_out: *mut i32, -) -> i32 { - let Ok(state) = (unsafe { env_mut(env) }) else { - return NAPI_INVALID_ARG; - }; - let status = match refresh_module_status(state, handle_id) { - Ok(status) => status, - Err(error) => return error, - }; - unsafe { write(status_out, status) }.map_or_else(|error| error, |()| NAPI_OK) -} -#[unsafe(no_mangle)] -pub unsafe extern "C" fn snapi_bridge_unofficial_module_wrap_get_error( - env: SnapiEnv, - handle_id: u32, - result_out: *mut u32, -) -> i32 { - let Ok(state) = (unsafe { env_mut(env) }) else { - return NAPI_INVALID_ARG; - }; - if !state.synthetic_modules.contains_key(&handle_id) - && !state.source_text_modules.contains_key(&handle_id) - { - return NAPI_INVALID_ARG; - } - let error = state - .synthetic_modules - .get(&handle_id) - .and_then(|module| module.error.clone()) - .or_else(|| { - state - .source_text_modules - .get(&handle_id) - .and_then(|module| module.error.clone()) - }) - .unwrap_or(JsValue::UNDEFINED); - let id = state.insert(error); - unsafe { write(result_out, id) }.map_or_else(|error| error, |()| NAPI_OK) -} -#[unsafe(no_mangle)] -pub unsafe extern "C" fn snapi_bridge_unofficial_module_wrap_has_top_level_await( - env: SnapiEnv, - handle_id: u32, - result_out: *mut i32, -) -> i32 { - let Ok(state) = (unsafe { env_mut(env) }) else { - return NAPI_INVALID_ARG; - }; - let result = if state.synthetic_modules.contains_key(&handle_id) { - false - } else if let Some(module) = state.source_text_modules.get(&handle_id) { - module.has_top_level_await - } else { - return NAPI_INVALID_ARG; - }; - unsafe { write(result_out, i32::from(result)) }.map_or_else(|error| error, |()| NAPI_OK) -} fn module_graph_has_top_level_await( state: &HostJsEnv, @@ -4974,23 +4967,66 @@ fn module_graph_has_top_level_await( } #[unsafe(no_mangle)] -pub unsafe extern "C" fn snapi_bridge_unofficial_module_wrap_has_async_graph( +pub unsafe extern "C" fn snapi_bridge_unofficial_module_wrap_get_state( env: SnapiEnv, handle_id: u32, - result_out: *mut i32, + status_out: *mut i32, + error_out: *mut u32, + has_async_graph_out: *mut i32, ) -> i32 { let Ok(state) = (unsafe { env_mut(env) }) else { return NAPI_INVALID_ARG; }; - let Some(result) = module_graph_has_top_level_await(state, handle_id, &mut Vec::new()) else { + if status_out.is_null() && error_out.is_null() && has_async_graph_out.is_null() { return NAPI_INVALID_ARG; + } + let status = match refresh_module_status(state, handle_id) { + Ok(status) => status, + Err(error) => return error, + }; + let has_async_graph = if has_async_graph_out.is_null() { + None + } else { + let Some(has_async_graph) = + module_graph_has_top_level_await(state, handle_id, &mut Vec::new()) + else { + return NAPI_INVALID_ARG; + }; + Some(status >= 2 && has_async_graph) }; - unsafe { write(result_out, i32::from(result)) }.map_or_else(|error| error, |()| NAPI_OK) + if !status_out.is_null() { + if let Err(error) = unsafe { write(status_out, status) } { + return error; + } + } + if !error_out.is_null() { + let error = state + .synthetic_modules + .get(&handle_id) + .and_then(|module| module.error.clone()) + .or_else(|| { + state + .source_text_modules + .get(&handle_id) + .and_then(|module| module.error.clone()) + }) + .unwrap_or(JsValue::UNDEFINED); + let error_id = state.insert(error); + if let Err(error) = unsafe { write(error_out, error_id) } { + return error; + } + } + if let Some(has_async_graph) = has_async_graph { + if let Err(error) = unsafe { write(has_async_graph_out, i32::from(has_async_graph)) } { + return error; + } + } + NAPI_OK } #[unsafe(no_mangle)] pub unsafe extern "C" fn snapi_bridge_unofficial_module_wrap_check_unsettled_top_level_await( env: SnapiEnv, - module_wrap_id: u32, + handle_id: u32, warnings: i32, settled_out: *mut i32, ) -> i32 { @@ -4998,19 +5034,14 @@ pub unsafe extern "C" fn snapi_bridge_unofficial_module_wrap_check_unsettled_top let Ok(state) = (unsafe { env_mut(env) }) else { return NAPI_INVALID_ARG; }; - let Some(wrapper) = state.get(module_wrap_id).cloned() else { - return unsafe { write(settled_out, 1) }.map_or_else(|error| error, |()| NAPI_OK); - }; - let handle_id = state - .source_text_modules - .iter() - .find_map(|(handle, module)| Object::is(&wrapper, &module.wrapper).then_some(*handle)); - let settled = if let Some(handle_id) = handle_id { + let settled = if state.source_text_modules.contains_key(&handle_id) { let is_async = module_graph_has_top_level_await(state, handle_id, &mut Vec::new()).unwrap_or(false); !is_async || refresh_module_status(state, handle_id).unwrap_or(4) != 3 - } else { + } else if state.synthetic_modules.contains_key(&handle_id) { true + } else { + return NAPI_INVALID_ARG; }; unsafe { write(settled_out, i32::from(settled)) }.map_or_else(|error| error, |()| NAPI_OK) } @@ -5079,32 +5110,15 @@ pub unsafe extern "C" fn snapi_bridge_unofficial_module_wrap_create_cached_data( NAPI_GENERIC_FAILURE } #[unsafe(no_mangle)] -pub unsafe extern "C" fn snapi_bridge_unofficial_module_wrap_set_import_module_dynamically_callback( +pub unsafe extern "C" fn snapi_bridge_unofficial_module_wrap_set_hooks( env: SnapiEnv, - callback_id: u32, + import_callback_id: u32, + import_meta_callback_id: u32, ) -> i32 { - let _ = (env, callback_id); - NAPI_OK -} -#[unsafe(no_mangle)] -pub unsafe extern "C" fn snapi_bridge_unofficial_module_wrap_set_initialize_import_meta_object_callback( - env: SnapiEnv, - callback_id: u32, -) -> i32 { - let _ = (env, callback_id); + let _ = (env, import_callback_id, import_meta_callback_id); NAPI_OK } #[unsafe(no_mangle)] -pub unsafe extern "C" fn snapi_bridge_unofficial_module_wrap_import_module_dynamically( - env: SnapiEnv, - argc: u32, - argv_ids: *const u32, - result_out: *mut u32, -) -> i32 { - let _ = (env, argc, argv_ids, result_out); - NAPI_GENERIC_FAILURE -} -#[unsafe(no_mangle)] pub unsafe extern "C" fn snapi_bridge_unofficial_module_wrap_create_required_module_facade( env: SnapiEnv, handle_id: u32, diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 702cd73..8ab62ef 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -152,8 +152,12 @@ function(add_fixture_test target_name) INSTALL_RPATH "${_napi_engine_lib_dir}" ) endif() + # Keep executable discovery in the test phase. Running every engine-backed + # binary as a parallel post-link command can turn a transient discovery + # failure into a failed build and an incomplete CMake JSON file. gtest_discover_tests( ${target_name} + DISCOVERY_MODE PRE_TEST DISCOVERY_TIMEOUT 30 TEST_PREFIX "${_napi_test_prefix}.${target_name}." ) diff --git a/tests/build-test-native.sh b/tests/build-test-native.sh index 7ad98f3..f98cc4c 100755 --- a/tests/build-test-native.sh +++ b/tests/build-test-native.sh @@ -192,7 +192,6 @@ while IFS= read -r dep; do done < <(find "$NAPI_INCLUDE_DIR" "$TEST_INCLUDE_DIR" -type f \( -name '*.h' -o -name '*.hpp' \) | sort) NAPI_V8_SOURCES=( - "$ROOT_DIR/src/edge_napi_embedder_hooks.cc" "$NAPI_LIB_SRC/napi_buffer_lease.cc" "$NAPI_LIB_SRC/napi_error_state.cc" "$NAPI_LIB_SRC/napi_lifetime_tracker.cc" diff --git a/tests/js-native-api/test_object/test_object.c b/tests/js-native-api/test_object/test_object.c index 6c36ca4..90ad1ad 100644 --- a/tests/js-native-api/test_object/test_object.c +++ b/tests/js-native-api/test_object/test_object.c @@ -84,6 +84,23 @@ static napi_value GetPropertyNames(napi_env env, napi_callback_info info) { return output; } +static napi_value GetOwnPropertyNamesKeepNumbers(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_value output; + NODE_API_CALL(env, + napi_get_all_property_names( + env, args[0], napi_key_own_only, napi_key_skip_symbols, + napi_key_keep_numbers, &output)); + + return output; +} + static napi_value GetSymbolNames(napi_env env, napi_callback_info info) { size_t argc = 1; napi_value args[1]; @@ -807,6 +824,8 @@ napi_value Init(napi_env env, napi_value exports) { DECLARE_NODE_API_PROPERTY("Get", Get), DECLARE_NODE_API_PROPERTY("GetNamed", GetNamed), DECLARE_NODE_API_PROPERTY("GetPropertyNames", GetPropertyNames), + DECLARE_NODE_API_PROPERTY("GetOwnPropertyNamesKeepNumbers", + GetOwnPropertyNamesKeepNumbers), DECLARE_NODE_API_PROPERTY("GetSymbolNames", GetSymbolNames), DECLARE_NODE_API_PROPERTY("GetEnumerableWritableNames", GetEnumerableWritableNames), diff --git a/tests/napi_native_init.cc b/tests/napi_native_init.cc index 482e946..7ed2583 100644 --- a/tests/napi_native_init.cc +++ b/tests/napi_native_init.cc @@ -7,11 +7,12 @@ #include "unofficial_napi.h" static napi_env g_env = nullptr; -static void* g_scope = nullptr; +static unofficial_napi_env_owner g_scope = nullptr; extern "C" napi_env napi_wasm_init_env(void) { if (g_env != nullptr) return g_env; - napi_status s = unofficial_napi_create_env(8, &g_env, &g_scope); + if (unofficial_napi_configure_runtime(nullptr) != napi_ok) return nullptr; + napi_status s = unofficial_napi_create_env(8, nullptr, &g_env, &g_scope); return (s == napi_ok) ? g_env : nullptr; } diff --git a/tests/programs/run_script_test.c b/tests/programs/run_script_test.c index 8023061..6fd094b 100644 --- a/tests/programs/run_script_test.c +++ b/tests/programs/run_script_test.c @@ -92,8 +92,8 @@ int main(void) { CHECK_OR_FAIL(marker == 41, "failed to set the first environment marker"); napi_env env2 = NULL; - void* env2_scope = NULL; - NAPI_CALL(env, unofficial_napi_create_env(8, &env2, &env2_scope)); + unofficial_napi_env_owner env2_scope = NULL; + NAPI_CALL(env, unofficial_napi_create_env(8, NULL, &env2, &env2_scope)); CHECK_OR_FAIL(env2 != NULL && env2_scope != NULL, "failed to create the second N-API environment"); @@ -111,7 +111,7 @@ int main(void) { NAPI_CALL(env, RunInt32Script( env, "globalThis.__napi_env_isolation_marker", &marker)); CHECK_OR_FAIL(marker == 41, "the second environment mutated the first global"); - NAPI_CALL(env, unofficial_napi_release_env(env2_scope)); + NAPI_CALL(env, unofficial_napi_release_env(env2_scope, NULL)); // Every code-generation path must select its execution scope explicitly. // In particular, vm compile-function and module compilation must honor the @@ -143,8 +143,8 @@ int main(void) { "globalThis.__napi_context_marker", NAPI_AUTO_LENGTH, &context_source_text)); - const unofficial_napi_js_source context_source = { - context_source_text, NULL}; + const unofficial_napi_js_source context_source = + unofficial_napi_js_source_from_text(context_source_text); napi_value context_result; NAPI_CALL(env, unofficial_napi_contextify_run_script( env, @@ -185,8 +185,8 @@ int main(void) { "__napi_context_extension_marker;", NAPI_AUTO_LENGTH, &function_source_text)); - const unofficial_napi_js_source function_source = { - function_source_text, NULL}; + const unofficial_napi_js_source function_source = + unofficial_napi_js_source_from_text(function_source_text); napi_value compiled; NAPI_CALL(env, unofficial_napi_contextify_compile_function( env, @@ -231,40 +231,74 @@ int main(void) { "globalThis.__napi_context_marker;", NAPI_AUTO_LENGTH, &module_source_text)); - const unofficial_napi_js_source module_source = {module_source_text, NULL}; - void* module_handle = NULL; - NAPI_CALL(env, unofficial_napi_module_wrap_create_source_text( - env, - module_wrapper, - module_url, - context, - &module_source, - 0, - 0, - undefined_value, - &module_handle)); - CHECK_OR_FAIL(module_handle != NULL, + const unofficial_napi_js_source module_source = + unofficial_napi_js_source_from_text(module_source_text); + unofficial_napi_module_create_options module_options = {0}; + module_options.size = sizeof(module_options); + module_options.version = UNOFFICIAL_NAPI_MODULE_CREATE_OPTIONS_VERSION; + module_options.kind = unofficial_napi_module_source_text; + module_options.wrapper = module_wrapper; + module_options.url = module_url; + module_options.context_or_undefined = context; + module_options.payload.source_text.source = &module_source; + module_options.payload.source_text.host_defined_option_id = undefined_value; + unofficial_napi_module_create_result module_create_result = {0}; + NAPI_CALL(env, unofficial_napi_module_wrap_create( + env, &module_options, &module_create_result)); + unofficial_napi_module module = module_create_result.module; + CHECK_OR_FAIL(module != NULL, "module compilation did not return a handle"); + CHECK_OR_FAIL(module_create_result.module_requests != NULL, + "module compilation did not return requests"); + CHECK_OR_FAIL(!module_create_result.has_top_level_await, + "synchronous module reported top-level await at creation"); + int32_t module_status = -1; + napi_value module_error = NULL; + bool module_has_async_graph = false; + NAPI_CALL(env, unofficial_napi_module_wrap_get_state( + env, + module, + &module_status, + &module_error, + &module_has_async_graph)); + CHECK_OR_FAIL(module_status == 0, + "new module snapshot did not report uninstantiated status"); + CHECK_OR_FAIL(module_error != NULL, + "module snapshot did not return an undefined error value"); + CHECK_OR_FAIL(!module_has_async_graph, + "uninstantiated module reported an async graph"); NAPI_CALL(env, unofficial_napi_module_wrap_link( - env, module_handle, 0, NULL)); - NAPI_CALL(env, unofficial_napi_module_wrap_instantiate(env, module_handle)); + env, module, 0, NULL)); + NAPI_CALL(env, unofficial_napi_module_wrap_instantiate(env, module)); + module_status = -1; + module_has_async_graph = true; + NAPI_CALL(env, unofficial_napi_module_wrap_get_state( + env, + module, + &module_status, + NULL, + &module_has_async_graph)); + CHECK_OR_FAIL(module_status == 2, + "instantiated module snapshot reported the wrong status"); + CHECK_OR_FAIL(!module_has_async_graph, + "synchronous module reported an async graph"); napi_value module_result; NAPI_CALL(env, unofficial_napi_module_wrap_evaluate_sync( env, - module_handle, + module, module_url, module_url, &module_result)); napi_value module_namespace; napi_value module_observed; NAPI_CALL(env, unofficial_napi_module_wrap_get_namespace( - env, module_handle, &module_namespace)); + env, module, &module_namespace)); NAPI_CALL(env, napi_get_named_property( env, module_namespace, "observed", &module_observed)); NAPI_CALL(env, napi_get_value_int32(env, module_observed, &marker)); CHECK_OR_FAIL(marker == 73, "module evaluation escaped its explicit context"); - NAPI_CALL(env, unofficial_napi_module_wrap_destroy(env, module_handle)); + NAPI_CALL(env, unofficial_napi_module_wrap_destroy(env, module)); // A memory lease, rather than a scope-bound napi_value or its data pointer, // owns the value and copy-back. Releasing after the handle scope closes is @@ -442,6 +476,7 @@ int main(void) { // Forged guest lengths are rejected before allocating a host scratch // buffer. This call must return an error rather than aborting the host. +#if defined(__wasm__) napi_status oversized_string_status = napi_get_value_string_utf8(env, result, @@ -450,14 +485,15 @@ int main(void) { &len); CHECK_OR_FAIL(oversized_string_status != napi_ok, "oversized guest string length was not rejected"); +#endif // Guest wrap/add-finalizer callbacks are dispatched at an explicit provider // checkpoint; environment teardown force-drains remaining live records. napi_env finalizer_env = NULL; - void* finalizer_env_scope = NULL; + unofficial_napi_env_owner finalizer_env_scope = NULL; NAPI_CALL(env, unofficial_napi_create_env( - 8, &finalizer_env, &finalizer_env_scope)); + 8, NULL, &finalizer_env, &finalizer_env_scope)); napi_value wrapped_object; napi_value finalized_object; NAPI_CALL(finalizer_env, @@ -478,7 +514,7 @@ int main(void) { CountAddedFinalizer, (void*)(uintptr_t)0x22, NULL)); - NAPI_CALL(env, unofficial_napi_release_env(finalizer_env_scope)); + NAPI_CALL(env, unofficial_napi_release_env(finalizer_env_scope, NULL)); CHECK_OR_FAIL(wrap_finalizer_count == 1 && add_finalizer_count == 1, "guest finalizers were not dispatched exactly once"); @@ -487,8 +523,8 @@ int main(void) { // a scope-bound value or an explicit release from already-destroyed Edge // state. napi_env lease_env = NULL; - void* lease_env_scope = NULL; - NAPI_CALL(env, unofficial_napi_create_env(8, &lease_env, &lease_env_scope)); + unofficial_napi_env_owner lease_env_scope = NULL; + NAPI_CALL(env, unofficial_napi_create_env(8, NULL, &lease_env, &lease_env_scope)); napi_value abandoned_script; napi_value abandoned_value; NAPI_CALL(lease_env, @@ -511,7 +547,7 @@ int main(void) { &abandoned_data)); CHECK_OR_FAIL(abandoned_lease != NULL && abandoned_data != NULL, "failed to acquire teardown lease"); - NAPI_CALL(env, unofficial_napi_release_env(lease_env_scope)); + NAPI_CALL(env, unofficial_napi_release_env(lease_env_scope, NULL)); return PrintSuccess("RUN_SCRIPT_TEST"); } diff --git a/tests/runners/test_14_exception.cc b/tests/runners/test_14_exception.cc index 55a3058..f2560b3 100644 --- a/tests/runners/test_14_exception.cc +++ b/tests/runners/test_14_exception.cc @@ -27,18 +27,23 @@ std::string ValueToUtf8(napi_env env, napi_value value) { std::string GetArrowMessage(napi_env env, napi_value exception) { if (env == nullptr || exception == nullptr) return {}; - napi_value source_line = nullptr; - napi_value thrown_at = nullptr; - if (unofficial_napi_take_preserved_error_formatting( - env, exception, &source_line, &thrown_at) == napi_ok) { - const std::string preserved = ValueToUtf8(env, source_line); + unofficial_napi_error_metadata metadata{}; + if (unofficial_napi_get_error_metadata( + env, + exception, + unofficial_napi_error_metadata_take_preserved, + &metadata) == napi_ok) { + const std::string preserved = ValueToUtf8(env, metadata.stderr_line); if (!preserved.empty()) return preserved; } - source_line = nullptr; - if (unofficial_napi_get_error_source_line_for_stderr(env, exception, &source_line) == - napi_ok) { - const std::string formatted = ValueToUtf8(env, source_line); + metadata = {}; + if (unofficial_napi_get_error_metadata( + env, + exception, + unofficial_napi_error_metadata_current, + &metadata) == napi_ok) { + const std::string formatted = ValueToUtf8(env, metadata.stderr_line); if (!formatted.empty()) return formatted; } @@ -112,6 +117,66 @@ TEST_F(Test14Exception, SetLastExceptionStoresArrowMessageOnThrownError) { EXPECT_NE(GetArrowMessage(s.env, exception).find("throw new Error('boom')"), std::string::npos); } +TEST_F(Test14Exception, ErrorMetadataSnapshotConsumesPreservedStateAtomically) { + EnvScope s(runtime_.get()); + + napi_value script = nullptr; + ASSERT_EQ( + napi_create_string_utf8( + s.env, + "throw new Error('metadata')\n//# sourceURL=metadata.js", + NAPI_AUTO_LENGTH, + &script), + napi_ok); + napi_value result = nullptr; + ASSERT_EQ(napi_run_script(s.env, script, &result), napi_pending_exception); + + napi_value exception = nullptr; + ASSERT_EQ(napi_get_and_clear_last_exception(s.env, &exception), napi_ok); + ASSERT_NE(exception, nullptr); + + unofficial_napi_error_metadata metadata{}; + ASSERT_EQ(unofficial_napi_get_error_metadata( + s.env, + exception, + unofficial_napi_error_metadata_current, + &metadata), + napi_ok); + EXPECT_NE(ValueToUtf8(s.env, metadata.stderr_line).find("metadata"), + std::string::npos); + EXPECT_EQ(unofficial_napi_get_error_metadata( + s.env, + exception, + static_cast(99), + &metadata), + napi_invalid_arg); + + ASSERT_EQ(unofficial_napi_preserve_error_source_message(s.env, exception), + napi_ok); + metadata = {}; + ASSERT_EQ(unofficial_napi_get_error_metadata( + s.env, + exception, + unofficial_napi_error_metadata_take_preserved, + &metadata), + napi_ok); +#if defined(NAPI_TEST_ENGINE_QUICKJS) + EXPECT_EQ(metadata.stderr_line, nullptr); +#else + EXPECT_NE(ValueToUtf8(s.env, metadata.stderr_line).find("metadata"), + std::string::npos); +#endif + + metadata = {}; + ASSERT_EQ(unofficial_napi_get_error_metadata( + s.env, + exception, + unofficial_napi_error_metadata_take_preserved, + &metadata), + napi_ok); + EXPECT_EQ(metadata.stderr_line, nullptr); +} + TEST_F(Test14Exception, SetLastExceptionPreservesArrowMessageAcrossSameErrorRethrow) { EnvScope s(runtime_.get()); @@ -141,8 +206,6 @@ TEST_F(Test14Exception, SetLastExceptionPreservesArrowMessageAcrossSameErrorReth TEST_F(Test14Exception, PreserveErrorSourceMessageStoresMappedArrowMessageWhenSourceMapsEnabled) { EnvScope s(runtime_.get()); - ASSERT_EQ(unofficial_napi_set_source_maps_enabled(s.env, true), napi_ok); - napi_value callback_script = nullptr; ASSERT_EQ( napi_create_string_utf8( @@ -155,7 +218,7 @@ TEST_F(Test14Exception, PreserveErrorSourceMessageStoresMappedArrowMessageWhenSo ASSERT_EQ(napi_run_script(s.env, callback_script, &callback), napi_ok); ASSERT_NE(callback, nullptr); ASSERT_EQ( - unofficial_napi_set_get_source_map_error_source_callback(s.env, callback), + unofficial_napi_configure_source_maps(s.env, true, callback), napi_ok); napi_value script = nullptr; @@ -181,3 +244,54 @@ TEST_F(Test14Exception, PreserveErrorSourceMessageStoresMappedArrowMessageWhenSo "mapped.js:10\nconst boom = 1;\n ^\n\n"); #endif } + +TEST_F(Test14Exception, ThrownAtOnlyMetadataDoesNotInvokeSourceMapCallback) { + EnvScope s(runtime_.get()); + + napi_value script = nullptr; + napi_value callback = nullptr; + ASSERT_EQ(napi_create_string_utf8( + s.env, + "globalThis.__sourceMapCalls = 0; " + "(() => { ++globalThis.__sourceMapCalls; return 'mapped'; })", + NAPI_AUTO_LENGTH, + &script), + napi_ok); + ASSERT_EQ(napi_run_script(s.env, script, &callback), napi_ok); + ASSERT_NE(callback, nullptr); + ASSERT_EQ(unofficial_napi_configure_source_maps(s.env, true, callback), napi_ok); + + ASSERT_EQ(napi_create_string_utf8( + s.env, + "(() => { try { throw new Error('cheap metadata'); } " + "catch (error) { return error; } })()", + NAPI_AUTO_LENGTH, + &script), + napi_ok); + napi_value error = nullptr; + ASSERT_EQ(napi_run_script(s.env, script, &error), napi_ok); + ASSERT_NE(error, nullptr); + + unofficial_napi_error_metadata metadata{}; + ASSERT_EQ(unofficial_napi_get_error_metadata( + s.env, + error, + unofficial_napi_error_metadata_thrown_at_only, + &metadata), + napi_ok); + EXPECT_EQ(metadata.source_line, nullptr); + EXPECT_EQ(metadata.script_resource_name, nullptr); + EXPECT_EQ(metadata.stderr_line, nullptr); + // A caught V8 Error does not necessarily retain message stack frames, so + // thrown_at may legitimately be absent. The contract here is that no + // unrequested source/position metadata (and no source-map JS) is evaluated. + + napi_value calls = nullptr; + ASSERT_EQ(napi_create_string_utf8( + s.env, "globalThis.__sourceMapCalls", NAPI_AUTO_LENGTH, &script), + napi_ok); + ASSERT_EQ(napi_run_script(s.env, script, &calls), napi_ok); + int32_t call_count = -1; + ASSERT_EQ(napi_get_value_int32(s.env, calls, &call_count), napi_ok); + EXPECT_EQ(call_count, 0); +} diff --git a/tests/runners/test_21_general.cc b/tests/runners/test_21_general.cc index ea18c0a..d6ad76e 100644 --- a/tests/runners/test_21_general.cc +++ b/tests/runners/test_21_general.cc @@ -6,13 +6,203 @@ #include "test_env.h" #include "upstream_js_test.h" +#include +#include #include #include +#include +#include extern "C" napi_value Init(napi_env env, napi_value exports); +namespace { +int g_guest_heap_release_calls = 0; +void* g_last_released_guest_heap_ctx = nullptr; +} // namespace + +extern "C" void napi_host_guest_heap_release(void* ctx) { + ++g_guest_heap_release_calls; + g_last_released_guest_heap_ctx = ctx; +} + class Test21General : public FixtureTestBase {}; +namespace { + +size_t NearHeapLimitProbe(napi_env env, + void* data, + size_t current_heap_limit, + size_t initial_heap_limit) { + EXPECT_NE(env, nullptr); + EXPECT_NE(data, nullptr); + EXPECT_GE(current_heap_limit, initial_heap_limit); + return current_heap_limit; +} + +} // namespace + +TEST_F(Test21General, EnvironmentCreationOptionsAreVersioned) { + napi_env env = nullptr; + unofficial_napi_env_owner owner = nullptr; + + unofficial_napi_env_create_options options{}; + options.size = sizeof(options) - 1; + options.version = UNOFFICIAL_NAPI_ENV_CREATE_OPTIONS_VERSION; + EXPECT_EQ(unofficial_napi_create_env( + NAPI_TEST_MODULE_API_VERSION, &options, &env, &owner), + napi_invalid_arg); + EXPECT_EQ(env, nullptr); + EXPECT_EQ(owner, nullptr); + + options.size = sizeof(options); + options.version += 1; + EXPECT_EQ(unofficial_napi_create_env( + NAPI_TEST_MODULE_API_VERSION, &options, &env, &owner), + napi_invalid_arg); + +} + +TEST_F(Test21General, RuntimeConfigurationIsVersionedAndImmutable) { + unofficial_napi_runtime_options malformed{}; + malformed.size = sizeof(malformed) - 1; + malformed.version = UNOFFICIAL_NAPI_RUNTIME_OPTIONS_VERSION; + EXPECT_EQ(unofficial_napi_configure_runtime(&malformed), napi_invalid_arg); + + malformed.size = sizeof(malformed); + malformed.version += 1; + EXPECT_EQ(unofficial_napi_configure_runtime(&malformed), napi_invalid_arg); + + static constexpr char kConflictingFlags[] = "--no-js-float16array"; + unofficial_napi_runtime_options conflicting{}; + conflicting.size = sizeof(conflicting); + conflicting.version = UNOFFICIAL_NAPI_RUNTIME_OPTIONS_VERSION; + conflicting.engine_flags = kConflictingFlags; + conflicting.engine_flags_length = sizeof(kConflictingFlags) - 1; + EXPECT_EQ(unofficial_napi_configure_runtime(&conflicting), napi_invalid_arg); +} + +TEST_F(Test21General, TruncatedEnvironmentOptionsDoNotTransferGuestHeapOwnership) { + alignas(unofficial_napi_env_create_options) + std::array bytes{}; + const uint32_t size = 2 * sizeof(uint32_t); + const uint32_t version = UNOFFICIAL_NAPI_ENV_CREATE_OPTIONS_VERSION; + int guest_heap_marker = 0; + unofficial_napi_guest_heap guest_heap = + reinterpret_cast(&guest_heap_marker); + std::memcpy(bytes.data(), &size, sizeof(size)); + std::memcpy(bytes.data() + sizeof(size), &version, sizeof(version)); + std::memcpy(bytes.data() + offsetof(unofficial_napi_env_create_options, + guest_heap), + &guest_heap, + sizeof(guest_heap)); + + g_guest_heap_release_calls = 0; + g_last_released_guest_heap_ctx = nullptr; + napi_env env = nullptr; + unofficial_napi_env_owner owner = nullptr; + EXPECT_EQ(unofficial_napi_create_env( + NAPI_TEST_MODULE_API_VERSION, + reinterpret_cast( + bytes.data()), + &env, + &owner), + napi_invalid_arg); + EXPECT_EQ(env, nullptr); + EXPECT_EQ(owner, nullptr); + EXPECT_EQ(g_guest_heap_release_calls, 0); + EXPECT_EQ(g_last_released_guest_heap_ctx, nullptr); +} + +TEST_F(Test21General, FullEnvironmentOptionsTransferGuestHeapOwnershipBeforeOutputValidation) { + unofficial_napi_env_create_options options{}; + InitializeTestEnvCreateOptions(&options); + int first_guest_heap_marker = 0; + options.guest_heap = + reinterpret_cast(&first_guest_heap_marker); + + g_guest_heap_release_calls = 0; + g_last_released_guest_heap_ctx = nullptr; + unofficial_napi_env_owner owner = nullptr; + EXPECT_EQ(unofficial_napi_create_env( + NAPI_TEST_MODULE_API_VERSION, &options, nullptr, &owner), + napi_invalid_arg); + EXPECT_EQ(owner, nullptr); + EXPECT_EQ(g_guest_heap_release_calls, 1); + EXPECT_EQ(g_last_released_guest_heap_ctx, options.guest_heap); + + int second_guest_heap_marker = 0; + options.guest_heap = + reinterpret_cast(&second_guest_heap_marker); + napi_env env = nullptr; + EXPECT_EQ(unofficial_napi_create_env( + NAPI_TEST_MODULE_API_VERSION, &options, &env, nullptr), + napi_invalid_arg); + EXPECT_EQ(env, nullptr); + EXPECT_EQ(g_guest_heap_release_calls, 2); + EXPECT_EQ(g_last_released_guest_heap_ctx, options.guest_heap); +} + +TEST_F(Test21General, EnvironmentHooksAttachAtomicallyOnce) { + napi_env env = nullptr; + unofficial_napi_env_owner owner = nullptr; + unofficial_napi_env_create_options options{}; + InitializeTestEnvCreateOptions(&options); + ASSERT_EQ(unofficial_napi_create_env( + NAPI_TEST_MODULE_API_VERSION, &options, &env, &owner), + napi_ok); + ASSERT_NE(env, nullptr); + ASSERT_NE(owner, nullptr); + + unofficial_napi_env_hooks hooks{}; + hooks.size = sizeof(hooks); + hooks.version = UNOFFICIAL_NAPI_ENV_HOOKS_VERSION; + + unofficial_napi_env_hooks invalid = hooks; + invalid.version += 1; + uint64_t accepted_hooks = 0; + EXPECT_EQ(unofficial_napi_attach_env(env, &invalid, &accepted_hooks), napi_invalid_arg); + ASSERT_EQ(unofficial_napi_attach_env(env, &hooks, &accepted_hooks), napi_ok); + EXPECT_EQ(accepted_hooks, 0); + EXPECT_EQ(unofficial_napi_attach_env(env, &hooks, &accepted_hooks), napi_invalid_arg); + + ASSERT_EQ(unofficial_napi_release_env(owner, nullptr), napi_ok); +} + +TEST_F(Test21General, GarbageCollectionUsesProductionProviderPrimitive) { + EnvScope s(runtime_.get()); + + EXPECT_EQ(unofficial_napi_collect_garbage(nullptr), napi_invalid_arg); + EXPECT_EQ(unofficial_napi_collect_garbage(s.env), napi_ok); +} + +TEST_F(Test21General, NearHeapLimitCallbackUsesOneConfigurationSlot) { + EnvScope s(runtime_.get()); + int callback_data = 1; + + EXPECT_EQ(unofficial_napi_configure_near_heap_limit_callback( + s.env, NearHeapLimitProbe, &callback_data, 1), + napi_invalid_arg); + EXPECT_EQ(unofficial_napi_configure_near_heap_limit_callback( + s.env, nullptr, &callback_data, 0), + napi_invalid_arg); + + ASSERT_EQ(unofficial_napi_configure_near_heap_limit_callback( + s.env, NearHeapLimitProbe, &callback_data, 0), + napi_ok); + // Reconfiguration replaces the one logical slot rather than registering a + // second provider callback. + ASSERT_EQ(unofficial_napi_configure_near_heap_limit_callback( + s.env, NearHeapLimitProbe, &callback_data, 0), + napi_ok); + ASSERT_EQ(unofficial_napi_configure_near_heap_limit_callback( + s.env, nullptr, nullptr, 0), + napi_ok); + // Removing an already-empty slot is idempotent. + EXPECT_EQ(unofficial_napi_configure_near_heap_limit_callback( + s.env, nullptr, nullptr, 0), + napi_ok); +} + TEST_F(Test21General, PortedCoreFlow) { EnvScope s(runtime_.get()); napi_value exports = nullptr; @@ -56,6 +246,321 @@ TEST_F(Test21General, ProviderOwnsUninitializedArrayBufferAllocation) { EXPECT_EQ(length, 0u); } +TEST_F(Test21General, StructuredCloneOptionalTransferListDetachesArrayBuffer) { + EnvScope s(runtime_.get()); + + void* source_data = nullptr; + napi_value source_buffer = nullptr; + ASSERT_EQ(napi_create_arraybuffer(s.env, 8, &source_data, &source_buffer), napi_ok); + ASSERT_NE(source_buffer, nullptr); + ASSERT_NE(source_data, nullptr); + static_cast(source_data)[0] = 42; + + napi_value source = nullptr; + ASSERT_EQ(napi_create_object(s.env, &source), napi_ok); + ASSERT_EQ(napi_set_named_property(s.env, source, "buffer", source_buffer), napi_ok); + + napi_value transfer_list = nullptr; + ASSERT_EQ(napi_create_array_with_length(s.env, 1, &transfer_list), napi_ok); + ASSERT_EQ(napi_set_element(s.env, transfer_list, 0, source_buffer), napi_ok); + + napi_value clone = nullptr; + ASSERT_EQ(unofficial_napi_structured_clone(s.env, source, transfer_list, &clone), napi_ok); + ASSERT_NE(clone, nullptr); + + napi_value source_byte_length = nullptr; + ASSERT_EQ(napi_get_named_property(s.env, source_buffer, "byteLength", &source_byte_length), + napi_ok); + uint32_t source_length = 8; + ASSERT_EQ(napi_get_value_uint32(s.env, source_byte_length, &source_length), napi_ok); + EXPECT_EQ(source_length, 0u); + + napi_value cloned_buffer = nullptr; + ASSERT_EQ(napi_get_named_property(s.env, clone, "buffer", &cloned_buffer), napi_ok); + void* cloned_data = nullptr; + size_t cloned_length = 0; + ASSERT_EQ(napi_get_arraybuffer_info(s.env, cloned_buffer, &cloned_data, &cloned_length), napi_ok); + ASSERT_EQ(cloned_length, 8u); + ASSERT_NE(cloned_data, nullptr); + EXPECT_EQ(static_cast(cloned_data)[0], 42u); +} + +TEST_F(Test21General, MessageTakeConsumesOpaqueMessage) { + EnvScope s(runtime_.get()); + + napi_value source = nullptr; + ASSERT_EQ(napi_create_object(s.env, &source), napi_ok); + napi_value expected = nullptr; + ASSERT_EQ(napi_create_uint32(s.env, 42, &expected), napi_ok); + ASSERT_EQ(napi_set_named_property(s.env, source, "answer", expected), napi_ok); + + unofficial_napi_message message = nullptr; + ASSERT_EQ(unofficial_napi_message_create(s.env, source, &message), napi_ok); + ASSERT_NE(message, nullptr); + + napi_value result = nullptr; + ASSERT_EQ(unofficial_napi_message_take(s.env, message, &result), napi_ok); + ASSERT_NE(result, nullptr); + napi_value answer = nullptr; + ASSERT_EQ(napi_get_named_property(s.env, result, "answer", &answer), napi_ok); + uint32_t actual = 0; + ASSERT_EQ(napi_get_value_uint32(s.env, answer, &actual), napi_ok); + EXPECT_EQ(actual, 42u); + + unofficial_napi_message dropped = nullptr; + ASSERT_EQ(unofficial_napi_message_create(s.env, source, &dropped), napi_ok); + ASSERT_NE(dropped, nullptr); + unofficial_napi_message_drop(dropped); +} + +TEST_F(Test21General, ProviderFiltersIndexedPropertyNamesInBulk) { + EnvScope s(runtime_.get()); + + napi_value source = nullptr; + ASSERT_EQ(napi_create_object(s.env, &source), napi_ok); + napi_value index_value = nullptr; + ASSERT_EQ(napi_create_uint32(s.env, 1, &index_value), napi_ok); + ASSERT_EQ(napi_set_element(s.env, source, 0, index_value), napi_ok); + ASSERT_EQ(napi_set_element(s.env, source, 999999, index_value), napi_ok); + + napi_value visible = nullptr; + ASSERT_EQ(napi_get_boolean(s.env, true, &visible), napi_ok); + ASSERT_EQ(napi_set_named_property(s.env, source, "visible", visible), napi_ok); + + napi_value description = nullptr; + napi_value symbol = nullptr; + ASSERT_EQ(napi_create_string_utf8(s.env, "own-symbol", NAPI_AUTO_LENGTH, &description), + napi_ok); + ASSERT_EQ(napi_create_symbol(s.env, description, &symbol), napi_ok); + ASSERT_EQ(napi_set_property(s.env, source, symbol, visible), napi_ok); + + napi_value keys = nullptr; + ASSERT_EQ(unofficial_napi_get_own_non_index_properties( + s.env, source, napi_key_all_properties, &keys), + napi_ok); + ASSERT_NE(keys, nullptr); + uint32_t length = 0; + ASSERT_EQ(napi_get_array_length(s.env, keys, &length), napi_ok); + ASSERT_EQ(length, 2u); + + bool saw_visible = false; + bool saw_symbol = false; + for (uint32_t index = 0; index < length; ++index) { + napi_value key = nullptr; + ASSERT_EQ(napi_get_element(s.env, keys, index, &key), napi_ok); + napi_valuetype type = napi_undefined; + ASSERT_EQ(napi_typeof(s.env, key, &type), napi_ok); + if (type == napi_symbol) { + saw_symbol = true; + } else if (type == napi_string) { + char text[16] = {}; + size_t copied = 0; + ASSERT_EQ(napi_get_value_string_utf8(s.env, key, text, sizeof(text), &copied), + napi_ok); + saw_visible = std::string(text, copied) == "visible"; + } else { + ADD_FAILURE() << "unexpected key type " << static_cast(type); + } + } + EXPECT_TRUE(saw_visible); + EXPECT_TRUE(saw_symbol); +} + +TEST_F(Test21General, PropertyEnumerationPreservesThrownProxyException) { + EnvScope s(runtime_.get()); + + napi_value source = nullptr; + ASSERT_EQ(napi_create_string_utf8( + s.env, + "(() => { const sentinel = {}; return { sentinel, proxy: new Proxy({}, { ownKeys() { throw sentinel; } }) }; })()", + NAPI_AUTO_LENGTH, + &source), + napi_ok); + napi_value fixture = nullptr; + ASSERT_EQ(napi_run_script(s.env, source, &fixture), napi_ok); + ASSERT_NE(fixture, nullptr); + napi_value sentinel = nullptr; + napi_value proxy = nullptr; + ASSERT_EQ(napi_get_named_property(s.env, fixture, "sentinel", &sentinel), napi_ok); + ASSERT_EQ(napi_get_named_property(s.env, fixture, "proxy", &proxy), napi_ok); + + napi_value keys = nullptr; + EXPECT_EQ(unofficial_napi_get_own_non_index_properties( + s.env, proxy, napi_key_all_properties, &keys), + napi_pending_exception); + EXPECT_EQ(keys, nullptr); + bool pending = false; + ASSERT_EQ(napi_is_exception_pending(s.env, &pending), napi_ok); + EXPECT_TRUE(pending); + + napi_value caught = nullptr; + ASSERT_EQ(napi_get_and_clear_last_exception(s.env, &caught), napi_ok); + bool same = false; + ASSERT_EQ(napi_strict_equals(s.env, caught, sentinel, &same), napi_ok); + EXPECT_TRUE(same); +} + +TEST_F(Test21General, HeapSpaceStatisticsUseOneBulkSnapshot) { + EnvScope s(runtime_.get()); + + uint32_t required_count = 0; + ASSERT_EQ(unofficial_napi_get_heap_space_statistics( + s.env, nullptr, 0, &required_count), + napi_ok); + ASSERT_GT(required_count, 0u); + + std::array statistics{}; + uint32_t snapshot_count = 0; + ASSERT_EQ(unofficial_napi_get_heap_space_statistics( + s.env, + statistics.data(), + static_cast(statistics.size()), + &snapshot_count), + napi_ok); + ASSERT_EQ(snapshot_count, required_count); + ASSERT_LE(snapshot_count, statistics.size()); + + for (uint32_t index = 0; index < snapshot_count; ++index) { + EXPECT_NE(statistics[index].space_name[0], '\0') << "heap space " << index; + EXPECT_EQ(statistics[index].space_name[ + UNOFFICIAL_NAPI_HEAP_SPACE_NAME_MAX_LENGTH - 1], + '\0') + << "heap space " << index; + } + + uint32_t partial_count = 0; + unofficial_napi_heap_space_statistics first_space{}; + ASSERT_EQ(unofficial_napi_get_heap_space_statistics( + s.env, &first_space, 1, &partial_count), + napi_ok); + EXPECT_EQ(partial_count, required_count); + EXPECT_NE(first_space.space_name[0], '\0'); + + EXPECT_EQ(unofficial_napi_get_heap_space_statistics(s.env, nullptr, 1, &partial_count), + napi_invalid_arg); + EXPECT_EQ(unofficial_napi_get_heap_space_statistics( + s.env, statistics.data(), statistics.size(), nullptr), + napi_invalid_arg); +} + +TEST_F(Test21General, HeapStatisticsDeclareMeasuredFields) { + EnvScope s(runtime_.get()); + unofficial_napi_heap_statistics statistics{}; + unofficial_napi_heap_statistics_init(&statistics); + ASSERT_EQ(unofficial_napi_get_heap_statistics(s.env, &statistics), napi_ok); + EXPECT_NE(statistics.valid_fields & unofficial_napi_heap_stat_total_heap_size, 0u); + EXPECT_NE(statistics.valid_fields & unofficial_napi_heap_stat_used_heap_size, 0u); + EXPECT_NE(statistics.valid_fields & unofficial_napi_heap_stat_heap_size_limit, 0u); +#if defined(NAPI_TEST_ENGINE_V8) + EXPECT_EQ(statistics.valid_fields, unofficial_napi_heap_stat_all); +#elif defined(NAPI_TEST_ENGINE_QUICKJS) + EXPECT_NE(statistics.valid_fields & unofficial_napi_heap_stat_peak_malloced_memory, 0u); + EXPECT_NE(statistics.valid_fields & unofficial_napi_heap_stat_array_buffer_memory, 0u); + EXPECT_EQ(statistics.peak_malloced_memory, statistics.malloced_memory); + EXPECT_EQ(statistics.array_buffer_memory, statistics.external_memory); +#endif +} + +TEST_F(Test21General, HeapStatisticsNormalizationClearsUnsupportedFields) { + unofficial_napi_heap_statistics statistics{}; + unofficial_napi_heap_statistics_init(&statistics); + statistics.valid_fields = unofficial_napi_heap_stat_used_heap_size | + (uint64_t{1} << 63); + statistics.used_heap_size = 42; + statistics.heap_size_limit = 99; + statistics.external_memory = 77; + + unofficial_napi_heap_statistics_normalize(&statistics); + + EXPECT_EQ(statistics.valid_fields, unofficial_napi_heap_stat_used_heap_size); + EXPECT_EQ(statistics.used_heap_size, 42u); + EXPECT_EQ(statistics.heap_size_limit, 0u); + EXPECT_EQ(statistics.external_memory, 0u); +} + +#if defined(NAPI_TEST_ENGINE_QUICKJS) +TEST_F(Test21General, UnsupportedProfileStartLeavesOutputsUntouched) { + EnvScope s(runtime_.get()); + unofficial_napi_profile_start_result result = + unofficial_napi_profile_start_busy; + auto profile = reinterpret_cast(uintptr_t{1}); + + EXPECT_EQ(unofficial_napi_profile_start( + s.env, unofficial_napi_profile_cpu, &result, &profile), + napi_generic_failure); + EXPECT_EQ(result, unofficial_napi_profile_start_busy); + EXPECT_EQ(profile, + reinterpret_cast(uintptr_t{1})); +} +#endif + +#if defined(NAPI_TEST_ENGINE_V8) +TEST_F(Test21General, CpuProfileResultUsesExternalUtf8Bytes) { + EnvScope s(runtime_.get()); + + unofficial_napi_profile_start_result start_result = + unofficial_napi_profile_start_busy; + unofficial_napi_profile profile = nullptr; + ASSERT_EQ(unofficial_napi_profile_start( + s.env, unofficial_napi_profile_cpu, &start_result, &profile), + napi_ok); + ASSERT_EQ(start_result, unofficial_napi_profile_start_ok); + ASSERT_NE(profile, nullptr); + + ASSERT_TRUE(RunScript(s, + "let total = 0; for (let i = 0; i < 10000; ++i) total += i;", + "cpu-profile-work.js")); + + napi_value json = nullptr; + ASSERT_EQ(unofficial_napi_profile_stop(s.env, profile, &json), + napi_ok); + ASSERT_NE(json, nullptr); + + bool is_typedarray = false; + ASSERT_EQ(napi_is_typedarray(s.env, json, &is_typedarray), napi_ok); + ASSERT_TRUE(is_typedarray); + napi_typedarray_type type = napi_int8_array; + size_t length = 0; + void* data = nullptr; + ASSERT_EQ(napi_get_typedarray_info( + s.env, json, &type, &length, &data, nullptr, nullptr), + napi_ok); + EXPECT_EQ(type, napi_uint8_array); + EXPECT_GT(length, 0u); + ASSERT_NE(data, nullptr); + EXPECT_EQ(static_cast(data)[0], '{'); + + napi_value second_json = nullptr; + EXPECT_EQ(unofficial_napi_profile_stop(s.env, profile, &second_json), + napi_invalid_arg); +} + +TEST_F(Test21General, CpuProfileStopCanRetryFromTheEnvironmentThread) { + EnvScope s(runtime_.get()); + + unofficial_napi_profile_start_result start_result = + unofficial_napi_profile_start_busy; + unofficial_napi_profile profile = nullptr; + ASSERT_EQ(unofficial_napi_profile_start( + s.env, unofficial_napi_profile_cpu, &start_result, &profile), + napi_ok); + ASSERT_EQ(start_result, unofficial_napi_profile_start_ok); + ASSERT_NE(profile, nullptr); + + napi_status off_thread_status = napi_ok; + std::thread off_thread([&]() { + napi_value json = nullptr; + off_thread_status = unofficial_napi_profile_stop(s.env, profile, &json); + }); + off_thread.join(); + EXPECT_EQ(off_thread_status, napi_cannot_run_js); + + napi_value json = nullptr; + EXPECT_EQ(unofficial_napi_profile_stop(s.env, profile, &json), napi_ok); + EXPECT_NE(json, nullptr); +} +#endif + #ifdef NAPI_TEST_ENGINE_QUICKJS TEST_F(Test21General, GlobalBufferPrototypeDetection) { EnvScope s(runtime_.get()); diff --git a/tests/runners/test_27_object.cc b/tests/runners/test_27_object.cc index 49fa92a..d4f5c46 100644 --- a/tests/runners/test_27_object.cc +++ b/tests/runners/test_27_object.cc @@ -200,6 +200,19 @@ assert.deepStrictEqual(__to.GetEnumerableWritableNames(pnObj), ['5','normal','wr assert.deepStrictEqual(__to.GetOwnWritableNames(pnObj), ['5','normal','unenumerable','writable',fooSymbol]); assert.deepStrictEqual(__to.GetEnumerableConfigurableNames(pnObj), ['5','normal','configurable',fooSymbol,'inherited']); assert.deepStrictEqual(__to.GetOwnConfigurableNames(pnObj), ['5','normal','unenumerable','configurable',fooSymbol]); + +const keepNumbers = {}; +keepNumbers[0] = 'zero'; +keepNumbers[42] = 'forty-two'; +keepNumbers[4294967294] = 'max-array-index'; +keepNumbers['01'] = 'leading-zero'; +keepNumbers['-0'] = 'negative-zero'; +keepNumbers['1.0'] = 'decimal'; +keepNumbers['4294967295'] = 'past-array-index'; +keepNumbers.normal = true; +assert.deepStrictEqual(__to.GetOwnPropertyNamesKeepNumbers(keepNumbers), + [0, 42, 4294967294, '01', '-0', '1.0', + '4294967295', 'normal']); })(); )JS", "test_27_object")); } diff --git a/tests/runners/test_65_unofficial_contextify.cc b/tests/runners/test_65_unofficial_contextify.cc index 0831b75..96de108 100644 --- a/tests/runners/test_65_unofficial_contextify.cc +++ b/tests/runners/test_65_unofficial_contextify.cc @@ -21,6 +21,15 @@ napi_value Sym(napi_env env, const char* value) { } #if defined(NAPI_TEST_ENGINE_V8) +constexpr char kPreparedStack[] = + "Error: sentinel\n" + " at process.processTicksAndRejections (node:internal/process/task_queues:85:11)\n" + " at triggerUncaughtException (node:internal/process/promises:251:13)"; + +napi_value ReturnPreparedStack(napi_env env, napi_callback_info /*info*/) { + return Str(env, kPreparedStack); +} + napi_value CaptureDynamicImportId(napi_env env, napi_callback_info info) { size_t argc = 5; napi_value argv[5] = {nullptr, nullptr, nullptr, nullptr, nullptr}; @@ -49,7 +58,7 @@ napi_value CaptureDynamicImportId(napi_env env, napi_callback_info info) { } // namespace -TEST_F(Test65UnofficialContextify, MakeRunDisposeRoundTrip) { +TEST_F(Test65UnofficialContextify, MakeRunRoundTrip) { EnvScope s(runtime_.get()); napi_value sandbox = nullptr; @@ -69,7 +78,8 @@ TEST_F(Test65UnofficialContextify, MakeRunDisposeRoundTrip) { ASSERT_NE(result, nullptr); napi_value eval_result = nullptr; - const unofficial_napi_js_source run_source{Str(s.env, "globalThis.answer = 42; answer"), nullptr}; + const unofficial_napi_js_source run_source = + unofficial_napi_js_source_from_text(Str(s.env, "globalThis.answer = 42; answer")); ASSERT_EQ(unofficial_napi_contextify_run_script(s.env, sandbox, &run_source, @@ -94,24 +104,50 @@ TEST_F(Test65UnofficialContextify, MakeRunDisposeRoundTrip) { ASSERT_EQ(napi_get_value_int32(s.env, answer_value, &answer), napi_ok); EXPECT_EQ(answer, 42); - ASSERT_EQ(unofficial_napi_contextify_dispose_context(s.env, sandbox), napi_ok); - const unofficial_napi_js_source disposed_source{Str(s.env, "1"), nullptr}; - EXPECT_EQ(unofficial_napi_contextify_run_script(s.env, - sandbox, - &disposed_source, - Str(s.env, "after_dispose.js"), - 0, - 0, - -1, - true, - false, - false, - Sym(s.env, "hdo"), - &eval_result), - napi_invalid_arg); } -TEST_F(Test65UnofficialContextify, SandboxGlobalThisAndMarkerAreNotEnumerableForDeepFreeze) { +#if defined(NAPI_TEST_ENGINE_V8) +TEST_F(Test65UnofficialContextify, MakeContextPreservesThrownProxyException) { + EnvScope s(runtime_.get()); + + napi_value source = Str( + s.env, + "(() => { const sentinel = {}; return { sentinel, sandbox: new Proxy({}, { ownKeys() { throw sentinel; } }) }; })()"); + ASSERT_NE(source, nullptr); + napi_value fixture = nullptr; + ASSERT_EQ(napi_run_script(s.env, source, &fixture), napi_ok); + ASSERT_NE(fixture, nullptr); + + napi_value sentinel = nullptr; + napi_value sandbox = nullptr; + ASSERT_EQ(napi_get_named_property(s.env, fixture, "sentinel", &sentinel), napi_ok); + ASSERT_EQ(napi_get_named_property(s.env, fixture, "sandbox", &sandbox), napi_ok); + + napi_value result = nullptr; + EXPECT_EQ(unofficial_napi_contextify_make_context(s.env, + sandbox, + Str(s.env, "ctx"), + Str(s.env, "test://origin"), + true, + true, + false, + Sym(s.env, "hdo"), + &result), + napi_pending_exception); + EXPECT_EQ(result, nullptr); + + bool pending = false; + ASSERT_EQ(napi_is_exception_pending(s.env, &pending), napi_ok); + EXPECT_TRUE(pending); + napi_value caught = nullptr; + ASSERT_EQ(napi_get_and_clear_last_exception(s.env, &caught), napi_ok); + bool same = false; + ASSERT_EQ(napi_strict_equals(s.env, caught, sentinel, &same), napi_ok); + EXPECT_TRUE(same); +} +#endif + +TEST_F(Test65UnofficialContextify, SandboxGlobalThisIsNotEnumerableForDeepFreeze) { EnvScope s(runtime_.get()); napi_value sandbox = nullptr; @@ -130,24 +166,54 @@ TEST_F(Test65UnofficialContextify, SandboxGlobalThisAndMarkerAreNotEnumerableFor napi_ok); ASSERT_NE(result, nullptr); +#if defined(NAPI_TEST_ENGINE_QUICKJS) + // QuickJS keeps its context marker on the sandbox rather than copying it + // into the context global. Pin the host-visible property contract directly. + napi_value marker_key = Str(s.env, "__quickjs_contextified"); + bool has_marker = false; + ASSERT_EQ(napi_has_own_property(s.env, sandbox, marker_key, &has_marker), napi_ok); + EXPECT_TRUE(has_marker); + + napi_value enumerable_keys = nullptr; + ASSERT_EQ(napi_get_all_property_names(s.env, + sandbox, + napi_key_own_only, + napi_key_enumerable, + napi_key_numbers_to_strings, + &enumerable_keys), + napi_ok); + uint32_t key_count = 0; + ASSERT_EQ(napi_get_array_length(s.env, enumerable_keys, &key_count), napi_ok); + for (uint32_t index = 0; index < key_count; ++index) { + napi_value key = nullptr; + ASSERT_EQ(napi_get_element(s.env, enumerable_keys, index, &key), napi_ok); + char text[64] = {}; + size_t copied = 0; + ASSERT_EQ(napi_get_value_string_utf8(s.env, key, text, sizeof(text), &copied), napi_ok); + EXPECT_STRNE(text, "__quickjs_contextified"); + } + + napi_value marker_false = nullptr; + napi_value marker_true = nullptr; + ASSERT_EQ(napi_get_boolean(s.env, false, &marker_false), napi_ok); + ASSERT_EQ(napi_get_boolean(s.env, true, &marker_true), napi_ok); + ASSERT_EQ(napi_set_named_property(s.env, sandbox, "__quickjs_contextified", marker_false), napi_ok); + ASSERT_EQ(napi_set_named_property(s.env, sandbox, "__quickjs_contextified", marker_true), napi_ok); +#endif + napi_value eval_result = nullptr; - const unofficial_napi_js_source freeze_source{ - Str(s.env, - R"JS( + std::string freeze_script = R"JS( const globalThisDescriptor = Object.getOwnPropertyDescriptor(globalThis, "globalThis"); -const markerDescriptor = Object.getOwnPropertyDescriptor(globalThis, "__quickjs_contextified"); if (!globalThisDescriptor || globalThisDescriptor.enumerable || !globalThisDescriptor.writable || !globalThisDescriptor.configurable) { throw new Error("globalThis should be writable/configurable but non-enumerable"); } -if (!markerDescriptor || markerDescriptor.enumerable || - !markerDescriptor.writable || !markerDescriptor.configurable) { - throw new Error("contextify marker should be writable/configurable but non-enumerable"); -} const keys = Object.keys(globalThis); -if (keys.includes("globalThis") || keys.includes("__quickjs_contextified")) { +if (keys.includes("globalThis")) { throw new Error("contextify internals should not be enumerable"); } +)JS"; + freeze_script += R"JS( globalThis.__RSC_MANIFEST = {}; globalThis.__RSC_MANIFEST["/x"] = { ok: true }; function deepFreeze(obj) { @@ -161,8 +227,9 @@ function deepFreeze(obj) { } deepFreeze(globalThis); globalThis.__RSC_MANIFEST["/x"].ok; -)JS"), - nullptr}; +)JS"; + const unofficial_napi_js_source freeze_source = unofficial_napi_js_source_from_text( + Str(s.env, freeze_script.c_str())); ASSERT_EQ(unofficial_napi_contextify_run_script(s.env, sandbox, &freeze_source, @@ -198,7 +265,8 @@ TEST_F(Test65UnofficialContextify, CompileFunctionAndCachedData) { ASSERT_EQ(napi_get_undefined(s.env, &undef), napi_ok); napi_value out = nullptr; - const unofficial_napi_js_source fn_source{Str(s.env, "return a + b;"), nullptr}; + const unofficial_napi_js_source fn_source = + unofficial_napi_js_source_from_text(Str(s.env, "return a + b;")); ASSERT_EQ(unofficial_napi_contextify_compile_function(s.env, &fn_source, Str(s.env, "fn.js"), @@ -230,18 +298,17 @@ TEST_F(Test65UnofficialContextify, CompileFunctionAndCachedData) { // Cached data now flows through the bytecode handle APIs: compile eagerly, // serialize the engine bytes, and restore a live artifact from them. - void* bytecode = nullptr; - ASSERT_EQ(unofficial_napi_bytecode_compile(s.env, - Str(s.env, "1 + 1"), - Str(s.env, "script.js"), - unofficial_napi_bytecode_shape_script, - undef, - Sym(s.env, "hdo"), - 0, - 0, - &bytecode, - nullptr), - napi_ok); + unofficial_napi_bytecode_open_options open_options{}; + open_options.size = sizeof(open_options); + open_options.version = UNOFFICIAL_NAPI_BYTECODE_OPEN_OPTIONS_VERSION; + open_options.source_text = Str(s.env, "1 + 1"); + open_options.filename = Str(s.env, "script.js"); + open_options.shape = unofficial_napi_bytecode_shape_script; + open_options.params_or_undefined = undef; + open_options.host_defined_option_id = Sym(s.env, "hdo"); + unofficial_napi_bytecode_open_result open_result{}; + ASSERT_EQ(unofficial_napi_bytecode_open(s.env, &open_options, &open_result), napi_ok); + unofficial_napi_bytecode bytecode = open_result.bytecode; ASSERT_NE(bytecode, nullptr); napi_value cached_data = nullptr; @@ -264,22 +331,124 @@ TEST_F(Test65UnofficialContextify, CompileFunctionAndCachedData) { ASSERT_GT(byte_length, 0u); bytes = static_cast(data); - void* restored = nullptr; - bool rejected = false; - ASSERT_EQ(unofficial_napi_bytecode_deserialize(s.env, - bytes, - byte_length, - Str(s.env, "1 + 1"), - Str(s.env, "script.js"), - unofficial_napi_bytecode_shape_script, - undef, - Sym(s.env, "hdo"), - &restored, - &rejected), - napi_ok); - EXPECT_FALSE(rejected); + open_options.cache_bytes = bytes; + open_options.cache_byte_length = byte_length; + open_options.has_cache = 1; + open_result = {}; + ASSERT_EQ(unofficial_napi_bytecode_open(s.env, &open_options, &open_result), napi_ok); + EXPECT_EQ(open_result.cache_rejected, 0); + unofficial_napi_bytecode restored = open_result.bytecode; ASSERT_NE(restored, nullptr); ASSERT_EQ(unofficial_napi_bytecode_release(s.env, restored), napi_ok); + + open_options.source_text = Str(s.env, "1000 + 2000 + 3000"); + open_options.cache_policy = unofficial_napi_bytecode_cache_validate_only; + open_result = {}; + ASSERT_EQ(unofficial_napi_bytecode_open(s.env, &open_options, &open_result), napi_ok); + EXPECT_EQ(open_result.cache_rejected, 1); + EXPECT_EQ(open_result.bytecode, nullptr); + + // A present-but-empty cache is a cache miss, not a malformed transaction. + // The provider reports the rejection and atomically prepares the fallback + // artifact from source so callers never need a deserialize/compile branch. + open_options.cache_bytes = nullptr; + open_options.cache_byte_length = 0; + open_options.has_cache = 1; + open_options.cache_policy = unofficial_napi_bytecode_cache_compile_on_reject; + open_result = {}; + ASSERT_EQ(unofficial_napi_bytecode_open(s.env, &open_options, &open_result), napi_ok); + EXPECT_EQ(open_result.cache_rejected, 1); + ASSERT_NE(open_result.bytecode, nullptr); + ASSERT_EQ(unofficial_napi_bytecode_release(s.env, open_result.bytecode), napi_ok); +} + +TEST_F(Test65UnofficialContextify, ModuleStateIsOneAtomicSnapshot) { + EnvScope s(runtime_.get()); + + napi_value wrapper = nullptr; + napi_value undefined = nullptr; + ASSERT_EQ(napi_create_object(s.env, &wrapper), napi_ok); + ASSERT_EQ(napi_get_undefined(s.env, &undefined), napi_ok); + const unofficial_napi_js_source source = + unofficial_napi_js_source_from_text(Str(s.env, "export const value = 42;")); + unofficial_napi_module_create_options create_options{}; + create_options.size = sizeof(create_options); + create_options.version = UNOFFICIAL_NAPI_MODULE_CREATE_OPTIONS_VERSION; + create_options.kind = unofficial_napi_module_source_text; + create_options.wrapper = wrapper; + create_options.url = Str(s.env, "state.mjs"); + create_options.context_or_undefined = undefined; + create_options.payload.source_text.source = &source; + create_options.payload.source_text.host_defined_option_id = undefined; + unofficial_napi_module_create_result create_result{}; + + auto invalid_options = create_options; + invalid_options.size = sizeof(invalid_options) - 1; + EXPECT_EQ(unofficial_napi_module_wrap_create(s.env, &invalid_options, &create_result), + napi_invalid_arg); + invalid_options = create_options; + invalid_options.version++; + EXPECT_EQ(unofficial_napi_module_wrap_create(s.env, &invalid_options, &create_result), + napi_invalid_arg); + invalid_options = create_options; + invalid_options.kind = static_cast(99); + EXPECT_EQ(unofficial_napi_module_wrap_create(s.env, &invalid_options, &create_result), + napi_invalid_arg); + + ASSERT_EQ(unofficial_napi_module_wrap_create(s.env, &create_options, &create_result), + napi_ok); + unofficial_napi_module module = create_result.module; + ASSERT_NE(module, nullptr); + ASSERT_NE(create_result.module_requests, nullptr); + uint32_t request_count = 1; + ASSERT_EQ(napi_get_array_length(s.env, create_result.module_requests, &request_count), + napi_ok); + EXPECT_EQ(request_count, 0u); + EXPECT_FALSE(create_result.has_top_level_await); + + EXPECT_EQ(unofficial_napi_module_wrap_get_state( + s.env, module, nullptr, nullptr, nullptr), + napi_invalid_arg); + int32_t status = -1; + napi_value error = nullptr; + bool has_async_graph = true; + ASSERT_EQ(unofficial_napi_module_wrap_get_state( + s.env, module, &status, &error, &has_async_graph), + napi_ok); + EXPECT_EQ(status, 0); + EXPECT_FALSE(has_async_graph); + ASSERT_NE(error, nullptr); + napi_valuetype error_type = napi_object; + ASSERT_EQ(napi_typeof(s.env, error, &error_type), napi_ok); + EXPECT_EQ(error_type, napi_undefined); + + ASSERT_EQ(unofficial_napi_module_wrap_link(s.env, module, 0, nullptr), napi_ok); + ASSERT_EQ(unofficial_napi_module_wrap_instantiate(s.env, module), napi_ok); + status = -1; + has_async_graph = true; + ASSERT_EQ(unofficial_napi_module_wrap_get_state( + s.env, module, &status, nullptr, &has_async_graph), + napi_ok); + EXPECT_EQ(status, 2); + EXPECT_FALSE(has_async_graph); + + EXPECT_EQ(unofficial_napi_module_wrap_destroy(s.env, module), napi_ok); +} + +TEST_F(Test65UnofficialContextify, ModuleHooksUseOneVersionedConfiguration) { + EnvScope s(runtime_.get()); + + unofficial_napi_module_hooks hooks{}; + hooks.size = sizeof(hooks) - 1; + hooks.version = UNOFFICIAL_NAPI_MODULE_HOOKS_VERSION; + EXPECT_EQ(unofficial_napi_module_wrap_set_hooks(s.env, &hooks), napi_invalid_arg); + + hooks.size = sizeof(hooks); + hooks.version = 0; + EXPECT_EQ(unofficial_napi_module_wrap_set_hooks(s.env, &hooks), napi_invalid_arg); + + hooks.version = UNOFFICIAL_NAPI_MODULE_HOOKS_VERSION; + EXPECT_EQ(unofficial_napi_module_wrap_set_hooks(s.env, &hooks), napi_ok); } #if defined(NAPI_TEST_ENGINE_V8) @@ -299,9 +468,13 @@ TEST_F(Test65UnofficialContextify, nullptr, &callback), napi_ok); - ASSERT_EQ( - unofficial_napi_module_wrap_set_import_module_dynamically_callback(s.env, callback), - napi_ok); + const unofficial_napi_module_hooks hooks = { + sizeof(unofficial_napi_module_hooks), + UNOFFICIAL_NAPI_MODULE_HOOKS_VERSION, + callback, + nullptr, + }; + ASSERT_EQ(unofficial_napi_module_wrap_set_hooks(s.env, &hooks), napi_ok); napi_value undefined = nullptr; ASSERT_EQ(napi_get_undefined(s.env, &undefined), napi_ok); @@ -309,23 +482,22 @@ TEST_F(Test65UnofficialContextify, ASSERT_NE(explicit_id, nullptr); auto compile_invoke_and_capture = [&](napi_value host_id) -> napi_value { - void* bytecode = nullptr; - EXPECT_EQ(unofficial_napi_bytecode_compile( - s.env, - Str(s.env, "return import('node:test');"), - Str(s.env, "host-id.js"), - unofficial_napi_bytecode_shape_cjs_function, - undefined, - host_id, - 0, - 0, - &bytecode, - nullptr), - napi_ok); + unofficial_napi_bytecode_open_options options{}; + options.size = sizeof(options); + options.version = UNOFFICIAL_NAPI_BYTECODE_OPEN_OPTIONS_VERSION; + options.source_text = Str(s.env, "return import('node:test');"); + options.filename = Str(s.env, "host-id.js"); + options.shape = unofficial_napi_bytecode_shape_cjs_function; + options.params_or_undefined = undefined; + options.host_defined_option_id = host_id; + unofficial_napi_bytecode_open_result result{}; + EXPECT_EQ(unofficial_napi_bytecode_open(s.env, &options, &result), napi_ok); + unofficial_napi_bytecode bytecode = result.bytecode; EXPECT_NE(bytecode, nullptr); if (bytecode == nullptr) return nullptr; - const unofficial_napi_js_source source{nullptr, bytecode}; + const unofficial_napi_js_source source = + unofficial_napi_js_source_from_bytecode(bytecode); napi_value compiled = nullptr; EXPECT_EQ(unofficial_napi_contextify_compile_function(s.env, &source, @@ -414,7 +586,8 @@ globalThis.Function = function Function() { ASSERT_EQ(napi_set_element(s.env, params, 0, Str(s.env, "value")), napi_ok); napi_value out = nullptr; - const unofficial_napi_js_source fn_source{Str(s.env, "return value + 1;"), nullptr}; + const unofficial_napi_js_source fn_source = + unofficial_napi_js_source_from_text(Str(s.env, "return value + 1;")); ASSERT_EQ(unofficial_napi_contextify_compile_function(s.env, &fn_source, Str(s.env, "no-global-function.js"), @@ -451,7 +624,8 @@ TEST_F(Test65UnofficialContextify, CompileFunctionAcceptsHashbangBody) { ASSERT_EQ(napi_get_undefined(s.env, &undef), napi_ok); napi_value out = nullptr; - const unofficial_napi_js_source hashbang_source{Str(s.env, "#!/usr/bin/env node\nreturn 42;"), nullptr}; + const unofficial_napi_js_source hashbang_source = unofficial_napi_js_source_from_text( + Str(s.env, "#!/usr/bin/env node\nreturn 42;")); ASSERT_EQ(unofficial_napi_contextify_compile_function(s.env, &hashbang_source, Str(s.env, ""), @@ -486,8 +660,8 @@ TEST_F(Test65UnofficialContextify, CompileFunctionRejectsBomBeforeHashbang) { ASSERT_EQ(napi_get_undefined(s.env, &undef), napi_ok); napi_value out = nullptr; - const unofficial_napi_js_source bom_source{ - Str(s.env, "\xEF\xBB\xBF#!/usr/bin/env node\nreturn 42;"), nullptr}; + const unofficial_napi_js_source bom_source = unofficial_napi_js_source_from_text( + Str(s.env, "\xEF\xBB\xBF#!/usr/bin/env node\nreturn 42;")); EXPECT_EQ(unofficial_napi_contextify_compile_function(s.env, &bom_source, Str(s.env, "bom_hashbang.js"), @@ -559,7 +733,8 @@ TEST_F(Test65UnofficialContextify, CjsCompileAndSyntaxDetection) { ASSERT_EQ(napi_get_undefined(s.env, &undef), napi_ok); napi_value out = nullptr; - const unofficial_napi_js_source cjs_source{Str(s.env, "module.exports = 1;"), nullptr}; + const unofficial_napi_js_source cjs_source = + unofficial_napi_js_source_from_text(Str(s.env, "module.exports = 1;")); ASSERT_EQ(unofficial_napi_contextify_compile_function(s.env, &cjs_source, Str(s.env, "cjs.js"), @@ -640,3 +815,40 @@ TEST_F(Test65UnofficialContextify, PrivateSymbolAcceptsAutoLength) { ASSERT_EQ(napi_typeof(s.env, symbol, &type), napi_ok); EXPECT_TRUE(type == napi_symbol || type == napi_object); } + +#if defined(NAPI_TEST_ENGINE_V8) +TEST_F(Test65UnofficialContextify, PrepareStackTraceResultIsNotRewritten) { + EnvScope s(runtime_.get()); + + napi_value callback = nullptr; + ASSERT_EQ(napi_create_function(s.env, + "prepareStackTrace", + NAPI_AUTO_LENGTH, + ReturnPreparedStack, + nullptr, + &callback), + napi_ok); + ASSERT_NE(callback, nullptr); + ASSERT_EQ(unofficial_napi_set_prepare_stack_trace_callback(s.env, callback), + napi_ok); + + napi_value source = Str(s.env, "new Error('sentinel').stack"); + napi_value stack = nullptr; + ASSERT_EQ(napi_run_script(s.env, source, &stack), napi_ok); + ASSERT_NE(stack, nullptr); + + size_t length = 0; + ASSERT_EQ(napi_get_value_string_utf8(s.env, stack, nullptr, 0, &length), + napi_ok); + std::string actual(length + 1, '\0'); + size_t written = 0; + ASSERT_EQ(napi_get_value_string_utf8( + s.env, stack, actual.data(), actual.size(), &written), + napi_ok); + actual.resize(written); + EXPECT_EQ(actual, kPreparedStack); + + ASSERT_EQ(unofficial_napi_set_prepare_stack_trace_callback(s.env, nullptr), + napi_ok); +} +#endif diff --git a/tests/runners/test_env.h b/tests/runners/test_env.h index ae5658e..0dac85e 100644 --- a/tests/runners/test_env.h +++ b/tests/runners/test_env.h @@ -15,9 +15,12 @@ class NapiTestRuntime { public: NapiTestRuntime() { static constexpr char kDefaultFlags[] = "--expose-gc --js-float16array"; - EXPECT_EQ(unofficial_napi_set_flags_from_string( - kDefaultFlags, sizeof(kDefaultFlags) - 1), - napi_ok); + unofficial_napi_runtime_options options{}; + options.size = sizeof(options); + options.version = UNOFFICIAL_NAPI_RUNTIME_OPTIONS_VERSION; + options.engine_flags = kDefaultFlags; + options.engine_flags_length = sizeof(kDefaultFlags) - 1; + EXPECT_EQ(unofficial_napi_configure_runtime(&options), napi_ok); } ~NapiTestRuntime() = default; @@ -34,10 +37,20 @@ class FixtureTestBase : public ::testing::Test { inline std::unique_ptr FixtureTestBase::runtime_; +inline void InitializeTestEnvCreateOptions( + unofficial_napi_env_create_options* options) { + ASSERT_NE(options, nullptr); + *options = {}; + options->size = sizeof(*options); + options->version = UNOFFICIAL_NAPI_ENV_CREATE_OPTIONS_VERSION; +} + struct EnvScope { explicit EnvScope(NapiTestRuntime* runtime) { (void)runtime; - EXPECT_EQ(unofficial_napi_create_env(NAPI_TEST_MODULE_API_VERSION, &env, &scope), + unofficial_napi_env_create_options options{}; + InitializeTestEnvCreateOptions(&options); + EXPECT_EQ(unofficial_napi_create_env(NAPI_TEST_MODULE_API_VERSION, &options, &env, &scope), napi_ok); EXPECT_NE(env, nullptr); EXPECT_NE(scope, nullptr); @@ -45,7 +58,7 @@ struct EnvScope { ~EnvScope() { if (scope != nullptr) { - EXPECT_EQ(unofficial_napi_release_env(scope), napi_ok); + EXPECT_EQ(unofficial_napi_release_env(scope, nullptr), napi_ok); scope = nullptr; env = nullptr; } @@ -55,7 +68,7 @@ struct EnvScope { EnvScope& operator=(const EnvScope&) = delete; napi_env env = nullptr; - void* scope = nullptr; + unofficial_napi_env_owner scope = nullptr; }; #endif // NAPI_SHARED_TEST_ENV_H_ diff --git a/tests/runners/upstream_js_test.h b/tests/runners/upstream_js_test.h index 75748b7..ee1f3b1 100644 --- a/tests/runners/upstream_js_test.h +++ b/tests/runners/upstream_js_test.h @@ -90,7 +90,7 @@ inline bool RunScript(EnvScope& s, const std::string& source_text, const char* l inline napi_value ForceGcCallback(napi_env env, napi_callback_info info) { (void)info; - (void)unofficial_napi_request_gc_for_testing(env); + (void)unofficial_napi_collect_garbage(env); (void)unofficial_napi_event_loop_checkpoint( env, unofficial_napi_event_loop_checkpoint_microtasks, true, nullptr); diff --git a/v8/src/internal/napi_v8_env.h b/v8/src/internal/napi_v8_env.h index ef32b43..0c7a5f1 100644 --- a/v8/src/internal/napi_v8_env.h +++ b/v8/src/internal/napi_v8_env.h @@ -95,7 +95,6 @@ struct napi_env__ { void* instance_data = nullptr; napi_finalize instance_data_finalize_cb = nullptr; void* instance_data_finalize_hint = nullptr; - void* edge_environment = nullptr; std::vector env_cleanup_hooks; uint64_t env_cleanup_hook_counter = 0; std::vector buffer_records; @@ -105,15 +104,12 @@ struct napi_env__ { std::unordered_set pending_finalizers; std::unordered_set pending_buffer_finalizers; bool finalization_scheduled = false; - unofficial_napi_env_cleanup_callback env_cleanup_callback = nullptr; - void* env_cleanup_callback_data = nullptr; - unofficial_napi_env_destroy_callback env_destroy_callback = nullptr; - void* env_destroy_callback_data = nullptr; unofficial_napi_context_token_callback context_token_assign_callback = nullptr; unofficial_napi_context_token_callback context_token_unassign_callback = nullptr; void* context_token_callback_data = nullptr; unofficial_napi_enqueue_foreground_task_callback enqueue_foreground_task_callback = nullptr; void* enqueue_foreground_task_target = nullptr; + bool embedder_hooks_attached = false; #if defined(NAPI_ENABLE_LIFETIME_TRACKER) && defined(NAPI_ENABLE_LIFETIME_PERIODIC_STATS) napi::periodic_gate__ lifetime_stats_gate_{2000}; @@ -130,6 +126,11 @@ napi_status napi_v8_set_last_error(napi_env env, napi_status napi_v8_clear_last_error(napi_env env); +void napi_v8_set_last_exception( + napi_env env, + v8::Local exception, + v8::Local message = v8::Local()); + napi_value napi_v8_wrap_value(napi_env env, v8::Local value); v8::Local napi_v8_unwrap_value(napi_value value); void napi_v8_finalize_buffer_records(napi_env env); diff --git a/v8/src/internal/unofficial_napi_bridge.h b/v8/src/internal/unofficial_napi_bridge.h index 31710d6..b1baf85 100644 --- a/v8/src/internal/unofficial_napi_bridge.h +++ b/v8/src/internal/unofficial_napi_bridge.h @@ -14,7 +14,5 @@ void NapiV8ApplyPromiseHooksToContextifyContexts(napi_env env); void NapiV8TrackProviderPromise(v8::Isolate* isolate, v8::Local promise); bool NapiV8HasPendingProviderWork(napi_env env); -void* NapiV8GetCurrentEdgeEnvironment(v8::Isolate* isolate); -void* NapiV8GetCurrentEdgeEnvironment(v8::Local context); #endif // NAPI_V8_UNOFFICIAL_NAPI_BRIDGE_H_ diff --git a/v8/src/js_native_api_v8.cc b/v8/src/js_native_api_v8.cc index 7150840..f25e027 100644 --- a/v8/src/js_native_api_v8.cc +++ b/v8/src/js_native_api_v8.cc @@ -501,6 +501,12 @@ void SetterTrampoline(v8::Local property, } // namespace +void napi_v8_set_last_exception(napi_env env, + v8::Local exception, + v8::Local message) { + SetLastException(env, exception, message); +} + napi_handle_scope__::napi_handle_scope__(napi_env env) : env(env), wrapper(env->isolate) {} @@ -584,10 +590,6 @@ napi_env__::~napi_env__() { if (instance_data_finalize_cb != nullptr) { instance_data_finalize_cb(this, instance_data, instance_data_finalize_hint); } - if (env_destroy_callback != nullptr) { - env_destroy_callback(this, env_destroy_callback_data); - } - edge_environment = nullptr; NAPI_V8_LIFETIME_DUMP(this, "napi_env__ teardown end"); } diff --git a/v8/src/unofficial_napi.cc b/v8/src/unofficial_napi.cc index aa157fd..7b47638 100644 --- a/v8/src/unofficial_napi.cc +++ b/v8/src/unofficial_napi.cc @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -30,17 +31,19 @@ #include "unofficial_napi_error_utils.h" #include "edge_v8_platform.h" +struct unofficial_napi_profile__ { + unofficial_napi_profile_kind kind = unofficial_napi_profile_cpu; + uint32_t provider_id = 0; +}; + namespace { struct SharedRuntime { std::unique_ptr platform; + std::string engine_flags; uint32_t refcount = 0; }; -struct EmbedderHooksState { - unofficial_napi_embedder_hooks hooks{}; -}; - class TrackingArrayBufferAllocator; struct UnofficialEnvScope { @@ -88,7 +91,6 @@ std::mutex g_runtime_mu; // for the teardown thread (Isolate::Delete touches the platform's page // allocator and tracing controller). The OS reclaims everything at _exit. SharedRuntime& g_runtime = *new SharedRuntime(); -EmbedderHooksState g_embedder_hooks; std::unordered_map& g_env_by_isolate = *new std::unordered_map(); std::unordered_map& g_hash_seeds = @@ -127,33 +129,12 @@ struct InterruptRequest { struct ProfilerState { v8::CpuProfiler* cpu_profiler = nullptr; - std::vector active_cpu_profiles; - bool heap_profile_started = false; + std::vector active_profiles; }; std::unordered_map& g_profiler_states = *new std::unordered_map(); -unofficial_napi_embedder_hooks CopyEmbedderHooks() { - std::lock_guard lock(g_runtime_mu); - return g_embedder_hooks.hooks; -} - -bool QueryEmbedderMemoryInfo(unofficial_napi_embedder_memory_info* out) { - if (out == nullptr) return false; - *out = unofficial_napi_embedder_memory_info{}; - const unofficial_napi_embedder_hooks hooks = CopyEmbedderHooks(); - if (hooks.memory_info_callback == nullptr) return false; - return hooks.memory_info_callback(hooks.memory_info_target, out) == napi_ok; -} - -bool PumpEmbedderShutdown(void* handle) { - if (handle == nullptr) return false; - const unofficial_napi_embedder_hooks hooks = CopyEmbedderHooks(); - if (hooks.shutdown_pump_callback == nullptr) return false; - return hooks.shutdown_pump_callback(hooks.shutdown_pump_target, handle) == napi_ok; -} - class StringOutputStream final : public v8::OutputStream { public: WriteResult WriteAsciiChunk(char* data, int size) override { @@ -163,25 +144,65 @@ class StringOutputStream final : public v8::OutputStream { void EndOfStream() override {} - const std::string& output() const { return output_; } + std::string TakeOutput() { return std::move(output_); } private: std::string output_; }; +napi_status CreateExternalUtf8Bytes(napi_env env, + std::string&& bytes, + napi_value* result) { + if (env == nullptr || result == nullptr) return napi_invalid_arg; + auto* owner = new (std::nothrow) std::string(std::move(bytes)); + if (owner == nullptr) return napi_generic_failure; + if (owner->empty()) { + delete owner; + napi_value arraybuffer = nullptr; + napi_status status = napi_create_arraybuffer(env, 0, nullptr, &arraybuffer); + if (status != napi_ok) return status; + return napi_create_typedarray( + env, napi_uint8_array, 0, arraybuffer, 0, result); + } + napi_value arraybuffer = nullptr; + napi_status status = napi_create_external_arraybuffer( + env, + owner->data(), + owner->size(), + [](napi_env, void*, void* hint) { + delete static_cast(hint); + }, + owner, + &arraybuffer); + if (status != napi_ok) { + delete owner; + return status; + } + return napi_create_typedarray( + env, napi_uint8_array, owner->size(), arraybuffer, 0, result); +} + void DisposeProfilerState(napi_env env, ProfilerState* state) { if (env == nullptr || env->isolate == nullptr || state == nullptr) return; - if (state->heap_profile_started) { - env->isolate->GetHeapProfiler()->StopSamplingHeapProfiler(); - state->heap_profile_started = false; - } - if (state->cpu_profiler != nullptr) { - for (uint32_t profile_id : state->active_cpu_profiles) { - if (v8::CpuProfile* profile = state->cpu_profiler->Stop(profile_id)) { + bool heap_profile_started = false; + for (unofficial_napi_profile profile_session : state->active_profiles) { + if (profile_session == nullptr) continue; + if (profile_session->kind == unofficial_napi_profile_cpu && + state->cpu_profiler != nullptr) { + if (v8::CpuProfile* profile = + state->cpu_profiler->Stop(profile_session->provider_id)) { profile->Delete(); } + } else if (profile_session->kind == unofficial_napi_profile_heap) { + heap_profile_started = true; } - state->active_cpu_profiles.clear(); + delete profile_session; + } + state->active_profiles.clear(); + if (heap_profile_started) { + env->isolate->GetHeapProfiler()->StopSamplingHeapProfiler(); + } + if (state->cpu_profiler != nullptr) { state->cpu_profiler->Dispose(); state->cpu_profiler = nullptr; } @@ -191,21 +212,6 @@ ProfilerState& EnsureProfilerState(napi_env env) { return g_profiler_states[env]; } -bool CopyStringToMallocBuffer(const std::string& input, char** data_out, size_t* len_out) { - if (data_out == nullptr || len_out == nullptr) return false; - *data_out = nullptr; - *len_out = 0; - char* buffer = static_cast(std::malloc(input.size() + 1)); - if (buffer == nullptr) return false; - if (!input.empty()) { - std::memcpy(buffer, input.data(), input.size()); - } - buffer[input.size()] = '\0'; - *data_out = buffer; - *len_out = input.size(); - return true; -} - uint64_t GenerateHashSeed() { try { std::random_device random_device; @@ -269,28 +275,6 @@ void AppendJsonNumber(std::string* out, T value) { out->append(stream.str()); } -void StripStackFrameLine(std::string* stack, const char* prefix) { - if (stack == nullptr || prefix == nullptr || stack->empty()) return; - size_t pos = stack->find(prefix); - while (pos != std::string::npos) { - const size_t end = stack->find('\n', pos + 1); - stack->erase(pos, end == std::string::npos ? std::string::npos : end - pos); - pos = stack->find(prefix); - } -} - -void StripInternalAsyncStackFrames(std::string* stack) { - static constexpr const char* kPrefixes[] = { - "\n at process.processTicksAndRejections (node:internal/process/task_queues:", - "\n at process.processTicksAndRejections (node:internal/process/task_queues)", - "\n at triggerUncaughtException (node:internal/process/promises:", - "\n at triggerUncaughtException (node:internal/modules/run_main:", - }; - for (const char* prefix : kPrefixes) { - StripStackFrameLine(stack, prefix); - } -} - void BuildHeapProfileNode(v8::Isolate* isolate, const v8::AllocationProfile::Node* profile_node, std::string* out) { @@ -432,25 +416,7 @@ v8::MaybeLocal NapiPrepareStackTraceCallback(v8::Local c if (try_catch.HasCaught() && !try_catch.HasTerminated()) { try_catch.ReThrow(); } - v8::Local value; - if (!result.ToLocal(&value) || !value->IsString()) { - return result; - } - - v8::String::Utf8Value utf8(context->GetIsolate(), value); - std::string formatted = - *utf8 != nullptr ? std::string(*utf8, utf8.length()) : std::string(); - StripInternalAsyncStackFrames(&formatted); - v8::Local filtered; - if (!v8::String::NewFromUtf8( - context->GetIsolate(), - formatted.c_str(), - v8::NewStringType::kNormal, - static_cast(formatted.size())) - .ToLocal(&filtered)) { - return value; - } - return filtered; + return result; } bool IsEnvThreadEntered(napi_env env) { @@ -503,9 +469,10 @@ class TrackingArrayBufferAllocator final : public v8::ArrayBuffer::Allocator { : backing_(v8::ArrayBuffer::Allocator::NewDefaultAllocator()) {} ~TrackingArrayBufferAllocator() override { - void* gctx = guest_heap_ctx_.load(std::memory_order_acquire); - if (gctx != nullptr) { - napi_host_guest_heap_release(gctx); + unofficial_napi_guest_heap guest_heap = + guest_heap_.load(std::memory_order_acquire); + if (guest_heap != nullptr) { + napi_host_guest_heap_release(guest_heap); } delete backing_; } @@ -517,9 +484,10 @@ class TrackingArrayBufferAllocator final : public v8::ArrayBuffer::Allocator { } void Free(void* data, size_t length) override { - void* gctx = guest_heap_ctx_.load(std::memory_order_acquire); - if (gctx != nullptr && data != nullptr && - napi_host_guest_heap_free(gctx, data, length) != 0) { + unofficial_napi_guest_heap guest_heap = + guest_heap_.load(std::memory_order_acquire); + if (guest_heap != nullptr && data != nullptr && + napi_host_guest_heap_free(guest_heap, data, length) != 0) { // Guest-heap provenance: no external-budget uncharge (those bytes were // never charged; they are wasm linear memory). total_mem_usage_.fetch_sub(length, std::memory_order_relaxed); @@ -537,7 +505,7 @@ class TrackingArrayBufferAllocator final : public v8::ArrayBuffer::Allocator { size_t backing_max = backing_ != nullptr ? backing_->MaxAllocationSize() : v8::ArrayBuffer::Allocator::MaxAllocationSize(); - if (guest_heap_ctx_.load(std::memory_order_acquire) != nullptr) { + if (guest_heap_.load(std::memory_order_acquire) != nullptr) { // Guest-heap backing stores live in a 32-bit address space. constexpr size_t kGuestHeapMax = 0x7fff0000u; return backing_max < kGuestHeapMax ? backing_max : kGuestHeapMax; @@ -556,19 +524,21 @@ class TrackingArrayBufferAllocator final : public v8::ArrayBuffer::Allocator { // Install the guest-heap context: from now on every backing store is // allocated inside the guest's linear memory. Ownership transfers to the // allocator, which releases it in its destructor. - void set_guest_heap_ctx(void* ctx) { - guest_heap_ctx_.store(ctx, std::memory_order_release); + void set_guest_heap(unofficial_napi_guest_heap guest_heap) { + guest_heap_.store(guest_heap, std::memory_order_release); } private: void* AllocateImpl(size_t length, bool zeroed) { - void* gctx = guest_heap_ctx_.load(std::memory_order_acquire); - if (gctx != nullptr) { + unofficial_napi_guest_heap guest_heap = + guest_heap_.load(std::memory_order_acquire); + if (guest_heap != nullptr) { // Guest-heap path. The claimed pages are already charged as wasm linear // memory, so the external budget is deliberately not consulted. On // failure return null (V8 throws RangeError); never fall back to host // memory, which the guest could not address. - void* data = napi_host_guest_heap_alloc(gctx, length, zeroed ? 1 : 0); + void* data = napi_host_guest_heap_alloc( + guest_heap, length, zeroed ? 1 : 0); if (data != nullptr) { total_mem_usage_.fetch_add(length, std::memory_order_relaxed); } @@ -589,20 +559,19 @@ class TrackingArrayBufferAllocator final : public v8::ArrayBuffer::Allocator { v8::ArrayBuffer::Allocator* backing_ = nullptr; std::atomic total_mem_usage_ {0}; - std::atomic guest_heap_ctx_ {nullptr}; + std::atomic guest_heap_ {nullptr}; }; -void ApplyNodeIsolateCreateParams(v8::Isolate::CreateParams* params) { - if (params == nullptr) return; +void ApplyNodeIsolateCreateParams( + v8::Isolate::CreateParams* params, + const unofficial_napi_env_create_options* options) { + if (params == nullptr || options == nullptr) return; - unofficial_napi_embedder_memory_info memory_info{}; - if (!QueryEmbedderMemoryInfo(&memory_info)) return; - - const uint64_t constrained_memory = memory_info.constrained_memory; + const uint64_t constrained_memory = options->constrained_memory; const uint64_t total_memory = constrained_memory > 0 - ? std::min(memory_info.total_memory, constrained_memory) - : memory_info.total_memory; + ? std::min(options->total_memory, constrained_memory) + : options->total_memory; if (total_memory > 0 && params->constraints.max_old_generation_size_in_bytes() == 0) { params->constraints.ConfigureDefaults(total_memory, 0); @@ -645,18 +614,10 @@ void OnPlatformShutdownFinished(void* data) { } void WaitForPlatformShutdown(PlatformShutdownWaiter* waiter, - void* shutdown_pump_handle) { + void* /*shutdown_pump_handle*/) { if (waiter == nullptr) return; std::unique_lock lock(waiter->mutex); while (!waiter->finished) { - if (shutdown_pump_handle != nullptr) { - lock.unlock(); - if (!PumpEmbedderShutdown(shutdown_pump_handle)) { - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - } - lock.lock(); - continue; - } waiter->cv.wait_for(lock, std::chrono::milliseconds(1)); } } @@ -728,19 +689,42 @@ void OOMErrorCallback(const char* location, const v8::OOMDetails& details) { } } +napi_status ConfigureRuntime(const char* engine_flags, + size_t engine_flags_length) { + if ((engine_flags_length > 0 && engine_flags == nullptr) || + engine_flags_length > static_cast(std::numeric_limits::max())) { + return napi_invalid_arg; + } + std::lock_guard lock(g_runtime_mu); + if (g_runtime.platform != nullptr) { + return g_runtime.engine_flags.size() == engine_flags_length && + (engine_flags_length == 0 || + std::memcmp(g_runtime.engine_flags.data(), engine_flags, + engine_flags_length) == 0) + ? napi_ok + : napi_invalid_arg; + } + + ApplyDefaultV8Flags(); + if (engine_flags_length > 0) { + v8::V8::SetFlagsFromString(engine_flags, + static_cast(engine_flags_length)); + } + v8::V8::InitializeICUDefaultLocation(""); + v8::V8::InitializeExternalStartupData(""); + g_runtime.platform = EdgeV8Platform::Create(); + if (g_runtime.platform == nullptr) return napi_generic_failure; + v8::V8::InitializePlatform(g_runtime.platform.get()); + v8::V8::Initialize(); + g_runtime.engine_flags.assign(engine_flags_length > 0 ? engine_flags : "", + engine_flags_length); + return napi_ok; +} + napi_status AcquireRuntime(EdgeV8Platform** platform_out) { if (platform_out == nullptr) return napi_invalid_arg; std::lock_guard lock(g_runtime_mu); - - if (g_runtime.refcount == 0 && g_runtime.platform == nullptr) { - ApplyDefaultV8Flags(); - v8::V8::InitializeICUDefaultLocation(""); - v8::V8::InitializeExternalStartupData(""); - g_runtime.platform = EdgeV8Platform::Create(); - v8::V8::InitializePlatform(g_runtime.platform.get()); - v8::V8::Initialize(); - } - + if (g_runtime.platform == nullptr) return napi_invalid_arg; g_runtime.refcount++; *platform_out = g_runtime.platform.get(); return *platform_out != nullptr ? napi_ok : napi_generic_failure; @@ -1722,6 +1706,17 @@ void NapiV8ApplyPromiseHooksToContext(napi_env env, v8::Local conte extern "C" { +napi_status NAPI_CDECL unofficial_napi_configure_runtime( + const unofficial_napi_runtime_options* options) { + if (options != nullptr && + (options->size < sizeof(unofficial_napi_runtime_options) || + options->version != UNOFFICIAL_NAPI_RUNTIME_OPTIONS_VERSION)) { + return napi_invalid_arg; + } + return ConfigureRuntime(options != nullptr ? options->engine_flags : nullptr, + options != nullptr ? options->engine_flags_length : 0); +} + napi_status NAPI_CDECL unofficial_napi_create_guest_backed_typedarray( napi_env env, napi_typedarray_type type, size_t length, void** data, napi_value* result) { @@ -1746,31 +1741,44 @@ napi_status NAPI_CDECL unofficial_napi_create_guest_backed_typedarray( return napi_create_typedarray(env, type, length, arraybuffer, 0, result); } -napi_status NAPI_CDECL unofficial_napi_set_embedder_hooks( - const unofficial_napi_embedder_hooks* hooks) { - std::lock_guard lock(g_runtime_mu); - if (hooks == nullptr) { - g_embedder_hooks.hooks = unofficial_napi_embedder_hooks{}; - } else { - g_embedder_hooks.hooks = *hooks; - } - return napi_ok; -} - -napi_status NAPI_CDECL unofficial_napi_set_enqueue_foreground_task_callback( +napi_status NAPI_CDECL unofficial_napi_attach_env( napi_env env, - unofficial_napi_enqueue_foreground_task_callback callback, - void* target) { - if (env == nullptr) return napi_invalid_arg; - env->enqueue_foreground_task_callback = callback; - env->enqueue_foreground_task_target = target; + const unofficial_napi_env_hooks* hooks, + uint64_t* accepted_hooks_out) { + if (env == nullptr || env->isolate == nullptr || hooks == nullptr || + accepted_hooks_out == nullptr || + hooks->size < sizeof(unofficial_napi_env_hooks) || + hooks->version != UNOFFICIAL_NAPI_ENV_HOOKS_VERSION || + env->embedder_hooks_attached) { + return napi_invalid_arg; + } { std::lock_guard lock(g_runtime_mu); if (g_runtime.platform != nullptr && - !g_runtime.platform->BindForegroundTaskTarget(env->isolate, env, callback, target)) { + !g_runtime.platform->BindForegroundTaskTarget( + env->isolate, + env, + hooks->enqueue_foreground_task_callback, + hooks->data)) { return napi_generic_failure; } + auto& fatal = g_fatal_error_callbacks[env->isolate]; + fatal.fatal = hooks->fatal_error_callback; + fatal.oom = hooks->oom_error_callback; } + + env->context_token_assign_callback = hooks->context_token_assign_callback; + env->context_token_unassign_callback = hooks->context_token_unassign_callback; + env->context_token_callback_data = hooks->data; + env->enqueue_foreground_task_callback = hooks->enqueue_foreground_task_callback; + env->enqueue_foreground_task_target = hooks->data; + env->embedder_hooks_attached = true; + + env->isolate->SetFatalErrorHandler( + hooks->fatal_error_callback != nullptr ? FatalErrorCallback : nullptr); + env->isolate->SetOOMErrorHandler( + hooks->oom_error_callback != nullptr ? OOMErrorCallback : nullptr); + *accepted_hooks_out = unofficial_napi_env_hooks_requested(hooks); return napi_ok; } @@ -1788,53 +1796,8 @@ napi_status NAPI_CDECL unofficial_napi_create_env_from_context( return napi_ok; } -napi_status NAPI_CDECL unofficial_napi_set_edge_environment(napi_env env, void* environment) { - if (env == nullptr) return napi_invalid_arg; - env->edge_environment = environment; - return napi_ok; -} - -void* unofficial_napi_get_edge_environment(napi_env env) { - return env == nullptr ? nullptr : env->edge_environment; -} - -napi_status NAPI_CDECL unofficial_napi_set_env_cleanup_callback( - napi_env env, - unofficial_napi_env_cleanup_callback callback, - void* data) { - if (env == nullptr) return napi_invalid_arg; - env->env_cleanup_callback = callback; - env->env_cleanup_callback_data = data; - return napi_ok; -} - -napi_status NAPI_CDECL unofficial_napi_set_env_destroy_callback( - napi_env env, - unofficial_napi_env_destroy_callback callback, - void* data) { +static napi_status DestroyEnvInstance(napi_env env) { if (env == nullptr) return napi_invalid_arg; - env->env_destroy_callback = callback; - env->env_destroy_callback_data = data; - return napi_ok; -} - -napi_status NAPI_CDECL unofficial_napi_set_context_token_callbacks( - napi_env env, - unofficial_napi_context_token_callback assign_callback, - unofficial_napi_context_token_callback unassign_callback, - void* data) { - if (env == nullptr) return napi_invalid_arg; - env->context_token_assign_callback = assign_callback; - env->context_token_unassign_callback = unassign_callback; - env->context_token_callback_data = data; - return napi_ok; -} - -napi_status NAPI_CDECL unofficial_napi_destroy_env_instance(napi_env env) { - if (env == nullptr) return napi_invalid_arg; - if (env->env_cleanup_callback != nullptr) { - env->env_cleanup_callback(env, env->env_cleanup_callback_data); - } ProfilerState profiler_state; bool has_profiler_state = false; { @@ -1888,55 +1851,43 @@ napi_status NAPI_CDECL unofficial_napi_destroy_env_instance(napi_env env) { return napi_ok; } -napi_status NAPI_CDECL unofficial_napi_set_fatal_error_callbacks( +napi_status NAPI_CDECL unofficial_napi_configure_near_heap_limit_callback( napi_env env, - unofficial_napi_fatal_error_callback fatal_callback, - unofficial_napi_oom_error_callback oom_callback) { - if (env == nullptr || env->isolate == nullptr) return napi_invalid_arg; - - { - std::lock_guard lock(g_runtime_mu); - auto& entry = g_fatal_error_callbacks[env->isolate]; - entry.fatal = fatal_callback; - entry.oom = oom_callback; + unofficial_napi_near_heap_limit_callback callback_or_null, + void* data, + size_t restored_heap_limit) { + if (env == nullptr || env->isolate == nullptr || + (callback_or_null != nullptr && restored_heap_limit != 0) || + (callback_or_null == nullptr && data != nullptr)) { + return napi_invalid_arg; } - env->isolate->SetFatalErrorHandler(fatal_callback != nullptr ? FatalErrorCallback : nullptr); - env->isolate->SetOOMErrorHandler(oom_callback != nullptr ? OOMErrorCallback : nullptr); - return napi_ok; -} - -napi_status NAPI_CDECL unofficial_napi_set_near_heap_limit_callback( - napi_env env, - unofficial_napi_near_heap_limit_callback callback, - void* data) { - if (env == nullptr || env->isolate == nullptr) return napi_invalid_arg; - - { - std::lock_guard lock(g_runtime_mu); - auto& entry = g_near_heap_limit_callbacks[env->isolate]; - entry.callback = callback; - entry.data = data; + if (callback_or_null != nullptr) { + bool install_provider_callback = false; + { + std::lock_guard lock(g_runtime_mu); + auto [it, inserted] = + g_near_heap_limit_callbacks.try_emplace(env->isolate); + it->second.callback = callback_or_null; + it->second.data = data; + install_provider_callback = inserted; + } + if (install_provider_callback) { + env->isolate->AddNearHeapLimitCallback(NearHeapLimitCallback, env); + } + return napi_ok; } - env->isolate->AddNearHeapLimitCallback(NearHeapLimitCallback, env); - return napi_ok; -} -napi_status NAPI_CDECL unofficial_napi_remove_near_heap_limit_callback( - napi_env env, - size_t heap_limit) { - if (env == nullptr || env->isolate == nullptr) return napi_invalid_arg; + bool remove_provider_callback = false; { std::lock_guard lock(g_runtime_mu); - g_near_heap_limit_callbacks.erase(env->isolate); + remove_provider_callback = + g_near_heap_limit_callbacks.erase(env->isolate) != 0; + } + if (remove_provider_callback) { + env->isolate->RemoveNearHeapLimitCallback( + NearHeapLimitCallback, restored_heap_limit); } - env->isolate->RemoveNearHeapLimitCallback(NearHeapLimitCallback, heap_limit); - return napi_ok; -} - -napi_status NAPI_CDECL unofficial_napi_set_stack_limit(napi_env env, void* stack_limit) { - if (env == nullptr || env->isolate == nullptr || stack_limit == nullptr) return napi_invalid_arg; - env->isolate->SetStackLimit(reinterpret_cast(stack_limit)); return napi_ok; } @@ -1948,46 +1899,44 @@ napi_status NAPI_CDECL unofficial_napi_wrap_existing_value(napi_env env, return (*result == nullptr) ? napi_generic_failure : napi_ok; } -napi_status NAPI_CDECL unofficial_napi_create_env(int32_t module_api_version, - napi_env* env_out, - void** scope_out) { - return unofficial_napi_create_env_with_options( - module_api_version, nullptr, env_out, scope_out); -} - -napi_status NAPI_CDECL unofficial_napi_create_env_with_options( +napi_status NAPI_CDECL unofficial_napi_create_env( int32_t module_api_version, const unofficial_napi_env_create_options* options, napi_env* env_out, - void** scope_out) { - // This function owns options->guest_heap_ctx from here on: it must be - // released exactly once, either by the allocator's destructor or on a - // failure before the allocator takes it. - void* guest_heap_ctx = - options != nullptr ? options->guest_heap_ctx : nullptr; - if (env_out == nullptr || scope_out == nullptr) { - if (guest_heap_ctx != nullptr) napi_host_guest_heap_release(guest_heap_ctx); + unofficial_napi_env_owner* owner_out) { + if (options != nullptr && + (options->size < sizeof(unofficial_napi_env_create_options) || + options->version != UNOFFICIAL_NAPI_ENV_CREATE_OPTIONS_VERSION)) { + // The descriptor is not large enough to transfer guest_heap ownership. return napi_invalid_arg; } + // Size/version validation above proves that guest_heap is present. This + // function owns it from here on and must release it exactly once. + unofficial_napi_guest_heap guest_heap = + options != nullptr ? options->guest_heap : nullptr; + if (env_out == nullptr || owner_out == nullptr) { + if (guest_heap != nullptr) napi_host_guest_heap_release(guest_heap); + return napi_invalid_arg; + } EdgeV8Platform* platform = nullptr; napi_status status = AcquireRuntime(&platform); if (status != napi_ok || platform == nullptr) { - if (guest_heap_ctx != nullptr) napi_host_guest_heap_release(guest_heap_ctx); + if (guest_heap != nullptr) napi_host_guest_heap_release(guest_heap); return status != napi_ok ? status : napi_generic_failure; } auto allocator = std::make_shared(); if (!allocator) { - if (guest_heap_ctx != nullptr) napi_host_guest_heap_release(guest_heap_ctx); + if (guest_heap != nullptr) napi_host_guest_heap_release(guest_heap); ReleaseRuntime(); return napi_generic_failure; } - if (guest_heap_ctx != nullptr) { + if (guest_heap != nullptr) { // Armed before the isolate exists, so even bootstrap-time backing stores // are guest-memory-backed. The allocator's destructor releases the ctx on // every later failure path. - allocator->set_guest_heap_ctx(guest_heap_ctx); + allocator->set_guest_heap(guest_heap); } v8::Isolate::CreateParams params{}; @@ -2010,7 +1959,7 @@ napi_status NAPI_CDECL unofficial_napi_create_env_with_options( static_cast(options->stack_limit)); } } - ApplyNodeIsolateCreateParams(¶ms); + ApplyNodeIsolateCreateParams(¶ms, options); v8::Isolate* isolate = CreateIsolateForEnv(platform, params); if (isolate == nullptr) { ReleaseRuntime(); @@ -2049,17 +1998,18 @@ napi_status NAPI_CDECL unofficial_napi_create_env_with_options( } *env_out = scope->env; - *scope_out = scope; + *owner_out = reinterpret_cast(scope); return napi_ok; } -napi_status ReleaseEnvScope(void* scope_ptr, void* shutdown_pump_handle) { - if (scope_ptr == nullptr) return napi_invalid_arg; - auto* scope = static_cast(scope_ptr); +napi_status ReleaseEnvScope(unofficial_napi_env_owner owner, + void* shutdown_pump_handle) { + if (owner == nullptr) return napi_invalid_arg; + auto* scope = reinterpret_cast(owner); napi_status status = napi_ok; if (scope->env != nullptr) { - status = unofficial_napi_destroy_env_instance(scope->env); + status = DestroyEnvInstance(scope->env); scope->env = nullptr; } @@ -2085,29 +2035,20 @@ napi_status ReleaseEnvScope(void* scope_ptr, void* shutdown_pump_handle) { return status; } -napi_status NAPI_CDECL unofficial_napi_release_env(void* scope_ptr) { - return ReleaseEnvScope(scope_ptr, nullptr); +napi_status NAPI_CDECL unofficial_napi_release_env(unofficial_napi_env_owner owner, + struct uv_loop_s* loop) { + return ReleaseEnvScope(owner, static_cast(loop)); } -napi_status NAPI_CDECL unofficial_napi_release_env_with_loop(void* scope_ptr, - struct uv_loop_s* loop) { - return ReleaseEnvScope(scope_ptr, static_cast(loop)); -} - -napi_status NAPI_CDECL unofficial_napi_low_memory_notification(napi_env env) { +napi_status NAPI_CDECL unofficial_napi_collect_garbage(napi_env env) { if (env == nullptr || env->isolate == nullptr) return napi_invalid_arg; + // This is the production embedder API for asking V8 to reclaim as much + // memory as possible. RequestGarbageCollectionForTesting is intentionally + // not used: it aborts unless V8 was initialized with testing-only flags. env->isolate->LowMemoryNotification(); return napi_ok; } -napi_status NAPI_CDECL unofficial_napi_set_flags_from_string( - const char* flags, - size_t length) { - if (flags == nullptr) return napi_invalid_arg; - v8::V8::SetFlagsFromString(flags, static_cast(length)); - return napi_ok; -} - napi_status NAPI_CDECL unofficial_napi_set_prepare_stack_trace_callback( napi_env env, napi_value callback) { @@ -2210,15 +2151,6 @@ void DrainMicrotasksForEnv(napi_env env) { PumpPlatformForegroundTasks(env); } -napi_status NAPI_CDECL unofficial_napi_request_gc_for_testing(napi_env env) { - if (env == nullptr || env->isolate == nullptr) return napi_invalid_arg; - // Match Node test expectations for global.gc(): force an actual full GC - // cycle rather than only hinting memory pressure. - env->isolate->RequestGarbageCollectionForTesting( - v8::Isolate::GarbageCollectionType::kFullGarbageCollection); - return napi_ok; -} - napi_status NAPI_CDECL unofficial_napi_event_loop_checkpoint( napi_env env, unofficial_napi_event_loop_checkpoint_mode mode, @@ -2276,18 +2208,6 @@ napi_status NAPI_CDECL unofficial_napi_cancel_terminate_execution(napi_env env) return napi_ok; } -napi_status NAPI_CDECL unofficial_napi_set_pending_exception(napi_env env, - napi_value error) { - if (env == nullptr || env->isolate == nullptr || error == nullptr) { - return napi_invalid_arg; - } - env->last_exception.Reset(); - env->last_exception_source_line.clear(); - env->last_exception_thrown_at.clear(); - env->last_exception.Reset(env->isolate, napi_v8_unwrap_value(error)); - return napi_ok; -} - napi_status NAPI_CDECL unofficial_napi_request_interrupt( napi_env env, unofficial_napi_interrupt_callback callback, @@ -2324,26 +2244,19 @@ napi_status NAPI_CDECL unofficial_napi_request_interrupt( napi_status NAPI_CDECL unofficial_napi_structured_clone( napi_env env, napi_value value, + napi_value transfer_list_or_null, napi_value* result_out) { - return StructuredCloneImpl(env, value, nullptr, result_out); -} - -napi_status NAPI_CDECL unofficial_napi_structured_clone_with_transfer( - napi_env env, - napi_value value, - napi_value transfer_list, - napi_value* result_out) { - return StructuredCloneImpl(env, value, transfer_list, result_out); + return StructuredCloneImpl(env, value, transfer_list_or_null, result_out); } -napi_status NAPI_CDECL unofficial_napi_serialize_value( +napi_status NAPI_CDECL unofficial_napi_message_create( napi_env env, napi_value value, - void** payload_out) { - if (env == nullptr || env->isolate == nullptr || value == nullptr || payload_out == nullptr) { + unofficial_napi_message* message_out) { + if (env == nullptr || env->isolate == nullptr || value == nullptr || message_out == nullptr) { return napi_invalid_arg; } - *payload_out = nullptr; + *message_out = nullptr; v8::Isolate* isolate = env->isolate; v8::HandleScope handle_scope(isolate); @@ -2371,20 +2284,22 @@ napi_status NAPI_CDECL unofficial_napi_serialize_value( std::free(released.first); payload->shared_array_buffers = serializer_delegate.shared_array_buffers(); payload->wasm_modules = serializer_delegate.TakeWasmModules(); - *payload_out = payload; + *message_out = reinterpret_cast(payload); return napi_ok; } -napi_status NAPI_CDECL unofficial_napi_deserialize_value( +napi_status NAPI_CDECL unofficial_napi_message_take( napi_env env, - void* payload_ptr, + unofficial_napi_message message, napi_value* result_out) { - if (env == nullptr || env->isolate == nullptr || payload_ptr == nullptr || result_out == nullptr) { + if (message == nullptr) return napi_invalid_arg; + std::unique_ptr payload( + reinterpret_cast(message)); + if (env == nullptr || env->isolate == nullptr || result_out == nullptr) { return napi_invalid_arg; } *result_out = nullptr; - auto* payload = static_cast(payload_ptr); v8::Isolate* isolate = env->isolate; v8::EscapableHandleScope handle_scope(isolate); v8::Local context = env->context(); @@ -2404,8 +2319,8 @@ napi_status NAPI_CDECL unofficial_napi_deserialize_value( return *result_out == nullptr ? napi_generic_failure : napi_ok; } -void NAPI_CDECL unofficial_napi_release_serialized_value(void* payload_ptr) { - delete static_cast(payload_ptr); +void NAPI_CDECL unofficial_napi_message_drop(unofficial_napi_message message) { + delete reinterpret_cast(message); } napi_status NAPI_CDECL unofficial_napi_enqueue_microtask(napi_env env, napi_value callback) { @@ -2509,46 +2424,19 @@ napi_status NAPI_CDECL unofficial_napi_get_promise_details(napi_env env, return napi_ok; } -napi_status NAPI_CDECL unofficial_napi_get_error_source_positions( +napi_status NAPI_CDECL unofficial_napi_get_error_metadata( napi_env env, napi_value error, - unofficial_napi_error_source_positions* out) { - return unofficial_napi_internal::GetErrorSourcePositions(env, error, out); -} - -napi_status NAPI_CDECL unofficial_napi_set_source_maps_enabled( - napi_env env, - bool enabled) { - return unofficial_napi_internal::SetSourceMapsEnabled(env, enabled); + unofficial_napi_error_metadata_mode mode, + unofficial_napi_error_metadata* out) { + return unofficial_napi_internal::GetErrorMetadata(env, error, mode, out); } -napi_status NAPI_CDECL unofficial_napi_set_get_source_map_error_source_callback( +napi_status NAPI_CDECL unofficial_napi_configure_source_maps( napi_env env, + bool enabled, napi_value callback) { - return unofficial_napi_internal::SetGetSourceMapErrorSourceCallback(env, callback); -} - -napi_status NAPI_CDECL unofficial_napi_get_error_source_line_for_stderr( - napi_env env, - napi_value error, - napi_value* result_out) { - return unofficial_napi_internal::GetErrorSourceLineForStderr(env, error, result_out); -} - -napi_status NAPI_CDECL unofficial_napi_get_error_thrown_at( - napi_env env, - napi_value error, - napi_value* result_out) { - return unofficial_napi_internal::GetErrorThrownAt(env, error, result_out); -} - -napi_status NAPI_CDECL unofficial_napi_take_preserved_error_formatting( - napi_env env, - napi_value error, - napi_value* source_line_out, - napi_value* thrown_at_out) { - return unofficial_napi_internal::TakePreservedErrorFormatting( - env, error, source_line_out, thrown_at_out); + return unofficial_napi_internal::ConfigureSourceMaps(env, enabled, callback); } napi_status NAPI_CDECL unofficial_napi_preserve_error_source_message( @@ -2719,39 +2607,6 @@ napi_status NAPI_CDECL unofficial_napi_get_call_sites(napi_env env, return GetCallSitesImpl(env, frames, 1, callsites_out); } -napi_status NAPI_CDECL unofficial_napi_get_current_stack_trace(napi_env env, - uint32_t frames, - napi_value* callsites_out) { - return GetCallSitesImpl(env, frames, 0, callsites_out); -} - -napi_status NAPI_CDECL unofficial_napi_get_caller_location(napi_env env, napi_value* location_out) { - if (env == nullptr || env->isolate == nullptr || location_out == nullptr) return napi_invalid_arg; - *location_out = nullptr; - - v8::Isolate* isolate = env->isolate; - v8::EscapableHandleScope scope(isolate); - - v8::Local trace = v8::StackTrace::CurrentStackTrace(isolate, 2); - if (trace->GetFrameCount() != 2) { - return napi_ok; - } - - v8::Local frame = trace->GetFrame(isolate, 1); - v8::Local file = frame->GetScriptNameOrSourceURL(); - if (file.IsEmpty()) { - return napi_ok; - } - v8::Local values[] = { - v8::Integer::New(isolate, frame->GetLineNumber()), - v8::Integer::New(isolate, frame->GetColumn()), - file, - }; - v8::Local location = v8::Array::New(isolate, values, 3); - *location_out = napi_v8_wrap_value(env, scope.Escape(location)); - return *location_out == nullptr ? napi_generic_failure : napi_ok; -} - napi_status NAPI_CDECL unofficial_napi_arraybuffer_view_has_buffer(napi_env env, napi_value value, bool* result_out) { @@ -2773,29 +2628,6 @@ napi_status NAPI_CDECL unofficial_napi_get_constructor_name(napi_env env, return *name_out == nullptr ? napi_generic_failure : napi_ok; } -napi_status NAPI_CDECL unofficial_napi_get_own_non_index_properties( - napi_env env, - napi_value value, - uint32_t filter_bits, - napi_value* result_out) { - if (env == nullptr || value == nullptr || result_out == nullptr) return napi_invalid_arg; - v8::Local raw = napi_v8_unwrap_value(value); - if (raw.IsEmpty() || !raw->IsObject()) return napi_invalid_arg; - - v8::Local properties; - if (!raw.As() - ->GetPropertyNames(env->context(), - v8::KeyCollectionMode::kOwnOnly, - static_cast(filter_bits), - v8::IndexFilter::kSkipIndices) - .ToLocal(&properties)) { - return napi_generic_failure; - } - - *result_out = napi_v8_wrap_value(env, properties); - return *result_out == nullptr ? napi_generic_failure : napi_ok; -} - napi_status NAPI_CDECL unofficial_napi_create_private_symbol(napi_env env, const char* utf8description, size_t length, @@ -2833,34 +2665,35 @@ napi_status NAPI_CDECL unofficial_napi_create_private_symbol(napi_env env, return *result_out == nullptr ? napi_generic_failure : napi_ok; } -napi_status NAPI_CDECL unofficial_napi_get_process_memory_info( +napi_status NAPI_CDECL unofficial_napi_get_own_non_index_properties( napi_env env, - double* heap_total_out, - double* heap_used_out, - double* external_out, - double* array_buffers_out) { - if (env == nullptr || env->isolate == nullptr || heap_total_out == nullptr || - heap_used_out == nullptr || external_out == nullptr || array_buffers_out == nullptr) { + napi_value value, + uint32_t filter_bits, + napi_value* result_out) { + if (env == nullptr || value == nullptr || result_out == nullptr) { return napi_invalid_arg; } + v8::Local raw = napi_v8_unwrap_value(value); + if (raw.IsEmpty() || !raw->IsObject()) return napi_object_expected; - v8::HeapStatistics stats; - env->isolate->GetHeapStatistics(&stats); - - uint64_t array_buffers = 0; - { - std::lock_guard lock(g_runtime_mu); - auto it = g_tracking_allocators.find(env->isolate->GetArrayBufferAllocator()); - if (it != g_tracking_allocators.end() && it->second) { - array_buffers = it->second->total_mem_usage(); + v8::TryCatch try_catch(env->isolate); + v8::Local properties; + if (!raw.As() + ->GetPropertyNames(env->context(), + v8::KeyCollectionMode::kOwnOnly, + static_cast(filter_bits), + v8::IndexFilter::kSkipIndices) + .ToLocal(&properties)) { + if (try_catch.HasCaught()) { + napi_v8_set_last_exception( + env, try_catch.Exception(), try_catch.Message()); + return napi_pending_exception; } + return napi_generic_failure; } - *heap_total_out = static_cast(stats.total_heap_size()); - *heap_used_out = static_cast(stats.used_heap_size()); - *external_out = static_cast(stats.external_memory()); - *array_buffers_out = static_cast(array_buffers); - return napi_ok; + *result_out = napi_v8_wrap_value(env, properties); + return *result_out == nullptr ? napi_generic_failure : napi_ok; } napi_status NAPI_CDECL unofficial_napi_get_hash_seed(napi_env env, @@ -2883,13 +2716,20 @@ napi_status NAPI_CDECL unofficial_napi_get_hash_seed(napi_env env, napi_status NAPI_CDECL unofficial_napi_get_heap_statistics( napi_env env, unofficial_napi_heap_statistics* stats_out) { - if (env == nullptr || env->isolate == nullptr || stats_out == nullptr) { + if (env == nullptr || env->isolate == nullptr || stats_out == nullptr || + stats_out->size < sizeof(*stats_out) || + stats_out->version != UNOFFICIAL_NAPI_HEAP_STATISTICS_VERSION) { return napi_invalid_arg; } v8::HeapStatistics stats; env->isolate->GetHeapStatistics(&stats); + const uint32_t output_size = stats_out->size; + std::memset(stats_out, 0, sizeof(*stats_out)); + stats_out->size = output_size; + stats_out->version = UNOFFICIAL_NAPI_HEAP_STATISTICS_VERSION; + stats_out->valid_fields = unofficial_napi_heap_stat_all; stats_out->total_heap_size = stats.total_heap_size(); stats_out->total_heap_size_executable = stats.total_heap_size_executable(); stats_out->total_physical_size = stats.total_physical_size(); @@ -2904,45 +2744,45 @@ napi_status NAPI_CDECL unofficial_napi_get_heap_statistics( stats_out->total_global_handles_size = stats.total_global_handles_size(); stats_out->used_global_handles_size = stats.used_global_handles_size(); stats_out->external_memory = stats.external_memory(); - return napi_ok; -} - -napi_status NAPI_CDECL unofficial_napi_get_heap_space_count( - napi_env env, - uint32_t* count_out) { - if (env == nullptr || env->isolate == nullptr || count_out == nullptr) { - return napi_invalid_arg; + stats_out->array_buffer_memory = 0; + { + std::lock_guard lock(g_runtime_mu); + auto it = g_tracking_allocators.find(env->isolate->GetArrayBufferAllocator()); + if (it != g_tracking_allocators.end() && it->second) { + stats_out->array_buffer_memory = it->second->total_mem_usage(); + } } - - *count_out = static_cast(env->isolate->NumberOfHeapSpaces()); return napi_ok; } napi_status NAPI_CDECL unofficial_napi_get_heap_space_statistics( napi_env env, - uint32_t space_index, - unofficial_napi_heap_space_statistics* stats_out) { - if (env == nullptr || env->isolate == nullptr || stats_out == nullptr) { + unofficial_napi_heap_space_statistics* stats_out, + uint32_t capacity, + uint32_t* count_out) { + if (env == nullptr || env->isolate == nullptr || count_out == nullptr || + (capacity > 0 && stats_out == nullptr)) { return napi_invalid_arg; } const uint32_t space_count = static_cast(env->isolate->NumberOfHeapSpaces()); - if (space_index >= space_count) { - return napi_invalid_arg; + *count_out = space_count; + const uint32_t output_count = std::min(capacity, space_count); + for (uint32_t space_index = 0; space_index < output_count; ++space_index) { + v8::HeapSpaceStatistics stats; + env->isolate->GetHeapSpaceStatistics(&stats, space_index); + + auto& output = stats_out[space_index]; + std::snprintf(output.space_name, + sizeof(output.space_name), + "%s", + stats.space_name() != nullptr ? stats.space_name() : ""); + output.space_size = stats.space_size(); + output.space_used_size = stats.space_used_size(); + output.space_available_size = stats.space_available_size(); + output.physical_space_size = stats.physical_space_size(); } - - v8::HeapSpaceStatistics stats; - env->isolate->GetHeapSpaceStatistics(&stats, space_index); - - std::snprintf(stats_out->space_name, - sizeof(stats_out->space_name), - "%s", - stats.space_name() != nullptr ? stats.space_name() : ""); - stats_out->space_size = stats.space_size(); - stats_out->space_used_size = stats.space_used_size(); - stats_out->space_available_size = stats.space_available_size(); - stats_out->physical_space_size = stats.physical_space_size(); return napi_ok; } @@ -2963,168 +2803,153 @@ napi_status NAPI_CDECL unofficial_napi_get_heap_code_statistics( return napi_ok; } -napi_status NAPI_CDECL unofficial_napi_start_cpu_profile( +napi_status NAPI_CDECL unofficial_napi_profile_start( napi_env env, - unofficial_napi_cpu_profile_start_result* result_out, - uint32_t* profile_id_out) { + unofficial_napi_profile_kind kind, + unofficial_napi_profile_start_result* result_out, + unofficial_napi_profile* profile_out) { if (env == nullptr || env->isolate == nullptr || result_out == nullptr || - profile_id_out == nullptr) { + profile_out == nullptr || + (kind != unofficial_napi_profile_cpu && + kind != unofficial_napi_profile_heap)) { return napi_invalid_arg; } - *result_out = unofficial_napi_cpu_profile_start_ok; - *profile_id_out = 0; + *result_out = unofficial_napi_profile_start_ok; + *profile_out = nullptr; if (!IsEnvThreadEntered(env)) return napi_cannot_run_js; - ProfilerState* state = nullptr; + if (kind == unofficial_napi_profile_heap) { + { + std::lock_guard lock(g_runtime_mu); + auto it = g_profiler_states.find(env); + if (it != g_profiler_states.end() && + std::any_of(it->second.active_profiles.begin(), + it->second.active_profiles.end(), + [](unofficial_napi_profile profile) { + return profile != nullptr && + profile->kind == unofficial_napi_profile_heap; + })) { + *result_out = unofficial_napi_profile_start_busy; + return napi_ok; + } + } + if (!env->isolate->GetHeapProfiler()->StartSamplingHeapProfiler()) { + *result_out = unofficial_napi_profile_start_busy; + return napi_ok; + } + auto* profile = new (std::nothrow) unofficial_napi_profile__; + if (profile == nullptr) { + env->isolate->GetHeapProfiler()->StopSamplingHeapProfiler(); + return napi_generic_failure; + } + profile->kind = kind; + { + std::lock_guard lock(g_runtime_mu); + EnsureProfilerState(env).active_profiles.push_back(profile); + } + *profile_out = profile; + return napi_ok; + } + + v8::CpuProfiler* cpu_profiler = nullptr; { std::lock_guard lock(g_runtime_mu); - state = &EnsureProfilerState(env); - if (state->cpu_profiler == nullptr) { - state->cpu_profiler = v8::CpuProfiler::New(env->isolate); - if (state->cpu_profiler == nullptr) return napi_generic_failure; + ProfilerState& state = EnsureProfilerState(env); + if (state.cpu_profiler == nullptr) { + state.cpu_profiler = v8::CpuProfiler::New(env->isolate); + if (state.cpu_profiler == nullptr) return napi_generic_failure; } + cpu_profiler = state.cpu_profiler; } - v8::CpuProfilingResult result = state->cpu_profiler->Start( + v8::CpuProfilingResult result = cpu_profiler->Start( v8::CpuProfilingOptions{v8::CpuProfilingMode::kLeafNodeLineNumbers, v8::CpuProfilingOptions::kNoSampleLimit}); if (result.status == v8::CpuProfilingStatus::kErrorTooManyProfilers) { - *result_out = unofficial_napi_cpu_profile_start_too_many; + *result_out = unofficial_napi_profile_start_busy; return napi_ok; } if (result.status != v8::CpuProfilingStatus::kStarted) { return napi_generic_failure; } - *profile_id_out = static_cast(result.id); + auto* profile = new (std::nothrow) unofficial_napi_profile__; + if (profile == nullptr) { + if (v8::CpuProfile* stopped = cpu_profiler->Stop(result.id)) { + stopped->Delete(); + } + return napi_generic_failure; + } + profile->kind = kind; + profile->provider_id = static_cast(result.id); { std::lock_guard lock(g_runtime_mu); auto it = g_profiler_states.find(env); if (it != g_profiler_states.end()) { - it->second.active_cpu_profiles.push_back(*profile_id_out); + it->second.active_profiles.push_back(profile); } } + *profile_out = profile; return napi_ok; } -napi_status NAPI_CDECL unofficial_napi_stop_cpu_profile( +napi_status NAPI_CDECL unofficial_napi_profile_stop( napi_env env, - uint32_t profile_id, - bool* found_out, - char** json_out, - size_t* json_len_out) { - if (env == nullptr || env->isolate == nullptr || found_out == nullptr || - json_out == nullptr || json_len_out == nullptr) { + unofficial_napi_profile profile, + napi_value* json_out) { + if (env == nullptr || env->isolate == nullptr || profile == nullptr || + json_out == nullptr) { return napi_invalid_arg; } - *found_out = false; *json_out = nullptr; - *json_len_out = 0; if (!IsEnvThreadEntered(env)) return napi_cannot_run_js; v8::CpuProfiler* cpu_profiler = nullptr; + unofficial_napi_profile_kind kind = unofficial_napi_profile_cpu; + uint32_t provider_id = 0; { std::lock_guard lock(g_runtime_mu); auto it = g_profiler_states.find(env); - if (it == g_profiler_states.end() || it->second.cpu_profiler == nullptr) { - return napi_ok; - } - auto active_it = std::find( - it->second.active_cpu_profiles.begin(), - it->second.active_cpu_profiles.end(), - profile_id); - if (active_it == it->second.active_cpu_profiles.end()) { - return napi_ok; - } - it->second.active_cpu_profiles.erase(active_it); + if (it == g_profiler_states.end()) return napi_invalid_arg; + auto active_it = std::find(it->second.active_profiles.begin(), + it->second.active_profiles.end(), profile); + if (active_it == it->second.active_profiles.end()) return napi_invalid_arg; + kind = profile->kind; + provider_id = profile->provider_id; + it->second.active_profiles.erase(active_it); cpu_profiler = it->second.cpu_profiler; } - v8::CpuProfile* profile = cpu_profiler->Stop(profile_id); - if (profile == nullptr) { - return napi_ok; - } - - StringOutputStream stream; - profile->Serialize(&stream, v8::CpuProfile::SerializationFormat::kJSON); - profile->Delete(); - if (!CopyStringToMallocBuffer(stream.output(), json_out, json_len_out)) { - return napi_generic_failure; - } - *found_out = true; - return napi_ok; -} - -napi_status NAPI_CDECL unofficial_napi_start_heap_profile( - napi_env env, - bool* started_out) { - if (env == nullptr || env->isolate == nullptr || started_out == nullptr) { - return napi_invalid_arg; - } - *started_out = false; - if (!IsEnvThreadEntered(env)) return napi_cannot_run_js; - - const bool started = env->isolate->GetHeapProfiler()->StartSamplingHeapProfiler(); - if (started) { - std::lock_guard lock(g_runtime_mu); - EnsureProfilerState(env).heap_profile_started = true; - } - *started_out = started; - return napi_ok; -} - -napi_status NAPI_CDECL unofficial_napi_stop_heap_profile( - napi_env env, - bool* found_out, - char** json_out, - size_t* json_len_out) { - if (env == nullptr || env->isolate == nullptr || found_out == nullptr || - json_out == nullptr || json_len_out == nullptr) { - return napi_invalid_arg; - } - *found_out = false; - *json_out = nullptr; - *json_len_out = 0; - if (!IsEnvThreadEntered(env)) return napi_cannot_run_js; - - { - std::lock_guard lock(g_runtime_mu); - auto it = g_profiler_states.find(env); - if (it == g_profiler_states.end() || !it->second.heap_profile_started) { - return napi_ok; - } + if (kind == unofficial_napi_profile_heap) { + std::string json; + const bool serialized = SerializeHeapProfile(env->isolate, &json); + env->isolate->GetHeapProfiler()->StopSamplingHeapProfiler(); + delete profile; + if (!serialized) return napi_generic_failure; + return CreateExternalUtf8Bytes(env, std::move(json), json_out); } - std::string json; - if (!SerializeHeapProfile(env->isolate, &json)) { - return napi_ok; - } - env->isolate->GetHeapProfiler()->StopSamplingHeapProfiler(); - { - std::lock_guard lock(g_runtime_mu); - auto it = g_profiler_states.find(env); - if (it != g_profiler_states.end()) { - it->second.heap_profile_started = false; - } - } - if (!CopyStringToMallocBuffer(json, json_out, json_len_out)) { + if (cpu_profiler == nullptr) { + delete profile; return napi_generic_failure; } - *found_out = true; - return napi_ok; + v8::CpuProfile* cpu_profile = cpu_profiler->Stop(provider_id); + delete profile; + if (cpu_profile == nullptr) return napi_generic_failure; + StringOutputStream stream; + cpu_profile->Serialize(&stream, v8::CpuProfile::SerializationFormat::kJSON); + cpu_profile->Delete(); + return CreateExternalUtf8Bytes(env, stream.TakeOutput(), json_out); } napi_status NAPI_CDECL unofficial_napi_take_heap_snapshot( napi_env env, const unofficial_napi_heap_snapshot_options* options, - char** json_out, - size_t* json_len_out) { - if (env == nullptr || env->isolate == nullptr || json_out == nullptr || - json_len_out == nullptr) { + napi_value* json_out) { + if (env == nullptr || env->isolate == nullptr || json_out == nullptr) { return napi_invalid_arg; } *json_out = nullptr; - *json_len_out = 0; if (!IsEnvThreadEntered(env)) return napi_cannot_run_js; v8::HeapProfiler::HeapSnapshotOptions snapshot_options; @@ -3144,14 +2969,7 @@ napi_status NAPI_CDECL unofficial_napi_take_heap_snapshot( StringOutputStream stream; snapshot->Serialize(&stream, v8::HeapSnapshot::kJSON); const_cast(snapshot)->Delete(); - if (!CopyStringToMallocBuffer(stream.output(), json_out, json_len_out)) { - return napi_generic_failure; - } - return napi_ok; -} - -void NAPI_CDECL unofficial_napi_free_buffer(void* data) { - std::free(data); + return CreateExternalUtf8Bytes(env, stream.TakeOutput(), json_out); } napi_status NAPI_CDECL unofficial_napi_get_continuation_preserved_embedder_data( @@ -3245,29 +3063,3 @@ napi_status NAPI_CDECL unofficial_napi_create_serdes_binding(napi_env env, } } // extern "C" - -void* NapiV8GetCurrentEdgeEnvironment(v8::Isolate* isolate) { - if (isolate == nullptr) return nullptr; - std::lock_guard lock(g_runtime_mu); - auto it = g_env_by_isolate.find(isolate); - if (it == g_env_by_isolate.end()) return nullptr; - napi_env env = it->second; - return env != nullptr ? env->edge_environment : nullptr; -} - -void* NapiV8GetCurrentEdgeEnvironment(v8::Local context) { - if (context.IsEmpty()) return nullptr; - napi_env env = nullptr; - { - std::lock_guard lock(g_runtime_mu); - auto it = g_env_by_isolate.find(context->GetIsolate()); - if (it == g_env_by_isolate.end()) return nullptr; - env = it->second; - } - if (env == nullptr) return nullptr; - v8::Local principal_context = env->context(); - if (context != principal_context && !NapiV8IsContextifyContext(env, context)) { - return nullptr; - } - return env->edge_environment; -} diff --git a/v8/src/unofficial_napi_contextify.cc b/v8/src/unofficial_napi_contextify.cc index 14bab77..ed23b40 100644 --- a/v8/src/unofficial_napi_contextify.cc +++ b/v8/src/unofficial_napi_contextify.cc @@ -96,6 +96,14 @@ std::mutex g_module_wrap_mu; std::unordered_map g_module_wrap_states; std::unordered_set g_module_wrap_cleanup_hooks; +ModuleWrapRecord* ModuleRecord(unofficial_napi_module module) { + return reinterpret_cast(module); +} + +unofficial_napi_module ModuleHandle(ModuleWrapRecord* record) { + return reinterpret_cast(record); +} + v8::Local OneByteString(v8::Isolate* isolate, const char* value) { return v8::String::NewFromUtf8(isolate, value, v8::NewStringType::kInternalized) .ToLocalChecked(); @@ -922,20 +930,14 @@ ModuleWrapRecord* FindModuleRecordForModule(napi_env env, v8::Local return nullptr; } -ModuleWrapRecord* FindModuleRecordForWrapper(napi_env env, napi_value wrapper) { - if (env == nullptr || wrapper == nullptr) return nullptr; +ModuleWrapRecord* FindModuleRecordForHandle(napi_env env, + unofficial_napi_module module) { + if (env == nullptr || module == nullptr) return nullptr; auto* state = GetModuleWrapState(env); if (state == nullptr) return nullptr; - for (ModuleWrapRecord* record : state->modules) { - if (record == nullptr || record->wrapper_ref == nullptr) continue; - napi_value candidate = GetRefValue(env, record->wrapper_ref); - if (candidate == nullptr) continue; - bool same = false; - if (napi_strict_equals(env, candidate, wrapper, &same) == napi_ok && same) { - return record; - } - } - return nullptr; + ModuleWrapRecord* candidate = ModuleRecord(module); + auto it = std::find(state->modules.begin(), state->modules.end(), candidate); + return it != state->modules.end() ? candidate : nullptr; } bool SetHostDefinedOptionSymbolOnWrapper(napi_env env, napi_value wrapper, napi_value id_value) { @@ -1474,7 +1476,11 @@ struct BytecodeRecord { }; BytecodeRecord* BytecodeRecordFromSource(const unofficial_napi_js_source* source) { - return source != nullptr ? static_cast(source->bytecode) : nullptr; + if (source == nullptr || source->kind != unofficial_napi_js_source_bytecode || + source->text != nullptr || source->bytecode == nullptr) { + return nullptr; + } + return reinterpret_cast(source->bytecode); } bool ReadParamsArray(napi_env env, napi_value params_or_undefined, std::vector* out) { @@ -1521,18 +1527,6 @@ v8::ScriptOrigin BytecodeOrigin(v8::Isolate* isolate, HostDefinedOptions(isolate, host_id_symbol)); } -v8::Local DefaultCjsHostSymbol(napi_env env) { - v8::Local host_id_symbol; - napi_value host_id_value = GetVmDynamicImportDefaultInternalSymbol(env); - if (host_id_value != nullptr) { - v8::Local host_raw = napi_v8_unwrap_value(host_id_value); - if (!host_raw.IsEmpty() && host_raw->IsSymbol()) { - host_id_symbol = host_raw.As(); - } - } - return host_id_symbol; -} - // Compiles record->source_utf8 per record->shape and stores the live artifact. // consume == true offers record->bytes as a code cache (rejected_out gets // V8's verdict); consume == false compiles eagerly and fills record->bytes @@ -1715,21 +1709,35 @@ napi_status NAPI_CDECL unofficial_napi_contextify_make_context( v8::Local sandbox_object; v8::MaybeLocal maybe_global_object; if (!vanilla) { - v8::Local sandbox_value = napi_v8_unwrap_value(sandbox_or_symbol); - if (sandbox_value.IsEmpty() || !sandbox_value->IsObject()) return napi_invalid_arg; + v8::Local sandbox_value = + napi_v8_unwrap_value(sandbox_or_symbol); + if (sandbox_value.IsEmpty() || !sandbox_value->IsObject()) { + return napi_invalid_arg; + } sandbox_object = sandbox_value.As(); - if (!SnapshotOwnProperties(isolate, current, sandbox_object, &saved_properties)) { + + // Reading a proxy's own keys or values may run JavaScript and throw. Keep + // the exception boundary local to that operation: successful context + // creation must retain V8's normal, uncaught lifecycle. + v8::TryCatch try_catch(isolate); + if (!SnapshotOwnProperties( + isolate, current, sandbox_object, &saved_properties)) { + if (try_catch.HasCaught() && !try_catch.HasTerminated()) { + napi_v8_set_last_exception( + env, try_catch.Exception(), try_catch.Message()); + } return napi_pending_exception; } maybe_global_object = sandbox_value; } - v8::Local context = v8::Context::New(isolate, - nullptr, - v8::MaybeLocal(), - maybe_global_object, - v8::DeserializeInternalFieldsCallback(), - queue); + v8::Local context = + v8::Context::New(isolate, + nullptr, + v8::MaybeLocal(), + maybe_global_object, + v8::DeserializeInternalFieldsCallback(), + queue); if (context.IsEmpty()) { return napi_pending_exception; @@ -1744,7 +1752,8 @@ napi_status NAPI_CDECL unofficial_napi_contextify_make_context( key_object = context->Global(); } else { key_object = sandbox_object; - if (!RestoreOwnProperties(isolate, context, key_object, saved_properties)) { + if (!RestoreOwnProperties( + isolate, context, key_object, saved_properties)) { return napi_pending_exception; } } @@ -1774,7 +1783,7 @@ napi_status NAPI_CDECL unofficial_napi_contextify_make_context( return napi_ok; } -napi_status NAPI_CDECL unofficial_napi_bytecode_compile( +static napi_status BytecodeCompile( napi_env env, napi_value source_text, napi_value filename, @@ -1783,7 +1792,7 @@ napi_status NAPI_CDECL unofficial_napi_bytecode_compile( napi_value host_defined_option_id, int32_t line_offset, int32_t column_offset, - void** bytecode_out, + unofficial_napi_bytecode* bytecode_out, bool* can_parse_as_module_out) { if (env == nullptr || source_text == nullptr || filename == nullptr || bytecode_out == nullptr) { return napi_invalid_arg; @@ -1836,11 +1845,11 @@ napi_status NAPI_CDECL unofficial_napi_bytecode_compile( return napi_generic_failure; } - *bytecode_out = record.release(); + *bytecode_out = reinterpret_cast(record.release()); return napi_ok; } -napi_status NAPI_CDECL unofficial_napi_bytecode_deserialize( +static napi_status BytecodeDeserialize( napi_env env, const uint8_t* bytes, size_t byte_length, @@ -1849,7 +1858,7 @@ napi_status NAPI_CDECL unofficial_napi_bytecode_deserialize( int32_t shape, napi_value params_or_undefined, napi_value host_defined_option_id, - void** bytecode_out, + unofficial_napi_bytecode* bytecode_out, bool* rejected_out) { if (env == nullptr || bytes == nullptr || byte_length == 0 || source_text == nullptr || filename == nullptr || bytecode_out == nullptr) { @@ -1890,16 +1899,70 @@ napi_status NAPI_CDECL unofficial_napi_bytecode_deserialize( return napi_ok; } - *bytecode_out = record.release(); + *bytecode_out = reinterpret_cast(record.release()); return napi_ok; } +napi_status NAPI_CDECL unofficial_napi_bytecode_open( + napi_env env, + const unofficial_napi_bytecode_open_options* options, + unofficial_napi_bytecode_open_result* result) { + if (env == nullptr || options == nullptr || result == nullptr || + options->size < sizeof(*options) || + options->version != UNOFFICIAL_NAPI_BYTECODE_OPEN_OPTIONS_VERSION || + options->source_text == nullptr || options->filename == nullptr || + (options->cache_policy != unofficial_napi_bytecode_cache_compile_on_reject && + options->cache_policy != unofficial_napi_bytecode_cache_validate_only)) { + return napi_invalid_arg; + } + *result = {}; + + if (options->has_cache != 0 && options->cache_bytes != nullptr && + options->cache_byte_length != 0) { + bool rejected = false; + const napi_status status = BytecodeDeserialize(env, + options->cache_bytes, + options->cache_byte_length, + options->source_text, + options->filename, + options->shape, + options->params_or_undefined, + options->host_defined_option_id, + &result->bytecode, + &rejected); + result->cache_rejected = rejected ? 1 : 0; + if (status != napi_ok) return status; + if (result->bytecode != nullptr) return napi_ok; + result->cache_rejected = 1; + } else if (options->has_cache != 0) { + result->cache_rejected = 1; + } + + if (result->cache_rejected != 0 && + options->cache_policy == unofficial_napi_bytecode_cache_validate_only) { + return napi_ok; + } + bool can_parse_as_module = false; + const napi_status status = BytecodeCompile(env, + options->source_text, + options->filename, + options->shape, + options->params_or_undefined, + options->host_defined_option_id, + options->line_offset, + options->column_offset, + &result->bytecode, + &can_parse_as_module); + result->can_parse_as_module = can_parse_as_module ? 1 : 0; + return status; +} + napi_status NAPI_CDECL unofficial_napi_bytecode_serialize( napi_env env, - void* bytecode, + unofficial_napi_bytecode bytecode, napi_value* buffer_out) { if (env == nullptr || bytecode == nullptr || buffer_out == nullptr) return napi_invalid_arg; - auto* record = static_cast(bytecode); + auto* record = reinterpret_cast(bytecode); v8::Isolate* isolate = env->isolate; v8::EscapableHandleScope handle_scope(isolate); v8::Local context = env->context(); @@ -1916,9 +1979,10 @@ napi_status NAPI_CDECL unofficial_napi_bytecode_serialize( return *buffer_out != nullptr ? napi_ok : napi_generic_failure; } -napi_status NAPI_CDECL unofficial_napi_bytecode_release(napi_env env, void* bytecode) { +napi_status NAPI_CDECL unofficial_napi_bytecode_release( + napi_env env, unofficial_napi_bytecode bytecode) { if (env == nullptr || bytecode == nullptr) return napi_invalid_arg; - delete static_cast(bytecode); + delete reinterpret_cast(bytecode); return napi_ok; } @@ -1935,8 +1999,7 @@ napi_status NAPI_CDECL unofficial_napi_contextify_run_script( bool break_on_first_line, napi_value host_defined_option_id, napi_value* result_out) { - if (env == nullptr || source == nullptr || - (source->text == nullptr && source->bytecode == nullptr) || filename == nullptr || + if (env == nullptr || !unofficial_napi_js_source_is_valid(source) || filename == nullptr || result_out == nullptr) { return napi_invalid_arg; } @@ -2078,41 +2141,6 @@ napi_status NAPI_CDECL unofficial_napi_contextify_run_script( return *result_out == nullptr ? napi_generic_failure : napi_ok; } -napi_status NAPI_CDECL unofficial_napi_contextify_dispose_context( - napi_env env, - napi_value sandbox_or_context_global) { - if (env == nullptr || sandbox_or_context_global == nullptr) return napi_invalid_arg; - - std::lock_guard lock(g_context_mu); - auto it = g_context_records.find(env); - if (it == g_context_records.end()) return napi_ok; - - auto& records = it->second; - for (size_t i = 0; i < records.size(); ++i) { - ContextRecord& rec = records[i]; - if (rec.key_ref == nullptr) continue; - napi_value candidate = nullptr; - if (napi_get_reference_value(env, rec.key_ref, &candidate) != napi_ok || candidate == nullptr) continue; - bool same = false; - if (napi_strict_equals(env, candidate, sandbox_or_context_global, &same) != napi_ok || !same) continue; - if (env->context_token_unassign_callback != nullptr) { - env->context_token_unassign_callback( - env, rec.key_ref, env->context_token_callback_data); - } - napi_delete_reference(env, rec.key_ref); - rec.key_ref = nullptr; - rec.context.Reset(); - rec.own_microtask_queue.reset(); - records.erase(records.begin() + static_cast(i)); - break; - } - - if (records.empty()) { - g_context_records.erase(it); - } - return napi_ok; -} - napi_status NAPI_CDECL unofficial_napi_contextify_compile_function( napi_env env, const unofficial_napi_js_source* source, @@ -2124,8 +2152,7 @@ napi_status NAPI_CDECL unofficial_napi_contextify_compile_function( napi_value params_or_undefined, napi_value host_defined_option_id, napi_value* result_out) { - if (env == nullptr || source == nullptr || - (source->text == nullptr && source->bytecode == nullptr) || filename == nullptr || + if (env == nullptr || !unofficial_napi_js_source_is_valid(source) || filename == nullptr || result_out == nullptr) { return napi_invalid_arg; } @@ -2306,127 +2333,6 @@ napi_status NAPI_CDECL unofficial_napi_contextify_compile_function( return *result_out == nullptr ? napi_generic_failure : napi_ok; } -napi_status NAPI_CDECL unofficial_napi_contextify_compile_function_for_cjs_loader( - napi_env env, - const unofficial_napi_js_source* source, - napi_value filename, - bool is_sea_main, - bool should_detect_module, - napi_value* result_out) { - if (env == nullptr || source == nullptr || - (source->text == nullptr && source->bytecode == nullptr) || filename == nullptr || - result_out == nullptr) { - return napi_invalid_arg; - } - (void)is_sea_main; - - v8::Isolate* isolate = env->isolate; - v8::EscapableHandleScope handle_scope(isolate); - v8::Local context = env->context(); - v8::Context::Scope context_scope(context); - - BytecodeRecord* bytecode_record = BytecodeRecordFromSource(source); - if (bytecode_record != nullptr && - bytecode_record->shape != unofficial_napi_bytecode_shape_cjs_function) { - return napi_invalid_arg; - } - - v8::Local code_str = - bytecode_record != nullptr - ? v8::String::NewFromUtf8(isolate, bytecode_record->source_utf8.c_str(), - v8::NewStringType::kNormal, - static_cast(bytecode_record->source_utf8.size())) - .ToLocalChecked() - : ToV8String(env, source->text, ""); - v8::Local filename_str = ToV8String(env, filename, "[eval]"); - v8::Local host_id_symbol = DefaultCjsHostSymbol(env); - - v8::Local fn; - v8::Local cjs_exception; - v8::Local cjs_message; - bool cjs_ok = false; - - if (bytecode_record != nullptr) { - // Fast path: bytecode_compile/deserialize built the artifact in this env - // context with the same loader host symbol — reuse it directly. - if (!bytecode_record->function.IsEmpty() && - bytecode_record->function_context.Get(isolate) == context) { - fn = bytecode_record->function.Get(isolate); - cjs_ok = true; - } else if (!bytecode_record->bytes.empty()) { - v8::TryCatch tc(isolate); - auto* cached_data = new v8::ScriptCompiler::CachedData( - bytecode_record->bytes.data(), static_cast(bytecode_record->bytes.size()), - v8::ScriptCompiler::CachedData::BufferNotOwned); - v8::ScriptOrigin origin = BytecodeOrigin(isolate, filename_str, *bytecode_record, host_id_symbol); - v8::ScriptCompiler::Source source_obj(code_str, origin, cached_data); - std::vector> params = ToV8Params(isolate, bytecode_record->params); - cjs_ok = v8::ScriptCompiler::CompileFunction(context, - &source_obj, - params.size(), - params.empty() ? nullptr : params.data(), - 0, - nullptr, - v8::ScriptCompiler::kConsumeCodeCache, - v8::ScriptCompiler::NoCacheReason::kNoCacheNoReason) - .ToLocal(&fn); - // A failure here is unexpected (the retained source is valid); fall - // through to a plain text compile below. - } - } - - if (!cjs_ok) { - v8::TryCatch tc(isolate); - cjs_ok = CompileCjsFunction(context, code_str, filename_str, true, host_id_symbol).ToLocal(&fn); - if (!cjs_ok && tc.HasCaught()) { - cjs_exception = tc.Exception(); - cjs_message = tc.Message(); - } - } - - bool can_parse_as_esm = false; - if (!cjs_ok) { - if (!cjs_message.IsEmpty()) { - can_parse_as_esm = - ShouldRetryAsEsm(isolate, context, env, cjs_message->Get(), code_str, filename_str); - } - if (!can_parse_as_esm || !should_detect_module) { - if (!cjs_exception.IsEmpty()) { - unofficial_napi_internal::AttachSyntaxArrowMessage(isolate, context, cjs_exception, cjs_message); - isolate->ThrowException(cjs_exception); - } - return cjs_exception.IsEmpty() ? napi_generic_failure : napi_pending_exception; - } - } - - v8::Local out = v8::Object::New(isolate); - if (!SetNamed(context, out, "cachedDataRejected", v8::Boolean::New(isolate, false)) || - !SetNamed(context, out, "canParseAsESM", v8::Boolean::New(isolate, can_parse_as_esm))) { - return napi_generic_failure; - } - - if (cjs_ok) { - if (!host_id_symbol.IsEmpty()) { - SetApiPrivate(context, fn.As(), "node:host_defined_option_symbol", host_id_symbol.As()); - } - v8::ScriptOrigin origin = fn->GetScriptOrigin(); - if (!SetNamed(context, out, "sourceMapURL", origin.SourceMapUrl()) || - !SetNamed(context, out, "sourceURL", origin.ResourceName()) || - !SetNamed(context, out, "function", fn)) { - return napi_generic_failure; - } - } else { - if (!SetNamed(context, out, "sourceMapURL", v8::Undefined(isolate)) || - !SetNamed(context, out, "sourceURL", v8::Undefined(isolate)) || - !SetNamed(context, out, "function", v8::Undefined(isolate))) { - return napi_generic_failure; - } - } - - *result_out = napi_v8_wrap_value(env, handle_scope.Escape(out)); - return *result_out == nullptr ? napi_generic_failure : napi_ok; -} - napi_status NAPI_CDECL unofficial_napi_contextify_contains_module_syntax( napi_env env, napi_value code, @@ -2464,33 +2370,12 @@ napi_status NAPI_CDECL unofficial_napi_contextify_contains_module_syntax( return napi_ok; } -napi_status NAPI_CDECL unofficial_napi_contextify_start_sigint_watchdog( +static napi_status MaterializeModuleRequests( napi_env env, - bool* result_out) { - if (env == nullptr || result_out == nullptr) return napi_invalid_arg; - std::lock_guard action_lock(SigintWatchdogHelper::GetActionMutex()); - *result_out = SigintWatchdogHelper::GetInstance().Start() == 0; - return napi_ok; -} - -napi_status NAPI_CDECL unofficial_napi_contextify_stop_sigint_watchdog( - napi_env env, - bool* had_pending_signal_out) { - if (env == nullptr || had_pending_signal_out == nullptr) return napi_invalid_arg; - std::lock_guard action_lock(SigintWatchdogHelper::GetActionMutex()); - *had_pending_signal_out = SigintWatchdogHelper::GetInstance().Stop(); - return napi_ok; -} + unofficial_napi_module module, + napi_value* result_out); -napi_status NAPI_CDECL unofficial_napi_contextify_watchdog_has_pending_sigint( - napi_env env, - bool* result_out) { - if (env == nullptr || result_out == nullptr) return napi_invalid_arg; - *result_out = SigintWatchdogHelper::GetInstance().HasPendingSignal(); - return napi_ok; -} - -napi_status NAPI_CDECL unofficial_napi_module_wrap_create_source_text( +static napi_status CreateSourceTextModule( napi_env env, napi_value wrapper, napi_value url, @@ -2499,12 +2384,13 @@ napi_status NAPI_CDECL unofficial_napi_module_wrap_create_source_text( int32_t line_offset, int32_t column_offset, napi_value host_defined_option_id, - void** handle_out) { - if (env == nullptr || wrapper == nullptr || url == nullptr || source == nullptr || - (source->text == nullptr && source->bytecode == nullptr) || handle_out == nullptr) { + unofficial_napi_module* module_out) { + if (env == nullptr || wrapper == nullptr || url == nullptr || + !unofficial_napi_js_source_is_valid(source) || + module_out == nullptr) { return napi_invalid_arg; } - *handle_out = nullptr; + *module_out = nullptr; EnsureModuleWrapCleanupHook(env); v8::Isolate* isolate = env->isolate; @@ -2621,23 +2507,23 @@ napi_status NAPI_CDECL unofficial_napi_module_wrap_create_source_text( std::lock_guard lock(g_module_wrap_mu); GetModuleWrapState(env)->modules.push_back(record); } - *handle_out = record; + *module_out = ModuleHandle(record); return napi_ok; } -napi_status NAPI_CDECL unofficial_napi_module_wrap_create_synthetic( +static napi_status CreateSyntheticModule( napi_env env, napi_value wrapper, napi_value url, napi_value context_or_undefined, napi_value export_names, napi_value synthetic_eval_steps, - void** handle_out) { + unofficial_napi_module* module_out) { if (env == nullptr || wrapper == nullptr || url == nullptr || export_names == nullptr || synthetic_eval_steps == nullptr || - handle_out == nullptr) { + module_out == nullptr) { return napi_invalid_arg; } - *handle_out = nullptr; + *module_out = nullptr; bool is_array = false; if (napi_is_array(env, export_names, &is_array) != napi_ok || !is_array) return napi_invalid_arg; @@ -2680,31 +2566,85 @@ napi_status NAPI_CDECL unofficial_napi_module_wrap_create_synthetic( std::lock_guard lock(g_module_wrap_mu); GetModuleWrapState(env)->modules.push_back(record); } - *handle_out = record; + *module_out = ModuleHandle(record); return napi_ok; } -napi_status NAPI_CDECL unofficial_napi_module_wrap_destroy(napi_env env, void* handle) { - if (env == nullptr || handle == nullptr) return napi_invalid_arg; +napi_status NAPI_CDECL unofficial_napi_module_wrap_create( + napi_env env, + const unofficial_napi_module_create_options* options, + unofficial_napi_module_create_result* result_out) { + if (options == nullptr || options->size < sizeof(*options) || + options->version != UNOFFICIAL_NAPI_MODULE_CREATE_OPTIONS_VERSION || + result_out == nullptr) { + return napi_invalid_arg; + } + *result_out = {}; + napi_status status = napi_invalid_arg; + switch (options->kind) { + case unofficial_napi_module_source_text: + status = CreateSourceTextModule( + env, + options->wrapper, + options->url, + options->context_or_undefined, + options->payload.source_text.source, + options->payload.source_text.line_offset, + options->payload.source_text.column_offset, + options->payload.source_text.host_defined_option_id, + &result_out->module); + break; + case unofficial_napi_module_synthetic: + status = CreateSyntheticModule( + env, + options->wrapper, + options->url, + options->context_or_undefined, + options->payload.synthetic.export_names, + options->payload.synthetic.synthetic_evaluation_steps, + &result_out->module); + break; + default: + return napi_invalid_arg; + } + if (status != napi_ok) return status; + + status = MaterializeModuleRequests(env, result_out->module, + &result_out->module_requests); + if (status != napi_ok) { + DestroyModuleRecord(ModuleRecord(result_out->module)); + *result_out = {}; + return status; + } + result_out->has_top_level_await = + ModuleRecord(result_out->module)->module.Get(env->isolate)->HasTopLevelAwait(); + return napi_ok; +} + +napi_status NAPI_CDECL unofficial_napi_module_wrap_destroy( + napi_env env, + unofficial_napi_module module) { + if (env == nullptr || module == nullptr) return napi_invalid_arg; + ModuleWrapRecord* record = ModuleRecord(module); { std::lock_guard lock(g_module_wrap_mu); auto it = g_module_wrap_states.find(env); if (it == g_module_wrap_states.end()) return napi_ok; auto& modules = it->second.modules; - if (std::find(modules.begin(), modules.end(), static_cast(handle)) == modules.end()) { + if (std::find(modules.begin(), modules.end(), record) == modules.end()) { return napi_ok; } } - DestroyModuleRecord(static_cast(handle)); + DestroyModuleRecord(record); return napi_ok; } -napi_status NAPI_CDECL unofficial_napi_module_wrap_get_module_requests( +static napi_status MaterializeModuleRequests( napi_env env, - void* handle, + unofficial_napi_module module, napi_value* result_out) { - if (env == nullptr || handle == nullptr || result_out == nullptr) return napi_invalid_arg; - ModuleWrapRecord* record = static_cast(handle); + if (env == nullptr || module == nullptr || result_out == nullptr) return napi_invalid_arg; + ModuleWrapRecord* record = ModuleRecord(module); v8::Isolate* isolate = env->isolate; v8::EscapableHandleScope handle_scope(isolate); v8::Local context = env->context(); @@ -2745,11 +2685,11 @@ napi_status NAPI_CDECL unofficial_napi_module_wrap_get_module_requests( napi_status NAPI_CDECL unofficial_napi_module_wrap_link( napi_env env, - void* handle, + unofficial_napi_module module, size_t count, - void* const* linked_handles) { - if (env == nullptr || handle == nullptr) return napi_invalid_arg; - ModuleWrapRecord* record = static_cast(handle); + const unofficial_napi_module* linked_modules) { + if (env == nullptr || module == nullptr) return napi_invalid_arg; + ModuleWrapRecord* record = ModuleRecord(module); if (count != record->module_requests.size()) { ThrowCodeError(env, "ERR_VM_MODULE_LINK_FAILURE", "linked modules array length mismatch"); return napi_pending_exception; @@ -2757,7 +2697,8 @@ napi_status NAPI_CDECL unofficial_napi_module_wrap_link( record->linked_requests.assign(count, nullptr); for (size_t i = 0; i < count; ++i) { - ModuleWrapRecord* linked = linked_handles != nullptr ? static_cast(linked_handles[i]) : nullptr; + ModuleWrapRecord* linked = + linked_modules != nullptr ? ModuleRecord(linked_modules[i]) : nullptr; if (linked == nullptr) { ThrowCodeError(env, "ERR_VM_MODULE_LINK_FAILURE", "linked module missing"); return napi_pending_exception; @@ -2776,9 +2717,11 @@ napi_status NAPI_CDECL unofficial_napi_module_wrap_link( return napi_ok; } -napi_status NAPI_CDECL unofficial_napi_module_wrap_instantiate(napi_env env, void* handle) { - if (env == nullptr || handle == nullptr) return napi_invalid_arg; - ModuleWrapRecord* record = static_cast(handle); +napi_status NAPI_CDECL unofficial_napi_module_wrap_instantiate( + napi_env env, + unofficial_napi_module module_handle) { + if (env == nullptr || module_handle == nullptr) return napi_invalid_arg; + ModuleWrapRecord* record = ModuleRecord(module_handle); v8::Isolate* isolate = env->isolate; v8::HandleScope handle_scope(isolate); v8::Local context = record->context.Get(isolate); @@ -2798,12 +2741,12 @@ napi_status NAPI_CDECL unofficial_napi_module_wrap_instantiate(napi_env env, voi napi_status NAPI_CDECL unofficial_napi_module_wrap_evaluate( napi_env env, - void* handle, + unofficial_napi_module module_handle, int64_t /*timeout*/, bool /*break_on_sigint*/, napi_value* result_out) { - if (env == nullptr || handle == nullptr || result_out == nullptr) return napi_invalid_arg; - ModuleWrapRecord* record = static_cast(handle); + if (env == nullptr || module_handle == nullptr || result_out == nullptr) return napi_invalid_arg; + ModuleWrapRecord* record = ModuleRecord(module_handle); v8::Isolate* isolate = env->isolate; v8::EscapableHandleScope handle_scope(isolate); v8::Local context = record->context.Get(isolate); @@ -2824,14 +2767,14 @@ napi_status NAPI_CDECL unofficial_napi_module_wrap_evaluate( napi_status NAPI_CDECL unofficial_napi_module_wrap_evaluate_sync( napi_env env, - void* handle, + unofficial_napi_module module_handle, napi_value filename, napi_value parent_filename, napi_value* result_out) { - if (env == nullptr || handle == nullptr || result_out == nullptr) return napi_invalid_arg; + if (env == nullptr || module_handle == nullptr || result_out == nullptr) return napi_invalid_arg; *result_out = nullptr; - ModuleWrapRecord* record = static_cast(handle); + ModuleWrapRecord* record = ModuleRecord(module_handle); v8::Isolate* isolate = env->isolate; v8::EscapableHandleScope handle_scope(isolate); v8::Local context = record->context.Get(isolate); @@ -2889,10 +2832,10 @@ napi_status NAPI_CDECL unofficial_napi_module_wrap_evaluate_sync( napi_status NAPI_CDECL unofficial_napi_module_wrap_get_namespace( napi_env env, - void* handle, + unofficial_napi_module module_handle, napi_value* result_out) { - if (env == nullptr || handle == nullptr || result_out == nullptr) return napi_invalid_arg; - ModuleWrapRecord* record = static_cast(handle); + if (env == nullptr || module_handle == nullptr || result_out == nullptr) return napi_invalid_arg; + ModuleWrapRecord* record = ModuleRecord(module_handle); v8::Isolate* isolate = env->isolate; v8::EscapableHandleScope handle_scope(isolate); v8::Local context = record->context.Get(isolate); @@ -2906,69 +2849,50 @@ napi_status NAPI_CDECL unofficial_napi_module_wrap_get_namespace( return napi_ok; } -napi_status NAPI_CDECL unofficial_napi_module_wrap_get_status( - napi_env env, - void* handle, - int32_t* status_out) { - if (env == nullptr || handle == nullptr || status_out == nullptr) return napi_invalid_arg; - ModuleWrapRecord* record = static_cast(handle); - *status_out = static_cast(record->module.Get(env->isolate)->GetStatus()); - return napi_ok; -} - -napi_status NAPI_CDECL unofficial_napi_module_wrap_get_error( +napi_status NAPI_CDECL unofficial_napi_module_wrap_get_state( napi_env env, - void* handle, - napi_value* result_out) { - if (env == nullptr || handle == nullptr || result_out == nullptr) return napi_invalid_arg; - ModuleWrapRecord* record = static_cast(handle); + unofficial_napi_module module_handle, + int32_t* status_out, + napi_value* error_out, + bool* has_async_graph_out) { + if (env == nullptr || module_handle == nullptr || + (status_out == nullptr && error_out == nullptr && + has_async_graph_out == nullptr)) { + return napi_invalid_arg; + } + ModuleWrapRecord* record = ModuleRecord(module_handle); v8::Isolate* isolate = env->isolate; - v8::EscapableHandleScope handle_scope(isolate); - *result_out = napi_v8_wrap_value(env, handle_scope.Escape(record->module.Get(isolate)->GetException())); - return napi_ok; -} - -napi_status NAPI_CDECL unofficial_napi_module_wrap_has_top_level_await( - napi_env env, - void* handle, - bool* result_out) { - if (env == nullptr || handle == nullptr || result_out == nullptr) return napi_invalid_arg; - ModuleWrapRecord* record = static_cast(handle); - *result_out = record->module.Get(env->isolate)->HasTopLevelAwait(); - return napi_ok; -} - -napi_status NAPI_CDECL unofficial_napi_module_wrap_has_async_graph( - napi_env env, - void* handle, - bool* result_out) { - if (env == nullptr || handle == nullptr || result_out == nullptr) return napi_invalid_arg; - ModuleWrapRecord* record = static_cast(handle); v8::Local module = record->module.Get(env->isolate); - if (module->GetStatus() < v8::Module::Status::kInstantiated) { - ThrowCodeError(env, "ERR_MODULE_NOT_INSTANTIATED", "Module is not instantiated"); - return napi_pending_exception; + const v8::Module::Status status = module->GetStatus(); + if (status_out != nullptr) { + *status_out = static_cast(status); + } + if (has_async_graph_out != nullptr) { + *has_async_graph_out = + status >= v8::Module::Status::kInstantiated && module->IsGraphAsync(); + } + if (error_out != nullptr) { + v8::Local error = + status == v8::Module::Status::kErrored + ? module->GetException() + : v8::Undefined(isolate).As(); + *error_out = napi_v8_wrap_value(env, error); + if (*error_out == nullptr) return napi_generic_failure; } - *result_out = module->IsGraphAsync(); return napi_ok; } napi_status NAPI_CDECL unofficial_napi_module_wrap_check_unsettled_top_level_await( napi_env env, - napi_value module_wrap, + unofficial_napi_module module_handle, bool warnings, bool* settled_out) { - if (env == nullptr || settled_out == nullptr) return napi_invalid_arg; - *settled_out = true; - if (module_wrap == nullptr) return napi_ok; - - napi_valuetype type = napi_undefined; - if (napi_typeof(env, module_wrap, &type) != napi_ok || type != napi_object) { - return napi_ok; + if (env == nullptr || module_handle == nullptr || settled_out == nullptr) { + return napi_invalid_arg; } - - ModuleWrapRecord* record = FindModuleRecordForWrapper(env, module_wrap); - if (record == nullptr) return napi_ok; + *settled_out = true; + ModuleWrapRecord* record = FindModuleRecordForHandle(env, module_handle); + if (record == nullptr) return napi_invalid_arg; v8::Isolate* isolate = env->isolate; v8::HandleScope handle_scope(isolate); @@ -2994,11 +2918,11 @@ napi_status NAPI_CDECL unofficial_napi_module_wrap_check_unsettled_top_level_awa napi_status NAPI_CDECL unofficial_napi_module_wrap_set_export( napi_env env, - void* handle, + unofficial_napi_module module, napi_value export_name, napi_value export_value) { - if (env == nullptr || handle == nullptr || export_name == nullptr) return napi_invalid_arg; - ModuleWrapRecord* record = static_cast(handle); + if (env == nullptr || module == nullptr || export_name == nullptr) return napi_invalid_arg; + ModuleWrapRecord* record = ModuleRecord(module); v8::Local name = napi_v8_unwrap_value(export_name); v8::Local value = export_value != nullptr ? napi_v8_unwrap_value(export_value) : v8::Undefined(env->isolate).As(); @@ -3011,10 +2935,10 @@ napi_status NAPI_CDECL unofficial_napi_module_wrap_set_export( napi_status NAPI_CDECL unofficial_napi_module_wrap_set_module_source_object( napi_env env, - void* handle, + unofficial_napi_module module, napi_value source_object) { - if (env == nullptr || handle == nullptr) return napi_invalid_arg; - ModuleWrapRecord* record = static_cast(handle); + if (env == nullptr || module == nullptr) return napi_invalid_arg; + ModuleWrapRecord* record = ModuleRecord(module); ResetRef(env, &record->source_object_ref); if (source_object != nullptr) { napi_create_reference(env, source_object, 1, &record->source_object_ref); @@ -3024,20 +2948,20 @@ napi_status NAPI_CDECL unofficial_napi_module_wrap_set_module_source_object( napi_status NAPI_CDECL unofficial_napi_module_wrap_get_module_source_object( napi_env env, - void* handle, + unofficial_napi_module module, napi_value* result_out) { - if (env == nullptr || handle == nullptr || result_out == nullptr) return napi_invalid_arg; - ModuleWrapRecord* record = static_cast(handle); + if (env == nullptr || module == nullptr || result_out == nullptr) return napi_invalid_arg; + ModuleWrapRecord* record = ModuleRecord(module); *result_out = GetRefValue(env, record->source_object_ref); return napi_ok; } napi_status NAPI_CDECL unofficial_napi_module_wrap_create_cached_data( napi_env env, - void* handle, + unofficial_napi_module module_handle, napi_value* result_out) { - if (env == nullptr || handle == nullptr || result_out == nullptr) return napi_invalid_arg; - ModuleWrapRecord* record = static_cast(handle); + if (env == nullptr || module_handle == nullptr || result_out == nullptr) return napi_invalid_arg; + ModuleWrapRecord* record = ModuleRecord(module_handle); v8::Isolate* isolate = env->isolate; v8::EscapableHandleScope handle_scope(isolate); v8::Local module = record->module.Get(isolate); @@ -3057,69 +2981,49 @@ napi_status NAPI_CDECL unofficial_napi_module_wrap_create_cached_data( return *result_out != nullptr ? napi_ok : napi_generic_failure; } -napi_status NAPI_CDECL unofficial_napi_module_wrap_set_import_module_dynamically_callback( +napi_status NAPI_CDECL unofficial_napi_module_wrap_set_hooks( napi_env env, - napi_value callback) { - if (env == nullptr) return napi_invalid_arg; + const unofficial_napi_module_hooks* hooks) { + if (env == nullptr || hooks == nullptr || hooks->size < sizeof(*hooks) || + hooks->version != UNOFFICIAL_NAPI_MODULE_HOOKS_VERSION) { + return napi_invalid_arg; + } + + napi_ref import_ref = nullptr; + napi_ref import_meta_ref = nullptr; + if (hooks->import_module_dynamically != nullptr) { + napi_status status = napi_create_reference( + env, hooks->import_module_dynamically, 1, &import_ref); + if (status != napi_ok) return status; + } + if (hooks->initialize_import_meta_object != nullptr) { + napi_status status = napi_create_reference( + env, hooks->initialize_import_meta_object, 1, &import_meta_ref); + if (status != napi_ok) { + ResetRef(env, &import_ref); + return status; + } + } + EnsureModuleWrapCleanupHook(env); std::lock_guard lock(g_module_wrap_mu); auto* state = GetModuleWrapState(env); ResetRef(env, &state->import_module_dynamically_ref); - if (callback != nullptr) napi_create_reference(env, callback, 1, &state->import_module_dynamically_ref); + ResetRef(env, &state->initialize_import_meta_ref); + state->import_module_dynamically_ref = import_ref; + state->initialize_import_meta_ref = import_meta_ref; env->isolate->SetHostImportModuleDynamicallyCallback(ImportModuleDynamically); env->isolate->SetHostImportModuleWithPhaseDynamicallyCallback(ImportModuleDynamicallyWithPhase); - return napi_ok; -} - -napi_status NAPI_CDECL unofficial_napi_module_wrap_set_initialize_import_meta_object_callback( - napi_env env, - napi_value callback) { - if (env == nullptr) return napi_invalid_arg; - EnsureModuleWrapCleanupHook(env); - std::lock_guard lock(g_module_wrap_mu); - auto* state = GetModuleWrapState(env); - ResetRef(env, &state->initialize_import_meta_ref); - if (callback != nullptr) napi_create_reference(env, callback, 1, &state->initialize_import_meta_ref); env->isolate->SetHostInitializeImportMetaObjectCallback(HostInitializeImportMetaObject); return napi_ok; } -napi_status NAPI_CDECL unofficial_napi_module_wrap_import_module_dynamically( - napi_env env, - size_t argc, - napi_value* argv, - napi_value* result_out) { - if (env == nullptr || argv == nullptr || result_out == nullptr) return napi_invalid_arg; - *result_out = nullptr; - - auto* state = GetModuleWrapState(env); - if (state == nullptr) return napi_generic_failure; - napi_value callback = GetRefValue(env, state->import_module_dynamically_ref); - if (callback == nullptr) return napi_invalid_arg; - - napi_value global = nullptr; - napi_get_global(env, &global); - if (argc >= 5) { - return napi_call_function(env, global, callback, 5, argv, result_out); - } - - napi_value referrer_symbol = GetVmDynamicImportDefaultInternalSymbol(env); - napi_value phase = nullptr; - napi_create_int32(env, 2, &phase); - std::vector> empty_names; - std::vector> empty_values; - napi_value attrs = napi_v8_wrap_value(env, CreateFrozenNullProtoObject(env, empty_names, empty_values)); - napi_value referrer_name = argc >= 2 ? argv[1] : nullptr; - napi_value call_argv[5] = {referrer_symbol, argv[0], phase, attrs, referrer_name}; - return napi_call_function(env, global, callback, 5, call_argv, result_out); -} - napi_status NAPI_CDECL unofficial_napi_module_wrap_create_required_module_facade( napi_env env, - void* handle, + unofficial_napi_module module, napi_value* result_out) { - if (env == nullptr || handle == nullptr || result_out == nullptr) return napi_invalid_arg; - ModuleWrapRecord* record = static_cast(handle); + if (env == nullptr || module == nullptr || result_out == nullptr) return napi_invalid_arg; + ModuleWrapRecord* record = ModuleRecord(module); v8::Isolate* isolate = env->isolate; v8::EscapableHandleScope handle_scope(isolate); v8::Local context = env->context(); diff --git a/v8/src/unofficial_napi_error_utils.cc b/v8/src/unofficial_napi_error_utils.cc index 365dd3d..360997a 100644 --- a/v8/src/unofficial_napi_error_utils.cc +++ b/v8/src/unofficial_napi_error_utils.cc @@ -209,6 +209,25 @@ bool TakePreservedErrorFormattingImpl(napi_env env, return true; } +napi_status WrapUtf8String(napi_env env, + const std::string& value, + napi_value* out) { + if (out == nullptr) return napi_invalid_arg; + *out = nullptr; + if (value.empty()) return napi_ok; + + v8::Local string; + if (!v8::String::NewFromUtf8(env->isolate, + value.c_str(), + v8::NewStringType::kNormal, + static_cast(value.size())) + .ToLocal(&string)) { + return napi_generic_failure; + } + *out = napi_v8_wrap_value(env, string); + return *out == nullptr ? napi_generic_failure : napi_ok; +} + } // namespace namespace unofficial_napi_internal { @@ -346,14 +365,9 @@ void SetArrowMessage(v8::Isolate* isolate, isolate, context, exception, BuildSyntaxArrowMessage(isolate, context, message)); } -napi_status SetSourceMapsEnabled(napi_env env, bool enabled) { - if (env == nullptr || env->isolate == nullptr) return napi_invalid_arg; - std::lock_guard lock(g_error_formatting_mu); - g_error_formatting_states[env].source_maps_enabled = enabled; - return napi_ok; -} - -napi_status SetGetSourceMapErrorSourceCallback(napi_env env, napi_value callback) { +napi_status ConfigureSourceMaps(napi_env env, + bool enabled, + napi_value callback) { if (env == nullptr || env->isolate == nullptr) return napi_invalid_arg; v8::Local raw = @@ -362,6 +376,7 @@ napi_status SetGetSourceMapErrorSourceCallback(napi_env env, napi_value callback std::lock_guard lock(g_error_formatting_mu); ErrorFormattingState& state = g_error_formatting_states[env]; + state.source_maps_enabled = enabled; state.get_source_map_error_source.Reset(); if (!raw.IsEmpty()) { state.get_source_map_error_source.Reset(env->isolate, raw.As()); @@ -393,56 +408,72 @@ napi_status PreserveErrorSourceMessage(napi_env env, napi_value error) { return napi_ok; } -napi_status TakePreservedErrorFormatting(napi_env env, - napi_value error, - napi_value* source_line_out, - napi_value* thrown_at_out) { - if (env == nullptr || env->isolate == nullptr || error == nullptr) { +napi_status GetErrorMetadata(napi_env env, + napi_value error, + unofficial_napi_error_metadata_mode mode, + unofficial_napi_error_metadata* out) { + if (env == nullptr || env->isolate == nullptr || error == nullptr || + out == nullptr) { return napi_invalid_arg; } - - if (source_line_out != nullptr) *source_line_out = nullptr; - if (thrown_at_out != nullptr) *thrown_at_out = nullptr; + *out = {}; v8::Isolate* isolate = env->isolate; v8::Local raw = napi_v8_unwrap_value(error); if (raw.IsEmpty()) return napi_invalid_arg; - std::string preserved_source_line; - std::string preserved_thrown_at; - if (!TakePreservedErrorFormattingImpl( - env, raw, &preserved_source_line, &preserved_thrown_at)) { - return napi_ok; + if (mode == unofficial_napi_error_metadata_take_preserved) { + std::string source_line; + std::string thrown_at; + if (!TakePreservedErrorFormattingImpl(env, raw, &source_line, &thrown_at)) { + return napi_ok; + } + + napi_status status = WrapUtf8String(env, source_line, &out->stderr_line); + if (status != napi_ok) return status; + return WrapUtf8String(env, thrown_at, &out->thrown_at); + } + if (mode != unofficial_napi_error_metadata_current && + mode != unofficial_napi_error_metadata_positions_only && + mode != unofficial_napi_error_metadata_thrown_at_only) { + return napi_invalid_arg; } - if (source_line_out != nullptr && !preserved_source_line.empty()) { - v8::Local source_line_value; - if (!v8::String::NewFromUtf8( - isolate, - preserved_source_line.c_str(), - v8::NewStringType::kNormal, - static_cast(preserved_source_line.size())) - .ToLocal(&source_line_value)) { - return napi_generic_failure; + v8::Local context = env->context(); + v8::Local message = v8::Exception::CreateMessage(isolate, raw); + if (message.IsEmpty()) return napi_generic_failure; + + if (mode != unofficial_napi_error_metadata_thrown_at_only) { + v8::Local source_line; + if (message->GetSourceLine(context).ToLocal(&source_line)) { + out->source_line = napi_v8_wrap_value(env, source_line); + if (out->source_line == nullptr) return napi_generic_failure; } - *source_line_out = napi_v8_wrap_value(env, source_line_value); - if (*source_line_out == nullptr) return napi_generic_failure; - } - if (thrown_at_out != nullptr && !preserved_thrown_at.empty()) { - v8::Local thrown_at_value; - if (!v8::String::NewFromUtf8( - isolate, - preserved_thrown_at.c_str(), - v8::NewStringType::kNormal, - static_cast(preserved_thrown_at.size())) - .ToLocal(&thrown_at_value)) { - return napi_generic_failure; + v8::Local resource_name = message->GetScriptOrigin().ResourceName(); + if (!resource_name.IsEmpty()) { + out->script_resource_name = napi_v8_wrap_value(env, resource_name); + if (out->script_resource_name == nullptr) return napi_generic_failure; } - *thrown_at_out = napi_v8_wrap_value(env, thrown_at_value); - if (*thrown_at_out == nullptr) return napi_generic_failure; + + out->line_number = message->GetLineNumber(context).FromMaybe(0); + out->start_column = message->GetStartColumn(context).FromMaybe(0); + out->end_column = + message->GetEndColumn(context).FromMaybe(out->start_column + 1); + + if (mode == unofficial_napi_error_metadata_positions_only) return napi_ok; + + napi_status status = WrapUtf8String( + env, GetErrorSourceLineForStderrImpl(env, message), &out->stderr_line); + if (status != napi_ok) return status; } + v8::Local stack = message->GetStackTrace(); + if (!stack.IsEmpty() && stack->GetFrameCount() > 0) { + napi_status status = WrapUtf8String( + env, "Thrown at:\n" + FormatStackTrace(isolate, stack), &out->thrown_at); + if (status != napi_ok) return status; + } return napi_ok; } @@ -456,109 +487,4 @@ void ResetErrorFormattingState(napi_env env) { g_error_formatting_states.erase(it); } -napi_status GetErrorSourcePositions(napi_env env, - napi_value error, - unofficial_napi_error_source_positions* out) { - if (env == nullptr || env->isolate == nullptr || error == nullptr || out == nullptr) { - return napi_invalid_arg; - } - - out->source_line = nullptr; - out->script_resource_name = nullptr; - out->line_number = 0; - out->start_column = 0; - out->end_column = 0; - - v8::Isolate* isolate = env->isolate; - v8::Local context = env->context(); - v8::Local raw = napi_v8_unwrap_value(error); - if (raw.IsEmpty() || !raw->IsObject()) return napi_invalid_arg; - - v8::Local msg = GetMessageFromError(env, error); - if (msg.IsEmpty()) return napi_generic_failure; - - v8::Local source_line; - if (!msg->GetSourceLine(context).ToLocal(&source_line)) { - return napi_generic_failure; - } - - int line_number = 0; - if (!msg->GetLineNumber(context).To(&line_number)) { - return napi_generic_failure; - } - - out->source_line = napi_v8_wrap_value(env, source_line); - if (out->source_line == nullptr) return napi_generic_failure; - - v8::Local resource_name = msg->GetScriptOrigin().ResourceName(); - out->script_resource_name = napi_v8_wrap_value(env, resource_name); - if (out->script_resource_name == nullptr) return napi_generic_failure; - - out->line_number = line_number; - out->start_column = msg->GetStartColumn(context).FromMaybe(0); - out->end_column = msg->GetEndColumn(context).FromMaybe(out->start_column + 1); - return napi_ok; -} - -napi_status GetErrorSourceLineForStderr(napi_env env, - napi_value error, - napi_value* result_out) { - if (env == nullptr || env->isolate == nullptr || error == nullptr || - result_out == nullptr) { - return napi_invalid_arg; - } - - *result_out = nullptr; - v8::Isolate* isolate = env->isolate; - v8::EscapableHandleScope scope(isolate); - v8::Local message = GetMessageFromError(env, error); - if (message.IsEmpty()) return napi_generic_failure; - - const std::string formatted = GetErrorSourceLineForStderrImpl(env, message); - if (formatted.empty()) return napi_ok; - - v8::Local out; - if (!v8::String::NewFromUtf8( - isolate, - formatted.c_str(), - v8::NewStringType::kNormal, - static_cast(formatted.size())) - .ToLocal(&out)) { - return napi_generic_failure; - } - *result_out = napi_v8_wrap_value(env, scope.Escape(out)); - return *result_out == nullptr ? napi_generic_failure : napi_ok; -} - -napi_status GetErrorThrownAt(napi_env env, - napi_value error, - napi_value* result_out) { - if (env == nullptr || env->isolate == nullptr || error == nullptr || - result_out == nullptr) { - return napi_invalid_arg; - } - - *result_out = nullptr; - v8::Isolate* isolate = env->isolate; - v8::EscapableHandleScope scope(isolate); - v8::Local message = GetMessageFromError(env, error); - if (message.IsEmpty()) return napi_generic_failure; - - v8::Local stack = message->GetStackTrace(); - if (stack.IsEmpty() || stack->GetFrameCount() == 0) return napi_ok; - - const std::string thrown_at = "Thrown at:\n" + FormatStackTrace(isolate, stack); - v8::Local out; - if (!v8::String::NewFromUtf8( - isolate, - thrown_at.c_str(), - v8::NewStringType::kNormal, - static_cast(thrown_at.size())) - .ToLocal(&out)) { - return napi_generic_failure; - } - *result_out = napi_v8_wrap_value(env, scope.Escape(out)); - return *result_out == nullptr ? napi_generic_failure : napi_ok; -} - } // namespace unofficial_napi_internal diff --git a/v8/src/unofficial_napi_error_utils.h b/v8/src/unofficial_napi_error_utils.h index 40cb2f3..212a053 100644 --- a/v8/src/unofficial_napi_error_utils.h +++ b/v8/src/unofficial_napi_error_utils.h @@ -35,25 +35,14 @@ void PreserveErrorFormatting(napi_env env, const std::string& source_line, const std::string& thrown_at); -napi_status SetSourceMapsEnabled(napi_env env, bool enabled); -napi_status SetGetSourceMapErrorSourceCallback(napi_env env, napi_value callback); +napi_status ConfigureSourceMaps(napi_env env, bool enabled, napi_value callback); napi_status PreserveErrorSourceMessage(napi_env env, napi_value error); -napi_status GetErrorSourceLineForStderr(napi_env env, - napi_value error, - napi_value* result_out); -napi_status GetErrorThrownAt(napi_env env, +napi_status GetErrorMetadata(napi_env env, napi_value error, - napi_value* result_out); -napi_status TakePreservedErrorFormatting(napi_env env, - napi_value error, - napi_value* source_line_out, - napi_value* thrown_at_out); + unofficial_napi_error_metadata_mode mode, + unofficial_napi_error_metadata* out); void ResetErrorFormattingState(napi_env env); -napi_status GetErrorSourcePositions(napi_env env, - napi_value error, - unofficial_napi_error_source_positions* out); - } // namespace unofficial_napi_internal #endif // NAPI_V8_UNOFFICIAL_NAPI_ERROR_UTILS_H_