Skip to content

Make the allocation-only collection APIs infallible and drop the OOM wrappers around them - #38888

Open
robobun wants to merge 7 commits into
mainfrom
farm/e571fddf/infallible-collections
Open

robobun wants to merge 7 commits into
mainfrom
farm/e571fddf/infallible-collections

Conversation

@robobun

@robobun robobun commented Aug 15, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

  • Most of the Result<_, AllocError> plumbing in the tree is a leftover of the Zig port: the callee grows a Vec or a hashbrown table, which aborts on allocation failure, and then returns Ok unconditionally. ArrayHashMap::put, get_or_put, ensure_unused_capacity, StringHashMap::put, HashMap::put, ByteVecExt::write, the bit sets and so on all worked this way (array_hash_map.rs even said "kept as Result for API stability").
  • Every caller then paid for it: bun_core::handle_oom(..), .unwrap_or_oom(), .expect("oom"), ?, .map_err(|_| OutOfMemory)?, let _ = ..; // OOM/capacity: fire-and-forget, // bun.handleOom comments, and branches that look like graceful OOM handling but are unreachable (Worker env clone returning "Out of memory", Bun.spawn throwing OOM for the env block, ArrayBufferSink / HTTP stream writes returning ENOMEM, SQL buffer writes mapping to OutOfMemory, markdown slug dedupe and package.json scripts silently giving up).
  • let _ = self.graph.ast.append(..) on a MultiArrayList (the one container whose Result was real) would have silently desynced graph.ast from graph.input_files had the allocation ever failed.
  • Reporting was inconsistent: handle_oom produced an out-of-memory crash report, .expect("oom") a panic report saying it is a bug in Bun, and a plain Vec growth failure went through std's handle_alloc_error, printing memory allocation of N bytes failed and aborting, which is reported as an abort() crash (or, on ASAN builds, not at all). Nothing hooked std's alloc error path.

Fix

  • bun_collections: the allocation-only methods return their value directly. MultiArrayList and the dynamic bit sets, which allocate by hand, call std::alloc::handle_alloc_error on failure exactly like Vec does. Types whose inherent clone() returned Result implement Clone instead (ArrayHashMap, StringArrayHashMap, StringSet, the bit sets, bun_dotenv::Map, DeclaredSymbolList), which also removes the hand-written deep-clone code that existed to work around that (clone_macro_remap, clone_macro_map, Map::clone_with_allocator, the MacroRemap rebuilds in transpiler.rs, RuntimeTranspilerStore.rs and jsc_hooks.rs, and the manual Clone impls / clone() methods on ExternalModules, DependencyMap, Framework and ESMConditions, which are derives now).
  • bun_crash_handler::install_hooks registers std::alloc::set_alloc_error_hook next to the panic hook, routing into the existing out_of_memory() path. With that, dropping the wrappers is purely a cleanup: a failed allocation anywhere in Rust code now produces the same "Bun has run out of memory" report handle_oom produced, and this is also what makes the handle_alloc_error calls in the containers correct. The hook does not allocate (one diverging call); a second failure inside the report hits the existing re-entry guard.
  • Callers: the wrappers listed above are removed everywhere the compiler forced it, on all 10 CI targets. Thin wrappers whose only error source was one of these calls return directly (bun_dotenv::Map and Loader::load_process / load_node_js_config, DeclaredSymbolList, semver::StringPool, PathToSourceIndexMap, SavedSourceMap::put_value / put_mappings, LineOffsetTable::generate, mime_type::create_hash_table, ensure_stale_bit_capacity, register_barrel_with_deferrals, ...). bun_dotenv::Error::Alloc and the bun_alloc dependency of bun_dotenv / bun_http_types are gone because nothing produces an AllocError there anymore.
  • The one real use of the old Result is kept: source map parsing reserves rows from the segment count of an untrusted map and turns a failed reservation into a parse error for the user. It now goes through MultiArrayList::try_ensure_total_capacity (the Vec::try_reserve analog); everything else that reached that Result crashed on Err anyway.
  • Deliberately left alone: LinearFifo (a full StaticBuffer is reported through the same error and Channel blocks on it; it needs its own error type), yaml.rs (uses AllocError as its merge-key budget error), and the next layer of functions that still return Result<_, AllocError> (about 470, roughly half of which can no longer fail after this change), which cascade further and are for follow-up PRs, ending with deleting handle_oom / UnwrapOrOom and giving the few user-facing buffers that should survive OOM (the sinks and SQL buffers above) real try_ paths.
  • Ratchets so this does not come back: clippy::let_unit_value is no longer allowed (the 174 remaining let _ = unit_call(); bindings are removed; cppbind.ts stops emitting let __r = for void C++ functions so the generated wrappers pass too), and the port-era source lint now rejects bun.handleOom comments (all 29 removed).
  • Verified:
    • test/cli/run/run-crash-handler.test.ts: new crash_handler.allocError() hook calls handle_alloc_error; added to the terminal-signal and automatic-reporter matrices. Fails without the hook (memory allocation of 4096 bytes failed, no report on the ASAN debug build), passes with it.
    • test/internal/source-lints/port-era-markers.test.ts with the new pattern.
    • cargo check --workspace --tests, cargo clippy --workspace (clean, with let_unit_value enforced), bun run rust:check-all (10 of 10 targets), cargo test and cargo miri test for bun_collections.
    • bun bd test on sourcemap, env, bundler (barrel, splitting, html manifest), transpiler, isolated install / patch / audit, sql, glob, fs.watch, http response streams, http2, framework router suites.

Background

  • AllocError is the unit error type every crate's Error enum wraps as Alloc(..); in Zig every allocating call returned error.OutOfMemory and bun.handleOom(x) was the idiom for "crash with an OOM report if this failed". The port translated the signatures literally, but Rust's Vec, Box and hashbrown never return an error: on failure they call handle_alloc_error, so a Result returned from code built on them can only ever be Ok.
  • handle_alloc_error consults a process-global hook (std::alloc::set_alloc_error_hook, a nightly API; the workspace is already pinned to nightly) and aborts if the hook returns. Bun's out_of_memory() never returns: it prints the crash report with CrashReason::OutOfMemory and exits, which is what distinguishes an OOM from a bug in the crash telemetry.
  • MultiArrayList is a struct-of-arrays container that allocates its columns with a raw Allocator, and the dynamic bit sets realloc a word buffer by hand, so those two were the only collections whose Result could actually be Err; they now have the same split as Vec (ensure_* aborts, try_ensure_* reports).
  • clippy::let_unit_value flags let _ = f(); where f returns (); it had been allowed in Cargo.toml because the port produced these bindings wherever a formerly fallible call became infallible.

This also covers the hook proposed in #31690, which this PR supersedes.

Counts

Occurrences in src/**/*.rs, main vs this branch:

pattern before after
handle_oom( 168 75
.unwrap_or_oom() 59 18
.expect("oom") / .expect("OOM") 167 88
// OOM/capacity comments 62 35
// OOM-only comments 25 11
bun.handleOom comments 29 0
let _ = <unit> (clippy) 174 0

The remaining ones wrap functions outside bun_collections that still return Result<_, AllocError> (e.g. semver::Builder::allocate, AstBuilder, ini's OOM<T>, enqueue_* in bundle_v2); those are the next layer.


no test proof · iteration 0 · Platform-specific test(s) that do not run on this machine. Deferring to CI, which covers all platforms: test/cli/run/run-crash-handler.test.ts

@robobun

robobun commented Aug 15, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 11:58 PM PT - Aug 14th, 2026

@robobun, your commit 55a42d9 is building: #97543

@robobun

robobun commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator Author

Status: diff complete and verified locally (cargo check/clippy on all targets, collections unit tests under miri, the crash handler test matrix, and the bundler/install/sourcemap/env/sql suites listed in the description). Waiting on CI. Follow-up PRs will peel the next layer of functions that still return Result<_, AllocError> and then remove handle_oom itself.

@alii

alii commented Aug 15, 2026

Copy link
Copy Markdown
Member

@robobun fix conflicts + rebase

ArrayHashMap, StringArrayHashMap, StringHashMap, StringSet, StringMap,
HashMap, MultiArrayList, the bit sets, ByteVecExt, OffsetByteList and
PriorityQueue returned Result<_, AllocError> from methods whose bodies
grow a Vec or hashbrown table, which abort on allocation failure and
never return Err. The raw-allocation containers (MultiArrayList, the
dynamic bit sets) now call handle_alloc_error like Vec does.

The crash handler installs an alloc error hook so every failed
infallible allocation is reported as out-of-memory instead of as an
abort() crash.

Types whose clone() returned Result now implement Clone. Leaf callers
updated; LinearFifo is left fallible because a full StaticBuffer is
reported through the same error.
…f memory

Adds a crash_handler.allocError() test hook that calls handle_alloc_error,
the path every infallible std allocation takes when the allocator returns
null. Without the alloc error hook std prints "memory allocation of N
bytes failed" and aborts, which is reported as an ordinary crash (or, on
ASAN builds, not reported at all).
let_unit_value was allowed because the port left many
`let _ = call();` bindings around calls that return (); those are all
removed in the next commit. The generated check_slow wrappers for void
C++ functions bound their result too, so cppbind now emits a plain call
for those.

dotenv and http_types no longer use bun_alloc.
Callers of the collection APIs made infallible in the previous commits
no longer wrap them in handle_oom, unwrap_or_oom, expect, ?, map_err or
let _ =. Thin wrappers whose only error source was one of those calls
(bun_dotenv::Map and Loader::load_process, DeclaredSymbolList,
StringPool, PathToSourceIndexMap, SavedSourceMap::put_value, ...) return
their value directly, and types that used to expose a fallible inherent
clone() now implement Clone, which removes the hand-written deep-clone
helpers that worked around that.

Several of the removed branches looked like graceful handling (Worker
env clone, Bun.spawn env block, ArrayBufferSink/HTTP stream writes,
SQL buffer writes, markdown slug dedupe, package.json scripts map) but
were unreachable: the callee never returned Err. The one real one,
source map parsing reserving rows from an untrusted segment count, keeps
its error path through MultiArrayList::try_ensure_total_capacity.

Also removes the let _ = bindings on unit-returning calls that
let_unit_value now flags, and the remaining bun.handleOom comments.
Comment thread src/jsc/RuntimeTranspilerStore.rs Outdated
Comment thread src/runtime/api/JSON5Object.rs Outdated
…e clone()

StringArrayHashMap, StringSet and ArrayHashMap implement Clone now, so
the entrywise MacroMap rebuilds in RuntimeTranspilerStore, jsc_hooks and
JSTranspiler (clone_macro_map), and the hand-written Clone impls and
clone() methods on ExternalModules, DependencyMap, Framework and
ESMConditions collapse to .clone() / #[derive(Clone)]. Also drops the
comments next to changed call sites that still described the removed
Result plumbing.
@robobun
robobun force-pushed the farm/e571fddf/infallible-collections branch from 17b6f92 to aec6dc0 Compare August 15, 2026 06:10
@robobun

robobun commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator Author

Rebased on main (conflicts were in computeChunks.rs, npm.rs, patch_install.rs, PackageManagerTask.rs, PackageInstall.rs and bun.lock.rs, all resolved in favor of main's newer code; main's new collect_bundled_deps returns bool now since StringSet::insert no longer returns a Result).

Also addressed the review on the remaining manual clones: the MacroMap rebuilds in RuntimeTranspilerStore.rs, jsc_hooks.rs and JSTranspiler.rs (clone_macro_map) are .clone() now, and ExternalModules, DependencyMap, Framework and ESMConditions derive Clone instead of hand-writing it. Swept the comments next to the changed call sites that still described the removed Result plumbing (JSON5Object.rs and a few others). Workspace check is clean on all targets, clippy is clean, and the affected suites pass locally.

Comment thread src/bundler/options.rs Outdated
Comment thread src/bundler/linker_context/computeCrossChunkDependencies.rs Outdated
…iscarded results

ESMConditions derives Clone now, so BundleOptions::deep_clone can clone
it directly. The remaining StringSet::insert callers no longer bind the
bool, and the comments that explained discarding a Result from put,
insert and append_slice are gone along with the Results.
@robobun

robobun commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator Author

Pushed 55a42d9 for the two remaining review threads: the ESMConditions rebuild in BundleOptions::deep_clone is self.conditions.clone(), the stale "result intentionally discarded" / "OOM-only Result" comments are gone, and every StringSet::insert caller is a bare call now. Clippy and the md, bundler, test-changed, websocket subprotocol and transpiler suites pass locally; the branch is still mergeable against main without another rebase.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reviewed this PR across three passes; the earlier findings (redundant clone rebuilds, stale OOM comments) were addressed in aec6dc0 and 55a42d9, and this pass found no new issues. Given the scope — 247 files, a new nightly alloc_error_hook install in the crash handler, and API-surface changes to every allocating collection — a human sign-off on the overall design is still worthwhile.

What was reviewed: the bun_collections signature changes and their call-site rewrites (verified the compiler forces every dropped ?/handle_oom); the MultiArrayList try_ split and its one intentional caller in sourcemap parsing; rust_alloc_error_hook wiring and the new allocError() test hook; and that LinearFifo/yaml.rs were left alone as stated.

Extended reasoning...

Overview

This PR makes the allocation-only methods on bun_collections (ArrayHashMap, StringArrayHashMap, StringHashMap, HashMap, MultiArrayList, the bit sets, ByteVecExt, StringSet, PriorityQueue) return their value directly instead of Result<_, AllocError>, and removes the ~400 caller-side handle_oom/.unwrap_or_oom()/.expect("oom")/let _ = wrappers the compiler then flags. It installs std::alloc::set_alloc_error_hook in bun_crash_handler::install_hooks so any handle_alloc_error (from Vec, hashbrown, or the containers' own new abort paths) routes through the existing out_of_memory() crash report. Types whose inherent clone() was fallible now impl Clone (or #[derive(Clone)]), which let several manual deep-clone helpers and impl Clone blocks collapse. Two ratchets are added: clippy::let_unit_value is un-allowed, and the port-era source lint rejects bun.handleOom comments. A new crash_handler.allocError() test hook is added to the crash-handler test matrix.

Security risks

None identified. The change is error-plumbing removal around allocation paths that already could not return Err; the one place that genuinely reserved from untrusted input (sourcemap segment count) keeps its fallible path via the new try_ensure_total_capacity. No auth/crypto/permission logic is touched beyond mechanical wrapper removal.

Level of scrutiny

High. The individual edits are mechanical and compiler-enforced (a missed ? or wrong return type would not build, and the PR states cargo check / clippy / rust:check-all on 10 targets pass), but the aggregate is a design decision about OOM handling for the whole runtime: a nightly #[feature(alloc_error_hook)] is added, a process-global hook is installed, bun_dotenv::Error::Alloc is removed, and the intent is to eventually delete handle_oom entirely. That direction — and whether the sinks/SQL buffers listed as future try_ candidates are the right cut line — is worth a maintainer's eye.

Other factors

I left two prior rounds of findings on this PR (redundant MacroRemap/ESMConditions rebuilds and stale "result discarded"/"OOM-only" comments); all are marked resolved and the fixes are visible in the current diff and commit log. The PR adds a real regression test for the new hook (fails without it per the description), extends the source lint, and documents what was deliberately left for follow-up. The sheer breadth (247 files across bundler, install, runtime, jsc, crash handler) is the reason to defer rather than any specific concern.

@robobun

robobun commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator Author

Nothing further from my side: the review rounds are addressed and resolved, and CI on 55a42d9 is at 176 jobs passed with the last 3 still running, no failures so far (the lint workflows, including mordant, were already green on the previous push). Ready for a maintainer to look at the design, in particular the cut line described under Fix: bulk reservations sized from untrusted input keep a try_ path (source maps today), everything else aborts through the OOM report like Vec, and the sinks and SQL buffers get real try_ paths in a follow-up.

@robobun

robobun commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator Author

CI is green on 55a42d9 (build 97543, all 179 jobs passed; the only test failures were per-test retries that passed on retry). Ready for review.

Jarred-Sumner pushed a commit that referenced this pull request Aug 22, 2026
…nk's output buffer cannot be allocated (#38941)

### Problem
- A `TextEncoderStream` chunk whose encoded output cannot be allocated
aborts the process: `memory allocation of N bytes failed`, then
`panic(main thread): abort() called` and a "Bun has crashed" report. The
output buffer is 1x to 3x the chunk, so for a large chunk this is where
the API runs out of memory.
- `encode_latin1_into`
(`src/runtime/webcore/TextEncoderStreamEncoder.rs`) reserves infallibly
at three places (the up-front reservation, the `reserve(2)` retry when a
two-byte char does not fit, the grow-to-fit reservation) and returns
nothing. `encode_utf16_into` returns a `Result`, but its main
reservation is infallible too, and the replacement encoder it falls back
to for invalid UTF-16, `to_utf8_list_with_type_bun`
(`src/bun_core/string/immutable/unicode.rs`), returns `Result<_,
AllocError>` while growing with `reserve_exact`, so its `Err` was
unreachable.
- The Zig version (`TextEncoderStreamEncoder.zig` at the parent of
23427db, lines 68 to 169) returned `throwOutOfMemoryValue()` at each
of these sites; the port lost that. #33014 fixed the same regression in
the decoding direction (`to_utf16_alloc*`, `TextDecoder`).

### Fix
- Every reservation in `encode_latin1_into`, `encode_utf16_into` and
`to_utf8_list_with_type_bun` is a `try_reserve` / `try_reserve_exact`
mapped to `AllocError`. The amounts are unchanged:
`ensure_total_capacity(len + remain + 1)` is `try_reserve(remain + 1)`,
and the old `reserve_exact((i + count + len + extra) - len)` is
`try_reserve_exact(i + count + extra)`.
- `encode_latin1_into` returns `Result` like its UTF-16 sibling, and the
two callers that did not handle an error yet (`encode_latin1`, and the
Latin-1 arm of `TextEncoderStreamEncoder__encodeIntoSink`) throw
out-of-memory the way `encode_utf16` and the UTF-16 arm already did.
`to_utf8_list_with_type_bun` has no other caller.
- Why this is the right behavior: the buffer's size is chosen by the
caller's data, and a failure to get it was part of the JS contract
before the port. The C++ transform step (`encodeAndEnqueue` in
`JSTextEncoderStream.cpp`) already turns a pending exception into a
rejected transform promise, so the `write()` rejects and the stream
errors with `RangeError: Out of memory`; nothing else needed to change.
This does not touch the abort-on-OOM policy for internal allocations; it
is the same shape as #33014 and the `try_reserve` sites in
`encoding.rs`, `PBKDF2.rs` and `TextDecoder`.
- Verified with `test/js/web/encoding/text-encoder-stream.test.ts`, two
ASAN-only tests (the gate and the ASAN lanes) that run a child under
`allocator_may_return_null=1:max_allocation_size_mb=4`. JSC allocates
the input strings outside that cap, so each of five inputs makes a
different encoder reservation fail: the Latin-1 up-front reservation,
the Latin-1 grow-to-fit reservation, the Latin-1 `reserve(2)` retry (an
ASCII byte plus an odd number of two-byte chars leaves one spare byte),
the UTF-16 simdutf-sized reservation, and the replacement encoder's
first reservation (a lone surrogate ahead of N ASCII units: simdutf's N
+ 2 fits, 1.2N + 3 does not). One test feeds the chunks to a JS reader
(write and read both reject with `RangeError: Out of memory`, and
encoding still works afterwards); the other serves them through
Bun.serve's native response sink, each behind a dangling lead surrogate
so the output is assembled in the encoder's own buffer (the arm that
stays the encoder's job if #36877 hands plain chunks to the sink
directly), which also covers the prepend variant of every reservation;
the errored transform cancels the source with the error and the server
keeps serving. Without the fix both children abort on the first input
(`memory allocation of 8388608 bytes failed`); the per-site sizes are in
the details below.
- Also run: `bun bd test test/js/web/encoding/` (577 pass),
`test/js/web/streams/streams.test.js`,
`test/regression/issue/29225.test.ts`,
`test/js/node/test/parallel/test-whatwg-webstreams-encoding.js`, `cargo
fmt --check`.
- Scope: this PR originally also contained the `Bun.password` argon2
change; that is an unrelated allocation with its own design question and
now lives in #39021. The same port regression exists in other user-sized
buffers that are not touched here and have been handed off separately:
the `Bun.gzipSync` / `gunzipSync` / `deflateSync` / `inflateSync` output
buffers (`BunObject.rs`, including the one sized from the gzip trailer's
ISIZE) and `Bun.zstdCompressSync` plus zstd decompression
(`BunObject.rs`, `zstd/lib.rs`). The sinks and SQL buffers are covered
by #38888's follow-up plan.

### Background
- `Vec::try_reserve` returns `Err` when the allocator returns null,
where `reserve` calls `handle_alloc_error` and aborts. Under ASAN the
allocator is libc's, and
`ASAN_OPTIONS=allocator_may_return_null=1:max_allocation_size_mb=N`
makes it return null for any single allocation above N MiB, which is
what makes these failures reproducible; JSC allocates JS strings through
its own allocator, which the cap does not apply to.
- `AllocError` is the unit error type the crates' `Error` enums wrap as
`Alloc(..)`; `JSGlobalObject::throw_out_of_memory_value` sets JSC's
out-of-memory `RangeError` as the pending exception.
- A TextEncoderStream transform that throws rejects the transform
promise, which errors both halves of the stream and, through
`pipeThrough`, cancels the stream being piped in with the same error;
that cancel reason is what the native-sink test observes.
- Native-sink path: when the stream's readable is consumed by a native
sink such as an HTTP response,
`TextEncoderStreamEncoder__encodeIntoSink` encodes into a reusable
buffer and hands it to the sink instead of enqueueing a `Uint8Array` per
chunk. A lead surrogate left over from the previous chunk is encoded as
a prefix of the next chunk's output, so that case always needs the
encoder's buffer.

<details>
<summary>Per-site aborts without the fix (debug ASAN build), each size
identifying the reservation that failed</summary>

48 MiB cap, JS path:

```
latin1, 64 MiB ASCII                      memory allocation of 67108864 bytes failed   (up-front reservation)
latin1, 40 MiB of 0xE9                    memory allocation of 83886080 bytes failed   (grow-to-fit, amortized from 40 MiB)
latin1, "a" + odd run of 0xE9             memory allocation of 83886084 bytes failed   (reserve(2) retry, amortized from 40 MiB + 2)
utf16, 20M x U+65E5                       memory allocation of 62914560 bytes failed   (simdutf-sized reservation, 3 bytes/unit)
utf16, U+D800 + 44 MiB of "a"             memory allocation of 55364815 bytes failed   (replacement encoder: 3 + 1.2 * 46137344)
```

4 MiB cap (the sizes the tests use), with the fix, ASAN's refused
allocation per case; JS path then native-sink path behind a carried lead
surrogate (+3 bytes of replacement prefix in each reservation):

```
latin1        8388608    8388611
latin1Grow    6291456    6291462
latin1Stuck   6291460    6291466
utf16         6291456    6291459
utf16Invalid  4718595    4718598
```

Every case reports `{"name":"RangeError","message":"Out of memory"}` and
the child exits 0.

</details>

<!-- robobun:evidence:begin -->

---

**[review]** gate passed · iteration 0 · 3 files touched

<details><summary>fails on main (without fix)</summary>

```console
ASAN without fix: BUILD FAILED (no junit output)
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/js/web/encoding/text-encoder-stream.test.ts
ninja: Entering directory `/workspace/bun/build/debug'
[1/85] gen BunProcess.lut.h
Generating /workspace/bun/build/debug/codegen/BunProcess.lut.h from /workspace/bun/src/jsc/bindings/BunProcess.cpp
[2/85] gen generated_host_exports.rs
generated_host_exports.rs: 92 exports (host=3, lazy=10, generic=79, rust=0); 240 extern-C blocks audited
[3/85] gen cpp.rs (cppbind)
[4/85] gen JS modules (bundle-modules)
Preprocess modules (11911ms)
Bundle modules (45ms)
Postprocesss modules (181ms)
Bundle Functions (696ms)
Generate Code (13ms)

[12.87s] Bundled "src/js" for development
  2826 kb
  197 internal modules
  13 native modules
  91 internal functions across 17 files
[4/80] cargo bun_bin → libbun_rust.a (--target x86_64-unknown-linux-gnu)

  nightly-2026-07-20-x86_64-unknown-linux-gnu unchanged - rustc 1.99.0-nightly (9f36de775 2026-07-19)

[76/80] cxx obj/codegen/ZigGeneratedClasses.cpp.o
FAILED: rust-target/x86_64-unknown-linux-gnu/debug/libbun_rust.a 
/workspace/bun/build/release/bun /work
... (truncated)

release without fix: 2 skipped
bun test v1.4.0-canary.1 (eabb96d)

test/js/web/encoding/text-encoder-stream.test.ts:
(pass) encoding one string of UTF-8 should give one complete chunk [0.61ms]
(pass) a character split between chunks should be correctly encoded [0.06ms]
(pass) a character following one split between chunks should be correctly encoded [0.04ms]
(pass) two consecutive astral characters each split down the middle should be correctly reassembled [0.03ms]
(pass) two consecutive astral characters each split down the middle with an invalid surrogate in the middle should be correctly encoded [0.06ms]
(pass) a stream ending in a leading surrogate should emit a replacement character as a final chunk [0.02ms]
(pass) an unmatched surrogate at the end of a chunk followed by an astral character in the next chunk should be replaced with the replacement character at the start of the next output chunk [0.03ms]
(pass) an unmatched surrogate at the end of a chunk followed by an ascii character in the next chunk should be replaced with the replacement character at the start of the next output chunk [0.02ms]
(pass) an unmatched surrogate at the end of a chunk followed by a plane 1 character split int
... (truncated)
```

</details>

<details><summary>passes on PR (with fix)</summary>

```console
ASAN with fix: all passed
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/js/web/encoding/text-encoder-stream.test.ts
bun test v1.4.0 (3250eb4)

test/js/web/encoding/text-encoder-stream.test.ts:
(pass) encoding one string of UTF-8 should give one complete chunk [41.59ms]
(pass) a character split between chunks should be correctly encoded [8.27ms]
(pass) a character following one split between chunks should be correctly encoded [6.15ms]
(pass) two consecutive astral characters each split down the middle should be correctly reassembled [6.95ms]
(pass) two consecutive astral characters each split down the middle with an invalid surrogate in the middle should be correctly encoded [9.03ms]
(pass) a stream ending in a leading surrogate should emit a replacement character as a final chunk [4.68ms]
(pass) an unmatched surrogate at the end of a chunk followed by an astral character in the next chunk should be replaced with the replacement character at the start of the next output chunk [5.68ms]
(pass) an unmatched surrogate at the end of a chunk followed by an ascii character in the next chunk should be replac
... (truncated)

release with fix: 2 skipped
$ bun scripts/build.ts --profile=release
[configured] bun-profile → bun (stripped)
  target       linux-x64-gnu
  build type   Release
  build dir    ./build/release
  revision     3250eb4
  features     baseline

22 deps, 123 codegen, 1176 objects in 1165ms

ninja: Entering directory `/workspace/bun/build/release'
[1/1238] gen ErrorCode+*.h
[2/1238] gen bindgenv2
[3/1238] install /workspace/bun
bun install v1.4.0-canary.1 (eabb96d)

Checked 107 installs across 153 packages (no changes) [74.00ms]
[4/1238] install /workspace/bun/packages/bun-error
bun install v1.4.0-canary.1 (eabb96d)

Checked 1 install across 2 packages (no changes) [4.00ms]
[5/1238] fetch zlib
[zlib] up to date
[6/1238] gen .bind.ts → GeneratedBindings.cpp
[7/1238] fetch tinycc
[tinycc] up to date
[8/1237] fetch libjpeg-turbo
[libjpeg-turbo] up to date
[9/1237] install /workspace/bun/src/node-fallbacks
bun install v1.4.0-canary.1 (eabb96d)

Checked 129 installs across 147 packages (no changes) [55.00ms]
[10/1237] subst deps/zlib/zconf.h
[11/1237] subst deps/zlib/zlib.h
[12/1188] gen JSEvent.lut.h
Generating /workspace/bun/build/release/codegen/JSEvent.lut.h from /workspace/bun/src/jsc/
... (truncated)
```

</details>

<details><summary>diff hotspot</summary>

```
src/bun_core/string/immutable/unicode.rs         |  17 +--
 src/runtime/webcore/TextEncoderStreamEncoder.rs  |  49 ++++---
 test/js/web/encoding/text-encoder-stream.test.ts | 163 ++++++++++++++++++++++-
 3 files changed, 195 insertions(+), 34 deletions(-)
```

</details>

**gate history** · 1 passed · 0 rejected · iteration 0

<details><summary>evidence per changed file</summary>

```
file                                              reads  edits  tests
src/bun_core/string/immutable/unicode.rs              5      5      0
src/runtime/webcore/TextEncoderStreamEncoder.rs       4     11      0
test/js/web/encoding/text-encoder-stream.test.ts      3      7      0
```

</details>

<!-- robobun:evidence:end -->

This branch has not been deployed

No deployments
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.

2 participants