Repository navigation
Conversation
|
@robobun adopt |
|
Retargeted at main by alii; kept current since (latest: 3623211, nineteen rebases plus three small review commits), mergeable, all review-bot threads settled, mordant and binary-size green. Two pieces have been dropped as main landed its own versions (cron in #40024, URL in #40238), so the PR is now 45 files. In the latest CI run the one red is the RSS assertion in |
|
Updated 7:05 PM PT - Sep 7th, 2026
❌ @robobun, your commit 3623211 has 1 failures in 🧪 To try this PR locally: bunx bun-pr 32023That installs a local version of the PR into your bun-32023 --bun |
fbe8f1a to
a1c67b5
Compare
4a6537d to
b6193e2
Compare
There was a problem hiding this comment.
I didn't find any bugs, but this is a large refactor (53 files, net −5k lines) touching unsafe runtime internals across the VM event loop, IPC, NAPI, DNS, crypto, shell, and cron — worth a human pass to confirm the behavioral-equivalence claims hold across all the consolidations.
Extended reasoning...
Overview
This PR consolidates duplicated code across ~53 files in the Bun runtime: collapsing the TagPayload/Tag split in ConsoleObject, unifying the four PendingCacheKey types in DNS into one generic, merging auto_tick/auto_tick_active via a const generic, factoring shell builtins' OutputTaskVTable into a macro, restructuring CronRegisterJob/CronRemoveJob around a shared CronJobCommon base, deduping the CryptoHasher digest-output parsing, deleting the dead draft FFI implementation, and turning jsc::URL into a re-export of bun_url::whatwg::URL plus a UrlJsc extension trait. The diff is ~525k chars (truncated) with a net −5k lines.
Security risks
Several touched files are security-adjacent: csrf_jsc.rs (CSRF token generation/verification option parsing), CryptoHasher.rs and PasswordObject.rs (digest output handling, algorithm validation), and napi_body.rs (unsafe pointer/length handling for caller-supplied buffers). The changes look like straight extractions of existing logic into helpers with identical semantics, and the bug-hunting pass found nothing, but crypto/CSRF argument parsing and unsafe NAPI slice construction are exactly the kind of code where a subtle reordering or changed early-return can matter.
Level of scrutiny
High. Even granting the "zero intended behavior change" framing, this PR rewrites substantial unsafe Rust across the event loop (jsc_hooks.rs), intrusive-list DNS draining (drain_chain_ok/drain_chain_err), raw-pointer self-freeing state machines in cron.rs, and Stacked-Borrows-sensitive ManuallyDrop/ptr::read paths in subprocess Writable. The cron rework in particular merges two separate state enums and maybe_finished/finish paths into one generic — the kind of consolidation where a per-variant edge case (e.g. which states tolerate nonzero exit, which Drop runs) is easy to lose. These are production-critical hot paths, not sandbox code.
Other factors
CI is green across all lanes per the robobun comment, and the existing test suite is cited as regression coverage. The PR is also stacked (foundations → install-cli → this), so merge ordering matters. No prior human reviews are on the timeline. Given the breadth, the unsafe-heavy surface, and the security-adjacent files, this should get a human reviewer's eyes even if mechanically clean.
|
Re the specific high-scrutiny areas called out above, on the rebased head (b6193e2):
Build 64472 is at 281 jobs green with zero test-failure annotations; the only red is a darwin 26 lane that exited on a buildkite artifact-download timeout before running anything. Agreed a human pass is warranted given the scope; flagging @alii since this was handed over via adopt. |
a1c67b5 to
2f5dd36
Compare
b6193e2 to
f29ce8f
Compare
There was a problem hiding this comment.
The automated bug hunt came back clean, but this needs a human pass — 52 files across the event loop, JSC GC/formatter, DNS, IPC, NAPI, sockets, cron, and shell, with two rounds of non-trivial conflict resolution against main.
Checked and ruled out: the merged auto_tick<ACTIVE> preserves both original tails (GC-timer / rejected-promises skipped only when ACTIVE); drain_chain_ok keeps the ensure_still_alive bracketing and per-global re-materialization from every original drain_pending_*; the cron CronJobCommon extraction preserves the raw-ptr / no-FnEntry-protector discipline and per-job accepts_nonzero_exit differences; digest_to_bytes still returns the freshly-written digest bytes after the output_digest extraction.
Extended reasoning...
Overview
Net −5k line deduplication refactor across 52 runtime files: collapses TagPayload→Tag in the console formatter (shared with macro evaluator and test-runner pretty-printer), unifies four near-identical PendingCacheKey types in DNS behind a generic + HasTail/NameKeyed traits, extracts drain_chain_{ok,err} from five drain_pending_* copies, merges auto_tick/auto_tick_active into one const-generic fn, folds CronRegisterJob/CronRemoveJob state into a CronJobCommon base, macro-izes the shell builtins' OutputTaskVTable impl, extracts shared IPC drain loops, deletes the dead draft FFI implementation, and moves URL to a re-export with a UrlJsc extension trait. Two rebases onto main with 11+5 documented conflict resolutions.
Security risks
No new attack surface — this is code motion and helper extraction. The touched areas include NAPI string creation (unsafe slice construction from C pointers) and CSRF option parsing, but both are straight extractions of existing validation into helpers with identical guard order. No parsing/auth/crypto logic changed.
Level of scrutiny
High. This is production-critical native runtime code with pervasive unsafe, raw-pointer receivers whose contracts depend on Stacked-Borrows reasoning (cron's "local reborrow, no FnEntry protector" pattern), GC-rooting order (ensure_still_alive placement in DNS drain chains), and event-loop hot paths. The refactor's correctness hinges on every extracted helper preserving side-effect order and every conflict resolution correctly threading main's concurrent semantic changes (e.g. Rc<Handlers> in socket_body, transfer_to_js in napi) through the moved code. A subtle divergence in any one of the ~30 extractions could ship a UAF or a lost error path that the test suite doesn't cover.
Other factors
CI is green (281+ jobs) and the PR description documents per-file test verification, but the author's own bot already flagged that a human pass is warranted. The diff is stacked on another PR (install-cli) with a required merge order. Several extractions change control-flow shape in ways that are equivalent but not mechanically obvious — e.g. PasswordObject's verify_sync previously returned JSValue::ZERO on one dead-code branch and now returns Err(Thrown), and Mkdir::on_io_writer_chunk previously panicked on Idle/Done and now falls through to next(). These looked benign on inspection but are exactly the kind of thing a maintainer should confirm.
|
On the two specific control-flow shape changes called out above: PasswordObject Mkdir Build 72447 on the rebased head: 82 passed so far, one |
|
@robobun split this into a few prs. it's too big to land safely. |
|
On it. Splitting by subsystem into separate PRs, each stacked on |
|
Split into eight PRs, all stacked on
Each body carries the rebase-resolution notes relevant to its files and the suites run against it. Converting this one to a draft and leaving it open as the validated union for reference; happy to close it outright once the splits start landing. |
2f5dd36 to
9ae3ff5
Compare
f29ce8f to
1aba1ea
Compare
|
Re-landed directly on The PR description now lists what was dropped because main already has it or it depended on #32022, and which parts were re-derived against main (matchers on the #32024 is still stacked on this branch and needs the same treatment on its own. |
|
@robobun get this mergeable — main moved and it conflicts again. |
|
On it. Checking whether the base moved under the splits too; will rebase whatever conflicts and report back here. |
|
Conflicts again: #40374 (lifetimes for borrowed string bytes) touches 28 of the 45 files in this diff. Working through it now. |
56216db to
c370bb7
Compare
|
Mergeable again at c370bb7. #40374 (lifetimes for borrowed string bytes) reached 28 files here; where it landed in code this PR had moved into helpers, the change is applied inside the helper, most visibly the |
|
@robobun conflicts - also make the pr body much shorter |
|
On it: rebasing now, and I will cut the body down to a short summary with the history folded away. |
c370bb7 to
25037bc
Compare
|
Done both: mergeable again at 25037bc (one trivial conflict, #40410 making |
|
Conflicts again (#40478, RefPtr releases on Drop, touches 9 files here). Rebasing now. |
25037bc to
b6c15ae
Compare
|
Mergeable again at b6c15ae. One small conflict (#40478: |
|
Conflicts again (23 new main commits, 7 files in this diff touched). Rebasing now. |
d064697 to
b42f557
Compare
|
Rebased onto main at 0e395c2 (the seventeenth time). The branch is mergeable, 45 files. This round absorbed #40511 ( The first push of this rebase (d064697) still matched The PR body has the #40511 note in the rebase section. |
b42f557 to
7ce9e50
Compare
|
Rebased onto main at a46c250 (the eighteenth time). The branch is mergeable, 45 files, head 7ce9e50. Eleven of the 35 new main commits touch files in this PR. One conflict, in Checks after the rebase: the PR removes none of the lines main added in these files and re-adds none main removed. The unified The PR body has the #40516 and #40678 notes in the rebase section. |
Consolidates duplicated code across the jsc and runtime crates without changing behavior: - ConsoleObject: drop the TagPayload mirror of Tag; TagResult carries the custom-formatter payload as an Option. Shared with the macro evaluator and the test-runner pretty printer. WrappedWriter::new replaces 32 struct-literal constructions; the per-function pf! macros go away. - URL: bun_jsc::URL becomes a re-export of bun_url::whatwg::URL (which gains the getters and destroy() that only the jsc copy had); the JSValue entry points move to the UrlJsc extension trait. - VirtualMachine: one wait loop for load_entry_point and the test-runner variant. jsc_hooks: auto_tick and auto_tick_active become one const-generic function. - bun:test: toEqual/toStrictEqual share one implementation; the toHave*With matcher family shares argument comparison, mock.results parsing and the failure epilogues, written against the throw! macro so user data is never passed through the color-tag rewrite. - Shell: basename/dirname share PathBuiltin; the OutputTaskVTable impls of cp/ls/mkdir/touch come from one macro; the lex/parse testing APIs share their argument prologue; subproc Writable/Readable use one match with per-arm cfg instead of two platform copies, and the Blob/ArrayBuffer stdin writer is shared with Bun.spawn. - cron: the register and remove jobs share CronJobCommon and CronJobBase. - dns: one generic PendingCacheKey and one pending-chain drain instead of a copy per record kind; CacheHit is now an alias of LookupCacheHit. - valkey: the seven command-shape macros become cmd! and cmd_varargs!. - Smaller dedups in AsyncModule, CryptoHasher, PasswordObject, csrf, HashObject, MarkdownObject, JSON5Object, napi string creation, bake transpiler setup and DevServer field exhaustiveness checks, udp setsockopt helpers, ipc message draining, socket event callbacks, ServerWebSocket publish prologue and CodeCoverage line mapping.
toHaveNthReturnedWith and toHaveLastReturnedWith ran the same match over a parsed mock result; it now lives once in mock::check_returned_with.
The tag strings already live in the map used by jest_mock_return_object_type; parse_mock_result now matches on the ReturnStatus it yields instead of repeating the literals. Unrecognized and incomplete entries still map to MockResult::Other rather than throwing, which is what the *ReturnedWith matchers rely on.
The helper only ever forwards zero or one argument after the socket. Taking Option<JSValue> makes that limit part of the signature and removes the two-slot scratch array and the index arithmetic. The arguments passed to the callback are unchanged.
7ce9e50 to
3623211
Compare
|
Rebased onto main at a3e0ab6 (the nineteenth time). The branch is mergeable, 45 files, head 3623211. 264 new main commits, 35 of them touching files in this PR. Seven conflicts, all in the matchers, from #40068 (missing exception checks): main replaced the Checks after the rebase: the matcher and mock suites pass under Main's own changes in these files survive: the udp hostname validation and Win32 error mapping (#40986, #40860), the napi symbol rename, and the dns and shell stdio fixes are all intact. The PR body has the #40068 note in the rebase section. |
Dedup pass over the jsc bindings, runtime APIs,
bun:testmatchers and shell builtins. Only moves and removes code; no intended behavior change. 45 files, about -1.4k lines net.TagPayload(a mirror ofTagthat existed to carry one variant's payload) is gone;TagResultcarries the payload as anOption.WrappedWriter::newreplaces 32 struct literals and the per-functionpf!macros.load_entry_pointandload_entry_point_for_test_runner;auto_tick/auto_tick_activebecomeauto_tick<const ACTIVE: bool>.toEqual/toStrictEqualshare one implementation; thetoHave*Withfamily shares argument comparison,mock.resultsparsing and failure epilogues.basename/dirnamesharePathBuiltin; cp/ls/mkdir/touch get theirOutputTaskVTableimpl from one macro;subprocstdio init is one match with per-armcfg; the Blob stdin writer is shared withBun.spawn.PendingCacheKey<Req>and one pending-chain drain replace the per-record-kind copies.cmd!andcmd_varargs!; all commands keep their name, wire command, argument names and state requirement.create_string_*prologue, bakeinit_transpiler_impland theDevServerfield-exhaustiveness macro, udp setsockopt helpers, ipc drain loops, socket callback tail,CodeCoverageline mapping.One deliberate non-1:1 spot: mkdir's
on_io_writer_chunkalone had anIdle | Done => panic!arm, which the macro drops to match the other three builtins;Idlestill panics one frame down inMkdir::next, andDoneis only set after the last chunk callback has run.Dropped along the way because main landed its own version: the cron consolidation (#40024) and the URL unification (#40238).
Verified with a debug build,
cargo clippy --workspace,rust:check-all(12 targets), and the touched suites (console / inspect, expect + matchers, shell, crypto, csrf, dns, ipc, socket, udp, napi, bake, coverage, valkey), plus byte-for-byte differential checks against canary for the matcher messages, shell output and publish errors. The unifiedauto_tickis re-compared statement for statement against main's two functions after every rebase.Review-driven commits
same_match_twiceontoHaveNthReturnedWith/toHaveLastReturnedWith, which now sharemock::check_returned_with.parse_mock_resultlooks the result tag up through the existingRETURN_STATUS_MAP.call_socket_handlertakes its optional extra argument as anOption<JSValue>.Rebase notes
Rebased onto main nineteen times. Where main changed code this PR had moved into a helper, the change is applied once inside the helper; where main landed its own version of a piece (cron #40024, URL #40238, the websocket publish prologue #39389), the piece was dropped. The larger re-derivations:
print_*functions,parse_verify_algorithm,CryptoHasher::hash, the napi string helpers, the shell argument helper,parse_mock_result, and the AsyncModule error builders (now fallible, same nine properties in the same order as main's inlined versions).parse_verify_algorithm, the ipc drain helpers (dispatch::foldaroundhandle_ipc_message), and the matchers'get_direct_indexcalls.RefPtrreleases on Drop):DNSLookup.resolveris aRefPtras on main; the manualderefsites main deleted were never in this PR's helpers.Stdio::ArrayBufferremoved,PinnedArrayBufferredirects): the shared stdin writer is Blob-only on both platform arms; the shell's singleReadable::inittakes main'sredirect_bufand hands theBufferedOutputtoPipeReader::createas main does.Writable::initkeeps main'sFileSink::create_with_pipevalue return on the Windows arm; the dnsResolverDrop/RefPtr::init_refguards and theProcessHandleholder land outside this PR's helpers.bun test --parallelcoverage wire format): theresolve_line/map_to_originalclosures inCodeCoverage::generatesit on top of main'sByteRangeloops (for block in blocks,set(stmts.len())).DiffFormatter::new(global, received, expected, not)?replaces the struct literal once insidemock::throw_diffand once in the unifiedtoEqual, so the sixtoHave*Withmatchers andtoStrictEqualget main's fallible formatting through the shared code. The matcher suites pass underBUN_JSC_validateExceptionChecks=1.perform_gc()removal applied once in the shared entry-point wait; Don't request a GC before waiting on the entry point #39541's own tests pass here.Pre-existing items surfaced by review, not changed here
Handed off: the redis
bitcountrange /hscancursor argument handling.Follow-ups: shell stdio init
StdioResult::unwraps as error returns; rediskeysargument reported askeyrather thanpattern; dnsPendingCacheKey::appendas anunsafe fn; bake productioninit_transpilerfreeing the arena projection like DevServer does; sealingStaticHasherso theDigestcast is enforced by the trait; harmonizing the three napi string failure paths; the cron consolidation redone against #40024's shape.