diff --git a/.github/workflows/napi-native-runtime.yml b/.github/workflows/napi-native-runtime.yml new file mode 100644 index 00000000..5cdab99b --- /dev/null +++ b/.github/workflows/napi-native-runtime.yml @@ -0,0 +1,93 @@ +name: napi-native-runtime + +on: + push: + branches: + - main + pull_request: + workflow_dispatch: + +permissions: + contents: read + +jobs: + rust-native-runtime: + name: Rust native runtime regressions + runs-on: ubuntu-24.04 + timeout-minutes: 45 + env: + CC: clang + CXX: clang++ + CARGO_BUILD_JOBS: "2" + CARGO_TARGET_DIR: ${{ github.workspace }}/target + CARGO_PROFILE_DEV_DEBUG: "0" + CARGO_PROFILE_TEST_DEBUG: "0" + CARGO_INCREMENTAL: "0" + NAPI_WASMER_FORCE_STANDALONE: "1" + RUST_BACKTRACE: "1" + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install Rust + uses: dtolnay/rust-toolchain@1.97.1 + with: + components: rustfmt, clippy + + - name: Install native build dependencies + run: | + sudo apt-get update + sudo apt-get install --yes clang pkg-config libssl-dev + + - name: Cache Rust dependencies and native V8 archive + uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-napi-runtime-rust-1.97.1-${{ hashFiles('Cargo.standalone.toml', 'Cargo.standalone.lock', 'build.rs', '.github/workflows/napi-native-runtime.yml') }} + restore-keys: | + ${{ runner.os }}-napi-runtime-rust-1.97.1- + + - name: Check Rust formatting + run: ./cargo-standalone.sh +1.97.1 fmt --all --check + + - name: Lint the native provider + run: ./cargo-standalone.sh +1.97.1 clippy --quiet --locked --lib + + - name: Test native Rust runtime and accounting + shell: bash + run: | + set -euo pipefail + test_targets=() + # Each split PR adds its own suites. Exercise those already present + # without requiring a later PR's test target to exist yet. + for suite in \ + core_callbacks external_transfer guest_boundary_accounting \ + guest_error_callbacks message_memory review_memory \ + v8_heap_limit v8_page_accounting v8_wasm_metering \ + v8_wasm_process_budget v8_wasm_tiering; do + if [[ -f "tests/${suite}.rs" ]]; then + test_targets+=(--test "$suite") + fi + done + # Unit tests exercise lane admission, stop races and teardown. + # The integration suites use WAT or native FFI, so the immutable + # public SDK in Cargo.standalone.lock needs no private credentials + # or WASIX C compiler. CLI guest integration still needs the + # wrapper-preserving SDK used by Edge and is a separate check. + # Serialize test functions to bound heap-bomb RSS on hosted runners; + # the concurrency regressions create their own competing workers. + ./cargo-standalone.sh +1.97.1 test --quiet --locked \ + --lib "${test_targets[@]}" -- --test-threads=1 \ + 2>&1 | tee "${RUNNER_TEMP}/napi-runtime-tests.log" + + - name: Upload runtime failure output + if: failure() + uses: actions/upload-artifact@v4 + with: + name: napi-native-runtime-failure + path: ${{ runner.temp }}/napi-runtime-tests.log + if-no-files-found: ignore diff --git a/src/cli.rs b/src/cli.rs index 26e2ca48..5f185545 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -277,7 +277,10 @@ fn run_wasix_main_with_runner( ) { Ok(()) => 0, Err(err) => { - if let Some(WasiError::Exit(code)) = err.downcast_ref::() { + if let Some(WasiError::Exit(code)) = err + .chain() + .find_map(|cause| cause.downcast_ref::()) + { i32::from(*code) } else { return Err(err).context("failed to run WASIX module through WasiRunner"); diff --git a/src/env.rs b/src/env.rs index b25e9092..1dc3fed5 100644 --- a/src/env.rs +++ b/src/env.rs @@ -449,6 +449,16 @@ impl NapiEnv { } } + pub(crate) fn in_callback(&self) -> bool { + self.callback_depth != 0 + } + + pub(crate) fn scope_env(&self, scope_id: u32) -> Option<(u32, SnapiEnv)> { + let env_id = *self.napi_scopes.get(&scope_id)?; + let env = *self.napi_envs.get(&env_id)?; + Some((env_id, env as SnapiEnv)) + } + pub(crate) fn begin_unregister_napi_scope(&mut self, scope_id: u32) -> Option<(u32, SnapiEnv)> { let env_id = self.napi_scopes.remove(&scope_id)?; if self.default_napi_env_id == Some(env_id) { diff --git a/src/guest/napi.rs b/src/guest/napi.rs index 8e982dbb..ca819ed2 100644 --- a/src/guest/napi.rs +++ b/src/guest/napi.rs @@ -8,6 +8,7 @@ use std::ffi::{CString, c_char}; use wasmer::{AsStoreMut, Function, FunctionEnv, FunctionEnvMut, Imports, namespace}; use wasmer_wasix::WasiError; +use wasmer_wasix::wasmer_wasix_types::wasi::ExitCode; use crate::{ NAPI_EXTENSION_WASMER_MODULE_NAME, NAPI_MODULE_NAME, NapiEnv, RequestedHeap, @@ -199,12 +200,12 @@ fn guest_unofficial_napi_configure_runtime( let Some(flags) = abi::read_runtime_options(&mut env, options_ptr) else { return 1; }; - let Ok(flags) = CString::new(flags) else { + // V8 flags are process-wide. A guest must not change flags for other + // tenants or make the first caller decide the host's security policy. + if !flags.is_empty() { return 1; - }; - unsafe { - snapi_bridge_unofficial_configure_runtime(flags.as_ptr(), flags.as_bytes().len() as u32) } + unsafe { snapi_bridge_unofficial_configure_runtime(std::ptr::null(), 0) } } fn guest_unofficial_napi_create_env( @@ -281,17 +282,25 @@ fn guest_unofficial_napi_release_env( scope_ptr: u32, loop_ptr: u32, ) -> Result { - let scope_id = scope_ptr; - let Some((guest_env, snapi_env_state)) = env.data_mut().begin_unregister_napi_scope(scope_id) - else { + // Disposing an isolate while its JavaScript callback is entered aborts V8. + if env.data().in_callback() { + return Ok(1); + } + let scope_id = if scope_ptr != 0 { scope_ptr } else { 0 }; + let Some((guest_env, snapi_env_state)) = env.data().scope_env(scope_id) else { return Ok(1); }; let loop_id = loop_ptr; let status = with_cb_context(&mut env, guest_env, || unsafe { snapi_bridge_unofficial_release_env_with_loop(snapi_env_state, loop_id) }); - env.data_mut() - .finish_unregister_napi_env(guest_env, snapi_env_state); + if status.as_ref().is_ok_and(|code| *code == 0) + && let Some((guest_env, snapi_env_state)) = + env.data_mut().begin_unregister_napi_scope(scope_id) + { + env.data_mut() + .finish_unregister_napi_env(guest_env, snapi_env_state); + } status } @@ -357,10 +366,8 @@ fn guest_unofficial_napi_create_uninitialized_arraybuffer( length: u32, _zero_fill: i32, result_ptr: u32, -) -> i32 { - // Imported providers cannot adopt a guest allocation as host engine - // storage. Allocate directly in the provider without requesting a guest - // pointer; engines which mandate zero initialization may return zeros. +) -> Result { + // Engines which mandate zero initialization may return zeroed storage. guest_napi_create_arraybuffer(env, napi_env, length, 0, result_ptr) } @@ -1833,13 +1840,16 @@ fn guest_napi_create_error( code: u32, msg: u32, rp: u32, -) -> i32 { +) -> Result { let mut out: u32 = 0; - let s = unsafe { snapi_bridge_create_error(snapi_env(&env, e), code, msg, &mut out) }; + let snapi = snapi_env(&env, e); + let s = with_cb_context(&mut env, e, || unsafe { + snapi_bridge_create_error(snapi, code, msg, &mut out) + })?; if s == 0 { write_guest_u32(&mut env, rp, out); } - s + Ok(s) } fn guest_napi_create_type_error( @@ -1848,13 +1858,16 @@ fn guest_napi_create_type_error( code: u32, msg: u32, rp: u32, -) -> i32 { +) -> Result { let mut out: u32 = 0; - let s = unsafe { snapi_bridge_create_type_error(snapi_env(&env, e), code, msg, &mut out) }; + let snapi = snapi_env(&env, e); + let s = with_cb_context(&mut env, e, || unsafe { + snapi_bridge_create_type_error(snapi, code, msg, &mut out) + })?; if s == 0 { write_guest_u32(&mut env, rp, out); } - s + Ok(s) } fn guest_napi_create_range_error( @@ -1863,13 +1876,16 @@ fn guest_napi_create_range_error( code: u32, msg: u32, rp: u32, -) -> i32 { +) -> Result { let mut out: u32 = 0; - let s = unsafe { snapi_bridge_create_range_error(snapi_env(&env, e), code, msg, &mut out) }; + let snapi = snapi_env(&env, e); + let s = with_cb_context(&mut env, e, || unsafe { + snapi_bridge_create_range_error(snapi, code, msg, &mut out) + })?; if s == 0 { write_guest_u32(&mut env, rp, out); } - s + Ok(s) } fn guest_napi_create_bigint_int64( @@ -2157,13 +2173,16 @@ fn guest_napi_instanceof( obj: u32, ctor: u32, rp: u32, -) -> i32 { +) -> Result { let mut r: i32 = 0; - let s = unsafe { snapi_bridge_instanceof(snapi_env(&env, e), obj, ctor, &mut r) }; + let snapi = snapi_env(&env, e); + let s = with_cb_context(&mut env, e, || unsafe { + snapi_bridge_instanceof(snapi, obj, ctor, &mut r) + })?; if s == 0 { write_guest_u8(&mut env, rp, r as u8); } - s + Ok(s) } // --- Coercion --- @@ -2181,10 +2200,31 @@ macro_rules! guest_coerce { }; } +macro_rules! guest_coerce_with_callbacks { + ($name:ident, $bridge:ident) => { + fn $name( + mut env: FunctionEnvMut, + e: u32, + vh: u32, + rp: u32, + ) -> Result { + let mut out: u32 = 0; + let snapi = snapi_env(&env, e); + let status = with_cb_context(&mut env, e, || unsafe { + $bridge(snapi, vh as u32, &mut out) + })?; + if status == 0 { + write_guest_u32(&mut env, rp, out); + } + Ok(status) + } + }; +} + guest_coerce!(guest_napi_coerce_to_bool, snapi_bridge_coerce_to_bool); -guest_coerce!(guest_napi_coerce_to_number, snapi_bridge_coerce_to_number); -guest_coerce!(guest_napi_coerce_to_string, snapi_bridge_coerce_to_string); -guest_coerce!(guest_napi_coerce_to_object, snapi_bridge_coerce_to_object); +guest_coerce_with_callbacks!(guest_napi_coerce_to_number, snapi_bridge_coerce_to_number); +guest_coerce_with_callbacks!(guest_napi_coerce_to_string, snapi_bridge_coerce_to_string); +guest_coerce_with_callbacks!(guest_napi_coerce_to_object, snapi_bridge_coerce_to_object); // --- Object operations --- @@ -2458,12 +2498,26 @@ fn guest_napi_get_prototype(mut env: FunctionEnvMut, e: u32, o: u32, rp s } -fn guest_napi_object_freeze(env: FunctionEnvMut, e: u32, o: u32) -> i32 { - unsafe { snapi_bridge_object_freeze(snapi_env(&env, e), o) } +fn guest_napi_object_freeze( + mut env: FunctionEnvMut, + e: u32, + o: u32, +) -> Result { + let snapi = snapi_env(&env, e); + with_cb_context(&mut env, e, || unsafe { + snapi_bridge_object_freeze(snapi, o) + }) } -fn guest_napi_object_seal(env: FunctionEnvMut, e: u32, o: u32) -> i32 { - unsafe { snapi_bridge_object_seal(snapi_env(&env, e), o) } +fn guest_napi_object_seal( + mut env: FunctionEnvMut, + e: u32, + o: u32, +) -> Result { + let snapi = snapi_env(&env, e); + with_cb_context(&mut env, e, || unsafe { + snapi_bridge_object_seal(snapi, o) + }) } // --- Comparison --- @@ -2494,24 +2548,25 @@ fn guest_napi_throw_error( e: u32, code_ptr: u32, msg_ptr: u32, -) -> i32 { +) -> Result { + let snapi = snapi_env(&env, e); let code_bytes = if code_ptr != 0 { read_guest_c_string(&mut env, code_ptr) } else { None }; let Some(msg_bytes) = read_guest_c_string(&mut env, msg_ptr) else { - return 1; + return Ok(1); }; let c_code = code_bytes.map(|b| CString::new(b).unwrap_or_default()); let c_msg = CString::new(msg_bytes).unwrap_or_default(); - unsafe { + with_cb_context(&mut env, e, || unsafe { snapi_bridge_throw_error( - snapi_env(&env, e), + snapi, c_code.as_ref().map_or(std::ptr::null(), |c| c.as_ptr()), c_msg.as_ptr(), ) - } + }) } fn guest_napi_throw_type_error( @@ -2519,24 +2574,25 @@ fn guest_napi_throw_type_error( e: u32, code_ptr: u32, msg_ptr: u32, -) -> i32 { +) -> Result { + let snapi = snapi_env(&env, e); let code_bytes = if code_ptr != 0 { read_guest_c_string(&mut env, code_ptr) } else { None }; let Some(msg_bytes) = read_guest_c_string(&mut env, msg_ptr) else { - return 1; + return Ok(1); }; let c_code = code_bytes.map(|b| CString::new(b).unwrap_or_default()); let c_msg = CString::new(msg_bytes).unwrap_or_default(); - unsafe { + with_cb_context(&mut env, e, || unsafe { snapi_bridge_throw_type_error( - snapi_env(&env, e), + snapi, c_code.as_ref().map_or(std::ptr::null(), |c| c.as_ptr()), c_msg.as_ptr(), ) - } + }) } fn guest_napi_throw_range_error( @@ -2544,24 +2600,25 @@ fn guest_napi_throw_range_error( e: u32, code_ptr: u32, msg_ptr: u32, -) -> i32 { +) -> Result { + let snapi = snapi_env(&env, e); let code_bytes = if code_ptr != 0 { read_guest_c_string(&mut env, code_ptr) } else { None }; let Some(msg_bytes) = read_guest_c_string(&mut env, msg_ptr) else { - return 1; + return Ok(1); }; let c_code = code_bytes.map(|b| CString::new(b).unwrap_or_default()); let c_msg = CString::new(msg_bytes).unwrap_or_default(); - unsafe { + with_cb_context(&mut env, e, || unsafe { snapi_bridge_throw_range_error( - snapi_env(&env, e), + snapi, c_code.as_ref().map_or(std::ptr::null(), |c| c.as_ptr()), c_msg.as_ptr(), ) - } + }) } fn guest_napi_is_exception_pending(mut env: FunctionEnvMut, e: u32, rp: u32) -> i32 { @@ -2577,36 +2634,61 @@ fn guest_napi_get_and_clear_last_exception( mut env: FunctionEnvMut, e: u32, rp: u32, -) -> i32 { +) -> Result { let mut out: u32 = 0; - let s = unsafe { snapi_bridge_get_and_clear_last_exception(snapi_env(&env, e), &mut out) }; + let snapi = snapi_env(&env, e); + let s = with_cb_context(&mut env, e, || unsafe { + snapi_bridge_get_and_clear_last_exception(snapi, &mut out) + })?; if s == 0 { write_guest_u32(&mut env, rp, out); } - s + Ok(s) } // --- Promise --- -fn guest_napi_create_promise(mut env: FunctionEnvMut, e: u32, dp: u32, rp: u32) -> i32 { +fn guest_napi_create_promise( + mut env: FunctionEnvMut, + e: u32, + dp: u32, + rp: u32, +) -> Result { let mut deferred_id: u32 = 0; let mut promise_id: u32 = 0; - let s = unsafe { - snapi_bridge_create_promise(snapi_env(&env, e), &mut deferred_id, &mut promise_id) - }; + let snapi = snapi_env(&env, e); + let s = with_cb_context(&mut env, e, || unsafe { + snapi_bridge_create_promise(snapi, &mut deferred_id, &mut promise_id) + })?; if s == 0 { write_guest_u32(&mut env, dp, deferred_id); write_guest_u32(&mut env, rp, promise_id); } - s + Ok(s) } -fn guest_napi_resolve_deferred(env: FunctionEnvMut, e: u32, d: u32, v: u32) -> i32 { - unsafe { snapi_bridge_resolve_deferred(snapi_env(&env, e), d, v) } +fn guest_napi_resolve_deferred( + mut env: FunctionEnvMut, + e: u32, + d: u32, + v: u32, +) -> Result { + let snapi = snapi_env(&env, e); + with_cb_context(&mut env, e, || unsafe { + snapi_bridge_resolve_deferred(snapi, d, v) + }) } -fn guest_napi_reject_deferred(env: FunctionEnvMut, e: u32, d: u32, v: u32) -> i32 { - unsafe { snapi_bridge_reject_deferred(snapi_env(&env, e), d, v) } +fn guest_napi_reject_deferred( + mut env: FunctionEnvMut, + e: u32, + d: u32, + v: u32, +) -> Result { + let snapi = snapi_env(&env, e); + with_cb_context(&mut env, e, || unsafe { + snapi_bridge_reject_deferred(snapi, d, v) + }) } // --- ArrayBuffer --- @@ -2617,11 +2699,20 @@ fn guest_napi_create_arraybuffer( byte_length: u32, data_ptr: u32, rp: u32, -) -> i32 { +) -> Result { // Guest-memory-backed ArrayBuffer via the host-side heap (WASIX path). + let Ok(length) = usize::try_from(byte_length) else { + return Ok(1); + }; + if rp == 0 + || !guest_range_is_valid(&mut env, rp, 4) + || (data_ptr != 0 && !guest_range_is_valid(&mut env, data_ptr, 4)) + { + return Ok(1); + } if let Some(heap) = env.data().guest_heap.clone() { - let Some(guest_ptr) = alloc_guest(&mut env, &heap, byte_length as usize, true) else { - return 9; // napi_generic_failure: memory maximum or budget exhausted + let Some(guest_ptr) = alloc_guest(&mut env, &heap, length, true) else { + return Ok(9); // napi_generic_failure: memory maximum or budget exhausted }; let host_addr = heap.offset_to_host(guest_ptr) as u64; @@ -2630,15 +2721,30 @@ fn guest_napi_create_arraybuffer( let hint = heap.make_finalize_ctx(guest_ptr); let mut out: u32 = 0; let mut backing_store_token: u64 = 0; - let s = unsafe { - snapi_bridge_create_external_arraybuffer_finalized( - snapi_env(&env, e), + let snapi = snapi_env(&env, e); + let mut bridge_status = 9; + let mut ownership_transferred = 0; + let result = with_cb_context(&mut env, e, || unsafe { + bridge_status = snapi_bridge_create_external_arraybuffer_finalized( + snapi, host_addr, - byte_length, + length as u32, hint, &mut backing_store_token, &mut out, - ) + &mut ownership_transferred, + ); + bridge_status + }); + let s = match result { + Ok(status) => status, + Err(error) => { + if ownership_transferred == 0 { + crate::guest_heap::GuestHeap::reclaim_finalize_ctx(hint); + heap.free_offset(guest_ptr); + } + return Err(error); + } }; if s == 0 { write_guest_u32(&mut env, rp, out); @@ -2646,19 +2752,23 @@ fn guest_napi_create_arraybuffer( write_guest_u32(&mut env, data_ptr, guest_ptr); } } else { - crate::guest_heap::GuestHeap::reclaim_finalize_ctx(hint); - heap.free_offset(guest_ptr); + if ownership_transferred == 0 { + crate::guest_heap::GuestHeap::reclaim_finalize_ctx(hint); + heap.free_offset(guest_ptr); + } } - s + Ok(s) } else { // Fallback: host-memory-backed arraybuffer (non-WASIX path) let mut out: u32 = 0; - let s = - unsafe { snapi_bridge_create_arraybuffer(snapi_env(&env, e), byte_length, &mut out) }; + let snapi = snapi_env(&env, e); + let s = with_cb_context(&mut env, e, || unsafe { + snapi_bridge_create_arraybuffer(snapi, length as u32, &mut out) + })?; if s == 0 { write_guest_u32(&mut env, rp, out); } - s + Ok(s) } } @@ -2670,10 +2780,19 @@ fn guest_napi_create_external_arraybuffer( finalize_cb: u32, finalize_hint: u32, rp: u32, -) -> i32 { +) -> Result { + let Ok(length) = usize::try_from(byte_length) else { + return Ok(1); + }; + if !guest_range_is_valid(&mut env, external_data, length) { + return Ok(1); + } + if rp == 0 || !guest_range_is_valid(&mut env, rp, 4) { + return Ok(1); + } let memory = env.data().memory.clone(); let Some(memory) = memory else { - return 1; + return Ok(1); }; let host_addr: u64 = { @@ -2688,35 +2807,38 @@ fn guest_napi_create_external_arraybuffer( // When the guest supplies a finalize callback (its own allocation to free), // route through the guest-finalized variant so the callback is re-run on // the deferred drain. Otherwise the plain path (no finalizer). - let s = if finalize_cb != 0 { - unsafe { - snapi_bridge_create_external_arraybuffer_guest_finalized( - snapi_env(&env, e), - host_addr, - byte_length, - e, - finalize_cb, - external_data, - finalize_hint, - &mut backing_store_token, - &mut out, - ) - } - } else { - unsafe { - snapi_bridge_create_external_arraybuffer( - snapi_env(&env, e), - host_addr, - byte_length, - &mut backing_store_token, - &mut out, - ) + let snapi = snapi_env(&env, e); + let s = with_cb_context(&mut env, e, || { + if finalize_cb != 0 { + unsafe { + snapi_bridge_create_external_arraybuffer_guest_finalized( + snapi, + host_addr, + length as u32, + e, + finalize_cb, + external_data, + finalize_hint, + &mut backing_store_token, + &mut out, + ) + } + } else { + unsafe { + snapi_bridge_create_external_arraybuffer( + snapi, + host_addr, + length as u32, + &mut backing_store_token, + &mut out, + ) + } } - }; + })?; if s == 0 { write_guest_u32(&mut env, rp, out); } - s + Ok(s) } // NOTE: napi_create_external_buffer takes (env, length, data, ...) — unlike @@ -2729,10 +2851,19 @@ fn guest_napi_create_external_buffer( finalize_cb: u32, finalize_hint: u32, rp: u32, -) -> i32 { +) -> Result { + let Ok(length) = usize::try_from(byte_length) else { + return Ok(1); + }; + if !guest_range_is_valid(&mut env, external_data, length) { + return Ok(1); + } + if rp == 0 || !guest_range_is_valid(&mut env, rp, 4) { + return Ok(1); + } let memory = env.data().memory.clone(); let Some(memory) = memory else { - return 1; + return Ok(1); }; let host_addr: u64 = { @@ -2744,35 +2875,38 @@ fn guest_napi_create_external_buffer( let mut out: u32 = 0; let mut backing_store_token: u64 = 0; - let s = if finalize_cb != 0 { - unsafe { - snapi_bridge_create_external_buffer_guest_finalized( - snapi_env(&env, e), - host_addr, - byte_length, - e, - finalize_cb, - external_data, - finalize_hint, - &mut backing_store_token, - &mut out, - ) - } - } else { - unsafe { - snapi_bridge_create_external_buffer( - snapi_env(&env, e), - host_addr, - byte_length, - &mut backing_store_token, - &mut out, - ) + let snapi = snapi_env(&env, e); + let s = with_cb_context(&mut env, e, || { + if finalize_cb != 0 { + unsafe { + snapi_bridge_create_external_buffer_guest_finalized( + snapi, + host_addr, + length as u32, + e, + finalize_cb, + external_data, + finalize_hint, + &mut backing_store_token, + &mut out, + ) + } + } else { + unsafe { + snapi_bridge_create_external_buffer( + snapi, + host_addr, + length as u32, + &mut backing_store_token, + &mut out, + ) + } } - }; + })?; if s == 0 { write_guest_u32(&mut env, rp, out); } - s + Ok(s) } fn guest_napi_get_arraybuffer_info( @@ -2849,39 +2983,61 @@ fn guest_node_api_create_sharedarraybuffer( byte_length: u32, data_ptr: u32, rp: u32, -) -> i32 { +) -> Result { + let Ok(length) = usize::try_from(byte_length) else { + return Ok(1); + }; + if rp == 0 + || !guest_range_is_valid(&mut env, rp, 4) + || (data_ptr != 0 + && (!guest_range_is_valid(&mut env, data_ptr, 4) || env.data().guest_heap.is_none())) + { + return Ok(1); + } let mut host_data_addr = 0u64; let mut out = 0u32; - let s = unsafe { - snapi_bridge_create_sharedarraybuffer( - snapi_env(&env, e), - byte_length, - &mut host_data_addr, - &mut out, - ) - }; + let snapi = snapi_env(&env, e); + let s = with_cb_context(&mut env, e, || unsafe { + snapi_bridge_create_sharedarraybuffer(snapi, byte_length, &mut host_data_addr, &mut out) + })?; if s != 0 { - return s; + return Ok(s); } if data_ptr != 0 { - write_guest_u32(&mut env, data_ptr, host_data_addr as u32); + // The isolate's ArrayBuffer allocator puts shared backing stores in + // the guest heap. Check the entire range before exposing its offset; + // a raw host pointer is never a valid wasm32 address. + let guest_ptr = if length == 0 { + 0 + } else { + let Some(guest_ptr) = host_ptr_to_guest_ptr(&mut env, host_data_addr) else { + return Ok(9); + }; + if !guest_range_is_valid(&mut env, guest_ptr, length) { + return Ok(9); + } + guest_ptr + }; + write_guest_u32(&mut env, data_ptr, guest_ptr); } + write_guest_u32(&mut env, rp, out); - s + Ok(0) } fn guest_node_api_set_prototype( - env: FunctionEnvMut, + mut env: FunctionEnvMut, napi_env: u32, object: u32, prototype: u32, -) -> i32 { - let object_id = object; - let prototype_id = prototype; - unsafe { - snapi_bridge_node_api_set_prototype(snapi_env(&env, napi_env), object_id, prototype_id) - } +) -> Result { + let object_id = if object != 0 { object } else { 0 }; + let prototype_id = if prototype != 0 { prototype } else { 0 }; + let snapi = snapi_env(&env, napi_env); + with_cb_context(&mut env, napi_env, || unsafe { + snapi_bridge_node_api_set_prototype(snapi, object_id, prototype_id) + }) } // --- TypedArray --- @@ -2894,15 +3050,16 @@ fn guest_napi_create_typedarray( ab: u32, offset: u32, rp: u32, -) -> i32 { +) -> Result { let mut out: u32 = 0; - let s = unsafe { - snapi_bridge_create_typedarray(snapi_env(&env, e), typ, length, ab, offset, &mut out) - }; + let snapi = snapi_env(&env, e); + let s = with_cb_context(&mut env, e, || unsafe { + snapi_bridge_create_typedarray(snapi, typ, length, ab, offset, &mut out) + })?; if s == 0 { write_guest_u32(&mut env, rp, out); } - s + Ok(s) } #[allow(clippy::too_many_arguments)] @@ -2971,13 +3128,16 @@ fn guest_napi_create_dataview( ab: u32, bo: u32, rp: u32, -) -> i32 { +) -> Result { let mut out: u32 = 0; - let s = unsafe { snapi_bridge_create_dataview(snapi_env(&env, e), bl, ab, bo, &mut out) }; + let snapi = snapi_env(&env, e); + let s = with_cb_context(&mut env, e, || unsafe { + snapi_bridge_create_dataview(snapi, bl, ab, bo, &mut out) + })?; if s == 0 { write_guest_u32(&mut env, rp, out); } - s + Ok(s) } fn guest_napi_get_dataview_info( @@ -3375,14 +3535,15 @@ fn guest_napi_define_class( prop_count: u32, props_ptr: u32, rp: u32, -) -> i32 { +) -> Result { + let snapi = snapi_env(&env, e); // Read class name let wl = name_len; let name_bytes: Vec = if wl == 0xFFFFFFFFu32 { read_guest_c_string(&mut env, name_ptr).unwrap_or_default() } else if wl > 0 && name_ptr != 0 { let Some(bytes) = read_guest_bytes(&mut env, name_ptr, wl as usize) else { - return 1; + return Ok(1); }; bytes } else { @@ -3390,7 +3551,6 @@ fn guest_napi_define_class( }; // Register the constructor callback - let snapi = snapi_env(&env, e); let ctor_reg_id = unsafe { snapi_bridge_alloc_cb_reg_id(snapi) }; unsafe { snapi_bridge_register_callback(snapi, ctor_reg_id, e, constructor, ctor_data as u64) }; @@ -3400,9 +3560,9 @@ fn guest_napi_define_class( if pc == 0 { // No properties — simple case let mut out: u32 = 0; - let s = unsafe { + let s = with_cb_context(&mut env, e, || unsafe { snapi_bridge_define_class( - snapi_env(&env, e), + snapi, c_name.as_ptr(), wl, ctor_reg_id, @@ -3417,18 +3577,18 @@ fn guest_napi_define_class( std::ptr::null(), &mut out, ) - }; + })?; if s != 0 { - return s; + return Ok(s); } write_guest_u32(&mut env, rp, out); - return 0; + return Ok(0); } // Read property descriptors from guest memory let total_bytes = pc as usize * PROP_DESC_SIZE; let Some(raw) = read_guest_bytes(&mut env, props_ptr, total_bytes) else { - return 1; + return Ok(1); }; let mut prop_names_c: Vec = Vec::with_capacity(pc as usize); @@ -3560,9 +3720,9 @@ fn guest_napi_define_class( } let mut out: u32 = 0; - let s = unsafe { + let s = with_cb_context(&mut env, e, || unsafe { snapi_bridge_define_class( - snapi_env(&env, e), + snapi, c_name.as_ptr(), wl, ctor_reg_id, @@ -3577,12 +3737,12 @@ fn guest_napi_define_class( prop_attributes.as_ptr(), &mut out, ) - }; + })?; if s != 0 { - return s; + return Ok(s); } write_guest_u32(&mut env, rp, out); - 0 + Ok(0) } fn guest_napi_define_properties( @@ -3591,11 +3751,11 @@ fn guest_napi_define_properties( obj: u32, prop_count: u32, props_ptr: u32, -) -> i32 { +) -> Result { let snapi = snapi_env(&env, e); let pc = prop_count; if pc == 0 { - return unsafe { + return with_cb_context(&mut env, e, || unsafe { snapi_bridge_define_properties( snapi, obj, @@ -3609,12 +3769,12 @@ fn guest_napi_define_properties( std::ptr::null(), std::ptr::null(), ) - }; + }); } let total_bytes = pc as usize * PROP_DESC_SIZE; let Some(raw) = read_guest_bytes(&mut env, props_ptr, total_bytes) else { - return 1; + return Ok(1); }; let mut prop_names_c: Vec = Vec::with_capacity(pc as usize); @@ -3737,7 +3897,7 @@ fn guest_napi_define_properties( prop_names_ptrs.push(cn.as_ptr()); } - unsafe { + with_cb_context(&mut env, e, || unsafe { snapi_bridge_define_properties( snapi, obj, @@ -3751,7 +3911,7 @@ fn guest_napi_define_properties( prop_setter_reg_ids.as_ptr(), prop_attributes.as_ptr(), ) - } + }) } // --- Script execution --- @@ -4029,50 +4189,77 @@ fn guest_napi_create_buffer( length: u32, data_ptr: u32, rp: u32, -) -> i32 { +) -> Result { // Buffers must be backed by guest linear memory (same pattern as create_arraybuffer) + let Ok(length_usize) = usize::try_from(length) else { + return Ok(1); + }; + if rp == 0 + || !guest_range_is_valid(&mut env, rp, 4) + || (data_ptr != 0 && !guest_range_is_valid(&mut env, data_ptr, 4)) + { + return Ok(1); + } if let Some(heap) = env.data().guest_heap.clone() { - let Some(guest_ptr) = alloc_guest(&mut env, &heap, length as usize, true) else { - return 9; // napi_generic_failure: memory maximum or budget exhausted + let Some(guest_ptr) = alloc_guest(&mut env, &heap, length_usize, true) else { + return Ok(9); // napi_generic_failure: memory maximum or budget exhausted }; let host_addr = heap.offset_to_host(guest_ptr) as u64; let hint = heap.make_finalize_ctx(guest_ptr); let mut buf_id: u32 = 0; let mut backing_store_token: u64 = 0; - let s = unsafe { - snapi_bridge_create_external_buffer_finalized( - snapi_env(&env, e), + let snapi = snapi_env(&env, e); + let mut bridge_status = 9; + let mut ownership_transferred = 0; + let result = with_cb_context(&mut env, e, || unsafe { + bridge_status = snapi_bridge_create_external_buffer_finalized( + snapi, host_addr, length, hint, &mut backing_store_token, &mut buf_id, - ) + &mut ownership_transferred, + ); + bridge_status + }); + let s = match result { + Ok(status) => status, + Err(error) => { + if ownership_transferred == 0 { + crate::guest_heap::GuestHeap::reclaim_finalize_ctx(hint); + heap.free_offset(guest_ptr); + } + return Err(error); + } }; if s != 0 { - crate::guest_heap::GuestHeap::reclaim_finalize_ctx(hint); - heap.free_offset(guest_ptr); - return s; + if ownership_transferred == 0 { + crate::guest_heap::GuestHeap::reclaim_finalize_ctx(hint); + heap.free_offset(guest_ptr); + } + return Ok(s); } write_guest_u32(&mut env, rp, buf_id); if data_ptr != 0 { write_guest_u32(&mut env, data_ptr, guest_ptr); } - 0 + Ok(0) } else { // Fallback for non-WASIX: use bridge directly let mut host_data: u64 = 0; let mut out: u32 = 0; - let s = unsafe { - snapi_bridge_create_buffer(snapi_env(&env, e), length, &mut host_data, &mut out) - }; + let snapi = snapi_env(&env, e); + let s = with_cb_context(&mut env, e, || unsafe { + snapi_bridge_create_buffer(snapi, length, &mut host_data, &mut out) + })?; if s != 0 { - return s; + return Ok(s); } write_guest_u32(&mut env, rp, out); - 0 + Ok(0) } } @@ -4083,15 +4270,24 @@ fn guest_napi_create_buffer_copy( data_ptr: u32, result_data_ptr: u32, rp: u32, -) -> i32 { +) -> Result { + let Ok(length_usize) = usize::try_from(length) else { + return Ok(1); + }; + if rp == 0 + || !guest_range_is_valid(&mut env, rp, 4) + || (result_data_ptr != 0 && !guest_range_is_valid(&mut env, result_data_ptr, 4)) + { + return Ok(1); + } // Read source data from guest memory first - let Some(src_data) = read_guest_bytes(&mut env, data_ptr, length as usize) else { - return 1; + let Some(src_data) = read_guest_bytes(&mut env, data_ptr, length_usize) else { + return Ok(1); }; if let Some(heap) = env.data().guest_heap.clone() { - let Some(guest_ptr) = alloc_guest(&mut env, &heap, length as usize, false) else { - return 9; // napi_generic_failure: memory maximum or budget exhausted + let Some(guest_ptr) = alloc_guest(&mut env, &heap, length_usize, false) else { + return Ok(9); // napi_generic_failure: memory maximum or budget exhausted }; write_guest_bytes(&mut env, guest_ptr, &src_data); let host_addr = heap.offset_to_host(guest_ptr) as u64; @@ -4099,44 +4295,62 @@ fn guest_napi_create_buffer_copy( let hint = heap.make_finalize_ctx(guest_ptr); let mut buf_id: u32 = 0; let mut backing_store_token: u64 = 0; - let s = unsafe { - snapi_bridge_create_external_buffer_finalized( - snapi_env(&env, e), + let snapi = snapi_env(&env, e); + let mut bridge_status = 9; + let mut ownership_transferred = 0; + let result = with_cb_context(&mut env, e, || unsafe { + bridge_status = snapi_bridge_create_external_buffer_finalized( + snapi, host_addr, length, hint, &mut backing_store_token, &mut buf_id, - ) + &mut ownership_transferred, + ); + bridge_status + }); + let s = match result { + Ok(status) => status, + Err(error) => { + if ownership_transferred == 0 { + crate::guest_heap::GuestHeap::reclaim_finalize_ctx(hint); + heap.free_offset(guest_ptr); + } + return Err(error); + } }; if s != 0 { - crate::guest_heap::GuestHeap::reclaim_finalize_ctx(hint); - heap.free_offset(guest_ptr); - return s; + if ownership_transferred == 0 { + crate::guest_heap::GuestHeap::reclaim_finalize_ctx(hint); + heap.free_offset(guest_ptr); + } + return Ok(s); } write_guest_u32(&mut env, rp, buf_id); if result_data_ptr != 0 { write_guest_u32(&mut env, result_data_ptr, guest_ptr); } - 0 + Ok(0) } else { // Fallback for non-WASIX let mut result_host_data: u64 = 0; let mut out: u32 = 0; - let s = unsafe { + let snapi = snapi_env(&env, e); + let s = with_cb_context(&mut env, e, || unsafe { snapi_bridge_create_buffer_copy( - snapi_env(&env, e), + snapi, length, src_data.as_ptr(), &mut result_host_data, &mut out, ) - }; + })?; if s == 0 { write_guest_u32(&mut env, rp, out); } - s + Ok(s) } } @@ -4502,38 +4716,11 @@ fn guest_napi_fatal_error( loc_len: u32, msg_ptr: u32, msg_len: u32, -) -> i32 { - // Read location and message from guest memory - let loc = if loc_ptr != 0 { - let len = if loc_len == 0xFFFFFFFFu32 { - read_guest_c_string(&mut env, loc_ptr) - .map(|v| v.len()) - .unwrap_or(0) - } else { - loc_len as usize - }; - read_guest_bytes(&mut env, loc_ptr, len).map(|b| String::from_utf8_lossy(&b).to_string()) - } else { - None - }; - let msg = if msg_ptr != 0 { - let len = if msg_len == 0xFFFFFFFFu32 { - read_guest_c_string(&mut env, msg_ptr) - .map(|v| v.len()) - .unwrap_or(0) - } else { - msg_len as usize - }; - read_guest_bytes(&mut env, msg_ptr, len).map(|b| String::from_utf8_lossy(&b).to_string()) - } else { - None - }; - eprintln!( - "FATAL ERROR: location={}, message={}", - loc.as_deref().unwrap_or("(null)"), - msg.as_deref().unwrap_or("(null)") - ); - std::process::abort(); +) -> Result<(), WasiError> { + let loc = read_guest_fatal_text(&mut env, loc_ptr, loc_len); + let msg = read_guest_fatal_text(&mut env, msg_ptr, msg_len); + eprintln!("FATAL ERROR: location={}, message={}", loc, msg); + Err(WasiError::Exit(ExitCode::from(1))) } // --- Constructor --- diff --git a/src/guest/napi_js.rs b/src/guest/napi_js.rs index 81141e36..1b027dc3 100644 --- a/src/guest/napi_js.rs +++ b/src/guest/napi_js.rs @@ -7,6 +7,8 @@ use std::ffi::{CString, c_void}; use wasmer::{AsStoreMut, Function, FunctionEnv, FunctionEnvMut, Imports, namespace}; +use wasmer_wasix::WasiError; +use wasmer_wasix::wasmer_wasix_types::wasi::ExitCode; #[cfg(all(target_arch = "wasm32", feature = "js"))] use wasm_bindgen::JsValue; @@ -488,12 +490,10 @@ fn guest_unofficial_napi_configure_runtime( let Some(flags) = abi::read_runtime_options(&mut env, options_ptr) else { return 1; }; - let Ok(flags) = CString::new(flags) else { + if !flags.is_empty() { return 1; - }; - unsafe { - snapi_bridge_unofficial_configure_runtime(flags.as_ptr(), flags.as_bytes().len() as u32) } + unsafe { snapi_bridge_unofficial_configure_runtime(std::ptr::null(), 0) } } fn guest_unofficial_napi_create_env( @@ -5273,38 +5273,11 @@ fn guest_napi_fatal_error( loc_len: u32, msg_ptr: u32, msg_len: u32, -) -> i32 { - // Read location and message from guest memory - let loc = if loc_ptr != 0 { - let len = if loc_len == 0xFFFFFFFFu32 { - read_guest_c_string(&mut env, loc_ptr) - .map(|v| v.len()) - .unwrap_or(0) - } else { - loc_len as usize - }; - read_guest_bytes(&mut env, loc_ptr, len).map(|b| String::from_utf8_lossy(&b).to_string()) - } else { - None - }; - let msg = if msg_ptr != 0 { - let len = if msg_len == 0xFFFFFFFFu32 { - read_guest_c_string(&mut env, msg_ptr) - .map(|v| v.len()) - .unwrap_or(0) - } else { - msg_len as usize - }; - read_guest_bytes(&mut env, msg_ptr, len).map(|b| String::from_utf8_lossy(&b).to_string()) - } else { - None - }; - eprintln!( - "FATAL ERROR: location={}, message={}", - loc.as_deref().unwrap_or("(null)"), - msg.as_deref().unwrap_or("(null)") - ); - std::process::abort(); +) -> Result<(), WasiError> { + let loc = read_guest_fatal_text(&mut env, loc_ptr, loc_len); + let msg = read_guest_fatal_text(&mut env, msg_ptr, msg_len); + eprintln!("FATAL ERROR: location={}, message={}", loc, msg); + Err(WasiError::Exit(ExitCode::from(1))) } // --- Constructor --- diff --git a/src/guest/util.rs b/src/guest/util.rs index 9961d1d0..29a54075 100644 --- a/src/guest/util.rs +++ b/src/guest/util.rs @@ -6,6 +6,13 @@ use wasmer::{AsStoreMut, FunctionEnvMut}; use crate::NapiEnv; +pub fn guest_range_is_valid(env: &mut FunctionEnvMut, ptr: u32, len: usize) -> bool { + let Some(end) = u64::from(ptr).checked_add(len as u64) else { + return false; + }; + end <= guest_data_size(env) +} + pub fn write_guest_bytes(env: &mut FunctionEnvMut, guest_ptr: u32, data: &[u8]) -> bool { let (state, store) = env.data_and_store_mut(); let Some(memory) = state.memory.clone() else { @@ -60,6 +67,32 @@ pub fn read_guest_bytes( Some(out) } +/// Copy a bounded diagnostic string supplied to `napi_fatal_error`. +/// `NAPI_AUTO_LENGTH` is a NUL-terminated string; explicit lengths may +/// include NUL bytes and are deliberately kept intact in the diagnostic. +pub(crate) fn read_guest_fatal_text( + env: &mut FunctionEnvMut, + guest_ptr: u32, + len: u32, +) -> String { + const MAX_DIAGNOSTIC_BYTES: usize = 4096; + if guest_ptr == 0 { + return "(null)".to_owned(); + } + let bytes = if len == u32::MAX { + read_guest_c_string(env, guest_ptr) + } else { + usize::try_from(len) + .ok() + .and_then(|len| read_guest_bytes(env, guest_ptr, len.min(MAX_DIAGNOSTIC_BYTES))) + }; + bytes + .map(|bytes| { + String::from_utf8_lossy(&bytes[..bytes.len().min(MAX_DIAGNOSTIC_BYTES)]).into_owned() + }) + .unwrap_or_else(|| "(invalid guest string)".to_owned()) +} + /// Live size of the guest's linear memory in bytes, or 0 if it has none. Used /// to bound host allocations sized by a guest-supplied length: a guest can /// never reference more than its own memory holds. diff --git a/src/guest/util_js.rs b/src/guest/util_js.rs index 3dcd4317..971cc6d4 100644 --- a/src/guest/util_js.rs +++ b/src/guest/util_js.rs @@ -408,6 +408,31 @@ pub fn read_guest_bytes( wasmer_napi_guest_read(&memory, guest_ptr, &mut out).then_some(out) } +/// Copy a bounded diagnostic string supplied to `napi_fatal_error`. +/// Explicit lengths may include NUL bytes; `-1` requests NUL termination. +pub(crate) fn read_guest_fatal_text( + env: &mut FunctionEnvMut, + guest_ptr: u32, + len: u32, +) -> String { + const MAX_DIAGNOSTIC_BYTES: usize = 4096; + if guest_ptr == 0 { + return "(null)".to_owned(); + } + let bytes = if len == u32::MAX { + read_guest_c_string(env, guest_ptr) + } else { + usize::try_from(len) + .ok() + .and_then(|len| read_guest_bytes(env, guest_ptr, len.min(MAX_DIAGNOSTIC_BYTES))) + }; + bytes + .map(|bytes| { + String::from_utf8_lossy(&bytes[..bytes.len().min(MAX_DIAGNOSTIC_BYTES)]).into_owned() + }) + .unwrap_or_else(|| "(invalid guest string)".to_owned()) +} + pub fn allocate_guest_buffer(env: &mut FunctionEnvMut, byte_len: usize) -> Option { // Exact-size large transfers avoid doubling peak memory for package // archives and Wasm binaries while retaining cheap pooled size classes for diff --git a/src/napi_bridge_init.cc b/src/napi_bridge_init.cc index 688a0f04..4cd95f95 100644 --- a/src/napi_bridge_init.cc +++ b/src/napi_bridge_init.cc @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -22,10 +23,11 @@ #define NAPI_EXPERIMENTAL #endif -#include "node_api.h" -#include "unofficial_napi.h" #include "edge_v8_platform.h" +#include "internal/napi_external_transfer_observer.h" #include "internal/napi_v8_env.h" +#include "node_api.h" +#include "unofficial_napi.h" namespace { @@ -48,32 +50,37 @@ struct CallbackBinding; // guest stores across calls. Used for the engine-owned handle kinds whose // lifetime is not tied to a napi scope (module_wrap, bytecode). struct HandleTable { - std::unordered_map handles; + std::unordered_map handles; uint32_t next_id = 1; - uint32_t Store(void* handle) { - if (handle == nullptr) return 0; + uint32_t Store(void *handle) { + if (handle == nullptr) + return 0; const uint32_t id = next_id++; handles[id] = handle; return id; } - void* Load(uint32_t id) const { - if (id == 0) return nullptr; + void *Load(uint32_t id) const { + if (id == 0) + return nullptr; const auto it = handles.find(id); return it != handles.end() ? it->second : nullptr; } - void* Take(uint32_t id) { - if (id == 0) return 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; + if (it == handles.end()) + return nullptr; + void *handle = it->second; handles.erase(it); return handle; } - std::vector TakeAll() { - std::vector result; + std::vector TakeAll() { + std::vector result; result.reserve(handles.size()); - for (const auto& entry : handles) result.push_back(entry.second); + for (const auto &entry : handles) + result.push_back(entry.second); Reset(); return result; } @@ -117,6 +124,15 @@ struct ScopeFrame { }; struct SnapiEnvState { + // V8 and the bridge's handle tables for one environment are serialized + // independently of every other environment. Reentrant guest callbacks may + // enter the same environment on the same OS thread. + std::recursive_mutex mutex; + size_t active_bridge_calls = 0; + std::mutex control_mutex; + std::condition_variable control_cv; + size_t active_control_calls = 0; + bool disposing = false; napi_env env = nullptr; unofficial_napi_env_owner owner = nullptr; @@ -158,7 +174,7 @@ struct SnapiEnvState { // Current guest invocation driving this env. This is only valid while the // driving host import remains on the stack. - std::atomic active_callback_ctx{nullptr}; + std::atomic active_callback_ctx{nullptr}; std::unordered_map callback_invocations; uint32_t next_callback_invocation_id = 1; std::unordered_map cb_registry; @@ -172,20 +188,23 @@ struct SnapiEnvState { }; struct CallbackBinding { - SnapiEnvState* state = nullptr; + SnapiEnvState *state = nullptr; uint32_t reg_id = 0; }; // 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(); +std::unordered_map> &g_envs = + *new std::unordered_map>(); // 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(); +HandleTable &g_message_handles = *new HandleTable(); -CallbackBinding* RegisterCallbackBinding(SnapiEnvState* state, uint32_t reg_id) { - if (state == nullptr || reg_id == 0) return nullptr; +CallbackBinding *RegisterCallbackBinding(SnapiEnvState *state, + uint32_t reg_id) { + if (state == nullptr || reg_id == 0) + return nullptr; if (state->value_limit != 0 && state->callback_bindings.size() >= state->value_limit) { return nullptr; @@ -195,27 +214,57 @@ CallbackBinding* RegisterCallbackBinding(SnapiEnvState* state, uint32_t reg_id) return state->callback_bindings.back().get(); } -SnapiEnvState* LookupEnvState(SnapiEnvState* env_state) { - if (env_state == nullptr) return nullptr; +std::shared_ptr LookupEnvState(SnapiEnvState *env_state) { + if (env_state == nullptr) + return {}; // The pointer originates from the guest (via the Rust env-id layer); gate it // on live-env membership before the first dereference so a stale or forged // env handle fails cleanly instead of touching freed memory. std::lock_guard lock(g_mu); - if (g_envs.find(env_state) == g_envs.end()) return nullptr; - if (env_state->env == nullptr) return nullptr; - return env_state; -} + auto it = g_envs.find(env_state); + return it == g_envs.end() ? std::shared_ptr{} : it->second; +} + +struct EnvLease { + explicit EnvLease(std::shared_ptr value) + : state(std::move(value)), + lock(state != nullptr + ? std::unique_lock(state->mutex) + : std::unique_lock()) { + if (state != nullptr) { + ++state->active_bridge_calls; + // The calling worker may predate V8's allocation of its Linux JIT + // protection key. Restore V8's default read-only permissions before + // accessing isolate-owned memory on this thread. + v8::ThreadIsolatedAllocator::SetDefaultPermissionsForSignalHandler(); + } + } -uint32_t RegisterCallbackInvocation(SnapiEnvState* state, napi_callback_info info) { - if (state == nullptr || info == nullptr) return 0; + ~EnvLease() { + if (state != nullptr) --state->active_bridge_calls; + } + + SnapiEnvState *get() const { + return state != nullptr && state->env != nullptr ? state.get() : nullptr; + } + + std::shared_ptr state; + std::unique_lock lock; +}; + +uint32_t RegisterCallbackInvocation(SnapiEnvState *state, + napi_callback_info info) { + if (state == nullptr || info == nullptr) + return 0; uint32_t id = state->next_callback_invocation_id++; - if (id == 0) id = state->next_callback_invocation_id++; + if (id == 0) + id = state->next_callback_invocation_id++; state->callback_invocations[id] = CallbackInvocation{info}; return id; } // Innermost open frame, creating the root frame on first use. -ScopeFrame& CurrentFrame(SnapiEnvState& state) { +ScopeFrame &CurrentFrame(SnapiEnvState &state) { if (state.scope_frames.empty()) { state.scope_frames.emplace_back(); } @@ -224,9 +273,12 @@ ScopeFrame& CurrentFrame(SnapiEnvState& state) { // Mints an id for `val` owned by `frame`. The common path stores into the // innermost frame; escape_handle stores into the parent of the escaped scope. -uint32_t StoreValueInFrame(SnapiEnvState& state, napi_value val, ScopeFrame& frame) { - if (val == nullptr) return 0; - if (state.env == nullptr) return 0; +uint32_t StoreValueInFrame(SnapiEnvState &state, napi_value val, + ScopeFrame &frame) { + if (val == nullptr) + return 0; + if (state.env == nullptr) + return 0; // Refuse once the per-env handle cap is reached: returning 0 surfaces as an // N-API failure the guest can handle, instead of leaking host RSS unbounded. if (state.value_limit != 0 && state.live_value_count >= state.value_limit) { @@ -237,11 +289,12 @@ uint32_t StoreValueInFrame(SnapiEnvState& state, napi_value val, ScopeFrame& fra index = state.value_free_slots.back(); state.value_free_slots.pop_back(); } else { - if (state.value_slots.size() >= kValueIdMaxSlots) return 0; + if (state.value_slots.size() >= kValueIdMaxSlots) + return 0; index = static_cast(state.value_slots.size()); state.value_slots.emplace_back(); } - ValueSlot& slot = state.value_slots[index]; + ValueSlot &slot = state.value_slots[index]; slot.value = val; slot.in_use = true; ++state.live_value_count; @@ -249,18 +302,20 @@ uint32_t StoreValueInFrame(SnapiEnvState& state, napi_value val, ScopeFrame& fra return ((index + 1) << kValueIdGenBits) | slot.generation; } -uint32_t StoreValue(SnapiEnvState& state, napi_value val) { +uint32_t StoreValue(SnapiEnvState &state, napi_value val) { return StoreValueInFrame(state, val, CurrentFrame(state)); } -napi_value LoadValue(SnapiEnvState& state, uint32_t id) { - if (id == 0) return nullptr; - if (state.env == nullptr) return nullptr; +napi_value LoadValue(SnapiEnvState &state, uint32_t id) { + if (id == 0) + return nullptr; + if (state.env == nullptr) + return nullptr; const uint32_t index_plus_one = id >> kValueIdGenBits; if (index_plus_one == 0 || index_plus_one > state.value_slots.size()) { return nullptr; } - ValueSlot& slot = state.value_slots[index_plus_one - 1]; + ValueSlot &slot = state.value_slots[index_plus_one - 1]; if (!slot.in_use || slot.generation != (id & kValueIdGenMask) || slot.value == nullptr) { return nullptr; @@ -271,12 +326,14 @@ napi_value LoadValue(SnapiEnvState& state, uint32_t id) { // Releases a slot and bumps its generation so any id minted for the old // occupant is rejected by LoadValue. The Local itself dies when the owning // napi scope closes; this only retires the id. -void FreeValueSlot(SnapiEnvState& state, uint32_t index) { - ValueSlot& slot = state.value_slots[index]; - if (!slot.in_use) return; +void FreeValueSlot(SnapiEnvState &state, uint32_t index) { + ValueSlot &slot = state.value_slots[index]; + if (!slot.in_use) + return; slot.value = nullptr; slot.in_use = false; - slot.generation = static_cast((slot.generation + 1) & kValueIdGenMask); + slot.generation = + static_cast((slot.generation + 1) & kValueIdGenMask); state.value_free_slots.push_back(index); --state.live_value_count; } @@ -284,8 +341,9 @@ void FreeValueSlot(SnapiEnvState& state, uint32_t index) { // Frees every slot the innermost frame owns and pops it. Closes the frame's // napi scope (which kills the backing Locals) unless `close_napi_scope` is // false (env-teardown drain, where release_env reclaims the whole env). -void PopCurrentFrame(SnapiEnvState& state, bool close_napi_scope) { - if (state.scope_frames.empty()) return; +void PopCurrentFrame(SnapiEnvState &state, bool close_napi_scope) { + if (state.scope_frames.empty()) + return; ScopeFrame frame = std::move(state.scope_frames.back()); state.scope_frames.pop_back(); for (uint32_t index : frame.owned_slots) { @@ -300,133 +358,161 @@ void PopCurrentFrame(SnapiEnvState& state, bool close_napi_scope) { } } -uint64_t BackingStoreToken(const std::shared_ptr& backing_store) { +uint64_t +BackingStoreToken(const std::shared_ptr &backing_store) { return backing_store == nullptr ? 0 - : static_cast(reinterpret_cast(backing_store.get())); + : static_cast( + reinterpret_cast(backing_store.get())); } -uint64_t napi_v8_get_arraybuffer_backing_store_token(napi_env env, napi_value value) { +uint64_t napi_v8_get_arraybuffer_backing_store_token(napi_env env, + napi_value value) { (void)env; - if (value == nullptr) return 0; + if (value == nullptr) + return 0; v8::Local raw = napi_v8_unwrap_value(value); - if (raw.IsEmpty() || !raw->IsArrayBuffer()) return 0; + if (raw.IsEmpty() || !raw->IsArrayBuffer()) + return 0; return BackingStoreToken(raw.As()->GetBackingStore()); } -uint64_t napi_v8_get_arraybuffer_view_backing_store_token(napi_env env, napi_value value) { +uint64_t napi_v8_get_arraybuffer_view_backing_store_token(napi_env env, + napi_value value) { (void)env; - if (value == nullptr) return 0; + if (value == nullptr) + return 0; v8::Local raw = napi_v8_unwrap_value(value); - if (raw.IsEmpty() || !raw->IsArrayBufferView()) return 0; + if (raw.IsEmpty() || !raw->IsArrayBufferView()) + return 0; - return BackingStoreToken(raw.As()->Buffer()->GetBackingStore()); + return BackingStoreToken( + raw.As()->Buffer()->GetBackingStore()); } size_t TypedArrayElementSize(napi_typedarray_type type) { switch (type) { - case napi_int8_array: - case napi_uint8_array: - case napi_uint8_clamped_array: - return 1; - case napi_int16_array: - case napi_uint16_array: - return 2; - case napi_int32_array: - case napi_uint32_array: - case napi_float32_array: - return 4; - case napi_float64_array: - case napi_bigint64_array: - case napi_biguint64_array: - return 8; + case napi_int8_array: + case napi_uint8_array: + case napi_uint8_clamped_array: + return 1; + case napi_int16_array: + case napi_uint16_array: + return 2; + case napi_int32_array: + case napi_uint32_array: + case napi_float32_array: + return 4; + case napi_float64_array: + case napi_bigint64_array: + case napi_biguint64_array: + return 8; } return 0; } -uint32_t StoreRef(SnapiEnvState& state, napi_ref ref) { - if (ref == nullptr) return 0; +uint32_t StoreRef(SnapiEnvState &state, napi_ref ref) { + if (ref == nullptr) + return 0; uint32_t id = state.next_ref_id++; state.refs[id] = ref; return id; } -napi_ref LoadRef(SnapiEnvState& state, uint32_t id) { - if (id == 0) return nullptr; +napi_ref LoadRef(SnapiEnvState &state, uint32_t id) { + if (id == 0) + return nullptr; auto it = state.refs.find(id); return it != state.refs.end() ? it->second : nullptr; } -void RemoveRef(SnapiEnvState& state, uint32_t id) { - state.refs.erase(id); -} +void RemoveRef(SnapiEnvState &state, uint32_t id) { state.refs.erase(id); } -uint32_t StoreDeferred(SnapiEnvState& state, napi_deferred d) { - if (d == nullptr) return 0; +uint32_t StoreDeferred(SnapiEnvState &state, napi_deferred d) { + if (d == nullptr) + return 0; uint32_t id = state.next_deferred_id++; state.deferreds[id] = d; return id; } -napi_deferred LoadDeferred(SnapiEnvState& state, uint32_t id) { - if (id == 0) return nullptr; +napi_deferred LoadDeferred(SnapiEnvState &state, uint32_t id) { + if (id == 0) + return nullptr; auto it = state.deferreds.find(id); return it != state.deferreds.end() ? it->second : nullptr; } -void RemoveDeferred(SnapiEnvState& state, uint32_t id) { +void RemoveDeferred(SnapiEnvState &state, uint32_t id) { state.deferreds.erase(id); } // Finds the open frame with the given guest-visible id (innermost first). -int FindFrameById(SnapiEnvState& state, uint32_t scope_id) { - if (scope_id == 0) return -1; +int FindFrameById(SnapiEnvState &state, uint32_t scope_id) { + if (scope_id == 0) + return -1; for (int i = static_cast(state.scope_frames.size()) - 1; i >= 0; --i) { - if (state.scope_frames[i].id == scope_id) return i; + if (state.scope_frames[i].id == scope_id) + return i; } return -1; } -uint32_t StoreModuleWrapHandle(SnapiEnvState& state, unofficial_napi_module module) { - return state.module_wrap_handles.Store(reinterpret_cast(module)); +uint32_t StoreModuleWrapHandle(SnapiEnvState &state, + unofficial_napi_module module) { + return state.module_wrap_handles.Store(reinterpret_cast(module)); } -unofficial_napi_module LoadModuleWrapHandle(SnapiEnvState& state, uint32_t id) { - return reinterpret_cast(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) { +void RemoveModuleWrapHandle(SnapiEnvState &state, uint32_t id) { state.module_wrap_handles.Remove(id); } -uint32_t StoreBytecodeHandle(SnapiEnvState& state, unofficial_napi_bytecode handle) { - return state.bytecode_handles.Store(reinterpret_cast(handle)); +uint32_t StoreBytecodeHandle(SnapiEnvState &state, + unofficial_napi_bytecode handle) { + return state.bytecode_handles.Store(reinterpret_cast(handle)); } -unofficial_napi_bytecode LoadBytecodeHandle(SnapiEnvState& state, uint32_t id) { - return reinterpret_cast(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) { +void RemoveBytecodeHandle(SnapiEnvState &state, uint32_t id) { state.bytecode_handles.Remove(id); } -SnapiEnvState* RequireEnvState(SnapiEnvState* env_state) { - auto* bridge_state = LookupEnvState(env_state); - if (bridge_state == nullptr || bridge_state->env == nullptr) { - return nullptr; - } - return bridge_state; +EnvLease RequireEnvState(SnapiEnvState *env_state) { + return EnvLease(LookupEnvState(env_state)); } -napi_status DisposeBridgeStateLocked(SnapiEnvState* state) { - if (state == nullptr) return napi_ok; - for (const auto& entry : state->buffer_lease_handles.handles) { +napi_status DisposeBridgeStateLocked(SnapiEnvState *state) { + if (state == nullptr) + return napi_ok; + // Releasing an environment from its own callback would dispose an entered + // isolate. Keep all ownership and registry state intact on this refusal. + if (state->active_bridge_calls != 1 || !state->callback_invocations.empty()) + return napi_generic_failure; + // Stop admitting control calls. Keep the state in the registry through V8 + // release: finalizer callbacks can reenter the bridge on this same thread. + // Other callers wait on the per-env mutex and find a null env afterward. + { + std::unique_lock control_lock(state->control_mutex); + state->disposing = true; + state->control_cv.wait(control_lock, + [&] { return state->active_control_calls == 0; }); + } + for (const auto &entry : state->buffer_lease_handles.handles) { (void)unofficial_napi_release_buffer_lease( - state->env, static_cast(entry.second), false); + state->env, static_cast(entry.second), + false); } state->buffer_lease_handles.Reset(); // Slots hold raw Locals; they die with the env's scopes when release_env @@ -446,7 +532,7 @@ napi_status DisposeBridgeStateLocked(SnapiEnvState* state) { // Bytecode handles own engine resources outside any napi scope (V8 // Globals / QuickJS JSValue refcounts), so they must be released // explicitly — env teardown does not reclaim them. - for (auto& entry : state->bytecode_handles.handles) { + for (auto &entry : state->bytecode_handles.handles) { if (entry.second != nullptr) { (void)unofficial_napi_bytecode_release( state->env, reinterpret_cast(entry.second)); @@ -459,23 +545,21 @@ napi_status DisposeBridgeStateLocked(SnapiEnvState* state) { state->cb_registry.clear(); state->next_cb_reg_id = 1; state->callback_bindings.clear(); + napi_status release_status = napi_ok; if (state->owner != nullptr) { - napi_status s = unofficial_napi_release_env(state->owner, nullptr); - state->active_callback_ctx.store(nullptr, std::memory_order_release); - state->owner = nullptr; - state->env = nullptr; - g_envs.erase(state); - delete state; - return s; + release_status = unofficial_napi_release_env(state->owner, nullptr); } state->active_callback_ctx.store(nullptr, std::memory_order_release); + state->owner = nullptr; state->env = nullptr; - g_envs.erase(state); - delete state; - return napi_ok; + { + std::lock_guard registry_lock(g_mu); + g_envs.erase(state); + } + return release_status; } -} // namespace +} // namespace // ============================================================ // Initialization @@ -489,11 +573,9 @@ extern "C" int snapi_bridge_set_v8_worker_thread_count(uint32_t count) { } extern "C" int snapi_bridge_init() { - std::lock_guard lock(g_mu); // Intentionally do not create a N-API env here. // Env creation is deferred until the guest explicitly calls // `unofficial_napi_create_env`, so init happens on the execution thread. - (void)lock; return 1; } @@ -501,154 +583,206 @@ extern "C" int snapi_bridge_init() { // Value creation // ============================================================ -extern "C" int snapi_bridge_get_undefined(SnapiEnvState* env_state, uint32_t* out_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_get_undefined(SnapiEnvState *env_state, + uint32_t *out_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value result; napi_status s = napi_get_undefined(env, &result); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *out_id = StoreValue(*bridge_state, result); return napi_ok; } -extern "C" int snapi_bridge_get_null(SnapiEnvState* env_state, uint32_t* out_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_get_null(SnapiEnvState *env_state, + uint32_t *out_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value result; napi_status s = napi_get_null(env, &result); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *out_id = StoreValue(*bridge_state, result); return napi_ok; } -extern "C" int snapi_bridge_get_boolean(SnapiEnvState* env_state, int value, uint32_t* out_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_get_boolean(SnapiEnvState *env_state, int value, + uint32_t *out_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value result; napi_status s = napi_get_boolean(env, value != 0, &result); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *out_id = StoreValue(*bridge_state, result); return napi_ok; } -extern "C" int snapi_bridge_get_global(SnapiEnvState* env_state, uint32_t* out_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_get_global(SnapiEnvState *env_state, + uint32_t *out_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value result; napi_status s = napi_get_global(env, &result); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *out_id = StoreValue(*bridge_state, result); return napi_ok; } -extern "C" int snapi_bridge_create_string_utf8(SnapiEnvState* env_state, const char* str, +extern "C" int snapi_bridge_create_string_utf8(SnapiEnvState *env_state, + const char *str, uint32_t wasm_length, - uint32_t* out_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; + uint32_t *out_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; size_t length = (wasm_length == 0xFFFFFFFFu) ? NAPI_AUTO_LENGTH : (size_t)wasm_length; napi_value result; napi_status s = napi_create_string_utf8(env, str, length, &result); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *out_id = StoreValue(*bridge_state, result); return napi_ok; } -extern "C" int snapi_bridge_create_string_latin1(SnapiEnvState* env_state, const char* str, +extern "C" int snapi_bridge_create_string_latin1(SnapiEnvState *env_state, + const char *str, uint32_t wasm_length, - uint32_t* out_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; + uint32_t *out_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; size_t length = (wasm_length == 0xFFFFFFFFu) ? NAPI_AUTO_LENGTH : (size_t)wasm_length; napi_value result; napi_status s = napi_create_string_latin1(env, str, length, &result); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *out_id = StoreValue(*bridge_state, result); return napi_ok; } -extern "C" int snapi_bridge_create_int32(SnapiEnvState* env_state, int32_t value, uint32_t* out_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_create_int32(SnapiEnvState *env_state, + int32_t value, uint32_t *out_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value result; napi_status s = napi_create_int32(env, value, &result); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *out_id = StoreValue(*bridge_state, result); return napi_ok; } -extern "C" int snapi_bridge_create_uint32(SnapiEnvState* env_state, uint32_t value, uint32_t* out_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_create_uint32(SnapiEnvState *env_state, + uint32_t value, uint32_t *out_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value result; napi_status s = napi_create_uint32(env, value, &result); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *out_id = StoreValue(*bridge_state, result); return napi_ok; } -extern "C" int snapi_bridge_create_double(SnapiEnvState* env_state, double value, uint32_t* out_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_create_double(SnapiEnvState *env_state, + double value, uint32_t *out_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value result; napi_status s = napi_create_double(env, value, &result); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *out_id = StoreValue(*bridge_state, result); return napi_ok; } -extern "C" int snapi_bridge_create_int64(SnapiEnvState* env_state, int64_t value, uint32_t* out_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_create_int64(SnapiEnvState *env_state, + int64_t value, uint32_t *out_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value result; napi_status s = napi_create_int64(env, value, &result); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *out_id = StoreValue(*bridge_state, result); return napi_ok; } -extern "C" int snapi_bridge_create_object(SnapiEnvState* env_state, uint32_t* out_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_create_object(SnapiEnvState *env_state, + uint32_t *out_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value result; napi_status s = napi_create_object(env, &result); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *out_id = StoreValue(*bridge_state, result); return napi_ok; } -extern "C" int snapi_bridge_create_array(SnapiEnvState* env_state, uint32_t* out_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_create_array(SnapiEnvState *env_state, + uint32_t *out_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value result; napi_status s = napi_create_array(env, &result); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *out_id = StoreValue(*bridge_state, result); return napi_ok; } -extern "C" int snapi_bridge_create_array_with_length(SnapiEnvState* env_state, uint32_t length, - uint32_t* out_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_create_array_with_length(SnapiEnvState *env_state, + uint32_t length, + uint32_t *out_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value result; napi_status s = napi_create_array_with_length(env, (size_t)length, &result); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *out_id = StoreValue(*bridge_state, result); return napi_ok; } @@ -657,73 +791,102 @@ extern "C" int snapi_bridge_create_array_with_length(SnapiEnvState* env_state, u // Value reading // ============================================================ -extern "C" int snapi_bridge_get_value_string_utf8(SnapiEnvState* env_state, uint32_t id, char* buf, +extern "C" int snapi_bridge_get_value_string_utf8(SnapiEnvState *env_state, + uint32_t id, char *buf, size_t bufsize, - size_t* result) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; + size_t *result) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value val = LoadValue(*bridge_state, id); - if (!val) return napi_invalid_arg; + if (!val) + return napi_invalid_arg; return napi_get_value_string_utf8(env, val, buf, bufsize, result); } -extern "C" int snapi_bridge_get_value_string_latin1(SnapiEnvState* env_state, uint32_t id, char* buf, +extern "C" int snapi_bridge_get_value_string_latin1(SnapiEnvState *env_state, + uint32_t id, char *buf, size_t bufsize, - size_t* result) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; + size_t *result) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value val = LoadValue(*bridge_state, id); - if (!val) return napi_invalid_arg; + if (!val) + return napi_invalid_arg; return napi_get_value_string_latin1(env, val, buf, bufsize, result); } -extern "C" int snapi_bridge_get_value_int32(SnapiEnvState* env_state, uint32_t id, int32_t* result) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_get_value_int32(SnapiEnvState *env_state, + uint32_t id, int32_t *result) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value val = LoadValue(*bridge_state, id); - if (!val) return napi_invalid_arg; + if (!val) + return napi_invalid_arg; return napi_get_value_int32(env, val, result); } -extern "C" int snapi_bridge_get_value_uint32(SnapiEnvState* env_state, uint32_t id, uint32_t* result) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_get_value_uint32(SnapiEnvState *env_state, + uint32_t id, uint32_t *result) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value val = LoadValue(*bridge_state, id); - if (!val) return napi_invalid_arg; + if (!val) + return napi_invalid_arg; return napi_get_value_uint32(env, val, result); } -extern "C" int snapi_bridge_get_value_double(SnapiEnvState* env_state, uint32_t id, double* result) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_get_value_double(SnapiEnvState *env_state, + uint32_t id, double *result) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value val = LoadValue(*bridge_state, id); - if (!val) return napi_invalid_arg; + if (!val) + return napi_invalid_arg; return napi_get_value_double(env, val, result); } -extern "C" int snapi_bridge_get_value_int64(SnapiEnvState* env_state, uint32_t id, int64_t* result) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_get_value_int64(SnapiEnvState *env_state, + uint32_t id, int64_t *result) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value val = LoadValue(*bridge_state, id); - if (!val) return napi_invalid_arg; + if (!val) + return napi_invalid_arg; return napi_get_value_int64(env, val, result); } -extern "C" int snapi_bridge_get_value_bool(SnapiEnvState* env_state, uint32_t id, int* result) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_get_value_bool(SnapiEnvState *env_state, + uint32_t id, int *result) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value val = LoadValue(*bridge_state, id); - if (!val) return napi_invalid_arg; + if (!val) + return napi_invalid_arg; bool b; napi_status s = napi_get_value_bool(env, val, &b); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *result = b ? 1 : 0; return napi_ok; } @@ -732,121 +895,166 @@ extern "C" int snapi_bridge_get_value_bool(SnapiEnvState* env_state, uint32_t id // Type checking // ============================================================ -extern "C" int snapi_bridge_typeof(SnapiEnvState* env_state, uint32_t id, int* result) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_typeof(SnapiEnvState *env_state, uint32_t id, + int *result) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value val = LoadValue(*bridge_state, id); - if (!val) return napi_invalid_arg; + if (!val) + return napi_invalid_arg; napi_valuetype vtype; napi_status s = napi_typeof(env, val, &vtype); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *result = (int)vtype; return napi_ok; } -extern "C" int snapi_bridge_is_array(SnapiEnvState* env_state, uint32_t id, int* result) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_is_array(SnapiEnvState *env_state, uint32_t id, + int *result) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value val = LoadValue(*bridge_state, id); - if (!val) return napi_invalid_arg; + if (!val) + return napi_invalid_arg; bool is; napi_status s = napi_is_array(env, val, &is); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *result = is ? 1 : 0; return napi_ok; } -extern "C" int snapi_bridge_is_error(SnapiEnvState* env_state, uint32_t id, int* result) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_is_error(SnapiEnvState *env_state, uint32_t id, + int *result) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value val = LoadValue(*bridge_state, id); - if (!val) return napi_invalid_arg; + if (!val) + return napi_invalid_arg; bool is; napi_status s = napi_is_error(env, val, &is); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *result = is ? 1 : 0; return napi_ok; } -extern "C" int snapi_bridge_is_arraybuffer(SnapiEnvState* env_state, uint32_t id, int* result) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_is_arraybuffer(SnapiEnvState *env_state, + uint32_t id, int *result) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value val = LoadValue(*bridge_state, id); - if (!val) return napi_invalid_arg; + if (!val) + return napi_invalid_arg; bool is; napi_status s = napi_is_arraybuffer(env, val, &is); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *result = is ? 1 : 0; return napi_ok; } -extern "C" int snapi_bridge_is_typedarray(SnapiEnvState* env_state, uint32_t id, int* result) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_is_typedarray(SnapiEnvState *env_state, uint32_t id, + int *result) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value val = LoadValue(*bridge_state, id); - if (!val) return napi_invalid_arg; + if (!val) + return napi_invalid_arg; bool is; napi_status s = napi_is_typedarray(env, val, &is); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *result = is ? 1 : 0; return napi_ok; } -extern "C" int snapi_bridge_is_dataview(SnapiEnvState* env_state, uint32_t id, int* result) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_is_dataview(SnapiEnvState *env_state, uint32_t id, + int *result) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value val = LoadValue(*bridge_state, id); - if (!val) return napi_invalid_arg; + if (!val) + return napi_invalid_arg; bool is; napi_status s = napi_is_dataview(env, val, &is); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *result = is ? 1 : 0; return napi_ok; } -extern "C" int snapi_bridge_is_date(SnapiEnvState* env_state, uint32_t id, int* result) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_is_date(SnapiEnvState *env_state, uint32_t id, + int *result) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value val = LoadValue(*bridge_state, id); - if (!val) return napi_invalid_arg; + if (!val) + return napi_invalid_arg; bool is; napi_status s = napi_is_date(env, val, &is); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *result = is ? 1 : 0; return napi_ok; } -extern "C" int snapi_bridge_is_promise(SnapiEnvState* env_state, uint32_t id, int* result) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_is_promise(SnapiEnvState *env_state, uint32_t id, + int *result) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value val = LoadValue(*bridge_state, id); - if (!val) return napi_invalid_arg; + if (!val) + return napi_invalid_arg; bool is; napi_status s = napi_is_promise(env, val, &is); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *result = is ? 1 : 0; return napi_ok; } -extern "C" int snapi_bridge_instanceof(SnapiEnvState* env_state, uint32_t obj_id, uint32_t ctor_id, - int* result) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_instanceof(SnapiEnvState *env_state, + uint32_t obj_id, uint32_t ctor_id, + int *result) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value obj = LoadValue(*bridge_state, obj_id); napi_value ctor = LoadValue(*bridge_state, ctor_id); - if (!obj || !ctor) return napi_invalid_arg; + if (!obj || !ctor) + return napi_invalid_arg; bool is; napi_status s = napi_instanceof(env, obj, ctor, &is); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *result = is ? 1 : 0; return napi_ok; } @@ -855,54 +1063,74 @@ extern "C" int snapi_bridge_instanceof(SnapiEnvState* env_state, uint32_t obj_id // Coercion // ============================================================ -extern "C" int snapi_bridge_coerce_to_bool(SnapiEnvState* env_state, uint32_t id, uint32_t* out_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_coerce_to_bool(SnapiEnvState *env_state, + uint32_t id, uint32_t *out_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value val = LoadValue(*bridge_state, id); - if (!val) return napi_invalid_arg; + if (!val) + return napi_invalid_arg; napi_value result; napi_status s = napi_coerce_to_bool(env, val, &result); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *out_id = StoreValue(*bridge_state, result); return napi_ok; } -extern "C" int snapi_bridge_coerce_to_number(SnapiEnvState* env_state, uint32_t id, uint32_t* out_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_coerce_to_number(SnapiEnvState *env_state, + uint32_t id, uint32_t *out_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value val = LoadValue(*bridge_state, id); - if (!val) return napi_invalid_arg; + if (!val) + return napi_invalid_arg; napi_value result; napi_status s = napi_coerce_to_number(env, val, &result); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *out_id = StoreValue(*bridge_state, result); return napi_ok; } -extern "C" int snapi_bridge_coerce_to_string(SnapiEnvState* env_state, uint32_t id, uint32_t* out_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_coerce_to_string(SnapiEnvState *env_state, + uint32_t id, uint32_t *out_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value val = LoadValue(*bridge_state, id); - if (!val) return napi_invalid_arg; + if (!val) + return napi_invalid_arg; napi_value result; napi_status s = napi_coerce_to_string(env, val, &result); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *out_id = StoreValue(*bridge_state, result); return napi_ok; } -extern "C" int snapi_bridge_coerce_to_object(SnapiEnvState* env_state, uint32_t id, uint32_t* out_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_coerce_to_object(SnapiEnvState *env_state, + uint32_t id, uint32_t *out_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value val = LoadValue(*bridge_state, id); - if (!val) return napi_invalid_arg; + if (!val) + return napi_invalid_arg; napi_value result; napi_status s = napi_coerce_to_object(env, val, &result); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *out_id = StoreValue(*bridge_state, result); return napi_ok; } @@ -911,243 +1139,325 @@ extern "C" int snapi_bridge_coerce_to_object(SnapiEnvState* env_state, uint32_t // Object operations // ============================================================ -extern "C" int snapi_bridge_set_property(SnapiEnvState* env_state, uint32_t obj_id, uint32_t key_id, +extern "C" int snapi_bridge_set_property(SnapiEnvState *env_state, + uint32_t obj_id, uint32_t key_id, uint32_t val_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value obj = LoadValue(*bridge_state, obj_id); napi_value key = LoadValue(*bridge_state, key_id); napi_value val = LoadValue(*bridge_state, val_id); - if (!obj || !key || !val) return napi_invalid_arg; + if (!obj || !key || !val) + return napi_invalid_arg; return napi_set_property(env, obj, key, val); } -extern "C" int snapi_bridge_get_property(SnapiEnvState* env_state, uint32_t obj_id, uint32_t key_id, - uint32_t* out_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_get_property(SnapiEnvState *env_state, + uint32_t obj_id, uint32_t key_id, + uint32_t *out_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value obj = LoadValue(*bridge_state, obj_id); napi_value key = LoadValue(*bridge_state, key_id); - if (!obj || !key) return napi_invalid_arg; + if (!obj || !key) + return napi_invalid_arg; napi_value result; napi_status s = napi_get_property(env, obj, key, &result); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *out_id = StoreValue(*bridge_state, result); return napi_ok; } -extern "C" int snapi_bridge_has_property(SnapiEnvState* env_state, uint32_t obj_id, uint32_t key_id, - int* result) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_has_property(SnapiEnvState *env_state, + uint32_t obj_id, uint32_t key_id, + int *result) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value obj = LoadValue(*bridge_state, obj_id); napi_value key = LoadValue(*bridge_state, key_id); - if (!obj || !key) return napi_invalid_arg; + if (!obj || !key) + return napi_invalid_arg; bool has; napi_status s = napi_has_property(env, obj, key, &has); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *result = has ? 1 : 0; return napi_ok; } -extern "C" int snapi_bridge_has_own_property(SnapiEnvState* env_state, uint32_t obj_id, uint32_t key_id, - int* result) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_has_own_property(SnapiEnvState *env_state, + uint32_t obj_id, uint32_t key_id, + int *result) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value obj = LoadValue(*bridge_state, obj_id); napi_value key = LoadValue(*bridge_state, key_id); - if (!obj || !key) return napi_invalid_arg; + if (!obj || !key) + return napi_invalid_arg; bool has; napi_status s = napi_has_own_property(env, obj, key, &has); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *result = has ? 1 : 0; return napi_ok; } -extern "C" int snapi_bridge_delete_property(SnapiEnvState* env_state, uint32_t obj_id, uint32_t key_id, - int* result) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_delete_property(SnapiEnvState *env_state, + uint32_t obj_id, uint32_t key_id, + int *result) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value obj = LoadValue(*bridge_state, obj_id); napi_value key = LoadValue(*bridge_state, key_id); - if (!obj || !key) return napi_invalid_arg; + if (!obj || !key) + return napi_invalid_arg; bool deleted; napi_status s = napi_delete_property(env, obj, key, &deleted); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *result = deleted ? 1 : 0; return napi_ok; } -extern "C" int snapi_bridge_set_named_property(SnapiEnvState* env_state, uint32_t obj_id, - const char* name, +extern "C" int snapi_bridge_set_named_property(SnapiEnvState *env_state, + uint32_t obj_id, + const char *name, uint32_t val_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value obj = LoadValue(*bridge_state, obj_id); napi_value val = LoadValue(*bridge_state, val_id); - if (!obj || !val || !name) return napi_invalid_arg; + if (!obj || !val || !name) + return napi_invalid_arg; return napi_set_named_property(env, obj, name, val); } -extern "C" int snapi_bridge_get_named_property(SnapiEnvState* env_state, uint32_t obj_id, - const char* name, - uint32_t* out_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_get_named_property(SnapiEnvState *env_state, + uint32_t obj_id, + const char *name, + uint32_t *out_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value obj = LoadValue(*bridge_state, obj_id); - if (!obj || !name) return napi_invalid_arg; + if (!obj || !name) + return napi_invalid_arg; napi_value result; napi_status s = napi_get_named_property(env, obj, name, &result); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *out_id = StoreValue(*bridge_state, result); return napi_ok; } -extern "C" int snapi_bridge_has_named_property(SnapiEnvState* env_state, uint32_t obj_id, - const char* name, - int* result) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_has_named_property(SnapiEnvState *env_state, + uint32_t obj_id, + const char *name, int *result) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value obj = LoadValue(*bridge_state, obj_id); - if (!obj || !name) return napi_invalid_arg; + if (!obj || !name) + return napi_invalid_arg; bool has; napi_status s = napi_has_named_property(env, obj, name, &has); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *result = has ? 1 : 0; return napi_ok; } -extern "C" int snapi_bridge_set_element(SnapiEnvState* env_state, uint32_t obj_id, uint32_t index, +extern "C" int snapi_bridge_set_element(SnapiEnvState *env_state, + uint32_t obj_id, uint32_t index, uint32_t val_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value obj = LoadValue(*bridge_state, obj_id); napi_value val = LoadValue(*bridge_state, val_id); - if (!obj || !val) return napi_invalid_arg; + if (!obj || !val) + return napi_invalid_arg; return napi_set_element(env, obj, index, val); } -extern "C" int snapi_bridge_get_element(SnapiEnvState* env_state, uint32_t obj_id, uint32_t index, - uint32_t* out_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_get_element(SnapiEnvState *env_state, + uint32_t obj_id, uint32_t index, + uint32_t *out_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value obj = LoadValue(*bridge_state, obj_id); - if (!obj) return napi_invalid_arg; + if (!obj) + return napi_invalid_arg; napi_value result; napi_status s = napi_get_element(env, obj, index, &result); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *out_id = StoreValue(*bridge_state, result); return napi_ok; } -extern "C" int snapi_bridge_has_element(SnapiEnvState* env_state, uint32_t obj_id, uint32_t index, - int* result) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_has_element(SnapiEnvState *env_state, + uint32_t obj_id, uint32_t index, + int *result) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value obj = LoadValue(*bridge_state, obj_id); - if (!obj) return napi_invalid_arg; + if (!obj) + return napi_invalid_arg; bool has; napi_status s = napi_has_element(env, obj, index, &has); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *result = has ? 1 : 0; return napi_ok; } -extern "C" int snapi_bridge_delete_element(SnapiEnvState* env_state, uint32_t obj_id, uint32_t index, - int* result) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_delete_element(SnapiEnvState *env_state, + uint32_t obj_id, uint32_t index, + int *result) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value obj = LoadValue(*bridge_state, obj_id); - if (!obj) return napi_invalid_arg; + if (!obj) + return napi_invalid_arg; bool deleted; napi_status s = napi_delete_element(env, obj, index, &deleted); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *result = deleted ? 1 : 0; return napi_ok; } -extern "C" int snapi_bridge_get_array_length(SnapiEnvState* env_state, uint32_t arr_id, - uint32_t* result) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_get_array_length(SnapiEnvState *env_state, + uint32_t arr_id, + uint32_t *result) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value arr = LoadValue(*bridge_state, arr_id); - if (!arr) return napi_invalid_arg; + if (!arr) + return napi_invalid_arg; return napi_get_array_length(env, arr, result); } -extern "C" int snapi_bridge_get_property_names(SnapiEnvState* env_state, uint32_t obj_id, - uint32_t* out_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_get_property_names(SnapiEnvState *env_state, + uint32_t obj_id, + uint32_t *out_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value obj = LoadValue(*bridge_state, obj_id); - if (!obj) return napi_invalid_arg; + if (!obj) + return napi_invalid_arg; napi_value result; napi_status s = napi_get_property_names(env, obj, &result); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *out_id = StoreValue(*bridge_state, result); return napi_ok; } -extern "C" int snapi_bridge_get_all_property_names(SnapiEnvState* env_state, uint32_t obj_id, - int mode, int filter, - int conversion, - uint32_t* out_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_get_all_property_names(SnapiEnvState *env_state, + uint32_t obj_id, int mode, + int filter, int conversion, + uint32_t *out_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value obj = LoadValue(*bridge_state, obj_id); - if (!obj) return napi_invalid_arg; + if (!obj) + return napi_invalid_arg; napi_value result; napi_status s = napi_get_all_property_names( env, obj, (napi_key_collection_mode)mode, (napi_key_filter)filter, (napi_key_conversion)conversion, &result); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *out_id = StoreValue(*bridge_state, result); return napi_ok; } -extern "C" int snapi_bridge_get_prototype(SnapiEnvState* env_state, uint32_t obj_id, uint32_t* out_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_get_prototype(SnapiEnvState *env_state, + uint32_t obj_id, uint32_t *out_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value obj = LoadValue(*bridge_state, obj_id); - if (!obj) return napi_invalid_arg; + if (!obj) + return napi_invalid_arg; napi_value result; napi_status s = napi_get_prototype(env, obj, &result); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *out_id = StoreValue(*bridge_state, result); return napi_ok; } -extern "C" int snapi_bridge_object_freeze(SnapiEnvState* env_state, uint32_t obj_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_object_freeze(SnapiEnvState *env_state, + uint32_t obj_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value obj = LoadValue(*bridge_state, obj_id); - if (!obj) return napi_invalid_arg; + if (!obj) + return napi_invalid_arg; return napi_object_freeze(env, obj); } -extern "C" int snapi_bridge_object_seal(SnapiEnvState* env_state, uint32_t obj_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_object_seal(SnapiEnvState *env_state, + uint32_t obj_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value obj = LoadValue(*bridge_state, obj_id); - if (!obj) return napi_invalid_arg; + if (!obj) + return napi_invalid_arg; return napi_object_seal(env, obj); } @@ -1155,17 +1465,22 @@ extern "C" int snapi_bridge_object_seal(SnapiEnvState* env_state, uint32_t obj_i // Comparison // ============================================================ -extern "C" int snapi_bridge_strict_equals(SnapiEnvState* env_state, uint32_t a_id, uint32_t b_id, - int* result) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_strict_equals(SnapiEnvState *env_state, + uint32_t a_id, uint32_t b_id, + int *result) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value a = LoadValue(*bridge_state, a_id); napi_value b = LoadValue(*bridge_state, b_id); - if (!a || !b) return napi_invalid_arg; + if (!a || !b) + return napi_invalid_arg; bool eq; napi_status s = napi_strict_equals(env, a, b, &eq); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *result = eq ? 1 : 0; return napi_ok; } @@ -1174,103 +1489,138 @@ extern "C" int snapi_bridge_strict_equals(SnapiEnvState* env_state, uint32_t a_i // Error handling // ============================================================ -extern "C" int snapi_bridge_create_error(SnapiEnvState* env_state, uint32_t code_id, uint32_t msg_id, - uint32_t* out_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_create_error(SnapiEnvState *env_state, + uint32_t code_id, uint32_t msg_id, + uint32_t *out_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; - napi_value code = LoadValue(*bridge_state, code_id); // can be null (0) + napi_value code = LoadValue(*bridge_state, code_id); // can be null (0) napi_value msg = LoadValue(*bridge_state, msg_id); - if (!msg) return napi_invalid_arg; + if (!msg) + return napi_invalid_arg; napi_value result; napi_status s = napi_create_error(env, code, msg, &result); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *out_id = StoreValue(*bridge_state, result); return napi_ok; } -extern "C" int snapi_bridge_create_type_error(SnapiEnvState* env_state, uint32_t code_id, - uint32_t msg_id, - uint32_t* out_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_create_type_error(SnapiEnvState *env_state, + uint32_t code_id, uint32_t msg_id, + uint32_t *out_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value code = LoadValue(*bridge_state, code_id); napi_value msg = LoadValue(*bridge_state, msg_id); - if (!msg) return napi_invalid_arg; + if (!msg) + return napi_invalid_arg; napi_value result; napi_status s = napi_create_type_error(env, code, msg, &result); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *out_id = StoreValue(*bridge_state, result); return napi_ok; } -extern "C" int snapi_bridge_create_range_error(SnapiEnvState* env_state, uint32_t code_id, +extern "C" int snapi_bridge_create_range_error(SnapiEnvState *env_state, + uint32_t code_id, uint32_t msg_id, - uint32_t* out_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; + uint32_t *out_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value code = LoadValue(*bridge_state, code_id); napi_value msg = LoadValue(*bridge_state, msg_id); - if (!msg) return napi_invalid_arg; + if (!msg) + return napi_invalid_arg; napi_value result; napi_status s = napi_create_range_error(env, code, msg, &result); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *out_id = StoreValue(*bridge_state, result); return napi_ok; } -extern "C" int snapi_bridge_throw(SnapiEnvState* env_state, uint32_t error_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_throw(SnapiEnvState *env_state, uint32_t error_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value error = LoadValue(*bridge_state, error_id); - if (!error) return napi_invalid_arg; + if (!error) + return napi_invalid_arg; return napi_throw(env, error); } -extern "C" int snapi_bridge_throw_error(SnapiEnvState* env_state, const char* code, const char* msg) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_throw_error(SnapiEnvState *env_state, + const char *code, const char *msg) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; return napi_throw_error(env, code, msg); } -extern "C" int snapi_bridge_throw_type_error(SnapiEnvState* env_state, const char* code, - const char* msg) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_throw_type_error(SnapiEnvState *env_state, + const char *code, + const char *msg) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; return napi_throw_type_error(env, code, msg); } -extern "C" int snapi_bridge_throw_range_error(SnapiEnvState* env_state, const char* code, - const char* msg) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_throw_range_error(SnapiEnvState *env_state, + const char *code, + const char *msg) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; return napi_throw_range_error(env, code, msg); } -extern "C" int snapi_bridge_is_exception_pending(SnapiEnvState* env_state, int* result) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_is_exception_pending(SnapiEnvState *env_state, + int *result) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; bool pending; napi_status s = napi_is_exception_pending(env, &pending); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *result = pending ? 1 : 0; return napi_ok; } -extern "C" int snapi_bridge_get_and_clear_last_exception(SnapiEnvState* env_state, uint32_t* out_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int +snapi_bridge_get_and_clear_last_exception(SnapiEnvState *env_state, + uint32_t *out_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value result; napi_status s = napi_get_and_clear_last_exception(env, &result); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *out_id = StoreValue(*bridge_state, result); return napi_ok; } @@ -1279,15 +1629,20 @@ extern "C" int snapi_bridge_get_and_clear_last_exception(SnapiEnvState* env_stat // Symbol // ============================================================ -extern "C" int snapi_bridge_create_symbol(SnapiEnvState* env_state, uint32_t description_id, - uint32_t* out_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_create_symbol(SnapiEnvState *env_state, + uint32_t description_id, + uint32_t *out_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; - napi_value description = LoadValue(*bridge_state, description_id); // can be null (0) + napi_value description = + LoadValue(*bridge_state, description_id); // can be null (0) napi_value result; napi_status s = napi_create_symbol(env, description, &result); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *out_id = StoreValue(*bridge_state, result); return napi_ok; } @@ -1296,55 +1651,73 @@ extern "C" int snapi_bridge_create_symbol(SnapiEnvState* env_state, uint32_t des // BigInt // ============================================================ -extern "C" int snapi_bridge_create_bigint_int64(SnapiEnvState* env_state, int64_t value, - uint32_t* out_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_create_bigint_int64(SnapiEnvState *env_state, + int64_t value, + uint32_t *out_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value result; napi_status s = napi_create_bigint_int64(env, value, &result); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *out_id = StoreValue(*bridge_state, result); return napi_ok; } -extern "C" int snapi_bridge_create_bigint_uint64(SnapiEnvState* env_state, uint64_t value, - uint32_t* out_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_create_bigint_uint64(SnapiEnvState *env_state, + uint64_t value, + uint32_t *out_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value result; napi_status s = napi_create_bigint_uint64(env, value, &result); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *out_id = StoreValue(*bridge_state, result); return napi_ok; } -extern "C" int snapi_bridge_get_value_bigint_int64(SnapiEnvState* env_state, uint32_t id, int64_t* value, - int* lossless) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_get_value_bigint_int64(SnapiEnvState *env_state, + uint32_t id, int64_t *value, + int *lossless) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value val = LoadValue(*bridge_state, id); - if (!val) return napi_invalid_arg; + if (!val) + return napi_invalid_arg; bool loss; napi_status s = napi_get_value_bigint_int64(env, val, value, &loss); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *lossless = loss ? 1 : 0; return napi_ok; } -extern "C" int snapi_bridge_get_value_bigint_uint64(SnapiEnvState* env_state, uint32_t id, - uint64_t* value, - int* lossless) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_get_value_bigint_uint64(SnapiEnvState *env_state, + uint32_t id, + uint64_t *value, + int *lossless) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value val = LoadValue(*bridge_state, id); - if (!val) return napi_invalid_arg; + if (!val) + return napi_invalid_arg; bool loss; napi_status s = napi_get_value_bigint_uint64(env, val, value, &loss); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *lossless = loss ? 1 : 0; return napi_ok; } @@ -1353,23 +1726,31 @@ extern "C" int snapi_bridge_get_value_bigint_uint64(SnapiEnvState* env_state, ui // Date // ============================================================ -extern "C" int snapi_bridge_create_date(SnapiEnvState* env_state, double time, uint32_t* out_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_create_date(SnapiEnvState *env_state, double time, + uint32_t *out_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value result; napi_status s = napi_create_date(env, time, &result); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *out_id = StoreValue(*bridge_state, result); return napi_ok; } -extern "C" int snapi_bridge_get_date_value(SnapiEnvState* env_state, uint32_t id, double* result) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_get_date_value(SnapiEnvState *env_state, + uint32_t id, double *result) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value val = LoadValue(*bridge_state, id); - if (!val) return napi_invalid_arg; + if (!val) + return napi_invalid_arg; return napi_get_date_value(env, val, result); } @@ -1377,43 +1758,57 @@ extern "C" int snapi_bridge_get_date_value(SnapiEnvState* env_state, uint32_t id // Promise // ============================================================ -extern "C" int snapi_bridge_create_promise(SnapiEnvState* env_state, uint32_t* deferred_out, - uint32_t* out_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_create_promise(SnapiEnvState *env_state, + uint32_t *deferred_out, + uint32_t *out_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_deferred deferred; napi_value promise; napi_status s = napi_create_promise(env, &deferred, &promise); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *deferred_out = StoreDeferred(*bridge_state, deferred); *out_id = StoreValue(*bridge_state, promise); return napi_ok; } -extern "C" int snapi_bridge_resolve_deferred(SnapiEnvState* env_state, uint32_t deferred_id, +extern "C" int snapi_bridge_resolve_deferred(SnapiEnvState *env_state, + uint32_t deferred_id, uint32_t value_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_deferred d = LoadDeferred(*bridge_state, deferred_id); napi_value val = LoadValue(*bridge_state, value_id); - if (!d || !val) return napi_invalid_arg; + if (!d || !val) + return napi_invalid_arg; napi_status s = napi_resolve_deferred(env, d, val); - if (s == napi_ok) RemoveDeferred(*bridge_state, deferred_id); + if (s == napi_ok) + RemoveDeferred(*bridge_state, deferred_id); return s; } -extern "C" int snapi_bridge_reject_deferred(SnapiEnvState* env_state, uint32_t deferred_id, +extern "C" int snapi_bridge_reject_deferred(SnapiEnvState *env_state, + uint32_t deferred_id, uint32_t value_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_deferred d = LoadDeferred(*bridge_state, deferred_id); napi_value val = LoadValue(*bridge_state, value_id); - if (!d || !val) return napi_invalid_arg; + if (!d || !val) + return napi_invalid_arg; napi_status s = napi_reject_deferred(env, d, val); - if (s == napi_ok) RemoveDeferred(*bridge_state, deferred_id); + if (s == napi_ok) + RemoveDeferred(*bridge_state, deferred_id); return s; } @@ -1421,16 +1816,20 @@ extern "C" int snapi_bridge_reject_deferred(SnapiEnvState* env_state, uint32_t d // ArrayBuffer // ============================================================ -extern "C" int snapi_bridge_create_arraybuffer(SnapiEnvState* env_state, uint32_t byte_length, - uint32_t* out_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_create_arraybuffer(SnapiEnvState *env_state, + uint32_t byte_length, + uint32_t *out_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; - void* data; + void *data; napi_value result; napi_status s = napi_create_arraybuffer(env, (size_t)byte_length, &data, &result); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *out_id = StoreValue(*bridge_state, result); return napi_ok; } @@ -1439,20 +1838,20 @@ extern "C" int snapi_bridge_create_arraybuffer(SnapiEnvState* env_state, uint32_ // back to the guest heap and drops the boxed hint. Weak no-op fallback keeps // embedders without the Rust host linking (they never create finalized // externals). -extern "C" __attribute__((weak)) void napi_host_guest_heap_buffer_finalize( - void* /*data*/, void* /*hint*/) {} +extern "C" __attribute__((weak)) void +napi_host_guest_heap_buffer_finalize(void * /*data*/, void * /*hint*/) {} namespace { -void GuestHeapBufferFinalizeTrampoline(node_api_basic_env /*env*/, void* data, - void* hint) { +void GuestHeapBufferFinalizeTrampoline(node_api_basic_env /*env*/, void *data, + void *hint) { napi_host_guest_heap_buffer_finalize(data, hint); } -} // namespace +} // namespace // Forward-declare the Rust finalizer trampoline (defined in guest/callback.rs // via #[no_mangle]). Re-enters the guest and dispatches the guest's finalize // callback through the indirect function table. -extern "C" uint32_t snapi_host_invoke_wasm_finalizer(void* callback_ctx, +extern "C" uint32_t snapi_host_invoke_wasm_finalizer(void *callback_ctx, uint32_t guest_env, uint32_t wasm_fn_ptr, uint32_t data, @@ -1469,19 +1868,21 @@ namespace { // GC, with no way back into the guest — they attach via napi_add_finalizer // instead.) Allocated on registration, freed exactly once when it fires. struct GuestFinalizerRecord { - SnapiEnvState* state; + SnapiEnvState *state; uint32_t guest_env; uint32_t wasm_fn_ptr; uint32_t data; uint32_t hint; }; -void GuestFinalizerTrampoline(node_api_basic_env /*env*/, void* /*data*/, - void* hint) { - auto* record = static_cast(hint); - if (record == nullptr) return; - auto* bridge_state = LookupEnvState(record->state); - void* callback_ctx = +void GuestFinalizerTrampoline(node_api_basic_env /*env*/, void * /*data*/, + void *hint) { + auto *record = static_cast(hint); + if (record == nullptr) + return; + auto bridge_state_lease = LookupEnvState(record->state); + auto *bridge_state = bridge_state_lease.get(); + void *callback_ctx = bridge_state != nullptr ? bridge_state->active_callback_ctx.load(std::memory_order_acquire) : nullptr; @@ -1491,8 +1892,7 @@ void GuestFinalizerTrampoline(node_api_basic_env /*env*/, void* /*data*/, // WasmInterruptCallback). napi_env env = bridge_state->env; napi_handle_scope fin_scope = nullptr; - if (env != nullptr && - napi_open_handle_scope(env, &fin_scope) == napi_ok && + if (env != nullptr && napi_open_handle_scope(env, &fin_scope) == napi_ok && fin_scope != nullptr) { (void)CurrentFrame(*bridge_state); ScopeFrame frame; @@ -1516,11 +1916,11 @@ void GuestFinalizerTrampoline(node_api_basic_env /*env*/, void* /*data*/, delete record; } -GuestFinalizerRecord* MakeGuestFinalizerRecord(SnapiEnvState* state, +GuestFinalizerRecord *MakeGuestFinalizerRecord(SnapiEnvState *state, uint32_t guest_env, uint32_t wasm_fn_ptr, uint32_t data, uint32_t hint) { - auto* record = new (std::nothrow) GuestFinalizerRecord(); + auto *record = new (std::nothrow) GuestFinalizerRecord(); if (record != nullptr) { record->state = state; record->guest_env = guest_env; @@ -1530,27 +1930,31 @@ GuestFinalizerRecord* MakeGuestFinalizerRecord(SnapiEnvState* state, } return record; } -} // namespace +} // namespace // Like snapi_bridge_create_external_arraybuffer, but re-runs the guest's own // finalize callback (a wasm function pointer) when V8 collects the value, so // guest-owned external allocations are reclaimed instead of leaked. extern "C" int snapi_bridge_create_external_arraybuffer_guest_finalized( - SnapiEnvState* env_state, uint64_t data_addr, uint32_t byte_length, + SnapiEnvState *env_state, uint64_t data_addr, uint32_t byte_length, uint32_t guest_env, uint32_t wasm_fn_ptr, uint32_t finalize_data, - uint32_t finalize_hint, uint64_t* backing_store_token_out, - uint32_t* out_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; + uint32_t finalize_hint, uint64_t *backing_store_token_out, + uint32_t *out_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; - void* data = (void*)(uintptr_t)data_addr; + void *data = (void *)(uintptr_t)data_addr; napi_value result; napi_status s = napi_create_external_arraybuffer( env, data, (size_t)byte_length, nullptr, nullptr, &result); - if (s != napi_ok) return s; - auto* record = MakeGuestFinalizerRecord(env_state, guest_env, wasm_fn_ptr, + if (s != napi_ok) + return s; + auto *record = MakeGuestFinalizerRecord(env_state, guest_env, wasm_fn_ptr, finalize_data, finalize_hint); - if (record == nullptr) return napi_generic_failure; + if (record == nullptr) + return napi_generic_failure; s = napi_add_finalizer(env, result, nullptr, GuestFinalizerTrampoline, record, nullptr); if (s != napi_ok) { @@ -1558,7 +1962,8 @@ extern "C" int snapi_bridge_create_external_arraybuffer_guest_finalized( return s; } if (backing_store_token_out) { - *backing_store_token_out = napi_v8_get_arraybuffer_backing_store_token(env, result); + *backing_store_token_out = + napi_v8_get_arraybuffer_backing_store_token(env, result); } *out_id = StoreValue(*bridge_state, result); return napi_ok; @@ -1566,21 +1971,25 @@ extern "C" int snapi_bridge_create_external_arraybuffer_guest_finalized( // Buffer twin of snapi_bridge_create_external_arraybuffer_guest_finalized. extern "C" int snapi_bridge_create_external_buffer_guest_finalized( - SnapiEnvState* env_state, uint64_t data_addr, uint32_t byte_length, + SnapiEnvState *env_state, uint64_t data_addr, uint32_t byte_length, uint32_t guest_env, uint32_t wasm_fn_ptr, uint32_t finalize_data, - uint32_t finalize_hint, uint64_t* backing_store_token_out, - uint32_t* out_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; + uint32_t finalize_hint, uint64_t *backing_store_token_out, + uint32_t *out_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; - void* data = (void*)(uintptr_t)data_addr; + void *data = (void *)(uintptr_t)data_addr; napi_value result; - napi_status s = napi_create_external_buffer( - env, (size_t)byte_length, data, nullptr, nullptr, &result); - if (s != napi_ok) return s; - auto* record = MakeGuestFinalizerRecord(env_state, guest_env, wasm_fn_ptr, + napi_status s = napi_create_external_buffer(env, (size_t)byte_length, data, + nullptr, nullptr, &result); + if (s != napi_ok) + return s; + auto *record = MakeGuestFinalizerRecord(env_state, guest_env, wasm_fn_ptr, finalize_data, finalize_hint); - if (record == nullptr) return napi_generic_failure; + if (record == nullptr) + return napi_generic_failure; s = napi_add_finalizer(env, result, nullptr, GuestFinalizerTrampoline, record, nullptr); if (s != napi_ok) { @@ -1588,7 +1997,8 @@ extern "C" int snapi_bridge_create_external_buffer_guest_finalized( return s; } if (backing_store_token_out) { - *backing_store_token_out = napi_v8_get_arraybuffer_view_backing_store_token(env, result); + *backing_store_token_out = + napi_v8_get_arraybuffer_view_backing_store_token(env, result); } *out_id = StoreValue(*bridge_state, result); return napi_ok; @@ -1596,22 +2006,38 @@ extern "C" int snapi_bridge_create_external_buffer_guest_finalized( // Like snapi_bridge_create_external_arraybuffer, but the buffer's lifetime is // tied to the JS value: when V8 collects it, `finalize_hint` is handed to the -// Rust guest-heap finalizer, which frees the guest allocation. Ownership of -// `finalize_hint` transfers only on success. +// Rust guest-heap finalizer, which frees the guest allocation. The ownership +// output reports the actual backing-store transfer, including error returns. extern "C" int snapi_bridge_create_external_arraybuffer_finalized( - SnapiEnvState* env_state, uint64_t data_addr, uint32_t byte_length, - void* finalize_hint, uint64_t* backing_store_token_out, uint32_t* out_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; + SnapiEnvState *env_state, uint64_t data_addr, uint32_t byte_length, + void *finalize_hint, uint64_t *backing_store_token_out, uint32_t *out_id, + int *ownership_transferred_out) { + if (ownership_transferred_out != nullptr) *ownership_transferred_out = 0; + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; - void* data = (void*)(uintptr_t)data_addr; + void *data = (void *)(uintptr_t)data_addr; napi_value result; - napi_status s = napi_create_external_arraybuffer( - env, data, (size_t)byte_length, GuestHeapBufferFinalizeTrampoline, - finalize_hint, &result); - if (s != napi_ok) return s; + napi_v8_external_transfer_observer observer; + napi_v8_begin_external_transfer_observation(&observer, finalize_hint, + ownership_transferred_out); + napi_status s; + try { + s = napi_create_external_arraybuffer( + env, data, (size_t)byte_length, GuestHeapBufferFinalizeTrampoline, + finalize_hint, &result); + } catch (const std::bad_alloc&) { + napi_v8_end_external_transfer_observation(&observer); + return napi_generic_failure; + } + napi_v8_end_external_transfer_observation(&observer); + if (s != napi_ok) + return s; if (backing_store_token_out) { - *backing_store_token_out = napi_v8_get_arraybuffer_backing_store_token(env, result); + *backing_store_token_out = + napi_v8_get_arraybuffer_backing_store_token(env, result); } *out_id = StoreValue(*bridge_state, result); return napi_ok; @@ -1619,141 +2045,192 @@ extern "C" int snapi_bridge_create_external_arraybuffer_finalized( // Buffer twin of snapi_bridge_create_external_arraybuffer_finalized. extern "C" int snapi_bridge_create_external_buffer_finalized( - SnapiEnvState* env_state, uint64_t data_addr, uint32_t byte_length, - void* finalize_hint, uint64_t* backing_store_token_out, uint32_t* out_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; + SnapiEnvState *env_state, uint64_t data_addr, uint32_t byte_length, + void *finalize_hint, uint64_t *backing_store_token_out, uint32_t *out_id, + int *ownership_transferred_out) { + if (ownership_transferred_out != nullptr) *ownership_transferred_out = 0; + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; - void* data = (void*)(uintptr_t)data_addr; + void *data = (void *)(uintptr_t)data_addr; napi_value result; - napi_status s = napi_create_external_buffer( - env, (size_t)byte_length, data, GuestHeapBufferFinalizeTrampoline, - finalize_hint, &result); - if (s != napi_ok) return s; + napi_v8_external_transfer_observer observer; + napi_v8_begin_external_transfer_observation(&observer, finalize_hint, + ownership_transferred_out); + napi_status s; + try { + s = napi_create_external_buffer(env, (size_t)byte_length, data, + GuestHeapBufferFinalizeTrampoline, + finalize_hint, &result); + } catch (const std::bad_alloc&) { + napi_v8_end_external_transfer_observation(&observer); + return napi_generic_failure; + } + napi_v8_end_external_transfer_observation(&observer); + if (s != napi_ok) + return s; if (backing_store_token_out) { - *backing_store_token_out = napi_v8_get_arraybuffer_view_backing_store_token(env, result); + *backing_store_token_out = + napi_v8_get_arraybuffer_view_backing_store_token(env, result); } *out_id = StoreValue(*bridge_state, result); return napi_ok; } -extern "C" int snapi_bridge_create_external_arraybuffer(SnapiEnvState* env_state, uint64_t data_addr, - uint32_t byte_length, - uint64_t* backing_store_token_out, - uint32_t* out_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_create_external_arraybuffer( + SnapiEnvState *env_state, uint64_t data_addr, uint32_t byte_length, + uint64_t *backing_store_token_out, uint32_t *out_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; - void* data = (void*)(uintptr_t)data_addr; + void *data = (void *)(uintptr_t)data_addr; napi_value result; napi_status s = napi_create_external_arraybuffer( env, data, (size_t)byte_length, nullptr, nullptr, &result); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; if (backing_store_token_out) { - *backing_store_token_out = napi_v8_get_arraybuffer_backing_store_token(env, result); + *backing_store_token_out = + napi_v8_get_arraybuffer_backing_store_token(env, result); } *out_id = StoreValue(*bridge_state, result); return napi_ok; } -extern "C" int snapi_bridge_create_external_buffer(SnapiEnvState* env_state, uint64_t data_addr, - uint32_t byte_length, - uint64_t* backing_store_token_out, - uint32_t* out_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_create_external_buffer( + SnapiEnvState *env_state, uint64_t data_addr, uint32_t byte_length, + uint64_t *backing_store_token_out, uint32_t *out_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; - void* data = (void*)(uintptr_t)data_addr; + void *data = (void *)(uintptr_t)data_addr; napi_value result; - napi_status s = napi_create_external_buffer( - env, (size_t)byte_length, data, nullptr, nullptr, &result); - if (s != napi_ok) return s; + napi_status s = napi_create_external_buffer(env, (size_t)byte_length, data, + nullptr, nullptr, &result); + if (s != napi_ok) + return s; if (backing_store_token_out) { - *backing_store_token_out = napi_v8_get_arraybuffer_view_backing_store_token(env, result); + *backing_store_token_out = + napi_v8_get_arraybuffer_view_backing_store_token(env, result); } *out_id = StoreValue(*bridge_state, result); return napi_ok; } -extern "C" int snapi_bridge_is_sharedarraybuffer(SnapiEnvState* env_state, uint32_t id, int* result) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_is_sharedarraybuffer(SnapiEnvState *env_state, + uint32_t id, int *result) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value val = LoadValue(*bridge_state, id); - if (!val) return napi_invalid_arg; + if (!val) + return napi_invalid_arg; bool is_sab = false; napi_status s = node_api_is_sharedarraybuffer(env, val, &is_sab); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *result = is_sab ? 1 : 0; return napi_ok; } -extern "C" int snapi_bridge_create_sharedarraybuffer(SnapiEnvState* env_state, uint32_t byte_length, - uint64_t* data_out, - uint32_t* out_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_create_sharedarraybuffer(SnapiEnvState *env_state, + uint32_t byte_length, + uint64_t *data_out, + uint32_t *out_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; - void* data = nullptr; + void *data = nullptr; napi_value result; - napi_status s = - node_api_create_sharedarraybuffer(env, (size_t)byte_length, &data, &result); - if (s != napi_ok) return s; - if (data_out) *data_out = (uint64_t)(uintptr_t)data; + napi_status s = node_api_create_sharedarraybuffer(env, (size_t)byte_length, + &data, &result); + if (s != napi_ok) + return s; + if (data_out) + *data_out = (uint64_t)(uintptr_t)data; *out_id = StoreValue(*bridge_state, result); return napi_ok; } -extern "C" int snapi_bridge_node_api_set_prototype(SnapiEnvState* env_state, uint32_t object_id, +extern "C" int snapi_bridge_node_api_set_prototype(SnapiEnvState *env_state, + uint32_t object_id, uint32_t prototype_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value object = LoadValue(*bridge_state, object_id); napi_value prototype = LoadValue(*bridge_state, prototype_id); - if (!object || !prototype) return napi_invalid_arg; + if (!object || !prototype) + return napi_invalid_arg; return node_api_set_prototype(env, object, prototype); } -extern "C" int snapi_bridge_get_arraybuffer_info(SnapiEnvState* env_state, uint32_t id, - uint64_t* data_out, - uint32_t* byte_length, - uint64_t* backing_store_token_out) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int +snapi_bridge_get_arraybuffer_info(SnapiEnvState *env_state, uint32_t id, + uint64_t *data_out, uint32_t *byte_length, + uint64_t *backing_store_token_out) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value val = LoadValue(*bridge_state, id); - if (!val) return napi_invalid_arg; - void* data; + if (!val) + return napi_invalid_arg; + void *data; size_t len; napi_status s = napi_get_arraybuffer_info(env, val, &data, &len); - if (s != napi_ok) return s; - if (data_out) *data_out = (uint64_t)(uintptr_t)data; + if (s != napi_ok) + return s; + if (data_out) + *data_out = (uint64_t)(uintptr_t)data; *byte_length = (uint32_t)len; if (backing_store_token_out) { - *backing_store_token_out = napi_v8_get_arraybuffer_backing_store_token(env, val); + *backing_store_token_out = + napi_v8_get_arraybuffer_backing_store_token(env, val); } return napi_ok; } -extern "C" int snapi_bridge_detach_arraybuffer(SnapiEnvState* env_state, uint32_t id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_detach_arraybuffer(SnapiEnvState *env_state, + uint32_t id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value val = LoadValue(*bridge_state, id); - if (!val) return napi_invalid_arg; + if (!val) + return napi_invalid_arg; return napi_detach_arraybuffer(env, val); } -extern "C" int snapi_bridge_is_detached_arraybuffer(SnapiEnvState* env_state, uint32_t id, int* result) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_is_detached_arraybuffer(SnapiEnvState *env_state, + uint32_t id, int *result) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value val = LoadValue(*bridge_state, id); - if (!val) return napi_invalid_arg; + if (!val) + return napi_invalid_arg; bool is; napi_status s = napi_is_detached_arraybuffer(env, val, &is); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *result = is ? 1 : 0; return napi_ok; } @@ -1762,50 +2239,63 @@ extern "C" int snapi_bridge_is_detached_arraybuffer(SnapiEnvState* env_state, ui // TypedArray // ============================================================ -extern "C" int snapi_bridge_create_typedarray(SnapiEnvState* env_state, int type, uint32_t length, +extern "C" int snapi_bridge_create_typedarray(SnapiEnvState *env_state, + int type, uint32_t length, uint32_t arraybuffer_id, uint32_t byte_offset, - uint32_t* out_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; + uint32_t *out_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value arraybuffer = LoadValue(*bridge_state, arraybuffer_id); - if (!arraybuffer) return napi_invalid_arg; + if (!arraybuffer) + return napi_invalid_arg; napi_value result; - napi_status s = napi_create_typedarray( - env, (napi_typedarray_type)type, (size_t)length, arraybuffer, - (size_t)byte_offset, &result); - if (s != napi_ok) return s; + napi_status s = + napi_create_typedarray(env, (napi_typedarray_type)type, (size_t)length, + arraybuffer, (size_t)byte_offset, &result); + if (s != napi_ok) + return s; *out_id = StoreValue(*bridge_state, result); return napi_ok; } -extern "C" int snapi_bridge_get_typedarray_info(SnapiEnvState* env_state, uint32_t id, int* type_out, - uint32_t* length_out, - uint64_t* data_out, - uint32_t* arraybuffer_out, - uint32_t* byte_offset_out, - uint64_t* backing_store_token_out) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_get_typedarray_info( + SnapiEnvState *env_state, uint32_t id, int *type_out, uint32_t *length_out, + uint64_t *data_out, uint32_t *arraybuffer_out, uint32_t *byte_offset_out, + uint64_t *backing_store_token_out) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value val = LoadValue(*bridge_state, id); - if (!val) return napi_invalid_arg; + if (!val) + return napi_invalid_arg; napi_typedarray_type type; size_t length; - void* data = nullptr; + void *data = nullptr; napi_value arraybuffer; size_t byte_offset; napi_status s = napi_get_typedarray_info(env, val, &type, &length, &data, &arraybuffer, &byte_offset); - if (s != napi_ok) return s; - if (type_out) *type_out = (int)type; - if (length_out) *length_out = (uint32_t)length; - if (data_out) *data_out = (uint64_t)(uintptr_t)data; - if (arraybuffer_out) *arraybuffer_out = StoreValue(*bridge_state, arraybuffer); - if (byte_offset_out) *byte_offset_out = (uint32_t)byte_offset; + if (s != napi_ok) + return s; + if (type_out) + *type_out = (int)type; + if (length_out) + *length_out = (uint32_t)length; + if (data_out) + *data_out = (uint64_t)(uintptr_t)data; + if (arraybuffer_out) + *arraybuffer_out = StoreValue(*bridge_state, arraybuffer); + if (byte_offset_out) + *byte_offset_out = (uint32_t)byte_offset; if (backing_store_token_out) { - *backing_store_token_out = napi_v8_get_arraybuffer_view_backing_store_token(env, val); + *backing_store_token_out = + napi_v8_get_arraybuffer_view_backing_store_token(env, val); } return napi_ok; } @@ -1814,47 +2304,59 @@ extern "C" int snapi_bridge_get_typedarray_info(SnapiEnvState* env_state, uint32 // DataView // ============================================================ -extern "C" int snapi_bridge_create_dataview(SnapiEnvState* env_state, uint32_t byte_length, +extern "C" int snapi_bridge_create_dataview(SnapiEnvState *env_state, + uint32_t byte_length, uint32_t arraybuffer_id, uint32_t byte_offset, - uint32_t* out_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; + uint32_t *out_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value arraybuffer = LoadValue(*bridge_state, arraybuffer_id); - if (!arraybuffer) return napi_invalid_arg; + if (!arraybuffer) + return napi_invalid_arg; napi_value result; napi_status s = napi_create_dataview(env, (size_t)byte_length, arraybuffer, (size_t)byte_offset, &result); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *out_id = StoreValue(*bridge_state, result); return napi_ok; } -extern "C" int snapi_bridge_get_dataview_info(SnapiEnvState* env_state, uint32_t id, - uint32_t* byte_length_out, - uint64_t* data_out, - uint32_t* arraybuffer_out, - uint32_t* byte_offset_out, - uint64_t* backing_store_token_out) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_get_dataview_info( + SnapiEnvState *env_state, uint32_t id, uint32_t *byte_length_out, + uint64_t *data_out, uint32_t *arraybuffer_out, uint32_t *byte_offset_out, + uint64_t *backing_store_token_out) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value val = LoadValue(*bridge_state, id); - if (!val) return napi_invalid_arg; + if (!val) + return napi_invalid_arg; size_t byte_length; - void* data = nullptr; + void *data = nullptr; napi_value arraybuffer; size_t byte_offset; napi_status s = napi_get_dataview_info(env, val, &byte_length, &data, &arraybuffer, &byte_offset); - if (s != napi_ok) return s; - if (byte_length_out) *byte_length_out = (uint32_t)byte_length; - if (data_out) *data_out = (uint64_t)(uintptr_t)data; - if (arraybuffer_out) *arraybuffer_out = StoreValue(*bridge_state, arraybuffer); - if (byte_offset_out) *byte_offset_out = (uint32_t)byte_offset; + if (s != napi_ok) + return s; + if (byte_length_out) + *byte_length_out = (uint32_t)byte_length; + if (data_out) + *data_out = (uint64_t)(uintptr_t)data; + if (arraybuffer_out) + *arraybuffer_out = StoreValue(*bridge_state, arraybuffer); + if (byte_offset_out) + *byte_offset_out = (uint32_t)byte_offset; if (backing_store_token_out) { - *backing_store_token_out = napi_v8_get_arraybuffer_view_backing_store_token(env, val); + *backing_store_token_out = + napi_v8_get_arraybuffer_view_backing_store_token(env, val); } return napi_ok; } @@ -1862,13 +2364,16 @@ extern "C" int snapi_bridge_get_dataview_info(SnapiEnvState* env_state, uint32_t // Attach the guest-heap finalizer to an arbitrary value: `finalize_hint` is // handed to the Rust guest-heap finalizer when V8 collects the value. Used to // tie the lifetime of a snapshot copy (foreign backing stores) to its value. -extern "C" int snapi_bridge_attach_guest_heap_finalizer(SnapiEnvState* env_state, - uint32_t id, - void* finalize_hint) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int +snapi_bridge_attach_guest_heap_finalizer(SnapiEnvState *env_state, uint32_t id, + void *finalize_hint) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_value val = LoadValue(*bridge_state, id); - if (!val) return napi_invalid_arg; + if (!val) + return napi_invalid_arg; return napi_add_finalizer(bridge_state->env, val, nullptr, GuestHeapBufferFinalizeTrampoline, finalize_hint, nullptr); @@ -1878,30 +2383,39 @@ extern "C" int snapi_bridge_attach_guest_heap_finalizer(SnapiEnvState* env_state // External values // ============================================================ -extern "C" int snapi_bridge_create_external(SnapiEnvState* env_state, uint64_t data_val, - uint32_t* out_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_create_external(SnapiEnvState *env_state, + uint64_t data_val, + uint32_t *out_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; // Store arbitrary u64 data value as a void*. No finalizer. napi_value result; - napi_status s = napi_create_external(env, (void*)(uintptr_t)data_val, + napi_status s = napi_create_external(env, (void *)(uintptr_t)data_val, nullptr, nullptr, &result); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *out_id = StoreValue(*bridge_state, result); return napi_ok; } -extern "C" int snapi_bridge_get_value_external(SnapiEnvState* env_state, uint32_t id, - uint64_t* data_out) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_get_value_external(SnapiEnvState *env_state, + uint32_t id, + uint64_t *data_out) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value val = LoadValue(*bridge_state, id); - if (!val) return napi_invalid_arg; - void* data; + if (!val) + return napi_invalid_arg; + void *data; napi_status s = napi_get_value_external(env, val, &data); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *data_out = (uint64_t)(uintptr_t)data; return napi_ok; } @@ -1910,60 +2424,83 @@ extern "C" int snapi_bridge_get_value_external(SnapiEnvState* env_state, uint32_ // References // ============================================================ -extern "C" int snapi_bridge_create_reference(SnapiEnvState* env_state, uint32_t value_id, +extern "C" int snapi_bridge_create_reference(SnapiEnvState *env_state, + uint32_t value_id, uint32_t initial_refcount, - uint32_t* ref_out) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; + uint32_t *ref_out) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value val = LoadValue(*bridge_state, value_id); - if (!val) return napi_invalid_arg; + if (!val) + return napi_invalid_arg; napi_ref ref; napi_status s = napi_create_reference(env, val, initial_refcount, &ref); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *ref_out = StoreRef(*bridge_state, ref); return napi_ok; } -extern "C" int snapi_bridge_delete_reference(SnapiEnvState* env_state, uint32_t ref_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_delete_reference(SnapiEnvState *env_state, + uint32_t ref_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_ref ref = LoadRef(*bridge_state, ref_id); - if (!ref) return napi_invalid_arg; + if (!ref) + return napi_invalid_arg; napi_status s = napi_delete_reference(env, ref); - if (s == napi_ok) RemoveRef(*bridge_state, ref_id); + if (s == napi_ok) + RemoveRef(*bridge_state, ref_id); return s; } -extern "C" int snapi_bridge_reference_ref(SnapiEnvState* env_state, uint32_t ref_id, uint32_t* result) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_reference_ref(SnapiEnvState *env_state, + uint32_t ref_id, uint32_t *result) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_ref ref = LoadRef(*bridge_state, ref_id); - if (!ref) return napi_invalid_arg; + if (!ref) + return napi_invalid_arg; return napi_reference_ref(env, ref, result); } -extern "C" int snapi_bridge_reference_unref(SnapiEnvState* env_state, uint32_t ref_id, uint32_t* result) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_reference_unref(SnapiEnvState *env_state, + uint32_t ref_id, uint32_t *result) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_ref ref = LoadRef(*bridge_state, ref_id); - if (!ref) return napi_invalid_arg; + if (!ref) + return napi_invalid_arg; return napi_reference_unref(env, ref, result); } -extern "C" int snapi_bridge_get_reference_value(SnapiEnvState* env_state, uint32_t ref_id, - uint32_t* out_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_get_reference_value(SnapiEnvState *env_state, + uint32_t ref_id, + uint32_t *out_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_ref ref = LoadRef(*bridge_state, ref_id); - if (!ref) return napi_invalid_arg; + if (!ref) + return napi_invalid_arg; napi_value result; napi_status s = napi_get_reference_value(env, ref, &result); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *out_id = StoreValue(*bridge_state, result); return napi_ok; } @@ -1972,26 +2509,34 @@ extern "C" int snapi_bridge_get_reference_value(SnapiEnvState* env_state, uint32 // Handle scopes // ============================================================ -extern "C" int snapi_bridge_open_handle_scope(SnapiEnvState* env_state, uint32_t* scope_out) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_open_handle_scope(SnapiEnvState *env_state, + uint32_t *scope_out) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; - (void)CurrentFrame(*bridge_state); // materialize the root frame first + (void)CurrentFrame(*bridge_state); // materialize the root frame first napi_handle_scope scope = nullptr; napi_status s = napi_open_handle_scope(env, &scope); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; ScopeFrame frame; frame.scope = scope; frame.id = bridge_state->next_scope_id++; - if (frame.id == 0) frame.id = bridge_state->next_scope_id++; + if (frame.id == 0) + frame.id = bridge_state->next_scope_id++; bridge_state->scope_frames.push_back(std::move(frame)); *scope_out = bridge_state->scope_frames.back().id; return napi_ok; } -extern "C" int snapi_bridge_close_handle_scope(SnapiEnvState* env_state, uint32_t scope_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_close_handle_scope(SnapiEnvState *env_state, + uint32_t scope_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; const int idx = FindFrameById(*bridge_state, scope_id); if (idx < 0 || bridge_state->scope_frames[idx].scope == nullptr) { return napi_invalid_arg; @@ -2005,26 +2550,36 @@ extern "C" int snapi_bridge_close_handle_scope(SnapiEnvState* env_state, uint32_ return napi_ok; } -extern "C" int snapi_bridge_open_escapable_handle_scope(SnapiEnvState* env_state, uint32_t* scope_out) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int +snapi_bridge_open_escapable_handle_scope(SnapiEnvState *env_state, + uint32_t *scope_out) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; - (void)CurrentFrame(*bridge_state); // materialize the root frame first + (void)CurrentFrame(*bridge_state); // materialize the root frame first napi_escapable_handle_scope scope = nullptr; napi_status s = napi_open_escapable_handle_scope(env, &scope); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; ScopeFrame frame; frame.esc_scope = scope; frame.id = bridge_state->next_scope_id++; - if (frame.id == 0) frame.id = bridge_state->next_scope_id++; + if (frame.id == 0) + frame.id = bridge_state->next_scope_id++; bridge_state->scope_frames.push_back(std::move(frame)); *scope_out = bridge_state->scope_frames.back().id; return napi_ok; } -extern "C" int snapi_bridge_close_escapable_handle_scope(SnapiEnvState* env_state, uint32_t scope_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int +snapi_bridge_close_escapable_handle_scope(SnapiEnvState *env_state, + uint32_t scope_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; const int idx = FindFrameById(*bridge_state, scope_id); if (idx < 0 || bridge_state->scope_frames[idx].esc_scope == nullptr) { return napi_invalid_arg; @@ -2036,11 +2591,14 @@ extern "C" int snapi_bridge_close_escapable_handle_scope(SnapiEnvState* env_stat return napi_ok; } -extern "C" int snapi_bridge_escape_handle(SnapiEnvState* env_state, uint32_t scope_id, +extern "C" int snapi_bridge_escape_handle(SnapiEnvState *env_state, + uint32_t scope_id, uint32_t escapee_id, - uint32_t* out_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; + uint32_t *out_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; const int idx = FindFrameById(*bridge_state, scope_id); // The escaped Local is created in the scope enclosing the escapable one, so @@ -2049,12 +2607,15 @@ extern "C" int snapi_bridge_escape_handle(SnapiEnvState* env_state, uint32_t sco return napi_invalid_arg; } napi_value escapee = LoadValue(*bridge_state, escapee_id); - if (!escapee) return napi_invalid_arg; + if (!escapee) + return napi_invalid_arg; napi_value result = nullptr; - napi_status s = - napi_escape_handle(env, bridge_state->scope_frames[idx].esc_scope, escapee, &result); - if (s != napi_ok) return s; - *out_id = StoreValueInFrame(*bridge_state, result, bridge_state->scope_frames[idx - 1]); + napi_status s = napi_escape_handle( + env, bridge_state->scope_frames[idx].esc_scope, escapee, &result); + if (s != napi_ok) + return s; + *out_id = StoreValueInFrame(*bridge_state, result, + bridge_state->scope_frames[idx - 1]); return napi_ok; } @@ -2062,35 +2623,43 @@ extern "C" int snapi_bridge_escape_handle(SnapiEnvState* env_state, uint32_t sco // Type tagging // ============================================================ -extern "C" int snapi_bridge_type_tag_object(SnapiEnvState* env_state, uint32_t obj_id, - uint64_t tag_lower, +extern "C" int snapi_bridge_type_tag_object(SnapiEnvState *env_state, + uint32_t obj_id, uint64_t tag_lower, uint64_t tag_upper) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value obj = LoadValue(*bridge_state, obj_id); - if (!obj) return napi_invalid_arg; + if (!obj) + return napi_invalid_arg; napi_type_tag tag; tag.lower = tag_lower; tag.upper = tag_upper; return napi_type_tag_object(env, obj, &tag); } -extern "C" int snapi_bridge_check_object_type_tag(SnapiEnvState* env_state, uint32_t obj_id, +extern "C" int snapi_bridge_check_object_type_tag(SnapiEnvState *env_state, + uint32_t obj_id, uint64_t tag_lower, uint64_t tag_upper, - int* result) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; + int *result) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value obj = LoadValue(*bridge_state, obj_id); - if (!obj) return napi_invalid_arg; + if (!obj) + return napi_invalid_arg; napi_type_tag tag; tag.lower = tag_lower; tag.upper = tag_upper; bool matches; napi_status s = napi_check_object_type_tag(env, obj, &tag, &matches); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *result = matches ? 1 : 0; return napi_ok; } @@ -2099,25 +2668,31 @@ extern "C" int snapi_bridge_check_object_type_tag(SnapiEnvState* env_state, uint // Function calling (call JS functions from native) // ============================================================ -extern "C" int snapi_bridge_call_function(SnapiEnvState* env_state, uint32_t recv_id, uint32_t func_id, +extern "C" int snapi_bridge_call_function(SnapiEnvState *env_state, + uint32_t recv_id, uint32_t func_id, uint32_t argc, - const uint32_t* argv_ids, - uint32_t* out_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; + const uint32_t *argv_ids, + uint32_t *out_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value recv = LoadValue(*bridge_state, recv_id); napi_value func = LoadValue(*bridge_state, func_id); - if (!recv || !func) return napi_invalid_arg; + if (!recv || !func) + return napi_invalid_arg; std::vector argv(argc); for (uint32_t i = 0; i < argc; i++) { argv[i] = LoadValue(*bridge_state, argv_ids[i]); - if (!argv[i]) return napi_invalid_arg; + if (!argv[i]) + return napi_invalid_arg; } napi_value result; napi_status s = napi_call_function(env, recv, func, argc, argv.data(), &result); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *out_id = StoreValue(*bridge_state, result); return napi_ok; } @@ -2126,16 +2701,21 @@ extern "C" int snapi_bridge_call_function(SnapiEnvState* env_state, uint32_t rec // Script execution // ============================================================ -extern "C" int snapi_bridge_run_script(SnapiEnvState* env_state, uint32_t script_id, - uint32_t* out_value_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_run_script(SnapiEnvState *env_state, + uint32_t script_id, + uint32_t *out_value_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value script_val = LoadValue(*bridge_state, script_id); - if (!script_val) return napi_invalid_arg; + if (!script_val) + return napi_invalid_arg; napi_value result; napi_status s = napi_run_script(env, script_val, &result); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *out_value_id = StoreValue(*bridge_state, result); return napi_ok; } @@ -2144,32 +2724,39 @@ extern "C" int snapi_bridge_run_script(SnapiEnvState* env_state, uint32_t script // UTF-16 strings // ============================================================ -extern "C" int snapi_bridge_create_string_utf16(SnapiEnvState* env_state, const uint16_t* str, +extern "C" int snapi_bridge_create_string_utf16(SnapiEnvState *env_state, + const uint16_t *str, uint32_t wasm_length, - uint32_t* out_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; + uint32_t *out_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; size_t length = (wasm_length == 0xFFFFFFFFu) ? NAPI_AUTO_LENGTH : (size_t)wasm_length; napi_value result; - napi_status s = napi_create_string_utf16(env, (const char16_t*)str, length, - &result); - if (s != napi_ok) return s; + napi_status s = + napi_create_string_utf16(env, (const char16_t *)str, length, &result); + if (s != napi_ok) + return s; *out_id = StoreValue(*bridge_state, result); return napi_ok; } -extern "C" int snapi_bridge_get_value_string_utf16(SnapiEnvState* env_state, uint32_t id, - uint16_t* buf, +extern "C" int snapi_bridge_get_value_string_utf16(SnapiEnvState *env_state, + uint32_t id, uint16_t *buf, size_t bufsize, - size_t* result) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; + size_t *result) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value val = LoadValue(*bridge_state, id); - if (!val) return napi_invalid_arg; - return napi_get_value_string_utf16(env, val, (char16_t*)buf, bufsize, + if (!val) + return napi_invalid_arg; + return napi_get_value_string_utf16(env, val, (char16_t *)buf, bufsize, result); } @@ -2177,30 +2764,37 @@ extern "C" int snapi_bridge_get_value_string_utf16(SnapiEnvState* env_state, uin // BigInt words (arbitrary precision) // ============================================================ -extern "C" int snapi_bridge_create_bigint_words(SnapiEnvState* env_state, int sign_bit, +extern "C" int snapi_bridge_create_bigint_words(SnapiEnvState *env_state, + int sign_bit, uint32_t word_count, - const uint64_t* words, - uint32_t* out_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; + const uint64_t *words, + uint32_t *out_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value result; napi_status s = napi_create_bigint_words(env, sign_bit, (size_t)word_count, words, &result); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *out_id = StoreValue(*bridge_state, result); return napi_ok; } -extern "C" int snapi_bridge_get_value_bigint_words(SnapiEnvState* env_state, uint32_t id, - int* sign_bit, - size_t* word_count, - uint64_t* words) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_get_value_bigint_words(SnapiEnvState *env_state, + uint32_t id, int *sign_bit, + size_t *word_count, + uint64_t *words) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value val = LoadValue(*bridge_state, id); - if (!val) return napi_invalid_arg; + if (!val) + return napi_invalid_arg; return napi_get_value_bigint_words(env, val, sign_bit, word_count, words); } @@ -2208,29 +2802,39 @@ extern "C" int snapi_bridge_get_value_bigint_words(SnapiEnvState* env_state, uin // Instance data // ============================================================ -extern "C" int snapi_bridge_set_instance_data(SnapiEnvState* env_state, uint64_t data_val) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_set_instance_data(SnapiEnvState *env_state, + uint64_t data_val) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; - return napi_set_instance_data(env, (void*)(uintptr_t)data_val, - nullptr, nullptr); + return napi_set_instance_data(env, (void *)(uintptr_t)data_val, nullptr, + nullptr); } -extern "C" int snapi_bridge_get_instance_data(SnapiEnvState* env_state, uint64_t* data_out) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_get_instance_data(SnapiEnvState *env_state, + uint64_t *data_out) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; - void* data = nullptr; + void *data = nullptr; napi_status s = napi_get_instance_data(env, &data); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *data_out = (uint64_t)(uintptr_t)data; return napi_ok; } -extern "C" int snapi_bridge_adjust_external_memory(SnapiEnvState* env_state, int64_t change, - int64_t* adjusted) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_adjust_external_memory(SnapiEnvState *env_state, + int64_t change, + int64_t *adjusted) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; return napi_adjust_external_memory(env, change, adjusted); } @@ -2239,87 +2843,111 @@ extern "C" int snapi_bridge_adjust_external_memory(SnapiEnvState* env_state, int // Node Buffers // ============================================================ -extern "C" int snapi_bridge_create_buffer(SnapiEnvState* env_state, uint32_t length, - uint64_t* data_out, - uint32_t* out_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_create_buffer(SnapiEnvState *env_state, + uint32_t length, uint64_t *data_out, + uint32_t *out_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value buffer; - void* data = nullptr; + void *data = nullptr; napi_status s = napi_create_buffer(env, (size_t)length, &data, &buffer); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *out_id = StoreValue(*bridge_state, buffer); - if (data_out) *data_out = (uint64_t)(uintptr_t)data; + if (data_out) + *data_out = (uint64_t)(uintptr_t)data; return napi_ok; } -extern "C" int snapi_bridge_create_buffer_copy(SnapiEnvState* env_state, uint32_t length, - const void* src_data, - uint64_t* result_data_out, - uint32_t* out_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_create_buffer_copy(SnapiEnvState *env_state, + uint32_t length, + const void *src_data, + uint64_t *result_data_out, + uint32_t *out_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value buffer; - void* result_data = nullptr; + void *result_data = nullptr; napi_status s = napi_create_buffer_copy(env, (size_t)length, src_data, &result_data, &buffer); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *out_id = StoreValue(*bridge_state, buffer); - if (result_data_out) *result_data_out = (uint64_t)(uintptr_t)result_data; + if (result_data_out) + *result_data_out = (uint64_t)(uintptr_t)result_data; return napi_ok; } -extern "C" int snapi_bridge_is_buffer(SnapiEnvState* env_state, uint32_t id, int* result) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_is_buffer(SnapiEnvState *env_state, uint32_t id, + int *result) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value val = LoadValue(*bridge_state, id); - if (!val) return napi_invalid_arg; + if (!val) + return napi_invalid_arg; bool is_buffer = false; napi_status s = napi_is_buffer(env, val, &is_buffer); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *result = is_buffer ? 1 : 0; return napi_ok; } -extern "C" int snapi_bridge_get_buffer_info(SnapiEnvState* env_state, uint32_t id, - uint64_t* data_out, - uint32_t* length_out, - uint64_t* backing_store_token_out) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_get_buffer_info(SnapiEnvState *env_state, + uint32_t id, uint64_t *data_out, + uint32_t *length_out, + uint64_t *backing_store_token_out) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value val = LoadValue(*bridge_state, id); - if (!val) return napi_invalid_arg; - void* data = nullptr; + if (!val) + return napi_invalid_arg; + void *data = nullptr; size_t length = 0; napi_status s = napi_get_buffer_info(env, val, &data, &length); - if (s != napi_ok) return s; - if (length_out) *length_out = (uint32_t)length; - if (data_out) *data_out = (uint64_t)(uintptr_t)data; + if (s != napi_ok) + return s; + if (length_out) + *length_out = (uint32_t)length; + if (data_out) + *data_out = (uint64_t)(uintptr_t)data; if (backing_store_token_out) { - *backing_store_token_out = napi_v8_get_arraybuffer_view_backing_store_token(env, val); + *backing_store_token_out = + napi_v8_get_arraybuffer_view_backing_store_token(env, val); } return napi_ok; } extern "C" int snapi_bridge_unofficial_acquire_buffer_lease( - SnapiEnvState* env_state, uint32_t value_id, uint32_t byte_offset, - uint32_t byte_length, int mode, uint32_t* lease_out, uint64_t* data_out) { - auto* bridge_state = RequireEnvState(env_state); + SnapiEnvState *env_state, uint32_t value_id, uint32_t byte_offset, + uint32_t byte_length, int mode, uint32_t *lease_out, uint64_t *data_out) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); if (bridge_state == nullptr || lease_out == nullptr || data_out == nullptr) { return napi_invalid_arg; } napi_value value = LoadValue(*bridge_state, value_id); - if (value == nullptr) return napi_invalid_arg; + if (value == nullptr) + return napi_invalid_arg; unofficial_napi_buffer_lease lease = nullptr; - void* data = nullptr; + void *data = nullptr; napi_status status = unofficial_napi_acquire_buffer_lease( bridge_state->env, value, byte_offset, byte_length, static_cast(mode), &lease, &data); - if (status != napi_ok) return status; + if (status != napi_ok) + return status; const uint32_t lease_id = bridge_state->buffer_lease_handles.Store(lease); if (lease_id == 0) { (void)unofficial_napi_release_buffer_lease(bridge_state->env, lease, false); @@ -2330,13 +2958,17 @@ extern "C" int snapi_bridge_unofficial_acquire_buffer_lease( return napi_ok; } -extern "C" int snapi_bridge_unofficial_release_buffer_lease( - SnapiEnvState* env_state, uint32_t lease_id, int modified) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int +snapi_bridge_unofficial_release_buffer_lease(SnapiEnvState *env_state, + uint32_t lease_id, int modified) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; auto lease = static_cast( bridge_state->buffer_lease_handles.Load(lease_id)); - if (lease == nullptr) return napi_invalid_arg; + if (lease == nullptr) + return napi_invalid_arg; napi_status status = unofficial_napi_release_buffer_lease( bridge_state->env, lease, modified != 0); bridge_state->buffer_lease_handles.Remove(lease_id); @@ -2344,18 +2976,20 @@ extern "C" int snapi_bridge_unofficial_release_buffer_lease( } extern "C" int snapi_bridge_unofficial_create_guest_backed_typedarray( - SnapiEnvState* env_state, int type, uint32_t length, uint64_t* data_out, - uint32_t* value_out) { - auto* bridge_state = RequireEnvState(env_state); + SnapiEnvState *env_state, int type, uint32_t length, uint64_t *data_out, + uint32_t *value_out) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); if (bridge_state == nullptr || data_out == nullptr || value_out == nullptr) { return napi_invalid_arg; } - void* data = nullptr; + void *data = nullptr; napi_value value = nullptr; napi_status status = unofficial_napi_create_guest_backed_typedarray( bridge_state->env, static_cast(type), length, &data, &value); - if (status != napi_ok) return status; + if (status != napi_ok) + return status; *data_out = reinterpret_cast(data); *value_out = StoreValue(*bridge_state, value); return *value_out == 0 ? napi_generic_failure : napi_ok; @@ -2365,16 +2999,21 @@ extern "C" int snapi_bridge_unofficial_create_guest_backed_typedarray( // Node version (stub — we're not running in Node, return fake version) // ============================================================ -extern "C" int snapi_bridge_get_node_version(SnapiEnvState* env_state, uint32_t* major, - uint32_t* minor, - uint32_t* patch) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_get_node_version(SnapiEnvState *env_state, + uint32_t *major, uint32_t *minor, + uint32_t *patch) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; // Return a reasonable fake version since we're running on pure V8 - if (major) *major = 22; - if (minor) *minor = 0; - if (patch) *patch = 0; + if (major) + *major = 22; + if (minor) + *minor = 0; + if (patch) + *patch = 0; return napi_ok; } @@ -2382,113 +3021,149 @@ extern "C" int snapi_bridge_get_node_version(SnapiEnvState* env_state, uint32_t* // Object wrapping // ============================================================ -extern "C" int snapi_bridge_wrap(SnapiEnvState* env_state, uint32_t obj_id, uint64_t native_data, - uint32_t* ref_out) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_wrap(SnapiEnvState *env_state, uint32_t obj_id, + uint64_t native_data, uint32_t *ref_out) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value obj = LoadValue(*bridge_state, obj_id); - if (!obj) return napi_invalid_arg; + if (!obj) + return napi_invalid_arg; napi_ref ref = nullptr; - napi_status s = napi_wrap(env, obj, (void*)(uintptr_t)native_data, - nullptr, nullptr, ref_out ? &ref : nullptr); - if (s != napi_ok) return s; - if (ref_out) *ref_out = StoreRef(*bridge_state, ref); + napi_status s = napi_wrap(env, obj, (void *)(uintptr_t)native_data, nullptr, + nullptr, ref_out ? &ref : nullptr); + if (s != napi_ok) + return s; + if (ref_out) + *ref_out = StoreRef(*bridge_state, ref); return napi_ok; } // Like snapi_bridge_wrap, but runs the guest's finalize callback when the // wrapped object is collected (napi_wrap's finalizer goes through the deferred // RefBase drain, so the guest can be re-entered safely). -extern "C" int snapi_bridge_wrap_finalized(SnapiEnvState* env_state, uint32_t obj_id, - uint64_t native_data, uint32_t guest_env, - uint32_t wasm_fn_ptr, uint32_t finalize_data, - uint32_t finalize_hint, uint32_t* ref_out) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int +snapi_bridge_wrap_finalized(SnapiEnvState *env_state, uint32_t obj_id, + uint64_t native_data, uint32_t guest_env, + uint32_t wasm_fn_ptr, uint32_t finalize_data, + uint32_t finalize_hint, uint32_t *ref_out) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value obj = LoadValue(*bridge_state, obj_id); - if (!obj) return napi_invalid_arg; - auto* record = MakeGuestFinalizerRecord(env_state, guest_env, wasm_fn_ptr, + if (!obj) + return napi_invalid_arg; + auto *record = MakeGuestFinalizerRecord(env_state, guest_env, wasm_fn_ptr, finalize_data, finalize_hint); - if (record == nullptr) return napi_generic_failure; + if (record == nullptr) + return napi_generic_failure; napi_ref ref = nullptr; - napi_status s = napi_wrap(env, obj, (void*)(uintptr_t)native_data, - GuestFinalizerTrampoline, record, - ref_out ? &ref : nullptr); + napi_status s = + napi_wrap(env, obj, (void *)(uintptr_t)native_data, + GuestFinalizerTrampoline, record, ref_out ? &ref : nullptr); if (s != napi_ok) { delete record; return s; } - if (ref_out) *ref_out = StoreRef(*bridge_state, ref); + if (ref_out) + *ref_out = StoreRef(*bridge_state, ref); return napi_ok; } -extern "C" int snapi_bridge_unwrap(SnapiEnvState* env_state, uint32_t obj_id, uint64_t* data_out) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_unwrap(SnapiEnvState *env_state, uint32_t obj_id, + uint64_t *data_out) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value obj = LoadValue(*bridge_state, obj_id); - if (!obj) return napi_invalid_arg; - void* data = nullptr; + if (!obj) + return napi_invalid_arg; + void *data = nullptr; napi_status s = napi_unwrap(env, obj, &data); - if (s != napi_ok) return s; - if (data_out) *data_out = (uint64_t)(uintptr_t)data; + if (s != napi_ok) + return s; + if (data_out) + *data_out = (uint64_t)(uintptr_t)data; return napi_ok; } -extern "C" int snapi_bridge_remove_wrap(SnapiEnvState* env_state, uint32_t obj_id, uint64_t* data_out) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_remove_wrap(SnapiEnvState *env_state, + uint32_t obj_id, uint64_t *data_out) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value obj = LoadValue(*bridge_state, obj_id); - if (!obj) return napi_invalid_arg; - void* data = nullptr; + if (!obj) + return napi_invalid_arg; + void *data = nullptr; napi_status s = napi_remove_wrap(env, obj, &data); - if (s != napi_ok) return s; - if (data_out) *data_out = (uint64_t)(uintptr_t)data; + if (s != napi_ok) + return s; + if (data_out) + *data_out = (uint64_t)(uintptr_t)data; return napi_ok; } -extern "C" int snapi_bridge_add_finalizer(SnapiEnvState* env_state, uint32_t obj_id, uint64_t data_val, - uint32_t* ref_out) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_add_finalizer(SnapiEnvState *env_state, + uint32_t obj_id, uint64_t data_val, + uint32_t *ref_out) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value obj = LoadValue(*bridge_state, obj_id); - if (!obj) return napi_invalid_arg; + if (!obj) + return napi_invalid_arg; // No actual WASM callback for finalizer; just register with nullptr callback napi_ref ref = nullptr; - napi_status s = napi_add_finalizer(env, obj, (void*)(uintptr_t)data_val, - nullptr, nullptr, ref_out ? &ref : nullptr); - if (s != napi_ok) return s; - if (ref_out) *ref_out = StoreRef(*bridge_state, ref); + napi_status s = + napi_add_finalizer(env, obj, (void *)(uintptr_t)data_val, nullptr, + nullptr, ref_out ? &ref : nullptr); + if (s != napi_ok) + return s; + if (ref_out) + *ref_out = StoreRef(*bridge_state, ref); return napi_ok; } // Like snapi_bridge_add_finalizer, but runs the guest's finalize callback when // the object is collected (goes through the deferred RefBase drain). -extern "C" int snapi_bridge_add_finalizer_cb(SnapiEnvState* env_state, uint32_t obj_id, - uint64_t data_val, uint32_t guest_env, - uint32_t wasm_fn_ptr, uint32_t finalize_data, - uint32_t finalize_hint, uint32_t* ref_out) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int +snapi_bridge_add_finalizer_cb(SnapiEnvState *env_state, uint32_t obj_id, + uint64_t data_val, uint32_t guest_env, + uint32_t wasm_fn_ptr, uint32_t finalize_data, + uint32_t finalize_hint, uint32_t *ref_out) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value obj = LoadValue(*bridge_state, obj_id); - if (!obj) return napi_invalid_arg; - auto* record = MakeGuestFinalizerRecord(env_state, guest_env, wasm_fn_ptr, + if (!obj) + return napi_invalid_arg; + auto *record = MakeGuestFinalizerRecord(env_state, guest_env, wasm_fn_ptr, finalize_data, finalize_hint); - if (record == nullptr) return napi_generic_failure; + if (record == nullptr) + return napi_generic_failure; napi_ref ref = nullptr; - napi_status s = napi_add_finalizer(env, obj, (void*)(uintptr_t)data_val, + napi_status s = napi_add_finalizer(env, obj, (void *)(uintptr_t)data_val, GuestFinalizerTrampoline, record, ref_out ? &ref : nullptr); if (s != napi_ok) { delete record; return s; } - if (ref_out) *ref_out = StoreRef(*bridge_state, ref); + if (ref_out) + *ref_out = StoreRef(*bridge_state, ref); return napi_ok; } @@ -2496,22 +3171,28 @@ extern "C" int snapi_bridge_add_finalizer_cb(SnapiEnvState* env_state, uint32_t // napi_new_instance // ============================================================ -extern "C" int snapi_bridge_new_instance(SnapiEnvState* env_state, uint32_t ctor_id, uint32_t argc, - const uint32_t* argv_ids, - uint32_t* out_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_new_instance(SnapiEnvState *env_state, + uint32_t ctor_id, uint32_t argc, + const uint32_t *argv_ids, + uint32_t *out_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value ctor = LoadValue(*bridge_state, ctor_id); - if (!ctor) return napi_invalid_arg; + if (!ctor) + return napi_invalid_arg; std::vector argv(argc); for (uint32_t i = 0; i < argc; i++) { argv[i] = LoadValue(*bridge_state, argv_ids[i]); - if (!argv[i]) return napi_invalid_arg; + if (!argv[i]) + return napi_invalid_arg; } napi_value result; napi_status s = napi_new_instance(env, ctor, argc, argv.data(), &result); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *out_id = StoreValue(*bridge_state, result); return napi_ok; } @@ -2523,49 +3204,50 @@ extern "C" int snapi_bridge_new_instance(SnapiEnvState* env_state, uint32_t ctor // Forward declaration (defined below in callback system section) static napi_value generic_wasm_callback(napi_env env, napi_callback_info info); -extern "C" int snapi_bridge_define_properties(SnapiEnvState* env_state, uint32_t obj_id, - uint32_t prop_count, - const char** utf8names, - const uint32_t* name_ids, - const uint32_t* prop_types, - const uint32_t* value_ids, - const uint32_t* method_reg_ids, - const uint32_t* getter_reg_ids, - const uint32_t* setter_reg_ids, - const int32_t* attributes) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_define_properties( + SnapiEnvState *env_state, uint32_t obj_id, uint32_t prop_count, + const char **utf8names, const uint32_t *name_ids, + const uint32_t *prop_types, const uint32_t *value_ids, + const uint32_t *method_reg_ids, const uint32_t *getter_reg_ids, + const uint32_t *setter_reg_ids, const int32_t *attributes) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; napi_value obj = LoadValue(*bridge_state, obj_id); - if (!obj) return napi_invalid_arg; + if (!obj) + return napi_invalid_arg; std::vector descs(prop_count); for (uint32_t i = 0; i < prop_count; i++) { memset(&descs[i], 0, sizeof(napi_property_descriptor)); descs[i].utf8name = utf8names != nullptr ? utf8names[i] : nullptr; - descs[i].name = (name_ids != nullptr && name_ids[i] != 0) ? LoadValue(*bridge_state, name_ids[i]) : nullptr; + descs[i].name = (name_ids != nullptr && name_ids[i] != 0) + ? LoadValue(*bridge_state, name_ids[i]) + : nullptr; descs[i].attributes = (napi_property_attributes)attributes[i]; switch (prop_types[i]) { - case 0: - descs[i].value = LoadValue(*bridge_state, value_ids[i]); - break; - case 1: - descs[i].method = generic_wasm_callback; - descs[i].data = RegisterCallbackBinding(bridge_state, method_reg_ids[i]); - break; - case 2: - descs[i].getter = generic_wasm_callback; - descs[i].data = RegisterCallbackBinding(bridge_state, getter_reg_ids[i]); - break; - case 3: - descs[i].setter = generic_wasm_callback; - descs[i].data = RegisterCallbackBinding(bridge_state, setter_reg_ids[i]); - break; - case 4: - descs[i].getter = generic_wasm_callback; - descs[i].setter = generic_wasm_callback; - descs[i].data = RegisterCallbackBinding(bridge_state, getter_reg_ids[i]); - break; + case 0: + descs[i].value = LoadValue(*bridge_state, value_ids[i]); + break; + case 1: + descs[i].method = generic_wasm_callback; + descs[i].data = RegisterCallbackBinding(bridge_state, method_reg_ids[i]); + break; + case 2: + descs[i].getter = generic_wasm_callback; + descs[i].data = RegisterCallbackBinding(bridge_state, getter_reg_ids[i]); + break; + case 3: + descs[i].setter = generic_wasm_callback; + descs[i].data = RegisterCallbackBinding(bridge_state, setter_reg_ids[i]); + break; + case 4: + descs[i].getter = generic_wasm_callback; + descs[i].setter = generic_wasm_callback; + descs[i].data = RegisterCallbackBinding(bridge_state, getter_reg_ids[i]); + break; } } return napi_define_properties(env, obj, prop_count, descs.data()); @@ -2585,21 +3267,17 @@ extern "C" int snapi_bridge_define_properties(SnapiEnvState* env_state, uint32_t // setter_reg_ids[i] - if type==3 or 4, the callback reg_id for setter // attributes[i] - napi_property_attributes -extern "C" int snapi_bridge_define_class(SnapiEnvState* env_state, - const char* utf8name, uint32_t name_len, - uint32_t ctor_reg_id, - uint32_t prop_count, - const char** prop_names, - const uint32_t* prop_name_ids, - const uint32_t* prop_types, - const uint32_t* prop_value_ids, - const uint32_t* prop_method_reg_ids, - const uint32_t* prop_getter_reg_ids, - const uint32_t* prop_setter_reg_ids, - const int32_t* prop_attributes, - uint32_t* out_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_define_class( + SnapiEnvState *env_state, const char *utf8name, uint32_t name_len, + uint32_t ctor_reg_id, uint32_t prop_count, const char **prop_names, + const uint32_t *prop_name_ids, const uint32_t *prop_types, + const uint32_t *prop_value_ids, const uint32_t *prop_method_reg_ids, + const uint32_t *prop_getter_reg_ids, const uint32_t *prop_setter_reg_ids, + const int32_t *prop_attributes, uint32_t *out_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; // Build property descriptors @@ -2607,32 +3285,38 @@ extern "C" int snapi_bridge_define_class(SnapiEnvState* env_state, for (uint32_t i = 0; i < prop_count; i++) { memset(&descs[i], 0, sizeof(napi_property_descriptor)); descs[i].utf8name = prop_names != nullptr ? prop_names[i] : nullptr; - descs[i].name = (prop_name_ids != nullptr && prop_name_ids[i] != 0) ? LoadValue(*bridge_state, prop_name_ids[i]) : nullptr; + descs[i].name = (prop_name_ids != nullptr && prop_name_ids[i] != 0) + ? LoadValue(*bridge_state, prop_name_ids[i]) + : nullptr; descs[i].attributes = (napi_property_attributes)prop_attributes[i]; switch (prop_types[i]) { - case 0: // value - descs[i].value = LoadValue(*bridge_state, prop_value_ids[i]); - break; - case 1: // method - descs[i].method = generic_wasm_callback; - descs[i].data = RegisterCallbackBinding(bridge_state, prop_method_reg_ids[i]); - break; - case 2: // getter only - descs[i].getter = generic_wasm_callback; - descs[i].data = RegisterCallbackBinding(bridge_state, prop_getter_reg_ids[i]); - break; - case 3: // setter only - descs[i].setter = generic_wasm_callback; - descs[i].data = RegisterCallbackBinding(bridge_state, prop_setter_reg_ids[i]); - break; - case 4: // getter + setter - descs[i].getter = generic_wasm_callback; - descs[i].setter = generic_wasm_callback; - descs[i].data = RegisterCallbackBinding(bridge_state, prop_getter_reg_ids[i]); - // Note: N-API uses the same data pointer for both getter and setter. - // The setter_reg_id is stored in the getter_reg_id for now. - break; + case 0: // value + descs[i].value = LoadValue(*bridge_state, prop_value_ids[i]); + break; + case 1: // method + descs[i].method = generic_wasm_callback; + descs[i].data = + RegisterCallbackBinding(bridge_state, prop_method_reg_ids[i]); + break; + case 2: // getter only + descs[i].getter = generic_wasm_callback; + descs[i].data = + RegisterCallbackBinding(bridge_state, prop_getter_reg_ids[i]); + break; + case 3: // setter only + descs[i].setter = generic_wasm_callback; + descs[i].data = + RegisterCallbackBinding(bridge_state, prop_setter_reg_ids[i]); + break; + case 4: // getter + setter + descs[i].getter = generic_wasm_callback; + descs[i].setter = generic_wasm_callback; + descs[i].data = + RegisterCallbackBinding(bridge_state, prop_getter_reg_ids[i]); + // Note: N-API uses the same data pointer for both getter and setter. + // The setter_reg_id is stored in the getter_reg_id for now. + break; } } @@ -2640,11 +3324,10 @@ extern "C" int snapi_bridge_define_class(SnapiEnvState* env_state, napi_status s = napi_define_class( env, utf8name, name_len == 0xFFFFFFFFu ? NAPI_AUTO_LENGTH : (size_t)name_len, - generic_wasm_callback, - RegisterCallbackBinding(bridge_state, ctor_reg_id), - prop_count, descs.data(), - &result); - if (s != napi_ok) return s; + generic_wasm_callback, RegisterCallbackBinding(bridge_state, ctor_reg_id), + prop_count, descs.data(), &result); + if (s != napi_ok) + return s; *out_id = StoreValue(*bridge_state, result); return napi_ok; } @@ -2653,36 +3336,47 @@ extern "C" int snapi_bridge_define_class(SnapiEnvState* env_state, // Callback system (napi_create_function + napi_get_cb_info) // ============================================================ -extern "C" void* snapi_bridge_swap_active_callback_ctx(SnapiEnvState* env_state, void* callback_ctx) { - auto* bridge_state = LookupEnvState(env_state); - if (bridge_state == nullptr) return nullptr; - return bridge_state->active_callback_ctx.exchange(callback_ctx, std::memory_order_acq_rel); -} - -extern "C" int snapi_bridge_get_cb_info(SnapiEnvState* env_state, uint32_t cbinfo_id, - uint32_t* argc_ptr, uint32_t* argv_out, - uint32_t max_argv, - uint32_t* this_out, uint64_t* data_out) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" void *snapi_bridge_swap_active_callback_ctx(SnapiEnvState *env_state, + void *callback_ctx) { + auto bridge_state_lease = LookupEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return nullptr; + return bridge_state->active_callback_ctx.exchange(callback_ctx, + std::memory_order_acq_rel); +} + +extern "C" int snapi_bridge_get_cb_info(SnapiEnvState *env_state, + uint32_t cbinfo_id, uint32_t *argc_ptr, + uint32_t *argv_out, uint32_t max_argv, + uint32_t *this_out, + uint64_t *data_out) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; auto it = bridge_state->callback_invocations.find(cbinfo_id); - if (it == bridge_state->callback_invocations.end()) return napi_generic_failure; + if (it == bridge_state->callback_invocations.end()) + return napi_generic_failure; size_t wanted = argc_ptr != nullptr ? static_cast(*argc_ptr) : 0; size_t actual = wanted; std::vector argv(wanted); napi_value this_arg = nullptr; - void* raw_data = nullptr; - napi_status s = - napi_get_cb_info(env, it->second.info, &actual, wanted > 0 ? argv.data() : nullptr, - &this_arg, &raw_data); - if (s != napi_ok) return s; + void *raw_data = nullptr; + napi_status s = napi_get_cb_info(env, it->second.info, &actual, + wanted > 0 ? argv.data() : nullptr, + &this_arg, &raw_data); + if (s != napi_ok) + return s; - if (argc_ptr) *argc_ptr = static_cast(actual); - if (this_out) *this_out = this_arg != nullptr ? StoreValue(*bridge_state, this_arg) : 0; + if (argc_ptr) + *argc_ptr = static_cast(actual); + if (this_out) + *this_out = this_arg != nullptr ? StoreValue(*bridge_state, this_arg) : 0; - auto* binding = static_cast(raw_data); + auto *binding = static_cast(raw_data); uint64_t data_val = 0; if (binding != nullptr) { auto reg_it = bridge_state->cb_registry.find(binding->reg_id); @@ -2690,9 +3384,11 @@ extern "C" int snapi_bridge_get_cb_info(SnapiEnvState* env_state, uint32_t cbinf data_val = reg_it->second.data_val; } } - if (data_out) *data_out = data_val; + if (data_out) + *data_out = data_val; - const uint32_t to_copy = static_cast(std::min(wanted, actual)); + const uint32_t to_copy = + static_cast(std::min(wanted, actual)); if (argv_out != nullptr) { for (uint32_t i = 0; i < to_copy; i++) { argv_out[i] = StoreValue(*bridge_state, argv[i]); @@ -2702,38 +3398,46 @@ extern "C" int snapi_bridge_get_cb_info(SnapiEnvState* env_state, uint32_t cbinf } // napi_get_new_target — only valid inside a constructor callback -extern "C" int snapi_bridge_get_new_target(SnapiEnvState* env_state, uint32_t cbinfo_id, - uint32_t* out_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_get_new_target(SnapiEnvState *env_state, + uint32_t cbinfo_id, + uint32_t *out_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; auto it = bridge_state->callback_invocations.find(cbinfo_id); - if (it == bridge_state->callback_invocations.end()) return napi_generic_failure; + if (it == bridge_state->callback_invocations.end()) + return napi_generic_failure; napi_value result; napi_status s = napi_get_new_target(env, it->second.info, &result); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *out_id = result ? StoreValue(*bridge_state, result) : 0; return napi_ok; } -// Forward-declare the Rust trampoline (defined in lib.rs via #[no_mangle] extern "C") -extern "C" uint32_t snapi_host_invoke_wasm_callback(void* callback_ctx, +// Forward-declare the Rust trampoline (defined in lib.rs via #[no_mangle] +// extern "C") +extern "C" uint32_t snapi_host_invoke_wasm_callback(void *callback_ctx, uint32_t guest_env, uint32_t wasm_fn_ptr, uint32_t callback_arg); struct WasmInterruptRequest { - SnapiEnvState* state; + SnapiEnvState *state; uint32_t guest_env; uint32_t wasm_fn_ptr; uint32_t callback_arg; }; -void WasmInterruptCallback(napi_env env, void* raw) { - auto* request = static_cast(raw); - if (request == nullptr) return; - auto* bridge_state = LookupEnvState(request->state); - void* callback_ctx = +void WasmInterruptCallback(napi_env env, void *raw) { + auto *request = static_cast(raw); + if (request == nullptr) + return; + auto bridge_state_lease = LookupEnvState(request->state); + auto *bridge_state = bridge_state_lease.get(); + void *callback_ctx = bridge_state != nullptr ? bridge_state->active_callback_ctx.load(std::memory_order_acquire) : nullptr; @@ -2749,8 +3453,9 @@ void WasmInterruptCallback(napi_env env, void* raw) { frame.scope = interrupt_scope; bridge_state->scope_frames.push_back(std::move(frame)); } - snapi_host_invoke_wasm_callback( - callback_ctx, request->guest_env, request->wasm_fn_ptr, request->callback_arg); + snapi_host_invoke_wasm_callback(callback_ctx, request->guest_env, + request->wasm_fn_ptr, + request->callback_arg); if (interrupt_scope != nullptr) { while (bridge_state->scope_frames.size() > 1 && bridge_state->scope_frames.back().scope != interrupt_scope) { @@ -2769,25 +3474,26 @@ void WasmInterruptCallback(napi_env env, void* raw) { // Passes the real callback frame explicitly to the Rust trampoline via a // short-lived callback-info token. static napi_value generic_wasm_callback(napi_env env, napi_callback_info info) { - void* raw_data; + void *raw_data; size_t argc = 64; napi_value argv[64]; napi_value this_arg; napi_get_cb_info(env, info, &argc, argv, &this_arg, &raw_data); - auto* binding = static_cast(raw_data); + auto *binding = static_cast(raw_data); if (binding == nullptr) { napi_value undef; napi_get_undefined(env, &undef); return undef; } - auto* bridge_state = LookupEnvState(binding->state); + auto bridge_state_lease = LookupEnvState(binding->state); + auto *bridge_state = bridge_state_lease.get(); if (bridge_state == nullptr) { napi_value undef; napi_get_undefined(env, &undef); return undef; } - void* callback_ctx = + void *callback_ctx = bridge_state->active_callback_ctx.load(std::memory_order_acquire); if (callback_ctx == nullptr) { napi_value undef; @@ -2813,21 +3519,20 @@ static napi_value generic_wasm_callback(napi_env env, napi_callback_info info) { // back to V8's trampoline in the enclosing scope. (void)CurrentFrame(*bridge_state); napi_escapable_handle_scope cb_scope = nullptr; - if (napi_open_escapable_handle_scope(env, &cb_scope) == napi_ok && cb_scope != nullptr) { + if (napi_open_escapable_handle_scope(env, &cb_scope) == napi_ok && + cb_scope != nullptr) { ScopeFrame frame; frame.esc_scope = cb_scope; bridge_state->scope_frames.push_back(std::move(frame)); } // Call Rust trampoline → WASM callback - const uint32_t wasm_fn_ptr = - (it->second.wasm_setter_fn_ptr != 0 && argc > 0) - ? it->second.wasm_setter_fn_ptr - : it->second.wasm_fn_ptr; - - uint32_t result_id = - snapi_host_invoke_wasm_callback( - callback_ctx, it->second.guest_env, wasm_fn_ptr, cbinfo_id); + const uint32_t wasm_fn_ptr = (it->second.wasm_setter_fn_ptr != 0 && argc > 0) + ? it->second.wasm_setter_fn_ptr + : it->second.wasm_fn_ptr; + + uint32_t result_id = snapi_host_invoke_wasm_callback( + callback_ctx, it->second.guest_env, wasm_fn_ptr, cbinfo_id); bridge_state->callback_invocations.erase(cbinfo_id); napi_value result = LoadValue(*bridge_state, result_id); @@ -2859,8 +3564,9 @@ static napi_value generic_wasm_callback(napi_env env, napi_callback_info info) { } // Allocate a registration ID for a new callback -extern "C" uint32_t snapi_bridge_alloc_cb_reg_id(SnapiEnvState* env_state) { - if (env_state == nullptr) return 0; +extern "C" uint32_t snapi_bridge_alloc_cb_reg_id(SnapiEnvState *env_state) { + if (env_state == nullptr) + return 0; // Cap callback registrations alongside value handles: 0 signals failure so // the registration chain aborts instead of growing cb_registry unbounded. if (env_state->value_limit != 0 && @@ -2871,50 +3577,58 @@ extern "C" uint32_t snapi_bridge_alloc_cb_reg_id(SnapiEnvState* env_state) { } // Register callback data for a registration ID -extern "C" void snapi_bridge_register_callback(SnapiEnvState* env_state, +extern "C" void snapi_bridge_register_callback(SnapiEnvState *env_state, uint32_t reg_id, uint32_t guest_env, uint32_t wasm_fn_ptr, uint64_t data_val) { - if (env_state == nullptr || reg_id == 0) return; - env_state->cb_registry[reg_id] = { guest_env, wasm_fn_ptr, 0, data_val }; + if (env_state == nullptr || reg_id == 0) + return; + env_state->cb_registry[reg_id] = {guest_env, wasm_fn_ptr, 0, data_val}; } -extern "C" void snapi_bridge_register_callback_pair(SnapiEnvState* env_state, +extern "C" void snapi_bridge_register_callback_pair(SnapiEnvState *env_state, uint32_t reg_id, uint32_t guest_env, uint32_t wasm_getter_fn_ptr, uint32_t wasm_setter_fn_ptr, uint64_t data_val) { - if (env_state == nullptr || reg_id == 0) return; - env_state->cb_registry[reg_id] = { guest_env, wasm_getter_fn_ptr, wasm_setter_fn_ptr, data_val }; + if (env_state == nullptr || reg_id == 0) + return; + env_state->cb_registry[reg_id] = {guest_env, wasm_getter_fn_ptr, + wasm_setter_fn_ptr, data_val}; } // Create a JS function with generic_wasm_callback as its native callback. // The reg_id is passed as the data pointer so the callback can look up // which WASM function to invoke. -extern "C" int snapi_bridge_create_function(SnapiEnvState* env_state, const char* utf8name, uint32_t name_len, - uint32_t reg_id, - uint32_t* out_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_create_function(SnapiEnvState *env_state, + const char *utf8name, + uint32_t name_len, uint32_t reg_id, + uint32_t *out_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; - CallbackBinding* binding = RegisterCallbackBinding(bridge_state, reg_id); + CallbackBinding *binding = RegisterCallbackBinding(bridge_state, reg_id); napi_value result; - napi_status s = napi_create_function(env, utf8name, - name_len == 0xFFFFFFFFu ? NAPI_AUTO_LENGTH : (size_t)name_len, - generic_wasm_callback, - binding, - &result); - if (s != napi_ok) return s; + napi_status s = napi_create_function( + env, utf8name, + name_len == 0xFFFFFFFFu ? NAPI_AUTO_LENGTH : (size_t)name_len, + generic_wasm_callback, binding, &result); + if (s != napi_ok) + return s; *out_id = StoreValue(*bridge_state, result); return napi_ok; } extern "C" int snapi_bridge_unofficial_create_env(int32_t module_api_version, - const void* guest_heap_ctx, - SnapiEnvState** env_out) { - std::lock_guard lock(g_mu); + const void *guest_heap_ctx, + SnapiEnvState **env_out) { + // V8's isolate initialization touches process-wide state. Serialize only + // construction, never execution or GC of an existing environment. + std::lock_guard init_lock(g_mu); napi_env env = nullptr; unofficial_napi_env_owner owner = nullptr; napi_status s; @@ -2923,29 +3637,35 @@ extern "C" int snapi_bridge_unofficial_create_env(int32_t module_api_version, options.size = sizeof(options); options.version = UNOFFICIAL_NAPI_ENV_CREATE_OPTIONS_VERSION; options.guest_heap = reinterpret_cast( - const_cast(guest_heap_ctx)); + 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, nullptr, &env, &owner); } - if (s != napi_ok) return s; + if (s != napi_ok) + return s; - auto* state = new (std::nothrow) SnapiEnvState(); + auto state = + std::shared_ptr(new (std::nothrow) SnapiEnvState()); if (state == nullptr) { (void)unofficial_napi_release_env(owner, nullptr); return napi_generic_failure; } state->env = env; state->owner = owner; - g_envs.insert(state); + { + std::lock_guard registry_lock(g_mu); + g_envs.emplace(state.get(), state); + } - if (env_out != nullptr) *env_out = state; + if (env_out != nullptr) + *env_out = state.get(); return napi_ok; } -extern "C" int snapi_bridge_unofficial_configure_runtime( - const char* engine_flags, - uint32_t engine_flags_length) { +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; @@ -2955,89 +3675,86 @@ extern "C" int snapi_bridge_unofficial_configure_runtime( } 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, + 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, - uint32_t /*stack_limit*/, - const void* guest_heap_ctx, - SnapiEnvState** env_out) { - std::lock_guard lock(g_mu); + uint32_t code_range_size_in_bytes, uint32_t /*stack_limit*/, + const void *guest_heap_ctx, SnapiEnvState **env_out) { + std::lock_guard init_lock(g_mu); unofficial_napi_env_create_options options{}; - const bool has_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; + const bool has_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 = - max_old_generation_size_in_bytes; + options.max_old_generation_size_in_bytes = max_old_generation_size_in_bytes; options.code_range_size_in_bytes = code_range_size_in_bytes; // 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 = reinterpret_cast( - const_cast(guest_heap_ctx)); + const_cast(guest_heap_ctx)); napi_env env = nullptr; 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; + if (s != napi_ok) + return s; - auto* state = new (std::nothrow) SnapiEnvState(); + auto state = + std::shared_ptr(new (std::nothrow) SnapiEnvState()); if (state == nullptr) { (void)unofficial_napi_release_env(owner, nullptr); return napi_generic_failure; } state->env = env; state->owner = owner; - g_envs.insert(state); + { + std::lock_guard registry_lock(g_mu); + g_envs.emplace(state.get(), state); + } - if (env_out != nullptr) *env_out = state; + if (env_out != nullptr) + *env_out = state.get(); return napi_ok; } // Rust-exported grant callback (see budget.rs). Charges a grow-step against // the resource budget and returns the raised (or unchanged) heap limit. -extern "C" size_t napi_host_near_heap_limit_grant(const void* data, +extern "C" size_t napi_host_near_heap_limit_grant(const void *data, size_t current_limit, size_t initial_limit); namespace { // V8-shaped trampoline forwarding to the Rust grant callback. The budget // tracker rides in `data`. -size_t HostNearHeapLimitTrampoline(napi_env /*env*/, void* data, - size_t current_limit, - size_t initial_limit) { +size_t HostNearHeapLimitTrampoline(napi_env /*env*/, void *data, + size_t current_limit, size_t initial_limit) { return napi_host_near_heap_limit_grant(data, current_limit, initial_limit); } -} // namespace +} // namespace extern "C" int snapi_bridge_unofficial_set_host_near_heap_limit_callback( - SnapiEnvState* env_state, const void* data) { - std::lock_guard lock(g_mu); - if (env_state == nullptr || env_state->env == nullptr) { + SnapiEnvState *env_state, const void *data) { + auto lease = RequireEnvState(env_state); + auto *state = lease.get(); + if (state == nullptr) { return napi_invalid_arg; } return unofficial_napi_configure_near_heap_limit_callback( - env_state->env, - HostNearHeapLimitTrampoline, - const_cast(data), - 0); + state->env, HostNearHeapLimitTrampoline, const_cast(data), 0); } // Debug/diagnostics: live count of slot-table entries (leak assertions). -extern "C" uint64_t snapi_bridge_unofficial_get_live_value_count(SnapiEnvState* env_state) { - auto* bridge_state = LookupEnvState(env_state); +extern "C" uint64_t +snapi_bridge_unofficial_get_live_value_count(SnapiEnvState *env_state) { + auto bridge_state_lease = LookupEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); return bridge_state != nullptr ? static_cast(bridge_state->live_value_count) : 0; @@ -3045,58 +3762,62 @@ extern "C" uint64_t snapi_bridge_unofficial_get_live_value_count(SnapiEnvState* // True if `id` still resolves. Used by the Rust layer to prune stale // guest-memory data-pointer mappings keyed by value id. -extern "C" int snapi_bridge_value_id_alive(SnapiEnvState* env_state, uint32_t id) { - auto* bridge_state = LookupEnvState(env_state); - if (bridge_state == nullptr) return 0; +extern "C" int snapi_bridge_value_id_alive(SnapiEnvState *env_state, + uint32_t id) { + auto bridge_state_lease = LookupEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return 0; return LoadValue(*bridge_state, id) != nullptr ? 1 : 0; } -extern "C" int snapi_bridge_unofficial_set_value_limit(SnapiEnvState* env_state, +extern "C" int snapi_bridge_unofficial_set_value_limit(SnapiEnvState *env_state, uint64_t limit) { - std::lock_guard lock(g_mu); - if (env_state == nullptr) { + auto lease = RequireEnvState(env_state); + auto *state = lease.get(); + if (state == nullptr) { return napi_invalid_arg; } - env_state->value_limit = static_cast(limit); + state->value_limit = static_cast(limit); return napi_ok; } -extern "C" int snapi_bridge_unofficial_release_env(SnapiEnvState* env_state) { - std::lock_guard lock(g_mu); - return DisposeBridgeStateLocked(env_state); +extern "C" int snapi_bridge_unofficial_release_env(SnapiEnvState *env_state) { + auto lease = RequireEnvState(env_state); + return DisposeBridgeStateLocked(lease.get()); } -extern "C" int snapi_bridge_unofficial_release_env_with_loop(SnapiEnvState* env_state, - uint32_t loop_id) { +extern "C" int +snapi_bridge_unofficial_release_env_with_loop(SnapiEnvState *env_state, + uint32_t loop_id) { (void)loop_id; - std::lock_guard lock(g_mu); - return DisposeBridgeStateLocked(env_state); + auto lease = RequireEnvState(env_state); + return DisposeBridgeStateLocked(lease.get()); } -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; +extern "C" int +snapi_bridge_unofficial_collect_garbage(SnapiEnvState *env_state) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; - std::lock_guard lock(g_mu); return unofficial_napi_collect_garbage(env); } -extern "C" int snapi_bridge_unofficial_event_loop_checkpoint( - SnapiEnvState* env_state, - int mode, - int has_runnable_work, - uint32_t* checkpoint_state) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int +snapi_bridge_unofficial_event_loop_checkpoint(SnapiEnvState *env_state, + int mode, int has_runnable_work, + uint32_t *checkpoint_state) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; - std::lock_guard lock(g_mu); uint32_t state = unofficial_napi_event_loop_checkpoint_state_none; const int status = unofficial_napi_event_loop_checkpoint( - env, - static_cast(mode), - has_runnable_work != 0, - &state); + env, static_cast(mode), + has_runnable_work != 0, &state); if (checkpoint_state != nullptr) { *checkpoint_state = state; } @@ -3104,176 +3825,208 @@ extern "C" int snapi_bridge_unofficial_event_loop_checkpoint( } extern "C" int snapi_bridge_unofficial_set_prepare_stack_trace_callback( - SnapiEnvState* env_state, - uint32_t callback_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; + SnapiEnvState *env_state, uint32_t callback_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + 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; + 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_prepare_stack_trace_callback(env, callback); } -extern "C" int snapi_bridge_unofficial_get_promise_details(SnapiEnvState* env_state, - uint32_t promise_id, - int32_t* state_out, - uint32_t* result_out, - int* has_result_out) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_unofficial_get_promise_details( + SnapiEnvState *env_state, uint32_t promise_id, int32_t *state_out, + uint32_t *result_out, int *has_result_out) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; - std::lock_guard lock(g_mu); napi_value promise = LoadValue(*bridge_state, promise_id); - if (promise == nullptr) return napi_invalid_arg; + if (promise == nullptr) + return napi_invalid_arg; napi_value result = nullptr; bool has_result = false; - napi_status s = - unofficial_napi_get_promise_details(env, promise, state_out, &result, &has_result); - if (s != napi_ok) return s; - if (result_out != nullptr) *result_out = StoreValue(*bridge_state, result); - if (has_result_out != nullptr) *has_result_out = has_result ? 1 : 0; + napi_status s = unofficial_napi_get_promise_details(env, promise, state_out, + &result, &has_result); + if (s != napi_ok) + return s; + if (result_out != nullptr) + *result_out = StoreValue(*bridge_state, result); + if (has_result_out != nullptr) + *has_result_out = has_result ? 1 : 0; return napi_ok; } -extern "C" int snapi_bridge_unofficial_get_proxy_details(SnapiEnvState* env_state, - uint32_t proxy_id, - uint32_t* target_out, - uint32_t* handler_out) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_unofficial_get_proxy_details( + SnapiEnvState *env_state, uint32_t proxy_id, uint32_t *target_out, + uint32_t *handler_out) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; - std::lock_guard lock(g_mu); napi_value proxy = LoadValue(*bridge_state, proxy_id); - if (proxy == nullptr) return napi_invalid_arg; + if (proxy == nullptr) + return napi_invalid_arg; napi_value target = nullptr; napi_value handler = nullptr; - napi_status s = unofficial_napi_get_proxy_details(env, proxy, &target, &handler); - if (s != napi_ok) return s; - if (target_out != nullptr) *target_out = StoreValue(*bridge_state, target); - if (handler_out != nullptr) *handler_out = StoreValue(*bridge_state, handler); + napi_status s = + unofficial_napi_get_proxy_details(env, proxy, &target, &handler); + if (s != napi_ok) + return s; + if (target_out != nullptr) + *target_out = StoreValue(*bridge_state, target); + if (handler_out != nullptr) + *handler_out = StoreValue(*bridge_state, handler); return napi_ok; } -extern "C" int snapi_bridge_unofficial_preview_entries(SnapiEnvState* env_state, +extern "C" int snapi_bridge_unofficial_preview_entries(SnapiEnvState *env_state, uint32_t value_id, - uint32_t* entries_out, - int* is_key_value_out) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; + uint32_t *entries_out, + int *is_key_value_out) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + 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; + if (value == nullptr) + return napi_invalid_arg; napi_value entries = nullptr; bool is_key_value = false; - napi_status s = unofficial_napi_preview_entries(env, value, &entries, &is_key_value); - if (s != napi_ok) return s; - if (entries_out != nullptr) *entries_out = StoreValue(*bridge_state, entries); - if (is_key_value_out != nullptr) *is_key_value_out = is_key_value ? 1 : 0; + napi_status s = + unofficial_napi_preview_entries(env, value, &entries, &is_key_value); + if (s != napi_ok) + return s; + if (entries_out != nullptr) + *entries_out = StoreValue(*bridge_state, entries); + if (is_key_value_out != nullptr) + *is_key_value_out = is_key_value ? 1 : 0; return napi_ok; } -extern "C" int snapi_bridge_unofficial_get_call_sites(SnapiEnvState* env_state, +extern "C" int snapi_bridge_unofficial_get_call_sites(SnapiEnvState *env_state, uint32_t frames, - uint32_t* callsites_out) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; + uint32_t *callsites_out) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + 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_call_sites(env, frames, &callsites); - if (s != napi_ok) return s; - if (callsites_out != nullptr) *callsites_out = StoreValue(*bridge_state, 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_arraybuffer_view_has_buffer(SnapiEnvState* env_state, - uint32_t value_id, - int* result_out) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_unofficial_arraybuffer_view_has_buffer( + SnapiEnvState *env_state, uint32_t value_id, int *result_out) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + 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; + if (value == nullptr) + return napi_invalid_arg; bool result = false; - napi_status s = unofficial_napi_arraybuffer_view_has_buffer(env, value, &result); - if (s != napi_ok) return s; - if (result_out != nullptr) *result_out = result ? 1 : 0; + napi_status s = + unofficial_napi_arraybuffer_view_has_buffer(env, value, &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_get_constructor_name(SnapiEnvState* env_state, - uint32_t value_id, - uint32_t* name_out) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_unofficial_get_constructor_name( + SnapiEnvState *env_state, uint32_t value_id, uint32_t *name_out) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + 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; + if (value == nullptr) + return napi_invalid_arg; napi_value name = nullptr; napi_status s = unofficial_napi_get_constructor_name(env, value, &name); - if (s != napi_ok) return s; - if (name_out != nullptr) *name_out = StoreValue(*bridge_state, name); + if (s != napi_ok) + return s; + if (name_out != nullptr) + *name_out = StoreValue(*bridge_state, name); return napi_ok; } -extern "C" int snapi_bridge_unofficial_create_private_symbol(SnapiEnvState* env_state, - const char* utf8description, - uint32_t wasm_length, - uint32_t* out_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_unofficial_create_private_symbol( + SnapiEnvState *env_state, const char *utf8description, uint32_t wasm_length, + uint32_t *out_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; - std::lock_guard lock(g_mu); - size_t length = - (wasm_length == 0xFFFFFFFFu) ? NAPI_AUTO_LENGTH : static_cast(wasm_length); + size_t length = (wasm_length == 0xFFFFFFFFu) + ? NAPI_AUTO_LENGTH + : static_cast(wasm_length); napi_value result = nullptr; - napi_status s = - unofficial_napi_create_private_symbol(env, utf8description, length, &result); - if (s != napi_ok) return s; - if (out_id != nullptr) *out_id = StoreValue(*bridge_state, result); + napi_status s = unofficial_napi_create_private_symbol(env, utf8description, + length, &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_continuation_preserved_embedder_data( - SnapiEnvState* env_state, - uint32_t* out_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; + SnapiEnvState *env_state, uint32_t *out_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; - std::lock_guard lock(g_mu); napi_value result = nullptr; napi_status s = unofficial_napi_get_continuation_preserved_embedder_data(env, &result); - if (s != napi_ok) return s; - if (out_id != nullptr) *out_id = StoreValue(*bridge_state, 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_set_continuation_preserved_embedder_data( - SnapiEnvState* env_state, - uint32_t value_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; + SnapiEnvState *env_state, uint32_t value_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; - std::lock_guard lock(g_mu); - napi_value value = value_id == 0 ? nullptr : LoadValue(*bridge_state, value_id); - if (value_id != 0 && value == nullptr) return napi_invalid_arg; + napi_value value = + value_id == 0 ? nullptr : LoadValue(*bridge_state, value_id); + if (value_id != 0 && value == nullptr) + return napi_invalid_arg; return unofficial_napi_set_continuation_preserved_embedder_data(env, value); } extern "C" int snapi_bridge_unofficial_attach_env( - SnapiEnvState* env_state, - uint32_t /*fatal_callback_id*/, - uint32_t /*oom_callback_id*/, - uint64_t* accepted_hooks_out) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr || accepted_hooks_out == nullptr) return napi_invalid_arg; + SnapiEnvState *env_state, uint32_t /*fatal_callback_id*/, + uint32_t /*oom_callback_id*/, uint64_t *accepted_hooks_out) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr || accepted_hooks_out == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; - std::lock_guard lock(g_mu); // 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. @@ -3283,98 +4036,134 @@ extern "C" int snapi_bridge_unofficial_attach_env( return unofficial_napi_attach_env(env, &hooks, accepted_hooks_out); } -extern "C" int snapi_bridge_unofficial_terminate_execution(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_terminate_execution(env); +extern "C" int +snapi_bridge_unofficial_terminate_execution(SnapiEnvState *env_state) { + // Termination must be able to run while the guest holds the environment + // lock in a JS loop. The registry reference keeps the bridge alive, and + // `active_control_calls` keeps its V8 env alive until this call returns. + auto state = LookupEnvState(env_state); + if (state == nullptr) + return napi_invalid_arg; + napi_env env; + { + std::lock_guard lock(state->control_mutex); + if (state->disposing || state->env == nullptr) + return napi_invalid_arg; + ++state->active_control_calls; + env = state->env; + } + const napi_status result = unofficial_napi_terminate_execution(env); + { + std::lock_guard lock(state->control_mutex); + --state->active_control_calls; + if (state->active_control_calls == 0) + state->control_cv.notify_all(); + } + return result; } -extern "C" int snapi_bridge_unofficial_cancel_terminate_execution( - SnapiEnvState* env_state) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int +snapi_bridge_unofficial_cancel_terminate_execution(SnapiEnvState *env_state) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; - std::lock_guard lock(g_mu); return unofficial_napi_cancel_terminate_execution(env); } -extern "C" int snapi_bridge_unofficial_request_interrupt(SnapiEnvState* env_state, - uint32_t guest_env, - uint32_t wasm_fn_ptr, - uint32_t data) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int +snapi_bridge_unofficial_request_interrupt(SnapiEnvState *env_state, + uint32_t guest_env, + uint32_t wasm_fn_ptr, uint32_t data) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; - std::lock_guard lock(g_mu); - if (wasm_fn_ptr == 0) return napi_invalid_arg; - auto* request = - new (std::nothrow) WasmInterruptRequest{bridge_state, guest_env, wasm_fn_ptr, data}; - if (request == nullptr) return napi_generic_failure; + if (wasm_fn_ptr == 0) + return napi_invalid_arg; + auto *request = new (std::nothrow) + WasmInterruptRequest{bridge_state, guest_env, wasm_fn_ptr, data}; + if (request == nullptr) + return napi_generic_failure; napi_status s = unofficial_napi_request_interrupt(env, WasmInterruptCallback, request); - if (s != napi_ok) delete request; + if (s != napi_ok) + delete request; return s; } -extern "C" int snapi_bridge_unofficial_enqueue_microtask(SnapiEnvState* env_state, - uint32_t callback_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int +snapi_bridge_unofficial_enqueue_microtask(SnapiEnvState *env_state, + uint32_t callback_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; - std::lock_guard lock(g_mu); napi_value callback = LoadValue(*bridge_state, callback_id); - if (callback == nullptr) return napi_invalid_arg; + if (callback == nullptr) + return napi_invalid_arg; return unofficial_napi_enqueue_microtask(env, callback); } -extern "C" int snapi_bridge_unofficial_set_promise_reject_callback(SnapiEnvState* env_state, - uint32_t callback_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int +snapi_bridge_unofficial_set_promise_reject_callback(SnapiEnvState *env_state, + uint32_t callback_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + 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; + 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_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; + SnapiEnvState *env_state, uint32_t value_id, uint32_t filter_bits, + uint32_t *out_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + 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; + 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; + 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, - uint32_t before_callback_id, - uint32_t after_callback_id, + SnapiEnvState *env_state, uint32_t init_callback_id, + uint32_t before_callback_id, uint32_t after_callback_id, uint32_t resolve_callback_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; - std::lock_guard lock(g_mu); - napi_value init = init_callback_id == 0 ? nullptr : LoadValue(*bridge_state, init_callback_id); - napi_value before = - before_callback_id == 0 ? nullptr : LoadValue(*bridge_state, before_callback_id); - napi_value after = after_callback_id == 0 ? nullptr : LoadValue(*bridge_state, after_callback_id); - napi_value resolve = - resolve_callback_id == 0 ? nullptr : LoadValue(*bridge_state, resolve_callback_id); + napi_value init = init_callback_id == 0 + ? nullptr + : LoadValue(*bridge_state, init_callback_id); + napi_value before = before_callback_id == 0 + ? nullptr + : LoadValue(*bridge_state, before_callback_id); + napi_value after = after_callback_id == 0 + ? nullptr + : LoadValue(*bridge_state, after_callback_id); + napi_value resolve = resolve_callback_id == 0 + ? nullptr + : LoadValue(*bridge_state, resolve_callback_id); if ((init_callback_id != 0 && init == nullptr) || (before_callback_id != 0 && before == nullptr) || (after_callback_id != 0 && after == nullptr) || @@ -3384,43 +4173,39 @@ 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_hash_seed(SnapiEnvState* env_state, - uint64_t* hash_seed_out) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_unofficial_get_hash_seed(SnapiEnvState *env_state, + uint64_t *hash_seed_out) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; - std::lock_guard lock(g_mu); - if ( - hash_seed_out == nullptr) { + if (hash_seed_out == nullptr) { return napi_invalid_arg; } return unofficial_napi_get_hash_seed(env, hash_seed_out); } 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) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; + 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) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + 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; + if (error == nullptr) + return napi_invalid_arg; unofficial_napi_error_metadata metadata{}; napi_status s = unofficial_napi_get_error_metadata( - env, - error, - static_cast(mode), + env, error, static_cast(mode), &metadata); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; if (source_line_out != nullptr) { *source_line_out = StoreValue(*bridge_state, metadata.source_line); } @@ -3434,144 +4219,156 @@ extern "C" int snapi_bridge_unofficial_get_error_metadata( 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; + 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_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; + SnapiEnvState *env_state, int enabled, uint32_t callback_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + 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; + 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_configure_source_maps(env, enabled != 0, callback); } -extern "C" int snapi_bridge_unofficial_preserve_error_source_message( - SnapiEnvState* env_state, - uint32_t error_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int +snapi_bridge_unofficial_preserve_error_source_message(SnapiEnvState *env_state, + uint32_t error_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + 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; + if (error == nullptr) + return napi_invalid_arg; return unofficial_napi_preserve_error_source_message(env, error); } -extern "C" int snapi_bridge_unofficial_mark_promise_as_handled( - SnapiEnvState* env_state, - uint32_t promise_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int +snapi_bridge_unofficial_mark_promise_as_handled(SnapiEnvState *env_state, + uint32_t promise_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; - std::lock_guard lock(g_mu); napi_value promise = LoadValue(*bridge_state, promise_id); - if (promise == nullptr) return napi_invalid_arg; + if (promise == nullptr) + return napi_invalid_arg; return unofficial_napi_mark_promise_as_handled(env, promise); } extern "C" int snapi_bridge_unofficial_get_heap_statistics( - SnapiEnvState* env_state, - unofficial_napi_heap_statistics* stats_out) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; + SnapiEnvState *env_state, unofficial_napi_heap_statistics *stats_out) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + 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 (stats_out == nullptr) { return napi_invalid_arg; } return unofficial_napi_get_heap_statistics(env, stats_out); } extern "C" int snapi_bridge_unofficial_get_heap_space_statistics( - SnapiEnvState* env_state, - 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; + SnapiEnvState *env_state, unofficial_napi_heap_space_statistics *stats_out, + uint32_t capacity, uint32_t *count_out) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; - std::lock_guard lock(g_mu); if (count_out == nullptr || (capacity > 0 && stats_out == nullptr)) { return napi_invalid_arg; } - return unofficial_napi_get_heap_space_statistics(env, stats_out, capacity, count_out); + return unofficial_napi_get_heap_space_statistics(env, stats_out, capacity, + count_out); } extern "C" int snapi_bridge_unofficial_get_heap_code_statistics( - SnapiEnvState* env_state, - unofficial_napi_heap_code_statistics* stats_out) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; + SnapiEnvState *env_state, unofficial_napi_heap_code_statistics *stats_out) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + 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 (stats_out == nullptr) { return napi_invalid_arg; } return unofficial_napi_get_heap_code_statistics(env, stats_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; +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_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + 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_out == nullptr) { return napi_invalid_arg; } - unofficial_napi_profile_start_result result = unofficial_napi_profile_start_ok; + 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; + if (s != napi_ok) + return s; *result_out = static_cast(result); - *profile_out = bridge_state->profile_handles.Store( - reinterpret_cast(profile)); + *profile_out = + bridge_state->profile_handles.Store(reinterpret_cast(profile)); return napi_ok; } -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; +extern "C" int snapi_bridge_unofficial_profile_stop(SnapiEnvState *env_state, + uint32_t profile_id, + uint32_t *json_out) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; - std::lock_guard lock(g_mu); - if (json_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; + 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); } - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *json_out = json != nullptr ? StoreValue(*bridge_state, json) : 0; return napi_ok; } extern "C" int snapi_bridge_unofficial_take_heap_snapshot( - SnapiEnvState* env_state, - int expose_internals, - int expose_numeric_values, - uint32_t* json_out) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; + SnapiEnvState *env_state, int expose_internals, int expose_numeric_values, + uint32_t *json_out) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; - std::lock_guard lock(g_mu); if (json_out == nullptr) { return napi_invalid_arg; } @@ -3580,53 +4377,61 @@ extern "C" int snapi_bridge_unofficial_take_heap_snapshot( options.expose_numeric_values = expose_numeric_values != 0; napi_value json = nullptr; napi_status s = unofficial_napi_take_heap_snapshot(env, &options, &json); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *json_out = json != nullptr ? StoreValue(*bridge_state, json) : 0; return napi_ok; } -extern "C" void snapi_bridge_free_buffer(void* data) { - std::free(data); -} +extern "C" void snapi_bridge_free_buffer(void *data) { std::free(data); } extern "C" int snapi_bridge_unofficial_structured_clone( - SnapiEnvState* env_state, - uint32_t value_id, - uint32_t transfer_list_id, - uint32_t* out_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; + SnapiEnvState *env_state, uint32_t value_id, uint32_t transfer_list_id, + uint32_t *out_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + 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; + if (value == nullptr) + return napi_invalid_arg; // A zero id means "no transfer list" (the common case: structuredClone with // no transfers). That is valid — StructuredCloneImpl handles a null transfer // list, exactly like the no-transfer entry point. Do NOT reject it. - napi_value transfer_list = - transfer_list_id == 0 ? nullptr : LoadValue(*bridge_state, transfer_list_id); + 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(env, value, transfer_list, &result); - if (s != napi_ok) return s; - if (out_id != nullptr) *out_id = StoreValue(*bridge_state, 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); +extern "C" int snapi_bridge_unofficial_message_create(SnapiEnvState *env_state, + uint32_t value_id, + uint32_t *message_out) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr || message_out == nullptr) + return napi_invalid_arg; napi_value value = LoadValue(*bridge_state, value_id); - if (value == nullptr) return napi_invalid_arg; + 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 (status != napi_ok) + return status; + uint32_t message_id; + { + std::lock_guard registry_lock(g_mu); + message_id = g_message_handles.Store(reinterpret_cast(message)); + } if (message_id == 0) { unofficial_napi_message_drop(message); return napi_generic_failure; @@ -3635,15 +4440,19 @@ extern "C" int snapi_bridge_unofficial_message_create( 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); +extern "C" int snapi_bridge_unofficial_message_take(SnapiEnvState *env_state, + uint32_t message_id, + uint32_t *value_out) { + unofficial_napi_message message; + { + std::lock_guard registry_lock(g_mu); + message = reinterpret_cast( + g_message_handles.Take(message_id)); + } + if (message == nullptr) + return napi_invalid_arg; + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); if (bridge_state == nullptr || value_out == nullptr) { unofficial_napi_message_drop(message); return napi_invalid_arg; @@ -3651,233 +4460,243 @@ extern "C" int snapi_bridge_unofficial_message_take( napi_value value = nullptr; napi_status status = unofficial_napi_message_take(bridge_state->env, message, &value); - if (status != napi_ok) return status; + if (status != napi_ok) + return status; const uint32_t value_id = StoreValue(*bridge_state, value); - if (value_id == 0) return napi_generic_failure; + 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); + unofficial_napi_message message; + { + std::lock_guard registry_lock(g_mu); + 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); - if (bridge_state == nullptr) return napi_invalid_arg; + SnapiEnvState *env_state) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; - std::lock_guard lock(g_mu); return unofficial_napi_notify_datetime_configuration_change(env); } -extern "C" int snapi_bridge_unofficial_create_serdes_binding(SnapiEnvState* env_state, - uint32_t* out_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int +snapi_bridge_unofficial_create_serdes_binding(SnapiEnvState *env_state, + uint32_t *out_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; - std::lock_guard lock(g_mu); napi_value result = nullptr; napi_status s = unofficial_napi_create_serdes_binding(env, &result); - if (s != napi_ok) return s; - if (out_id != nullptr) *out_id = StoreValue(*bridge_state, 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_contextify_contains_module_syntax( - SnapiEnvState* env_state, - uint32_t code_id, - uint32_t filename_id, - uint32_t resource_name_id, - int cjs_var_in_scope, - int* result_out) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; + SnapiEnvState *env_state, uint32_t code_id, uint32_t filename_id, + uint32_t resource_name_id, int cjs_var_in_scope, int *result_out) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; - std::lock_guard lock(g_mu); napi_value code = LoadValue(*bridge_state, code_id); napi_value filename = LoadValue(*bridge_state, filename_id); - napi_value resource_name = resource_name_id == 0 ? nullptr : LoadValue(*bridge_state, resource_name_id); - if (code == nullptr || filename == nullptr) return napi_invalid_arg; - if (resource_name_id != 0 && resource_name == nullptr) return napi_invalid_arg; + napi_value resource_name = resource_name_id == 0 + ? nullptr + : LoadValue(*bridge_state, resource_name_id); + if (code == nullptr || filename == nullptr) + return napi_invalid_arg; + if (resource_name_id != 0 && resource_name == nullptr) + return napi_invalid_arg; bool result = false; napi_status s = unofficial_napi_contextify_contains_module_syntax( env, code, filename, resource_name, cjs_var_in_scope != 0, &result); - if (s != napi_ok) return s; - if (result_out != nullptr) *result_out = result ? 1 : 0; + if (s != napi_ok) + return s; + if (result_out != nullptr) + *result_out = result ? 1 : 0; return napi_ok; } extern "C" int snapi_bridge_unofficial_contextify_make_context( - SnapiEnvState* env_state, - uint32_t sandbox_or_symbol_id, - uint32_t name_id, - uint32_t origin_id, - int allow_code_gen_strings, - int allow_code_gen_wasm, - int own_microtask_queue, - uint32_t host_defined_option_id, - uint32_t* result_out) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; + SnapiEnvState *env_state, uint32_t sandbox_or_symbol_id, uint32_t name_id, + uint32_t origin_id, int allow_code_gen_strings, int allow_code_gen_wasm, + int own_microtask_queue, uint32_t host_defined_option_id, + uint32_t *result_out) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; - std::lock_guard lock(g_mu); napi_value sandbox_or_symbol = LoadValue(*bridge_state, sandbox_or_symbol_id); napi_value name = LoadValue(*bridge_state, name_id); - napi_value origin = origin_id == 0 ? nullptr : LoadValue(*bridge_state, origin_id); + napi_value origin = + origin_id == 0 ? nullptr : LoadValue(*bridge_state, origin_id); napi_value host_defined_option = - host_defined_option_id == 0 ? nullptr : LoadValue(*bridge_state, host_defined_option_id); - if (sandbox_or_symbol == nullptr || name == nullptr) return napi_invalid_arg; - if (origin_id != 0 && origin == nullptr) return napi_invalid_arg; - if (host_defined_option_id != 0 && host_defined_option == nullptr) return napi_invalid_arg; + host_defined_option_id == 0 + ? nullptr + : LoadValue(*bridge_state, host_defined_option_id); + if (sandbox_or_symbol == nullptr || name == nullptr) + return napi_invalid_arg; + if (origin_id != 0 && origin == nullptr) + return napi_invalid_arg; + if (host_defined_option_id != 0 && host_defined_option == nullptr) + return napi_invalid_arg; napi_value result = nullptr; napi_status s = unofficial_napi_contextify_make_context( - env, - sandbox_or_symbol, - name, - origin, - allow_code_gen_strings != 0, - allow_code_gen_wasm != 0, - own_microtask_queue != 0, - host_defined_option, + env, sandbox_or_symbol, name, origin, allow_code_gen_strings != 0, + allow_code_gen_wasm != 0, own_microtask_queue != 0, host_defined_option, &result); - if (s != napi_ok) return s; - if (result_out != nullptr) *result_out = StoreValue(*bridge_state, 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_contextify_run_script( - SnapiEnvState* env_state, - uint32_t sandbox_or_null_id, - uint32_t source_text_id, - uint32_t source_bytecode_id, - uint32_t filename_id, - int32_t line_offset, - int32_t column_offset, - int64_t timeout, - int display_errors, - int break_on_sigint, - int break_on_first_line, - uint32_t host_defined_option_id, - uint32_t* result_out) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; + SnapiEnvState *env_state, uint32_t sandbox_or_null_id, + uint32_t source_text_id, uint32_t source_bytecode_id, uint32_t filename_id, + int32_t line_offset, int32_t column_offset, int64_t timeout, + int display_errors, int break_on_sigint, int break_on_first_line, + uint32_t host_defined_option_id, uint32_t *result_out) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; - 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); + 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); 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 != nullptr - ? unofficial_napi_js_source_from_text(source_text) - : unofficial_napi_js_source_from_bytecode(source_bytecode); + 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 != 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, - sandbox_or_null, - &source, - filename, - line_offset, - column_offset, - timeout, - display_errors != 0, - break_on_sigint != 0, - break_on_first_line != 0, - host_defined_option, - &result); - if (s != napi_ok) return s; - if (result_out != nullptr) *result_out = StoreValue(*bridge_state, result); + env, sandbox_or_null, &source, filename, line_offset, column_offset, + timeout, display_errors != 0, break_on_sigint != 0, + break_on_first_line != 0, host_defined_option, &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_contextify_compile_function( - SnapiEnvState* env_state, - uint32_t source_text_id, - uint32_t source_bytecode_id, - uint32_t filename_id, - int32_t line_offset, - int32_t column_offset, - uint32_t parsing_context_id, - uint32_t context_extensions_id, - uint32_t params_id, - uint32_t host_defined_option_id, - uint32_t* result_out) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; + SnapiEnvState *env_state, uint32_t source_text_id, + uint32_t source_bytecode_id, uint32_t filename_id, int32_t line_offset, + int32_t column_offset, uint32_t parsing_context_id, + uint32_t context_extensions_id, uint32_t params_id, + uint32_t host_defined_option_id, uint32_t *result_out) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + 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); + napi_value source_text = + source_text_id == 0 ? nullptr : LoadValue(*bridge_state, source_text_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 parsing_context = + parsing_context_id == 0 ? nullptr + : LoadValue(*bridge_state, parsing_context_id); napi_value context_extensions = - context_extensions_id == 0 ? nullptr : LoadValue(*bridge_state, context_extensions_id); - napi_value params = params_id == 0 ? nullptr : LoadValue(*bridge_state, params_id); + context_extensions_id == 0 + ? nullptr + : LoadValue(*bridge_state, context_extensions_id); + napi_value params = + params_id == 0 ? nullptr : LoadValue(*bridge_state, params_id); napi_value host_defined_option = - host_defined_option_id == 0 ? nullptr : LoadValue(*bridge_state, host_defined_option_id); - if ((source_text == nullptr && source_bytecode == nullptr) || filename == nullptr) return napi_invalid_arg; - if (parsing_context_id != 0 && parsing_context == nullptr) return napi_invalid_arg; - 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 != nullptr - ? unofficial_napi_js_source_from_text(source_text) - : unofficial_napi_js_source_from_bytecode(source_bytecode); + host_defined_option_id == 0 + ? nullptr + : LoadValue(*bridge_state, host_defined_option_id); + if ((source_text == nullptr && source_bytecode == nullptr) || + filename == nullptr) + return napi_invalid_arg; + if (parsing_context_id != 0 && parsing_context == nullptr) + return napi_invalid_arg; + 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 != 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, - &source, - filename, - line_offset, - column_offset, - parsing_context, - context_extensions, - params, - host_defined_option, - &result); - if (s != napi_ok) return s; - if (result_out != nullptr) *result_out = StoreValue(*bridge_state, result); + env, &source, filename, line_offset, column_offset, parsing_context, + context_extensions, params, host_defined_option, &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_open( - SnapiEnvState* env_state, - uint32_t source_text_id, - uint32_t filename_id, - int32_t shape, - uint32_t params_id, - 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; + SnapiEnvState *env_state, uint32_t source_text_id, uint32_t filename_id, + int32_t shape, uint32_t params_id, 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_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + 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); + napi_value params = + params_id == 0 ? nullptr : LoadValue(*bridge_state, params_id); napi_value host_defined_option = - host_defined_option_id == 0 ? nullptr : LoadValue(*bridge_state, host_defined_option_id); - 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; + host_defined_option_id == 0 + ? nullptr + : LoadValue(*bridge_state, host_defined_option_id); + 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; unofficial_napi_bytecode_open_options options{}; options.size = sizeof(options); options.version = UNOFFICIAL_NAPI_BYTECODE_OPEN_OPTIONS_VERSION; @@ -3894,11 +4713,13 @@ extern "C" int snapi_bridge_unofficial_bytecode_open( 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 (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 (s != napi_ok) return s; + if (s != napi_ok) + return s; if (bytecode_out != nullptr) { *bytecode_out = StoreBytecodeHandle(*bridge_state, result.bytecode); } @@ -3906,73 +4727,81 @@ extern "C" int snapi_bridge_unofficial_bytecode_open( } extern "C" int snapi_bridge_unofficial_bytecode_serialize( - SnapiEnvState* env_state, - uint32_t bytecode_id, - uint32_t* buffer_out) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; + SnapiEnvState *env_state, uint32_t bytecode_id, uint32_t *buffer_out) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; - std::lock_guard lock(g_mu); - unofficial_napi_bytecode bytecode = LoadBytecodeHandle(*bridge_state, bytecode_id); - if (bytecode == nullptr) return napi_invalid_arg; + 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); - if (s != napi_ok) return s; - if (buffer_out != nullptr) *buffer_out = StoreValue(*bridge_state, buffer); + if (s != napi_ok) + return s; + if (buffer_out != nullptr) + *buffer_out = StoreValue(*bridge_state, buffer); return napi_ok; } -extern "C" int snapi_bridge_unofficial_bytecode_release( - SnapiEnvState* env_state, - uint32_t bytecode_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int +snapi_bridge_unofficial_bytecode_release(SnapiEnvState *env_state, + uint32_t bytecode_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; - std::lock_guard lock(g_mu); - unofficial_napi_bytecode bytecode = LoadBytecodeHandle(*bridge_state, bytecode_id); - if (bytecode == nullptr) return napi_invalid_arg; + 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_module_wrap_create( - SnapiEnvState* env_state, - int32_t kind, - uint32_t wrapper_id, - uint32_t url_id, - uint32_t context_id, - uint32_t source_text_id, - uint32_t source_bytecode_id, - int32_t line_offset, - int32_t column_offset, - uint32_t host_defined_option_id, - 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; + SnapiEnvState *env_state, int32_t kind, uint32_t wrapper_id, + uint32_t url_id, uint32_t context_id, uint32_t source_text_id, + uint32_t source_bytecode_id, int32_t line_offset, int32_t column_offset, + uint32_t host_defined_option_id, 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_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + 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 source_text = source_text_id == 0 ? nullptr : LoadValue(*bridge_state, source_text_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); 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); - 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; - if (export_names_id != 0 && export_names == nullptr) return napi_invalid_arg; + host_defined_option_id == 0 + ? nullptr + : LoadValue(*bridge_state, host_defined_option_id); + 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; + 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; } @@ -3986,30 +4815,32 @@ extern "C" int snapi_bridge_unofficial_module_wrap_create( 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: + 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 (s != napi_ok) + return s; if (result.module == nullptr || result.module_requests == nullptr) { if (result.module != nullptr) { (void)unofficial_napi_module_wrap_destroy(env, result.module); @@ -4028,86 +4859,105 @@ extern "C" int snapi_bridge_unofficial_module_wrap_create( return napi_ok; } -extern "C" int snapi_bridge_unofficial_module_wrap_destroy(SnapiEnvState* env_state, - uint32_t handle_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int +snapi_bridge_unofficial_module_wrap_destroy(SnapiEnvState *env_state, + uint32_t handle_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; - std::lock_guard lock(g_mu); - unofficial_napi_module module = LoadModuleWrapHandle(*bridge_state, handle_id); - if (module == nullptr) return napi_invalid_arg; + 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); + if (s == napi_ok) + RemoveModuleWrapHandle(*bridge_state, handle_id); return s; } -extern "C" int snapi_bridge_unofficial_module_wrap_link(SnapiEnvState* env_state, - uint32_t handle_id, - uint32_t count, - const uint32_t* linked_handle_ids) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int +snapi_bridge_unofficial_module_wrap_link(SnapiEnvState *env_state, + uint32_t handle_id, uint32_t count, + const uint32_t *linked_handle_ids) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; - std::lock_guard lock(g_mu); - unofficial_napi_module module = LoadModuleWrapHandle(*bridge_state, handle_id); - if (module == nullptr) return napi_invalid_arg; + 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) { unofficial_napi_module linked = linked_handle_ids != nullptr ? LoadModuleWrapHandle(*bridge_state, linked_handle_ids[i]) : nullptr; - if (linked == nullptr) return napi_invalid_arg; + if (linked == nullptr) + return napi_invalid_arg; linked_modules[i] = linked; } return unofficial_napi_module_wrap_link( env, module, count, count == 0 ? nullptr : linked_modules.data()); } -extern "C" int snapi_bridge_unofficial_module_wrap_instantiate(SnapiEnvState* env_state, - uint32_t handle_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int +snapi_bridge_unofficial_module_wrap_instantiate(SnapiEnvState *env_state, + uint32_t handle_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; - std::lock_guard lock(g_mu); - unofficial_napi_module module = LoadModuleWrapHandle(*bridge_state, handle_id); - if (module == nullptr) return napi_invalid_arg; + 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, - uint32_t handle_id, - int64_t timeout, - int break_on_sigint, - uint32_t* result_out) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_unofficial_module_wrap_evaluate( + SnapiEnvState *env_state, uint32_t handle_id, int64_t timeout, + int break_on_sigint, uint32_t *result_out) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; - std::lock_guard lock(g_mu); - unofficial_napi_module module = LoadModuleWrapHandle(*bridge_state, handle_id); - if (module == 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, module, timeout, break_on_sigint != 0, &result); - if (s != napi_ok) return s; - if (result_out != nullptr) *result_out = StoreValue(*bridge_state, 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_evaluate_sync(SnapiEnvState* env_state, - uint32_t handle_id, - uint32_t filename_id, - uint32_t parent_filename_id, - uint32_t* result_out) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_unofficial_module_wrap_evaluate_sync( + SnapiEnvState *env_state, uint32_t handle_id, uint32_t filename_id, + uint32_t parent_filename_id, uint32_t *result_out) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; - std::lock_guard lock(g_mu); - 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); + 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); + parent_filename_id == 0 ? nullptr + : LoadValue(*bridge_state, parent_filename_id); if ((filename_id != 0 && filename == nullptr) || (parent_filename_id != 0 && parent_filename == nullptr)) { return napi_invalid_arg; @@ -4115,39 +4965,46 @@ 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, module, filename, parent_filename, &result); - if (s != napi_ok) return s; - if (result_out != nullptr) *result_out = StoreValue(*bridge_state, 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_namespace(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; +extern "C" int snapi_bridge_unofficial_module_wrap_get_namespace( + SnapiEnvState *env_state, uint32_t handle_id, uint32_t *result_out) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; - std::lock_guard lock(g_mu); - unofficial_napi_module module = LoadModuleWrapHandle(*bridge_state, handle_id); - if (module == 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, module, &result); - if (s != napi_ok) return s; - if (result_out != nullptr) *result_out = StoreValue(*bridge_state, 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_state( - SnapiEnvState* env_state, - uint32_t handle_id, - 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; + SnapiEnvState *env_state, uint32_t handle_id, int32_t *status_out, + uint32_t *error_out, int *has_async_graph_out) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; - std::lock_guard lock(g_mu); - unofficial_napi_module module = LoadModuleWrapHandle(*bridge_state, handle_id); - if (module == nullptr) return napi_invalid_arg; + 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; @@ -4155,119 +5012,141 @@ extern "C" int snapi_bridge_unofficial_module_wrap_get_state( 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, + 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 (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 handle_id, - int warnings, - int* settled_out) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int +snapi_bridge_unofficial_module_wrap_check_unsettled_top_level_await( + SnapiEnvState *env_state, uint32_t handle_id, int warnings, + int *settled_out) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + 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; + if (settled_out == nullptr) + return napi_invalid_arg; unofficial_napi_module module = LoadModuleWrapHandle(*bridge_state, handle_id); - if (module == nullptr) return napi_invalid_arg; + if (module == nullptr) + return napi_invalid_arg; bool settled = true; napi_status s = unofficial_napi_module_wrap_check_unsettled_top_level_await( env, module, warnings != 0, &settled); - if (s != napi_ok) return s; + if (s != napi_ok) + return s; *settled_out = settled ? 1 : 0; return napi_ok; } -extern "C" int snapi_bridge_unofficial_module_wrap_set_export(SnapiEnvState* env_state, - uint32_t handle_id, - uint32_t export_name_id, - uint32_t export_value_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; +extern "C" int snapi_bridge_unofficial_module_wrap_set_export( + SnapiEnvState *env_state, uint32_t handle_id, uint32_t export_name_id, + uint32_t export_value_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; - std::lock_guard lock(g_mu); - unofficial_napi_module module = 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 (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, module, export_name, export_value); + napi_value export_value = export_value_id == 0 + ? nullptr + : LoadValue(*bridge_state, export_value_id); + 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, module, export_name, + export_value); } extern "C" int snapi_bridge_unofficial_module_wrap_set_module_source_object( - SnapiEnvState* env_state, - uint32_t handle_id, - uint32_t source_object_id) { - auto* bridge_state = RequireEnvState(env_state); - if (bridge_state == nullptr) return napi_invalid_arg; + SnapiEnvState *env_state, uint32_t handle_id, uint32_t source_object_id) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; - std::lock_guard lock(g_mu); - 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 (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, module, source_object); + 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 (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, module, + source_object); } extern "C" int snapi_bridge_unofficial_module_wrap_get_module_source_object( - 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; + SnapiEnvState *env_state, uint32_t handle_id, uint32_t *result_out) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; - std::lock_guard lock(g_mu); - unofficial_napi_module module = LoadModuleWrapHandle(*bridge_state, handle_id); - if (module == 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, module, &result); - if (s != napi_ok) return s; - if (result_out != nullptr) *result_out = StoreValue(*bridge_state, 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; } extern "C" int snapi_bridge_unofficial_module_wrap_create_cached_data( - 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; + SnapiEnvState *env_state, uint32_t handle_id, uint32_t *result_out) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; - std::lock_guard lock(g_mu); - unofficial_napi_module module = LoadModuleWrapHandle(*bridge_state, handle_id); - if (module == 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, module, &result); - if (s != napi_ok) return s; - if (result_out != nullptr) *result_out = StoreValue(*bridge_state, 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_hooks( - SnapiEnvState* env_state, - uint32_t import_callback_id, + SnapiEnvState *env_state, 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; + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; - std::lock_guard lock(g_mu); - 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); + 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; @@ -4281,20 +5160,25 @@ extern "C" int snapi_bridge_unofficial_module_wrap_set_hooks( return unofficial_napi_module_wrap_set_hooks(env, &hooks); } -extern "C" int snapi_bridge_unofficial_module_wrap_create_required_module_facade( - 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; +extern "C" int +snapi_bridge_unofficial_module_wrap_create_required_module_facade( + SnapiEnvState *env_state, uint32_t handle_id, uint32_t *result_out) { + auto bridge_state_lease = RequireEnvState(env_state); + auto *bridge_state = bridge_state_lease.get(); + if (bridge_state == nullptr) + return napi_invalid_arg; napi_env env = bridge_state->env; - std::lock_guard lock(g_mu); - unofficial_napi_module module = LoadModuleWrapHandle(*bridge_state, handle_id); - if (module == 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, module, &result); - if (s != napi_ok) return s; - if (result_out != nullptr) *result_out = StoreValue(*bridge_state, 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; } @@ -4303,16 +5187,23 @@ extern "C" int snapi_bridge_unofficial_module_wrap_create_required_module_facade // ============================================================ extern "C" void snapi_bridge_dispose() { - std::lock_guard lock(g_mu); - std::vector env_states; - env_states.reserve(g_envs.size()); - for (auto* env_state : g_envs) { - env_states.push_back(env_state); + std::vector> env_states; + { + std::lock_guard registry_lock(g_mu); + env_states.reserve(g_envs.size()); + for (const auto &entry : g_envs) + env_states.push_back(entry.second); + } + for (const auto &state : env_states) { + EnvLease lease(state); + (void)DisposeBridgeStateLocked(lease.get()); } - for (auto* env_state : env_states) { - (void)DisposeBridgeStateLocked(env_state); + std::vector handles; + { + std::lock_guard registry_lock(g_mu); + handles = g_message_handles.TakeAll(); } - for (void* handle : g_message_handles.TakeAll()) { + for (void *handle : handles) { unofficial_napi_message_drop( reinterpret_cast(handle)); } diff --git a/src/snapi.rs b/src/snapi.rs index 3befe39d..90def3c7 100644 --- a/src/snapi.rs +++ b/src/snapi.rs @@ -672,6 +672,7 @@ unsafe extern "C" { finalize_hint: *mut core::ffi::c_void, backing_store_token_out: *mut u64, out_id: *mut u32, + ownership_transferred_out: *mut i32, ) -> i32; pub fn snapi_bridge_create_external_buffer_finalized( env: SnapiEnv, @@ -680,6 +681,7 @@ unsafe extern "C" { finalize_hint: *mut core::ffi::c_void, backing_store_token_out: *mut u64, out_id: *mut u32, + ownership_transferred_out: *mut i32, ) -> i32; pub fn snapi_bridge_create_external_arraybuffer_guest_finalized( env: SnapiEnv, diff --git a/tests/core_callbacks.rs b/tests/core_callbacks.rs new file mode 100644 index 00000000..8b5156e9 --- /dev/null +++ b/tests/core_callbacks.rs @@ -0,0 +1,281 @@ +use wasmer::{AsStoreMut, Function, Instance, Module, RuntimeError, Store}; +use wasmer_napi::NapiCtx; +use wasmer_wasix::{WasiError, wasmer_wasix_types::wasi::ExitCode}; +fn configure_runtime() { + unsafe extern "C" { + fn snapi_bridge_unofficial_configure_runtime( + flags: *const std::ffi::c_char, + length: u32, + ) -> i32; + } + assert_eq!( + unsafe { snapi_bridge_unofficial_configure_runtime(std::ptr::null(), 0) }, + 0 + ); +} + +#[test] +fn core_callback_exit_probe() { + configure_runtime(); + let cases = [ + ( + "create_buffer", + "(import \"napi\" \"napi_create_buffer\" (func $op (param i32 i32 i32 i32) (result i32)))", + "Object.defineProperty(globalThis, 'Buffer', {get(){exitNow();return undefined;}}); 0", + "", + "(call $op (local.get $env) (i32.const 8) (i32.const 28) (i32.const 32))", + ), + ( + "create_buffer_copy", + "(import \"napi\" \"napi_create_buffer_copy\" (func $op (param i32 i32 i32 i32 i32) (result i32)))", + "Object.defineProperty(globalThis, 'Buffer', {get(){exitNow();return undefined;}}); 0", + "", + "(call $op (local.get $env) (i32.const 1) (i32.const 500) (i32.const 28) (i32.const 32))", + ), + ( + "create_typedarray", + "(import \"napi\" \"napi_create_typedarray\" (func $op (param i32 i32 i32 i32 i32 i32) (result i32)))", + "globalThis.Uint8Array=function(){exitNow();}; new ArrayBuffer(8)", + "", + "(call $op (local.get $env) (i32.const 1) (i32.const 8) (i32.load (i32.const 24)) (i32.const 0) (i32.const 28))", + ), + ( + "create_dataview", + "(import \"napi\" \"napi_create_dataview\" (func $op (param i32 i32 i32 i32 i32) (result i32)))", + "globalThis.DataView=function(){exitNow();}; new ArrayBuffer(8)", + "", + "(call $op (local.get $env) (i32.const 8) (i32.load (i32.const 24)) (i32.const 0) (i32.const 28))", + ), + ( + "create_error", + "(import \"napi\" \"napi_create_error\" (func $op (param i32 i32 i32 i32) (result i32)))", + "Object.defineProperty(Error.prototype, 'code', { set(v) {exitNow();} }); 'msg'", + "", + "(call $op (local.get $env) (i32.load (i32.const 24)) (i32.load (i32.const 24)) (i32.const 28))", + ), + ( + "create_type_error", + "(import \"napi\" \"napi_create_type_error\" (func $op (param i32 i32 i32 i32) (result i32)))", + "Object.defineProperty(Error.prototype, 'code', { set(v) {exitNow();} }); 'msg'", + "", + "(call $op (local.get $env) (i32.load (i32.const 24)) (i32.load (i32.const 24)) (i32.const 28))", + ), + ( + "create_range_error", + "(import \"napi\" \"napi_create_range_error\" (func $op (param i32 i32 i32 i32) (result i32)))", + "Object.defineProperty(Error.prototype, 'code', { set(v) {exitNow();} }); 'msg'", + "", + "(call $op (local.get $env) (i32.load (i32.const 24)) (i32.load (i32.const 24)) (i32.const 28))", + ), + ( + "throw_error", + "(import \"napi\" \"napi_throw_error\" (func $op (param i32 i32 i32) (result i32)))", + "Object.defineProperty(Error.prototype, 'code', { set(v) {exitNow();} }); 'msg'", + "", + "(call $op (local.get $env) (i32.const 500) (i32.const 500))", + ), + ( + "throw_type_error", + "(import \"napi\" \"napi_throw_type_error\" (func $op (param i32 i32 i32) (result i32)))", + "Object.defineProperty(Error.prototype, 'code', { set(v) {exitNow();} }); 'msg'", + "", + "(call $op (local.get $env) (i32.const 500) (i32.const 500))", + ), + ( + "throw_range_error", + "(import \"napi\" \"napi_throw_range_error\" (func $op (param i32 i32 i32) (result i32)))", + "Object.defineProperty(Error.prototype, 'code', { set(v) {exitNow();} }); 'msg'", + "", + "(call $op (local.get $env) (i32.const 500) (i32.const 500))", + ), + ( + "coerce_number", + "(import \"napi\" \"napi_coerce_to_number\" (func $op (param i32 i32 i32) (result i32)))", + "({ valueOf() { exitNow(); return 7; } })", + "", + "(call $op (local.get $env) (i32.load (i32.const 24)) (i32.const 28))", + ), + ( + "coerce_string", + "(import \"napi\" \"napi_coerce_to_string\" (func $op (param i32 i32 i32) (result i32)))", + "({ toString() { exitNow(); return 'x'; } })", + "", + "(call $op (local.get $env) (i32.load (i32.const 24)) (i32.const 28))", + ), + ( + "instanceof", + "(import \"napi\" \"napi_instanceof\" (func $op (param i32 i32 i32 i32) (result i32)))", + "function C() {}; Object.defineProperty(C, Symbol.hasInstance, {value: function(){exitNow(); return true;}}); C", + "", + "(call $op (local.get $env) (i32.load (i32.const 16)) (i32.load (i32.const 24)) (i32.const 28))", + ), + ( + "freeze", + "(import \"napi\" \"napi_object_freeze\" (func $op (param i32 i32) (result i32)))", + "new Proxy({}, { preventExtensions(t) { exitNow(); return Reflect.preventExtensions(t) } })", + "", + "(call $op (local.get $env) (i32.load (i32.const 24)))", + ), + ( + "seal", + "(import \"napi\" \"napi_object_seal\" (func $op (param i32 i32) (result i32)))", + "new Proxy({}, { preventExtensions(t) { exitNow(); return Reflect.preventExtensions(t) } })", + "", + "(call $op (local.get $env) (i32.load (i32.const 24)))", + ), + ( + "set_prototype", + "(import \"napi\" \"node_api_set_prototype\" (func $op (param i32 i32 i32) (result i32)))", + "new Proxy({}, { setPrototypeOf(t,p) { exitNow(); return Reflect.setPrototypeOf(t,p) } })", + "", + "(call $op (local.get $env) (i32.load (i32.const 24)) (i32.load (i32.const 16)))", + ), + ( + "resolve", + "(import \"napi\" \"napi_create_promise\" (func $promise (param i32 i32 i32) (result i32))) (import \"napi\" \"napi_resolve_deferred\" (func $op (param i32 i32 i32) (result i32)))", + "({ get then() { exitNow(); return undefined; } })", + "", + "(drop (call $promise (local.get $env) (i32.const 28) (i32.const 32))) (call $op (local.get $env) (i32.load (i32.const 28)) (i32.load (i32.const 24)))", + ), + ( + "reject", + "(import \"napi\" \"napi_create_promise\" (func $promise (param i32 i32 i32) (result i32))) (import \"napi\" \"napi_reject_deferred\" (func $op (param i32 i32 i32) (result i32))) (import \"napi_extension_wasmer_v0\" \"unofficial_napi_set_promise_reject_callback\" (func $set_cb (param i32 i32) (result i32)))", + "7", + "(drop (call $set_cb (local.get $env) (i32.load (i32.const 12))))", + "(drop (call $promise (local.get $env) (i32.const 28) (i32.const 32))) (call $op (local.get $env) (i32.load (i32.const 28)) (i32.load (i32.const 24)))", + ), + ( + "create_promise", + "(import \"napi\" \"napi_create_promise\" (func $op (param i32 i32 i32) (result i32))) (import \"napi_extension_wasmer_v0\" \"unofficial_napi_set_promise_hooks\" (func $set_hooks (param i32 i32 i32 i32 i32) (result i32)))", + "7", + "(drop (call $set_hooks (local.get $env) (i32.load (i32.const 12)) (i32.const 0) (i32.const 0) (i32.const 0)))", + "(call $op (local.get $env) (i32.const 28) (i32.const 32))", + ), + ( + "define_properties", + "(import \"napi\" \"napi_define_properties\" (func $op (param i32 i32 i32 i32) (result i32)))", + "new Proxy({}, { defineProperty(t,k,d) { exitNow(); return Reflect.defineProperty(t,k,d) } })", + "(i32.store (i32.const 400) (i32.const 500)) (i32.store (i32.const 420) (i32.load (i32.const 16)))", + "(call $op (local.get $env) (i32.load (i32.const 24)) (i32.const 1) (i32.const 400))", + ), + ]; + let mut failures = Vec::new(); + for (name, import, js, setup, op) in cases { + let wat = format!( + r#"(module + (import "napi_extension_wasmer_v0" "unofficial_napi_create_env" (func $create (param i32 i32 i32 i32) (result i32))) + (import "napi" "napi_create_function" (func $function (param i32 i32 i32 i32 i32 i32) (result i32))) + (import "napi" "napi_get_global" (func $global (param i32 i32) (result i32))) + (import "napi" "napi_set_named_property" (func $set (param i32 i32 i32 i32) (result i32))) + (import "napi" "napi_create_string_utf8" (func $string (param i32 i32 i32 i32) (result i32))) + (import "napi" "napi_run_script" (func $script (param i32 i32 i32) (result i32))) + (import "wasi_snapshot_preview1" "proc_exit" (func $exit (param i32))) + {import} + (memory (export "memory") 1) + (table (export "__indirect_function_table") 1 funcref) + (elem (i32.const 0) $callback) + (data (i32.const 100) "exitNow\00") + (data (i32.const 150) "{js}\00") + (data (i32.const 500) "x\00") + (func $callback (param i32 i32) (result i32) + (call $exit (i32.const 57)) (i32.const 0)) + (func (export "run") (result i32) + (local $env i32) + (drop (call $create (i32.const 8) (i32.const 0) (i32.const 4) (i32.const 8))) + (local.set $env (i32.load (i32.const 4))) + (drop (call $function (local.get $env) (i32.const 100) (i32.const -1) (i32.const 0) (i32.const 0) (i32.const 12))) + (drop (call $global (local.get $env) (i32.const 16))) + (drop (call $set (local.get $env) (i32.load (i32.const 16)) (i32.const 100) (i32.load (i32.const 12)))) + (drop (call $string (local.get $env) (i32.const 150) (i32.const -1) (i32.const 20))) + (drop (call $script (local.get $env) (i32.load (i32.const 20)) (i32.const 24))) + {setup} + {op}))"# + ); + let ctx = NapiCtx::default(); + let mut store = Store::default(); + let module = Module::new(&store, wat::parse_str(&wat).unwrap()).unwrap(); + let session = ctx.new_session(&module).unwrap(); + let mut imports = session.create_imports(&mut store.as_store_mut()).unwrap(); + imports.define( + "wasi_snapshot_preview1", + "proc_exit", + Function::new_typed(&mut store, |code: i32| -> Result<(), RuntimeError> { + Err(RuntimeError::user(Box::new(WasiError::Exit( + ExitCode::from(code), + )))) + }), + ); + let instance = Instance::new(&mut store, &module, &imports).unwrap(); + session + .configure_instance( + &mut store.as_store_mut(), + &instance, + Some(instance.exports.get_memory("memory").unwrap()), + ) + .unwrap(); + let result = instance + .exports + .get_typed_function::<(), i32>(&store, "run") + .unwrap() + .call(&mut store); + if !matches!(result, Err(ref e) if matches!(e.downcast_ref::(), Some(WasiError::Exit(c)) if *c == ExitCode::from(57))) + { + failures.push(format!("{name}: {result:?}")); + } + } + assert!(failures.is_empty(), "Lost callback exits: {failures:?}"); +} + +#[test] +fn reentrant_release_refuses_to_dispose_the_entered_isolate() { + configure_runtime(); + let wat = r#"(module + (import "napi_extension_wasmer_v0" "unofficial_napi_create_env" (func $create (param i32 i32 i32 i32) (result i32))) + (import "napi_extension_wasmer_v0" "unofficial_napi_release_env" (func $release (param i32 i32) (result i32))) + (import "napi" "napi_create_function" (func $function (param i32 i32 i32 i32 i32 i32) (result i32))) + (import "napi" "napi_get_global" (func $global (param i32 i32) (result i32))) + (import "napi" "napi_set_named_property" (func $set (param i32 i32 i32 i32) (result i32))) + (import "napi" "napi_create_string_utf8" (func $string (param i32 i32 i32 i32) (result i32))) + (import "napi" "napi_run_script" (func $script (param i32 i32 i32) (result i32))) + (memory (export "memory") 1) + (table (export "__indirect_function_table") 1 funcref) + (elem (i32.const 0) $callback) + (data (i32.const 100) "releaseNow\00") + (data (i32.const 150) "releaseNow(); 42\00") + (func $callback (param i32 i32) (result i32) + (i32.store (i32.const 40) (call $release (i32.load (i32.const 8)) (i32.const 0))) + (i32.const 0)) + (func (export "run") (result i32) + (local $env i32) + (drop (call $create (i32.const 8) (i32.const 0) (i32.const 4) (i32.const 8))) + (local.set $env (i32.load (i32.const 4))) + (drop (call $function (local.get $env) (i32.const 100) (i32.const -1) (i32.const 0) (i32.const 0) (i32.const 12))) + (drop (call $global (local.get $env) (i32.const 16))) + (drop (call $set (local.get $env) (i32.load (i32.const 16)) (i32.const 100) (i32.load (i32.const 12)))) + (drop (call $string (local.get $env) (i32.const 150) (i32.const -1) (i32.const 20))) + (if (call $script (local.get $env) (i32.load (i32.const 20)) (i32.const 24)) + (then (return (i32.const -1)))) + ;; The callback must fail without losing the environment registration. + (if (i32.eqz (i32.load (i32.const 40))) (then (return (i32.const -2)))) + (call $script (local.get $env) (i32.load (i32.const 20)) (i32.const 24))))"#; + let ctx = NapiCtx::default(); + let mut store = Store::default(); + let module = Module::new(&store, wat::parse_str(wat).unwrap()).unwrap(); + let session = ctx.new_session(&module).unwrap(); + let imports = session.create_imports(&mut store.as_store_mut()).unwrap(); + let instance = Instance::new(&mut store, &module, &imports).unwrap(); + session + .configure_instance( + &mut store.as_store_mut(), + &instance, + Some(instance.exports.get_memory("memory").unwrap()), + ) + .unwrap(); + let status = instance + .exports + .get_typed_function::<(), i32>(&store, "run") + .unwrap() + .call(&mut store) + .unwrap(); + assert_eq!(status, 0); +} diff --git a/tests/external_transfer.rs b/tests/external_transfer.rs new file mode 100644 index 00000000..d6f61c67 --- /dev/null +++ b/tests/external_transfer.rs @@ -0,0 +1,64 @@ +use std::{ + ffi::{c_char, c_void}, + ptr, +}; + +use wasmer_napi::NapiCtx; + +unsafe extern "C" { + fn snapi_bridge_init() -> i32; + fn snapi_bridge_unofficial_configure_runtime(flags: *const c_char, length: u32) -> i32; + fn snapi_bridge_unofficial_create_env( + api_version: i32, + guest_heap: *const c_void, + env_out: *mut *mut c_void, + ) -> i32; + fn snapi_bridge_unofficial_release_env(env: *mut c_void) -> i32; + fn napi_v8_test_fail_next_external_buffer_after_transfer(); + fn snapi_bridge_create_external_buffer_finalized( + env: *mut c_void, + data_addr: u64, + byte_length: u32, + finalize_hint: *mut c_void, + backing_store_token_out: *mut u64, + out_id: *mut u32, + ownership_transferred_out: *mut i32, + ) -> i32; +} + +#[test] +fn bridge_reports_finalizer_ownership_even_when_buffer_creation_fails() { + let _ctx = NapiCtx::default(); + assert_eq!(unsafe { snapi_bridge_init() }, 1); + assert_eq!( + unsafe { snapi_bridge_unofficial_configure_runtime(ptr::null(), 0) }, + 0 + ); + let mut env = ptr::null_mut(); + assert_eq!( + unsafe { snapi_bridge_unofficial_create_env(8, ptr::null(), &mut env) }, + 0 + ); + let mut bytes = [0u8; 8]; + let mut backing_store_token = 0; + let mut out = 0; + let mut transferred = 0; + unsafe { napi_v8_test_fail_next_external_buffer_after_transfer() }; + assert_eq!( + unsafe { + snapi_bridge_create_external_buffer_finalized( + env, + bytes.as_mut_ptr() as u64, + bytes.len() as u32, + ptr::null_mut(), + &mut backing_store_token, + &mut out, + &mut transferred, + ) + }, + 9 + ); + assert_eq!(transferred, 1); + assert_eq!(out, 0); + assert_eq!(unsafe { snapi_bridge_unofficial_release_env(env) }, 0); +} diff --git a/tests/programs/test_env_sleep.c b/tests/programs/test_env_sleep.c new file mode 100644 index 00000000..6eb1548a --- /dev/null +++ b/tests/programs/test_env_sleep.c @@ -0,0 +1,13 @@ +#include +#include + +#include "napi_test_helpers.h" + +int main(void) { + napi_env env = napi_wasm_init_env(); + CHECK_OR_FAIL(env != NULL, "napi_wasm_init_env returned NULL"); + printf("ENV_READY\n"); + fflush(stdout); + sleep(5); + return 0; +} diff --git a/tests/programs/test_gc_once.c b/tests/programs/test_gc_once.c new file mode 100644 index 00000000..529c97fe --- /dev/null +++ b/tests/programs/test_gc_once.c @@ -0,0 +1,12 @@ +#include + +#include "napi_test_helpers.h" +#include "unofficial_napi.h" + +int main(void) { + napi_env env = napi_wasm_init_env(); + CHECK_OR_FAIL(env != NULL, "napi_wasm_init_env returned NULL"); + NAPI_CALL(env, unofficial_napi_collect_garbage(env)); + printf("GC_DONE\n"); + return 0; +} diff --git a/tests/programs/test_guest_fatal_error.c b/tests/programs/test_guest_fatal_error.c new file mode 100644 index 00000000..0b5e991d --- /dev/null +++ b/tests/programs/test_guest_fatal_error.c @@ -0,0 +1,8 @@ +#include "node_api.h" + +int main(void) { + static const char location[] = "guest-module"; + static const char message[] = {'b', 'a', 'd', '\0', 'i', 'n', 'p', 'u', 't'}; + napi_fatal_error(location, sizeof(location) - 1, message, sizeof(message)); + return 0; +} diff --git a/tests/programs/test_js_infinite_loop.cc b/tests/programs/test_js_infinite_loop.c similarity index 96% rename from tests/programs/test_js_infinite_loop.cc rename to tests/programs/test_js_infinite_loop.c index daaff9f1..b67f47fc 100644 --- a/tests/programs/test_js_infinite_loop.cc +++ b/tests/programs/test_js_infinite_loop.c @@ -13,7 +13,7 @@ int main(void) { napi_env env = napi_wasm_init_env(); - CHECK_OR_FAIL(env != nullptr, "napi_wasm_init_env returned NULL"); + CHECK_OR_FAIL(env != NULL, "napi_wasm_init_env returned NULL"); napi_value script; NAPI_CALL(env, napi_create_string_utf8(env, "while (true) {}", diff --git a/tests/v8_host_control.rs b/tests/v8_host_control.rs index f1515441..2c140609 100644 --- a/tests/v8_host_control.rs +++ b/tests/v8_host_control.rs @@ -29,6 +29,59 @@ const NAPI_PENDING_EXCEPTION: i32 = 10; /// ceiling is clamped. const BUDGET_BELOW_ISOLATE_FLOOR: u64 = 20 * 1024 * 1024; +#[test] +fn two_guest_isolates_run_sequentially() { + let wasm = build_wasix_test("hello_napi_test"); + for _ in 0..2 { + let (exit_code, stdout, stderr) = + run_guest(&NapiCtx::default(), &wasm).expect("guest run failed"); + assert_eq!(exit_code, 0, "{stderr}"); + assert!( + stdout.contains("HELLO_NAPI_TEST_OK=1"), + "{stdout}\n{stderr}" + ); + } +} + +#[test] +fn guest_fatal_error_exits_only_its_workload() { + let fatal_wasm = build_wasix_test("test_guest_fatal_error"); + let hello_wasm = build_wasix_test("hello_napi_test"); + + let (exit_code, stdout, stderr) = + run_guest(&NapiCtx::default(), &fatal_wasm).expect("fatal guest run failed"); + assert_ne!( + exit_code, 0, + "fatal guest unexpectedly survived: {stdout}\n{stderr}" + ); + + let (exit_code, stdout, stderr) = + run_guest(&NapiCtx::default(), &hello_wasm).expect("next guest run failed"); + assert_eq!(exit_code, 0, "{stderr}"); + assert!( + stdout.contains("HELLO_NAPI_TEST_OK=1"), + "{stdout}\n{stderr}" + ); +} + +#[test] +fn two_guest_isolates_can_coexist_while_one_sleeps() { + let sleeping_wasm = build_wasix_test("test_env_sleep"); + let hello_wasm = build_wasix_test("hello_napi_test"); + let sleeping = thread::spawn(move || run_guest(&NapiCtx::default(), &sleeping_wasm)); + thread::sleep(Duration::from_secs(1)); + let (exit_code, stdout, stderr) = + run_guest(&NapiCtx::default(), &hello_wasm).expect("second guest failed"); + assert_eq!(exit_code, 0, "{stderr}"); + assert!( + stdout.contains("HELLO_NAPI_TEST_OK=1"), + "{stdout}\n{stderr}" + ); + let (exit_code, stdout, stderr) = sleeping.join().unwrap().unwrap(); + assert_eq!(exit_code, 0, "{stderr}"); + assert!(stdout.contains("ENV_READY"), "{stdout}\n{stderr}"); +} + /// The kill path Edge relies on: a JS loop that never returns on its own has /// to stop when the host says so. Nothing in the guest cooperates here — the /// isolate is executing JS when the request arrives. @@ -97,6 +150,48 @@ fn terminate_all_stops_running_js() { ); } +/// A busy isolate cannot hold the bridge's process-wide registry lock while +/// another workload creates its isolate and requests GC. +#[test] +fn another_guest_can_collect_gc_while_js_is_spinning() { + let spinning_wasm = build_wasix_test("test_js_infinite_loop"); + let gc_wasm = build_wasix_test("test_gc_once"); + let busy_ctx = NapiCtx::default(); + let control = busy_ctx.runtime_control(); + let (busy_tx, busy_rx) = mpsc::channel(); + let busy = thread::spawn(move || { + let result = run_guest(&busy_ctx, &spinning_wasm); + let _ = busy_tx.send(()); + result + }); + + // The guest prints its marker before entering the loop. The captured + // stdout is only returned at completion, so allow startup time here. + thread::sleep(Duration::from_secs(2)); + + let (gc_tx, gc_rx) = mpsc::channel(); + let gc_guest = thread::spawn(move || { + let result = run_guest(&NapiCtx::default(), &gc_wasm); + let _ = gc_tx.send(()); + result + }); + let gc_finished = gc_rx.recv_timeout(Duration::from_secs(15)); + control.terminate_all(); + busy_rx + .recv_timeout(Duration::from_secs(60)) + .expect("spinning guest survived termination"); + + gc_finished.expect("second guest stalled behind busy isolate"); + let (exit_code, stdout, stderr) = gc_guest.join().unwrap().unwrap(); + assert_eq!(exit_code, 0, "{stderr}"); + assert!(stdout.contains("GC_DONE"), "{stdout}\n{stderr}"); + let (_, busy_stdout, busy_stderr) = busy.join().unwrap().unwrap(); + assert!( + busy_stdout.contains("JS_LOOP_ENTERED"), + "busy guest never entered JS: {busy_stdout}\n{busy_stderr}" + ); +} + /// Every byte an isolate reserves has to come back when it goes away, /// otherwise a long-lived app leaks budget until it can't start an isolate at /// all. Charging is by reservation, so this covers creation and teardown of a diff --git a/v8/src/internal/napi_external_transfer_observer.h b/v8/src/internal/napi_external_transfer_observer.h new file mode 100644 index 00000000..3ef413e0 --- /dev/null +++ b/v8/src/internal/napi_external_transfer_observer.h @@ -0,0 +1,18 @@ +#ifndef NAPI_V8_EXTERNAL_TRANSFER_OBSERVER_H_ +#define NAPI_V8_EXTERNAL_TRANSFER_OBSERVER_H_ + +// Stack-only private observer used by the Wasm guest-heap bridge. Nested +// native calls restore their caller's observer without an allocation. +struct napi_v8_external_transfer_observer { + void* finalize_hint = nullptr; + int* transferred = nullptr; + napi_v8_external_transfer_observer* previous = nullptr; +}; + +extern "C" void napi_v8_begin_external_transfer_observation( + napi_v8_external_transfer_observer* observer, void* finalize_hint, + int* transferred); +extern "C" void napi_v8_end_external_transfer_observation( + napi_v8_external_transfer_observer* observer); + +#endif // NAPI_V8_EXTERNAL_TRANSFER_OBSERVER_H_ diff --git a/v8/src/js_native_api_v8.cc b/v8/src/js_native_api_v8.cc index f25e0272..395f301f 100644 --- a/v8/src/js_native_api_v8.cc +++ b/v8/src/js_native_api_v8.cc @@ -1,6 +1,7 @@ #include "internal/napi_v8_env.h" #include "internal/napi_callback_payload.h" #include "internal/napi_env_records.h" +#include "internal/napi_external_transfer_observer.h" #include "internal/napi_escapable_handle_scope_wrapper.h" #include "internal/napi_external_wrapper.h" #include "internal/napi_function_callback_info.h" @@ -221,6 +222,22 @@ std::mutex& ExternalHintsMutex() { return mutex; } +// Private observation for the Wasm guest-heap bridge. The backing-store +// deleter owns the guest allocation as soon as NewBackingStore succeeds, +// including paths where a later Buffer record or value handle cannot be made. +// A nested call restores the outer observer, and the transfer is marked before +// Buffer prototype lookup can run guest JS. +thread_local napi_v8_external_transfer_observer* g_external_transfer_observer = nullptr; +thread_local bool g_test_fail_next_external_buffer_after_transfer = false; + +void MarkExternalBackingStoreTransferred(void* finalize_hint) { + auto* observer = g_external_transfer_observer; + if (observer != nullptr && observer->finalize_hint == finalize_hint && + observer->transferred != nullptr) { + *observer->transferred = 1; + } +} + void ExternalBackingStoreDeleter(void* data, size_t /*length*/, @@ -321,9 +338,8 @@ v8::Local CreateBufferObject(napi_env env, // type check, which fails when this runs inside a native libuv callback (e.g. // a UDP recv) — the call throws and every such buffer would silently degrade // to a plain Uint8Array (breaking msg.toString(), Buffer.isBuffer, etc.). - // Setting the prototype is a pure host-side V8 operation (Buffer and - // Buffer.prototype are ordinary JS objects), so it works regardless of the - // calling context and on the native-V8 lane alike. + // Looking up Buffer and its prototype can run JS getters, so the guest + // adapter installs callback context before entering this function. v8::Local view = v8::Uint8Array::New(ab, offset, length); v8::Local buffer_name = v8::String::NewFromUtf8Literal(env->isolate, "Buffer"); @@ -501,6 +517,23 @@ void SetterTrampoline(v8::Local property, } // namespace +extern "C" void napi_v8_begin_external_transfer_observation( + napi_v8_external_transfer_observer* observer, void* finalize_hint, + int* transferred) { + if (transferred != nullptr) *transferred = 0; + *observer = {finalize_hint, transferred, g_external_transfer_observer}; + g_external_transfer_observer = observer; +} + +extern "C" void napi_v8_end_external_transfer_observation( + napi_v8_external_transfer_observer* observer) { + g_external_transfer_observer = observer->previous; +} + +extern "C" void napi_v8_test_fail_next_external_buffer_after_transfer() { + g_test_fail_next_external_buffer_after_transfer = true; +} + void napi_v8_set_last_exception(napi_env env, v8::Local exception, v8::Local message) { @@ -852,12 +885,28 @@ napi_status NAPI_CDECL napi_create_external_arraybuffer( if (!backing) { return napi_generic_failure; } - v8impl::detail::napi_lifetime__:: - record_create(env, hint.get()); - { + // The backing-store deleter now owns the hint and will run its finalizer + // even if a later step fails to produce a napi_value. + hint.release(); + MarkExternalBackingStoreTransferred(finalize_hint); + bool tracker_recorded = false; + try { + v8impl::detail::napi_lifetime__:: + record_create(env, hint_ptr); + tracker_recorded = true; // Racing V8's ArrayBufferSweeper threads; see ExternalHintsMutex. std::lock_guard lock(ExternalHintsMutex()); - env->external_backing_store_hints.insert(hint.release()); + env->external_backing_store_hints.insert(hint_ptr); + } catch (const std::bad_alloc&) { + { + std::lock_guard lock(ExternalHintsMutex()); + hint_ptr->env = nullptr; + } + if (tracker_recorded) { + v8impl::detail::napi_lifetime__:: + record_release(env, hint_ptr); + } + return napi_generic_failure; } out = v8::ArrayBuffer::New(env->isolate, std::move(backing)); } @@ -1312,10 +1361,10 @@ napi_status NAPI_CDECL napi_get_value_bigint_words(napi_env env, int wc = static_cast(bigint->WordCount()); if (words == nullptr) { if (sign_bit != nullptr) { - int tmp_count = wc; - uint64_t dummy_word = 0; - uint64_t* tmp_words = (tmp_count > 0) ? &dummy_word : nullptr; - bigint->ToWordsArray(&sign, &tmp_count, tmp_words); + // ToWordsArray writes up to the supplied capacity. A count query has + // no output storage, even when the BigInt has many words. + int tmp_count = 0; + bigint->ToWordsArray(&sign, &tmp_count, nullptr); *sign_bit = sign; } *word_count = static_cast(wc); @@ -3035,12 +3084,33 @@ napi_status NAPI_CDECL napi_create_external_buffer(napi_env env, if (!backing) { return napi_generic_failure; } - v8impl::detail::napi_lifetime__:: - record_create(env, hint.get()); - { + hint.release(); + MarkExternalBackingStoreTransferred(finalize_hint); + bool tracker_recorded = false; + try { + v8impl::detail::napi_lifetime__:: + record_create(env, hint_ptr); + tracker_recorded = true; // Racing V8's ArrayBufferSweeper threads; see ExternalHintsMutex. std::lock_guard lock(ExternalHintsMutex()); - env->external_backing_store_hints.insert(hint.release()); + env->external_backing_store_hints.insert(hint_ptr); + } catch (const std::bad_alloc&) { + { + std::lock_guard lock(ExternalHintsMutex()); + hint_ptr->env = nullptr; + } + if (tracker_recorded) { + v8impl::detail::napi_lifetime__:: + record_release(env, hint_ptr); + } + return napi_generic_failure; + } + + // Private deterministic regression hook for failure after ownership moves + // to the backing-store deleter but before a JS Buffer value is returned. + if (g_test_fail_next_external_buffer_after_transfer) { + g_test_fail_next_external_buffer_after_transfer = false; + return napi_generic_failure; } auto* record = env->allocate(env); diff --git a/v8/src/unofficial_napi.cc b/v8/src/unofficial_napi.cc index 7b476389..66c7ed76 100644 --- a/v8/src/unofficial_napi.cc +++ b/v8/src/unofficial_napi.cc @@ -1926,6 +1926,11 @@ napi_status NAPI_CDECL unofficial_napi_create_env( return status != napi_ok ? status : napi_generic_failure; } + // Existing workers can predate the JIT protection key allocated by the + // first isolate. Restore read access before initializing a second isolate's + // builtin dispatch table; restoring permissions at bridge entry is too late. + v8::ThreadIsolatedAllocator::SetDefaultPermissionsForSignalHandler(); + auto allocator = std::make_shared(); if (!allocator) { if (guest_heap != nullptr) napi_host_guest_heap_release(guest_heap);