Repository navigation
SSG stuff - #20998
Merged
Merged
SSG stuff#20998
Conversation
Jarred-Sumner
requested changes
Jul 17, 2025
| const runtime_file: *const OutputFile = &bundled_outputs[runtime_file_index]; | ||
| _ = runtime_file.writeToDisk(root_dir, ".") catch |err| { | ||
| bun.handleErrorReturnTrace(err, @errorReturnTrace()); | ||
| Output.err(err, "Failed to write {} to output directory", .{bun.fmt.quote(runtime_file.dest_path)}); |
Collaborator
There was a problem hiding this comment.
Should this fail the build instead of logging to stderr? Should this be using a logger.Log?
Contributor
Author
There was a problem hiding this comment.
If the runtime file is not written to disk then a lot things will break so I think it is better to fail it now
| * as well as use the async primitives provided by the language. | ||
| */ | ||
| export function renderRoutesForProdStatic( | ||
| export async function renderRoutesForProdStatic( |
Collaborator
There was a problem hiding this comment.
Async creates microtasks. Do we need it?
Contributor
Author
There was a problem hiding this comment.
We need to sequentially load the page modules with await to prevent annoying bugs with concurrently loading and calling esm modules so yeah
Jarred-Sumner
pushed a commit
that referenced
this pull request
Sep 29, 2026
### Problem
- An assert-enabled build (debug, ASAN) aborts with `panic: assertion
failed: self.cap > 0` in `StringBuilder::writable()`. `bun install`
reaches it with a hosted-git shortcut that ends at the colon, like
`"overrides": { "zz": "github:" }`.
- `allocate()` stores a pointer also for a count of 0 bytes.
`writable()` and `allocated_slice()`
(`src/bun_core/string/StringBuilder.rs:201-226`) asserted `cap > 0` for
each builder with a pointer.
### Fix
- `writable()` asserts `len <= cap`, the precondition of its pointer
arithmetic. This removes the abort.
- `allocated_slice()` loses the same assert. The Postgres call site
drops its #22558 guard and reads `written_slice()`. No path on main
fails there.
- Verified: `test/cli/install/hosted-git-info/`. On a debug build of
main, 5 install tests and the guard of 13 parser rows fail (timeouts).
All 18 pass with the fix. Two unit tests cover both views (Miri).
- Self-reviewed: 2 concerns raised, 2 addressed.
### Background
- `StringBuilder` counts each slice, allocates one buffer, then appends
into it.
- `HostedGitInfo` mirrors npm's hosted-git-info. Both return `project:
""` for `github:`. The tests record that output, not a new contract.
- Considered a guard in `HostedGitInfo::copy_from`. It adds a release
branch and leaves the assert false. Bake `computeChunks` (#20998) and
Postgres `ErrorResponse` (#22558) hit it before.
### Downsides
- Assert-enabled builds lose the `cap > 0` check. A builder that counted
nothing and appends bytes still panics on the bounds check.
- Release: 0 bytes changed in 4 `StringBuilder` symbols and 3 callers.
Only `error_response_jsc::to_js` changes: 4412 -> 4400 B.
<details><summary>Notes</summary>
**Origin.** A fuzzer found the abort on an assert-enabled build. No
issue exists. Only linux x64 was run.
**Proof runs (debug build).**
- Source of main (a4f1429): 5 of 5 new install tests fail. The
`beforeAll` hook of the 13 parser rows fails too, so these rows do not
run. Each failure shows `timed out after 5000ms`, not an assertion diff.
The child that aborts needs 5 s or more to print its stack trace. The
late output has `panic: assertion failed: self.cap > 0`.
- This PR: 18 of 18 pass.
- The new tests pass on a release build of main, because a release build
has no assert. `USE_SYSTEM_BUN=1` cannot show the failure.
-
`test/regression/issue/postgres-stringbuilder-assertion-aggressive.test.ts`
passes on main and on this PR. It does not tell the two apart.
- No caller reaches `allocated_slice()` with a zero-capacity buffer now.
The Postgres call site was the one caller that could, and it reads
`written_slice()`. `allocated_slice()` makes a slice over the whole
capacity, and only the first `len` bytes are written.
- Two unit tests in `StringBuilder.rs` take an empty builder through
`writable()`, `allocated_slice()`, `append()`, `written_slice()` and
`move_to_slice()`. `cargo miri test -p bun_core --lib string_builder`
runs them: 2 pass. With one of the old asserts back, 2 fail. CI does not
run them. `cargo test -p bun_core` does not link, and the Miri job does
not list `bun_core`, because other tests of that crate call foreign
functions.
**Measurements.** Release builds of main (a4f1429) and of this PR,
same toolchain.
- release code, 4 StringBuilder symbols and 3 inlining callers: 0 bytes
changed, 0 instructions differ (objdump). The symbols are
`StringBuilder::allocate`, `append`, `append_count`, `add_concat`,
`HostedGitInfo::from_url`, `HTTPClient::clone_metadata` and
`bun_picohttp::Response::clone`.
- release .text: +0 B (readelf, 58,143,477 B on main and on the PR).
`error_response_jsc::to_js`: 4412 -> 4400 B. It is the only one of
78,610 text symbols that changes size.
- from_url instructions per call (`github:user/repo#main`): 2596 -> 2596
(gdb `stepi` count over the call, JIT off). valgrind is not installed in
the container.
- allocator calls per from_url call: malloc 7, zalloc 1, realloc 0, free
7 on PR (main: 7, 1, 0, 7).
- syscalls, `bun install` on the override fixture: file 67 -> 67, read
18 -> 18, write 3 -> 3, network 0 -> 0 (gdb `catch syscall`). strace is
not installed in the container.
- assert-enabled checks per view call: `allocated_slice` 1 -> 0,
`writable` 1 -> 1.
- `len <= cap` in `writable()`: 0 assertion failures in 8 existing test
files on the debug build. The files are the three in
`test/cli/install/hosted-git-info/`,
`test/cli/install/overrides.test.ts`,
`test/bundler/bundler_compile.test.ts`,
`test/js/web/fetch/fetch.test.ts`, `test/js/bun/spawn/spawn.test.ts` and
the Postgres test above. `fetch.test.ts` has the same 25 failures on
main and on the PR. On the PR run, 2 more tests of
`bundler_compile.test.ts` and 4 more tests of `spawn.test.ts` reached
the 5000 ms timeout. All 6 pass with a longer timeout. The host had a
load average above 250 during the runs.
**Callers.** `bun_core::StringBuilder` has 27 construction sites in 18
files: 19 direct and 8 through `HeaderBuilder::default()`. `writable()`
has 10 callers outside the builder. Each one indexes the returned slice
with bounds checks and adds to `len` only what it wrote. So `len <= cap`
holds for them.
**Sites left as they are.**
- `src/runtime/valkey_jsc/js_valkey.rs:657-675` and
`src/bundler/linker_context/computeChunks.rs:551-555` skip the builder
when there is nothing to build. They also skip work, so they stay.
- Open PR #43942 sizes a buffer above the exact sum because of this
assert. It can use the exact sum after this PR.
**Tests.**
- The install tests are in
`hosted-git-info/boundary-conditions.test.ts`, not in
`overrides.test.ts`. That file installs from the public npm registry,
and these tests are about the spec parser.
- The `dependency github:` test records what release bun does for that
spec today. It sends one `GET /repos//tarball/` and exits with code 1.
The test uses a local server through `GITHUB_API_URL`.
- The expected objects in `from-url.test.ts` are the output of
hosted-git-info 9.0.0 on node 26.3.0 for the same 13 inputs.
- The 13 parser rows call the parser in the test process. A `beforeAll`
hook runs `bun install` with the override `github:` in a child process
first. An abort in the parser fails the hook, and the rows do not run. A
child process that imports `bun:internal-for-testing` needs 2 s to 5 s
on a debug build under load. That is too near the 5000 ms default.
- The `bun.lock` test compares each stderr line that names the lockfile.
`bun install` prints `Ignoring lockfile` and continues when it cannot
load `bun.lock`.
- One `bun install` covers the override values of all five providers.
**Differences from hosted-git-info 9.0.0 that this PR does not change.**
- `github:user/repo%2Egit`: the reference returns project `repo`, bun
returns `repo.git`.
- `github:%`: the reference returns `undefined`, `hostedGitInfo.fromUrl`
in bun throws `Invalid Git URL: InvalidURL`.
- `github:/.git`: the reference returns project `""`, bun returns `git`.
`new URL("github:/.git").pathname` is `git` in bun. Open PR #39457
covers that.
</details>
<!-- robobun:evidence:begin -->
---
**[human-review]** gate passed · iteration 1 · 4 files touched
<details><summary>fails on main (without fix)</summary>
```console
ASAN without fix: 6 failed, 32 skipped
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/pr_gate.xml" test/cli/install/hosted-git-info/boundary-conditions.test.ts test/cli/install/hosted-git-info/from-url.test.ts
bun test v1.4.3 (367d939)
test/cli/install/hosted-git-info/from-url.test.ts:
killed 1 dangling process
(fail) fromUrl > shortcuts with an empty user, project and committish > (unnamed) [5008.49ms]
^ a beforeEach/afterEach hook timed out for this test.
(pass) fromUrl > valid urls > bitbucket > parses bitbucket:foo/bar [11.72ms]
(pass) fromUrl > valid urls > bitbucket > parses bitbucket:foo/bar#branch [4.62ms]
(pass) fromUrl > valid urls > bitbucket > parses bitbucket:user@foo/bar [3.28ms]
(pass) fromUrl > valid urls > bitbucket > parses bitbucket:user@foo/bar#branch [1.22ms]
(pass) fromUrl > valid urls > bitbucket > parses bitbucket:user:password@foo/bar [1.26ms]
(pass) fromUrl > valid urls > bitbucket > parses bitbucket:user:password@foo/bar#branch [3.13ms]
(pass) fromUrl > valid urls > bitbucket > parses bitbucket::password@foo/bar [0.94ms]
(pass) fromUrl > valid urls > bitbucket > parses bitbucket::password@foo/bar#branc
... (truncated)
release without fix: 32 skipped
bun test v1.4.3-canary.1 (6780601)
test/cli/install/hosted-git-info/from-url.test.ts:
(pass) fromUrl > shortcuts with an empty user, project and committish > parses github: [2.39ms]
(pass) fromUrl > shortcuts with an empty user, project and committish > parses gitlab: [0.07ms]
(pass) fromUrl > shortcuts with an empty user, project and committish > parses bitbucket:
(pass) fromUrl > shortcuts with an empty user, project and committish > parses gist:
(pass) fromUrl > shortcuts with an empty user, project and committish > parses sourcehut: [1.02ms]
(pass) fromUrl > shortcuts with an empty user, project and committish > parses github:.git [0.01ms]
(pass) fromUrl > shortcuts with an empty user, project and committish > parses github:/
(pass) fromUrl > shortcuts with an empty user, project and committish > parses github:// [0.01ms]
(pass) fromUrl > shortcuts with an empty user, project and committish > parses github:#
(pass) fromUrl > shortcuts with an empty user, project and committish > parses github:?x
(pass) fromUrl > shortcuts with an empty user, project and committish > parses github:@
(pass) fromUrl > shortcuts with an empty user, project and committish > parses
... (truncated)
```
</details>
<details><summary>passes on PR (with fix)</summary>
```console
ASAN with fix: 32 skipped
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/pr_gate.xml" test/cli/install/hosted-git-info/boundary-conditions.test.ts test/cli/install/hosted-git-info/from-url.test.ts
bun test v1.4.3 (367d939)
test/cli/install/hosted-git-info/from-url.test.ts:
(pass) fromUrl > shortcuts with an empty user, project and committish > parses github: [4.28ms]
(pass) fromUrl > shortcuts with an empty user, project and committish > parses gitlab: [1.19ms]
(pass) fromUrl > shortcuts with an empty user, project and committish > parses bitbucket: [0.91ms]
(pass) fromUrl > shortcuts with an empty user, project and committish > parses gist: [0.75ms]
(pass) fromUrl > shortcuts with an empty user, project and committish > parses sourcehut: [0.82ms]
(pass) fromUrl > shortcuts with an empty user, project and committish > parses github:.git [1.52ms]
(pass) fromUrl > shortcuts with an empty user, project and committish > parses github:/ [2.71ms]
(pass) fromUrl > shortcuts with an empty user, project and committish > parses github:// [0.98ms]
(pass) fromUrl > shortcuts with an empty user, project and committish > parses git
... (truncated)
release with fix: 32 skipped
$ bun scripts/build.ts --profile=release
[configured] bun-profile → bun (stripped) in 6641ms (unchanged)
ninja: Entering directory `/workspace/bun/build/release'
[1/89] build.rs build_script_build
[2/89] rustc bun_platform
[3/89] rustc bun_core
[4/89] rustc bun_boringssl_sys
[5/89] rustc bun_safety
[6/89] rustc bun_cares_sys
[7/89] rustc bun_zlib_sys
[8/89] rustc bun_errno
[9/89] rustc bun_picohttp
[10/89] rustc bun_brotli
[11/89] rustc bun_zstd
[12/89] rustc bun_base64
[13/89] rustc bun_ptr
[14/89] rustc bun_output
[15/89] rustc bun_valkey
[16/89] rustc bun_clap
[17/89] rustc bun_lsquic_sys
[18/89] rustc bun_paths
[19/89] rustc bun_tcc_sys
[20/89] rustc bun_collections
[21/89] rustc bun_shell_parser
warning: `feature(generic_const_exprs)` is not supported with the next-generation trait solver
--> src/shell_parser/lib.rs:1:30
|
1 | #![feature(adt_const_params, generic_const_exprs, allocator_api)]
| ^^^^^^^^^^^^^^^^^^^
|
= note: `-Znext-solver=globally` is currently enabled by default for testing
= note: reverted the setting to `-Znext-solver=coherence` for this crate
= note: the currently stable trait s
... (truncated)
```
</details>
<details><summary>diff hotspot</summary>
```
src/bun_core/string/StringBuilder.rs | 30 +++++++-
.../postgres/protocol/error_response_jsc.rs | 8 +-
.../hosted-git-info/boundary-conditions.test.ts | 89 ++++++++++++++++++++++
test/cli/install/hosted-git-info/from-url.test.ts | 47 +++++++++++-
4 files changed, 164 insertions(+), 10 deletions(-)
```
</details>
**gate history** · 1 passed · 0 rejected · iteration 1
<details><summary>evidence per changed file</summary>
```
file reads edits tests
src/bun_core/string/StringBuilder.rs 2 1 35
src/sql_jsc/postgres/protocol/error_response_jsc.rs 2 2 27
…cli/install/hosted-git-info/boundary-conditions.test.ts 5 4 20
test/cli/install/hosted-git-info/from-url.test.ts 6 6 24
```
</details>
<!-- robobun:evidence:end -->
robobun
added a commit
that referenced
this pull request
Sep 29, 2026
JSC::VM::tryCreate can return null. Zig__GlobalObject__create (#16068) and BakeCreateProdGlobal (#20998) check the result and panic with "Failed to allocate JavaScriptCore Virtual Machine". vmForBytecodeCache got the tryCreate call in #16068 without the check, and dereferences the result. Bun::createVM holds the call, the check and the message, and returns a Ref<JSC::VM>. The three sites call it. No report of the null result exists, and no test input produces it. With a debugger that makes the allocation of the first BigInt constant in the VM constructor fail, `bun build --bytecode` ends with "Segmentation fault at address 0x0" before this change and with the panic message after it.
This was referenced Oct 25, 2026
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.
What does this PR do?
Various changes and bug fixes for SSG:
chunk[i]corresponds tooutput_files[i](previously we were pushing sourcemaps and bytecode files and breaking the order, seeOutputFileListBuilder)import.meta.{dirname,dir,url,file}breaking in SSG (inline the values injs_parser.zig)__esmand__commonJSwrapper functions weren't being written to disk and were breaking everythinguse clientcomponent was not being foundimport Foo, * as from './module'would not be able to find the default exportuseStateis used inside of a server component and throw helpful error message'use client'work