forked from oven-sh/bun
-
Notifications
You must be signed in to change notification settings - Fork 0
Open
Conversation
github-actions
Bot
force-pushed
the
deps/update-sqlite
branch
from
March 15, 2026 06:17
01a2084 to
e960c93
Compare
@github-actions
github-actions
Bot
changed the title
(削除) deps: update sqlite to 3.52.0 (削除ここまで)
(追記) deps: update sqlite to 3.51.300 (追記ここまで)
Mar 15, 2026
igorls
pushed a commit
that referenced
this pull request
Apr 9, 2026
Fixes a segfault when reading `.fd` on the result of `Bun.listen({ tls:
{ ... } })`.
`Listener.getFD` was calling `uws_listener.socket(true).fd()` for TLS
listeners. For `is_ssl=true`, the uSockets wrapper
`us_internal_ssl_socket_get_native_handle` returns `s->ssl`, and `fd()`
then calls `SSL_get_fd()` on it. But a listen socket has no SSL object —
SSL is per-connection — so `s->ssl` is uninitialized memory (ASAN poison
`0xbebebe...`) and the call segfaults.
Listen sockets always have a plain poll fd regardless of TLS, so get it
via the non-SSL path.
```
#3 SSL_get_rfd (ssl=0xbebebe0000000018)
#4 SSL_get_fd (ssl=0xbebebe0000000018)
#5 deps.uws.socket.NewSocketHandler(true).fd () at src/deps/uws/socket.zig:283
bun.js.api.bun.socket.Listener.getFD at src/bun.js/api/bun/socket/Listener.zig:532
```
Repro (also triggered when `console.log()` introspects the listener):
```js
const s = Bun.listen({
hostname: "localhost", port: 0,
socket: { data(){}, open(){}, close(){} },
tls: { passphrase: "abc" },
});
console.log(s.fd);
```
Found by Fuzzilli.
---------
Co-authored-by: robobun <robobun@users.noreply.github.com>
@github-actions
github-actions
Bot
changed the title
(削除) deps: update sqlite to 3.51.300 (削除ここまで)
(追記) deps: update sqlite to 3.53.0 (追記ここまで)
Apr 12, 2026
github-actions
Bot
force-pushed
the
deps/update-sqlite
branch
from
April 12, 2026 06:28
e960c93 to
3967cfd
Compare
igorls
pushed a commit
that referenced
this pull request
May 6, 2026
×ばつ `Response.clone`/`blob.slice`/`createObjectURL`+revoke/`new Response([blob])`/`write({type})` — no double-free - RSS is flat across 50k ×ばつ 1KB-type `Response.clone()` and `blob.slice()` --------- Co-authored-by: robobun <robobun@users.noreply.github.com> Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>" data-pjax="true" href="/index.cgi/contrast/https://github.com/igorls/bun/commit/0f42aedbcfbb691b04db79fca807744c550a6ff8">blob: stop aliasing allocated content_type in dupeWithContentType ( ov...
...en-sh#29910) ## What `Blob.dupeWithContentType` guarded its content_type handling on `duped.isHeapAllocated()` immediately *after* calling `duped.setNotHeapAllocated()`, so both branches were dead. When the source Blob's `content_type` is heap-allocated, the bitwise-copied dupe aliased the same allocation while both sides had `content_type_allocated == true`. This is a regression from oven-sh#23015: the pre-refactor code checked `duped.allocator != null` *before* clearing it at the end of the function; the refactor moved the clear to the top but left the (renamed) guard in place. ## Repro ```js const file = Bun.file(path, { type: "application/x-custom-type-not-in-registry-abcdefghijklm" }); const response = new Response(file); // body holds a dupe that aliases file.content_type await file.write("hello", { type: "application/x-..." }); // frees file.content_type response.headers.get("content-type"); // reads freed memory ``` On ASAN builds: ``` ==716==ERROR: AddressSanitizer: use-after-poison on address 0x71df454301c0 #1 in Zig::toStringCopy(ZigString) helpers.h:217 #2 in WebCore__FetchHeaders__put bindings.cpp:2082 #5 in bun.js.webcore.Response.getOrCreateHeaders Response.zig:358 ``` On release builds the freed slot gets reused and the read produces garbage: ``` TypeError: Header '25' has invalid value: 'ion/x-custom-type-not-in-registry-abcdefghijklm' ``` ## Fix Drop the `isHeapAllocated()` guard and always deep-copy an allocated `content_type` in `dupeWithContentType`. The old `!include_content_type` branch's "resolve to static mime or fall back to empty" is gone — it would have dropped FormData's `multipart/form-data; boundary=...` (and any non-registry type) on `Response.clone()`, and the branch itself was marked `// TODO: fix this / this is a bug`. The `include_content_type` parameter is now a no-op. Since every dupe now owns its `content_type` copy, `Blob.deinit()` frees it. That in turn required closing a few places that held a bitwise-copied Blob alongside the live owner: - `fromJSWithoutDeferGC` `move=true`: deep-copy `name`/`content_type` into the moved-out value so the source JS Blob keeps sole ownership; the BuildArtifact arm now `dupe()`s (its "move" only nulled the store on a local copy). - `getSliceFrom()`: free the dupe's copy before overwriting it with the slice's own type. - `doWrite`/`getWriter`: clear `content_type_allocated` after the in-place free so a registry-resolved static string isn't later freed by `deinit()`. - `BlobOrStringOrBuffer.deinitAndUnprotect`: only deref the store (matching its `deinit()`) since `.blob` is a raw view of a live JS Blob. ## Verified - `bun bd test test/js/web/fetch/blob.test.ts` — 16/16 pass - New UAF test fails on both debug/ASAN (use-after-poison) and system bun (garbage header) without the fix, passes with it - New clone test guards against dropping FormData's boundary on `Response.clone()` - ASAN stress: 1k×ばつ `Response.clone`/`blob.slice`/`createObjectURL`+revoke/`new Response([blob])`/`write({type})` — no double-free - RSS is flat across 50k ×ばつ 1KB-type `Response.clone()` and `blob.slice()` --------- Co-authored-by: robobun <robobun@users.noreply.github.com> Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
igorls
pushed a commit
that referenced
this pull request
May 6, 2026
...es (oven-sh#30077) ## What When a chunked (or HTTP/3) request body exceeds `maxRequestBodySize`, `onBufferedBodyChunk` writes the 413 directly on the raw uWS response: ```zig resp.writeStatus("413 Payload Too Large"); resp.endWithoutBody(comptime !http3); ``` `internalEnd` → `markDone()` nulls `onAborted`, so when the socket closes no abort ever fires to detach `ctx.resp` or release the base ref. `this.resp` is left pointing at a completed response whose socket is about to be freed by `us_internal_free_closed_sockets`. If the fetch handler returned a pending Promise: - **resolve**: `handleResolve` → `isAbortedOrEnded()` is false (`this.resp != null`) → `render()` → `runCorkedWithType` corks the freed socket → **heap-use-after-free** (ASAN trace below). - **reject**: `handleReject` reads `resp.hasResponded()` off freed memory, sees `true`, skips the error handler, and returns without ever releasing the base ref → **RequestContext leaks** (`server.pendingRequests` never returns to 0). ## Fix Route through `this.endWithoutBody()` (the `RequestContext` wrapper) instead of the raw `resp.endWithoutBody()`. That path does `detachResponse()` (nulls `this.resp`, clears `onData`/`onAborted`/`onTimeout`) and `deref()` (releases the base ref), matching every other end path in this file. The body promise is rejected with the specific `"Request body exceeded maxRequestBodySize"` error *before* `endWithoutBody()` so `endRequestStreaming()` doesn't overwrite it with a generic `ConnectionClosed`. `has_written_status` is set so any later `renderMissing`/`renderMetadata` knows the status line is already committed. ## Repro ``` ==ERROR: AddressSanitizer: heap-use-after-free #0 us_socket_group socket.c:77 #1 uWS::AsyncSocket<false>::getLoopData() AsyncSocket.h:69 #2 uWS::AsyncSocket<false>::isCorked() AsyncSocket.h:141 #3 uWS::HttpResponse<false>::cork(...) HttpResponse.h:647 #4 uws_res_cork libuwsockets.cpp:1740 #5 ...runCorkedWithType Response.zig:299 #6 ...doRenderBlob RequestContext.zig:1942 ... oven-sh#11 ...handleResolve RequestContext.zig:220 oven-sh#12 ...onResolve RequestContext.zig:154 freed by: #1 us_poll_free epoll_kqueue.c:73 #2 us_internal_free_closed_sockets loop.c:305 ``` ## Test `test/js/bun/http/serve-pending-promise-abort-leak.test.ts` — new case sends a raw `Transfer-Encoding: chunked` POST exceeding `maxRequestBodySize` with a handler that holds its resolve/reject, waits for the socket to be reclaimed, then settles the Promise. Asserts `pendingRequests` returns to 0 for both paths, the body was rejected with the right message, and a follow-up request still works. Without the fix: ASAN heap-use-after-free on the resolve path; on release builds the reject path shows `pendingAfterReject: 1` (leak). Co-authored-by: robobun <robobun@users.noreply.github.com>
igorls
pushed a commit
that referenced
this pull request
May 6, 2026
...ven-sh#30136) ## Repro ```js const client = await Bun.connect({ hostname, port, tls, socket: { ... } }); // after handshake: client.end("x"); client.flush(); // ← second markInactive frees *Handlers // peer replies close_notify → onClose derefs freed Handlers ``` ASAN on debug build: ``` ==4075==ERROR: AddressSanitizer: use-after-poison on address 0x7aff355e0469 READ of size 1 at 0x7aff355e0469 thread T0 #0 bun.js.api.bun.socket.NewSocket(true).onClose src/bun.js/api/bun/socket.zig:661:46 #1 deps.uws.handlers.PtrHandler(...).onClose src/deps/uws/handlers.zig:49:61 ... #5 us_internal_ssl_on_close packages/bun-usockets/src/crypto/openssl.c:940:29 ``` ## Cause `end()` → `internalFlush` → `canEndAfterFlush()` → `markInactive()` → `closeAndDetach(.normal)` detaches `this.socket` and calls `us_socket_close(code=0)`. For TLS with `code==0`, `us_internal_ssl_close` sends close_notify and **defers** the raw close until the peer replies (so the loop stays alive to receive it). `markInactive` returns early without clearing `is_active`, relying on the eventual `onClose` → `markInactive` to run `handlers.markInactive()` and free the client-mode `*Handlers`. `flush()` was the only `internalFlush()` caller without an `isDetached()` guard. Calling it in that window re-enters `canEndAfterFlush()` (still `is_active && end_after_flush`) → `markInactive()`, which now sees the detached socket as closed and runs the **full** teardown: `handlers.markInactive()` → `active_connections == 0` → `vm.allocator.destroy(handlers)`. When the peer's close_notify later arrives, `onClose` calls `this.getHandlers()` on freed memory. ## Fix Add the same `isDetached()` early-return to `flush()` that `end()`, `endBuffered()`, `onWritable`, and every other `internalFlush()` caller already have. ## Verification New test in `test/js/bun/net/socket.test.ts` spawns a TLS client that does `end("x"); flush(); flush();` after handshake and awaits `close`. - Without fix (`git stash -- src/`): subprocess aborts with the ASAN trace above; test fails. - With fix: subprocess prints `OK` and exits 0; test passes. Co-authored-by: robobun <robobun@users.noreply.github.com>
igorls
pushed a commit
that referenced
this pull request
May 6, 2026
...before write (oven-sh#30155) ## Repro ```js Bun.serve({ port: 0, fetch: () => new Response("hello", { headers: [ ["Transfer-Encoding", "gzip"], ["Transfer-Encoding", "chunked"], ], }), }); // HEAD / → ASAN heap-use-after-free in uWS::HttpResponse::writeHeader ``` The duplicate entries make `FetchHeaders` combine them via `makeString()`, producing a `StringImpl` held only by the header map — the minimal condition for the free to actually happen. StringImpl is allocated via bmalloc which ASAN doesn't instrument by default; with `Malloc=1` (bmalloc → system heap) the debug build reports: ``` AddressSanitizer: heap-use-after-free READ of size 13 #2 uWS::HttpResponse<false>::writeHeader #5 doRenderHeadResponse RequestContext.zig:1378 freed by: oven-sh#23 HTTPHeaderMap::remove oven-sh#28 doWriteHeaders RequestContext.zig:2303 oven-sh#29 renderMetadata RequestContext.zig:2209 oven-sh#30 doRenderHeadResponse RequestContext.zig:1377 ``` ## Cause `doRenderHeadResponse()` calls `headers.fastGet(.TransferEncoding)`, which returns a `ZigString` that **borrows** the header map entry's `StringImpl` bytes (no ref taken). For an ASCII value, `toSlice()` also borrows rather than copying. It then calls `this.renderMetadata()`, whose `doWriteHeaders()` does `headers.fastRemove(.TransferEncoding)` (and `renderMetadata` also `swapInitHeaders()` + `deref()`s the whole `FetchHeaders`). When the map held the only reference to the `StringImpl`, it's destroyed right there — and the very next line `resp.writeHeader("transfer-encoding", transfer_encoding_str.slice())` writes the freed bytes to the socket. The adjacent `Content-Length` branch has the same bug: `std.fmt.parseInt()` runs on the borrowed slice *after* `renderMetadata()` has already `fastRemove(.ContentLength)`'d it. ## Fix - **Transfer-Encoding**: use `toSliceClone()` instead of `toSlice()` so the value is owned and survives `renderMetadata()`. - **Content-Length**: parse the integer *before* `renderMetadata()` (and drop the slice immediately), so the borrowed bytes are never touched after the header entry is removed. No extra allocation needed since only the parsed `usize` is used afterwards. ## Verification New test in `test/js/bun/http/bun-server.test.ts` (inside the existing `HEAD requests oven-sh#15355` block) spawns a subprocess with `Malloc=1` (non-Windows), serves HEAD responses whose Transfer-Encoding / Content-Length values are `makeString()`-combined (sole-owner StringImpl), and asserts the raw wire output. ``` git stash push -- src/ → test fails with "AddressSanitizer: heap-use-after-free" in stderr git stash pop → test passes ``` All other tests in the `HEAD requests oven-sh#15355` describe block continue to pass. Co-authored-by: robobun <robobun@users.noreply.github.com>
github-actions
Bot
force-pushed
the
deps/update-sqlite
branch
from
May 10, 2026 06:50
3967cfd to
bf1e289
Compare
@github-actions
github-actions
Bot
changed the title
(削除) deps: update sqlite to 3.53.0 (削除ここまで)
(追記) deps: update sqlite to 3.53.100 (追記ここまで)
May 10, 2026
igorls
pushed a commit
that referenced
this pull request
May 31, 2026
...er (oven-sh#31333) ### Problem Fuzzing found a second transpiler stack overflow (`sig:SIGSEGV:nostack`): ~600 nested `{` blocks crash the process. ```js new Bun.Transpiler({ loader: "tsx", target: "bun", minifyWhitespace: true, deadCodeElimination: true }) .transformSync("{".repeat(600) + 'class Test1 { static "prop1" = 0; }' + "}".repeat(600)); ``` oven-sh#31242 guarded the **expression** recursion (`visit_expr_in_out`, `print_expr`, DCE helpers), but the **statement** recursion was left unguarded. Nested blocks stay under `MAX_STMT_DEPTH` (1000) in `parse_stmt`, then the visit pass recurses through `visit_stmts → visit_and_append_stmt → s_block → visit_stmts` with no stack check — each level stacks several multi-KB frames, so a few hundred levels exhaust the thread's stack (reproduces at depth 800 on a debug build's 8 MB main stack; smaller stacks crash at 600): ``` #5 visit_stmts src/js_parser/visit/mod.rs:1280 #6 s_block src/js_parser/visit/visit_stmt.rs:1627 #7 visit_and_append_stmt src/js_parser/visit/visit_stmt.rs:108 #8 visit_stmts src/js_parser/visit/mod.rs:1336 ... (repeats until SIGSEGV) ``` ### Fix Guard the statement recursion the same way the expression recursion already is: - `visit_and_append_stmt` now checks `stack_check.is_safe_to_recurse()` (plus the `reported_stack_overflow` fast-path) and reports "Maximum call stack size exceeded" instead of descending, mirroring `visit_expr_in_out`. - `print_stmt` and `print_if` (which self-recurses for `else if` chains without passing through `print_stmt`) get the same guard `print_expr`/`print_binding` already have, so a deep AST printed on a thread with less stack headroom errors instead of overflowing. - Removed the `MAX_STMT_DEPTH`/`parse_stmt_depth` hard cap from `parse_stmt` (review feedback): recursion depth in every phase is now governed by `StackCheck` alone, matching the Zig parser. - Guarded `hoist_symbols` the same way: it walks the scope tree before the visit pass at the full depth the parser allowed, and was only kept safe previously by the now-removed cap (the 15k-deep `lots-of-for-loop.js` fixture overflowed it in release builds otherwise). With this, every arbitrarily-nestable AST recursion (statements, expressions, bindings) is stack-checked in all three phases (parse, visit, print); deep inputs throw a catchable `Maximum call stack size exceeded` error. ### Verification New test `deeply nested statement blocks error instead of crashing the process` in `test/bundler/transpiler/transpiler.test.js` transpiles nested-block and `else if`-chain shapes at depths 600/800/990 (below the parse-time cap, deep enough to overflow an unguarded visitor) in a subprocess and asserts it exits cleanly. - Without the fix: the subprocess dies with SIGSEGV at depth 800+ (debug build), so the test fails. - With the fix: `bun bd test test/bundler/transpiler/transpiler.test.js` → 147 pass, 0 fail; the repro above now throws `Maximum call stack size exceeded`.
github-actions
Bot
force-pushed
the
deps/update-sqlite
branch
from
June 7, 2026 07:18
bf1e289 to
c02bb39
Compare
@github-actions
github-actions
Bot
changed the title
(削除) deps: update sqlite to 3.53.100 (削除ここまで)
(追記) deps: update sqlite to 3.53.200 (追記ここまで)
Jun 7, 2026
@github-actions
github-actions
Bot
changed the title
(削除) deps: update sqlite to 3.53.200 (削除ここまで)
(追記) deps: update sqlite to 3.53.300 (追記ここまで)
Jun 28, 2026
github-actions
Bot
force-pushed
the
deps/update-sqlite
branch
from
June 28, 2026 07:15
c02bb39 to
6e9d880
Compare
github-actions
Bot
force-pushed
the
deps/update-sqlite
branch
from
July 26, 2026 06:51
6e9d880 to
7940968
Compare
@github-actions
github-actions
Bot
changed the title
(削除) deps: update sqlite to 3.53.300 (削除ここまで)
(追記) deps: update sqlite to 3.53.400 (追記ここまで)
Jul 26, 2026
igorls
pushed a commit
that referenced
this pull request
Aug 21, 2026
oven-sh#35255) `test/js/bun/http/serve-protocols.test.ts` has been going red on main (build 78445 darwin-x64 hard, 78462 debian-11-aarch64 hard, plus 78300/78419 with retries), always as ``` error: HTTP3StreamReset fetching "https://127.0.0.1:<port>/echo" ✗ Bun.serve over http/3 > POST echo 1000000 bytes [20169.37ms] ``` Reproduced on Linux by looping the file: the h3 subset alone fails about 17 of 100 runs. ## Cause The ten concurrent h3 tests share one lsquic client engine and one unconnected UDP socket. `bsd_create_udp_socket()` sets `IP_RECVERR` on every UDP socket (for `node:dgram`'s error surfacing, oven-sh#28827), including QUIC's. When a finished test `proc.kill()`s its server, the client session is still in the engine and keeps scheduling retransmits / `NEW_CONNECTION_ID` to an unbound port. With `IP_RECVERR` on, the resulting ICMP port-unreachable is queued on the shared socket and the next `sendmmsg` returns `-1 ECONNREFUSED`, even though that call is sending a datagram to a live peer. `us_quic_packets_out` reports that as a short return, lsquic clears `ENPUB_CAN_SEND` for the whole engine and only its one-second `resume_sending_at` failsafe re-enables it. With several dead sessions generating ICMPs, every failsafe retry fails the same way and the live 1MB upload never advances; the 20s in CI is two idle-timeout rounds through `retry_or_fail`. While tracing that I also found an unsigned underflow in lsquic's `send_batch` requeue loop: when the first unsent spec in a batch coalesces multiple packets (`pack_off[0] == 0`, `iovlen > 1`), `end = &batch->packets[off - 1]` indexes with `UINT_MAX` and only the last packet of the coalesced group is returned to the connection. The earlier ones are the INIT ACK and the HSK CRYPTO carrying the client Finished, so the peer can never complete the handshake. This is the same hang reached from a different direction (real EAGAIN backpressure instead of stale ICMP). ## Fix - `IP_RECVERR` is now opt-in via `LIBUS_UDP_LINUX_RECVERR`, set by `us_create_udp_socket` when a `recv_error_cb` is provided. `node:dgram` always passes one and keeps the option; QUIC passes `NULL` and no longer gets it. This matches libuv's `UV_UDP_LINUX_RECVERR` gating that `bsd.c` already cited. - `us_quic_packets_out()` retries once on a non-`EAGAIN`/`ENOBUFS` send failure before reporting a short return, so a stale `sk_err` that does surface cannot pause the engine. Both the `sendmmsg` and per-packet paths now go through `US_FAULT_CHECK(US_FAULT_SENDMSG, ...)` so the short-return path is reachable from tests. - `patches/lsquic/requeue-unsent-coalesced.patch` rewrites the requeue loop's bounds as `[off, off+count)` so every packet in an unsent coalesced datagram is returned to the connection. The same underflow is present in upstream lsquic master; I will open a PR there separately. - `serve-protocols.test.ts` now stops each fixture server gracefully on stdin close (`server.stop(true)`), matching `serve-http3.test.ts`, so the pooled client session sees `CONNECTION_CLOSE` instead of leaving the engine retransmitting to unbound ports. - `test/js/web/fetch/fetch-http3-syscall-fault.test.ts` injects `EAGAIN` on the coalesced handshake datagram (the `pack_off[0]==0`, `iovlen>1` spec the lsquic patch fixes), a one-shot `ECONNREFUSED` that the retry-once branch consumes, and a burst of `EAGAIN` that the `on_drain` path recovers from. ## Verification Release build, looped: | | before | after | | --- | --- | --- | | `serve-protocols -t "http/3"` | 17/100 fail | 2/100 fail | | `serve-protocols` (full) | 7/100 fail | 4/200 fail | Debug+ASAN: `serve-protocols`, `serve-http3` (46), `fetch-http3-client` (52), `fetch-http3-adversarial` (29), `fetch-http3-syscall-fault` (3) and `dgram.test.ts` all pass, 211 tests total. The residual ~1-2% is a separate pre-existing bug (the client's 36-byte HSK CRYPTO is buffered but never flushed when `drain_send_body` writes the whole 1MB body synchronously from `on_stream_open`); I've handed that off as its own issue. With CI's retry it is well under the flake threshold. ### Gate note The fault-injection hook that makes the new test deterministic lives in `packages/bun-usockets/src/quic.c`, so `git stash -- src/ packages/` removes it along with the fix and the fault never fires. The lsquic piece lives in `patches/` and `scripts/`, which the stash does not touch. That means a single stashed run passes (no fault, no stall) and a single unstashed run passes (fault fires, fix handles it), and the gate cannot distinguish them mechanically. The 300-iteration probe above is the evidence; the fault-injection tests pin the behavior going forward. <details> <summary>lsquic debug trace of the stall</summary> ``` engine: packets out returned 0 (out of 1) [C919...] event: unsent packet oven-sh#15 ACK_FREQUENCY, size 36 [C919...] sendctl: packet oven-sh#15 has been delayed engine: send_packets_out: sent 0 packets ... <- no "can send again"; nothing for 1s engine: failsafe activated: resume sending packets again after timeout engine: packets out returned 0 (out of 10) <- fails again, live conn's oven-sh#207 included ``` and for the underflow, a batch with `pack_off[0]=0`, `iovlen[0]=3`: ``` engine: packets out returned 0 (out of 2) event: unsent packet #3 ACK PADDING, size 1059 event: unsent packet #4 ACK CRYPTO, size 87 event: unsent packet #5 NEW_CONNECTION_ID, size 54 event: unsent packet #6 STREAM, size 114 sendctl: packet #6 has been delayed sendctl: packet #5 has been delayed ... <- #3 and #4 never requeued [WARN] sendctl: send history gap 2 - 5 ``` </details> <!-- robobun:evidence:begin --> --- **no test proof** · iteration 1 · Platform-specific test(s) that do not run on this machine. Deferring to CI, which covers all platforms: test/js/web/fetch/fetch-http3-syscall-fault.test.ts <!-- robobun:evidence:end --> --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
igorls
pushed a commit
that referenced
this pull request
Aug 21, 2026
...rier (oven-sh#36337) `JSNativeStreamSourceAdapter::m_controller` was a `JSC::Weak<JSReadableStreamDefaultController>`. When the native pull promise is rejected (socket fault on a fetch body) the adapter is queued as the `onNativePullRejected` reaction context, which roots the **adapter** but not the **controller**: the adapter's only edge to it was the `Weak`. `FetchTasklet` releases both native `Strong<>`s to the body stream before that microtask drains, so a GC in between can leave the entire consumer graph (`controller -> stream -> reader -> pipe op -> destination -> writer -> readyPromise`) white. The subsequent error cascade then enqueues the pipe's writes-drained shutdown deferral against a corpse `op`, and `performPipeShutdownAction(AbortDestination)` dereferences a swept `readyPromise`: ``` ASSERTION FAILED: result JSObject.h(583) JSGlobalObject *JSC::JSObject::realm() const #5 JSC::JSObject::realm() #6 JSC::JSPromise::rejectPromise #7 JSC::JSPromise::reject #8 Bun::WebStreams::writableStreamDefaultWriterEnsureReadyPromiseRejected #9 Bun::WebStreams::writableStreamStartErroring oven-sh#10 Bun::WebStreams::writableStreamAbort oven-sh#11 WebCore::performPipeShutdownAction (AbortDestination) oven-sh#12 WebCore::JSStreamPipeToOperation::onWritesFinishedForShutdown ``` On builds without the assert the same path is a silent write into freed/reused promise memory. ## Fix Hold `m_controller` as a visited internal field so a queued adapter roots the controller directly. The edge is cleared on every terminal path (`nativeSourcePullRejected`, `nativeSourceCallClose`, `nativeSourceCancel`); `controller->algorithmContext` is cleared by `readableStreamDefaultControllerClearAlgorithms`, so the abandoned case is an ordinary intra-heap cycle mark-sweep collects. `NewSource::this_jsvalue` is only `Strong` during FileReader I/O, where pinning the consumer graph is the correct behavior anyway. With the `Weak` gone the adapter no longer needs a destructor, so it is now a `JSInternalFieldObjectImpl<5>`: the five JSValue members (handle, pendingView, closer, drainValue, controller) are internal fields visited by the base class, with typed accessors at call sites. The scalar members (chunkSize, flag bitfield, text-decode state) stay as plain members. ## Verification `native-source-onclose-leak.test.ts` (the partial-read + `releaseLock` abandonment tests for Blob/fetch/File sources) continues to pass, confirming the cycle does not pin. `streams.test.js`, `pipeTo-signal-leak.test.ts`, `compression.test.ts`, `blob.test.ts` all pass. The crash itself is 0/1800 standalone; it reproduces ~1/3 only under a fault-injected tracer replay. `pipeTo-shutdown-gc.test.ts` exercises the shape (native body source, socket fault mid-stream, fire-and-forget `pipeTo` under `collectContinuously`, `AbortDestination` shutdown arm) as a regression surface. <!-- robobun:evidence:begin --> --- **no test proof** · iteration 2 · Platform-specific test(s) that do not run on this machine. Deferring to CI, which covers all platforms: test/js/web/streams/pipeTo-shutdown-gc.test.ts <!-- robobun:evidence:end -->
igorls
pushed a commit
that referenced
this pull request
Aug 21, 2026
...e cache (oven-sh#37034) ### Problem On the `13 x64-asan` lane, a test that exercises non-ISO Temporal calendars from a test callback can abort after a fully green run with a LeakSanitizer report. Seen in build 89504 on oven-sh#37024, whose `test/js/bun/bun-object/deep-equals-temporal.test.ts` uses `[u-ca=hebrew]`: ``` Direct leak of 624 byte(s) in 1 object(s) allocated from: #1 icu_75::HebrewCalendar::clone() const #2 icu_75::Calendar::createInstance(icu_75::TimeZone*, icu_75::Locale const&, UErrorCode&) #3 ucal_open_75 #4 JSC::TemporalCore::buildCalendarTemplate(WTF::AbstractLocker const&, unsigned int) #5 JSC::TemporalCore::withCalendar<JSC::TemporalCore::calendarYear(...)::$_0>(...) ``` The CI annotation titles this `direct leak of 624b in {closure#0} (src/jsc/JSValue.rs:1664:22)` because that is the first in-repo frame (the test-runner's `JSValue::call`); everything below it is WebKit/ICU. ### Cause `TemporalCore::withCalendar` (`vendor/WebKit/.../temporal/core/CalendarICUBridge.cpp`) keeps up to 8 open `UCalendar` templates in a process-lifetime `LazyNeverDestroyed` `TinyLRUCache`, one per calendar ID (non-ISO arithmetic, plus pure-ISO `PlainDateTime.prototype.with`, which reaches the same path unguarded); LRU eviction `ucal_close`s them, so the set is bounded. The `CalendarCacheEntry` that owns each `UCalendar` is `WTF_MAKE_TZONE_ALLOCATED` (bmalloc), which LSan does not scan, so the libc-allocated `UCalendar` (and the ICU `TimeZone` inside it) is reported as a direct leak even though it is reachable. Whether a given run aborts depends on whether some stale stack or register value still points at the ICU object when LSan scans at exit, hence the intermittence. This is the calendar twin of the already-suppressed `TemporalCore::withTimeZone` entry (same cache design, same TZone-allocated owner). ### Fix - Add a `leak:TemporalCore::buildCalendarTemplate` suppression to `test/leaksan.supp`, mirroring the `withTimeZone` entry. The pattern anchors on the template builder rather than `withCalendar` itself so that a future real leak inside one of the many op lambdas `withCalendar` runs would still be reported; every cached-template allocation carries the builder frame. (`withTimeZone` has no such builder frame, its `ucal_open` is inline, so that entry keeps its existing pattern.) - Drop the `test/no-validate-leaksan.txt` escape hatch oven-sh#37024 added for `deep-equals-temporal.test.ts`, re-enabling leak validation for it; that file exercises the suppressed path on the asan lane. ### Verification On a debug ASAN build, running `bun test test/js/bun/bun-object/deep-equals-temporal.test.ts` under the CI leak-validation env (`BUN_DESTRUCT_VM_ON_EXIT=1`, `detect_leaks=1:abort_on_error=1`, repo suppression file): - with the new entry: clean exit, 5/5 runs - without it: LSan abort with the calendar-template stacks above, 3/3 runs A standalone probe exercising 8 non-ISO calendars plus pure-ISO `PlainDateTime.with` from a timer callback shows the same split (10/10 aborts without, 10/10 clean with; `print_suppressions=1` attributes exactly the ICU template allocations to the new entry). Top-level module code cannot reproduce this: its allocation stacks carry `JSC::JSModuleLoader::evaluateNonVirtual`, which the suppression file already covers wholesale. An ASAN-gated test pinning the entry was part of an earlier revision and was dropped per review; the re-enabled `deep-equals-temporal.test.ts` covers the path in CI instead. The Expect-wrapper shutdown leak mentioned in the dropped no-validate comment is a separate issue tracked in oven-sh#32180: that is `bun test`'s own finalizer-owned memory, while this cache deliberately survives VM teardown, so oven-sh#32180 would not prevent this report. <!-- robobun:evidence:begin --> --- **no test proof** · iteration 1 · docs-only change; test-proof not applicable <!-- robobun:evidence:end --> --------- Co-authored-by: Dylan Conway <dylan.conway567@gmail.com>
igorls
pushed a commit
that referenced
this pull request
Aug 21, 2026
...ad transforms (oven-sh#37139) ### Repro `CompressionStream('brotli')` with a chunk over 128 KiB runs the codec step on a WorkPool thread. Tearing down the VM while that step is in flight frees the native coder under the pool thread: ```js // ASAN build, BUN_DESTRUCT_VM_ON_EXIT=1 (the CI test runner sets this) const s = new CompressionStream("brotli"); const w = s.writable.getWriter(); const big = new Uint8Array(6 << 20); for (let i = 0; i < big.length; i += 3) big[i] = (i * 2654435761) >>> 24; w.write(big).catch(() => {}); w.close().catch(() => {}); s.readable.getReader().read().catch(() => {}); setTimeout(() => process.exit(0), 15); ``` ``` ==ERROR: AddressSanitizer: heap-use-after-free ... thread (Bun Pool 0) #0 UpdateNodes vendor/brotli/c/enc/backward_references_hq.c:468 ... #4 BrotliEncoderCompressStream vendor/brotli/c/enc/encode.c:1661 #5 CompressionStreamCoder::transform src/runtime/webcore/CompressionStreamCoder.rs:367 freed by: BrotliEncoderDestroyInstance CompressionStreamCoder__destroy JSCompressionStream.cpp:176 (CFinalizer) JSC::Heap::CFinalizerOwner::finalize -> Heap::lastChanceToFinalize ``` The same free-under-the-pool-thread happens on `worker.terminate()` / `process.exit()` inside a worker while a large write is in flight (`WebWorker::shutdown` -> `WebWorker__teardownJSCVM` -> `lastChanceToFinalize`). Other faces of the same report: READ 1 in `BrotliEstimateBitCostsForLiterals` / `UpdateNodes`, WRITE 4 in `StoreAndFindMatchesH10`. `DecompressionStream` has the identical finalizer shape, and zstd/zlib formats share the path. ### Cause The stream cell's CFinalizer (registered in the constructor) destroys `m_coder` unconditionally. During normal operation the in-flight task's `Strong` root keeps the cell from being swept, and the eager ClearAlgorithms release already defers on `m_asyncCodecInFlight`. But `Heap::lastChanceToFinalize` at VM teardown runs every finalizer regardless of roots, so the coder (brotli ring buffer + hasher, zlib window, zstd ctx) is freed while the pool thread is still inside `transform`. ### Fix Reference-count the coder. The JS cell holds one reference, released where it released before (finalizer, or the eager ClearAlgorithms path; both already null the cell's pointer first, so `CompressionStreamCoder__destroy` keeps its signature and call sites). Each in-flight `CompressionAsyncCtx` takes its own reference when the async step is scheduled and drops it with the ctx on the JS thread. The backend is freed when the last reference drops, so teardown releases the cell's hold but can no longer free the state under the pool thread. On the teardown paths where the completion never gets delivered, the coder is abandoned with the dying process instead of freed early, which is the bounded-leak tradeoff the worker teardown path already takes elsewhere. Related: oven-sh#36983 fences `WebWorker::shutdown` on outstanding off-thread jobs, which closes the worker-terminate door from the other side (and is still needed for it: after this change, the worker repro's surviving report moves to `EventLoop::enqueue_task_concurrent` via `WorkTask::on_finish` on the freed worker loop, which is exactly the bug that PR addresses, now with a `WorkTask` stack). This change covers what the fence cannot: the main-thread `BUN_DESTRUCT_VM_ON_EXIT=1` exit path, and the coder's own lifetime independent of teardown ordering. ### Verification - New test in `test/js/web/streams/compression.test.ts` (ASAN-gated): fails on the unfixed build with the ASan report above, passes with the fix. - Main-thread repro: 5/5 clean runs with the fix (was UAF on every run before). - `test/js/web/streams/compression.test.ts` (37), `test/regression/issue/18413-all-compressions.test.ts`, `test/regression/issue/23314/zstd-large-decompression.test.ts`, and the four node webstreams compression compat tests all pass. <!-- robobun:evidence:begin --> --- **[review]** gate passed · iteration 0 · 4 files touched <details><summary>fails on main (without fix)</summary> ```console ASAN without fix: 1 FAILED $ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/js/web/streams/compression.test.ts bun test v1.4.0 (38b3183) test/js/web/streams/compression.test.ts: (pass) TransformStream.prototype getters reject native transform subclasses (0) [13.39ms] (pass) TransformStream.prototype getters reject native transform subclasses (1) [2.50ms] (pass) TransformStream.prototype getters reject native transform subclasses (2) [2.66ms] (pass) TransformStream.prototype getters reject native transform subclasses (3) [2.27ms] (pass) CompressionStream and DecompressionStream > brotli > compresses data with brotli [15.13ms] (pass) CompressionStream and DecompressionStream > brotli > decompresses brotli data [21.20ms] (pass) CompressionStream and DecompressionStream > brotli > round-trip compression with brotli [51.32ms] (pass) CompressionStream and DecompressionStream > zstd > compresses data with zstd [9.46ms] (pass) CompressionStream and DecompressionStream > zstd > decompresses zstd data [18.82ms] (pass) CompressionStream and DecompressionStream > zstd > round-trip compression with zstd [36.88ms] ( ... (truncated) release without fix: 1 skipped bun test v1.4.0-canary.1 (0ac8ea9) test/js/web/streams/compression.test.ts: (pass) TransformStream.prototype getters reject native transform subclasses (0) [0.42ms] (pass) TransformStream.prototype getters reject native transform subclasses (1) [0.07ms] (pass) TransformStream.prototype getters reject native transform subclasses (2) [0.06ms] (pass) TransformStream.prototype getters reject native transform subclasses (3) [0.03ms] (pass) CompressionStream and DecompressionStream > brotli > compresses data with brotli [0.91ms] (pass) CompressionStream and DecompressionStream > brotli > decompresses brotli data [0.78ms] (pass) CompressionStream and DecompressionStream > brotli > round-trip compression with brotli [1.80ms] (pass) CompressionStream and DecompressionStream > zstd > compresses data with zstd [0.58ms] (pass) CompressionStream and DecompressionStream > zstd > decompresses zstd data [0.43ms] (pass) CompressionStream and DecompressionStream > zstd > round-trip compression with zstd [1.02ms] (pass) CompressionStream and DecompressionStream > zstd > decompresses a multi-frame zstd stream [0.28ms] (pass) CompressionStream and DecompressionStream > zstd > decompr ... (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/streams/compression.test.ts bun test v1.4.0 (38b3183) test/js/web/streams/compression.test.ts: (pass) TransformStream.prototype getters reject native transform subclasses (0) [13.88ms] (pass) TransformStream.prototype getters reject native transform subclasses (1) [2.67ms] (pass) TransformStream.prototype getters reject native transform subclasses (2) [2.66ms] (pass) TransformStream.prototype getters reject native transform subclasses (3) [2.15ms] (pass) CompressionStream and DecompressionStream > brotli > compresses data with brotli [15.57ms] (pass) CompressionStream and DecompressionStream > brotli > decompresses brotli data [22.42ms] (pass) CompressionStream and DecompressionStream > brotli > round-trip compression with brotli [51.98ms] (pass) CompressionStream and DecompressionStream > zstd > compresses data with zstd [10.10ms] (pass) CompressionStream and DecompressionStream > zstd > decompresses zstd data [18.91ms] (pass) CompressionStream and DecompressionStream > zstd > round-trip compression with zstd [38.62ms] ... (truncated) release with fix: 1 skipped $ bun scripts/build.ts --profile=release [configured] bun-profile → bun (stripped) in 820ms (unchanged) ninja: Entering directory `/workspace/bun/build/release' [1/12] gen generated_host_exports.rs generated_host_exports.rs: 92 exports (host=3, lazy=10, generic=79, rust=0); 239 extern-C blocks audited [1/12] 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日) �[1m�[92m Compiling�[0m bun_core v0.0.0 (/workspace/bun/src/bun_core) �[1m�[92m Compiling�[0m bun_errno v0.0.0 (/workspace/bun/src/errno) �[1m�[92m Compiling�[0m bun_ptr v0.0.0 (/workspace/bun/src/ptr) �[1m�[92m Compiling�[0m bun_boringssl_sys v0.0.0 (/workspace/bun/src/boringssl_sys) �[1m�[92m Compiling�[0m bun_safety v0.0.0 (/workspace/bun/src/safety) �[1m�[92m Compiling�[0m bun_zlib_sys v0.0.0 (/workspace/bun/src/zlib_sys) �[1m�[92m Compiling�[0m bun_cares_sys v0.0.0 (/workspace/bun/src/cares_sys) �[1m�[92m Compiling�[0m bun_zstd v0.0.0 (/workspace/bun/src/zstd) �[1m�[92m Compiling�[0m bun_picohttp v0.0.0 (/workspace/bun/src/picohttp) �[1m�[92m Compiling�[0m bun_brotli ... (truncated) ``` </details> <details><summary>diff hotspot</summary> ``` .../bindings/webcore/streams/JSCompressionStream.h | 7 ++-- .../webcore/streams/JSCompressionStreamShared.h | 1 + src/runtime/webcore/CompressionStreamCoder.rs | 43 +++++++++++++++---- test/js/web/streams/compression.test.ts | 49 +++++++++++++++++++++- 4 files changed, 87 insertions(+), 13 deletions(-) ``` </details> **gate history** · 2 passed · 0 rejected · iteration 0 <details><summary>evidence per changed file</summary> ``` file reads edits tests src/jsc/bindings/webcore/streams/JSCompressionStream.h 2 2 0 ...sc/bindings/webcore/streams/JSCompressionStreamShared.h 2 2 0 src/runtime/webcore/CompressionStreamCoder.rs 3 6 0 test/js/web/streams/compression.test.ts 1 1 0 ``` </details> <!-- robobun:evidence:end -->
igorls
pushed a commit
that referenced
this pull request
Aug 21, 2026
...wo parallel vecs (oven-sh#39145) ### Problem - `LOLHTMLContext` in `src/runtime/api/html_rewriter.rs` keeps two vecs, `selectors` and `element_handlers`, that describe one thing: entry `i` of each is the selector and the handler object from the same `rewriter.on(selector, handlers)` call. - The pairing is only held up by convention: `on_()` pushes to both, `build_settings()` zips them back together, and a doc comment plus an invariant comment explain it. The mordant `parallel_vecs` lint flags this (the one baselined finding for this file). ### Fix - Add `ElementHandlerEntry { selector, handler: Box<ElementHandler> }` and store `element_handlers: Vec<ElementHandlerEntry>`. One vec, one push in `on_()`, and `build_settings()` destructures each entry instead of zipping. - No behavior change: the same values are pushed in the same order, the handler is still boxed (the lol-html closures built in `build_settings()` hold raw pointers into the box, so it must not move when the vec reallocates), and the body of the `build_settings()` loop is unchanged. The `#[expect(clippy::vec_box)]` comes off `element_handlers` because it is no longer a `Vec<Box<_>>`; `document_handlers` keeps its own. - Remove the `parallel_vecs:src/runtime/api/html_rewriter.rs` line from `mordant-baseline.toml`. - Tests, in `test/js/workerd/html-rewriter.test.js` (`on() registrations`), pin down the two things this storage has to get right. They pass before and after this change, since it is a refactor: - Many selectors registered on one rewriter, with two rejected `on()` calls in the middle, each still run the handlers they were registered with, on two transforms of the same rewriter. - `on()` called from inside a handler, often enough to reallocate the registry while lol-html is still calling the handlers registered before the transform started: the running transform is unaffected and the next one picks the additions up. With the `Box` removed from `ElementHandlerEntry` this test fails under ASAN with a heap-use-after-free (report in the details below), so the boxing is now covered rather than only commented. - Verified: - `bun bd test` on `test/js/workerd/html-rewriter.test.js` (165 tests, including the new ones), `html-rewriter-end-error.test.ts`, `html-rewriter-leak.test.ts`, `test/js/web/html/html-rewriter-doctype.test.ts` and the HTMLRewriter regression tests: all pass. - `cargo clippy -p bun_runtime --no-deps`: clean. - `cargo dylint --all -p bun_runtime` with this baseline: nothing over the baseline. The same command with the baseline line removed but the source change stashed reports exactly the one `parallel_vecs` finding for this file, so the removed line is the one this change fixes. - Regenerating the baseline with `MORDANT_BASELINE_WRITE=1` also drops two entries this PR does not touch (`always_unwrapped_option:src/install/PackageInstall.rs`, `narrowed_two_ways:src/runtime/node/node_crypto_binding.rs`); those findings were already fixed on main by other changes and are left for a separate cleanup. ### Background - `HTMLRewriter.on(selector, handlers)` parses the CSS selector with lol-html and wraps the JS handler object in an `ElementHandler` (the protected `element`/`comments`/`text` callbacks). Nothing is handed to lol-html at that point; registrations are collected in `LOLHTMLContext`, which is shared by the rewriter and every transform it starts, because `transform()` can run more than once. - `build_settings()` runs at transform time and turns each registration into a `(selector, ElementContentHandlers)` pair for lol-html. Its closures capture a `NonNull<ElementHandler>` pointing into the heap allocation owned by the `Box`, which is why the handler has to stay boxed even though clippy would normally suggest otherwise. An `on()` call after a transform has started (for example from inside a handler) pushes onto the same vec, which is what makes the reallocation case reachable from JS. - `mordant-baseline.toml` is the ratchet for the mordant lint pack run by the Rust lints workflow: it records the accepted number of findings per (lint, file), and CI reports anything above those counts. Removing the line here means a reintroduction of the pattern in this file would be reported. <details> <summary>ASAN report from the new test with the Box removed from ElementHandlerEntry</summary> ``` ERROR: AddressSanitizer: heap-use-after-free READ of size 8 #3 <ElementHandler as HandlerLike>::global src/runtime/api/html_rewriter.rs #4 handler_callback::<ElementHandler, Element, ...> src/runtime/api/html_rewriter.rs #5 ElementHandler::on_element src/runtime/api/html_rewriter.rs #6 build_settings::{closure#0} src/runtime/api/html_rewriter.rs #8 lol_html ContentHandlersDispatcher::handle_start_tag freed by thread T0 here: oven-sh#13 RawVec<ElementHandlerEntry>::grow_one oven-sh#15 Vec<ElementHandlerEntry>::push oven-sh#16 HTMLRewriter::on_ src/runtime/api/html_rewriter.rs ``` </details> <!-- robobun:evidence:begin --> --- **no test proof** · iteration 2 · Platform-specific test(s) that do not run on this machine. Deferring to CI, which covers all platforms: test/js/workerd/html-rewriter.test.js <!-- robobun:evidence:end --> --------- Co-authored-by: Alistair Smith <hi@alistair.sh>
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.
Uh oh!
There was an error while loading. Please reload this page.
What does this PR do?
Updates SQLite to version 3.53.400
Compare: https://sqlite.org/src/vdiff?from=3.51.2&to=3.53.400
Auto-updated by this workflow