Commit Graph

9358 Commits

Author SHA1 Message Date
autofix-ci[bot]
c6f246484c [autofix.ci] apply automated fixes 2026-02-26 22:11:27 +00:00
Claude Bot
deea9e810b fix(repl): prevent reassignment of const variables across lines
The REPL was converting all `const`/`let`/`var` declarations to `var`
for persistence across evaluations, which allowed `const` variables
to be silently reassigned. This fix preserves `const` semantics by
keeping const declarations inside the IIFE and using
Object.defineProperty with getter/setter to persist const bindings
on globalThis as non-reassignable properties.

Fixes #27485

Co-Authored-By: Claude <noreply@anthropic.com>
2026-02-26 22:08:59 +00:00
Luke Parker
84e4a5ce9c fix(windows): avoid standalone worker dotenv crash (#27434)
### What does this PR do?

Fixes #27431.

- fixes a Windows standalone executable crash when
`compile.autoloadDotenv = false`, a `.env` file exists in the runtime
cwd, and the executable spawns a `Worker`
- gives worker startup its own cloned `DotEnv.Loader` before
`configureDefines()`, so dotenv loading does not mutate env state owned
by another thread
- aligns worker startup with other Bun runtime paths by wiring
`resolver.env_loader = transpiler.env`
- extracts standalone runtime flag propagation into
`applyStandaloneRuntimeFlags(...)` so main and worker startup share the
same env/tsconfig/package.json behavior
- adds regression coverage in `test/regression/issue/27431.test.ts` and
bundler coverage in `test/bundler/bundler_compile_autoload.test.ts`

### How did you verify your code works?

- reproduced the original crash with `bun test
regression/issue/27431.test.ts` on stock `1.3.10-canary.104`; the test
fails on unpatched Bun
- rebuilt `build/debug/bun-debug.exe` with this patch and ran
`build/debug/bun-debug.exe test regression/issue/27431.test.ts`; the
test passes on the patched build
- manually validated the minimal repro from
`https://github.com/Hona/bun1310-minimal-repro` against the patched
`bun-debug.exe`; the standalone executable no longer crashes and still
keeps dotenv disabled (`process.env` does not pick up `.env`)
2026-02-25 19:49:56 -08:00
Jarred Sumner
89c70a76e8 feat(repl): add -e/-p flags, docs page, and shell completions (#27436)
## Summary

- Adds `bun repl -e <script>` / `-p <script>` for non-interactive
evaluation using REPL semantics (object literal wrapping, declaration
hoisting), draining the event loop before exit. Returns exit code 1 on
error.
- Adds `docs/runtime/repl.mdx` documenting the interactive REPL
(commands, keybindings, special variables, top-level await, imports) and
the new non-interactive mode.
- Updates bash/fish/zsh completions for the `repl` subcommand and its
flags.

## Test plan

- [x] `bun bd test test/js/bun/repl/repl.test.ts` — all 103 tests pass
(20 new)
- [x] `USE_SYSTEM_BUN=1 bun test` — new tests fail (validates they test
new behavior)
- [x] `bun run zig:check-all` — compiles on all platforms
- [x] `bash -n` / `fish -n` / `zsh -n` syntax checks on completion files

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-02-25 19:49:44 -08:00
robobun
b2d8504a09 fix(spawn): remove shutdown() on subprocess stdio socketpairs (#27435)
## Summary

- Remove `shutdown()` calls on subprocess stdio socketpair file
descriptors that were causing Python asyncio-based MCP servers to break

## Root Cause

Bun uses `SOCK_STREAM` socketpairs for subprocess stdio pipes. After
creating each socketpair, it called `shutdown(SHUT_WR)` on the parent's
read end (for stdout/stderr) and `shutdown(SHUT_RD)` on the parent's
write end (for stdin) to make them unidirectional.

On `SOCK_STREAM` sockets, `shutdown(fd, SHUT_WR)` sends a **FIN** to the
peer. Python's `asyncio.connect_write_pipe()` registers an `EPOLLIN`
watcher on the write pipe fd to detect peer closure. The FIN from
`shutdown()` triggers an immediate `EPOLLIN` event, causing asyncio to
interpret it as "connection closed" and tear down the write transport —
even though the pipe should remain open.

This broke **all Python MCP servers** using the `model_context_protocol`
SDK (which uses `connect_write_pipe()` in its stdio transport) whenever
they took more than a few seconds to initialize. Node.js does not have
this issue because it does not call `shutdown()` on its socketpairs.

## Fix

Remove the `shutdown()` calls entirely. The socketpairs are already used
unidirectionally by convention, and the `shutdown()` calls provided no
functional benefit while causing compatibility issues with any program
that polls its stdio fds for readability/writability events.

## Test plan

- [x] Added regression test
`test/js/bun/spawn/spawn-socketpair-shutdown.test.ts` with 3 test cases:
  - Subprocess stdout pipe stays writable after idle delay
  - Python asyncio `connect_write_pipe` works correctly with idle period
  - Subprocess stdin pipe stays readable for child after idle delay
- [x] Verified test fails on system bun (without fix) and passes on
debug build (with fix)
- [x] Verified existing spawn tests still pass
(`spawn-streaming-stdout`, `spawn-stdin-readable-stream`)
- [x] Verified original bug report repro script works with the fix


🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
2026-02-25 18:25:13 -08:00
Ciro Spaciari
e735bffaa9 fix(http): enable TLS keepalive for custom SSL configs (#27385)
## Summary

- **Enable keepalive for custom TLS configs (mTLS):** Previously, all
connections using custom TLS configurations (client certificates, custom
CA, etc.) had `disable_keepalive=true` forced, causing a new TCP+TLS
handshake on every request. This removes that restriction and properly
tracks SSL contexts per connection.

- **Intern SSLConfig with reference counting:** Identical TLS
configurations are now deduplicated via a global registry
(`SSLConfig.GlobalRegistry`), enabling O(1) pointer-equality lookups
instead of O(n) content comparisons. Uses `ThreadSafeRefCount` for safe
lifetime management across threads.

- **Bounded SSL context cache with LRU eviction:** The custom SSL
context map in `HTTPThread` is now bounded (max 60 entries, 30-minute
TTL) with proper cleanup of both SSL contexts and their associated
SSLConfig references when evicted.

- **Correct keepalive pool isolation:** Pooled sockets now track their
`ssl_config` (with refcount) and `owner` context, ensuring connections
are only reused when the TLS configuration matches exactly, and sockets
return to the correct pool on release.

Fixes #27358

## Changed files

- `src/bun.js/api/server/SSLConfig.zig` — ref counting, content hashing,
GlobalRegistry interning
- `src/bun.js/webcore/fetch.zig` — intern SSLConfig on creation, deref
on cleanup
- `src/http.zig` — `custom_ssl_ctx` field, `getSslCtx()` helper, updated
all callback sites
- `src/http/HTTPContext.zig` — `ssl_config`/`owner` on PooledSocket,
pointer-equality matching
- `src/http/HTTPThread.zig` — `SslContextCacheEntry` with timestamps,
TTL + LRU eviction

## Test plan

- [x] `test/regression/issue/27358.test.ts` — verifies keepalive
connection reuse with custom TLS and isolation between different configs
- [x] `test/js/bun/http/tls-keepalive.test.ts` — comprehensive tests:
keepalive reuse, config isolation, stress test (50 sequential requests),
keepalive-disabled control
- [x] `test/js/bun/http/tls-keepalive-leak-fixture.js` — memory leak
detection fixture (50k requests with same config, 200 requests with
distinct configs)

## Changelog
<!-- CHANGELOG:START -->
Fixed a bug where HTTP connections using custom TLS configurations
(mTLS, custom CA certificates) could not reuse keepalive connections,
causing a new TCP+TLS handshake for every request and leaking SSL
contexts. Custom TLS connections now properly participate in keepalive
pooling with correct isolation between different configurations.
<!-- CHANGELOG:END -->

🤖 Generated with [Claude Code](https://claude.com/claude-code) (0%
16-shotted by claude-opus-4-6, 3 memories recalled)

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
2026-02-25 15:39:13 -08:00
Dylan Conway
347c288d75 fix(shell): seq crashes when called with only flags and no numeric args (#27415)
## What

Fixes a null pointer dereference crash in the `seq` shell builtin when
called with only flags and no numeric arguments.

## Reproduction

```js
await Bun.$`seq -w`       // crash
await Bun.$`seq -s ,`     // crash
await Bun.$`seq -t .`     // crash
```

```
panic(main thread): attempt to use null value
src/shell/builtin/seq.zig:47:31
```

Also crashes release builds (segfault).

## Root cause

The flag-parsing loop at line 17 consumes all arguments. When the user
passes only flags, the iterator is exhausted after the loop exits. Line
47 then calls `iter.next().?` which panics on `null`.

The existing `args.len == 0` check on line 14 only catches the case
where no args are passed at all — it does not cover the case where all
args are consumed as flags.

## Fix

Changed `.?` to `orelse return this.fail(usageString)`, matching the
behavior when `seq` is called with zero arguments.
2026-02-25 15:28:43 -08:00
robobun
6cc1a70198 fix(streams): preserve AsyncLocalStorage context in stream.finished callback (#27429)
## Summary
- Bind the `stream.finished` callback with `AsyncLocalStorage.bind()`
before wrapping with `once()`, matching [Node.js
behavior](https://github.com/nodejs/node/blob/main/lib/internal/streams/end-of-stream.js#L70).
Without this, the async context active when `finished()` is called is
lost by the time the callback fires.

Closes #27428

## Test plan
- [x] Added regression test `test/regression/issue/27428.test.ts` that
spawns an HTTP server using `stream.finished` inside
`AsyncLocalStorage.run()` and verifies the store is preserved in the
callback
- [x] Verified test fails with system bun (`USE_SYSTEM_BUN=1`) and
passes with the debug build
- [x] Existing stream finished tests (`test-stream-finished.js`,
`test-stream-end-of-streams.js`, `test-http-outgoing-finished.js`,
`test-http-client-finished.js`) continue to pass

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
2026-02-25 15:28:17 -08:00
robobun
fa3a30f075 feat(repl): implement native Zig REPL with full TUI support (#26304)
## Summary

This PR implements a native Zig REPL for Bun with full TUI (Text User
Interface) support, providing a modern and feature-rich interactive
experience.

### Features

- **Syntax highlighting** using `QuickAndDirtySyntaxHighlighter` for
colorized JavaScript code
- **Full line editing** with Emacs-style keybindings:
  - `Ctrl+A/E` - Move to start/end of line
  - `Ctrl+B/F` - Move backward/forward one character
  - `Ctrl+K/U` - Kill to end/start of line
  - `Ctrl+W` - Delete word backward
  - `Ctrl+L` - Clear screen
  - Arrow keys for cursor movement
- **Persistent history** with file storage (`~/.bun_repl_history`)
  - Up/Down arrow for history navigation
  - `Ctrl+P/N` also works for history
- **Tab completion** for properties and commands
- **Multi-line input support** with automatic continuation detection
- **REPL commands**: `.help`, `.exit`, `.clear`, `.load`, `.save`,
`.editor`
- **Special variables**:
  - `_` - Contains the result of the last expression
  - `_error` - Contains the last error that occurred
- **Result formatting** with `util.inspect` integration
- **replMode transforms** for proper REPL semantics:
  - Expression result capture via `{ value: expr }` wrapper
- Variable hoisting for persistence across REPL lines (`const`/`let` →
`var`)
  - Function and class declaration hoisting
  - Top-level await support with async IIFE wrapper
  - Object literal detection (no parentheses needed for `{ a: 1 }`)

### Implementation

The REPL is implemented in pure Zig (`src/repl.zig`) with C++ bindings
for JSC integration:
- Uses raw terminal mode for character-by-character input
- Integrates with Bun's existing `VirtualMachine` for JavaScript
evaluation
- Uses the parser with `repl_mode=true` to apply REPL-specific AST
transforms
- Provides access to all Bun globals (`Bun`, `Buffer`, `console`,
`process`, etc.)

### Files Changed

- `src/repl.zig` - Main REPL implementation (~1500 lines)
- `src/cli/repl_command.zig` - CLI entry point
- `src/bun.js/bindings/bindings.cpp` - C++ REPL functions
- `src/bun.js/bindings/headers.h` - C++ declarations
- `src/ast/repl_transforms.zig` - REPL-specific AST transforms
(cherry-picked from jarred/repl-mode)
- `test/js/bun/repl/repl.test.ts` - Comprehensive tests

## Test Plan

- [x] Run `bun bd test test/js/bun/repl/repl.test.ts` - 27 tests pass
- [x] Manual testing of interactive features:
  - Basic expression evaluation
  - Special variables `_` and `_error`
  - History navigation
  - Tab completion
  - Multi-line input
  - REPL commands
  - Top-level await
  - Variable persistence
- [x] Verified REPL starts without downloading packages (fixes #26058)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
2026-02-25 15:15:37 -08:00
robobun
c643e0fad8 fix(fuzzilli): prevent crash from fprintf on null FILE* in FUZZILLI_PRINT handler (#27310)
## Summary
- The `fuzzilli('FUZZILLI_PRINT', ...)` native function handler in
FuzzilliREPRL.cpp called `fdopen(103, "w")` on every invocation and
passed the result directly to `fprintf()` without a NULL check
- When running the debug-fuzz binary outside the REPRL harness (where fd
103 is not open), `fdopen()` returns NULL, and `fprintf(NULL, ...)`
causes a SIGSEGV crash
- Fix: make the `FILE*` static (so `fdopen` is called once, avoiding fd
leaks) and guard `fprintf`/`fflush` behind a NULL check

## Crash reproduction
```js
// The crash is triggered when the Fuzzilli explore framework calls
// fuzzilli('FUZZILLI_PRINT', ...) on the native fuzzilli() function
// while running outside the REPRL harness (fd 103 not open).
// Minimal reproduction:
fuzzilli('FUZZILLI_PRINT', 'hello');
```

Running the above with the debug-fuzz binary (which registers the native
`fuzzilli` function) causes SIGSEGV in `__vfprintf_internal` due to NULL
FILE*.

## Test plan
- [x] Verified crash reproduces 10/10 times with the original binary
- [x] Verified 0/10 crashes with the fixed binary
- [x] Fix is trivially correct: static FILE* + NULL guard

Co-authored-by: Alistair Smith <alistair@anthropic.com>
2026-02-25 12:52:12 +00:00
robobun
2222aa9f47 fix(bundler): write external sourcemap .map files for bun build --compile (#27396)
## Summary
- When using `bun build --compile --sourcemap=external`, the `.map`
files were embedded in the executable but never written to disk. This
fix writes them next to the compiled executable.
- With `--splitting` enabled, multiple chunks each produce their own
sourcemap. Previously all would overwrite a single `{outfile}.map`; now
each `.map` file preserves its chunk-specific name (e.g.,
`chunk-XXXXX.js.map`).
- Fixes both the JavaScript API (`Bun.build`) and CLI (`bun build`) code
paths.

## Test plan
- [x] `bun bd test test/bundler/bun-build-compile-sourcemap.test.ts` —
all 8 tests pass
- [x] New test: `compile with sourcemap: external writes .map file to
disk` — verifies a single `.map` file is written and contains valid
sourcemap JSON
- [x] New test: `compile without sourcemap does not write .map file` —
verifies no `.map` file appears without the flag
- [x] New test: `compile with splitting and external sourcemap writes
multiple .map files` — verifies each chunk gets its own uniquely-named
`.map` file on disk
- [x] Verified new tests fail with `USE_SYSTEM_BUN=1` (confirming they
test the fix, not pre-existing behavior)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
2026-02-24 22:02:51 -08:00
Martin Amps
38e4340d28 fix(http): prevent duplicate Transfer-Encoding header in node:http (#27398)
### What does this PR do?

Fixes https://github.com/oven-sh/bun/issues/21201. Ran into this today
during a migration from node->bun. TLDR if you set `Transfer-Encoding:
chunked` via `res.writeHead()`, Bun sends the header twice because two
layers independently add it:

1. `writeFetchHeadersToUWSResponse` (NodeHTTP.cpp) writes the user's
headers to the response buffer
2. uWS's `HttpResponse::write()` auto-inserts `Transfer-Encoding:
chunked` since no flag indicates it was already set

Added a `HTTP_WROTE_TRANSFER_ENCODING_HEADER` flag copying the pattern
for `Content-Length` and `Date` deduping, checked before auto-insertion
in `write()`, `flushHeaders()`, and `sendTerminatingChunk()`.

### How did you verify your code works?

Minimal repro:

```
import http from "node:http";
import net from "node:net";

http.createServer((_, res) => {
  res.writeHead(200, { "Transfer-Encoding": "chunked" });
  res.write("ok");
}).listen(0, "127.0.0.1", function () {
  const s = net.createConnection(this.address().port, "127.0.0.1", () =>
    s.write("GET / HTTP/1.1\r\nHost: x\r\n\r\n"));
  s.on("data", (d) => {
    console.log(d.toString().split("\r\n\r\n")[0]);
    s.destroy(); this.close();
  });
});
```

Node working (showing header once)

```
$ node --version
v22.21.1

$ node /Users/mamps/code/labs/conway/tmp/bun-duplicate-te.mjs
HTTP/1.1 200 OK
Transfer-Encoding: chunked
Date: Tue, 24 Feb 2026 06:14:50 GMT
Connection: keep-alive
Keep-Alive: timeout=5
```

Bun bug (duplicate header):
```
$ bun --version
1.3.9

$ bun bun-duplicate-te.mjs
HTTP/1.1 200 OK
Transfer-Encoding: chunked
Date: Tue, 24 Feb 2026 06:13:55 GMT
Transfer-Encoding: chunked
```

Bun fixed:
```
$ ./build/debug/bun-debug --version
1.3.10-debug

$ BUN_DEBUG_QUIET_LOGS=1 ./build/debug/bun-debug /tmp/bun-duplicate-te.mjs
HTTP/1.1 200 OK
Transfer-Encoding: chunked
Date: Tue, 24 Feb 2026 06:15:53 GMT
```
2026-02-23 23:04:19 -08:00
robobun
6b1d6c769b fix(sys): remove MSG_NOSIGNAL from recvfrom flags (#27390)
## Summary

- `MSG_NOSIGNAL` is only valid for send operations (`send`, `sendto`,
`sendmsg`), not receive operations (`recv`, `recvfrom`, `recvmsg`).
Passing it to `recvfrom` causes `EINVAL` in strict environments like
gVisor (Google Cloud Run).
- Split the shared `socket_flags_nonblock` constant into
`recv_flags_nonblock` (`MSG_DONTWAIT` only) and `send_flags_nonblock`
(`MSG_DONTWAIT | MSG_NOSIGNAL`).

Closes #27389

## Test plan

- [x] Added regression test `test/regression/issue/27389.test.ts` that
exercises the socket recv path
- [x] Debug build passes (`bun bd test
test/regression/issue/27389.test.ts`)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
2026-02-23 18:24:37 -08:00
Jarred Sumner
6314363663 feat(bundler): barrel import optimization — skip parsing unused submodules (#26892) 2026-02-23 05:28:01 -08:00
robobun
77b6406415 Add arraybuffer output option to Bun.generateHeapSnapshot("v8") (#26861)
## Summary
- Add `Bun.generateHeapSnapshot("v8", "arraybuffer")` which returns the
heap snapshot as an `ArrayBuffer` instead of a `string`
- Avoids potential integer overflow crashes in `WTF::String` when heap
snapshots approach max uint32 length
- Eliminates the overhead of creating a JavaScript string for large
snapshots
- The `ArrayBuffer` contains UTF-8 encoded JSON that can be written
directly to a file or decoded with `TextDecoder`
- Updates TypeScript types in `bun.d.ts` with the new overload
- Adds tests for the new `"arraybuffer"` encoding option

Depends on oven-sh/WebKit#158 for the
`BunV8HeapSnapshotBuilder::jsonBytes()` method.

## Test plan
- [x] `Bun.generateHeapSnapshot("v8", "arraybuffer")` returns a valid
`ArrayBuffer`
- [x] Decoded ArrayBuffer produces valid JSON parseable as a V8 heap
snapshot
- [x] Existing `Bun.generateHeapSnapshot("v8")` string output still
works
- [x] Tests fail with `USE_SYSTEM_BUN=1` (system bun doesn't support the
new argument)
- [x] Tests pass with the debug-local build

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
2026-02-23 01:48:56 -08:00
Jarred Sumner
4d9752a1f0 Upgrade WebKit to f03492d0636f (#26922)
# JavaScriptCore Upstream Changes: 1b6f54d1c872..upstream/main

**Date range**: Feb 5 -- Feb 11, 2026
**Total JSC commits**: 38

---

## 1. New Features

### WebAssembly JSPI (JavaScript Promise Integration)
**Commit**: `53e97afd3421` -- [JSC] JSPI Implementation
**Files changed**: 59 files, +3879/-148 lines

Full implementation of the [WebAssembly JSPI
proposal](https://github.com/WebAssembly/js-promise-integration). Adds
two new APIs:

- `WebAssembly.promising(wasmFun)` -- wraps a wasm function so it
returns a Promise
- `new WebAssembly.Suspending(jsFun)` -- wraps a JS function so wasm can
suspend on its result

Controlled by `useJSPI` feature flag (disabled by default). This is a
large change that introduces:

- New classes: `EvacuatedStack`, `PinballCompletion`, `JSPIContext`,
`WebAssemblySuspending`, `WebAssemblyPromising`,
`JSWebAssemblySuspendError`
- New `JSPIContext` struct added to `VM` (field `topJSPIContext`)
- New `pinballCompletionStructure` in VM
- New `WebAssemblySuspendError` and `WebAssemblySuspending` lazy class
structures in JSGlobalObject
- New methods on VM: `addEvacuatedStackSlice`,
`removeEvacuatedStackSlice`, `gatherEvacuatedStackRoots`
- New iso subspace: `pinballCompletionSpace`

**Follow-up**: `f67441012b90` -- JSPI cages should only generate JIT
code when JITCage is enabled

### WebAssembly Memory64 in BBQ Tier
**Commit**: `7b98e4b17594` -- Add support for Memory64 in BBQ Tier

Extends Memory64 support from the interpreter to the BBQ JIT tier,
allowing wasm modules to address >4GB of memory.

### WebAssembly Multi-Memory (instantiation only)
**Commit**: `bdf26416947e` -- Support instantiating multiple wasm
memories

Adds support for instantiating multiple memories in wasm modules (but
not executing instructions that use memories other than index 0). Gated
behind `useWasmMultiMemory` flag.

### LOL (Lightweight Optimizing Language) JIT -- New Tier (In Progress)
A new JIT compilation tier "LOL" is being built incrementally across
multiple commits:

- `dd56e0f5b991` -- Add get_argument/argument_count bytecodes
- `fb43184a0a77` -- Add to_primitive-like bytecodes
- `f52fa4a30c76` -- Add switch/throw bytecodes
- `7fd04c82d291` -- Add support for this/prototype bytecodes
- `11127b8a61e0` -- Add support for op_ret

These commits add new files under `Source/JavaScriptCore/lol/`
(LOLJIT.cpp, LOLJIT.h, LOLRegisterAllocator.h). This appears to be a new
lightweight JIT tier between Baseline and DFG.

---

## 2. Performance Improvements

### Deep Rope String Slicing -- 168x faster
**Commit**: `93f2fd68619b` -- [JSC] Limit rope traversal depth in
`tryJSSubstringImpl`

Limits rope traversal depth to 16 in `tryJSSubstringImpl`. Deep rope
strings from repeated concatenation (`s += 'A'`) previously caused
O(n^2) behavior. When the depth limit is exceeded, it falls back to
`resolveRope` to flatten the string.

This directly addresses a performance issue reported against Bun where
it was significantly slower than Node.js.

### String#endsWith DFG/FTL Optimization -- up to 10.5x faster
**Commit**: `901865149859` -- [JSC] Optimize `String#endsWith` in
DFG/FTL

Adds a new DFG/FTL intrinsic `StringPrototypeEndsWithIntrinsic` for
`String.prototype.endsWith`. Constant folding case is 10.5x faster;
general case is 1.45x faster.

### RegExp Flag Getters in DFG/FTL and IC -- 1.6x faster
**Commit**: `1fe86a244d00` -- [JSC] Handle RegExp flag getters in
DFG/FTL and IC

Adds DFG/FTL and inline cache support for RegExp flag property getters
(`.global`, `.ignoreCase`, `.multiline`, `.dotAll`, `.sticky`,
`.unicode`, `.unicodeSets`, `.hasIndices`).

### String#@@iterator as NewInternalFieldObject in DFG/FTL
**Commit**: `44cba41d8eef` -- [JSC] Handle `String#@@iterator` as
`NewInternalFieldObject` in DFG/FTL

Optimizes string iterator creation in DFG/FTL by treating it as a
`NewInternalFieldObject`, enabling allocation sinking.

### ArithMod(Int52) in DFG/FTL
**Commit**: `49a21e7ff327` -- [JSC] Add ArithMod(Int52Use, Int52Use) in
DFG / FTL
**Follow-ups**: `f96be6066671` (constant folding fix), `c8f283248f3f`
(NaNOrInfinity fix)

Adds Int52 modulo support in DFG/FTL, avoiding expensive `fmod` double
operations when inputs are integer-like doubles.

### ArithDiv(Int52) in DFG/FTL -- REVERTED
**Commit**: `582cb0306b7c` -- [JSC] Add ArithDiv(Int52Use, Int52Use) in
DFG / FTL
**Revert**: `2e967edd1dc0` -- Reverted due to JetStream3 regressions

### Intl formatToParts Pre-built Structure -- up to 1.15x faster
**Commit**: `a5dd9753d23c` -- [JSC] Optimize Intl formatToParts methods
with pre-built Structure

Optimizes Intl's `formatToParts` methods by using pre-built Structures
for the returned part objects.

### JSBigInt Inline Storage
**Commit**: `dbc50284d4cf` -- [JSC] Inline storage into JSBigInt
**Related**: `304cf0713b9d` -- Remove JSBigInt::rightTrim and
JSBigInt::tryRightTrim

Major structural change to JSBigInt: digits are now stored inline
(trailing storage) instead of via a separate `CagedBarrierPtr`. This
eliminates the separate allocation and pointer indirection.
`tryRightTrim` and `rightTrim` removed; `createFrom` renamed to
`tryCreateFrom` with span-based API.

### WYHash Always Enabled for Strings
**Commit**: `14ba1421ca08` -- [JSC] Always use WYHash for strings

Previously, WYHash had a threshold (disabled for short strings on weak
devices). Now it's enabled regardless of string size.

### JIT Worklist Thread Count: 3 -> 4
**Commit**: `453c578cadf6` -- [JSC] Bump JIT worklist thread number from
3 to 4

### Greedy Register Allocator Improvements
**Commit**: `b642ae17aade` -- [JSC] Proactively coalesce spill slots
during register allocation
**Commit**: `a78705db7c08` -- [JSC] GreedyRegAlloc: amortize cost of
clearing the visited set in the evict loop

---

## 3. Bug Fixes

### RegExp (YARR) Fixes
- `dea6808af40e` -- **Fix stale captures in FixedCount groups in
MatchOnly mode**: `.test()` was not clearing captures between FixedCount
iterations, causing stale values visible to backreferences.

- `437e137889ea` -- **Fix infinite loop in JIT for non-greedy
backreference to zero-width capture**: A non-greedy backreference like
`\1*?` could loop forever when the referenced capture was undefined or
empty.

- `d4f884d21c0e` -- **Fix backtracking in NestedAlternativeEnd**: Wrong
jump target was used when backtracking from NestedAlternativeEnd.

### ARM64 Cached Immediate Fix
**Commit**: `8396ad321ad0` -- [JSC] Fix edge case issue of cached imm in
ARM64

TrustedImm32(-1) was not zero-extended when cached, causing wrong reuse
when followed by TrustedImm64(-1).

### Wasm Fixes
- `cea8513d43ef` -- **Fix RefCast/RefTest folding in B3**: The condition
was inverted, causing wrong cast results.
- `629632da96e1` -- **Fix debugger races with idle and active VMs in
stop-the-world**
- `e5391ad90f47` -- **Stack check on IPInt->BBQ loop OSR entry**
- `fd15e0d70ab1` -- **Use FrameTracer in wasm table_init operations**

### Structure Heap Alignment Assert Fix
**Commit**: `47b52526fd42` -- [JSC] Fix startOfStructureHeap alignment
RELEASE_ASSERT

---

## 4. Bun-side Changes

- Updated `WEBKIT_VERSION` to `f03492d0636f`
- Updated `SerializedScriptValue.cpp`: replaced removed
`JSBigInt::tryRightTrim` with `JSBigInt::tryCreateFrom` span-based API
- Fixed `CloneSerializer`/`CloneDeserializer` inheritance (`private` ->
`public CloneBase`)
- Explicitly disabled `ENABLE_MEDIA_SOURCE`, `ENABLE_MEDIA_STREAM`,
`ENABLE_WEB_RTC` in `build.ts` and `SetupWebKit.cmake` for JSCOnly port

---

## 5. Summary by Component

| Component | Commits | Key Changes |
|-----------|---------|-------------|
| **WASM** | 9 | JSPI implementation, Memory64 BBQ, multi-memory,
RefCast fix, stack checks, debugger races |
| **DFG/FTL** | 6 | String#endsWith opt, RegExp flags opt,
String@@iterator opt, ArithMod(Int52) |
| **YARR (RegExp)** | 3 | Stale captures fix, infinite loop fix,
NestedAlternativeEnd backtracking fix |
| **B3/Air** | 3 | Spill slot coalescing, generational set for eviction,
RefCast/RefTest fix |
| **LOL JIT** | 5 | New JIT tier being built incrementally |
| **Runtime** | 6 | BigInt inline storage, WYHash always-on, Intl
formatToParts opt, ARM64 imm fix, rope depth limit |

---------

Co-authored-by: Sosuke Suzuki <sosuke@bun.com>
2026-02-23 01:28:37 -08:00
robobun
51a74a81fd fix(windows): validate GetFinalPathNameByHandleW return length to prevent buffer overflows (#27343)
## Summary

- `GetFinalPathNameByHandleW` returns a value `>= buffer size` when the
path doesn't fit, but 4 out of 5 call sites only checked for `== 0` (the
error case). This could cause out-of-bounds reads/writes when Windows
paths exceed the buffer capacity — e.g. deeply nested `node_modules` or
`\\?\` extended paths.
- Also fixed `PackageInstall.zig:1264` passing `dest_buf.len` (a
`PathBuffer` = 4096 bytes) instead of `wbuf.len` (a `WPathBuffer` =
32767 u16 elements) as the buffer size parameter to the API.

| File | Line | Issue |
|------|------|-------|
| `src/windows.zig` | 3419 | `GetFinalPathNameByHandle` wrapper missing
`>= len` check |
| `src/install/PackageInstall.zig` | 538 | OOB write at
`state.buf[dest_path_length]` |
| `src/install/PackageInstall.zig` | 562 | OOB slice
`state.buf2[0..cache_path_length]` |
| `src/install/PackageInstall.zig` | 1261 | Wrong buffer size param +
missing bounds check |
| `src/bun.js/node/node_fs.zig` | 6578 | OOB slice in symlink path
resolution during `copyFile` |
| `src/install/isolated_install/Installer.zig` | 556 | `setLength` with
unchecked length |

The sole correct call site (`src/sys.zig:4062`) already had the proper
`>= len` check and was used as the reference pattern.

## Test plan

- [x] `bun run zig:check-windows` passes (x86_64 + aarch64, Debug +
ReleaseFast)
- [ ] Windows CI passes
- [ ] Test with deeply nested `node_modules` paths on Windows (>260
chars)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
2026-02-22 17:45:34 -08:00
Dylan Conway
cb3c39be23 ci: add Intel SDE baseline verification for Windows, unify baseline checks (#27121)
Adds a unified baseline verification script
(`scripts/verify-baseline.ts`) that combines basic CPU instruction
checks and JIT stress test fixtures into a single step.

**Changes:**
- New TypeScript script replaces separate `verify-baseline-cpu.sh` and
`verify-jit-stress-qemu.sh` CI steps
- Adds Windows x64 baseline verification using Intel SDE v9.58 with
Nehalem emulation
- Linux continues to use QEMU (Nehalem for x64, Cortex-A53 for aarch64)
- SDE violations are detected by checking output for `SDE-ERROR`
messages rather than exit codes, avoiding false positives from
application errors
- JIT stress fixtures now run on every build instead of only when WebKit
changes

**Platform support:**
| Platform | Emulator | CPU Model |
|----------|----------|-----------|
| Linux x64 baseline | QEMU | Nehalem (SSE4.2, no AVX) |
| Linux aarch64 | QEMU | Cortex-A53 (no LSE/SVE) |
| Windows x64 baseline | Intel SDE | Nehalem (no AVX) |

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-21 14:24:21 -08:00
robobun
bc98025d93 fix(spawn): close libuv pipes before freeing to prevent handle queue corruption (#27064)
## Summary

Fixes #27063

On Windows, when `Bun.spawn` fails (e.g., ENOENT for a nonexistent
executable), pipes that were already initialized with `uv_pipe_init`
were being freed directly with `allocator.destroy()` without first
calling `uv_close()`. This left dangling pointers in libuv's
`handle_queue` linked list, corrupting it. Subsequent spawn calls would
crash with a segfault when inserting new handles into the corrupted
list.

Three sites were freeing pipe handles without `uv_close`:

- **`process.zig` `Stdio.deinit()`**: When spawn failed,
already-initialized pipes were freed without `uv_close()`. Now uses
`closePipeAndDestroy()` which checks `pipe.loop` to determine if the
pipe was registered with the event loop.
- **`process.zig` `spawnProcessWindows` IPC handling**: Unsupported IPC
pipes in stdin/stdout/stderr were freed directly. Now uses the same safe
close-then-destroy pattern.
- **`source.zig` `openPipe()`**: If `pipe.open(fd)` failed after
`pipe.init()` succeeded, the pipe was destroyed directly. Now calls
`uv_close()` with a callback that frees the memory.

Additionally, pipe allocations in `stdio.zig` are now zero-initialized
so that the `loop` field is reliably `null` before `uv_pipe_init`,
enabling the init detection in `deinit`.

## Test plan

- [x] Added regression test `test/regression/issue/27063.test.ts` that
spawns nonexistent executables repeatedly and verifies a valid spawn
still works afterward
- [x] Verified existing spawn tests pass (`exit-code.test.ts`,
`spawnSync.test.ts` — timing-related pre-existing flakes only)
- [x] Debug build compiles successfully
- [ ] Windows CI should verify the fix prevents the segfault


🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Dylan Conway <dylan.conway567@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
2026-02-21 14:04:25 -08:00
Jarred Sumner
b371bf9420 fix(install): resolve DT_UNKNOWN entries on NFS/FUSE filesystems (#27008)
## Summary

- Fixes `bun install` producing incomplete `node_modules` on NFS, FUSE,
and some bind mount filesystems
- On these filesystems, `getdents64` returns `DT_UNKNOWN` for `d_type`
instead of `DT_DIR`/`DT_REG`
- The directory walker was silently skipping these entries, causing
missing files (e.g., 484 instead of 1070 for `@sinclair/typebox`)
- When an entry has unknown kind, we now fall back to `fstatat()` to
resolve the actual file type

## Test plan

- [x] Reproduced with Docker NFS environment: npm installs 1071 files,
bun installs only 484
- [x] Verified fix: bun-debug now installs 1070 files (matching npm
minus `.package-lock.json`)
- [x] Second install from cache also works correctly (1070 files)
- [x] `bun run zig:check-all` passes on all 16 platform targets
- [ ] CI passes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-21 02:18:47 -08:00
Jarred Sumner
e6ec92244c fix(bindgen): hoist WTF::String temps to dispatch scope to prevent use-after-free (#27324)
## Summary

Fixes a use-after-free in bindgen v1 generated C++ bindings that causes
`"switch on corrupt value"` panics in `String.deref` on Windows. This is
a top crash (500+ reports across v1.3.3–v1.3.9), predominantly affecting
standalone executables.

## Root Cause

`Bun::toString(WTF::String&)` copies the raw `StringImpl*` pointer
**without adding a reference**. For optional string arguments with
defaults and dictionary string fields, the generated code declares
`WTF::String` inside an `if` block, but the resulting `BunString`
outlives it:

```cpp
BunString argStr;
if (!arg.isUndefinedOrNull()) {
    WTF::String wtfString_0 = WebCore::convert<IDLDOMString>(...);
    argStr = Bun::toString(wtfString_0);  // copies pointer, no ref
}  // ← wtfString_0 destroyed here, drops ref → StringImpl may be freed
// argStr now holds a dangling pointer to freed memory
```

When the freed memory is reused, `String.deref()` reads garbage for the
tag field → `"switch on corrupt value"` panic.

### Why it was Windows-only / elevated recently

- The mimalloc v3 update (shipped in v1.3.7/v1.3.8) changed heap reuse
patterns on Windows, causing freed memory to be overwritten more
aggressively — turning a latent UAF into a frequent crash
- The mimalloc v3 revert in v1.3.9 reduced crash frequency back to
baseline but did not fix the underlying bug
- A [previous fix](https://github.com/oven-sh/bun/pull/26717) was
reverted due to unrelated CI failures

## Fix

Hoist all `WTF::String` temporaries to the same scope as the Zig
dispatch call, so they stay alive until the `BunString` values are
consumed:

1. **Function string arguments**: `WTF::String` is declared at the top
of the generated function, before any `if` blocks for optional arguments
2. **Dictionary string fields**: The `convert*` function accepts
`WTF::String&` references owned by the caller, so the string data
outlives the `convert*` function and remains valid through the dispatch
call

This approach is exception-safe — `WTF::String` destructors handle
cleanup automatically on all exit paths (normal return,
`RETURN_IF_EXCEPTION`, etc.) with no leaked refs.

### Difference from the previous fix

The [previous fix](https://github.com/oven-sh/bun/pull/26717) hoisted
`WTF::String` for function arguments but kept dictionary field temps
**inside** the `convert*` function. This left dictionary string fields
as use-after-return — `result->encoding` would be a dangling pointer
after `convert*` returned. This fix correctly passes `WTF::String&` refs
from the dispatch scope through to the `convert*` function.

### Affected call sites

Only 2 call sites have the vulnerable pattern (`DOMString` +
`.default(...)`):
- `Bun.stringWidth()` — `str: t.DOMString.default("")`  
- `os.userInfo()` — `encoding: t.DOMString.default("")` in
`UserInfoOptions` dictionary

Note: bindgen v2 is not affected — it uses `releaseImpl().leakRef()`
which transfers ownership correctly.
2026-02-21 02:15:52 -08:00
Dylan Conway
b509acb533 Revert "fix: clean up ESM registry when require() of ESM module fails… (#27325)
… (#27288)"

This reverts commit 21c3439bb4.
2026-02-21 01:14:27 -08:00
robobun
ede635b8a9 fix(install): store tarball integrity hash in lockfile for HTTPS dependencies (#27018)
## Summary
- HTTPS/URL tarball dependencies were not having their integrity hash
stored in the lockfile, allowing a malicious server to change the
tarball contents without detection on subsequent installs
- Now computes a sha512 hash from the downloaded tarball bytes during
extraction and stores it in both the binary lockfile and text bun.lock
- The hash is verified on re-download, matching the behavior of npm
registry packages
- Old lockfiles without integrity hashes continue to work (backward
compatible)

## Changes
- `src/install/integrity.zig`: Added `Integrity.forBytes()` to compute
sha512 from raw bytes
- `src/install/install.zig`: Added `integrity` field to `ExtractData`
struct
- `src/install/PackageManagerTask.zig`: Compute hash from tarball bytes
for both remote and local tarball tasks
- `src/install/PackageManager/processDependencyList.zig`: Set
`package.meta.integrity` from computed hash
- `src/install/lockfile/bun.lock.zig`: Serialize/deserialize integrity
for `remote_tarball` and `local_tarball` types

## Test plan
- [x] Integrity hash is stored in text lockfile for tarball URL deps
- [x] Integrity hash is consistent/deterministic across reinstalls
- [x] Integrity mismatch (changed tarball content) causes install
failure
- [x] Old lockfiles without integrity still install successfully
(backward compat)
- [x] Fresh installs produce integrity hashes
- [x] All 12 existing tarball tests pass (no regressions)
- [x] Tests fail with `USE_SYSTEM_BUN=1` (confirms fix is effective)

Fixes GHSA-jfhr-4v9p-9rm4

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
2026-02-20 23:52:45 -08:00
robobun
76ceb26e0a fix(socket): prevent null deref in Listener.getsockname (#27303)
## Summary
- Fix null pointer dereference in `Listener.getsockname()` when called
without an object argument (or with a non-object argument)
- `getsockname()` wrote properties directly into its first argument via
`.put()`, which calls `getObject()` in C++ — this returns null for
non-object values like `undefined`, causing a crash at
`BunString.cpp:942`
- Now validates the argument is an object first; if not, creates a new
empty object, writes properties into it, and returns it

## Crash reproduction
```js
const listener = Bun.listen({
    hostname: "localhost",
    port: 0,
    socket: { data() {} },
});
listener.getsockname(); // Segfault - null pointer dereference
```

## Test plan
- [x] Added `test/js/bun/http/listener-getsockname.test.ts` with tests
for calling `getsockname()` with no argument, with an object argument,
and with a non-object argument
- [x] Verified test crashes with system bun and passes with patched
build
- [x] Verified original fuzzer reproduction no longer crashes

---------

Co-authored-by: Dylan Conway <dylan.conway567@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 22:50:44 -08:00
robobun
06f26e5f01 fix: use BoringSSL for std.crypto random seed to support older Linux kernels (#27282)
## Summary

- Override Zig's default `cryptoRandomSeed` in `std_options` to use
BoringSSL's `RAND_bytes` (via `bun.csprng`) instead of the `getrandom()`
syscall
- On Linux kernels < 3.17 (e.g. Synology NAS with kernel 3.10), the
`getrandom` syscall doesn't exist and returns `ENOSYS`, causing Zig's
stdlib to panic with `"getrandom() failed to provide entropy"`
- BoringSSL already handles this gracefully by falling back to
`/dev/urandom`

## Details

Bun already uses BoringSSL's `RAND_bytes` for all its own cryptographic
random needs (`bun.csprng`). However, Zig's standard library
`std.crypto.random` uses a separate code path that calls the `getrandom`
syscall directly, with no fallback for `ENOSYS`.

Zig's `std.Options` struct provides a `cryptoRandomSeed` override for
exactly this purpose. This PR sets it to `bun.csprng` in both
`src/main.zig` and `src/main_test.zig`.

## Test plan

- [x] `bun bd` compiles successfully
- [x] `crypto.getRandomValues()`, `crypto.randomUUID()`, and
`require("crypto").randomFillSync()` all work correctly
- Cannot write a meaningful automated regression test since reproducing
requires a Linux kernel < 3.17

Closes #27279

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
2026-02-20 21:13:51 -08:00
robobun
9f5970938f fix(spawn): prevent integer overflow in getArgv with large array length (#27316)
## Crash
Integer overflow panic in `getArgv` when `Bun.spawn`/`Bun.spawnSync`
receives an array with `.length` near u32 max (e.g. 4294967295).

## Reproduction
```js
const arr = ["echo", "hello"];
Object.defineProperty(arr, "length", { value: 4294967295 });
Bun.spawnSync(arr);
```

## Root Cause
`JSArrayIterator.len` is a `u32` derived from the JS array's `.length`
property. In `getArgv`, the expression `cmds_array.len + 2` (for argv0 +
null terminator) overflows `u32` arithmetic when `len` is close to `u32`
max. This causes a panic in debug builds and a segfault in release
builds. Additionally, the validation checks (`isEmptyOrUndefinedOrNull`
and `len == 0`) were placed after the overflowing `initCapacity` call,
so they couldn't prevent the crash.

## Fix
- Move validation checks before the `initCapacity` call
- Add a length check rejecting arrays with length > `u32 max - 2`
- Widen `cmds_array.len` to `usize` before adding 2 to prevent overflow
- Use `try argv.append()` instead of `appendAssumeCapacity` for safety

## Verification
- Reproduction no longer crashes (throws clean "cmd array is too large"
error)
- Normal `Bun.spawn`/`Bun.spawnSync` usage unaffected
- Added regression test at
`test/js/bun/spawn/spawn-large-array-length.test.ts`

Co-authored-by: Claude <noreply@anthropic.com>
2026-02-20 20:24:22 -08:00
SUZUKI Sosuke
e9db16c257 fix: release ReadableStream Strong ref on S3 download stream cancel (#27277)
## Summary

Same issue as #27191 (FetchTasklet), but in `S3DownloadStreamWrapper`
(`src/s3/client.zig`).

When a streaming S3 download body is cancelled via `reader.cancel()`,
`S3DownloadStreamWrapper.readable_stream_ref` (a `ReadableStream.Strong`
GC root) was never released. The S3 download continued in the
background, and the Strong ref prevented GC of the ReadableStream —
leaking memory until the download eventually completed.

## Root Cause

`ByteStream.onCancel()` cleaned up its own state but **did not notify
the `S3DownloadStreamWrapper`**. The wrapper only called `deinit()`
(which releases the Strong ref) when `has_more == false` — i.e., when
the S3 download fully completed. If the user cancelled the stream
mid-download, the Strong ref was held until the entire file finished
downloading in the background.

This is the exact same pattern that was fixed for `FetchTasklet` in
#27191.

## Fix

- Register a `cancel_handler` on the `ByteStream.Source` that releases
`readable_stream_ref` when the stream is cancelled. The download
callback will see `readable_stream_ref.get()` return `null` and skip
data delivery until the download finishes and `deinit()` cleans up the
remaining resources.
- Add `clearStreamCancelHandler()` in `deinit()` to null the
`cancel_handler`/`cancel_ctx` on the `ByteStream.Source`, preventing
use-after-free when the wrapper is freed before `cancel()` is called
(e.g., download completes normally).

## Test

Added `test/js/bun/s3/s3-stream-cancel-leak.test.ts` — uses a raw TCP
server (`Bun.listen`) that mocks an S3 GET response: sends one HTTP
chunk then keeps the connection open. Client streams 30 times via
`s3.file().stream()`, reads one chunk, cancels, then asserts
`heapStats().objectTypeCounts.ReadableStream` does not accumulate.
Before the fix, all 30 ReadableStreams leaked; after the fix, 0 leak.
2026-02-20 17:49:01 -08:00
robobun
21c3439bb4 fix: clean up ESM registry when require() of ESM module fails (#27288)
## Summary

- When `require()` loads an ESM module (`.mjs`) that throws during
evaluation, the module was removed from `requireMap` but left in the ESM
registry (`Loader.registry`) in a partially-initialized state
- A subsequent `import()` of the same module would find this corrupt
entry and throw `ReferenceError: Cannot access 'foo' before
initialization` instead of re-throwing the original evaluation error
- Fix by also deleting the module from `Loader.registry` in both
`overridableRequire` and `requireESMFromHijackedExtension` when ESM
evaluation fails, allowing `import()` to re-evaluate from scratch

Closes #27287

## Test plan

- [x] Added regression test in `test/regression/issue/27287.test.ts`
- [x] Verified test fails with system bun (`USE_SYSTEM_BUN=1`)
- [x] Verified test passes with `bun bd test`
- [x] Manual verification: `require()` correctly throws original error,
`import()` now re-throws the same error instead of `ReferenceError`

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Alistair Smith <alistair@anthropic.com>
2026-02-20 17:08:50 -08:00
Dylan Conway
7baf50f379 fix(http): align DeadSocket to prevent crash on Windows ARM64 stable builds (#27290)
## Summary

- `DeadSocket` only contains a `u8` field (alignment 1), so the linker
could place `DeadSocket.dead_socket` at a non-8-byte-aligned address
- When `markTaggedSocketAsDead` creates a tagged pointer embedding this
address and passes it through `bun.cast(**anyopaque, ...)`, the
`@alignCast` panics with "incorrect alignment" because the bottom bits
of the tagged value come from the unaligned address
- Fix: add `align(@alignOf(usize))` to the `dead_socket` variable
declaration

This only manifested on stable (non-canary) Windows ARM64 builds because
the binary layout differs when `ci_assert` is false, shifting the static
variable to a non-aligned address. Canary builds happened to place it at
an aligned address by coincidence.

## Test plan

- [x] Verified `fetch('https://example.com')` no longer crashes on
Windows ARM64 stable build (ENABLE_CANARY=OFF)
- [x] Verified 5 sequential HTTPS fetches complete successfully
- [x] Verified the fix is a single-line change with no behavioral side
effects

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-02-20 13:51:24 -08:00
robobun
76754a8ead fix(shell): support -e and -E flags in builtin echo (#27144)
## Summary

- Bun's builtin `echo` only supported the `-n` flag. The `-e` flag (and
`-E`) were treated as literal text, causing `echo -e password` to output
`-e password` instead of `password`. This broke common patterns like
`echo -e $password | sudo -S ...`.
- Added full `-e` (enable backslash escapes) and `-E` (disable backslash
escapes) flag support, matching bash behavior including combined flags
like `-ne`, `-en`, `-eE`, `-Ee`.
- Supported escape sequences: `\\`, `\a`, `\b`, `\c`, `\e`/`\E`, `\f`,
`\n`, `\r`, `\t`, `\v`, `\0nnn` (octal), `\xHH` (hex).

Closes #17405

## Test plan

- [x] Added 22 tests in `test/regression/issue/17405.test.ts` covering
all escape sequences, flag combinations, and the original issue scenario
- [x] Verified tests fail with system bun (19/22 fail) and pass with
debug build (22/22 pass)
- [x] Verified existing shell tests (`bunshell.test.ts`) still pass —
all 27 echo-related tests pass, no regressions

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
2026-02-19 20:21:49 -08:00
robobun
ecd4e680eb fix(router): don't cache file descriptors in Route.parse to prevent stale fd reuse (#27164)
## Summary
- `FileSystemRouter.Route.parse()` was caching file descriptors in the
global entry cache (`entry.cache.fd`). When `Bun.build()` later closed
these fds during `ParseTask`, the cache still referenced them.
Subsequent `Bun.build()` calls would find these stale fds, pass them to
`readFileWithAllocator`, and `seekTo(0)` would fail with EBADF (errno
9).
- The fix ensures `Route.parse` always closes the file it opens for
`getFdPath` instead of caching it in the shared entry. The fd was only
used to resolve the absolute path via `getFdPath`, so caching was
unnecessary and harmful.

Closes #18242

## Test plan
- [x] Added regression test `test/regression/issue/18242.test.ts` that
creates a `FileSystemRouter` and runs `Bun.build()` three times
sequentially
- [x] Test passes with `bun bd test test/regression/issue/18242.test.ts`
- [x] Test fails with `USE_SYSTEM_BUN=1 bun test
test/regression/issue/18242.test.ts` (system bun v1.3.9)
- [x] Verified 5 sequential builds work correctly after the fix

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
2026-02-19 20:15:11 -08:00
robobun
044bb00382 fix(sqlite): finalize transaction statements on close() to prevent "database is locked" (#27202)
## Summary

- Fixes `db.close(true)` throwing "database is locked" after using
`db.transaction()`
- The `getController` function creates prepared statements via
`db.prepare()` which bypasses the query cache, so they were never
finalized during `close()`
- `close()` now explicitly finalizes any cached transaction controller
statements before calling `sqlite3_close()`

Fixes #14709

## Test plan

- [x] New regression tests in `test/regression/issue/14709.test.ts`
covering:
  - Basic `close(true)` after `transaction()`
  - `close(true)` after transaction with actual work
  - `using` declaration (calls `close(true)` via `Symbol.dispose`)
  - Multiple transaction types (deferred, immediate, exclusive)
  - Nested transactions
- [x] All new tests fail with system bun (`USE_SYSTEM_BUN=1`) and pass
with debug build
- [x] Existing SQLite test suite (`test/js/bun/sqlite/sqlite.test.js`)
passes with no regressions

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
2026-02-19 20:11:02 -08:00
robobun
655aab845d fix(css): prevent style rule deduplication across @property boundaries (#27119)
## Summary

- Fixes the CSS bundler incorrectly removing a `:root` selector when it
appears before an `@property` at-rule and another `:root` exists after
it
- The deduplication logic in `CssRuleList.minify()` was merging style
rules across non-style rule boundaries (like `@property`), which changes
CSS semantics
- Clears the `style_rules` deduplication map when a non-style rule is
appended, preventing merges across these boundaries

## Test plan

- [x] Added regression test in `test/regression/issue/27117.test.ts`
- [x] Verified test fails with system bun (`USE_SYSTEM_BUN=1`) —
reproduces the bug
- [x] Verified test passes with debug build (`bun bd test`)
- [x] Verified adjacent `:root` rules (without intervening at-rules) are
still correctly merged
- [x] All existing CSS bundler tests pass
(`test/bundler/esbuild/css.test.ts` — 53 tests)
- [x] All CSS modules tests pass (`test/bundler/css/css-modules.test.ts`
— 3 tests)

Closes #27117

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
2026-02-19 20:03:18 -08:00
robobun
4141ef1edf fix(shell): fix unicode cursor tracking causing __bunstr_N leak in output (#27226)
## Summary
- Fixed `srcBytesAtCursor()` and `cursorPos()` in the shell lexer's
unicode path (`ShellCharIter(.wtf8)`) to use `self.src.cursor.i` instead
of `self.src.iter.i`, which was permanently stuck at 0
- Fixed `bumpCursorAscii()` to properly decode the codepoint at the new
cursor position instead of storing the last digit character, which
caused the wrong character to be returned on the next read

## Root Cause
When the shell template literal source contained multi-byte UTF-8
characters (e.g., `Í`, `€`), the `LexerUnicode` path was used. In this
path, `srcBytesAtCursor()` and `cursorPos()` referenced
`self.src.iter.i` — the `CodepointIterator`'s internal field that is
never modified (the `next()` method takes `*const Iterator`). This
meant:

1. `srcBytesAtCursor()` always returned bytes from position 0 (the start
of the source)
2. `looksLikeJSStringRef()` checked for `__bunstr_` at position 0
instead of the current cursor position, failing to match
3. The `\x08__bunstr_N` reference was passed through as literal text
into the shell output

This only occurred when **both** conditions were met:
- An interpolated value contained a space (triggering
`needsEscapeBunstr` → stored as `__bunstr_N` ref)
- A subsequent value contained multi-byte UTF-8 (triggering
`LexerUnicode` instead of `LexerAscii`)

Closes #17244

## Test plan
- [x] Added regression tests in `test/regression/issue/17244.test.ts`
- [x] Verified tests fail with `USE_SYSTEM_BUN=1` (system bun 1.3.9)
- [x] Verified tests pass with `bun bd test`
- [x] Ran existing shell tests (`bunshell.test.ts`,
`bunshell-default.test.ts`, `bunshell-instance.test.ts`) — no
regressions

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
2026-02-19 19:38:33 -08:00
robobun
e7cf4b77ba fix(css): strip leading @layer declarations from bundled CSS output (#27131)
## Summary
- When bundling CSS with `@layer` declarations (e.g. `@layer one;`)
followed by `@import` rules with `layer()`, the bundler left the bare
`@layer` statements and `@import` lines in the output even though their
content was already inlined into `@layer` blocks
- The fix adds `.layer_statement` to the leading-rule filter in
`prepareCssAstsForChunk`, which already stripped `@import` and
`.ignored` rules but missed `@layer` statement rules

Closes #20546

## Test plan
- [x] New regression test in `test/regression/issue/20546.test.ts`
covers both separate `@layer` statements and comma syntax
- [x] Test fails with system bun (`USE_SYSTEM_BUN=1`) confirming the bug
- [x] Test passes with debug build (`bun bd test`)
- [x] All 53 existing CSS bundler tests pass
(`test/bundler/esbuild/css.test.ts`)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
2026-02-19 12:31:47 -08:00
robobun
2e5e21015f fix(bundler): emit valid JS for unused dynamic imports (#27176)
## Summary

- Fixes `bun build` producing syntactically invalid JavaScript
(`Promise.resolve().then(() => )`) for unused dynamic imports like `void
import("./dep.ts")` or bare `import("./dep.ts")` expression statements
- When `exports_ref` is cleared for unused results but the `.then(() =>
...)` wrapper was still emitted, the arrow function body was empty. Now
skips the `.then()` wrapper entirely when there's nothing to execute
inside the callback, producing just `Promise.resolve()`
- The bug only affected cases where the import result was unused —
`const x = import(...)`, `await import(...)`, and `.then()` chains were
already correct

Closes #24709

## Test plan

- [x] Added regression test in `test/regression/issue/24709.test.ts`
that validates both `void import()` and bare `import()` statement cases
- [x] Verified test fails with system bun (reproduces the bug) and
passes with debug build (fix works)
- [x] Verified used dynamic imports (`const m = await import(...)`)
still produce correct `.then(() => exports)` output

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
2026-02-19 12:30:15 -08:00
SUZUKI Sosuke
b04303cb23 fix(gc): fix three GC safety issues (speculative fix for BUN-Q81) (#27190)
## Speculative fix for
[BUN-Q81](https://bun-p9.sentry.io/issues/BUN-Q81)

BUN-Q81 is a long-standing `SlotVisitor::drain` segfault during GC
marking (150 occurrences since July 2025, across v1.1.10 through
v1.3.10). A full audit of the codebase for GC safety issues found three
bugs:

### 1. `JSCommonJSModule::m_overriddenCompile` not visited in
`visitChildren`

`m_overriddenCompile` is a `WriteBarrier<Unknown>` that stores the
overridden `module._compile` function (used by `ts-node`, `pirates`,
`@swc-node/register`, etc.). It was the only WriteBarrier field in the
class not visited by `visitChildrenImpl`, making it invisible to the GC.
The pointed-to function could be prematurely collected, and subsequent
GC marking would follow the dangling WriteBarrier pointer into freed
memory.

**This is the strongest candidate for BUN-Q81.**

### 2. `JSSQLStatement::userPrototype` — wrong owner in
`WriteBarrier::set()`

```cpp
// Before (wrong):
castedThis->userPrototype.set(vm, classObject, prototype.getObject());
// After (correct):
castedThis->userPrototype.set(vm, castedThis, prototype.getObject());
```

The owner parameter must be the object containing the WriteBarrier so
the GC's remembered set is updated correctly. All other `.set()` calls
in the same file correctly use `castedThis`.

### 3. `NodeVMSpecialSandbox` — missing `visitChildren` entirely

`NodeVMSpecialSandbox` has a `WriteBarrier<NodeVMGlobalObject>
m_parentGlobal` member but had no `visitChildren` implementation. Added
the standard boilerplate.
2026-02-19 12:28:49 -08:00
SUZUKI Sosuke
b6eaa96e56 fix: release ReadableStream Strong ref on fetch body cancel (#27191)
## Summary

When a streaming HTTP response body is cancelled via `reader.cancel()`
or `body.cancel()`, `FetchTasklet.readable_stream_ref` (a
`ReadableStream.Strong` GC root) was never released. This caused
ReadableStream objects, associated Promises, and Uint8Array buffers to
be retained indefinitely — leaking ~260KB per cancelled streaming
request.

## Root Cause

`ByteStream.onCancel()` cleaned up its own state (`done = true`, buffer
freed, pending promise resolved) but **did not notify the
FetchTasklet**. The Strong ref was only released when:
- `has_more` became `false` (HTTP response fully received) — but the
server may keep the connection open
- `Bun__FetchResponse_finalize` — but this checks
`readable_stream_ref.held.has()` and **skips cleanup when the Strong ref
is set** (line 958)

This created a circular dependency: the Strong ref prevented GC, and the
finalizer skipped cleanup because the Strong ref existed.

## Fix

Add a `cancel_handler` callback to `NewSource` (`ReadableStream.zig`)
that propagates cancel events to the data producer. `FetchTasklet`
registers this callback via `Body.PendingValue.onStreamCancelled`. When
the stream is cancelled, the handler calls
`ignoreRemainingResponseBody()` to release the Strong ref, stop
processing further HTTP data, and unref the event loop.

To prevent use-after-free when `FetchTasklet` is freed before `cancel()`
is called (e.g., HTTP response completes normally, then user cancels the
orphaned stream), `clearStreamCancelHandler()` nulls the
`cancel_handler` on the `ByteStream.Source` at all 3 sites where
`readable_stream_ref` is released.

## Test

Added `test/js/web/fetch/fetch-stream-cancel-leak.test.ts` — uses a raw
TCP server (`Bun.listen`) that sends one HTTP chunk then keeps the
connection open. Client fetches 30 times, reads one chunk, cancels, then
asserts `heapStats().objectTypeCounts.ReadableStream` does not
accumulate. Before the fix, all 30 ReadableStreams leaked; after the
fix, 0 leak.
2026-02-19 12:22:43 -08:00
robobun
6a8f33e7b1 fix(windows): close libuv pipes before freeing to prevent handle_queue corruption (#27124) 2026-02-19 00:29:43 -08:00
Jarred Sumner
c3ae343fc9 fix(windows): use-after-free in WindowsStreamingWriter (#27122) 2026-02-19 00:29:15 -08:00
SUZUKI Sosuke
e216be966e fix: avoid GC allocation inside ObjectInitializationScope (#27111)
## Summary
- Pre-convert strings to JSValues using `MarkedArgumentBuffer` before
entering `ObjectInitializationScope` in `JSC__JSObject__putRecord` and
`JSC__JSValue__putRecord`, since `jsString()` allocates GC cells which
is not allowed inside the scope
- Remove unused `ObjectInitializationScope` declaration in
`JSSQLStatement.cpp`'s `initializeColumnNames`

## Test plan
- [ ] Verify `bun bd test` passes for existing tests that exercise
`putRecord` paths (e.g., HTTP header handling, SQLite column names)
- [ ] Run with `BUN_JSC_validateExceptionChecks=1` to confirm no
exception scope violations

## Changelog
<!-- CHANGELOG:START -->
<!-- CHANGELOG:END -->

🤖 Generated with [Claude Code](https://claude.com/claude-code) (0%
9-shotted by claude-opus-4-6)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
2026-02-18 22:54:59 -08:00
Jarred Sumner
e84bee5d58 Fixes #26979 (#27118)
### What does this PR do?

Fixes https://github.com/oven-sh/bun/issues/26979


### How did you verify your code works?

The test in https://github.com/oven-sh/bun/issues/26979 successfully
reproduced the issue. Thank you!
2026-02-18 21:44:57 -08:00
SUZUKI Sosuke
fb2f304100 fix(node:fs): remove unnecessary path buffer pool alloc on Windows (#27115)
## Summary

- Removes an unnecessary 64KB `path_buffer_pool` allocation in
`PathLike.sliceZWithForceCopy` on Windows for paths that already have a
drive letter
- For drive-letter paths (e.g. `C:\foo\bar`),
`resolveCWDWithExternalBufZ` just does a memcpy, so the intermediate
buffer is unnecessary — we can pass the input slice directly to
`normalizeBuf`
- Eliminates an OOM crash path where `ObjectPool.get()` would panic via
`catch unreachable` when the allocator fails

## Test plan

- [ ] Verify Windows CI passes (this code path is Windows-only)
- [ ] Verify node:fs operations with absolute Windows paths still work
correctly
- [ ] Monitor BUN-Z4V crash reports after deployment to confirm fix

## Context

Speculative fix for BUN-Z4V (124 occurrences on Windows) showing `Panic:
attempt to unwrap error: OutOfMemory` in `sliceZWithForceCopy` →
`path_buffer_pool.get()` → `allocBytesWithAlignment`. We have not been
able to reproduce the crash locally, but the code analysis shows the
allocation is unnecessary for the drive-letter path case.

## Changelog
<!-- CHANGELOG:START -->
Fixed a crash on Windows (`OutOfMemory` panic) in `node:fs` path
handling when the system is under memory pressure.
<!-- CHANGELOG:END -->

🤖 Generated with [Claude Code](https://claude.com/claude-code) (0%
8-shotted by claude-opus-4-6)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 21:16:16 -08:00
Jarred Sumner
a350d496cb Revert "fix(bundler): place standalone HTML scripts in <head> to preserve execution order (#27114)"
This reverts commit 9e32360195.
2026-02-18 19:08:03 -08:00
robobun
9e32360195 fix(bundler): place standalone HTML scripts in <head> to preserve execution order (#27114)
## Summary

- Fix standalone HTML mode (`--compile --target=browser`) placing
bundled JS as `<script type="module">` before `</body>`, which broke
execution order with existing inline body scripts
- Move bundled JS into `<head>` as a classic `<script>` (not
`type="module"`) so it executes synchronously before inline body
scripts, preserving the original script execution order
- Remove the now-unnecessary `addBodyTags()` function and associated
body script injection paths

Fixes #27113

## Test plan

- [x] Added regression test `test/regression/issue/27113.test.ts` that
verifies head scripts appear before `</head>` and don't use
`type="module"`
- [x] Updated existing standalone HTML tests in
`test/bundler/standalone.test.ts` to reflect the change from `<script
type="module">` to classic `<script>`
- [x] All 18 standalone tests pass (`bun bd test
test/bundler/standalone.test.ts`)
- [x] Regression test passes (`bun bd test
test/regression/issue/27113.test.ts`)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
2026-02-18 19:07:10 -08:00
Dylan Conway
9785af304c Windows arm64 CI (#26746)
### What does this PR do?
Sets up ci for windows arm64
### How did you verify your code works?

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-02-18 18:08:10 -08:00
robobun
0b580054a7 fix(stripANSI): validate SIMD candidates to prevent infinite loop on non-escape control chars (#27015)
## Summary
- Fix infinite loop in `Bun.stripANSI()` when input contains control
characters in the `0x10-0x1F` range that are not ANSI escape introducers
(e.g. `0x16` SYN, `0x19` EM)
- The SIMD fast-path in `findEscapeCharacter` matched the broad range
`0x10-0x1F` / `0x90-0x9F`, but `consumeANSI` only handles a subset of
those characters. When `consumeANSI` returned the same pointer for an
unrecognized byte, the main loop in `stripANSI` never advanced, causing
a hang in release builds and an assertion failure in debug builds.
- Fix verifies SIMD candidates through `isEscapeCharacter()` before
returning, matching the behavior the scalar fallback path already had

## Test plan
- [x] Added regression test in `test/regression/issue/27014.test.ts`
with 4 test cases
- [x] Verified test hangs with system bun (v1.3.9) confirming the bug
- [x] All 4 new tests pass with debug build
- [x] All 265 existing `stripANSI.test.ts` tests pass with debug build

Closes #27014

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
2026-02-17 15:28:27 -08:00
robobun
b817abe55e feat(bundler): add --compile --target=browser for self-contained HTML output (#27056)
## Summary
- Adds self-contained HTML output mode: `--compile --target=browser`
(CLI) or `compile: true, target: "browser"` (`Bun.build()` API)
- Produces HTML files with all JS, CSS, and assets inlined directly:
`<script src="...">` → inline `<script>`, `<link rel="stylesheet">` →
inline `<style>`, asset references → `data:` URIs
- All entrypoints must be `.html` files when using `--compile
--target=browser`
- Validates: errors if any entrypoints aren't HTML, or if `--splitting`
is used
- Useful for distributing `.html` files that work via `file://` URLs
without needing a web server or worrying about CORS restrictions

## Test plan
- [x] Added `test/bundler/standalone.test.ts` covering:
  - Basic JS inlining into HTML
  - CSS inlining into HTML  
  - Combined JS + CSS inlining
  - Asset inlining as data URIs
  - CSS `url()` references inlined as data URIs
  - Validation: non-HTML entrypoints rejected
  - Validation: mixed HTML/non-HTML entrypoints rejected
  - Validation: splitting rejected
  - `Bun.build()` API with `compile: true, target: "browser"`
  - CLI `--compile --target=browser`
  - Minification works with compile+browser

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-02-17 15:27:36 -08:00
Jarred Sumner
9256b3d777 fix(Error): captureStackTrace with non-stack function returns proper stack string (#27017)
### What does this PR do?

When Error.captureStackTrace(e, fn) is called with a function that isn't
in the call stack, all frames are filtered out and e.stack should return
just the error name and message (e.g. "Error: test"), matching Node.js
behavior. Previously Bun returned undefined because:

1. The empty frame vector replaced the original stack frames via
setStackFrames(), but the lazy stack accessor was only installed when
hasMaterializedErrorInfo() was true (i.e. stack was previously
accessed). When it wasn't, JSC's internal materialization saw the
empty/null frames and produced no stack property at all.

2. The custom lazy getter returned undefined when stackTrace was
nullptr, instead of computing the error name+message string with zero
frames.

Fix: always force materialization before replacing frames, always
install the custom lazy accessor, and handle nullptr stackTrace in the
getter by computing the error string with an empty frame list.


### How did you verify your code works?

---------

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-02-17 12:34:19 -08:00
SUZUKI Sosuke
6763fe5a8a fix: remove unsafe ObjectInitializationScope from fromEntries (#27088)
## Summary

Speculative fix for
[BUN-1K54](https://bun-p9.sentry.io/issues/7260165386/) — a segfault in
`JSC__JSValue__fromEntries` with 238 occurrences on Windows x86_64 (Bun
1.3.9).

## Problem

`JSC__JSValue__fromEntries` wraps its property insertion loop inside a
`JSC::ObjectInitializationScope`. This scope is designed for fast,
allocation-free object initialization using `putDirectOffset`. However,
the code uses `putDirect` (which can trigger structure transitions)
along with `toJSStringGC` and `toIdentifier` (which allocate on the GC
heap).

In **debug builds**, `ObjectInitializationScope` includes `AssertNoGC`
and `DisallowVMEntry` guards that would catch this misuse immediately.
In **release builds**, the scope is essentially a no-op (only emits a
`mutatorFence` on destruction), so GC can silently trigger during the
loop. When this happens, the GC may encounter partially-initialized
object slots containing garbage values, leading to a segfault.

## Fix

- Remove the `ObjectInitializationScope` block, since `putDirect` with
allocating helpers is incompatible with its contract.
- Add `RETURN_IF_EXCEPTION` checks inside each loop iteration to
properly propagate exceptions (e.g., OOM during string allocation).

## Test

Added a regression test that creates a `FileSystemRouter` with 128
routes and accesses `router.routes` under GC pressure. Verified via
temporary `fprintf` logging that the test exercises the modified
`JSC__JSValue__fromEntries` code path with `initialCapacity=128,
clone=true`.

Note: The original crash is a GC timing issue that cannot be
deterministically reproduced in a test. This test validates correctness
of the code path rather than reproducing the specific crash.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 12:30:09 -08:00