diff --git a/CLAUDE.md b/CLAUDE.md index e9c1f3b71f..b75fb9fed1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,7 +8,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co Perry is a native TypeScript compiler written in Rust that compiles TypeScript source code directly to native executables. It uses SWC for TypeScript parsing and LLVM for code generation. -**Current Version:** 0.5.1412 +**Current Version:** 0.5.1413 ## TypeScript Parity Status diff --git a/Cargo.lock b/Cargo.lock index 15c646074f..39a18ad498 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5547,7 +5547,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "perry" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "anyhow", "base64", @@ -5607,14 +5607,14 @@ dependencies = [ [[package]] name = "perry-api-manifest" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "serde", ] [[package]] name = "perry-audio-miniaudio" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "cc", "libc", @@ -5622,7 +5622,7 @@ dependencies = [ [[package]] name = "perry-codegen" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "anyhow", "inkwell", @@ -5639,7 +5639,7 @@ dependencies = [ [[package]] name = "perry-codegen-arkts" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "anyhow", "perry-hir", @@ -5647,7 +5647,7 @@ dependencies = [ [[package]] name = "perry-codegen-glance" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "anyhow", "perry-hir", @@ -5655,7 +5655,7 @@ dependencies = [ [[package]] name = "perry-codegen-js" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "anyhow", "perry-dispatch", @@ -5664,7 +5664,7 @@ dependencies = [ [[package]] name = "perry-codegen-swiftui" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "anyhow", "perry-hir", @@ -5672,7 +5672,7 @@ dependencies = [ [[package]] name = "perry-codegen-wasm" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "anyhow", "base64", @@ -5684,7 +5684,7 @@ dependencies = [ [[package]] name = "perry-codegen-wear-tiles" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "anyhow", "perry-hir", @@ -5692,7 +5692,7 @@ dependencies = [ [[package]] name = "perry-container-compose" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "anyhow", "async-trait", @@ -5721,14 +5721,14 @@ dependencies = [ [[package]] name = "perry-container-e2e" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "anyhow", ] [[package]] name = "perry-diagnostics" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "serde", "serde_json", @@ -5736,7 +5736,7 @@ dependencies = [ [[package]] name = "perry-dispatch" -version = "0.5.1412" +version = "0.5.1413" [[package]] name = "perry-doc-fixture-my-bindings" @@ -5747,7 +5747,7 @@ dependencies = [ [[package]] name = "perry-doc-tests" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "anyhow", "clap", @@ -5762,7 +5762,7 @@ dependencies = [ [[package]] name = "perry-ext-ads" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "block2", "objc2", @@ -5772,7 +5772,7 @@ dependencies = [ [[package]] name = "perry-ext-argon2" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "argon2", "perry-ffi", @@ -5780,7 +5780,7 @@ dependencies = [ [[package]] name = "perry-ext-axios" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "perry-ffi", "reqwest", @@ -5789,7 +5789,7 @@ dependencies = [ [[package]] name = "perry-ext-bcrypt" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "bcrypt", "perry-ffi", @@ -5797,7 +5797,7 @@ dependencies = [ [[package]] name = "perry-ext-better-sqlite3" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "perry-ffi", "rusqlite", @@ -5805,7 +5805,7 @@ dependencies = [ [[package]] name = "perry-ext-cheerio" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "perry-ffi", "scraper", @@ -5813,7 +5813,7 @@ dependencies = [ [[package]] name = "perry-ext-commander" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "perry-ffi", "perry-runtime", @@ -5821,7 +5821,7 @@ dependencies = [ [[package]] name = "perry-ext-cron" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "chrono", "cron", @@ -5831,7 +5831,7 @@ dependencies = [ [[package]] name = "perry-ext-dayjs" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "chrono", "perry-ffi", @@ -5839,7 +5839,7 @@ dependencies = [ [[package]] name = "perry-ext-decimal" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "perry-ffi", "rust_decimal", @@ -5847,7 +5847,7 @@ dependencies = [ [[package]] name = "perry-ext-dotenv" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "perry-ffi", "serde_json", @@ -5855,7 +5855,7 @@ dependencies = [ [[package]] name = "perry-ext-ethers" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "perry-ffi", "rand 0.10.1", @@ -5863,7 +5863,7 @@ dependencies = [ [[package]] name = "perry-ext-events" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "perry-ffi", "perry-runtime", @@ -5871,14 +5871,14 @@ dependencies = [ [[package]] name = "perry-ext-exponential-backoff" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "perry-ffi", ] [[package]] name = "perry-ext-fastify" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "bytes", "http-body-util", @@ -5896,7 +5896,7 @@ dependencies = [ [[package]] name = "perry-ext-fetch" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "bytes", "lazy_static", @@ -5909,7 +5909,7 @@ dependencies = [ [[package]] name = "perry-ext-http" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "bytes", "h2", @@ -5933,7 +5933,7 @@ dependencies = [ [[package]] name = "perry-ext-ioredis" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "lazy_static", "perry-ffi", @@ -5943,7 +5943,7 @@ dependencies = [ [[package]] name = "perry-ext-jsonwebtoken" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "base64", "jsonwebtoken", @@ -5954,7 +5954,7 @@ dependencies = [ [[package]] name = "perry-ext-lru-cache" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "lru", "perry-ffi", @@ -5963,7 +5963,7 @@ dependencies = [ [[package]] name = "perry-ext-moment" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "chrono", "perry-ffi", @@ -5971,7 +5971,7 @@ dependencies = [ [[package]] name = "perry-ext-mongodb" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "bson", "futures-util", @@ -5983,7 +5983,7 @@ dependencies = [ [[package]] name = "perry-ext-mysql2" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "chrono", "perry-ffi", @@ -5993,7 +5993,7 @@ dependencies = [ [[package]] name = "perry-ext-nanoid" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "nanoid", "perry-ffi", @@ -6002,7 +6002,7 @@ dependencies = [ [[package]] name = "perry-ext-net" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "bytes", "perry-ffi", @@ -6015,7 +6015,7 @@ dependencies = [ [[package]] name = "perry-ext-node-forge" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "const-oid 0.9.6", "der 0.7.10", @@ -6034,7 +6034,7 @@ dependencies = [ [[package]] name = "perry-ext-nodemailer" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "lettre", "perry-ffi", @@ -6044,7 +6044,7 @@ dependencies = [ [[package]] name = "perry-ext-pdf" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "perry-ffi", "printpdf", @@ -6052,7 +6052,7 @@ dependencies = [ [[package]] name = "perry-ext-pg" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "perry-ffi", "sqlx", @@ -6061,7 +6061,7 @@ dependencies = [ [[package]] name = "perry-ext-ratelimit" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "governor", "perry-ffi", @@ -6069,7 +6069,7 @@ dependencies = [ [[package]] name = "perry-ext-sharp" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "fast_image_resize", "image", @@ -6079,14 +6079,14 @@ dependencies = [ [[package]] name = "perry-ext-slugify" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "perry-ffi", ] [[package]] name = "perry-ext-streams" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "lazy_static", "perry-ffi", @@ -6095,7 +6095,7 @@ dependencies = [ [[package]] name = "perry-ext-undici" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "perry-ffi", "perry-runtime", @@ -6104,7 +6104,7 @@ dependencies = [ [[package]] name = "perry-ext-uuid" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "perry-ffi", "uuid", @@ -6112,7 +6112,7 @@ dependencies = [ [[package]] name = "perry-ext-validator" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "perry-ffi", "regex", @@ -6122,7 +6122,7 @@ dependencies = [ [[package]] name = "perry-ext-ws" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "futures-util", "lazy_static", @@ -6135,7 +6135,7 @@ dependencies = [ [[package]] name = "perry-ext-zlib" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "brotli", "flate2", @@ -6145,7 +6145,7 @@ dependencies = [ [[package]] name = "perry-ffi" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "dashmap", "once_cell", @@ -6154,7 +6154,7 @@ dependencies = [ [[package]] name = "perry-hir" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "anyhow", "perry-api-manifest", @@ -6172,7 +6172,7 @@ dependencies = [ [[package]] name = "perry-parser" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "anyhow", "perry-diagnostics", @@ -6184,7 +6184,7 @@ dependencies = [ [[package]] name = "perry-runtime" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "anyhow", "base64", @@ -6226,14 +6226,14 @@ dependencies = [ [[package]] name = "perry-runtime-static" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "perry-runtime", ] [[package]] name = "perry-stdlib" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "aes 0.8.4", "aes 0.9.1", @@ -6328,14 +6328,14 @@ dependencies = [ [[package]] name = "perry-stdlib-static" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "perry-stdlib", ] [[package]] name = "perry-transform" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "anyhow", "perry-hir", @@ -6344,14 +6344,14 @@ dependencies = [ [[package]] name = "perry-ui" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "perry-ui-model", ] [[package]] name = "perry-ui-android" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "base64", "itoa", @@ -6368,7 +6368,7 @@ dependencies = [ [[package]] name = "perry-ui-geisterhand" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "rand 0.10.1", "serde", @@ -6378,7 +6378,7 @@ dependencies = [ [[package]] name = "perry-ui-gtk4" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "base64", "cairo-rs 0.22.0", @@ -6401,7 +6401,7 @@ dependencies = [ [[package]] name = "perry-ui-ios" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "base64", "block2", @@ -6417,7 +6417,7 @@ dependencies = [ [[package]] name = "perry-ui-macos" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "base64", "block2", @@ -6432,7 +6432,7 @@ dependencies = [ [[package]] name = "perry-ui-model" -version = "0.5.1412" +version = "0.5.1413" [[package]] name = "perry-ui-test" @@ -6443,11 +6443,11 @@ dependencies = [ [[package]] name = "perry-ui-testkit" -version = "0.5.1412" +version = "0.5.1413" [[package]] name = "perry-ui-tvos" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "base64", "block2", @@ -6463,7 +6463,7 @@ dependencies = [ [[package]] name = "perry-ui-visionos" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "base64", "block2", @@ -6479,7 +6479,7 @@ dependencies = [ [[package]] name = "perry-ui-watchos" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "block2", "libc", @@ -6492,7 +6492,7 @@ dependencies = [ [[package]] name = "perry-ui-windows" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "base64", "libc", @@ -6509,14 +6509,14 @@ dependencies = [ [[package]] name = "perry-ui-windows-winui" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "perry-ui-windows", ] [[package]] name = "perry-updater" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "anyhow", "base64", @@ -6532,7 +6532,7 @@ dependencies = [ [[package]] name = "perry-wasm-host" -version = "0.5.1412" +version = "0.5.1413" dependencies = [ "wasmi", ] diff --git a/Cargo.toml b/Cargo.toml index fcc328f707..59bf92ae68 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -315,7 +315,7 @@ codegen-units = 16 codegen-units = 16 [workspace.package] -version = "0.5.1412" +version = "0.5.1413" edition = "2021" license = "MIT" repository = "https://github.com/PerryTS/perry" diff --git a/changelog.d/7528-native-call-receiver-reread.md b/changelog.d/7528-native-call-receiver-reread.md new file mode 100644 index 0000000000..a2a14d8ef5 --- /dev/null +++ b/changelog.d/7528-native-call-receiver-reread.md @@ -0,0 +1,22 @@ +### Fixed + +- **`js_native_call_method` reused one copy of its rooted receiver across ~1160 lines of allocating probes (#7528).** The function *does* root the receiver — `object_handle` on its first lines — and then read it out exactly once: + + ```rust + let object = object_handle.get_nanbox_f64(); + let jsval = JSValue::from_bits(object.to_bits()); + ``` + + A value read out of a root and held in a local **is not rooted**: the root keeps the object alive and the collector rewrites the *slot*, not the copy (`docs/src/internals/gc-rooting-invariant.md`). Those two locals were then used across a dozen probes that allocate, so each one received a receiver address a moving collector may already have invalidated. + + The measured deref was the closure-magic probe: `is_closure_ptr(raw_addr)` on an address derived from the stale copy, faulting **5/5** under `PERRY_GC_PROTECT_FROMSPACE=1 PERRY_GC_PROTECT_FROMSPACE_DEPTH=200`, with lldb stopping on the magic read itself (`ldr w8, [x28, #0xc]`, `CLOSURE_MAGIC`). + + Not latent: `test_gap_gc_iterator_drain_rooting` printed `badLen 1` instead of `badLen 0` — one of 50,000 clones losing its `tags` array. + + Both are now closures, so every use is a fresh slot read. That is the point: it makes each of the 93 sites correct **by construction** rather than by an audit of which probes allocate — an audit that would have to be redone every time a line is added to a 1,168-line function. The cost is a slot load against a dispatch tower orders of magnitude more expensive, and the function already used this idiom for `refreshed_args`. + + **Verification status, stated plainly: the witness in #7528 no longer reproduces on `main`, so this fix is correct by construction rather than measured.** + + The A/B was run properly and both arms are clean — `checksum: 1249975000 badLen 0 badVal 0`, 5/5 on each. The first attempt at the base arm was *vacuous* and nearly reported as a result: `PERRY_GC_PROTECT_FROMSPACE=1` alone printed **zero** `[gc-fromspace-protect] retired_set=` lines, i.e. no copying minor ran and the quarantine protected nothing, so its clean exit meant nothing at all. With `PERRY_GC_MOVING_LOOP_POLLS=1` at compile time and `PERRY_GC_ZEAL=1` at run time the instrument arms (5 retired sets) and still does not fault. + + So one of #7516 / #7527 / #7529 / #7687 removed the observable, and what remains is the hazard itself: a value read out of a root and held across allocating calls is not rooted, which is an invariant (`docs/src/internals/gc-rooting-invariant.md`), not a judgement call. That class is invisible to every runtime GC probe by construction — at the moment of the collection there is nothing for the collector to find — which is exactly why it is fixed structurally instead of waiting for a witness to come back. diff --git a/crates/perry-runtime/src/object/native_call_method.rs b/crates/perry-runtime/src/object/native_call_method.rs index 4862ecf315..b2bafdc9b3 100644 --- a/crates/perry-runtime/src/object/native_call_method.rs +++ b/crates/perry-runtime/src/object/native_call_method.rs @@ -728,8 +728,23 @@ pub unsafe extern "C" fn js_native_call_method( }; let arg_handles = root_scope.root_nanbox_f64_slice(&original_args); let refreshed_args = || crate::gc::RuntimeHandleScope::refreshed_nanbox_f64_slice(&arg_handles); - let object = object_handle.get_nanbox_f64(); - let jsval = JSValue::from_bits(object.to_bits()); + // #7528: these are CLOSURES, not values, and that is the entire fix. + // + // `object_handle` roots the receiver, but a value READ OUT of a root and + // held in a local is not rooted -- the collector rewrites the SLOT, not the + // copy. This function then runs ~1160 more lines across a dozen probes that + // allocate, so a single `let object = object_handle.get_nanbox_f64()` at the + // top hands every one of them a receiver address a moving collector may + // already have invalidated. The measured deref was the closure-magic probe + // (`is_closure_ptr` -> `ldr w8, [x28, #0xc]`), faulting 5/5 under + // `PERRY_GC_PROTECT_FROMSPACE`. + // + // Re-reading per use makes each site correct by construction rather than by + // an audit of which probes allocate -- an audit that would have to be redone + // every time a line is added to this function. It is a slot load; the + // dispatch tower below it is orders of magnitude more expensive. + let object = || object_handle.get_nanbox_f64(); + let jsval = || JSValue::from_bits(object().to_bits()); // RAII recursion depth guard: prevent stack overflow from circular module deps. // The guard auto-decrements on drop, covering all ~20 return points in this function. // When max depth is hit, return a pointer to a static empty object instead of undefined. @@ -739,7 +754,7 @@ pub unsafe extern "C" fn js_native_call_method( None => { crate::object::class_registry::report_dispatch_miss( "call-method (recursion-depth guard)", - object, + object(), method_name, "empty object", ); @@ -760,7 +775,7 @@ pub unsafe extern "C" fn js_native_call_method( // keep using the shared object dispatch below; everything else is a genuine // module method whose result (incl. a legitimate `undefined`) is returned // directly — returning unconditionally avoids double-invoking a void method. - if jsval.is_pointer() + if jsval().is_pointer() && !method_name.starts_with("__perry_") && !matches!( method_name, @@ -773,7 +788,7 @@ pub unsafe extern "C" fn js_native_call_method( | "constructor" ) { - let ns_ptr = jsval.as_pointer::(); + let ns_ptr = jsval().as_pointer::(); // The POINTER_TAG payload reaching here can be a small registry handle // (zlib stream, fetch Request/Response, net.Socket, …) rather than a // heap address. `is_valid_obj_ptr` alone does NOT reject the handle @@ -806,7 +821,8 @@ pub unsafe extern "C" fn js_native_call_method( // and would fall through to "is not a function". Resolve the symbol-keyed // disposer here, with the spec async→sync fallback, before that happens. if matches!(method_name, "__perry_dispose__" | "__perry_async_dispose__") { - if let Some(result) = try_symbol_dispose_dispatch(object, method_name, args_ptr, args_len) { + if let Some(result) = try_symbol_dispose_dispatch(object(), method_name, args_ptr, args_len) + { return result; } } @@ -817,7 +833,7 @@ pub unsafe extern "C" fn js_native_call_method( if method_name == "__perry_using_check__" { let want_async = args_len > 0 && !args_ptr.is_null() && { crate::value::js_is_truthy(*args_ptr) != 0 }; - return js_using_check_disposable(object, want_async); + return js_using_check_disposable(object(), want_async); } // TextDecoder / TextEncoder registry handles on a type-erased receiver — // same wall class as the URLSearchParams / AbortSignal blocks below: the @@ -827,8 +843,8 @@ pub unsafe extern "C" fn js_native_call_method( // `get_field_by_name_tail.rs` reifies for `K.decode.bind(K)` — the shape // a minified SDK's cached decodeText helper takes) lands here, and the // generic field-scan would miss and throw "is not a function". - if matches!(method_name, "decode" | "encode" | "encodeInto") && jsval.is_pointer() { - let raw = (object.to_bits() & 0x0000_FFFF_FFFF_FFFF) as usize; + if matches!(method_name, "decode" | "encode" | "encodeInto") && jsval().is_pointer() { + let raw = (object().to_bits() & 0x0000_FFFF_FFFF_FFFF) as usize; if crate::value::addr_class::is_small_handle(raw) { let undef = f64::from_bits(crate::value::TAG_UNDEFINED); let arg0 = if args_len > 0 && !args_ptr.is_null() { @@ -837,7 +853,7 @@ pub unsafe extern "C" fn js_native_call_method( undef }; if method_name == "decode" && crate::text::is_known_text_decoder_id(raw as i64) { - let sp = crate::text::js_text_decoder_decode_llvm(object, arg0); + let sp = crate::text::js_text_decoder_decode_llvm(object(), arg0); return f64::from_bits( JSValue::string_ptr(sp as *mut crate::string::StringHeader).bits(), ); @@ -866,7 +882,7 @@ pub unsafe extern "C" fn js_native_call_method( // to the natives (shape-probed for the native form, hidden backing for the // subclass) before the generic field-scan misses. if let Some(result) = crate::url::search_params::try_url_search_params_dynamic_dispatch( - object, + object(), method_name, args_ptr, args_len, @@ -884,9 +900,9 @@ pub unsafe extern "C" fn js_native_call_method( if matches!( method_name, "addEventListener" | "removeEventListener" | "throwIfAborted" - ) && jsval.is_pointer() + ) && jsval().is_pointer() { - let recv_ptr = (object.to_bits() & 0x0000_FFFF_FFFF_FFFF) as *mut ObjectHeader; + let recv_ptr = (object().to_bits() & 0x0000_FFFF_FFFF_FFFF) as *mut ObjectHeader; // Skip native handles (nanbox-pointer-tagged small integer ids in the // low handle band) — dereferencing one as an `ObjectHeader` to read // `class_id` would fault. @@ -928,7 +944,7 @@ pub unsafe extern "C" fn js_native_call_method( "pop" | "shift" | "push" | "unshift" | "reverse" | "splice" | "sort" | "concat" ) { if let Some(result) = - crate::array::try_object_arraylike_mutator(object, method_name, args_ptr, args_len) + crate::array::try_object_arraylike_mutator(object(), method_name, args_ptr, args_len) { return result; } @@ -961,14 +977,14 @@ pub unsafe extern "C" fn js_native_call_method( | "join" | "slice" | "concat" - ) && crate::array::is_array_subclass_instance(object) + ) && crate::array::is_array_subclass_instance(object()) // Defer to a user override (own callable field of this name), matching // the own-slot gate in the mutator path. - && !crate::array::object_owns_user_method(object, method_name) + && !crate::array::object_owns_user_method(object(), method_name) { let args = refreshed_args(); if let Some(result) = crate::array::dispatch_arraylike_read_method( - object, + object(), method_name, args.as_ptr(), args.len(), @@ -1007,7 +1023,7 @@ pub unsafe extern "C" fn js_native_call_method( | "concat" ) { if let Some(result) = - crate::array::try_array_proto_chain_method(object, method_name, args_ptr, args_len) + crate::array::try_array_proto_chain_method(object(), method_name, args_ptr, args_len) { return result; } @@ -1031,17 +1047,17 @@ pub unsafe extern "C" fn js_native_call_method( | "@@__perry_wk_asyncDispose" ) { if let Some(result) = - try_disposable_stack_method_dispatch(object, method_name, args_ptr, args_len) + try_disposable_stack_method_dispatch(object(), method_name, args_ptr, args_len) { return result; } } { - let raw_addr = if jsval.is_pointer() { - crate::value::js_nanbox_get_pointer(object) as usize - } else if (object.to_bits() >> 48) == 0 { - object.to_bits() as usize + let raw_addr = if jsval().is_pointer() { + crate::value::js_nanbox_get_pointer(object()) as usize + } else if (object().to_bits() >> 48) == 0 { + object().to_bits() as usize } else { 0 }; @@ -1067,9 +1083,9 @@ pub unsafe extern "C" fn js_native_call_method( // win over IMPLICIT_THIS and leave `this` as the PROTO. let bound = crate::closure::clone_closure_rebind_this( dyn_val.to_bits(), - f64::from_bits(object.to_bits()), + f64::from_bits(object().to_bits()), ); - let prev_this = IMPLICIT_THIS.with(|c| c.replace(object.to_bits())); + let prev_this = IMPLICIT_THIS.with(|c| c.replace(object().to_bits())); let result = crate::closure::js_native_call_value(f64::from_bits(bound), args_ptr, args_len); IMPLICIT_THIS.with(|c| c.set(prev_this)); @@ -1100,19 +1116,22 @@ pub unsafe extern "C" fn js_native_call_method( // yield-star-* with `get next()`). `get_accessor_descriptor` is a cheap // keyed HashMap lookup (no deref), gated on the accessor hot-path flag so // non-accessor programs skip it entirely. - if jsval.is_pointer() && crate::state::state().descriptors.accessors_in_use.get() { - let obj_usize = crate::value::js_nanbox_get_pointer(object) as usize; + if jsval().is_pointer() && crate::state::state().descriptors.accessors_in_use.get() { + let obj_usize = crate::value::js_nanbox_get_pointer(object()) as usize; if crate::value::addr_class::is_above_handle_band(obj_usize) { if let Some(acc) = crate::object::get_accessor_descriptor(obj_usize, method_name) { if acc.get != 0 { let getter = (acc.get & crate::value::POINTER_MASK) as *const crate::closure::ClosureHeader; if !getter.is_null() { - let prev_getter_this = IMPLICIT_THIS.with(|c| c.replace(object.to_bits())); + let prev_getter_this = + IMPLICIT_THIS.with(|c| c.replace(object().to_bits())); let method_fn = crate::closure::js_closure_call0(getter); - let bound = - crate::closure::clone_closure_rebind_this(method_fn.to_bits(), object); - IMPLICIT_THIS.with(|c| c.set(object.to_bits())); + let bound = crate::closure::clone_closure_rebind_this( + method_fn.to_bits(), + object(), + ); + IMPLICIT_THIS.with(|c| c.set(object().to_bits())); let result = crate::closure::js_native_call_value( f64::from_bits(bound), args_ptr, @@ -1127,13 +1146,19 @@ pub unsafe extern "C" fn js_native_call_method( } // Check if this is a JS handle (V8 object from JS runtime) - if crate::value::is_js_handle(object) { + if crate::value::is_js_handle(object()) { let func_ptr = crate::value::JS_HANDLE_CALL_METHOD.load(std::sync::atomic::Ordering::SeqCst); if !func_ptr.is_null() { let func: unsafe extern "C" fn(f64, *const i8, usize, *const f64, usize) -> f64 = std::mem::transmute(func_ptr); - let result = func(object, method_name_ptr, method_name_len, args_ptr, args_len); + let result = func( + object(), + method_name_ptr, + method_name_len, + args_ptr, + args_len, + ); return result; } // No JS-handle dispatcher: return JS `undefined`. The literal must be @@ -1159,7 +1184,7 @@ pub unsafe extern "C" fn js_native_call_method( // Mirror the spec: `Get(proxy, "method")` (honors the get trap / forwards // through the target's prototype chain) then `Call(method, proxy, args)` // with `this` bound to the proxy itself. - if crate::proxy::js_proxy_is_proxy(object) == 1 { + if crate::proxy::js_proxy_is_proxy(object()) == 1 { // #5196: a generic, non-mutating `Array.prototype` method on a Proxy // (`proxyArray.map(fn)`). `Array.prototype.map` etc. iterate `this` // through `[[Get]]`/`length`; routing the spec-generic engine over the @@ -1171,7 +1196,7 @@ pub unsafe extern "C" fn js_native_call_method( // plain array-like objects whose prototype chain holds a real array. let args = refreshed_args(); if let Some(result) = crate::array::dispatch_arraylike_read_method( - object, + object(), method_name, args.as_ptr(), args.len(), @@ -1207,7 +1232,7 @@ pub unsafe extern "C" fn js_native_call_method( &root_scope, &object_handle, &arg_handles, - object, + object(), method_name, method_name_ptr, method_name_len, @@ -1221,7 +1246,7 @@ pub unsafe extern "C" fn js_native_call_method( &root_scope, &object_handle, &arg_handles, - object, + object(), method_name, method_name_ptr, method_name_len, @@ -1235,7 +1260,7 @@ pub unsafe extern "C" fn js_native_call_method( &root_scope, &object_handle, &arg_handles, - object, + object(), method_name, method_name_ptr, method_name_len, @@ -1249,7 +1274,7 @@ pub unsafe extern "C" fn js_native_call_method( &root_scope, &object_handle, &arg_handles, - object, + object(), method_name, method_name_ptr, method_name_len, @@ -1263,7 +1288,7 @@ pub unsafe extern "C" fn js_native_call_method( &root_scope, &object_handle, &arg_handles, - object, + object(), method_name, method_name_ptr, method_name_len, @@ -1277,7 +1302,7 @@ pub unsafe extern "C" fn js_native_call_method( &root_scope, &object_handle, &arg_handles, - object, + object(), method_name, method_name_ptr, method_name_len, @@ -1289,8 +1314,8 @@ pub unsafe extern "C" fn js_native_call_method( // If it's an object with a method stored as a closure in a field, // try to find and call it - if jsval.is_pointer() { - let obj = jsval.as_pointer::(); + if jsval().is_pointer() { + let obj = jsval().as_pointer::(); // Validate this is an ObjectHeader, not some other heap type. // Check GcHeader first (reliable for heap objects), then fallback to ObjectHeader.object_type @@ -1333,7 +1358,7 @@ pub unsafe extern "C" fn js_native_call_method( if is_closure { let dyn_val = crate::closure::closure_get_dynamic_prop(obj as usize, method_name); if dyn_val.to_bits() != crate::value::TAG_UNDEFINED { - let recv_bits = jsval.bits(); + let recv_bits = jsval().bits(); // #6438: `closure_get_dynamic_prop` also walks the closure's // `Object.setPrototypeOf` chain, so `dyn_val` may be a method // read off the PROTO object — and an object-literal method @@ -1392,7 +1417,7 @@ pub unsafe extern "C" fn js_native_call_method( && gc_type == crate::gc::GC_TYPE_OBJECT && crate::perf_hooks::is_perf_entry_object(obj) { - return crate::perf_hooks::perf_entry_to_json(object); + return crate::perf_hooks::perf_entry_to_json(object()); } // WeakMap/WeakSet dynamic method dispatch (issue #1757/#1758): these @@ -1402,7 +1427,7 @@ pub unsafe extern "C" fn js_native_call_method( // add to the js_weak* helpers instead of throwing "has is not a // function". The class_id guard + routing live in weakref.rs. if let Some(r) = - crate::weakref::try_weak_method_dispatch(obj, object, method_name, args_ptr, args_len) + crate::weakref::try_weak_method_dispatch(obj, object(), method_name, args_ptr, args_len) { return r; } @@ -1488,9 +1513,9 @@ pub unsafe extern "C" fn js_native_call_method( let field_val = js_object_get_field(obj as *mut _, i as u32); let bound = crate::closure::clone_closure_rebind_this( field_val.bits(), - f64::from_bits(jsval.bits()), + f64::from_bits(jsval().bits()), ); - let prev_this = IMPLICIT_THIS.with(|c| c.replace(jsval.bits())); + let prev_this = IMPLICIT_THIS.with(|c| c.replace(jsval().bits())); let result = crate::closure::js_native_call_value( f64::from_bits(bound), args_ptr, @@ -1511,9 +1536,9 @@ pub unsafe extern "C" fn js_native_call_method( if !field_val.is_undefined() && !field_val.is_null() { let bound = crate::closure::clone_closure_rebind_this( field_val.bits(), - f64::from_bits(jsval.bits()), + f64::from_bits(jsval().bits()), ); - let prev_this = IMPLICIT_THIS.with(|c| c.replace(jsval.bits())); + let prev_this = IMPLICIT_THIS.with(|c| c.replace(jsval().bits())); let result = crate::closure::js_native_call_value( f64::from_bits(bound), args_ptr, @@ -1532,7 +1557,7 @@ pub unsafe extern "C" fn js_native_call_method( if let Some(ref reg) = *registry { if let Some(vtable) = reg.get(&class_id) { if let Some(entry) = vtable.methods.get(method_name) { - let this_i64 = jsval.as_pointer::() as i64; + let this_i64 = jsval().as_pointer::() as i64; return call_vtable_method( entry.func_ptr, this_i64, @@ -1586,8 +1611,8 @@ pub unsafe extern "C" fn js_native_call_method( // Perry code that calls those on `undefined`/`null` keeps working // (Perry-ism — Node throws there too, but tightening that breaks // unrelated callers; the typo case below is what we want to surface). - if jsval.is_undefined() || jsval.is_null() { - let is_null_u32 = if jsval.is_null() { 1u32 } else { 0u32 }; + if jsval().is_undefined() || jsval().is_null() { + let is_null_u32 = if jsval().is_null() { 1u32 } else { 0u32 }; crate::error::js_throw_type_error_property_access( is_null_u32, method_name.as_ptr(), @@ -1607,8 +1632,8 @@ pub unsafe extern "C" fn js_native_call_method( // threw `(number). is not a function`. Paired with the // codegen-side fix in `lower_call.rs` for the simpler // `ClassRef.method()` shape. - if jsval.is_int32() { - let payload = jsval.as_int32() as u32; + if jsval().is_int32() { + let payload = jsval().as_int32() as u32; if payload != 0 { let guard = REGISTERED_CLASS_IDS.read().unwrap(); if let Some(set) = guard.as_ref() { @@ -1632,7 +1657,7 @@ pub unsafe extern "C" fn js_native_call_method( } } } - return object; + return object(); } } } @@ -1644,13 +1669,13 @@ pub unsafe extern "C" fn js_native_call_method( // rebox as a POINTER_TAG value and dispatch on the object it actually is. // Perry already recovers bare pointers on other dispatch paths; this one threw // before it ever got the chance. - if jsval.is_number() && (jsval.bits() >> 48) == 0 { - let raw = jsval.bits() as usize; + if jsval().is_number() && (jsval().bits() >> 48) == 0 { + let raw = jsval().bits() as usize; if crate::value::addr_class::is_above_handle_band(raw) && crate::value::addr_class::is_valid_obj_ptr(raw as *const u8) { let reboxed = crate::value::JSValue::pointer(raw as *const u8); - if reboxed.bits() != jsval.bits() { + if reboxed.bits() != jsval().bits() { return js_native_call_method( f64::from_bits(reboxed.bits()), method_name_ptr, @@ -1661,13 +1686,13 @@ pub unsafe extern "C" fn js_native_call_method( } } } - let primitive_kind: Option<&'static str> = if jsval.is_any_string() { + let primitive_kind: Option<&'static str> = if jsval().is_any_string() { Some("string") - } else if jsval.is_int32() || jsval.is_number() { + } else if jsval().is_int32() || jsval().is_number() { Some("number") - } else if jsval.is_bool() { + } else if jsval().is_bool() { Some("boolean") - } else if jsval.is_bigint() { + } else if jsval().is_bigint() { Some("bigint") } else { None @@ -1682,7 +1707,7 @@ pub unsafe extern "C" fn js_native_call_method( }; if let Some(name) = builtin_name { if let Some(result) = call_primitive_builtin_prototype_method( - object, + object(), name, method_name, args_ptr, @@ -1731,9 +1756,9 @@ pub unsafe extern "C" fn js_native_call_method( if matches!( method_name, "text" | "json" | "arrayBuffer" | "blob" | "bytes" | "formData" | "clone" - ) && jsval.is_pointer() + ) && jsval().is_pointer() { - let raw = crate::value::js_nanbox_get_pointer(object) as usize; + let raw = crate::value::js_nanbox_get_pointer(object()) as usize; if let Some(id) = crate::object::fetch_subclass_handle_id(raw) { if let Some(dispatch) = handle_method_dispatch() { let args = refreshed_args(); @@ -1755,11 +1780,11 @@ pub unsafe extern "C" fn js_native_call_method( // the reified thunk unwraps the backing cell and species-chains via // `receiver.constructor`. (Covers the `X.resolve().finally().then()` chains // that codegen dispatches straight through `js_native_call_method`.) - if jsval.is_pointer() && matches!(method_name, "then" | "catch" | "finally") { - if crate::promise::subclass_backing_promise(object).is_some() { + if jsval().is_pointer() && matches!(method_name, "then" | "catch" | "finally") { + if crate::promise::subclass_backing_promise(object()).is_some() { if let Some(m) = crate::promise::promise_proto_method(method_name) { let args = refreshed_args(); - let prev_this = crate::object::js_implicit_this_set(object); + let prev_this = crate::object::js_implicit_this_set(object()); let result = crate::closure::js_native_call_value(m, args.as_ptr(), args.len()); crate::object::js_implicit_this_set(prev_this); return result; @@ -1774,8 +1799,8 @@ pub unsafe extern "C" fn js_native_call_method( // only genuinely inherited Temporal methods reach this forward. Route them // to the stashed cell (`temporal_subclass_cell`). (#5587) #[cfg(feature = "temporal")] - if jsval.is_pointer() { - let raw = crate::value::js_nanbox_get_pointer(object) as usize; + if jsval().is_pointer() { + let raw = crate::value::js_nanbox_get_pointer(object()) as usize; if let Some(cell) = crate::object::temporal_subclass_cell(raw) { let args = refreshed_args(); return crate::temporal::dispatch::call_method(cell, method_name, &args); @@ -1788,7 +1813,7 @@ pub unsafe extern "C" fn js_native_call_method( // `server.listen(...)` / `server.on(...)` on the plain-object `this` // behave as calls on the underlying server. See native_this_alias.rs. if super::native_this_alias::alias_active() { - if let Some(handle_val) = super::native_this_alias::alias_handle_for_object(object) { + if let Some(handle_val) = super::native_this_alias::alias_handle_for_object(object()) { // Dispatch through the PRIMARY handle dispatcher only: the alias // handle is known to be an http(s) server handle, and the // composite's extension dispatchers (ext-net) may own an @@ -1818,12 +1843,12 @@ pub unsafe extern "C" fn js_native_call_method( // never sees them. Look the name up there; if it is a callable, invoke it // with the receiver bound as `this` (via IMPLICIT_THIS, matching the // closure-field dispatch path above). - if jsval.is_pointer() { - if let Some((addr, kind)) = super::exotic_expando::exotic_expando_kind_of_value(object) { + if jsval().is_pointer() { + if let Some((addr, kind)) = super::exotic_expando::exotic_expando_kind_of_value(object()) { if let Some(bits) = super::exotic_expando::value_lookup(kind, addr, method_name) { let candidate = f64::from_bits(bits); if crate::collection_iter::is_callable(candidate) { - let prev_this = IMPLICIT_THIS.with(|c| c.replace(object.to_bits())); + let prev_this = IMPLICIT_THIS.with(|c| c.replace(object().to_bits())); let result = crate::closure::js_native_call_value(candidate, args_ptr, args_len); IMPLICIT_THIS.with(|c| c.set(prev_this)); @@ -1841,7 +1866,7 @@ pub unsafe extern "C" fn js_native_call_method( // → #5931). Runs LAST, after every own-field / vtable / prototype-chain // lookup above, so a subclass that OVERRIDES one of these names keeps its // own method; only a genuine miss on a real event target reaches this. - if jsval.is_pointer() + if jsval().is_pointer() && crate::event_target::is_event_target_method_name(method_name.as_bytes()) { // Re-read the receiver from its root handle instead of reusing the @@ -1862,7 +1887,7 @@ pub unsafe extern "C" fn js_native_call_method( crate::object::class_registry::report_dispatch_miss( "call-method (no method/field/proto match)", - object, + object(), method_name, "throws \" is not a function\"", );