From ae376416a336dbb6408815e10e0ed531f630ee21 Mon Sep 17 00:00:00 2001 From: divybot Date: Wed, 20 May 2026 14:55:25 +0000 Subject: [PATCH 1/5] Require &'static [CFunction] in FunctionBuilder::build_fast Upstream crrev.com/c/7828135 (slated for V8 15) changes FunctionTemplateInfo to store the raw v8::CFunction pointers passed to NewWithCFunctionOverloads directly, rather than copying them into a Managed heap object. The pointed-to storage must therefore outlive every FunctionTemplate that references it - in practice this means it has to be 'static, since a FunctionTemplate can live until isolate disposal. Enforce the invariant at the Rust type level so embedders cannot pass stack-local overload storage: * FunctionBuilder::build_fast now requires &'static [CFunction]. * CFunction::new now requires &'static CFunctionInfo so the type_info pointer it caches cannot dangle. * CFunctionInfo::new now requires &'static [CTypeInfo] for arg_info for the same reason - the constructed CFunctionInfo caches arg_info.as_ptr(). The existing call sites in tests/test_api.rs and benches/function.rs already use the canonical pattern - const FAST: CFunction = ...; build_fast( scope, &[FAST]) - which Rust static-promotes to &'static [CFunction; 1], so no test changes are required. A new trybuild compile_fail test (tests/compile_fail/build_fast_local_overloads.rs) demonstrates that a runtime-built Vec is now rejected with E0597. Refs denoland/rusty_v8#1989 Closes denoland/orchid#160 Co-Authored-By: Divy Srivastava --- src/fast_api.rs | 38 ++++++++++++++++++- src/template.rs | 23 ++++++++++- .../build_fast_local_overloads.rs | 31 +++++++++++++++ .../build_fast_local_overloads.stderr | 12 ++++++ 4 files changed, 101 insertions(+), 3 deletions(-) create mode 100644 tests/compile_fail/build_fast_local_overloads.rs create mode 100644 tests/compile_fail/build_fast_local_overloads.stderr diff --git a/src/fast_api.rs b/src/fast_api.rs index 9849d33879..e4831b1a07 100644 --- a/src/fast_api.rs +++ b/src/fast_api.rs @@ -11,7 +11,35 @@ use std::ptr::NonNull; pub struct CFunction(v8__CFunction); impl CFunction { - pub const fn new(address: *const c_void, type_info: &CFunctionInfo) -> Self { + /// Construct a `CFunction` from a function address and its type info. + /// + /// `type_info` is borrowed for `'static` because the resulting `CFunction` + /// stores the raw `v8::CFunctionInfo*` pointer and, once handed to V8 via + /// [`FunctionBuilder::build_fast`], that pointer is retained for the + /// lifetime of the `FunctionTemplate` (and copied into each fast-call + /// dispatch site). Anything other than `'static` storage for the type info + /// would let the pointer dangle. + /// + /// In practice this is achieved by constructing the `CFunctionInfo` inside + /// a `const` initializer (where reference-to-temporary rvalues are promoted + /// to `'static`), exactly as the existing test/bench patterns do: + /// + /// ```ignore + /// const FAST_CALL: v8::fast_api::CFunction = v8::fast_api::CFunction::new( + /// my_fast_fn as _, + /// &v8::fast_api::CFunctionInfo::new( + /// v8::fast_api::Type::Void.as_info(), + /// &[v8::fast_api::Type::V8Value.as_info()], + /// v8::fast_api::Int64Representation::Number, + /// ), + /// ); + /// ``` + /// + /// [`FunctionBuilder::build_fast`]: crate::FunctionBuilder::build_fast + pub const fn new( + address: *const c_void, + type_info: &'static CFunctionInfo, + ) -> Self { Self(v8__CFunction { address_: address, type_info_: &type_info.0, @@ -38,9 +66,15 @@ impl CFunctionInfo { /// |arg_info| is an array of |arg_count| CTypeInfos describing the /// arguments. Only the last argument may be of the special type /// CTypeInfo::kCallbackOptionsType. + /// + /// `arg_info` is borrowed for `'static` because the resulting + /// `CFunctionInfo` stores the raw `arg_info.as_ptr()` pointer and V8 reads + /// it on every fast-call invocation through the `CFunction` chain. As with + /// [`CFunction::new`], `const` initializers make this trivial: rvalues + /// such as `&[Type::V8Value.as_info()]` are promoted to `'static` storage. pub const fn new( return_info: CTypeInfo, - arg_info: &[CTypeInfo], + arg_info: &'static [CTypeInfo], repr: Int64Representation, ) -> Self { Self(v8__CFunctionInfo { diff --git a/src/template.rs b/src/template.rs index 387bdcbb78..5e956af004 100644 --- a/src/template.rs +++ b/src/template.rs @@ -702,10 +702,31 @@ impl<'s> FunctionBuilder<'s, FunctionTemplate> { /// useful to pass them explicitly - eg. when you are snapshotting you'd provide /// the overloads and `CFunctionInfo` that would be placed in the external /// references array. + /// + /// # Lifetime invariant + /// + /// `overloads` is `&'static [CFunction]` because, since + /// [crrev.com/c/7828135], V8 stores the raw `v8::CFunction` pointers it + /// receives via `NewWithCFunctionOverloads` directly inside + /// `FunctionTemplateInfo` (rather than copying them into a managed heap + /// object). The pointed-to storage must therefore outlive every + /// `FunctionTemplate` that references it. A `FunctionTemplate` may live + /// until isolate disposal, so in practice this requires the slice (and its + /// elements, including the `CFunctionInfo`s they reference) to have + /// `'static` storage. Use a `const` slice such as + /// + /// ```ignore + /// const OVERLOADS: &[v8::fast_api::CFunction] = &[FAST_TEST]; + /// ``` + /// + /// or a `static` item; do **not** synthesize the slice from stack-local + /// data. + /// + /// [crrev.com/c/7828135]: https://chromium-review.googlesource.com/c/v8/v8/+/7828135 pub fn build_fast<'i>( self, scope: &PinScope<'s, 'i>, - overloads: &[CFunction], + overloads: &'static [CFunction], ) -> Local<'s, FunctionTemplate> { unsafe { scope.cast_local(|sd| { diff --git a/tests/compile_fail/build_fast_local_overloads.rs b/tests/compile_fail/build_fast_local_overloads.rs new file mode 100644 index 0000000000..808102bcfe --- /dev/null +++ b/tests/compile_fail/build_fast_local_overloads.rs @@ -0,0 +1,31 @@ +// Copyright 2019-2026 the Deno authors. All rights reserved. MIT license. +// +// Regression test for denoland/rusty_v8#1989 (Orchid #160). +// +// Since crrev.com/c/7828135, V8 stores the raw `v8::CFunction` pointers +// passed to `NewWithCFunctionOverloads` directly inside `FunctionTemplateInfo` +// instead of copying them into a managed heap object. The pointed-to storage +// must therefore outlive the resulting `FunctionTemplate`, which in practice +// means it must be `'static`. `FunctionBuilder::build_fast` enforces this at +// the type level by requiring `&'static [v8::fast_api::CFunction]`, so a +// stack-local `Vec` of overloads must be rejected. + +fn slow_fn( + _: &mut v8::PinScope, + _: v8::FunctionCallbackArguments, + _: v8::ReturnValue, +) { +} + +fn rejects_local_overloads(scope: &mut v8::PinScope<'_, '_>) { + let overloads: Vec = Vec::new(); + let _ = v8::FunctionTemplate::builder(slow_fn).build_fast(scope, &overloads); +} + +pub fn main() { + rejects_local_overloads(mock()); +} + +fn mock() -> T { + unimplemented!() +} diff --git a/tests/compile_fail/build_fast_local_overloads.stderr b/tests/compile_fail/build_fast_local_overloads.stderr new file mode 100644 index 0000000000..0ead906f29 --- /dev/null +++ b/tests/compile_fail/build_fast_local_overloads.stderr @@ -0,0 +1,12 @@ +error[E0597]: `overloads` does not live long enough + --> tests/compile_fail/build_fast_local_overloads.rs:22:68 + | +21 | let overloads: Vec = Vec::new(); + | --------- binding `overloads` declared here +22 | let _ = v8::FunctionTemplate::builder(slow_fn).build_fast(scope, &overloads); + | ---------------------------------------------------------^^^^^^^^^^- + | | | + | | borrowed value does not live long enough + | argument requires that `overloads` is borrowed for `'static` +23 | } + | - `overloads` dropped here while still borrowed From 52dbe578b873ce91bdc54936ff3dd89116ccbfe7 Mon Sep 17 00:00:00 2001 From: divybot Date: Fri, 5 Jun 2026 09:22:22 +0530 Subject: [PATCH 2/5] Rolling to V8 15.0.245.2 --- README.md | 2 +- buildtools | 2 +- third_party/abseil-cpp | 2 +- third_party/libc++/src | 2 +- third_party/libunwind/src | 2 +- third_party/llvm-libc/src | 2 +- third_party/partition_alloc | 2 +- third_party/rust | 2 +- tools/auto_update_v8.ts | 2 +- tools/clang | 2 +- tools/win | 2 +- v8 | 2 +- 12 files changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index bd17bc29a4..937e45a73e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Rusty V8 Binding -V8 Version: 14.9.207.2 +V8 Version: 15.0.245.2 [![ci](https://github.com/denoland/rusty_v8/workflows/ci/badge.svg?branch=main)](https://github.com/denoland/rusty_v8/actions) [![crates](https://img.shields.io/crates/v/v8.svg)](https://crates.io/crates/v8) diff --git a/buildtools b/buildtools index c9110ba715..17495e454a 160000 --- a/buildtools +++ b/buildtools @@ -1 +1 @@ -Subproject commit c9110ba7150d93134b1c85ea392033b4a82d28f7 +Subproject commit 17495e454aae81b581e8b3caccbb53054509b280 diff --git a/third_party/abseil-cpp b/third_party/abseil-cpp index 5e42a36a85..d16e32215c 160000 --- a/third_party/abseil-cpp +++ b/third_party/abseil-cpp @@ -1 +1 @@ -Subproject commit 5e42a36a85a252d8cdee6c39661d2bfd9883fd5c +Subproject commit d16e32215c3ab90ba57c2e904a5344d85c7353e4 diff --git a/third_party/libc++/src b/third_party/libc++/src index 99457fa555..5abc7f8397 160000 --- a/third_party/libc++/src +++ b/third_party/libc++/src @@ -1 +1 @@ -Subproject commit 99457fa555797f8c5ac3c076ca288d8481d3b23a +Subproject commit 5abc7f839700f0f17338434e1c1c6a8c87c00c11 diff --git a/third_party/libunwind/src b/third_party/libunwind/src index a2530baf3d..d6c7a21e97 160000 --- a/third_party/libunwind/src +++ b/third_party/libunwind/src @@ -1 +1 @@ -Subproject commit a2530baf3d11013afd0f1c1941ab6bef5ba71d0a +Subproject commit d6c7a21e978f0adaa43accaad53bc64f0b64f6ec diff --git a/third_party/llvm-libc/src b/third_party/llvm-libc/src index cb952785cc..9309c117eb 160000 --- a/third_party/llvm-libc/src +++ b/third_party/llvm-libc/src @@ -1 +1 @@ -Subproject commit cb952785ccee13811f293f3c419958d1e3ddafbf +Subproject commit 9309c117ebae84dd2f9df1ef99de4782162527d5 diff --git a/third_party/partition_alloc b/third_party/partition_alloc index fafdd4c9f5..ff3b8b885b 160000 --- a/third_party/partition_alloc +++ b/third_party/partition_alloc @@ -1 +1 @@ -Subproject commit fafdd4c9f559c6d0cfdf2ed3170ce370b59bfdbf +Subproject commit ff3b8b885b8374cbd3902642d94dc737bda93d5d diff --git a/third_party/rust b/third_party/rust index 2b055f4eca..26e8ff47f1 160000 --- a/third_party/rust +++ b/third_party/rust @@ -1 +1 @@ -Subproject commit 2b055f4ecac78bbf34a0d34217c699b7b09b44dd +Subproject commit 26e8ff47f18a8d28d6187a04b6a16cb7332356f8 diff --git a/tools/auto_update_v8.ts b/tools/auto_update_v8.ts index 8627c4550e..11aef7486b 100644 --- a/tools/auto_update_v8.ts +++ b/tools/auto_update_v8.ts @@ -1,4 +1,4 @@ -const V8_TRACKING_BRANCH = "14.9-lkgr-denoland"; +const V8_TRACKING_BRANCH = "15.0-lkgr-denoland"; const AUTOROLL_BRANCH = "autoroll"; function extractVersion() { diff --git a/tools/clang b/tools/clang index 61150a5f1d..45f4b9e251 160000 --- a/tools/clang +++ b/tools/clang @@ -1 +1 @@ -Subproject commit 61150a5f1ddf6460bad3d896c1502c6a56e15311 +Subproject commit 45f4b9e25124809497a27a8ae0e63d603b0f9f1b diff --git a/tools/win b/tools/win index d16e6b55b2..faefd1b6fa 160000 --- a/tools/win +++ b/tools/win @@ -1 +1 @@ -Subproject commit d16e6b55b2bd699735919d8a13a55ff284086603 +Subproject commit faefd1b6fa9eeb033ad6fe60368ccb9bf908cbd0 diff --git a/v8 b/v8 index 73d1969899..944a94aa17 160000 --- a/v8 +++ b/v8 @@ -1 +1 @@ -Subproject commit 73d19698991616a34a00ca691a6e697dbb69e2ef +Subproject commit 944a94aa1735e029805d665a63681e20757fa731 From 926d558849aa7bf5c3688ddcf371eae48cffa2a7 Mon Sep 17 00:00:00 2001 From: divybot Date: Sat, 6 Jun 2026 04:39:49 +0530 Subject: [PATCH 3/5] Roll build submodule to V8-15-compatible chromium_build Advance the build submodule from fffa45f2 to 2aeedfc1: the denoland chromium_build patches re-applied onto upstream build b787aa66 (the commit V8 15.0.245.2 pins in DEPS). b787aa66's BUILDCONFIG.gn uses //buildtools/third_party/libc++:all_modules instead of the removed :headers target, fixing the 'gn gen' unresolved-dependency failure on the build-from-source CI variants (ptrcomp / asan / Windows cross-compile). See denoland/chromium_build#204. --- build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build b/build index fffa45f2e9..2aeedfc1aa 160000 --- a/build +++ b/build @@ -1 +1 @@ -Subproject commit fffa45f2e9df48c0230a4c799b7d12e7f83d8219 +Subproject commit 2aeedfc1aae7eca3852cfe921657bc62330baaf9 From b5fcb91b26d35b054efc515d0e902198240037fc Mon Sep 17 00:00:00 2001 From: divybot Date: Sat, 6 Jun 2026 05:28:22 +0530 Subject: [PATCH 4/5] Fix security_token test crash on V8 15 (Atomics.pause bootstrap) V8 15 ships Atomics.pause: Genesis::InitializeGlobal_js_atomics_pause now does a GetProperty(global, "Atomics") while bootstrapping every context. The security_token test installs a *masking* named-property interceptor on the child context's global that delegates all lookups to the parent global, so that bootstrap lookup returned the parent's Atomics (which already had pause), V8 reinstalled pause, and aborted with a duplicate-descriptor CHECK. Make the interceptor NON_MASKING so it only fires for properties absent on the global (just `variable`, which is all the test accesses). This skips already-present built-ins during bootstrap and avoids the collision while preserving the test's behavior. --- tests/test_api.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/test_api.rs b/tests/test_api.rs index 55d2ce774c..252a40c3eb 100644 --- a/tests/test_api.rs +++ b/tests/test_api.rs @@ -4703,6 +4703,17 @@ fn security_token() { let global = v8::Local::new(scope, global); templ.set_named_property_handler( v8::NamedPropertyHandlerConfiguration::new() + // NON_MASKING so the interceptor only fires for properties that are + // absent on the global (here just `variable`). Without it the handler + // would also intercept lookups of built-in globals, including those + // V8 performs while bootstrapping the context: since V8 15 shipped + // `Atomics.pause`, `Genesis::InitializeGlobal_js_atomics_pause` does a + // `GetProperty(global, "Atomics")` during context creation. A masking + // handler answers that with the *parent's* `Atomics` (which already + // has `pause`), so V8 reinstalls `pause` and aborts with a duplicate + // descriptor CHECK. NON_MASKING skips already-present properties and + // avoids the collision while preserving what this test exercises. + .flags(v8::PropertyHandlerFlags::NON_MASKING) .getter( |scope: &mut v8::PinScope, key: v8::Local, From 354fa649fbda938b34bbfd3567deed10a161c2ce Mon Sep 17 00:00:00 2001 From: divybot Date: Sat, 6 Jun 2026 15:40:33 +0530 Subject: [PATCH 5/5] Repin v8 submodule to 15.0-lkgr-denoland tip ac1e2398 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Same content as the previous pin 944a94aa (identical tree ff06e1a909291763a6f8526b44079551f037246a and byte-identical DEPS) — the denoland patch series was re-cherry-picked onto the same V8 15.0.245.2 base. Repinning to the branch tip so the submodule matches V8_TRACKING_BRANCH (15.0-lkgr-denoland) and tools/auto_update_v8.ts no longer sees the pin as diverged. No third_party changes. --- v8 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/v8 b/v8 index 944a94aa17..ac1e239891 160000 --- a/v8 +++ b/v8 @@ -1 +1 @@ -Subproject commit 944a94aa1735e029805d665a63681e20757fa731 +Subproject commit ac1e23989121713ca642f6650b34deff7b686896