Skip to content
File

Blob: src/rust/jsg/v8.rs

rust3655 lines
1// Copyright (c) 2026 Cloudflare, Inc.
2// Licensed under the Apache 2.0 license found in the LICENSE file or at:
3// https://opensource.org/licenses/Apache-2.0
4 
5//! V8 JavaScript engine bindings and garbage collector integration.
6//!
7//! This module provides Rust wrappers for V8 types and integration with the C++ `Wrappable`
8//! garbage collection system used by workerd.
9//!
10//! # Core Types
11//!
12//! - [`IsolatePtr`] - Non-null wrapper around `v8::Isolate*`; callers must still
13//! ensure the isolate is alive and the current thread holds the isolate lock
14//! - [`Local<'a, T>`] - Stack-allocated handle to a V8 value, tied to a `HandleScope`
15//! - [`Global<T>`] - Persistent handle that outlives `HandleScope`s
16//! - [`BackingStore`] - Owned handle to the raw memory backing an `ArrayBuffer`;
17//! keeps the memory alive independently of any JS handle
18//!
19//! # Garbage Collection
20//!
21//! Rust resources integrate with V8's GC through the C++ `Wrappable` base class. Each Rust
22//! resource is wrapped in `Rc<R>` with a `Wrappable` on the KJ heap that bridges to cppgc
23//! via a `CppgcShim`. The Wrappable's `data[0..1]` stores a fat pointer to
24//! `dyn GarbageCollected` — the data part is the `Rc::into_raw` pointer (pointing to `R`
25//! inside the `Rc` allocation) and the vtable part carries `R`'s `GarbageCollected` impl.
26//! On destruction, `wrappable_invoke_drop` reconstructs the `Rc` via `Rc::from_raw` and
27//! drops it, which may drop the resource. The Rust `Ref<R>` smart pointer holds its own
28//! `Rc<R>` plus a `WrappableRc` (`KjRc<Wrappable>`) for reference-counted ownership. On
29//! drop, `wrappable_remove_strong_ref()` handles GC cleanup via `maybeDeferDestruction()`,
30//! then the `WrappableRc` drop decrements the `kj::Rc` refcount.
31 
32use std::any::TypeId;
33use std::cell::UnsafeCell;
34use std::fmt::Display;
35use std::marker::PhantomData;
36use std::num::NonZeroUsize;
37use std::pin::Pin;
38use std::ptr::NonNull;
39use std::rc::Rc;
40 
41use crate::Error;
42use crate::FromJS;
43use crate::GarbageCollected;
44use crate::Lock;
45use crate::Number;
46use crate::Resource;
47#[expect(clippy::missing_safety_doc)]
48#[cxx::bridge(namespace = "workerd::rust::jsg")]
49#[expect(
50 clippy::fn_params_excessive_bools,
51 reason = "bool parameters in FFI are dictated by the C++ interface"
52)]
53pub mod ffi {
54 #[derive(Debug)]
55 struct Local {
56 ptr: usize,
57 }
58 
59 /// Mirrors `v8::MaybeLocal<T>`: a single pointer-sized word where `ptr == 0` means empty.
60 /// This matches V8's internal layout exactly — `v8::MaybeLocal<T>` holds one `Local<T>`
61 /// field which is itself one `internal::Address*` (one pointer word).
62 #[derive(Debug)]
63 struct MaybeLocal {
64 ptr: usize,
65 }
66 
67 #[derive(Debug)]
68 struct Global {
69 /// Strong `v8::Global<v8::Value>` handle. Always valid when non-zero.
70 ptr: usize,
71 }
72 
73 #[derive(Debug)]
74 struct TracedReference {
75 /// Weak `v8::TracedReference<v8::Data>` handle used during GC tracing.
76 /// Zero/null when inactive (strong mode).
77 ptr: usize,
78 }
79 
80 #[derive(Debug)]
81 struct GcVisitor {
82 ptr: usize,
83 }
84 
85 #[derive(Debug)]
86 struct Utf8Value {
87 ptr: usize,
88 }
89 
90 /// Mirrors `v8::BackingStoreInitializationMode`. Must be kept in sync with
91 /// the V8-defined enum in `v8-array-buffer.h`.
92 #[derive(Debug, PartialEq, Eq, Copy, Clone)]
93 pub enum BackingStoreInitializationMode {
94 ZeroInitialized = 0,
95 Uninitialized = 1,
96 }
97 
98 #[derive(Debug, PartialEq, Eq, Copy, Clone)]
99 pub enum ExceptionType {
100 OperationError,
101 DataError,
102 DataCloneError,
103 InvalidAccessError,
104 InvalidStateError,
105 InvalidCharacterError,
106 NotSupportedError,
107 SyntaxError,
108 TimeoutError,
109 TypeMismatchError,
110 AbortError,
111 NotFoundError,
112 TypeError,
113 Error,
114 RangeError,
115 ReferenceError,
116 }
117 
118 extern "Rust" {
119 /// Called from C++ Wrappable destructor to drop the Rust object.
120 /// Reconstructs the `Rc<dyn GarbageCollected>` from `data[0..1]` and drops it.
121 unsafe fn wrappable_invoke_drop(wrappable: Pin<&mut Wrappable>);
122 
123 /// Called from C++ Wrappable::jsgVisitForGc to trace nested handles.
124 /// Reconstructs `&dyn GarbageCollected` from `data[0..1]` and calls `trace()`.
125 unsafe fn wrappable_invoke_trace(wrappable: &Wrappable, visitor: *mut GcVisitor);
126 
127 /// Called from C++ `Wrappable::jsgGetMemoryName`.
128 /// Returns the NUL-terminated class name for heap snapshots as a `rust::Str` view
129 /// into a `'static` string literal. The C++ side constructs a `kj::StringPtr`
130 /// directly from `.data()` / `.size()` — no allocation needed.
131 unsafe fn wrappable_invoke_get_name(wrappable: &Wrappable) -> &'static str;
132 }
133 
134 unsafe extern "C++" {
135 include!("workerd/rust/jsg/ffi.h");
136 
137 type Isolate;
138 type FunctionCallbackInfo;
139 type Wrappable;
140 
141 // BackingStore
142 pub unsafe fn backing_store_drop(store: usize);
143 pub unsafe fn backing_store_data(store: usize) -> *mut u8;
144 pub unsafe fn backing_store_byte_length(store: usize) -> usize;
145 pub unsafe fn backing_store_max_byte_length(store: usize) -> usize;
146 pub unsafe fn backing_store_is_shared(store: usize) -> bool;
147 pub unsafe fn backing_store_is_resizable_by_user_javascript(store: usize) -> bool;
148 pub unsafe fn backing_store_new_resizable(
149 byte_length: usize,
150 max_byte_length: usize,
151 ) -> usize;
152 
153 // Local<T>
154 pub unsafe fn local_drop(value: Local);
155 pub unsafe fn local_clone(value: &Local) -> Local;
156 pub unsafe fn local_to_global(isolate: *mut Isolate, value: Local) -> Global;
157 pub unsafe fn local_new_number(isolate: *mut Isolate, value: f64) -> Local;
158 pub unsafe fn local_new_string(isolate: *mut Isolate, value: &str) -> Local;
159 pub unsafe fn local_new_boolean(isolate: *mut Isolate, value: bool) -> Local;
160 pub unsafe fn local_new_object(isolate: *mut Isolate) -> Local;
161 pub unsafe fn local_new_null(isolate: *mut Isolate) -> Local;
162 pub unsafe fn local_new_undefined(isolate: *mut Isolate) -> Local;
163 pub unsafe fn local_new_array(isolate: *mut Isolate, length: usize) -> Local;
164 pub unsafe fn local_new_uint8_array(
165 isolate: *mut Isolate,
166 data: *const u8,
167 length: usize,
168 ) -> Local;
169 pub unsafe fn local_new_uint16_array(
170 isolate: *mut Isolate,
171 data: *const u16,
172 length: usize,
173 ) -> Local;
174 pub unsafe fn local_new_uint32_array(
175 isolate: *mut Isolate,
176 data: *const u32,
177 length: usize,
178 ) -> Local;
179 pub unsafe fn local_new_int8_array(
180 isolate: *mut Isolate,
181 data: *const i8,
182 length: usize,
183 ) -> Local;
184 pub unsafe fn local_new_int16_array(
185 isolate: *mut Isolate,
186 data: *const i16,
187 length: usize,
188 ) -> Local;
189 pub unsafe fn local_new_int32_array(
190 isolate: *mut Isolate,
191 data: *const i32,
192 length: usize,
193 ) -> Local;
194 pub unsafe fn local_new_float32_array(
195 isolate: *mut Isolate,
196 data: *const f32,
197 length: usize,
198 ) -> Local;
199 pub unsafe fn local_new_float64_array(
200 isolate: *mut Isolate,
201 data: *const f64,
202 length: usize,
203 ) -> Local;
204 pub unsafe fn local_new_bigint64_array(
205 isolate: *mut Isolate,
206 data: *const i64,
207 length: usize,
208 ) -> Local;
209 pub unsafe fn local_new_biguint64_array(
210 isolate: *mut Isolate,
211 data: *const u64,
212 length: usize,
213 ) -> Local;
214 pub unsafe fn local_new_array_buffer(
215 isolate: *mut Isolate,
216 data: *const u8,
217 length: usize,
218 ) -> Local;
219 pub unsafe fn local_new_array_buffer_empty(
220 isolate: *mut Isolate,
221 byte_length: usize,
222 ) -> Local;
223 pub unsafe fn array_buffer_new_with_mode(
224 isolate: *mut Isolate,
225 byte_length: usize,
226 mode: BackingStoreInitializationMode,
227 ) -> KjMaybe<Local>;
228 pub unsafe fn array_buffer_from_backing_store(isolate: *mut Isolate, store: usize)
229 -> Local;
230 pub unsafe fn local_array_buffer_byte_length(
231 isolate: *mut Isolate,
232 buffer: &Local,
233 ) -> usize;
234 pub unsafe fn local_array_buffer_data(isolate: *mut Isolate, buffer: &Local) -> *mut u8;
235 pub unsafe fn local_array_buffer_get_backing_store(
236 isolate: *mut Isolate,
237 buffer: &Local,
238 ) -> usize;
239 
240 // Local<ArrayBufferView>
241 pub unsafe fn local_array_buffer_view_byte_offset(
242 isolate: *mut Isolate,
243 view: &Local,
244 ) -> usize;
245 pub unsafe fn local_array_buffer_view_byte_length(
246 isolate: *mut Isolate,
247 view: &Local,
248 ) -> usize;
249 pub unsafe fn local_array_buffer_view_buffer_data(
250 isolate: *mut Isolate,
251 view: &Local,
252 ) -> *mut u8;
253 pub unsafe fn local_array_buffer_view_get_buffer(
254 isolate: *mut Isolate,
255 view: &Local,
256 ) -> Local;
257 pub unsafe fn local_array_buffer_view_element_size(
258 isolate: *mut Isolate,
259 view: &Local,
260 ) -> usize;
261 pub unsafe fn local_array_buffer_view_is_integer_type(
262 isolate: *mut Isolate,
263 view: &Local,
264 ) -> bool;
265 
266 pub unsafe fn local_eq(lhs: &Local, rhs: &Local) -> bool;
267 pub unsafe fn local_has_value(value: &Local) -> bool;
268 pub unsafe fn local_is_string(value: &Local) -> bool;
269 pub unsafe fn local_is_boolean(value: &Local) -> bool;
270 pub unsafe fn local_is_number(value: &Local) -> bool;
271 pub unsafe fn local_is_null(value: &Local) -> bool;
272 pub unsafe fn local_is_undefined(value: &Local) -> bool;
273 pub unsafe fn local_is_null_or_undefined(value: &Local) -> bool;
274 pub unsafe fn local_is_object(value: &Local) -> bool;
275 pub unsafe fn local_is_native_error(value: &Local) -> bool;
276 pub unsafe fn local_is_array(value: &Local) -> bool;
277 pub unsafe fn local_is_uint8_array(value: &Local) -> bool;
278 pub unsafe fn local_is_uint16_array(value: &Local) -> bool;
279 pub unsafe fn local_is_uint32_array(value: &Local) -> bool;
280 pub unsafe fn local_is_int8_array(value: &Local) -> bool;
281 pub unsafe fn local_is_int16_array(value: &Local) -> bool;
282 pub unsafe fn local_is_int32_array(value: &Local) -> bool;
283 pub unsafe fn local_is_float32_array(value: &Local) -> bool;
284 pub unsafe fn local_is_float64_array(value: &Local) -> bool;
285 pub unsafe fn local_is_bigint64_array(value: &Local) -> bool;
286 pub unsafe fn local_is_biguint64_array(value: &Local) -> bool;
287 pub unsafe fn local_is_float16_array(value: &Local) -> bool;
288 pub unsafe fn local_is_uint8clamped_array(value: &Local) -> bool;
289 pub unsafe fn local_is_array_buffer(value: &Local) -> bool;
290 pub unsafe fn local_is_array_buffer_view(value: &Local) -> bool;
291 pub unsafe fn local_is_shared_array_buffer(value: &Local) -> bool;
292 pub unsafe fn local_is_function(value: &Local) -> bool;
293 pub unsafe fn local_is_symbol(value: &Local) -> bool;
294 pub unsafe fn local_is_name(value: &Local) -> bool;
295 pub unsafe fn local_type_of(isolate: *mut Isolate, value: &Local) -> String;
296 
297 // Local<String>
298 pub unsafe fn local_string_length(value: &Local) -> i32;
299 pub unsafe fn local_string_is_one_byte(value: &Local) -> bool;
300 pub unsafe fn local_string_contains_only_one_byte(value: &Local) -> bool;
301 pub unsafe fn local_string_utf8_length(isolate: *mut Isolate, value: &Local) -> usize;
302 pub unsafe fn local_string_write_v2(
303 isolate: *mut Isolate,
304 value: &Local,
305 offset: u32,
306 length: u32,
307 buffer: *mut u16,
308 flags: i32,
309 );
310 pub unsafe fn local_string_write_one_byte_v2(
311 isolate: *mut Isolate,
312 value: &Local,
313 offset: u32,
314 length: u32,
315 buffer: *mut u8,
316 flags: i32,
317 );
318 pub unsafe fn local_string_write_utf8_v2(
319 isolate: *mut Isolate,
320 value: &Local,
321 buffer: *mut u8,
322 capacity: usize,
323 flags: i32,
324 ) -> usize;
325 pub unsafe fn local_string_empty(isolate: *mut Isolate) -> Local;
326 pub unsafe fn local_string_equals(lhs: &Local, rhs: &Local) -> bool;
327 pub unsafe fn local_string_is_flat(value: &Local) -> bool;
328 pub unsafe fn local_string_concat(
329 isolate: *mut Isolate,
330 left: Local,
331 right: Local,
332 ) -> Local;
333 pub unsafe fn local_string_internalize(isolate: *mut Isolate, value: &Local) -> Local;
334 pub unsafe fn local_string_new_from_utf8(
335 isolate: *mut Isolate,
336 data: *const u8,
337 length: i32,
338 internalized: bool,
339 ) -> MaybeLocal;
340 pub unsafe fn local_string_new_from_one_byte(
341 isolate: *mut Isolate,
342 data: *const u8,
343 length: i32,
344 internalized: bool,
345 ) -> MaybeLocal;
346 pub unsafe fn local_string_new_from_two_byte(
347 isolate: *mut Isolate,
348 data: *const u16,
349 length: i32,
350 internalized: bool,
351 ) -> MaybeLocal;
352 pub unsafe fn maybe_local_is_empty(value: &MaybeLocal) -> bool;
353 
354 // Local<Name>
355 pub unsafe fn local_name_get_identity_hash(value: &Local) -> i32;
356 
357 // Local<Symbol>
358 pub unsafe fn local_symbol_new(isolate: *mut Isolate) -> Local;
359 pub unsafe fn local_symbol_new_with_description(
360 isolate: *mut Isolate,
361 description: Local,
362 ) -> Local;
363 pub unsafe fn local_symbol_description(isolate: *mut Isolate, value: &Local) -> MaybeLocal;
364 
365 // Local<Function>
366 pub unsafe fn local_function_call(
367 isolate: *mut Isolate,
368 function: &Local,
369 recv: &Local,
370 args: &[Local],
371 ) -> Local;
372 
373 // Local<Object>
374 pub unsafe fn local_object_set_property(
375 isolate: *mut Isolate,
376 object: &mut Local,
377 key: &str,
378 value: Local,
379 );
380 pub unsafe fn local_object_has_property(
381 isolate: *mut Isolate,
382 object: &Local,
383 key: &str,
384 ) -> bool;
385 pub unsafe fn local_object_get_property(
386 isolate: *mut Isolate,
387 object: &Local,
388 key: &str,
389 ) -> KjMaybe<Local>;
390 
391 // Local<Array>
392 pub unsafe fn local_array_length(isolate: *mut Isolate, array: &Local) -> u32;
393 pub unsafe fn local_array_get(isolate: *mut Isolate, array: &Local, index: u32) -> Local;
394 pub unsafe fn local_array_set(
395 isolate: *mut Isolate,
396 array: &mut Local,
397 index: u32,
398 value: Local,
399 );
400 pub unsafe fn local_array_iterate(isolate: *mut Isolate, value: Local) -> Vec<Global>;
401 
402 // Local<TypedArray>
403 pub unsafe fn local_typed_array_length(isolate: *mut Isolate, array: &Local) -> usize;
404 pub unsafe fn local_typed_array_buffer_data(isolate: *mut Isolate, array: &Local) -> usize;
405 pub unsafe fn local_typed_array_byte_offset(isolate: *mut Isolate, array: &Local) -> usize;
406 pub unsafe fn local_typed_array_byte_length(isolate: *mut Isolate, array: &Local) -> usize;
407 pub unsafe fn local_uint8_array_get(
408 isolate: *mut Isolate,
409 array: &Local,
410 index: usize,
411 ) -> u8;
412 pub unsafe fn local_uint16_array_get(
413 isolate: *mut Isolate,
414 array: &Local,
415 index: usize,
416 ) -> u16;
417 pub unsafe fn local_uint32_array_get(
418 isolate: *mut Isolate,
419 array: &Local,
420 index: usize,
421 ) -> u32;
422 pub unsafe fn local_int8_array_get(
423 isolate: *mut Isolate,
424 array: &Local,
425 index: usize,
426 ) -> i8;
427 pub unsafe fn local_int16_array_get(
428 isolate: *mut Isolate,
429 array: &Local,
430 index: usize,
431 ) -> i16;
432 pub unsafe fn local_int32_array_get(
433 isolate: *mut Isolate,
434 array: &Local,
435 index: usize,
436 ) -> i32;
437 pub unsafe fn local_float32_array_get(
438 isolate: *mut Isolate,
439 array: &Local,
440 index: usize,
441 ) -> f32;
442 pub unsafe fn local_float64_array_get(
443 isolate: *mut Isolate,
444 array: &Local,
445 index: usize,
446 ) -> f64;
447 pub unsafe fn local_bigint64_array_get(
448 isolate: *mut Isolate,
449 array: &Local,
450 index: usize,
451 ) -> i64;
452 pub unsafe fn local_biguint64_array_get(
453 isolate: *mut Isolate,
454 array: &Local,
455 index: usize,
456 ) -> u64;
457 pub unsafe fn local_uint8clamped_array_get(
458 isolate: *mut Isolate,
459 array: &Local,
460 index: usize,
461 ) -> u8;
462 
463 // Global<T>
464 pub unsafe fn global_reset(value: Pin<&mut Global>);
465 pub unsafe fn global_clone(isolate: *mut Isolate, value: &Global) -> Global;
466 pub unsafe fn global_to_local(isolate: *mut Isolate, value: &Global) -> Local;
467 
468 // Wrappable - data access
469 #[expect(
470 clippy::needless_lifetimes,
471 reason = "CXX bridge requires explicit lifetimes on return references"
472 )]
473 pub unsafe fn wrappable_get_trait_object<'a>(
474 wrappable: &'a Wrappable,
475 ) -> &'a TraitObjectPtr;
476 pub unsafe fn wrappable_clear_trait_object(wrappable: Pin<&mut Wrappable>);
477 pub unsafe fn wrappable_strong_refcount(wrappable: &Wrappable) -> u32;
478 
479 // Wrappable lifecycle — KjRc<Wrappable> for reference-counted ownership
480 pub unsafe fn wrappable_new(ptr: TraitObjectPtr) -> KjRc<Wrappable>;
481 
482 pub unsafe fn wrappable_to_rc(wrappable: Pin<&mut Wrappable>) -> KjRc<Wrappable>;
483 pub unsafe fn wrappable_add_strong_ref(wrappable: Pin<&mut Wrappable>);
484 pub unsafe fn wrappable_remove_strong_ref(wrappable: Pin<&mut Wrappable>, is_strong: bool);
485 pub unsafe fn wrappable_visit_ref(
486 wrappable: Pin<&mut Wrappable>,
487 ref_parent: *mut usize,
488 ref_strong: *mut bool,
489 visitor: *mut GcVisitor,
490 );
491 /// Visit a `v8::Global` field during GC tracing, implementing the same
492 /// strong↔traced dual-mode switching that `jsg::Data` / `jsg::V8Ref<T>`
493 /// use in C++.
494 ///
495 /// `global` points to the `ptr` field of `ffi::Global` (the strong handle).
496 /// `traced` points to the `traced_ptr` field (the weak traced handle).
497 /// Both are mutated in-place to reflect the new handle state after the visit.
498 pub unsafe fn wrappable_visit_global(
499 visitor: *mut GcVisitor,
500 global: *mut usize,
501 traced: &mut TracedReference,
502 );
503 /// Resets a `v8::TracedReference`, releasing the weak GC handle.
504 /// Must be called when a `Global<T>` is dropped in traced mode to avoid
505 /// leaking a live `v8::TracedReference`.
506 pub unsafe fn traced_reference_reset(traced: &mut TracedReference);
507 
508 // Unwrappers
509 pub unsafe fn unwrap_string(isolate: *mut Isolate, value: Local) -> String;
510 pub unsafe fn unwrap_boolean(isolate: *mut Isolate, value: Local) -> bool;
511 pub unsafe fn unwrap_number(isolate: *mut Isolate, value: Local) -> f64;
512 pub unsafe fn unwrap_uint8_array(isolate: *mut Isolate, value: Local) -> Vec<u8>;
513 pub unsafe fn unwrap_uint16_array(isolate: *mut Isolate, value: Local) -> Vec<u16>;
514 pub unsafe fn unwrap_uint32_array(isolate: *mut Isolate, value: Local) -> Vec<u32>;
515 pub unsafe fn unwrap_int8_array(isolate: *mut Isolate, value: Local) -> Vec<i8>;
516 pub unsafe fn unwrap_int16_array(isolate: *mut Isolate, value: Local) -> Vec<i16>;
517 pub unsafe fn unwrap_int32_array(isolate: *mut Isolate, value: Local) -> Vec<i32>;
518 pub unsafe fn unwrap_float32_array(isolate: *mut Isolate, value: Local) -> Vec<f32>;
519 pub unsafe fn unwrap_float64_array(isolate: *mut Isolate, value: Local) -> Vec<f64>;
520 pub unsafe fn unwrap_bigint64_array(isolate: *mut Isolate, value: Local) -> Vec<i64>;
521 pub unsafe fn unwrap_biguint64_array(isolate: *mut Isolate, value: Local) -> Vec<u64>;
522 
523 // ArrayBuffer detach/detachable/was-detached
524 pub unsafe fn local_array_buffer_detach(isolate: *mut Isolate, buffer: &mut Local);
525 pub unsafe fn local_array_buffer_was_detached(
526 isolate: *mut Isolate,
527 buffer: &Local,
528 ) -> bool;
529 pub unsafe fn local_array_buffer_is_detachable(
530 isolate: *mut Isolate,
531 buffer: &Local,
532 ) -> bool;
533 
534 // Value-level shared check
535 pub fn local_array_buffer_is_shared(value: &Local) -> bool;
536 
537 // FunctionCallbackInfo
538 pub unsafe fn fci_get_isolate(args: *mut FunctionCallbackInfo) -> *mut Isolate;
539 pub unsafe fn fci_get_this(args: *mut FunctionCallbackInfo) -> Local;
540 pub unsafe fn fci_get_length(args: *mut FunctionCallbackInfo) -> usize;
541 pub unsafe fn fci_get_arg(args: *mut FunctionCallbackInfo, index: usize) -> Local;
542 pub unsafe fn fci_set_return_value(args: *mut FunctionCallbackInfo, value: Local);
543 
544 // Errors
545 pub unsafe fn exception_create(
546 isolate: *mut Isolate,
547 exception_type: ExceptionType,
548 message: &str,
549 ) -> Local;
550 
551 // Isolate
552 pub unsafe fn isolate_throw_exception(isolate: *mut Isolate, exception: Local);
553 pub unsafe fn isolate_throw_error(isolate: *mut Isolate, message: &str);
554 pub unsafe fn isolate_throw_internal_error(isolate: *mut Isolate, internal_message: &str);
555 pub unsafe fn isolate_terminate_execution(isolate: *mut Isolate);
556 pub unsafe fn isolate_is_locked(isolate: *mut Isolate) -> bool;
557 }
558 
559 /// Fat pointer to a `dyn GarbageCollected` trait object plus its TypeId.
560 ///
561 /// Stored inside the C++ `Wrappable` struct. Contains everything needed to
562 /// reconstruct the Rust trait object and verify its type.
563 pub struct TraitObjectPtr {
564 /// `Rc::into_raw(*const R)` — data pointer to the resource.
565 pub data_ptr: usize,
566 /// Vtable pointer for `R as dyn GarbageCollected`.
567 pub vtable_ptr: usize,
568 /// `TypeId::of::<R>()` low 64 bits.
569 pub type_id_lo: usize,
570 /// `TypeId::of::<R>()` high 64 bits.
571 pub type_id_hi: usize,
572 }
573 
574 pub struct ConstructorDescriptor {
575 callback: usize,
576 }
577 
578 pub struct MethodDescriptor {
579 name: String,
580 callback: usize,
581 }
582 
583 pub struct StaticConstantDescriptor {
584 pub name: String,
585 pub value: f64, /* number */
586 }
587 
588 // Canonical definition of `jsg::PropertyKind` (re-exported from `jsg::lib` as
589 // `pub use v8::ffi::PropertyKind`). jsg-macros uses its own compile-time copy
590 // because proc-macro crates cannot link against CXX-bridge runtime crates.
591 enum PropertyKind {
592 /// Accessor on the prototype chain; enumerable.
593 Prototype = 0,
594 /// Own accessor on every instance; enumerable.
595 Instance = 1,
596 /// Registered under a unique symbol on the prototype; invisible to normal
597 /// enumeration and string-key lookup; surfaced by `node:util` `inspect()`.
598 Inspect = 2,
599 }
600 
601 /// Descriptor for a single accessor property. `setter_callback` is `None` for
602 /// read-only properties; ignored entirely for `Inspect`.
603 pub struct PropertyDescriptor {
604 pub name: String,
605 pub kind: PropertyKind,
606 pub getter_callback: usize,
607 /// `None` means read-only (no setter).
608 pub setter_callback: KjMaybe<usize>,
609 }
610 
611 pub struct ResourceDescriptor {
612 pub name: String,
613 pub constructor: KjMaybe<ConstructorDescriptor>,
614 pub methods: Vec<MethodDescriptor>,
615 pub properties: Vec<PropertyDescriptor>,
616 pub static_methods: Vec<MethodDescriptor>,
617 pub static_constants: Vec<StaticConstantDescriptor>,
618 }
619 
620 // Resources
621 unsafe extern "C++" {
622 unsafe fn create_resource_template(
623 isolate: *mut Isolate,
624 descriptor: &ResourceDescriptor,
625 ) -> Global /* v8::Global<FunctionTemplate> */;
626 
627 pub unsafe fn wrap_resource(
628 isolate: *mut Isolate,
629 wrappable: KjRc<Wrappable>,
630 constructor: &Global, /* v8::Global<FunctionTemplate> */
631 ) -> Local /* v8::Local<Value> */;
632 
633 pub unsafe fn wrappable_attach_wrapper(
634 wrappable: KjRc<Wrappable>,
635 args: Pin<&mut FunctionCallbackInfo>,
636 );
637 
638 pub unsafe fn unwrap_resource(
639 isolate: *mut Isolate,
640 value: Local, /* v8::LocalValue */
641 ) -> KjRc<Wrappable>;
642 
643 pub unsafe fn function_template_get_function(
644 isolate: *mut Isolate,
645 constructor: &Global, /* v8::Global<FunctionTemplate> */
646 ) -> Local /* v8::Local<Function> */;
647 
648 pub unsafe fn utf8_value_new(isolate: *mut Isolate, value: Local) -> Utf8Value;
649 pub unsafe fn utf8_value_drop(value: Utf8Value);
650 pub unsafe fn utf8_value_length(value: &Utf8Value) -> usize;
651 pub unsafe fn utf8_value_data(value: &Utf8Value) -> *const u8;
652 }
653 
654 /// Module visibility level, mirroring workerd::jsg::ModuleType from modules.capnp.
655 ///
656 /// CXX shared enums cannot reference existing C++ enums, so we define matching values here.
657 /// The conversion to workerd::jsg::ModuleType happens in jsg.h's RustModuleRegistry.
658 enum ModuleType {
659 Bundle = 0,
660 Builtin = 1,
661 Internal = 2,
662 }
663 
664 unsafe extern "C++" {
665 type ModuleRegistry;
666 
667 pub unsafe fn register_add_builtin_module(
668 registry: Pin<&mut ModuleRegistry>,
669 specifier: &str,
670 callback: unsafe fn(*mut Isolate) -> Local,
671 module_type: ModuleType,
672 );
673 }
674}
675 
676impl std::fmt::Display for ffi::ExceptionType {
677 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
678 let name = match *self {
679 Self::OperationError => "OperationError",
680 Self::DataError => "DataError",
681 Self::DataCloneError => "DataCloneError",
682 Self::InvalidAccessError => "InvalidAccessError",
683 Self::InvalidStateError => "InvalidStateError",
684 Self::InvalidCharacterError => "InvalidCharacterError",
685 Self::NotSupportedError => "NotSupportedError",
686 Self::SyntaxError => "SyntaxError",
687 Self::TimeoutError => "TimeoutError",
688 Self::TypeMismatchError => "TypeMismatchError",
689 Self::AbortError => "AbortError",
690 Self::NotFoundError => "NotFoundError",
691 Self::TypeError => "TypeError",
692 Self::RangeError => "RangeError",
693 Self::ReferenceError => "ReferenceError",
694 _ => "Error",
695 };
696 write!(f, "{name}")
697 }
698}
699 
700// Marker types for Local<T>
701#[derive(Debug)]
702pub struct Value;
703/// Marker for `v8::Name` handles (supertype of `String` and `Symbol`).
704#[derive(Debug)]
705pub struct Name;
706/// Marker for `v8::String` handles.
707#[derive(Debug)]
708pub struct String;
709/// Marker for `v8::Symbol` handles.
710#[derive(Debug)]
711pub struct Symbol;
712 
713impl String {
714 /// Maximum length of a V8 string in UTF-16 code units.
715 ///
716 /// Matches `v8::String::kMaxLength`. Attempting to create a string longer than
717 /// this will cause V8 to return an empty `MaybeLocal`.
718 pub const MAX_LENGTH: i32 = if cfg!(target_pointer_width = "32") {
719 (1 << 28) - 16
720 } else {
721 (1 << 29) - 24
722 };
723 
724 /// Returns the empty string singleton.
725 ///
726 /// Corresponds to `v8::String::Empty()`.
727 pub fn empty<'a>(lock: &mut crate::Lock) -> Local<'a, Self> {
728 let isolate = lock.isolate();
729 // SAFETY: Lock guarantees the isolate is locked and a HandleScope is active.
730 unsafe { Local::from_ffi(isolate, ffi::local_string_empty(isolate.as_ffi())) }
731 }
732 
733 /// Creates a new string from a `&str`.
734 ///
735 /// Corresponds to `v8::String::NewFromUtf8`.
736 pub fn new_from_str<'a>(lock: &mut crate::Lock, data: &str) -> MaybeLocal<'a, Self> {
737 Self::new_from_utf8(lock, data.as_bytes())
738 }
739 
740 /// Creates an internalized string from a `&str`.
741 ///
742 /// Equal strings will be pointer-equal after internalization, which speeds up
743 /// property-key lookups at the cost of a hash-table probe on creation.
744 ///
745 /// Corresponds to `v8::String::NewFromUtf8` with `kInternalized`.
746 pub fn new_internalized_from_str<'a>(
747 lock: &mut crate::Lock,
748 data: &str,
749 ) -> MaybeLocal<'a, Self> {
750 Self::new_internalized_from_utf8(lock, data.as_bytes())
751 }
752 
753 /// Creates a new string from a UTF-8 string literal.
754 ///
755 /// Panics at runtime if `literal.len()` exceeds [`Self::MAX_LENGTH`], matching
756 /// the compile-time `static_assert` that `v8::String::NewFromUtf8Literal` performs.
757 ///
758 /// # Panics
759 ///
760 /// Panics if `literal.len()` exceeds [`Self::MAX_LENGTH`].
761 ///
762 /// Corresponds to `v8::String::NewFromUtf8Literal`.
763 pub fn new_from_utf8_literal<'a>(
764 lock: &mut crate::Lock,
765 literal: &'static str,
766 ) -> MaybeLocal<'a, Self> {
767 assert!(
768 literal.len() <= Self::MAX_LENGTH as usize,
769 "string literal exceeds v8::String::kMaxLength"
770 );
771 Self::new_from_utf8(lock, literal.as_bytes())
772 }
773 
774 /// Creates a new string from UTF-8 data.
775 ///
776 /// Returns an empty `MaybeLocal` if V8 cannot allocate the string.
777 ///
778 /// Strings longer than `i32::MAX` bytes are silently truncated to `i32::MAX` bytes
779 /// to satisfy V8's `int`-typed length parameter; in practice V8 will reject strings
780 /// that large anyway due to heap limits.
781 ///
782 /// Corresponds to `v8::String::NewFromUtf8`.
783 pub fn new_from_utf8<'a>(lock: &mut crate::Lock, data: &[u8]) -> MaybeLocal<'a, Self> {
784 let isolate = lock.isolate();
785 let len = i32::try_from(data.len()).unwrap_or(i32::MAX);
786 // SAFETY: Lock guarantees the isolate is locked and a HandleScope is active;
787 // data.as_ptr() and len describe a valid byte slice.
788 let handle =
789 unsafe { ffi::local_string_new_from_utf8(isolate.as_ffi(), data.as_ptr(), len, false) };
790 // SAFETY: handle is a valid MaybeLocal from V8; ptr==0 means empty.
791 unsafe { MaybeLocal::from_ffi(handle) }
792 }
793 
794 /// Creates an internalized string from UTF-8 data.
795 ///
796 /// Equal strings will be pointer-equal after internalization, which speeds up
797 /// property-key lookups at the cost of a hash-table probe on creation.
798 ///
799 /// Strings longer than `i32::MAX` bytes are silently truncated to `i32::MAX` bytes.
800 ///
801 /// Corresponds to `v8::String::NewFromUtf8` with `kInternalized`.
802 pub fn new_internalized_from_utf8<'a>(
803 lock: &mut crate::Lock,
804 data: &[u8],
805 ) -> MaybeLocal<'a, Self> {
806 let isolate = lock.isolate();
807 let len = i32::try_from(data.len()).unwrap_or(i32::MAX);
808 // SAFETY: Lock guarantees the isolate is locked and a HandleScope is active;
809 // data.as_ptr() and len describe a valid byte slice.
810 let handle =
811 unsafe { ffi::local_string_new_from_utf8(isolate.as_ffi(), data.as_ptr(), len, true) };
812 // SAFETY: handle is a valid MaybeLocal from V8; ptr==0 means empty.
813 unsafe { MaybeLocal::from_ffi(handle) }
814 }
815 
816 /// Creates a new string from Latin-1 (one-byte) data.
817 ///
818 /// Each byte is mapped to the Unicode code point with the same value.
819 /// Returns an empty `MaybeLocal` if V8 cannot allocate the string.
820 ///
821 /// Strings longer than `i32::MAX` bytes are silently truncated to `i32::MAX` bytes.
822 ///
823 /// Corresponds to `v8::String::NewFromOneByte`.
824 pub fn new_from_one_byte<'a>(lock: &mut crate::Lock, data: &[u8]) -> MaybeLocal<'a, Self> {
825 let isolate = lock.isolate();
826 let len = i32::try_from(data.len()).unwrap_or(i32::MAX);
827 // SAFETY: Lock guarantees the isolate is locked; data.as_ptr() and len are valid.
828 let handle = unsafe {
829 ffi::local_string_new_from_one_byte(isolate.as_ffi(), data.as_ptr(), len, false)
830 };
831 // SAFETY: handle is a valid MaybeLocal from V8; ptr==0 means empty.
832 unsafe { MaybeLocal::from_ffi(handle) }
833 }
834 
835 /// Creates an internalized string from Latin-1 (one-byte) data.
836 ///
837 /// Equal strings will be pointer-equal after internalization.
838 /// Strings longer than `i32::MAX` bytes are silently truncated to `i32::MAX` bytes.
839 ///
840 /// Corresponds to `v8::String::NewFromOneByte` with `kInternalized`.
841 pub fn new_internalized_from_one_byte<'a>(
842 lock: &mut crate::Lock,
843 data: &[u8],
844 ) -> MaybeLocal<'a, Self> {
845 let isolate = lock.isolate();
846 let len = i32::try_from(data.len()).unwrap_or(i32::MAX);
847 // SAFETY: Lock guarantees the isolate is locked; data.as_ptr() and len are valid.
848 let handle = unsafe {
849 ffi::local_string_new_from_one_byte(isolate.as_ffi(), data.as_ptr(), len, true)
850 };
851 // SAFETY: handle is a valid MaybeLocal from V8; ptr==0 means empty.
852 unsafe { MaybeLocal::from_ffi(handle) }
853 }
854 
855 /// Creates a new string from UTF-16 data.
856 ///
857 /// Returns an empty `MaybeLocal` if V8 cannot allocate the string.
858 ///
859 /// Strings longer than `i32::MAX` code units are silently truncated to `i32::MAX` code units.
860 ///
861 /// Corresponds to `v8::String::NewFromTwoByte`.
862 pub fn new_from_two_byte<'a>(lock: &mut crate::Lock, data: &[u16]) -> MaybeLocal<'a, Self> {
863 let isolate = lock.isolate();
864 let len = i32::try_from(data.len()).unwrap_or(i32::MAX);
865 // SAFETY: Lock guarantees the isolate is locked; data.as_ptr() and len are valid.
866 let handle = unsafe {
867 ffi::local_string_new_from_two_byte(isolate.as_ffi(), data.as_ptr(), len, false)
868 };
869 // SAFETY: handle is a valid MaybeLocal from V8; ptr==0 means empty.
870 unsafe { MaybeLocal::from_ffi(handle) }
871 }
872 
873 /// Creates an internalized string from UTF-16 data.
874 ///
875 /// Equal strings will be pointer-equal after internalization.
876 /// Strings longer than `i32::MAX` code units are silently truncated to `i32::MAX` code units.
877 ///
878 /// Corresponds to `v8::String::NewFromTwoByte` with `kInternalized`.
879 pub fn new_internalized_from_two_byte<'a>(
880 lock: &mut crate::Lock,
881 data: &[u16],
882 ) -> MaybeLocal<'a, Self> {
883 let isolate = lock.isolate();
884 let len = i32::try_from(data.len()).unwrap_or(i32::MAX);
885 // SAFETY: Lock guarantees the isolate is locked; data.as_ptr() and len are valid.
886 let handle = unsafe {
887 ffi::local_string_new_from_two_byte(isolate.as_ffi(), data.as_ptr(), len, true)
888 };
889 // SAFETY: handle is a valid MaybeLocal from V8; ptr==0 means empty.
890 unsafe { MaybeLocal::from_ffi(handle) }
891 }
892 
893 /// Concatenates two strings.
894 ///
895 /// Corresponds to `v8::String::Concat()`.
896 pub fn concat<'a>(
897 lock: &mut crate::Lock,
898 left: Local<'a, Self>,
899 right: Local<'a, Self>,
900 ) -> Local<'a, Self> {
901 let isolate = lock.isolate();
902 // SAFETY: Lock guarantees the isolate is locked and a HandleScope is active.
903 unsafe {
904 Local::from_ffi(
905 isolate,
906 ffi::local_string_concat(isolate.as_ffi(), left.into_ffi(), right.into_ffi()),
907 )
908 }
909 }
910}
911 
912impl Display for Local<'_, Value> {
913 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
914 // SAFETY: isolate is valid and locked (guaranteed by the Local's invariant).
915 let mut lock = unsafe { Lock::from_isolate_ptr(self.isolate.as_ffi()) };
916 match <std::string::String as FromJS>::from_js(&mut lock, self.clone()) {
917 Ok(value) => write!(f, "{value}"),
918 Err(e) => write!(f, "{e:?}"),
919 }
920 }
921}
922#[derive(Debug)]
923pub struct Object;
924pub struct Function;
925pub struct FunctionTemplate;
926pub struct Array;
927pub struct TypedArray;
928pub struct Uint8Array;
929pub struct Uint16Array;
930pub struct Uint32Array;
931pub struct Int8Array;
932pub struct Int16Array;
933pub struct Int32Array;
934pub struct Float32Array;
935pub struct Float64Array;
936pub struct BigInt64Array;
937pub struct BigUint64Array;
938pub struct Uint8ClampedArray;
939pub struct ArrayBuffer;
940pub struct ArrayBufferView;
941 
942// =============================================================================
943// `BackingStore` — owned handle to a heap-allocated `std::shared_ptr<v8::BackingStore>`
944// =============================================================================
945 
946/// Owned handle to a `v8::BackingStore`.
947///
948/// A `BackingStore` is the raw memory region that backs an `ArrayBuffer` or
949/// `SharedArrayBuffer`. Internally this type holds a raw pointer to a
950/// heap-allocated `std::shared_ptr<v8::BackingStore>`, keeping the underlying
951/// memory alive even after the original JS buffer handle goes out of scope.
952///
953/// Obtained via [`Local<ArrayBuffer>::backing_store()`].
954pub struct BackingStore {
955 /// Non-zero `usize` encoding the address of a heap-allocated
956 /// `std::shared_ptr<v8::BackingStore>`. Using `usize` matches the CXX FFI
957 /// convention of representing opaque pointers as `size_t`.
958 /// Freed by `ffi::backing_store_drop` on `Drop`.
959 ptr: NonZeroUsize,
960}
961 
962impl Drop for BackingStore {
963 fn drop(&mut self) {
964 // SAFETY: ptr was obtained from a `local_*_get_backing_store` FFI call
965 // and is uniquely owned by self.
966 unsafe { ffi::backing_store_drop(self.ptr.get()) }
967 }
968}
969 
970impl BackingStore {
971 /// Creates a new resizable `BackingStore` with an initial `byte_length` and a
972 /// maximum capacity of `max_byte_length`.
973 ///
974 /// The resulting `BackingStore` can be passed to [`ArrayBuffer::from_backing_store`]
975 /// to create a resizable `ArrayBuffer`.
976 pub fn new_resizable(byte_length: usize, max_byte_length: usize) -> Self {
977 // SAFETY: V8 guarantees a non-null result or crashes on OOM.
978 let ptr = unsafe { ffi::backing_store_new_resizable(byte_length, max_byte_length) };
979 // SAFETY: ptr is a freshly allocated shared_ptr, uniquely owned.
980 unsafe { Self::from_raw(ptr) }
981 }
982 
983 /// Wraps a `usize` address returned by a `local_*_get_backing_store` FFI call.
984 ///
985 /// # Safety
986 /// `ptr` must be a non-zero value returned by one of the
987 /// `local_*_get_backing_store` FFI functions, and the caller must transfer
988 /// unique ownership to this `BackingStore`.
989 unsafe fn from_raw(ptr: usize) -> Self {
990 Self {
991 ptr: NonZeroUsize::new(ptr).expect("backing_store pointer must be non-null"),
992 }
993 }
994 
995 /// Returns a raw pointer to the backing store's data.
996 ///
997 /// Valid for the lifetime of this `BackingStore`.
998 /// May be null for zero-byte buffers.
999 #[inline]
1000 pub fn data(&self) -> *mut u8 {
1001 // SAFETY: ptr is a valid heap-allocated shared_ptr owned by self.
1002 unsafe { ffi::backing_store_data(self.ptr.get()) }
1003 }
1004 
1005 /// Returns the current byte length of the backing store.
1006 #[inline]
1007 pub fn byte_length(&self) -> usize {
1008 // SAFETY: ptr is valid and owned by self.
1009 unsafe { ffi::backing_store_byte_length(self.ptr.get()) }
1010 }
1011 
1012 /// Returns the maximum byte length.
1013 ///
1014 /// For resizable `ArrayBuffer`s this is `>= byte_length()`. For fixed-size
1015 /// buffers it equals `byte_length()`.
1016 #[inline]
1017 pub fn max_byte_length(&self) -> usize {
1018 // SAFETY: ptr is valid and owned by self.
1019 unsafe { ffi::backing_store_max_byte_length(self.ptr.get()) }
1020 }
1021 
1022 /// Returns `true` if this backing store was created for a `SharedArrayBuffer`.
1023 #[inline]
1024 pub fn is_shared(&self) -> bool {
1025 // SAFETY: ptr is valid and owned by self.
1026 unsafe { ffi::backing_store_is_shared(self.ptr.get()) }
1027 }
1028 
1029 /// Returns `true` if user JavaScript code may resize this buffer.
1030 #[inline]
1031 pub fn is_resizable_by_user_javascript(&self) -> bool {
1032 // SAFETY: ptr is valid and owned by self.
1033 unsafe { ffi::backing_store_is_resizable_by_user_javascript(self.ptr.get()) }
1034 }
1035 
1036 /// Returns `true` if the backing store has zero bytes.
1037 #[inline]
1038 pub fn is_empty(&self) -> bool {
1039 self.byte_length() == 0
1040 }
1041 
1042 /// Returns a shared byte slice into the backing store data.
1043 ///
1044 /// Requires `&mut Lock` so that no JavaScript can execute while the slice
1045 /// is live — JS could otherwise modify the buffer through a `TypedArray`
1046 /// view, violating the immutability of `&[u8]`.
1047 ///
1048 /// # Safety
1049 /// For shared backing stores (`is_shared() == true`), other agents may
1050 /// concurrently write to this memory. The caller must ensure no concurrent
1051 /// writes occur for the duration of the borrow.
1052 ///
1053 /// TODO(soon): When there is a safe aliasing model for Rust mutable access,
1054 /// integrate this method with that somehow.
1055 #[inline]
1056 pub unsafe fn as_slice(&self, _lock: &mut crate::Lock) -> &[u8] {
1057 if self.is_empty() {
1058 return &[];
1059 }
1060 // SAFETY: data() is non-null for non-empty stores; byte_length() bytes are valid.
1061 unsafe { std::slice::from_raw_parts(self.data().cast_const(), self.byte_length()) }
1062 }
1063 
1064 /// Returns a mutable byte slice into the backing store data.
1065 ///
1066 /// Requires `&mut Lock` so that no JavaScript can execute while the mutable
1067 /// slice is live — the borrow on the lock prevents calling `eval`, invoking
1068 /// functions, or any other operation that could modify or detach the buffer.
1069 ///
1070 /// # Safety
1071 /// The caller must ensure no other live reference (shared or mutable) to
1072 /// this memory region exists for the duration of the borrow.
1073 ///
1074 /// TODO(soon): Define a safe aliasing model for Rust mutable access based on
1075 /// interrogating the backing store shared_ptr being singular for the lifetime
1076 /// of the current Rust execution model.
1077 #[doc(hidden)]
1078 #[inline]
1079 pub unsafe fn as_mut_slice(&mut self, _lock: &mut crate::Lock) -> &mut [u8] {
1080 if self.is_empty() {
1081 return &mut [];
1082 }
1083 // SAFETY: data() is non-null for non-empty stores; byte_length() bytes are valid.
1084 unsafe { std::slice::from_raw_parts_mut(self.data(), self.byte_length()) }
1085 }
1086}
1087 
1088// Generic Local<'a, T> handle with lifetime
1089#[derive(Debug)]
1090pub struct Local<'a, T> {
1091 handle: ffi::Local,
1092 isolate: IsolatePtr,
1093 _marker: PhantomData<(&'a (), T)>,
1094}
1095 
1096impl<T> Drop for Local<'_, T> {
1097 fn drop(&mut self) {
1098 if self.handle.ptr == 0 {
1099 return;
1100 }
1101 let handle = std::mem::replace(&mut self.handle, ffi::Local { ptr: 0 });
1102 // SAFETY: handle is valid within the current HandleScope.
1103 unsafe { ffi::local_drop(handle) };
1104 }
1105}
1106 
1107// Common implementations for all Local<'a, T>
1108impl<'a, T> Local<'a, T> {
1109 /// Creates a `Local` from an FFI handle.
1110 ///
1111 /// # Safety
1112 /// The caller must ensure that `isolate` is valid and that `handle` points to a V8 value
1113 /// that is still alive within the current `HandleScope`.
1114 pub unsafe fn from_ffi(isolate: IsolatePtr, handle: ffi::Local) -> Self {
1115 Local {
1116 handle,
1117 isolate,
1118 _marker: PhantomData,
1119 }
1120 }
1121 
1122 /// Consumes this `Local` and returns the underlying FFI handle.
1123 ///
1124 /// # Safety
1125 /// The returned FFI handle must not outlive the `HandleScope` that owns the V8 value.
1126 pub unsafe fn into_ffi(mut self) -> ffi::Local {
1127 let handle = ffi::Local {
1128 ptr: self.handle.ptr,
1129 };
1130 self.handle.ptr = 0;
1131 handle
1132 }
1133 
1134 /// Returns a reference to the underlying FFI handle.
1135 ///
1136 /// # Safety
1137 /// The caller must ensure the returned reference is not used after this `Local` is dropped.
1138 pub unsafe fn as_ffi(&self) -> &ffi::Local {
1139 &self.handle
1140 }
1141 
1142 /// Returns a mutable reference to the underlying FFI handle.
1143 ///
1144 /// # Safety
1145 /// The caller must ensure the returned reference is not used after this `Local` is dropped.
1146 pub unsafe fn as_ffi_mut(&mut self) -> &mut ffi::Local {
1147 &mut self.handle
1148 }
1149 
1150 pub fn null(lock: &mut crate::Lock) -> Local<'a, Value> {
1151 // SAFETY: isolate is valid and locked (guaranteed by Lock).
1152 unsafe { Local::from_ffi(lock.isolate(), ffi::local_new_null(lock.isolate().as_ffi())) }
1153 }
1154 
1155 pub fn undefined(lock: &mut crate::Lock) -> Local<'a, Value> {
1156 // SAFETY: isolate is valid and locked (guaranteed by Lock).
1157 unsafe {
1158 Local::from_ffi(
1159 lock.isolate(),
1160 ffi::local_new_undefined(lock.isolate().as_ffi()),
1161 )
1162 }
1163 }
1164 
1165 pub fn has_value(&self) -> bool {
1166 // SAFETY: handle is valid within the current HandleScope.
1167 unsafe { ffi::local_has_value(&self.handle) }
1168 }
1169 
1170 pub fn is_string(&self) -> bool {
1171 // SAFETY: handle is valid within the current HandleScope.
1172 unsafe { ffi::local_is_string(&self.handle) }
1173 }
1174 
1175 pub fn is_boolean(&self) -> bool {
1176 // SAFETY: handle is valid within the current HandleScope.
1177 unsafe { ffi::local_is_boolean(&self.handle) }
1178 }
1179 
1180 pub fn is_number(&self) -> bool {
1181 // SAFETY: handle is valid within the current HandleScope.
1182 unsafe { ffi::local_is_number(&self.handle) }
1183 }
1184 
1185 pub fn is_null(&self) -> bool {
1186 // SAFETY: handle is valid within the current HandleScope.
1187 unsafe { ffi::local_is_null(&self.handle) }
1188 }
1189 
1190 pub fn is_undefined(&self) -> bool {
1191 // SAFETY: handle is valid within the current HandleScope.
1192 unsafe { ffi::local_is_undefined(&self.handle) }
1193 }
1194 
1195 pub fn is_null_or_undefined(&self) -> bool {
1196 // SAFETY: handle is valid within the current HandleScope.
1197 unsafe { ffi::local_is_null_or_undefined(&self.handle) }
1198 }
1199 
1200 /// Returns true if the value is a JavaScript object.
1201 ///
1202 /// Note: Unlike JavaScript's `typeof` operator which returns "object" for `null`,
1203 /// this method returns `false` for `null` values. Use `is_null_or_undefined()`
1204 /// to check for nullish values separately.
1205 pub fn is_object(&self) -> bool {
1206 // SAFETY: handle is valid within the current HandleScope.
1207 unsafe { ffi::local_is_object(&self.handle) }
1208 }
1209 
1210 /// Returns true if the value is a native JavaScript error.
1211 pub fn is_native_error(&self) -> bool {
1212 // SAFETY: handle is valid within the current HandleScope.
1213 unsafe { ffi::local_is_native_error(&self.handle) }
1214 }
1215 
1216 /// Returns true if the value is a JavaScript array.
1217 pub fn is_array(&self) -> bool {
1218 // SAFETY: handle is valid within the current HandleScope.
1219 unsafe { ffi::local_is_array(&self.handle) }
1220 }
1221 
1222 /// Returns true if the value is a `Uint8Array`.
1223 pub fn is_uint8_array(&self) -> bool {
1224 // SAFETY: handle is valid within the current HandleScope.
1225 unsafe { ffi::local_is_uint8_array(&self.handle) }
1226 }
1227 
1228 /// Returns true if the value is a `Uint16Array`.
1229 pub fn is_uint16_array(&self) -> bool {
1230 // SAFETY: handle is valid within the current HandleScope.
1231 unsafe { ffi::local_is_uint16_array(&self.handle) }
1232 }
1233 
1234 /// Returns true if the value is a `Uint32Array`.
1235 pub fn is_uint32_array(&self) -> bool {
1236 // SAFETY: handle is valid within the current HandleScope.
1237 unsafe { ffi::local_is_uint32_array(&self.handle) }
1238 }
1239 
1240 /// Returns true if the value is an `Int8Array`.
1241 pub fn is_int8_array(&self) -> bool {
1242 // SAFETY: handle is valid within the current HandleScope.
1243 unsafe { ffi::local_is_int8_array(&self.handle) }
1244 }
1245 
1246 /// Returns true if the value is an `Int16Array`.
1247 pub fn is_int16_array(&self) -> bool {
1248 // SAFETY: handle is valid within the current HandleScope.
1249 unsafe { ffi::local_is_int16_array(&self.handle) }
1250 }
1251 
1252 /// Returns true if the value is an `Int32Array`.
1253 pub fn is_int32_array(&self) -> bool {
1254 // SAFETY: handle is valid within the current HandleScope.
1255 unsafe { ffi::local_is_int32_array(&self.handle) }
1256 }
1257 
1258 /// Returns true if the value is a `Float32Array`.
1259 pub fn is_float32_array(&self) -> bool {
1260 // SAFETY: handle is valid within the current HandleScope.
1261 unsafe { ffi::local_is_float32_array(&self.handle) }
1262 }
1263 
1264 /// Returns true if the value is a `Float64Array`.
1265 pub fn is_float64_array(&self) -> bool {
1266 // SAFETY: handle is valid within the current HandleScope.
1267 unsafe { ffi::local_is_float64_array(&self.handle) }
1268 }
1269 
1270 /// Returns true if the value is a `BigInt64Array`.
1271 pub fn is_bigint64_array(&self) -> bool {
1272 // SAFETY: handle is valid within the current HandleScope.
1273 unsafe { ffi::local_is_bigint64_array(&self.handle) }
1274 }
1275 
1276 /// Returns true if the value is a `BigUint64Array`.
1277 pub fn is_biguint64_array(&self) -> bool {
1278 // SAFETY: handle is valid within the current HandleScope.
1279 unsafe { ffi::local_is_biguint64_array(&self.handle) }
1280 }
1281 
1282 /// Returns true if the value is a `Float16Array`.
1283 pub fn is_float16_array(&self) -> bool {
1284 // SAFETY: handle is valid within the current HandleScope.
1285 unsafe { ffi::local_is_float16_array(&self.handle) }
1286 }
1287 
1288 /// Returns true if the value is a `Uint8ClampedArray`.
1289 pub fn is_uint8clamped_array(&self) -> bool {
1290 // SAFETY: handle is valid within the current HandleScope.
1291 unsafe { ffi::local_is_uint8clamped_array(&self.handle) }
1292 }
1293 
1294 /// Returns true if the value is an `ArrayBuffer`.
1295 pub fn is_array_buffer(&self) -> bool {
1296 // SAFETY: handle is valid within the current HandleScope.
1297 unsafe { ffi::local_is_array_buffer(&self.handle) }
1298 }
1299 
1300 /// Returns true if the value is an `ArrayBufferView`
1301 /// (i.e. any `TypedArray` or `DataView`).
1302 pub fn is_array_buffer_view(&self) -> bool {
1303 // SAFETY: handle is valid within the current HandleScope.
1304 unsafe { ffi::local_is_array_buffer_view(&self.handle) }
1305 }
1306 
1307 /// Returns true if the value is a `SharedArrayBuffer`.
1308 pub fn is_shared_array_buffer(&self) -> bool {
1309 // SAFETY: handle is valid within the current HandleScope.
1310 unsafe { ffi::local_is_shared_array_buffer(&self.handle) }
1311 }
1312 
1313 /// Returns true if the value is a JavaScript function.
1314 pub fn is_function(&self) -> bool {
1315 // SAFETY: handle is valid within the current HandleScope.
1316 unsafe { ffi::local_is_function(&self.handle) }
1317 }
1318 
1319 /// Returns true if the value is a JavaScript Symbol.
1320 ///
1321 /// Corresponds to `v8::Value::IsSymbol()`.
1322 pub fn is_symbol(&self) -> bool {
1323 // SAFETY: handle is valid within the current HandleScope.
1324 unsafe { ffi::local_is_symbol(&self.handle) }
1325 }
1326 
1327 /// Returns true if the value is a `v8::Name` (either a `String` or a `Symbol`).
1328 ///
1329 /// Corresponds to `v8::Value::IsName()`.
1330 pub fn is_name(&self) -> bool {
1331 // SAFETY: handle is valid within the current HandleScope.
1332 unsafe { ffi::local_is_name(&self.handle) }
1333 }
1334 
1335 /// Returns the JavaScript type of the underlying value as a string.
1336 ///
1337 /// Uses V8's native `TypeOf` method which returns the same result as
1338 /// JavaScript's `typeof` operator: "undefined", "boolean", "number",
1339 /// "bigint", "string", "symbol", "function", or "object".
1340 ///
1341 /// Note: For `null`, this returns "object" (JavaScript's historical behavior).
1342 pub fn type_of(&self) -> std::string::String {
1343 // SAFETY: handle is valid within the current HandleScope.
1344 unsafe { ffi::local_type_of(self.isolate.as_ffi(), &self.handle) }
1345 }
1346}
1347 
1348impl<T> Clone for Local<'_, T> {
1349 fn clone(&self) -> Self {
1350 // SAFETY: handle is valid within the current HandleScope.
1351 unsafe { Self::from_ffi(self.isolate, ffi::local_clone(&self.handle)) }
1352 }
1353}
1354 
1355/// Trait for safe checked casts between V8 `Local` handle types.
1356///
1357/// Use via the turbofish method on `Local<Value>`:
1358/// ```ignore
1359/// let func: Local<Function> = value.try_as::<Function>().unwrap();
1360/// ```
1361pub trait As<T>: Sized {
1362 type Output;
1363 
1364 /// Attempts to cast this handle to the target type.
1365 /// Returns `None` if the underlying value is not of the target type.
1366 fn try_as(self) -> Option<Self::Output>;
1367}
1368 
1369/// Implements `As<$target>` for `Local<Value>` using the given type-check method.
1370macro_rules! impl_as {
1371 ($target:ident, $check:ident) => {
1372 impl<'a> As<$target> for Local<'a, Value> {
1373 type Output = Local<'a, $target>;
1374 
1375 fn try_as(self) -> Option<Local<'a, $target>> {
1376 if self.$check() {
1377 // SAFETY: We verified the type above.
1378 Some(unsafe { Local::from_ffi(self.isolate, self.into_ffi()) })
1379 } else {
1380 None
1381 }
1382 }
1383 }
1384 };
1385}
1386 
1387impl_as!(Function, is_function);
1388impl_as!(Object, is_object);
1389impl_as!(Array, is_array);
1390impl_as!(String, is_string);
1391impl_as!(Symbol, is_symbol);
1392impl_as!(Name, is_name);
1393impl_as!(ArrayBuffer, is_array_buffer);
1394impl_as!(ArrayBufferView, is_array_buffer_view);
1395 
1396// Value-specific implementations
1397impl<'a> Local<'a, Value> {
1398 pub fn to_global(self, lock: &'a mut Lock) -> Global<Value> {
1399 // SAFETY: isolate is valid and locked (guaranteed by Lock); handle is valid.
1400 unsafe { ffi::local_to_global(lock.isolate().as_ffi(), self.into_ffi()).into() }
1401 }
1402 
1403 /// Attempts a checked cast to a more specific `Local<T>` type.
1404 ///
1405 /// Returns `None` if the value is not of the target type.
1406 pub fn try_as<T>(self) -> Option<<Self as As<T>>::Output>
1407 where
1408 Self: As<T>,
1409 {
1410 As::<T>::try_as(self)
1411 }
1412}
1413 
1414impl PartialEq for Local<'_, Value> {
1415 fn eq(&self, other: &Self) -> bool {
1416 // SAFETY: both handles are valid within the current HandleScope.
1417 unsafe { ffi::local_eq(&self.handle, &other.handle) }
1418 }
1419}
1420 
1421impl PartialEq for Local<'_, String> {
1422 fn eq(&self, other: &Self) -> bool {
1423 // SAFETY: Both handles are valid V8 String handles.
1424 unsafe { ffi::local_string_equals(&self.handle, &other.handle) }
1425 }
1426}
1427 
1428impl Eq for Local<'_, String> {}
1429 
1430impl Local<'_, Function> {
1431 /// Calls this function and converts the result via [`FromJS`].
1432 ///
1433 /// `receiver` is the `this` value; pass `None` for `undefined`. Any `Local<T>`
1434 /// that converts to `Local<Value>` (via `Into`) can be used directly.
1435 ///
1436 /// `args` is a slice of `Local<Value>` — use [`ToJS::to_js`] to convert Rust
1437 /// values, or `.into()` for other `Local<T>` handles.
1438 ///
1439 /// # Example
1440 ///
1441 /// ```ignore
1442 /// let result: Number = func.call(lock, None::<Local<Value>>, &[x.to_js(lock), y.to_js(lock)])?;
1443 /// ```
1444 pub fn call<'b, R: FromJS, Recv: Into<Local<'b, Value>>>(
1445 &self,
1446 lock: &mut Lock,
1447 receiver: Option<Recv>,
1448 args: &[Local<'_, Value>],
1449 ) -> Result<R::ResultType, Error> {
1450 let recv = receiver.map_or_else(|| Local::<Value>::undefined(lock), Into::into);
1451 
1452 // Build a contiguous array of ffi::Local handles for the FFI call.
1453 let ffi_args: Vec<ffi::Local> = args
1454 .iter()
1455 // SAFETY: each arg handle is valid within the current HandleScope.
1456 .map(|a| unsafe { ffi::local_clone(a.as_ffi()) })
1457 .collect();
1458 
1459 // SAFETY: lock guarantees the isolate is locked and a HandleScope is active.
1460 // self.handle is a valid Local<Function>. recv and ffi_args are valid Local handles.
1461 let result = unsafe {
1462 Local::from_ffi(
1463 lock.isolate(),
1464 ffi::local_function_call(
1465 lock.isolate().as_ffi(),
1466 &self.handle,
1467 recv.as_ffi(),
1468 &ffi_args,
1469 ),
1470 )
1471 };
1472 R::from_js(lock, result)
1473 }
1474}
1475 
1476/// Implements bidirectional `From` conversions between `Local<$from>` and `Local<$to>`.
1477///
1478/// All V8 handle subtypes share the same pointer representation, so these casts
1479/// are just reinterpretations of the handle. The `assert!` verifies the type
1480/// invariant at runtime — redundant for upcasts (always true) but guards
1481/// downcasts against misuse.
1482macro_rules! impl_local_cast {
1483 ($from:ident -> $to:ident, $check:ident) => {
1484 impl<'a> From<Local<'a, $from>> for Local<'a, $to> {
1485 fn from(value: Local<'a, $from>) -> Self {
1486 assert!(value.$check());
1487 // SAFETY: V8 subtypes share handle representation; assert verifies the invariant.
1488 unsafe { Self::from_ffi(value.isolate, value.into_ffi()) }
1489 }
1490 }
1491 impl<'a> From<Local<'a, $to>> for Local<'a, $from> {
1492 fn from(value: Local<'a, $to>) -> Self {
1493 assert!(value.$check());
1494 // SAFETY: V8 subtypes share handle representation; assert verifies the invariant.
1495 unsafe { Self::from_ffi(value.isolate, value.into_ffi()) }
1496 }
1497 }
1498 };
1499}
1500 
1501// Upcasts to Value
1502impl_local_cast!(String -> Value, is_string);
1503impl_local_cast!(Name -> Value, is_name);
1504impl_local_cast!(Symbol -> Value, is_symbol);
1505impl_local_cast!(Object -> Value, is_object);
1506impl_local_cast!(Function -> Value, is_function);
1507impl_local_cast!(Array -> Value, is_array);
1508 
1509// String and Symbol are both subtypes of Name
1510impl_local_cast!(String -> Name, is_string);
1511impl_local_cast!(Symbol -> Name, is_symbol);
1512impl_local_cast!(Uint8Array -> Value, is_uint8_array);
1513impl_local_cast!(Uint16Array -> Value, is_uint16_array);
1514impl_local_cast!(Uint32Array -> Value, is_uint32_array);
1515impl_local_cast!(Int8Array -> Value, is_int8_array);
1516impl_local_cast!(Int16Array -> Value, is_int16_array);
1517impl_local_cast!(Int32Array -> Value, is_int32_array);
1518impl_local_cast!(Float32Array -> Value, is_float32_array);
1519impl_local_cast!(Float64Array -> Value, is_float64_array);
1520impl_local_cast!(BigInt64Array -> Value, is_bigint64_array);
1521impl_local_cast!(BigUint64Array -> Value, is_biguint64_array);
1522impl_local_cast!(Uint8ClampedArray -> Value, is_uint8clamped_array);
1523 
1524// TypedArray base type to Value. Uses `is_array_buffer_view` which also matches
1525// `DataView`, but this is acceptable because `TypedArray` is only constructed from
1526// concrete typed array types (Uint8Array, etc.) that are always ArrayBufferViews.
1527impl_local_cast!(TypedArray -> Value, is_array_buffer_view);
1528 
1529// Concrete typed arrays to TypedArray base
1530impl_local_cast!(Uint8Array -> TypedArray, is_uint8_array);
1531impl_local_cast!(Uint16Array -> TypedArray, is_uint16_array);
1532impl_local_cast!(Uint32Array -> TypedArray, is_uint32_array);
1533impl_local_cast!(Int8Array -> TypedArray, is_int8_array);
1534impl_local_cast!(Int16Array -> TypedArray, is_int16_array);
1535impl_local_cast!(Int32Array -> TypedArray, is_int32_array);
1536impl_local_cast!(Float32Array -> TypedArray, is_float32_array);
1537impl_local_cast!(Float64Array -> TypedArray, is_float64_array);
1538impl_local_cast!(BigInt64Array -> TypedArray, is_bigint64_array);
1539impl_local_cast!(BigUint64Array -> TypedArray, is_biguint64_array);
1540impl_local_cast!(Uint8ClampedArray -> TypedArray, is_uint8clamped_array);
1541 
1542// Upcasts to Object (Function, Array, TypedArray are all Object subtypes in V8)
1543impl_local_cast!(Function -> Object, is_function);
1544impl_local_cast!(Array -> Object, is_array);
1545impl_local_cast!(TypedArray -> Object, is_array_buffer_view);
1546 
1547// ArrayBuffer <-> Value, ArrayBuffer <-> Object
1548impl_local_cast!(ArrayBuffer -> Value, is_array_buffer);
1549impl_local_cast!(ArrayBuffer -> Object, is_array_buffer);
1550 
1551// ArrayBufferView <-> Value, ArrayBufferView <-> Object
1552// All concrete TypedArray types and DataView are ArrayBufferViews.
1553impl_local_cast!(ArrayBufferView -> Value, is_array_buffer_view);
1554impl_local_cast!(ArrayBufferView -> Object, is_array_buffer_view);
1555 
1556// Concrete typed arrays <-> ArrayBufferView base
1557impl_local_cast!(Uint8Array -> ArrayBufferView, is_uint8_array);
1558impl_local_cast!(Uint16Array -> ArrayBufferView, is_uint16_array);
1559impl_local_cast!(Uint32Array -> ArrayBufferView, is_uint32_array);
1560impl_local_cast!(Int8Array -> ArrayBufferView, is_int8_array);
1561impl_local_cast!(Int16Array -> ArrayBufferView, is_int16_array);
1562impl_local_cast!(Int32Array -> ArrayBufferView, is_int32_array);
1563impl_local_cast!(Float32Array -> ArrayBufferView, is_float32_array);
1564impl_local_cast!(Float64Array -> ArrayBufferView, is_float64_array);
1565impl_local_cast!(BigInt64Array -> ArrayBufferView, is_bigint64_array);
1566impl_local_cast!(BigUint64Array -> ArrayBufferView, is_biguint64_array);
1567 
1568impl Array {
1569 /// Creates a new JavaScript array with the given length.
1570 pub fn new<'a>(lock: &mut crate::Lock, len: usize) -> Local<'a, Self> {
1571 let isolate = lock.isolate();
1572 // SAFETY: isolate is valid and locked (guaranteed by Lock).
1573 unsafe { Local::from_ffi(isolate, ffi::local_new_array(isolate.as_ffi(), len)) }
1574 }
1575}
1576 
1577impl Local<'_, Array> {
1578 /// Returns the length of the array.
1579 #[inline]
1580 pub fn len(&self) -> usize {
1581 // SAFETY: handle is valid within the current HandleScope.
1582 unsafe { ffi::local_array_length(self.isolate.as_ffi(), &self.handle) as usize }
1583 }
1584 
1585 /// Returns true if the array is empty.
1586 #[inline]
1587 pub fn is_empty(&self) -> bool {
1588 self.len() == 0
1589 }
1590 
1591 /// Sets an element at the given index.
1592 pub fn set(&mut self, index: usize, value: Local<'_, Value>) {
1593 // SAFETY: handle is valid within the current HandleScope.
1594 unsafe {
1595 ffi::local_array_set(
1596 self.isolate.as_ffi(),
1597 &mut self.handle,
1598 index as u32,
1599 value.into_ffi(),
1600 );
1601 }
1602 }
1603 
1604 /// Iterates over array elements using V8's native `Array::Iterate()`.
1605 /// Returns Global handles because Local handles get reused during iteration.
1606 pub fn iterate(self) -> Vec<Global<Value>> {
1607 // SAFETY: handle is valid within the current HandleScope.
1608 unsafe { ffi::local_array_iterate(self.isolate.as_ffi(), self.into_ffi()) }
1609 .into_iter()
1610 // SAFETY: each Global handle was created by the C++ side and is valid.
1611 .map(|g| unsafe { Global::from_ffi(g) })
1612 .collect()
1613 }
1614}
1615 
1616// =============================================================================
1617// `ArrayBuffer`-specific implementations
1618// =============================================================================
1619 
1620impl ArrayBuffer {
1621 /// Creates a new `ArrayBuffer` by copying `data`.
1622 pub fn new<'a>(lock: &mut crate::Lock, data: &[u8]) -> Local<'a, Self> {
1623 let isolate = lock.isolate();
1624 // SAFETY: Lock guarantees the isolate is locked and a HandleScope is active.
1625 unsafe {
1626 Local::from_ffi(
1627 isolate,
1628 ffi::local_new_array_buffer(isolate.as_ffi(), data.as_ptr(), data.len()),
1629 )
1630 }
1631 }
1632 
1633 /// Attempts to create a new `ArrayBuffer` with `byte_length` bytes using
1634 /// the given initialization mode (zeroed or uninitialized).
1635 ///
1636 /// Returns `None` if allocation fails.
1637 pub fn new_with_mode<'a>(
1638 lock: &mut crate::Lock,
1639 byte_length: usize,
1640 mode: ffi::BackingStoreInitializationMode,
1641 ) -> Option<Local<'a, Self>> {
1642 let isolate = lock.isolate();
1643 // SAFETY: Lock guarantees the isolate is locked and a HandleScope is active.
1644 let opt: Option<ffi::Local> =
1645 unsafe { ffi::array_buffer_new_with_mode(isolate.as_ffi(), byte_length, mode) }.into();
1646 // SAFETY: isolate is valid and locked; local is a valid handle from V8.
1647 opt.map(|local| unsafe { Local::from_ffi(isolate, local) })
1648 }
1649 
1650 /// Wraps an existing `BackingStore` in a new `ArrayBuffer`.
1651 ///
1652 /// The `ArrayBuffer` shares ownership of the backing store's memory via the
1653 /// underlying `shared_ptr` reference count.
1654 // The BackingStore is taken by value to express ownership transfer; the
1655 // C++ ArrayBuffer::New copies the shared_ptr so both sides hold a reference.
1656 #[expect(
1657 clippy::needless_pass_by_value,
1658 reason = "ownership transfer semantics"
1659 )]
1660 pub fn from_backing_store<'a>(lock: &mut crate::Lock, store: BackingStore) -> Local<'a, Self> {
1661 let isolate = lock.isolate();
1662 // SAFETY: Lock guarantees the isolate is locked and a HandleScope is active.
1663 // store.ptr is a valid heap-allocated shared_ptr; we pass it by raw pointer
1664 // so the C++ side can copy the shared_ptr (incrementing refcount).
1665 unsafe {
1666 Local::from_ffi(
1667 isolate,
1668 ffi::array_buffer_from_backing_store(isolate.as_ffi(), store.ptr.get()),
1669 )
1670 }
1671 }
1672 
1673 /// Creates a new zero-initialized `ArrayBuffer` with the given byte length.
1674 pub fn new_zeroed<'a>(lock: &mut crate::Lock, byte_length: usize) -> Local<'a, Self> {
1675 let isolate = lock.isolate();
1676 // SAFETY: Lock guarantees the isolate is locked and a HandleScope is active.
1677 unsafe {
1678 Local::from_ffi(
1679 isolate,
1680 ffi::local_new_array_buffer_empty(isolate.as_ffi(), byte_length),
1681 )
1682 }
1683 }
1684}
1685 
1686impl Local<'_, ArrayBuffer> {
1687 /// Returns the byte length of this `ArrayBuffer`.
1688 #[inline]
1689 pub fn byte_length(&self) -> usize {
1690 // SAFETY: handle is valid within the current HandleScope.
1691 unsafe { ffi::local_array_buffer_byte_length(self.isolate.as_ffi(), &self.handle) }
1692 }
1693 
1694 /// Returns `true` if this `ArrayBuffer` has zero bytes.
1695 #[inline]
1696 pub fn is_empty(&self) -> bool {
1697 self.byte_length() == 0
1698 }
1699 
1700 /// Returns a raw pointer to the backing store data.
1701 ///
1702 /// # Safety
1703 /// The pointer is valid only while the backing store is alive (i.e. while this
1704 /// `Local` handle — or another handle to the same buffer — remains in scope).
1705 #[inline]
1706 pub unsafe fn data(&self) -> *mut u8 {
1707 // SAFETY: handle is valid within the current HandleScope.
1708 unsafe { ffi::local_array_buffer_data(self.isolate.as_ffi(), &self.handle) }
1709 }
1710 
1711 /// Returns a shared byte slice view into the `ArrayBuffer`'s backing store.
1712 ///
1713 /// Zero-copy: the slice points directly into V8-managed memory.
1714 ///
1715 /// TODO(soon): When there is a safe aliasing model for Rust mutable access,
1716 /// integrate this method with that somehow.
1717 #[inline]
1718 pub fn as_slice(&self) -> &[u8] {
1719 if self.is_empty() {
1720 return &[];
1721 }
1722 // SAFETY: data() is non-null for non-empty buffers; byte_length() bytes are valid.
1723 unsafe { std::slice::from_raw_parts(self.data().cast_const(), self.byte_length()) }
1724 }
1725 
1726 /// Returns a mutable byte slice view into the `ArrayBuffer`'s backing store.
1727 ///
1728 /// Zero-copy: points directly into V8-managed memory.
1729 ///
1730 /// Requires `&mut Lock` so that no JavaScript can execute while the mutable
1731 /// slice is live — the borrow on the lock prevents calling `eval`, invoking
1732 /// functions, or any other operation that could modify or detach the buffer.
1733 ///
1734 /// # Safety
1735 ///
1736 /// The caller must ensure that no other live reference (shared or mutable)
1737 /// into the same `ArrayBuffer` region exists for the duration of the returned
1738 /// slice. `&mut self` prevents aliasing through *this* `Local` handle, but
1739 /// two distinct `Local` handles may back the same buffer — the caller is
1740 /// responsible for ensuring exclusivity.
1741 ///
1742 /// TODO(soon): Define a safe aliasing model for Rust mutable access based on
1743 /// interrogating the backing store shared_ptr being singular for the lifetime
1744 /// of the current Rust execution model.
1745 #[doc(hidden)]
1746 #[inline]
1747 pub unsafe fn as_mut_slice(&mut self, _lock: &mut crate::Lock) -> &mut [u8] {
1748 if self.is_empty() {
1749 return &mut [];
1750 }
1751 // SAFETY: caller guarantees exclusive access; data() is non-null for
1752 // non-empty buffers; byte_length() bytes are valid.
1753 unsafe { std::slice::from_raw_parts_mut(self.data(), self.byte_length()) }
1754 }
1755 
1756 /// Copies the `ArrayBuffer` contents into a new `Vec<u8>`.
1757 pub fn to_vec(&self) -> Vec<u8> {
1758 self.as_slice().to_vec()
1759 }
1760 
1761 /// Detaches this `ArrayBuffer`, setting its byte length to zero.
1762 ///
1763 /// After detaching, the buffer's data is no longer accessible from JS.
1764 /// Typed array views backed by this buffer also become zero-length.
1765 pub fn detach(&mut self) {
1766 // SAFETY: handle is valid within the current HandleScope.
1767 unsafe { ffi::local_array_buffer_detach(self.isolate.as_ffi(), &mut self.handle) }
1768 }
1769 
1770 /// Returns `true` if this `ArrayBuffer` has been detached.
1771 pub fn was_detached(&self) -> bool {
1772 // SAFETY: handle is valid within the current HandleScope.
1773 unsafe { ffi::local_array_buffer_was_detached(self.isolate.as_ffi(), &self.handle) }
1774 }
1775 
1776 /// Returns `true` if this `ArrayBuffer` can be detached.
1777 pub fn is_detachable(&self) -> bool {
1778 // SAFETY: handle is valid within the current HandleScope.
1779 unsafe { ffi::local_array_buffer_is_detachable(self.isolate.as_ffi(), &self.handle) }
1780 }
1781 
1782 /// Returns `true` if the underlying V8 value is a `SharedArrayBuffer`.
1783 ///
1784 /// This performs a value-level check, allowing code that receives a
1785 /// `Local<ArrayBuffer>` via cast to distinguish shared buffers.
1786 pub fn is_shared(&self) -> bool {
1787 ffi::local_array_buffer_is_shared(&self.handle)
1788 }
1789 
1790 /// Returns the `BackingStore` for this `ArrayBuffer`.
1791 ///
1792 /// The returned `BackingStore` keeps the underlying memory alive independently
1793 /// of this `Local` handle.
1794 pub fn backing_store(&self) -> BackingStore {
1795 // SAFETY: handle is valid within the current HandleScope; the returned pointer
1796 // is a freshly heap-allocated shared_ptr that we uniquely own.
1797 let ptr = unsafe {
1798 ffi::local_array_buffer_get_backing_store(self.isolate.as_ffi(), &self.handle)
1799 };
1800 // SAFETY: the FFI guarantees a non-null pointer on success.
1801 unsafe { BackingStore::from_raw(ptr) }
1802 }
1803}
1804 
1805// =============================================================================
1806// `ArrayBufferView`-specific implementations
1807// =============================================================================
1808 
1809impl<'a> Local<'a, ArrayBufferView> {
1810 /// Returns the byte offset of this view within its backing `ArrayBuffer`.
1811 #[inline]
1812 pub fn byte_offset(&self) -> usize {
1813 // SAFETY: handle is valid within the current HandleScope.
1814 unsafe { ffi::local_array_buffer_view_byte_offset(self.isolate.as_ffi(), &self.handle) }
1815 }
1816 
1817 /// Returns the byte length of this view.
1818 #[inline]
1819 pub fn byte_length(&self) -> usize {
1820 // SAFETY: handle is valid within the current HandleScope.
1821 unsafe { ffi::local_array_buffer_view_byte_length(self.isolate.as_ffi(), &self.handle) }
1822 }
1823 
1824 /// Returns `true` if this view covers zero bytes.
1825 #[inline]
1826 pub fn is_empty(&self) -> bool {
1827 self.byte_length() == 0
1828 }
1829 
1830 /// Returns the size in bytes of a single element for typed arrays.
1831 ///
1832 /// Returns `0` for `DataView` (which has no fixed element size).
1833 /// For example: `1` for `Uint8Array`, `4` for `Float32Array`, `8` for
1834 /// `Float64Array`.
1835 #[inline]
1836 pub fn element_size(&self) -> usize {
1837 // SAFETY: handle is valid within the current HandleScope.
1838 unsafe { ffi::local_array_buffer_view_element_size(self.isolate.as_ffi(), &self.handle) }
1839 }
1840 
1841 /// Returns `true` if the element type is an integer type.
1842 ///
1843 /// Returns `false` for `Float32Array`, `Float64Array`, and `DataView`.
1844 #[inline]
1845 pub fn is_integer_type(&self) -> bool {
1846 // SAFETY: handle is valid within the current HandleScope.
1847 unsafe { ffi::local_array_buffer_view_is_integer_type(self.isolate.as_ffi(), &self.handle) }
1848 }
1849 
1850 /// Returns a raw pointer to the backing `ArrayBuffer`'s data (without byte offset).
1851 ///
1852 /// Add `byte_offset()` to reach the first byte of this view's region.
1853 ///
1854 /// # Safety
1855 /// The pointer is valid only while the backing `ArrayBuffer` is alive.
1856 #[inline]
1857 pub unsafe fn buffer_data(&self) -> *mut u8 {
1858 // SAFETY: handle is valid within the current HandleScope.
1859 unsafe { ffi::local_array_buffer_view_buffer_data(self.isolate.as_ffi(), &self.handle) }
1860 }
1861 
1862 /// Returns the backing `ArrayBuffer` of this view.
1863 pub fn buffer(&self) -> Local<'a, ArrayBuffer> {
1864 // SAFETY: handle is valid within the current HandleScope.
1865 unsafe {
1866 Local::from_ffi(
1867 self.isolate,
1868 ffi::local_array_buffer_view_get_buffer(self.isolate.as_ffi(), &self.handle),
1869 )
1870 }
1871 }
1872 
1873 /// Returns a shared byte slice of the view's visible region.
1874 ///
1875 /// Zero-copy: points directly into V8-managed memory.
1876 ///
1877 /// TODO(soon): When there is a safe aliasing model for Rust mutable access,
1878 /// integrate this method with that somehow.
1879 #[inline]
1880 pub fn as_slice(&self) -> &[u8] {
1881 if self.is_empty() {
1882 return &[];
1883 }
1884 // SAFETY: buffer_data() + byte_offset() gives the start of the view's region;
1885 // byte_length() bytes are valid for the lifetime of this Local.
1886 unsafe {
1887 let ptr = self.buffer_data().byte_add(self.byte_offset());
1888 std::slice::from_raw_parts(ptr.cast_const(), self.byte_length())
1889 }
1890 }
1891 
1892 /// Returns a mutable byte slice of the view's visible region.
1893 ///
1894 /// Zero-copy: points directly into V8-managed memory.
1895 ///
1896 /// Requires `&mut Lock` so that no JavaScript can execute while the mutable
1897 /// slice is live — the borrow on the lock prevents calling `eval`, invoking
1898 /// functions, or any other operation that could modify or detach the buffer.
1899 ///
1900 /// # Safety
1901 ///
1902 /// The caller must ensure that no other live reference (shared or mutable)
1903 /// into the same buffer region exists for the duration of the returned
1904 /// slice. `&mut self` prevents aliasing through *this* handle, but two
1905 /// distinct handles backed by the same buffer could alias — the caller is
1906 /// responsible for ensuring exclusivity.
1907 ///
1908 /// TODO(soon): Define a safe aliasing model for Rust mutable access based on
1909 /// interrogating the backing store shared_ptr being singular for the lifetime
1910 /// of the current Rust execution model.
1911 #[doc(hidden)]
1912 #[inline]
1913 pub unsafe fn as_mut_slice(&mut self, _lock: &mut crate::Lock) -> &mut [u8] {
1914 if self.is_empty() {
1915 return &mut [];
1916 }
1917 // SAFETY: caller guarantees exclusive access; same pointer derivation
1918 // as as_slice; &mut self prevents aliasing through this handle.
1919 unsafe {
1920 let ptr = self.buffer_data().byte_add(self.byte_offset());
1921 std::slice::from_raw_parts_mut(ptr, self.byte_length())
1922 }
1923 }
1924 
1925 /// Copies the view's visible byte range into a new `Vec<u8>`.
1926 pub fn to_vec(&self) -> Vec<u8> {
1927 self.as_slice().to_vec()
1928 }
1929}
1930 
1931// `TypedArray`-specific implementations
1932impl Local<'_, TypedArray> {
1933 /// Returns the number of elements in this `TypedArray`.
1934 pub fn len(&self) -> usize {
1935 // SAFETY: handle is valid within the current HandleScope.
1936 unsafe { ffi::local_typed_array_length(self.isolate.as_ffi(), &self.handle) }
1937 }
1938 
1939 /// Returns true if the `TypedArray` is empty.
1940 pub fn is_empty(&self) -> bool {
1941 self.len() == 0
1942 }
1943}
1944 
1945// =============================================================================
1946// `TypedArray` Iterator Types
1947// =============================================================================
1948 
1949/// Iterator over `TypedArray` elements by reference.
1950///
1951/// Created by calling `iter()` on a `Local<'a, TypedArray>`.
1952/// Does not consume the array, allowing multiple iterations.
1953pub struct TypedArrayIter<'a, 'b, T, E> {
1954 array: &'b Local<'a, T>,
1955 index: usize,
1956 len: usize,
1957 _marker: PhantomData<E>,
1958}
1959 
1960/// Owning iterator over `TypedArray` elements.
1961///
1962/// Created by calling `into_iter()` on a `Local<'a, TypedArray>`.
1963/// Consumes the array handle.
1964pub struct TypedArrayIntoIter<'a, T, E> {
1965 array: Local<'a, T>,
1966 index: usize,
1967 len: usize,
1968 _marker: PhantomData<E>,
1969}
1970 
1971// =============================================================================
1972// `TypedArray` Implementation Macro
1973// =============================================================================
1974 
1975/// Implements methods and traits for a specific `TypedArray` type.
1976///
1977/// For each `TypedArray` marker type (e.g., `Uint8Array`), this macro generates:
1978/// - `len()`, `is_empty()`, `get(index)` methods
1979/// - `iter()` method for borrowing iteration
1980/// - `IntoIterator` for owned and borrowed iteration
1981/// - `Iterator` implementations for both iterator types
1982macro_rules! impl_typed_array {
1983 ($marker:ident, $elem:ty, $get_fn:ident) => {
1984 impl<'a> Local<'a, $marker> {
1985 /// Returns the number of elements in this `TypedArray`.
1986 #[inline]
1987 pub fn len(&self) -> usize {
1988 // SAFETY: handle is valid within the current HandleScope.
1989 unsafe { ffi::local_typed_array_length(self.isolate.as_ffi(), &self.handle) }
1990 }
1991 
1992 /// Returns `true` if the `TypedArray` contains no elements.
1993 #[inline]
1994 pub fn is_empty(&self) -> bool {
1995 self.len() == 0
1996 }
1997 
1998 /// Returns the element at `index`.
1999 ///
2000 /// # Panics
2001 ///
2002 /// Panics if `index >= self.len()`.
2003 #[inline]
2004 pub fn get(&self, index: usize) -> $elem {
2005 debug_assert!(index < self.len(), "index out of bounds");
2006 // SAFETY: handle is valid within the current HandleScope.
2007 unsafe { ffi::$get_fn(self.isolate.as_ffi(), &self.handle, index) }
2008 }
2009 
2010 /// Returns the byte offset of this view within its backing `ArrayBuffer`.
2011 #[inline]
2012 pub fn byte_offset(&self) -> usize {
2013 // SAFETY: handle is valid within the current HandleScope.
2014 unsafe { ffi::local_typed_array_byte_offset(self.isolate.as_ffi(), &self.handle) }
2015 }
2016 
2017 /// Returns the byte length of this view.
2018 #[inline]
2019 pub fn byte_length(&self) -> usize {
2020 // SAFETY: handle is valid within the current HandleScope.
2021 unsafe { ffi::local_typed_array_byte_length(self.isolate.as_ffi(), &self.handle) }
2022 }
2023 
2024 /// Returns the size in bytes of a single element.
2025 ///
2026 /// For example, `1` for `Uint8Array`, `4` for `Uint32Array` and
2027 /// `Float32Array`, `8` for `Float64Array`.
2028 #[inline]
2029 pub fn element_size(&self) -> usize {
2030 std::mem::size_of::<$elem>()
2031 }
2032 
2033 /// Returns `true` if the element type is an integer type.
2034 ///
2035 /// Returns `false` for `Float32Array` and `Float64Array`.
2036 #[inline]
2037 pub fn is_integer_type(&self) -> bool {
2038 let id = TypeId::of::<$elem>();
2039 id != TypeId::of::<f32>() && id != TypeId::of::<f64>()
2040 }
2041 
2042 /// Returns a raw pointer to the backing `ArrayBuffer`'s data.
2043 ///
2044 /// This does **not** account for `byte_offset()` — the caller must
2045 /// add it manually when accessing this view's region of the buffer.
2046 ///
2047 /// # Safety
2048 /// The pointer is valid only while the backing `ArrayBuffer` is alive
2049 /// and the `Local` handle is in scope.
2050 #[inline]
2051 pub unsafe fn data(&self) -> *mut $elem {
2052 // SAFETY: handle is valid within the current HandleScope.
2053 unsafe {
2054 ffi::local_typed_array_buffer_data(self.isolate.as_ffi(), &self.handle)
2055 as *mut $elem
2056 }
2057 }
2058 
2059 /// Returns a shared slice view of the typed array's data.
2060 ///
2061 /// Zero-copy: points directly into the V8 `ArrayBuffer`'s backing store.
2062 /// The slice is valid for the lifetime of this `Local` handle.
2063 ///
2064 /// TODO(soon): When there is a safe aliasing model for Rust mutable access,
2065 /// integrate this method with that somehow.
2066 #[inline]
2067 pub fn as_slice(&self) -> &[$elem] {
2068 if self.is_empty() {
2069 return &[];
2070 }
2071 // SAFETY: handle is valid; non-empty guarantees non-null data pointer.
2072 // data() returns the ArrayBuffer base; byte_offset() gives this view's start.
2073 unsafe {
2074 let ptr = self.data().byte_add(self.byte_offset());
2075 std::slice::from_raw_parts(ptr.cast_const(), self.len())
2076 }
2077 }
2078 
2079 /// Returns a mutable slice view of the typed array's data.
2080 ///
2081 /// Zero-copy: points directly into the V8 `ArrayBuffer`'s backing store.
2082 /// The slice is valid for the lifetime of this `Local` handle.
2083 ///
2084 /// Requires `&mut Lock` so that no JavaScript can execute while the
2085 /// mutable slice is live — the borrow on the lock prevents calling
2086 /// `eval`, invoking functions, or any other operation that could modify
2087 /// or detach the underlying buffer.
2088 ///
2089 /// # Safety
2090 ///
2091 /// The caller must ensure that no other live reference (shared or mutable)
2092 /// into the same `ArrayBuffer` region exists for the duration of the returned
2093 /// slice. `&mut self` prevents aliasing through *this* `Local` handle, but
2094 /// two distinct `Local` handles may back the same buffer — the caller is
2095 /// responsible for ensuring exclusivity.
2096 ///
2097 /// TODO(soon): Define a safe aliasing model for Rust mutable access based on
2098 /// interrogating the backing store shared_ptr being singular for the lifetime
2099 /// of the current Rust execution model.
2100 #[doc(hidden)]
2101 #[inline]
2102 pub unsafe fn as_mut_slice(&mut self, _lock: &mut crate::Lock) -> &mut [$elem] {
2103 if self.is_empty() {
2104 return &mut [];
2105 }
2106 // SAFETY: caller guarantees exclusive access to this buffer region;
2107 // handle is valid and non-empty guarantees a non-null data pointer.
2108 unsafe {
2109 let ptr = self.data().byte_add(self.byte_offset());
2110 std::slice::from_raw_parts_mut(ptr, self.len())
2111 }
2112 }
2113 
2114 /// Returns an iterator over the elements.
2115 ///
2116 /// The iterator yields elements by value (copied from V8 memory).
2117 #[inline]
2118 pub fn iter(&self) -> TypedArrayIter<'a, '_, $marker, $elem> {
2119 TypedArrayIter {
2120 array: self,
2121 index: 0,
2122 len: self.len(),
2123 _marker: PhantomData,
2124 }
2125 }
2126 }
2127 
2128 // Owned iteration: `for x in array`
2129 impl<'a> IntoIterator for Local<'a, $marker> {
2130 type Item = $elem;
2131 type IntoIter = TypedArrayIntoIter<'a, $marker, $elem>;
2132 
2133 #[inline]
2134 fn into_iter(self) -> Self::IntoIter {
2135 let len = self.len();
2136 TypedArrayIntoIter {
2137 array: self,
2138 index: 0,
2139 len,
2140 _marker: PhantomData,
2141 }
2142 }
2143 }
2144 
2145 // Borrowed iteration: `for x in &array`
2146 impl<'a, 'b> IntoIterator for &'b Local<'a, $marker> {
2147 type Item = $elem;
2148 type IntoIter = TypedArrayIter<'a, 'b, $marker, $elem>;
2149 
2150 #[inline]
2151 fn into_iter(self) -> Self::IntoIter {
2152 self.iter()
2153 }
2154 }
2155 
2156 impl<'a, 'b> Iterator for TypedArrayIter<'a, 'b, $marker, $elem> {
2157 type Item = $elem;
2158 
2159 #[inline]
2160 fn next(&mut self) -> Option<Self::Item> {
2161 if self.index < self.len {
2162 // SAFETY: handle is valid within the current HandleScope; index < len.
2163 let value = unsafe {
2164 ffi::$get_fn(self.array.isolate.as_ffi(), &self.array.handle, self.index)
2165 };
2166 self.index += 1;
2167 Some(value)
2168 } else {
2169 None
2170 }
2171 }
2172 
2173 #[inline]
2174 fn size_hint(&self) -> (usize, Option<usize>) {
2175 let remaining = self.len - self.index;
2176 (remaining, Some(remaining))
2177 }
2178 }
2179 
2180 impl<'a, 'b> ExactSizeIterator for TypedArrayIter<'a, 'b, $marker, $elem> {}
2181 
2182 impl<'a, 'b> DoubleEndedIterator for TypedArrayIter<'a, 'b, $marker, $elem> {
2183 #[inline]
2184 fn next_back(&mut self) -> Option<Self::Item> {
2185 if self.index < self.len {
2186 self.len -= 1;
2187 // SAFETY: handle is valid within the current HandleScope; len is in bounds.
2188 let value = unsafe {
2189 ffi::$get_fn(self.array.isolate.as_ffi(), &self.array.handle, self.len)
2190 };
2191 Some(value)
2192 } else {
2193 None
2194 }
2195 }
2196 }
2197 
2198 impl<'a, 'b> std::iter::FusedIterator for TypedArrayIter<'a, 'b, $marker, $elem> {}
2199 
2200 impl<'a> Iterator for TypedArrayIntoIter<'a, $marker, $elem> {
2201 type Item = $elem;
2202 
2203 #[inline]
2204 fn next(&mut self) -> Option<Self::Item> {
2205 if self.index < self.len {
2206 // SAFETY: handle is valid within the current HandleScope; index < len.
2207 let value = unsafe {
2208 ffi::$get_fn(self.array.isolate.as_ffi(), &self.array.handle, self.index)
2209 };
2210 self.index += 1;
2211 Some(value)
2212 } else {
2213 None
2214 }
2215 }
2216 
2217 #[inline]
2218 fn size_hint(&self) -> (usize, Option<usize>) {
2219 let remaining = self.len - self.index;
2220 (remaining, Some(remaining))
2221 }
2222 }
2223 
2224 impl<'a> ExactSizeIterator for TypedArrayIntoIter<'a, $marker, $elem> {}
2225 
2226 impl<'a> DoubleEndedIterator for TypedArrayIntoIter<'a, $marker, $elem> {
2227 #[inline]
2228 fn next_back(&mut self) -> Option<Self::Item> {
2229 if self.index < self.len {
2230 self.len -= 1;
2231 // SAFETY: handle is valid within the current HandleScope; len is in bounds.
2232 let value = unsafe {
2233 ffi::$get_fn(self.array.isolate.as_ffi(), &self.array.handle, self.len)
2234 };
2235 Some(value)
2236 } else {
2237 None
2238 }
2239 }
2240 }
2241 
2242 impl<'a> std::iter::FusedIterator for TypedArrayIntoIter<'a, $marker, $elem> {}
2243 };
2244}
2245 
2246/// Generates `FromJS` for `Local<'_, T>` typed array types.
2247macro_rules! impl_typed_array_from_js {
2248 ($marker:ident, $check:ident, $name:expr) => {
2249 impl crate::FromJS for Local<'_, $marker> {
2250 type ResultType = Self;
2251 
2252 fn from_js(_lock: &mut crate::Lock, value: Local<Value>) -> Result<Self, crate::Error> {
2253 if !value.$check() {
2254 return Err(crate::Error::new_type_error(format!(
2255 "expected {}, got {}",
2256 $name,
2257 value.type_of()
2258 )));
2259 }
2260 // SAFETY: type check passed; V8 handles share the same pointer
2261 // representation across subtypes within the same HandleScope.
2262 Ok(unsafe { Self::from_ffi(value.isolate, value.into_ffi()) })
2263 }
2264 }
2265 };
2266}
2267 
2268impl_typed_array_from_js!(Uint8Array, is_uint8_array, "Uint8Array");
2269impl_typed_array_from_js!(Uint16Array, is_uint16_array, "Uint16Array");
2270impl_typed_array_from_js!(Uint32Array, is_uint32_array, "Uint32Array");
2271impl_typed_array_from_js!(Int8Array, is_int8_array, "Int8Array");
2272impl_typed_array_from_js!(Int16Array, is_int16_array, "Int16Array");
2273impl_typed_array_from_js!(Int32Array, is_int32_array, "Int32Array");
2274impl_typed_array_from_js!(Float32Array, is_float32_array, "Float32Array");
2275impl_typed_array_from_js!(Float64Array, is_float64_array, "Float64Array");
2276impl_typed_array_from_js!(BigInt64Array, is_bigint64_array, "BigInt64Array");
2277impl_typed_array_from_js!(BigUint64Array, is_biguint64_array, "BigUint64Array");
2278impl_typed_array_from_js!(
2279 Uint8ClampedArray,
2280 is_uint8clamped_array,
2281 "Uint8ClampedArray"
2282);
2283impl_typed_array_from_js!(ArrayBuffer, is_array_buffer, "ArrayBuffer");
2284impl_typed_array_from_js!(ArrayBufferView, is_array_buffer_view, "ArrayBufferView");
2285 
2286impl_typed_array!(Uint8Array, u8, local_uint8_array_get);
2287impl_typed_array!(Uint16Array, u16, local_uint16_array_get);
2288impl_typed_array!(Uint32Array, u32, local_uint32_array_get);
2289impl_typed_array!(Int8Array, i8, local_int8_array_get);
2290impl_typed_array!(Int16Array, i16, local_int16_array_get);
2291impl_typed_array!(Int32Array, i32, local_int32_array_get);
2292impl_typed_array!(Float32Array, f32, local_float32_array_get);
2293impl_typed_array!(Float64Array, f64, local_float64_array_get);
2294impl_typed_array!(BigInt64Array, i64, local_bigint64_array_get);
2295impl_typed_array!(BigUint64Array, u64, local_biguint64_array_get);
2296// Uint8ClampedArray has the same element type as Uint8Array; clamping is a write-side JS concern.
2297impl_typed_array!(Uint8ClampedArray, u8, local_uint8clamped_array_get);
2298 
2299// =============================================================================
2300// `String`-specific implementations
2301// =============================================================================
2302 
2303/// Write flags matching `v8::String::WriteFlags`.
2304///
2305/// These correspond directly to `kNone`, `kNullTerminate`, and `kReplaceInvalidUtf8`.
2306/// Flags can be combined with `|` via the `BitOr` impl.
2307#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
2308#[repr(i32)]
2309pub enum WriteFlags {
2310 /// No special write flags.
2311 #[default]
2312 None = 0,
2313 /// Include a null terminator in the output. The buffer must have space for it.
2314 NullTerminate = 1,
2315 /// Replace invalid UTF-8/UTF-16 sequences with the Unicode replacement character U+FFFD.
2316 /// Set this to guarantee valid UTF-8 output from `write_utf8`.
2317 ReplaceInvalidUtf8 = 2,
2318 /// Combines `NullTerminate` and `ReplaceInvalidUtf8`.
2319 ///
2320 /// Equivalent to `NullTerminate | ReplaceInvalidUtf8`. This variant exists so that
2321 /// the enum covers every value in `0..=3`, making the `BitOr` transmute sound.
2322 NullTerminateAndReplaceInvalidUtf8 = 3,
2323}
2324 
2325impl WriteFlags {
2326 /// Returns the underlying `i32` bitmask value.
2327 #[inline]
2328 pub fn bits(self) -> i32 {
2329 self as i32
2330 }
2331}
2332 
2333impl std::ops::BitOr for WriteFlags {
2334 type Output = Self;
2335 
2336 fn bitor(self, rhs: Self) -> Self {
2337 // SAFETY: WriteFlags variants cover all values 0..=3; OR-ing any two
2338 // produces a value in that range, all of which have valid discriminants.
2339 unsafe { std::mem::transmute(self.bits() | rhs.bits()) }
2340 }
2341}
2342 
2343/// Rust equivalent of `v8::MaybeLocal<T>` — either a `Local<T>` or empty.
2344///
2345/// Mirrors V8's layout exactly: one pointer-sized word where `ptr == 0` is empty.
2346/// No boxing, no `Option` overhead, no stored isolate — this is a direct ABI-compatible
2347/// view of the `ffi::MaybeLocal` returned across the FFI boundary.
2348///
2349/// Methods that produce a `Local<T>` require a `&mut Lock` so they can recover the
2350/// isolate pointer on demand, matching the pattern used by `Local<T>` constructors.
2351pub struct MaybeLocal<'a, T> {
2352 /// The raw FFI handle. `ptr == 0` means empty (matches `v8::MaybeLocal` default).
2353 handle: ffi::MaybeLocal,
2354 _marker: PhantomData<(&'a (), T)>,
2355}
2356 
2357impl<'a, T> MaybeLocal<'a, T> {
2358 /// Wraps a raw `ffi::MaybeLocal` returned from the FFI layer.
2359 ///
2360 /// # Safety
2361 /// If `handle.ptr != 0`, the handle must point to a live V8 value within the
2362 /// current `HandleScope` of the active isolate.
2363 pub unsafe fn from_ffi(handle: ffi::MaybeLocal) -> Self {
2364 Self {
2365 handle,
2366 _marker: PhantomData,
2367 }
2368 }
2369 
2370 /// Returns `true` if this `MaybeLocal` is empty.
2371 pub fn is_empty(&self) -> bool {
2372 // SAFETY: handle is a valid ffi::MaybeLocal; no isolate or HandleScope needed.
2373 unsafe { ffi::maybe_local_is_empty(&self.handle) }
2374 }
2375 
2376 /// Returns the contained value as a `Local<T>` without consuming `self`, or `None` if empty.
2377 ///
2378 /// Copies the underlying V8 handle pointer so that both `self` and the returned `Local`
2379 /// refer to the same V8 value. V8 `Local` handles are non-owning references into the
2380 /// `HandleScope` stack; `local_clone` is a cheap pointer copy (not a deep clone), and
2381 /// `local_drop` is a no-op for locals. Use [`into_option`](Self::into_option) to transfer
2382 /// ownership without the copy when `self` is no longer needed.
2383 pub fn to_local(&self, lock: &mut crate::Lock) -> Option<Local<'a, T>> {
2384 // SAFETY: handle is a valid ffi::MaybeLocal; no isolate or HandleScope needed.
2385 if unsafe { ffi::maybe_local_is_empty(&self.handle) } {
2386 return None;
2387 }
2388 // local_clone is a bitwise pointer copy — both the MaybeLocal and the returned Local
2389 // refer to the same HandleScope entry. This is safe because Local handles are
2390 // non-owning and local_drop is a no-op; the HandleScope itself manages the lifetime.
2391 // SAFETY: handle is non-empty and points to a live V8 value in the current HandleScope.
2392 let cloned = unsafe {
2393 ffi::local_clone(&ffi::Local {
2394 ptr: self.handle.ptr,
2395 })
2396 };
2397 // SAFETY: handle is non-empty, isolate is valid, and cloned is a valid Local.
2398 Some(unsafe { Local::from_ffi(lock.isolate(), cloned) })
2399 }
2400 
2401 /// Converts into `Option<Local<'a, T>>`, consuming `self`.
2402 ///
2403 /// Transfers ownership of the underlying handle without cloning, which is more efficient
2404 /// than [`to_local`](Self::to_local) when `self` is no longer needed after the call.
2405 pub fn into_option(self, lock: &mut crate::Lock) -> Option<Local<'a, T>> {
2406 if self.handle.ptr == 0 {
2407 return None;
2408 }
2409 // Transfer ownership: zero out the ptr so Drop becomes a no-op, then wrap in Local.
2410 let ptr = self.handle.ptr;
2411 // SAFETY: We are taking ownership of the handle; zeroing the ptr prevents double-free.
2412 let mut this = std::mem::ManuallyDrop::new(self);
2413 this.handle.ptr = 0;
2414 // SAFETY: ptr is non-zero (checked above), isolate is valid, handle ownership transferred.
2415 Some(unsafe { Local::from_ffi(lock.isolate(), ffi::Local { ptr }) })
2416 }
2417 
2418 /// Unwraps the value, panicking if empty.
2419 ///
2420 /// # Panics
2421 ///
2422 /// Panics if the `MaybeLocal` is empty.
2423 pub fn unwrap(self, lock: &mut crate::Lock) -> Local<'a, T> {
2424 self.into_option(lock).expect("MaybeLocal is empty")
2425 }
2426 
2427 /// Returns the contained `Local<T>`, or `default` if empty.
2428 pub fn unwrap_or(self, lock: &mut crate::Lock, default: Local<'a, T>) -> Local<'a, T> {
2429 self.into_option(lock).unwrap_or(default)
2430 }
2431}
2432 
2433impl<T> Drop for MaybeLocal<'_, T> {
2434 fn drop(&mut self) {
2435 if self.handle.ptr != 0 {
2436 let handle = std::mem::replace(&mut self.handle, ffi::MaybeLocal { ptr: 0 });
2437 // SAFETY: handle is a valid non-empty V8 handle being released.
2438 unsafe { ffi::local_drop(ffi::Local { ptr: handle.ptr }) };
2439 }
2440 }
2441}
2442 
2443impl<'a, T> From<Option<Local<'a, T>>> for MaybeLocal<'a, T> {
2444 /// Constructs a `MaybeLocal` from an `Option<Local<T>>`.
2445 /// `None` produces an empty handle (`ptr == 0`); `Some(local)` reuses its pointer.
2446 fn from(opt: Option<Local<'a, T>>) -> Self {
2447 let ptr = match opt {
2448 None => 0,
2449 Some(local) => {
2450 // SAFETY: we take the handle's ptr out without dropping the Local so the
2451 // handle slot stays alive in the HandleScope.
2452 let ptr = local.handle.ptr;
2453 std::mem::forget(local);
2454 ptr
2455 }
2456 };
2457 Self {
2458 handle: ffi::MaybeLocal { ptr },
2459 _marker: PhantomData,
2460 }
2461 }
2462}
2463 
2464impl Local<'_, String> {
2465 // Instance methods — correspond to `v8::String` member functions
2466 
2467 /// Returns the number of characters (UTF-16 code units) in the string.
2468 ///
2469 /// Returns `i32` to match the V8 API (`v8::String::Length()` returns `int`).
2470 /// V8 enforces a maximum string length well below `i32::MAX`, so the result
2471 /// is always non-negative.
2472 ///
2473 /// Corresponds to `v8::String::Length()`.
2474 #[inline]
2475 pub fn length(&self) -> i32 {
2476 // SAFETY: self.handle is a valid V8 String handle.
2477 unsafe { ffi::local_string_length(&self.handle) }
2478 }
2479 
2480 /// Returns `true` if the string is represented internally as one-byte (Latin-1).
2481 ///
2482 /// Corresponds to `v8::String::IsOneByte()`.
2483 #[inline]
2484 pub fn is_one_byte(&self) -> bool {
2485 // SAFETY: self.handle is a valid V8 String handle.
2486 unsafe { ffi::local_string_is_one_byte(&self.handle) }
2487 }
2488 
2489 /// Returns `true` if all characters in the string fit in one byte (Latin-1).
2490 ///
2491 /// Unlike `is_one_byte()`, this scans the entire string and may be slow for
2492 /// two-byte strings that happen to contain only Latin-1 characters.
2493 ///
2494 /// Corresponds to `v8::String::ContainsOnlyOneByte()`.
2495 #[inline]
2496 pub fn contains_only_one_byte(&self) -> bool {
2497 // SAFETY: self.handle is a valid V8 String handle.
2498 unsafe { ffi::local_string_contains_only_one_byte(&self.handle) }
2499 }
2500 
2501 /// Returns the number of bytes required to encode the string as UTF-8.
2502 ///
2503 /// Does not include a null terminator.
2504 ///
2505 /// Corresponds to `v8::String::Utf8LengthV2()`.
2506 #[inline]
2507 pub fn utf8_length(&self, lock: &mut crate::Lock) -> usize {
2508 // SAFETY: Lock guarantees the isolate is locked; self.handle is a valid String handle.
2509 unsafe { ffi::local_string_utf8_length(lock.isolate().as_ffi(), &self.handle) }
2510 }
2511 
2512 /// Writes the string as UTF-16 code units into `buffer`.
2513 ///
2514 /// `offset` is the index of the first character to write; `length` is the
2515 /// maximum number of characters to write. `flags` is a combination of
2516 /// [`WriteFlags`] variants.
2517 ///
2518 /// # Panics
2519 ///
2520 /// Panics if `buffer.len() < length`.
2521 ///
2522 /// Corresponds to `v8::String::WriteV2()`.
2523 pub fn write(
2524 &self,
2525 lock: &mut crate::Lock,
2526 offset: u32,
2527 length: u32,
2528 buffer: &mut [u16],
2529 flags: WriteFlags,
2530 ) {
2531 assert!(
2532 buffer.len() >= length as usize,
2533 "buffer too small for requested length"
2534 );
2535 // SAFETY: Lock guarantees the isolate is locked; buffer is valid and large enough.
2536 unsafe {
2537 ffi::local_string_write_v2(
2538 lock.isolate().as_ffi(),
2539 &self.handle,
2540 offset,
2541 length,
2542 buffer.as_mut_ptr(),
2543 flags.bits(),
2544 );
2545 }
2546 }
2547 
2548 /// Writes the string as Latin-1 bytes into `buffer`.
2549 ///
2550 /// Only meaningful when `is_one_byte()` returns `true`; characters outside
2551 /// the Latin-1 range are truncated.
2552 ///
2553 /// # Panics
2554 ///
2555 /// Panics if `buffer.len() < length`.
2556 ///
2557 /// Corresponds to `v8::String::WriteOneByteV2()`.
2558 pub fn write_one_byte(
2559 &self,
2560 lock: &mut crate::Lock,
2561 offset: u32,
2562 length: u32,
2563 buffer: &mut [u8],
2564 flags: WriteFlags,
2565 ) {
2566 assert!(
2567 buffer.len() >= length as usize,
2568 "buffer too small for requested length"
2569 );
2570 // SAFETY: Lock guarantees the isolate is locked; buffer is valid and large enough.
2571 unsafe {
2572 ffi::local_string_write_one_byte_v2(
2573 lock.isolate().as_ffi(),
2574 &self.handle,
2575 offset,
2576 length,
2577 buffer.as_mut_ptr(),
2578 flags.bits(),
2579 );
2580 }
2581 }
2582 
2583 /// Writes the string as UTF-8 into `buffer`.
2584 ///
2585 /// Returns the number of bytes written. `flags` is a combination of
2586 /// [`WriteFlags`] constants.
2587 ///
2588 /// Corresponds to `v8::String::WriteUtf8V2()`.
2589 pub fn write_utf8(
2590 &self,
2591 lock: &mut crate::Lock,
2592 buffer: &mut [u8],
2593 flags: WriteFlags,
2594 ) -> usize {
2595 // SAFETY: Lock guarantees the isolate is locked; buffer is valid for `capacity` bytes.
2596 unsafe {
2597 ffi::local_string_write_utf8_v2(
2598 lock.isolate().as_ffi(),
2599 &self.handle,
2600 buffer.as_mut_ptr(),
2601 buffer.len(),
2602 flags.bits(),
2603 )
2604 }
2605 }
2606 
2607 // -------------------------------------------------------------------------
2608 // Convenience helpers
2609 // -------------------------------------------------------------------------
2610 
2611 /// Decodes the string to an owned Rust `String` via UTF-8.
2612 ///
2613 /// Allocates a buffer using `utf8_length`, writes into it, and converts.
2614 pub fn to_string(&self, lock: &mut crate::Lock) -> std::string::String {
2615 let byte_len = self.utf8_length(lock);
2616 let mut buf = vec![0u8; byte_len];
2617 let written = self.write_utf8(lock, &mut buf, WriteFlags::None);
2618 buf.truncate(written);
2619 // V8 guarantees valid UTF-8 output when REPLACE_INVALID_UTF8 is not set and
2620 // the string was originally created from valid data. Use from_utf8 to avoid a
2621 // redundant allocation in the common (valid UTF-8) case; fall back to
2622 // from_utf8_lossy only when the bytes are not valid UTF-8 (e.g. two-byte strings
2623 // with unpaired surrogates).
2624 std::string::String::from_utf8(buf)
2625 .unwrap_or_else(|e| std::string::String::from_utf8_lossy(e.as_bytes()).into_owned())
2626 }
2627 
2628 // -------------------------------------------------------------------------
2629 // Additional string operations
2630 // -------------------------------------------------------------------------
2631 
2632 /// Returns an internalized version of this string.
2633 ///
2634 /// If an equal internalized string already exists in V8's string table it is
2635 /// returned; otherwise a new internalized copy is created. The result is
2636 /// pointer-equal to any other internalized string with the same content.
2637 ///
2638 /// Corresponds to `v8::String::InternalizeString()`.
2639 #[must_use]
2640 pub fn internalize(&self, lock: &mut crate::Lock) -> Self {
2641 let isolate = lock.isolate();
2642 // SAFETY: Lock guarantees the isolate is locked; self.handle is a valid String handle.
2643 unsafe {
2644 Local::from_ffi(
2645 isolate,
2646 ffi::local_string_internalize(isolate.as_ffi(), &self.handle),
2647 )
2648 }
2649 }
2650 
2651 /// Returns `true` if the string has a flat (contiguous) internal representation.
2652 ///
2653 /// A flat string stores its characters in a single contiguous buffer, which
2654 /// is required by some V8 APIs. Newly created strings are usually flat; cons
2655 /// strings produced by concatenation may not be.
2656 ///
2657 /// Note: This method is available via a Cloudflare-specific V8 patch
2658 /// (`0029-Add-v8-String-IsFlat-API.patch`).
2659 ///
2660 /// Corresponds to `v8::String::IsFlat()`.
2661 #[inline]
2662 pub fn is_flat(&self) -> bool {
2663 // SAFETY: self.handle is a valid V8 String handle.
2664 unsafe { ffi::local_string_is_flat(&self.handle) }
2665 }
2666}
2667 
2668// =============================================================================
2669// `Name` — supertype of `String` and `Symbol`
2670// =============================================================================
2671 
2672impl Local<'_, Name> {
2673 /// Returns the identity hash for this name.
2674 ///
2675 /// The hash is stable for the lifetime of the name and is never `0`,
2676 /// but is not guaranteed to be unique across different names.
2677 ///
2678 /// Corresponds to `v8::Name::GetIdentityHash()`.
2679 #[inline]
2680 pub fn get_identity_hash(&self) -> i32 {
2681 // SAFETY: self.handle is a valid V8 Name handle.
2682 unsafe { ffi::local_name_get_identity_hash(&self.handle) }
2683 }
2684}
2685 
2686// =============================================================================
2687// `Symbol`
2688// =============================================================================
2689 
2690impl Symbol {
2691 /// Creates a new unique Symbol with an optional description.
2692 ///
2693 /// The returned symbol is unique — two calls with the same description return
2694 /// distinct symbols that are not `===` equal. Pass `None` to create a symbol
2695 /// without a description.
2696 ///
2697 /// Corresponds to `v8::Symbol::New()`.
2698 pub fn new<'a>(
2699 lock: &mut crate::Lock,
2700 description: Option<Local<'a, String>>,
2701 ) -> Local<'a, Self> {
2702 let isolate = lock.isolate();
2703 // SAFETY: Lock guarantees the isolate is locked and a HandleScope is active.
2704 unsafe {
2705 let handle = match description {
2706 Some(desc) => {
2707 ffi::local_symbol_new_with_description(isolate.as_ffi(), desc.into_ffi())
2708 }
2709 None => ffi::local_symbol_new(isolate.as_ffi()),
2710 };
2711 Local::from_ffi(isolate, handle)
2712 }
2713 }
2714}
2715 
2716impl<'a> Local<'a, Symbol> {
2717 /// Returns the description of this symbol, if any.
2718 ///
2719 /// Returns `None` if the symbol was created without a description.
2720 ///
2721 /// Corresponds to `v8::Symbol::Description()`.
2722 pub fn description(&self, lock: &mut crate::Lock) -> Option<Local<'a, String>> {
2723 let isolate = lock.isolate();
2724 // SAFETY: Lock guarantees the isolate is locked; self.handle is a valid Symbol handle.
2725 let maybe = unsafe {
2726 MaybeLocal::<String>::from_ffi(ffi::local_symbol_description(
2727 isolate.as_ffi(),
2728 &self.handle,
2729 ))
2730 };
2731 maybe.to_local(lock)
2732 }
2733}
2734 
2735// =============================================================================
2736// `Utf8Value`
2737// =============================================================================
2738 
2739/// Rust equivalent of `v8::String::Utf8Value`.
2740///
2741/// Converts any V8 value to its UTF-8 string representation (analogous to calling
2742/// `.toString()` in JavaScript) and holds the result for the duration of its lifetime.
2743///
2744/// The UTF-8 bytes are a **heap-allocated copy** independent of the V8 heap — the data
2745/// remains valid and stable for the full lifetime of this `Utf8Value` regardless of GC
2746/// activity.
2747///
2748/// If the value cannot be converted to a string (e.g. a `Symbol`), V8 stores a null
2749/// pointer internally. In that case [`as_ptr`](Self::as_ptr) returns null,
2750/// [`length`](Self::length) returns `0`, and [`as_bytes`](Self::as_bytes) /
2751/// [`as_str`](Self::as_str) return empty slices.
2752///
2753/// # Example
2754///
2755/// ```ignore
2756/// let utf8 = Utf8Value::new(lock, &value);
2757/// println!("{}", utf8.as_str().unwrap_or(""));
2758/// ```
2759pub struct Utf8Value {
2760 inner: ffi::Utf8Value,
2761}
2762 
2763impl Utf8Value {
2764 /// Constructs a `Utf8Value` by converting `value` to its UTF-8 string representation.
2765 ///
2766 /// Produces a heap-allocated copy of the UTF-8 bytes that is independent of the V8
2767 /// heap. If `value` cannot be converted to a string (e.g. a `Symbol`), the internal
2768 /// data pointer will be null and [`length`](Self::length) will return `0`.
2769 ///
2770 /// Corresponds to `v8::String::Utf8Value(isolate, obj)`.
2771 pub fn new(lock: &mut Lock, value: &Local<'_, Value>) -> Self {
2772 // SAFETY: Lock guarantees the isolate is locked and a HandleScope is active.
2773 // local_clone produces a cheap handle copy matching V8's by-value constructor semantics.
2774 let inner = unsafe {
2775 ffi::utf8_value_new(lock.isolate().as_ffi(), ffi::local_clone(value.as_ffi()))
2776 };
2777 Self { inner }
2778 }
2779 
2780 /// Returns the number of UTF-8 bytes in the string, excluding the null terminator.
2781 ///
2782 /// Returns `0` if V8 could not convert the value to a string.
2783 ///
2784 /// Corresponds to `v8::String::Utf8Value::length()`.
2785 #[inline]
2786 pub fn length(&self) -> usize {
2787 // SAFETY: self.inner is a valid Utf8Value.
2788 unsafe { ffi::utf8_value_length(&self.inner) }
2789 }
2790 
2791 /// Returns a raw pointer to the null-terminated UTF-8 bytes stored in this copy.
2792 ///
2793 /// The pointer points into a heap-allocated buffer owned by this `Utf8Value`, not into
2794 /// V8 memory. It is valid for the lifetime of this `Utf8Value`.
2795 ///
2796 /// Returns null if V8 could not convert the value to a string (e.g. a `Symbol`).
2797 ///
2798 /// Corresponds to `v8::String::Utf8Value::operator*()`.
2799 #[inline]
2800 pub fn as_ptr(&self) -> *const u8 {
2801 // SAFETY: self.inner is a valid Utf8Value.
2802 unsafe { ffi::utf8_value_data(&self.inner) }
2803 }
2804 
2805 /// Returns the UTF-8 content as a byte slice.
2806 ///
2807 /// Returns an empty slice if V8 could not convert the value to a string (e.g. a `Symbol`),
2808 /// in which case `operator*()` returns a null pointer.
2809 #[inline]
2810 pub fn as_bytes(&self) -> &[u8] {
2811 let ptr = self.as_ptr();
2812 if ptr.is_null() {
2813 return &[];
2814 }
2815 // SAFETY: ptr is non-null and points to length() valid bytes for the lifetime of self.
2816 unsafe { std::slice::from_raw_parts(ptr, self.length()) }
2817 }
2818 
2819 /// Returns the UTF-8 content as a `&str`, or `None` if the bytes are not valid UTF-8.
2820 #[inline]
2821 pub fn as_str(&self) -> Option<&str> {
2822 std::str::from_utf8(self.as_bytes()).ok()
2823 }
2824}
2825 
2826impl Drop for Utf8Value {
2827 fn drop(&mut self) {
2828 let inner = ffi::Utf8Value {
2829 ptr: self.inner.ptr,
2830 };
2831 // SAFETY: self.inner is a valid Utf8Value being released.
2832 unsafe { ffi::utf8_value_drop(inner) };
2833 }
2834}
2835 
2836// Object-specific implementations
2837impl<'a> Local<'a, Object> {
2838 pub fn set(&mut self, lock: &mut Lock, key: &str, value: Local<'a, Value>) {
2839 // SAFETY: isolate is valid and locked (guaranteed by Lock); handle is valid.
2840 unsafe {
2841 ffi::local_object_set_property(
2842 lock.isolate().as_ffi(),
2843 &mut self.handle,
2844 key,
2845 value.into_ffi(),
2846 );
2847 }
2848 }
2849 
2850 pub fn has(&self, lock: &mut Lock, key: &str) -> bool {
2851 // SAFETY: isolate is valid and locked (guaranteed by Lock); handle is valid.
2852 unsafe { ffi::local_object_has_property(lock.isolate().as_ffi(), &self.handle, key) }
2853 }
2854 
2855 pub fn get(&self, lock: &mut Lock, key: &str) -> Option<Local<'a, Value>> {
2856 if !self.has(lock, key) {
2857 return None;
2858 }
2859 
2860 // SAFETY: isolate is valid and locked (guaranteed by Lock); handle is valid.
2861 unsafe {
2862 let maybe_local =
2863 ffi::local_object_get_property(lock.isolate().as_ffi(), &self.handle, key);
2864 let opt_local: Option<ffi::Local> = maybe_local.into();
2865 opt_local.map(|local| Local::from_ffi(lock.isolate(), local))
2866 }
2867 }
2868}
2869 
2870// Generic Global<T> handle without lifetime
2871pub struct Global<T> {
2872 handle: ffi::Global,
2873 /// Weak `v8::TracedReference<v8::Data>` handle used during GC tracing.
2874 ///
2875 /// Empty (`ptr == 0`) when the strong handle is active; becomes non-empty
2876 /// once the parent `Wrappable` is downgraded to traced mode (all strong Rust
2877 /// `Rc`s dropped). Reset to empty when strong refs are re-acquired.
2878 ///
2879 /// `UnsafeCell` is required because `GcVisitor::visit_global` takes `&Global<T>`
2880 /// (shared reference) but must mutate this field to install the traced handle.
2881 /// This is sound because GC tracing is always single-threaded within a V8
2882 /// isolate and `trace` is never re-entrant on the same object.
2883 traced: UnsafeCell<ffi::TracedReference>,
2884 _marker: PhantomData<T>,
2885}
2886 
2887// Common implementations for all Global<T>
2888impl<T> Global<T> {
2889 /// Creates a `Global` from an FFI handle.
2890 ///
2891 /// # Safety
2892 /// The caller must ensure that `handle` points to a valid V8 persistent handle.
2893 pub unsafe fn from_ffi(handle: ffi::Global) -> Self {
2894 Self {
2895 handle,
2896 traced: UnsafeCell::new(ffi::TracedReference { ptr: 0 }),
2897 _marker: PhantomData,
2898 }
2899 }
2900 
2901 /// Returns a reference to the underlying FFI handle.
2902 ///
2903 /// # Safety
2904 /// The caller must ensure the returned reference is not used after this `Global` is dropped.
2905 pub unsafe fn as_ffi_ref(&self) -> &ffi::Global {
2906 &self.handle
2907 }
2908 
2909 /// Returns a mutable reference to the underlying FFI handle.
2910 ///
2911 /// # Safety
2912 /// The caller must ensure the returned reference is not used after this `Global` is dropped.
2913 pub unsafe fn as_ffi_mut(&mut self) -> *mut ffi::Global {
2914 &raw mut self.handle
2915 }
2916 
2917 pub fn as_local<'a>(&self, lock: &mut Lock) -> Local<'a, T> {
2918 // SAFETY: isolate is valid and locked (guaranteed by Lock); global handle is valid.
2919 unsafe {
2920 Local::from_ffi(
2921 lock.isolate(),
2922 ffi::global_to_local(lock.isolate().as_ffi(), &self.handle),
2923 )
2924 }
2925 }
2926 
2927 /// Resets this global handle, releasing both the strong and traced V8 handles.
2928 ///
2929 /// # Safety
2930 /// The caller must ensure the global handle is valid.
2931 pub unsafe fn reset(&mut self) {
2932 // Reset the strong handle.
2933 // SAFETY: global handle is valid; Pin is sound because ffi::Global is not moved.
2934 unsafe {
2935 ffi::global_reset(Pin::new_unchecked(&mut self.handle));
2936 }
2937 // Reset the TracedReference to avoid leaking a live V8 handle when this
2938 // Global is dropped while in traced mode.
2939 // SAFETY: traced is valid for the lifetime of self.
2940 unsafe {
2941 ffi::traced_reference_reset(self.traced.get_mut());
2942 }
2943 }
2944}
2945 
2946impl<T> From<Local<'_, T>> for Global<T> {
2947 fn from(local: Local<'_, T>) -> Self {
2948 Self {
2949 // SAFETY: isolate is valid (guaranteed by Local's invariant); handle is valid.
2950 handle: unsafe { ffi::local_to_global(local.isolate.as_ffi(), local.into_ffi()) },
2951 traced: UnsafeCell::new(ffi::TracedReference { ptr: 0 }),
2952 _marker: PhantomData,
2953 }
2954 }
2955}
2956 
2957impl Global<FunctionTemplate> {
2958 /// Returns the constructor function for this function template.
2959 ///
2960 /// This is the V8 `Function` object that can be called as a constructor or
2961 /// used to access static methods, analogous to a JavaScript class reference
2962 /// (e.g., `URL`, `TextEncoder`).
2963 pub fn as_local_function<'a>(&self, lock: &mut Lock) -> Local<'a, Function> {
2964 // SAFETY: `lock` guarantees the isolate is locked and a HandleScope is active.
2965 // `self.handle` is a valid `Global<FunctionTemplate>` created by `create_resource_template`.
2966 // The returned `Local` is tied to the current HandleScope via the `'a` lifetime.
2967 unsafe {
2968 Local::from_ffi(
2969 lock.isolate(),
2970 ffi::function_template_get_function(lock.isolate().as_ffi(), &self.handle),
2971 )
2972 }
2973 }
2974}
2975 
2976// Allow implicit conversion from ffi::Global
2977impl<T> From<ffi::Global> for Global<T> {
2978 fn from(handle: ffi::Global) -> Self {
2979 Self {
2980 handle,
2981 traced: UnsafeCell::new(ffi::TracedReference { ptr: 0 }),
2982 _marker: PhantomData,
2983 }
2984 }
2985}
2986 
2987impl<T> Drop for Global<T> {
2988 fn drop(&mut self) {
2989 // SAFETY: global handle is valid (guaranteed by construction).
2990 unsafe { self.reset() };
2991 }
2992}
2993 
2994/// `Global<T>` is a strong GC handle — visited via `GcVisitor::visit_global`.
2995impl<T> crate::Traced for Global<T> {
2996 fn trace(&self, visitor: &mut GcVisitor) {
2997 visitor.visit_global(self);
2998 }
2999}
3000 
3001// Note: Global<T> intentionally does NOT implement the std Clone trait.
3002// Cloning a V8 persistent handle requires an isolate pointer to create an
3003// independent handle via v8::Global::New(isolate, original). The Clone trait
3004// cannot provide this. Use the `clone()` method directly instead.
3005impl<T> Global<T> {
3006 /// Creates an independent copy of this persistent handle.
3007 ///
3008 /// This properly creates a new V8 persistent handle that references the same
3009 /// JS object. Both the original and clone can be independently dropped.
3010 ///
3011 /// The returned clone always starts in **strong mode** (`traced_ptr = 0`),
3012 /// regardless of whether `self` is currently in traced mode. If the clone
3013 /// needs to be traced, `GcVisitor::visit_global` will transition it on the
3014 /// next GC cycle.
3015 #[must_use]
3016 pub fn clone(&self, lock: &mut Lock) -> Self {
3017 // SAFETY: isolate is valid and locked (guaranteed by Lock); global handle is valid.
3018 unsafe { ffi::global_clone(lock.isolate().as_ffi(), &self.handle).into() }
3019 }
3020}
3021 
3022pub trait ToLocalValue {
3023 fn to_local<'a>(&self, lock: &mut Lock) -> Local<'a, Value>;
3024}
3025 
3026macro_rules! impl_to_local_value_integer {
3027 ($($type:ty),*) => {
3028 $(
3029 impl ToLocalValue for $type {
3030 fn to_local<'a>(&self, lock: &mut Lock) -> Local<'a, Value> {
3031 // SAFETY: isolate is valid and locked (guaranteed by Lock).
3032 unsafe {
3033 Local::from_ffi(
3034 lock.isolate(),
3035 ffi::local_new_number(lock.isolate().as_ffi(), f64::from(*self)),
3036 )
3037 }
3038 }
3039 }
3040 )*
3041 };
3042}
3043 
3044impl_to_local_value_integer!(u8, u16, u32, i8, i16, i32);
3045 
3046impl ToLocalValue for std::string::String {
3047 fn to_local<'a>(&self, lock: &mut Lock) -> Local<'a, Value> {
3048 self.as_str().to_local(lock)
3049 }
3050}
3051 
3052impl ToLocalValue for &str {
3053 fn to_local<'a>(&self, lock: &mut Lock) -> Local<'a, Value> {
3054 // SAFETY: isolate is valid and locked (guaranteed by Lock).
3055 unsafe {
3056 Local::from_ffi(
3057 lock.isolate(),
3058 ffi::local_new_string(lock.isolate().as_ffi(), self),
3059 )
3060 }
3061 }
3062}
3063 
3064impl ToLocalValue for bool {
3065 fn to_local<'a>(&self, lock: &mut Lock) -> Local<'a, Value> {
3066 // SAFETY: isolate is valid and locked (guaranteed by Lock).
3067 unsafe {
3068 Local::from_ffi(
3069 lock.isolate(),
3070 ffi::local_new_boolean(lock.isolate().as_ffi(), *self),
3071 )
3072 }
3073 }
3074}
3075 
3076impl ToLocalValue for Number {
3077 fn to_local<'a>(&self, lock: &mut Lock) -> Local<'a, Value> {
3078 // SAFETY: isolate is valid and locked (guaranteed by Lock).
3079 unsafe {
3080 Local::from_ffi(
3081 lock.isolate(),
3082 ffi::local_new_number(lock.isolate().as_ffi(), self.value()),
3083 )
3084 }
3085 }
3086}
3087 
3088pub struct FunctionCallbackInfo<'a>(*mut ffi::FunctionCallbackInfo, PhantomData<&'a ()>);
3089 
3090impl<'a> FunctionCallbackInfo<'a> {
3091 /// # Safety
3092 /// The caller must ensure that `info` is a valid pointer to `FunctionCallbackInfo`.
3093 pub unsafe fn from_ffi(info: *mut ffi::FunctionCallbackInfo) -> Self {
3094 Self(info, PhantomData)
3095 }
3096 
3097 /// Returns the V8 isolate associated with this function callback.
3098 pub fn isolate(&self) -> IsolatePtr {
3099 // SAFETY: self.0 is a valid FunctionCallbackInfo pointer (guaranteed by constructor).
3100 unsafe { IsolatePtr::from_ffi(ffi::fci_get_isolate(self.0)) }
3101 }
3102 
3103 pub fn this(&self) -> Local<'a, Value> {
3104 // SAFETY: self.0 is a valid FunctionCallbackInfo pointer (guaranteed by constructor).
3105 unsafe { Local::from_ffi(self.isolate(), ffi::fci_get_this(self.0)) }
3106 }
3107 
3108 pub fn len(&self) -> usize {
3109 // SAFETY: self.0 is a valid FunctionCallbackInfo pointer (guaranteed by constructor).
3110 unsafe { ffi::fci_get_length(self.0) }
3111 }
3112 
3113 pub fn is_empty(&self) -> bool {
3114 self.len() == 0
3115 }
3116 
3117 pub fn get(&self, index: usize) -> Local<'a, Value> {
3118 debug_assert!(index <= self.len(), "index out of bounds");
3119 // SAFETY: self.0 is a valid FunctionCallbackInfo pointer (guaranteed by constructor).
3120 unsafe { Local::from_ffi(self.isolate(), ffi::fci_get_arg(self.0, index)) }
3121 }
3122 
3123 pub fn set_return_value(&self, value: Local<Value>) {
3124 // SAFETY: self.0 is a valid FunctionCallbackInfo pointer (guaranteed by constructor).
3125 unsafe {
3126 ffi::fci_set_return_value(self.0, value.into_ffi());
3127 }
3128 }
3129}
3130 
3131/// A fat pointer to a `dyn GarbageCollected` trait object, decomposed into its
3132/// data pointer and vtable pointer halves.
3133///
3134/// Rust trait object pointers (`*mut dyn Trait`) are fat pointers with the
3135/// layout `[data_ptr, vtable_ptr]`. We decompose them so the two halves can be
3136/// stored in the C++ `Wrappable::data[0..1]` slots (`uintptr_t`).
3137///
3138/// # Safety of the transmute
3139///
3140/// Rust guarantees that `*mut dyn Trait` is two pointers wide (the "fat
3141/// pointer" representation). We transmute to `[*mut (); 2]` to split and
3142/// rejoin. While the *exact* field order (`[data, vtable]`) is not
3143/// stabilised in a language RFC, it has been the layout since Rust 1.0 and
3144/// is relied upon by miri, the compiler test suite, and the unstable
3145/// `ptr_metadata` API. A layout change would break the ecosystem; we add a
3146/// compile-time size assert as a safety net.
3147///
3148/// TODO(rust-lang/rust#81513): Replace the transmute with `std::ptr::metadata`
3149/// / `std::ptr::from_raw_parts_mut` once the `ptr_metadata` feature is
3150/// stabilised. That will make the `[data, vtable]` ordering an explicit API
3151/// contract rather than an assumed layout.
3152// Fat pointer must be exactly two pointers wide.
3153const _: () = assert!(
3154 size_of::<*mut dyn GarbageCollected>() == 2 * size_of::<usize>(),
3155 "trait object pointer must be two pointers wide",
3156);
3157 
3158impl Clone for ffi::TraitObjectPtr {
3159 fn clone(&self) -> Self {
3160 Self {
3161 data_ptr: self.data_ptr,
3162 vtable_ptr: self.vtable_ptr,
3163 type_id_lo: self.type_id_lo,
3164 type_id_hi: self.type_id_hi,
3165 }
3166 }
3167}
3168 
3169impl ffi::TraitObjectPtr {
3170 /// Creates a `TraitObjectPtr` from a raw `*mut dyn GarbageCollected` fat pointer
3171 /// and the concrete type's `TypeId`.
3172 pub(crate) fn from_raw(ptr: *mut dyn GarbageCollected, type_id: std::any::TypeId) -> Self {
3173 // SAFETY: a fat pointer is layout-equivalent to [*mut (); 2].
3174 let [data, vtable]: [*mut (); 2] = unsafe { std::mem::transmute(ptr) };
3175 
3176 // Casting a fat pointer to *mut () is guaranteed to yield the data pointer.
3177 // If the transmuted layout ever diverges from [data, vtable], this catches it.
3178 debug_assert_eq!(
3179 data.cast::<()>(),
3180 ptr.cast::<()>(),
3181 "fat pointer layout assumption violated: expected [data, vtable]"
3182 );
3183 
3184 assert!(!data.is_null(), "Rc::into_raw returned null");
3185 
3186 // SAFETY: TypeId is 128 bits (two usize on 64-bit), transmute to split halves for FFI storage.
3187 let [type_id_lo, type_id_hi]: [usize; 2] = unsafe { std::mem::transmute(type_id) };
3188 
3189 Self {
3190 data_ptr: data as usize,
3191 vtable_ptr: vtable as usize,
3192 type_id_lo,
3193 type_id_hi,
3194 }
3195 }
3196 
3197 /// Returns `true` if this trait object has been cleared (data pointer is null).
3198 pub(crate) fn is_cleared(&self) -> bool {
3199 self.data_ptr == 0
3200 }
3201 
3202 /// Reads the trait object pointer from a Wrappable.
3203 ///
3204 /// Returns `None` if the trait object has been cleared (resource already dropped).
3205 fn from_wrappable(wrappable: &ffi::Wrappable) -> Option<&Self> {
3206 // SAFETY: wrappable is valid (guaranteed by KjRc lifetime).
3207 let ptr = unsafe { ffi::wrappable_get_trait_object(wrappable) };
3208 if ptr.is_cleared() { None } else { Some(ptr) }
3209 }
3210 
3211 /// Returns the stored `TypeId`.
3212 pub(crate) fn type_id(&self) -> std::any::TypeId {
3213 // SAFETY: Reconstructing TypeId from the same [lo, hi] halves stored in from_raw.
3214 unsafe { std::mem::transmute([self.type_id_lo, self.type_id_hi]) }
3215 }
3216 
3217 /// Returns the data pointer as a `NonNull<R>`.
3218 ///
3219 /// # Safety
3220 /// The caller must have verified the `TypeId` matches `R`.
3221 unsafe fn data_as<R>(&self) -> NonNull<R> {
3222 // SAFETY: data_ptr is non-null (checked in from_raw) and TypeId was verified by caller.
3223 unsafe { NonNull::new_unchecked(self.data_ptr as *mut R) }
3224 }
3225 
3226 /// Reconstructs a shared reference to the `dyn GarbageCollected` object.
3227 ///
3228 /// # Safety
3229 /// The original object must still be alive for lifetime `'a`.
3230 #[expect(clippy::needless_lifetimes)]
3231 unsafe fn as_gc_ref<'a>(&'a self) -> &'a dyn GarbageCollected {
3232 // SAFETY: transmuting [data, vtable] back into a fat pointer.
3233 unsafe {
3234 let fat_ptr: *const dyn GarbageCollected =
3235 std::mem::transmute([self.data_ptr as *const (), self.vtable_ptr as *const ()]);
3236 &*fat_ptr
3237 }
3238 }
3239 
3240 /// Reconstructs the `Rc<dyn GarbageCollected>` and drops it.
3241 ///
3242 /// # Safety
3243 /// The data pointer must have originated from `Rc::into_raw`.
3244 /// Must only be called once per allocation.
3245 unsafe fn drop_rc(&self) {
3246 // SAFETY: transmuting [data, vtable] back into a fat pointer; Rc::from_raw
3247 // reclaims the allocation originally created by Rc::into_raw in from_raw.
3248 unsafe {
3249 let fat_ptr: *const dyn GarbageCollected =
3250 std::mem::transmute([self.data_ptr as *const (), self.vtable_ptr as *const ()]);
3251 drop(Rc::from_raw(fat_ptr));
3252 }
3253 }
3254 
3255 /// Zeroes the trait object in a Wrappable.
3256 ///
3257 /// Called before `drop_rc` so that any re-entrant `wrappable_invoke_trace`
3258 /// calls during destruction see null and no-op.
3259 ///
3260 /// # Safety
3261 /// Must only be called while holding exclusive access (e.g. via `Pin<&mut Wrappable>`).
3262 unsafe fn clear_wrappable(wrappable: Pin<&mut ffi::Wrappable>) {
3263 // SAFETY: wrappable is valid and exclusively accessed (caller holds Pin<&mut>).
3264 unsafe { ffi::wrappable_clear_trait_object(wrappable) };
3265 }
3266}
3267 
3268/// `TraitObjectPtr` → `WrappableRc`: allocates a new Wrappable on the KJ heap,
3269/// transferring ownership of the `Rc`-backed `dyn GarbageCollected` fat pointer.
3270///
3271/// # Safety
3272/// The data pointer must have come from `Rc::into_raw` and must not be used to
3273/// reconstruct the Rc after this call (the Wrappable now owns it).
3274impl From<ffi::TraitObjectPtr> for WrappableRc {
3275 fn from(ptr: ffi::TraitObjectPtr) -> Self {
3276 Self {
3277 // SAFETY: ptr contains a valid Rc::into_raw data pointer (guaranteed by caller).
3278 handle: unsafe { ffi::wrappable_new(ptr) },
3279 }
3280 }
3281}
3282 
3283unsafe fn wrappable_invoke_drop(wrappable: Pin<&mut ffi::Wrappable>) {
3284 // SAFETY: wrappable is valid and exclusively accessed (Pin<&mut>).
3285 // Clear data slots before drop to prevent re-entrant trace during destruction,
3286 // then drop the Rc<dyn GarbageCollected> to release the resource.
3287 unsafe {
3288 let Some(trait_ptr) = ffi::TraitObjectPtr::from_wrappable(wrappable.as_ref().get_ref())
3289 else {
3290 return;
3291 };
3292 // Clone before clearing — clear_wrappable_data zeroes data_ptr.
3293 let saved = trait_ptr.clone();
3294 ffi::TraitObjectPtr::clear_wrappable(wrappable);
3295 saved.drop_rc();
3296 }
3297}
3298 
3299unsafe fn wrappable_invoke_trace(wrappable: &ffi::Wrappable, visitor: *mut ffi::GcVisitor) {
3300 // SAFETY: wrappable is valid. visitor is a valid C++ GcVisitor pointer.
3301 unsafe {
3302 let Some(trait_ptr) = ffi::TraitObjectPtr::from_wrappable(wrappable) else {
3303 return;
3304 };
3305 let mut gc_visitor = GcVisitor::from_ffi(visitor);
3306 trait_ptr.as_gc_ref().trace(&mut gc_visitor);
3307 }
3308}
3309 
3310unsafe fn wrappable_invoke_get_name(wrappable: &ffi::Wrappable) -> &'static str {
3311 // SAFETY: wrappable is valid.
3312 unsafe {
3313 let Some(trait_ptr) = ffi::TraitObjectPtr::from_wrappable(wrappable) else {
3314 return "";
3315 };
3316 // memory_name() returns a &'static CStr (a compile-time NUL-terminated
3317 // literal). Convert to &str so CXX can pass it as rust::Str. The C++
3318 // side uses .data()/.size() directly as a kj::StringPtr — no copy.
3319 trait_ptr.as_gc_ref().memory_name().to_str().unwrap_or("")
3320 }
3321}
3322 
3323/// Visitor for garbage collection tracing.
3324///
3325/// `GcVisitor` wraps a C++ `jsg::GcVisitor` pointer. All GC visitation logic
3326/// (strong/traced switching, parent tracking) is handled by the C++ side via
3327/// `Wrappable::visitRef()`.
3328#[derive(Debug)]
3329pub struct GcVisitor {
3330 pub(crate) handle: ffi::GcVisitor,
3331}
3332 
3333impl GcVisitor {
3334 /// Creates a `GcVisitor` from a raw FFI pointer.
3335 ///
3336 /// # Safety
3337 ///
3338 /// `visitor` must be a valid, non-null pointer to a live `ffi::GcVisitor`.
3339 pub(crate) unsafe fn from_ffi(visitor: *mut ffi::GcVisitor) -> Self {
3340 Self {
3341 handle: ffi::GcVisitor {
3342 // SAFETY: visitor is a valid, non-null pointer (guaranteed by caller).
3343 ptr: unsafe { (*visitor).ptr },
3344 },
3345 }
3346 }
3347 
3348 /// Visits a `jsg::Rc<R>` field during GC tracing.
3349 ///
3350 /// Delegates to the C++ `Wrappable::visitRef()` which handles all the
3351 /// strong/traced switching logic and transitive tracing.
3352 pub fn visit_rc<R: crate::Resource>(&mut self, r: &crate::Rc<R>) {
3353 r.visit(self);
3354 }
3355 
3356 /// Visits a `v8::Global<T>` field during GC tracing.
3357 ///
3358 /// Implements the same strong↔traced dual-mode switching that `jsg::Data`
3359 /// / `jsg::V8Ref<T>` use in C++. When the parent `Wrappable` has strong
3360 /// Rust refs the handle stays strong; once all Rust refs are dropped and
3361 /// only the JS wrapper keeps it alive, the handle is downgraded to a
3362 /// `v8::TracedReference` that cppgc can follow — allowing GC to detect
3363 /// and break reference cycles.
3364 ///
3365 /// Accepts `&Global<T>` even though it mutates the `traced` slot inside the
3366 /// handle. This is safe because:
3367 /// - GC tracing is always single-threaded within a V8 isolate.
3368 /// - `trace` is never re-entrant on the same object during a GC cycle.
3369 /// - The mutation only touches `traced` (the weak traced handle slot),
3370 /// never `handle` (the strong handle), so the value observed through any
3371 /// other `&Global<T>` reference remains valid.
3372 pub fn visit_global<T>(&mut self, global: &Global<T>) {
3373 // SAFETY: `global.traced` is an `UnsafeCell`; accessing it via `get()`
3374 // is sound under the single-threaded, non-reentrant GC tracing contract
3375 // documented on `Global<T>::traced`.
3376 unsafe {
3377 ffi::wrappable_visit_global(
3378 &raw mut self.handle,
3379 (&raw const global.handle.ptr).cast_mut(),
3380 &mut *global.traced.get(),
3381 );
3382 }
3383 }
3384}
3385 
3386/// A safe wrapper around a V8 isolate pointer.
3387///
3388/// `IsolatePtr` provides a type-safe abstraction over raw `v8::Isolate*` pointers,
3389/// ensuring that the pointer is always non-null. This type is `Copy` and can be
3390/// freely passed around without worrying about ownership.
3391///
3392/// # Thread Safety
3393///
3394/// V8 isolates are single-threaded. While `IsolatePtr` itself is `Send` and `Sync`
3395/// (as it's just a pointer wrapper), V8 operations must only be performed on the
3396/// thread that owns the isolate lock. Use `is_locked()` to verify the current
3397/// thread holds the lock before performing V8 operations.
3398///
3399/// # Example
3400///
3401/// ```ignore
3402/// // Create from raw pointer (unsafe)
3403/// let isolate = unsafe { v8::IsolatePtr::from_ffi(raw_ptr) };
3404///
3405/// // Check if locked before V8 operations
3406/// assert!(unsafe { isolate.is_locked() });
3407///
3408/// // Get raw pointer for FFI calls
3409/// let ptr = isolate.as_ffi();
3410/// ```
3411#[derive(Clone, Copy, Debug)]
3412pub struct IsolatePtr {
3413 handle: NonNull<ffi::Isolate>,
3414}
3415 
3416impl IsolatePtr {
3417 /// Creates an `IsolatePtr` from a raw pointer.
3418 ///
3419 /// # Safety
3420 /// The pointer must be non-null and point to a valid V8 isolate.
3421 pub unsafe fn from_ffi(handle: *mut ffi::Isolate) -> Self {
3422 // SAFETY: isolate pointer is valid (guaranteed by caller).
3423 debug_assert!(unsafe { ffi::isolate_is_locked(handle) });
3424 Self {
3425 // SAFETY: handle is non-null (guaranteed by caller).
3426 handle: unsafe { NonNull::new_unchecked(handle) },
3427 }
3428 }
3429 
3430 /// Creates an `IsolatePtr` from a `NonNull` pointer.
3431 pub fn from_non_null(handle: NonNull<ffi::Isolate>) -> Self {
3432 // SAFETY: handle is non-null (guaranteed by NonNull) and points to a valid isolate.
3433 debug_assert!(unsafe { ffi::isolate_is_locked(handle.as_ptr()) });
3434 Self { handle }
3435 }
3436 
3437 /// Returns whether this isolate is currently locked by the current thread.
3438 ///
3439 /// # Safety
3440 ///
3441 /// The caller must ensure the isolate is still valid and not deallocated.
3442 pub unsafe fn is_locked(&self) -> bool {
3443 // SAFETY: isolate pointer is valid (guaranteed by caller).
3444 unsafe { ffi::isolate_is_locked(self.handle.as_ptr()) }
3445 }
3446 
3447 /// Returns the raw pointer to the V8 isolate.
3448 pub fn as_ffi(&self) -> *mut ffi::Isolate {
3449 self.handle.as_ptr()
3450 }
3451 
3452 /// Returns the `NonNull` pointer to the V8 isolate.
3453 pub fn as_non_null(&self) -> NonNull<ffi::Isolate> {
3454 self.handle
3455 }
3456}
3457 
3458// =============================================================================
3459// Wrappable — owned, reference-counted handle
3460// =============================================================================
3461 
3462/// Owned, reference-counted handle to a C++ `Wrappable` on the KJ heap.
3463///
3464/// Encapsulates `KjRc<ffi::Wrappable>` so that modules outside `v8` never
3465/// reference the CXX-generated `ffi::Wrappable` type directly.
3466///
3467/// `Clone` / `Drop` only affect the `KjRc` refcount (`kj::Rc` reference counting).
3468/// GC strong-ref tracking (`addStrongRef` / `removeStrongRef`) is handled by
3469/// `Ref<R>`, not here.
3470#[derive(Clone)]
3471pub struct WrappableRc {
3472 handle: kj_rs::KjRc<ffi::Wrappable>,
3473}
3474 
3475impl WrappableRc {
3476 /// Unwraps a JavaScript value to get an owned Wrappable handle.
3477 ///
3478 /// Returns `None` if the value is not a Rust-tagged Wrappable
3479 /// (e.g. a C++ JSG object, a plain JS object, or a primitive).
3480 ///
3481 /// The C++ `unwrap_resource` returns a `KjRc<Wrappable>` whose inner
3482 /// pointer is null when the value doesn't contain a Rust Wrappable.
3483 /// We check `get().is_null()` to distinguish that case.
3484 #[doc(hidden)]
3485 pub fn from_js(isolate: IsolatePtr, value: Local<Value>) -> Option<Self> {
3486 // SAFETY: isolate is valid and locked; value handle is valid.
3487 let handle = unsafe { ffi::unwrap_resource(isolate.as_ffi(), value.into_ffi()) };
3488 if handle.get().is_null() {
3489 return None;
3490 }
3491 Some(Self { handle })
3492 }
3493 
3494 /// Wraps this Wrappable as a JavaScript object using the given constructor template.
3495 pub(crate) fn to_js<'a>(
3496 &self,
3497 isolate: IsolatePtr,
3498 constructor: &Global<FunctionTemplate>,
3499 ) -> Local<'a, Value> {
3500 // SAFETY: isolate is valid and locked; constructor global handle is valid.
3501 unsafe {
3502 Local::from_ffi(
3503 isolate,
3504 ffi::wrap_resource(
3505 isolate.as_ffi(),
3506 self.handle.clone(),
3507 constructor.as_ffi_ref(),
3508 ),
3509 )
3510 }
3511 }
3512 
3513 /// Attaches this Wrappable to the `this` object in a V8 constructor callback.
3514 ///
3515 /// V8 has already created the `this` object from the `FunctionTemplate`'s
3516 /// `InstanceTemplate`; this method attaches the Wrappable to it via
3517 /// `CppgcShim` so that instance methods can resolve the resource.
3518 pub fn attach_to_this(&self, info: &mut FunctionCallbackInfo) {
3519 // SAFETY: info is valid for the duration of the callback.
3520 let pin = unsafe { std::pin::Pin::new_unchecked(&mut *info.0) };
3521 // SAFETY: The Pin guarantees info is valid. wrap_constructor attaches
3522 // the Wrappable to args.This() and sets the Rust tag.
3523 unsafe { ffi::wrappable_attach_wrapper(self.handle.clone(), pin) };
3524 }
3525 
3526 /// Creates an owning `WrappableRc` from a raw `*const ffi::Wrappable` pointer.
3527 ///
3528 /// Increments the `kj::Rc` refcount via `addRefToThis()`.
3529 ///
3530 /// # Safety
3531 /// The pointed-to Wrappable must still be alive.
3532 pub(crate) unsafe fn from_raw_wrappable(ptr: *const ffi::Wrappable) -> Self {
3533 // SAFETY: ptr is valid and alive (caller verified via Weak::upgrade).
3534 // The const-to-mut cast is sound for the same reason as as_pin_mut().
3535 // wrappable_to_rc calls addRefToThis() which uses interior mutability.
3536 unsafe {
3537 let wrappable = Pin::new_unchecked(&mut *ptr.cast_mut());
3538 Self {
3539 handle: ffi::wrappable_to_rc(wrappable),
3540 }
3541 }
3542 }
3543 
3544 /// Returns a `Pin<&mut ffi::Wrappable>` for C++ FFI calls.
3545 ///
3546 /// Takes `&self` because the mutation target is the C++ `Wrappable` on the
3547 /// KJ heap (behind the raw pointer in `KjRc`), not the `WrappableRc` wrapper
3548 /// itself. `KjRc::get()` returns `*const T`; the const-to-mut cast is
3549 /// required because CXX maps non-const C++ references (`T&`) to
3550 /// `Pin<&mut T>`. `KjRc::get_mut()` cannot be used because it returns
3551 /// `None` when the refcount > 1 (the common case).
3552 ///
3553 /// # Safety
3554 ///
3555 /// The returned `Pin<&mut Wrappable>` must be used transiently — passed
3556 /// directly into a C++ FFI call and never stored. This prevents aliased
3557 /// `&mut` references from coexisting. The invariant is enforced by:
3558 ///
3559 /// 1. **`pub(crate)` visibility** — only code in this crate can call this.
3560 /// 2. **Single-threaded V8 isolate** — all callers run on the isolate's
3561 /// thread, so no concurrent access is possible.
3562 /// 3. **No same-object re-entrancy** — the C++ methods called through this
3563 /// pin (`addStrongRef`, `removeStrongRef`, `visitRef`) may re-enter Rust
3564 /// for *different* Wrappables during GC tracing (e.g. `visitRef` traces
3565 /// children, which calls `Traced::trace()` on them), but never
3566 /// create a second `Pin<&mut Wrappable>` for the *same* object.
3567 #[expect(
3568 clippy::mut_from_ref,
3569 reason = "Pin<&mut> comes from a raw pointer, not from &self"
3570 )]
3571 unsafe fn as_pin_mut(&self) -> Pin<&mut ffi::Wrappable> {
3572 // SAFETY: KjRc pointer is valid; const-to-mut cast is sound (see doc comment above).
3573 unsafe { Pin::new_unchecked(&mut *self.handle.get().cast_mut()) }
3574 }
3575 
3576 /// Visits this Wrappable during GC tracing.
3577 ///
3578 /// Takes `&self` because this is called from `Ref::visit(&self)` which is
3579 /// called from `Traced::trace(&self)`. The mutation target is
3580 /// the C++ `Wrappable` on the KJ heap, not the `WrappableRc` wrapper.
3581 pub(crate) fn visit_rc(&self, parent: *mut usize, strong: *mut bool, visitor: &mut GcVisitor) {
3582 // SAFETY: wrappable, parent, strong, and visitor pointers are all valid (guaranteed by callers).
3583 unsafe {
3584 ffi::wrappable_visit_ref(
3585 self.as_pin_mut(),
3586 parent,
3587 strong,
3588 std::ptr::from_mut(&mut visitor.handle),
3589 );
3590 }
3591 }
3592 
3593 /// Returns a `NonNull` pointer to the underlying `ffi::Wrappable`.
3594 ///
3595 /// The `KjRc` always holds a valid, non-null pointer to the Wrappable on
3596 /// the KJ heap.
3597 pub(crate) fn as_ptr(&self) -> NonNull<ffi::Wrappable> {
3598 // SAFETY: KjRc always holds a valid, non-null pointer.
3599 unsafe { NonNull::new_unchecked(self.handle.get().cast_mut()) }
3600 }
3601 
3602 /// Increments the strong reference count on the underlying Wrappable.
3603 ///
3604 /// Called when a new `Ref<R>` is created (clone, unwrap) to inform the GC
3605 /// that this Wrappable has an additional strong reference from Rust.
3606 pub(crate) fn add_strong_ref(&mut self) {
3607 // SAFETY: wrappable is valid (guaranteed by KjRc lifetime).
3608 unsafe { ffi::wrappable_add_strong_ref(self.as_pin_mut()) };
3609 }
3610 
3611 /// Decrements the strong reference count and potentially defers destruction.
3612 ///
3613 /// Called when a `Ref<R>` is dropped. Calls `maybeDeferDestruction` on
3614 /// the C++ side with the ref's current `strong` flag. If `is_strong` is
3615 /// true, `~RefToDelete` will call `removeStrongRef()`; if false (the ref
3616 /// was already weakened by GC tracing), it skips the decrement.
3617 // The bool maps directly to the C++ FFI parameter; an enum would just
3618 // convert back to bool immediately before crossing the boundary.
3619 #[expect(
3620 clippy::fn_params_excessive_bools,
3621 reason = "thin wrapper over FFI; bool is dictated by the C++ interface"
3622 )]
3623 pub(crate) fn remove_strong_ref(&mut self, is_strong: bool) {
3624 // SAFETY: wrappable is valid (guaranteed by KjRc lifetime).
3625 unsafe { ffi::wrappable_remove_strong_ref(self.as_pin_mut(), is_strong) };
3626 }
3627 
3628 /// Returns the current strong reference count from the C++ Wrappable.
3629 #[cfg(debug_assertions)]
3630 pub(crate) fn strong_refcount(&self) -> u32 {
3631 // SAFETY: wrappable pointer is valid (guaranteed by KjRc lifetime).
3632 unsafe { ffi::wrappable_strong_refcount(&*self.handle.get()) }
3633 }
3634 
3635 /// Resolves the `Rc::into_raw` pointer stored in the Wrappable as a typed `NonNull<R>`.
3636 ///
3637 /// Returns `None` if the trait object has been cleared or the stored `TypeId`
3638 /// does not match `R`, preventing type confusion in all builds.
3639 #[doc(hidden)]
3640 pub fn resolve_resource<R: Resource>(&self) -> Option<NonNull<R>> {
3641 // SAFETY: wrappable pointer is valid (guaranteed by KjRc lifetime).
3642 let trait_ptr = unsafe {
3643 let wrappable = &*self.handle.get();
3644 ffi::TraitObjectPtr::from_wrappable(wrappable)?
3645 };
3646 
3647 if trait_ptr.type_id() != std::any::TypeId::of::<R>() {
3648 return None;
3649 }
3650 
3651 // SAFETY: TypeId matched, so data_ptr is a valid Rc::into_raw pointer to R.
3652 Some(unsafe { trait_ptr.data_as::<R>() })
3653 }
3654}