Repository navigation
Conversation
Collaborator
Author
|
Updated 2:06 PM PT - Oct 27th, 2026
✅ @robobun, your commit a424f27804b53e0dfeda808cd35e0a610f0143a8 passed in 🧪 To try this PR locally: bunx bun-pr 47099That installs a local version of the PR into your bun-47099 --bun |
robobun
force-pushed
the
robobun/225b7f1d/free-through-owner-not-shared-view
branch
from
October 27, 2026 18:43
7be1dad to
9860424
Compare
robobun
force-pushed
the
robobun/225b7f1d/transpiler-job-owns-path-text
branch
from
October 27, 2026 18:43
3604e54 to
21fd929
Compare
Collaborator
Author
|
Status
|
This was referenced Oct 27, 2026
…n run() `TranspilerJob` stored a `Path<'static>` view of a leaked `Box<[u8]>` and freed the box through `Path::text`. A pointer made from a shared reference may only read, so the free is undefined behaviour under Tree Borrows and Stacked Borrows. - `TranspilerJob` owns `path_text: Box<[u8]>`. It stores no view. - `run()` borrows the text for the `Path` it parses under. No lifetime is erased: `ParseOptions.path` is a `Path<'a>`. - The drop glue of the slot frees the text, so `reset_for_pool` only disables the poll ref. - The `self-receiver-reclaim` source lint no longer allows an instance in this file.
robobun
force-pushed
the
robobun/225b7f1d/transpiler-job-owns-path-text
branch
from
October 27, 2026 19:03
21fd929 to
a424f27
Compare
Collaborator
Author
|
Closing: main has this change since #47893 (commit 9239165). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Draft: stacked on #47098, and held until #44833 is merged (Notes).
Problem
TranspilerJob, the record of one asyncimport, stores aPath<'static>view of a leakedBox<[u8]>and frees the box throughPath::text:heap::take(ptr::from_ref::<[u8]>(old_path.text).cast_mut())(src/jsc/RuntimeTranspilerStore.rs:506). A pointer made from&Tmay only read, so the free is undefined behaviour.bun_jsccannot run under Miri. A reduction of this shape givesUndefined Behavior: deallocation through <tag> is forbidden. No crash is known.Fix
TranspilerJobownspath_text: Box<[u8]>and stores no view.run()borrows the text for thePaththat it parses under. No lifetime is erased:ParseOptions.pathis aPath<'a>since Fix use-after-free ofBuildMessage.position.file: real lifetimes forSource,Path,ImportRecordand resolve results #46834.reset_for_poolonly disables the poll ref.test/internal/source-lints/self-receiver-reclaim.test.ts, which no longer allows the instance in this file. Also the import,require.cacheand transpiler cache suites (debug build).Background
HTTPResponseMetadata, the lint, and the reason that a raw owner beside the stored view is not enough.Downsides
TranspilerJob376 to 336 B, the block of each VM 32,768 to 28,672 B.Notes
Why a draft
Some(e) => (String::clone_utf8(self.path.text), Err(e))inTranspilerJob::run_from_js_thread. This PR changes that line to&self.path_text, sogit merge-treereports one conflict region against its head. No other file of this PR conflicts with it.Proof
test/internal/source-lints/self-receiver-reclaim.test.tswithsrc/from http: HTTPResponseMetadata owns its buffers and builds its view on demand #47098 listssrc/jsc/RuntimeTranspilerStore.rs:506. With this branch it lists nothing.bun_jscneeds JavaScriptCore, so Miri cannot run it. A standalone reduction of the old shape (aPathview of a leaked box, freed throughptr::from_ref(text).cast_mut()) is UB under Tree Borrows and Stacked Borrows. The reduction of the new shape passes under both.Measurements (release builds of #47098 and of this branch, x86_64 Linux)
size_of::<TranspilerJob>()size_of::<RuntimeTranspilerStore>()size_of::<VirtualMachine>()VirtualMachineRuntimeTranspilerStore::transpileRuntimeTranspilerStore::run_from_js_threadTranspilerJobTranspilerJob::run_from_worker_thread.textofbun-profile(size -A)size_ofin a const, compiled with the flags of the release build. Block size:mi_usable_size(mi_malloc_aligned(size, 64))in the release binary.VirtualMachine::initallocates with that size and alignment, one block for the main thread and one for each Worker.transpileallocates for the text and themi_freecall that frees it, over 1,000 imports. Each block is freed one time and none is live at exit. The free moves fromrun_from_js_threadto the drop glue of the job.nexti). One async import: -14 in total.transpilehas a few more in a call that scans past used slots.test/cli/run/esm-fixture-leak-small.mjs(100,000 imports) on the release builds: 11 MB and 10 MB on http: HTTPResponseMetadata owns its buffers and builds its view on demand #47098, 12 MB and 11 MB on this branch. The limit is 100 MB.Suites (debug ASAN build)
test/js/bun/resolvefiles (dynamic import,import.meta, build errors,requireof ESM),transpiler-cache24,esm-defineProperty3,syntax592.require-cache.test.tson the release builds: 17 of 18 pass on http: HTTPResponseMetadata owns its buffers and builds its view on demand #47098 and on this branch. The 100,000 import test runs for 45 s in my container and its limit is 30 s.Not in this change
TranspilerJob::run_from_js_thread(&mut self)puts its own slot back. For a job on the heap fallback (more than 64 jobs at a time) that frees the receiver. Hand pool-finished transpiler and patch jobs back through their pointer, not a &mut receiver #37778 addresses it.'staticstring. A rebase of it replaces this field.