Skip to content

chore: bump v8 crate to 152.2.0 - #36665

Open
bartlomieju wants to merge 5 commits into
mainfrom
chore/update-v8-152
Open

chore: bump v8 crate to 152.2.0#36665
bartlomieju wants to merge 5 commits into
mainfrom
chore/update-v8-152

Conversation

@bartlomieju

@bartlomieju bartlomieju commented Aug 22, 2026

Copy link
Copy Markdown
Member

Updates the v8 crate from 150.4.0 to 152.2.0, which brings V8 15.2.124.1.

Four things in the new version needed adapting to.

Global::open is now unsafe, because the reference it hands back is tied to
the lifetime of the Global rather than to a handle scope. Every call site in
this repo was of the same shape — open a Global and immediately call a method
on it — so rather than wrapping them in unsafe blocks they now go through
v8::Local::new(scope, handle), which is safe and is what upstream recommends.
That covers 25 sites in libs/core plus a handful in ext/webgpu and
ext/node.

MicrotaskQueue::new now returns an owning MicrotaskQueueHandle that frees
the queue on drop, where it previously returned a UniqueRef whose into_raw
leaked it. ext/node/ops/vm.rs relies on that leak: the queue is handed to a
context as a raw pointer and has to outlive it, and V8 offers no hook for when
the context goes away. The leak is now spelled out in a small helper with a
comment explaining why it is deliberate, instead of being implied by
into_raw.

The named and indexed setter and definer interceptors now take
ReturnValue<Boolean> instead of ReturnValue<()>, matching the deleter
interceptor, which already used Boolean. All the affected callbacks either
ignore the return value or forward it unchanged, so this is a type change only.

Finally, V8 removed asm.js validation and with it the --no-validate-asm flag.
This one is worth calling out: the flag sat first in base_flags, and V8 stops
parsing at the first unrecognized flag, so leaving it in caused every flag after
it — --turbo_fast_api_calls, --harmony-temporal, --js-float16array,
--js-explicit-resource-management, --js-source-phase-imports,
--js-defer-import-eval and --enable-queue-microtask — to be silently
dropped. The no_validate_asm integration test stays as-is and still passes,
since removing the validator means there is no output to suppress either way.

The two heap-limit tests in libs/core capped the isolate at 5MB, which V8
152 can no longer bootstrap within; since the callbacks under test are
registered after JsRuntime::new returns, there was nothing to raise the limit
and the test aborted the process on OOM. Both caps are raised to 20MB, which
leaves what the tests actually assert untouched.

cargo test -p deno_core passes (451 tests) and the full runtime unit suite
passes (104 test files), as do the node:vm tests that exercise the microtask
queue change.

One wrinkle worth flagging for review: the quickjs backend behind the
deno_v8 facade vendors an older rusty_v8 which still expects
ReturnValue<()> for the setter and definer interceptors, and there is no
v8x release tracking v8 152 to bump to. So rather than writing the payload
type directly in ext/node/ops/vm.rs, the interceptors now name a
PropertyInterceptorReturnValue alias defined in the facade, which resolves to
Boolean on the v8 backend and () on QuickJS. Papering over backend
differences is what the facade is for, and this keeps both backends building
from one set of callbacks. cargo check -p deno -p denort --no-default-features --features quickjs passes alongside the default build.

- `Global::open` is now `unsafe`; migrate call sites to the safe
  `v8::Local::new(scope, handle)`.
- Interceptor setter/definer callbacks now take `ReturnValue<Boolean>`.
- V8 removed asm.js validation, so `--no-validate-asm` is gone. Leaving it
  in made V8 reject the flag string and silently drop every flag after it.
- Bootstrap no longer fits in a 5MB heap; raise the heap-limit test caps.
`MicrotaskQueue::new` now returns an owning `MicrotaskQueueHandle` instead of
a `UniqueRef`, so the leak that hands the queue to the context is now explicit.
Indexed/named setter and definer interceptors take `ReturnValue<Boolean>`.
The setter/definer `ReturnValue` payload changed to `Boolean` in v8 152, but
the QuickJS backend vendors an older rusty_v8 that still expects `()`. Add a
`PropertyInterceptorReturnValue` alias to the deno_v8 facade, which is where
backend differences belong, and name it from the interceptors.
@bartlomieju bartlomieju changed the title chore: update v8 to 152.2.0 chore: bump v8 crate to 152.2.0 Aug 22, 2026
v8 152 replaced the `UniqueRef<MicrotaskQueue>` from `MicrotaskQueue::new`
with an owning `MicrotaskQueueHandle`. Two things followed from that which the
first pass got wrong.

`MicrotaskQueue` no longer implements `Drop` — only the handle does — so the
`drop_in_place` in `ContextifyContext::drop` silently became a no-op, and
forgetting the handle to recover a raw pointer leaked it outright. Under the
default `v8_cppgc_microtask_queue` setting the handle is a root into the
isolate's heap that contexts outlive on their own, so it wants to be held and
dropped, not leaked.

`ContextifyContext` now stores an `Option<OwnedMicrotaskQueue>` and lets
ordinary ownership release it, which drops the hand-written `Drop` impl, the
`mem::forget`, and the `unsafe { drop_in_place }` along with it.
`ContextifyModule` keeps borrowing the queue as a raw alias, which is now
sounder: a context keeps its queue alive independently of the handle, so the
alias survives even if the owning `ContextifyContext` is collected first.

`OwnedMicrotaskQueue` is aliased in the deno_v8 facade because the QuickJS
backend still returns `UniqueRef<MicrotaskQueue>`; both are RAII wrappers that
deref to `MicrotaskQueue`, so callers just name the alias.
@bartlomieju

Copy link
Copy Markdown
Member Author

Blocking on v8x release for now

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant