Repository navigation
Conversation
WalkthroughThe printer now preserves non-ASCII raw template and regex contents. Runtime source, cache, bytecode, and HMR paths use UTF-8 handling. The cache format version increases from 24 to 25. Tests cover runtime values, builds, bytecode cache hits, shell output, and a regression case. ChangesNon-ASCII preservation in printer, transpiler, and cache
Possibly related PRs
Suggested reviewers: Pre-merge checks:
|
|
Updated 2:47 AM PT - Oct 23rd, 2026
✅ @robobun, your commit 297c680b4a218991a021df6d89b5edb24cd4dd9a passed in 🧪 To try this PR locally: bunx bun-pr 33866That installs a local version of the PR into your bun-33866 --bun |
|
Found 3 issues this PR may fix:
🤖 Generated with Claude Code |
|
Checked the three suggested issues against this branch:
Leaving the closes list as-is. |
|
This PR may be a duplicate of:
🤖 Generated with Claude Code |
|
On the duplicate-PR report:
This PR additionally gates the zero-copy Latin-1 external-string paths on |
|
Same root-cause fix landed here 23s before #33867 and with broader coverage ( One thing #33867 had that may be worth lifting: |
There was a problem hiding this comment.
Thanks for the quick fix on the bytecode SourceCodeKey path — that and the new compile/BytecodeNonAsciiRegexAndRawTemplate tests look right. I didn't find further issues, but this changes a load-bearing invariant (--target=bun output is no longer guaranteed ASCII) across ~8 encoding-sensitive consumers in the module loader, transpiler cache, and C++ bytecode path, so it's worth a human pass to confirm no other Latin-1-assuming sites were missed.
Extended reasoning...
Overview
This PR stops the JS printer from rewriting non-ASCII code points to \uXXXX inside regex literal patterns and tagged-template raw contents (both are runtime-observable via .source / .raw), and updates every downstream consumer that previously relied on --target=bun printer output being pure ASCII/Latin-1. Touches: js_printer/lib.rs (the two escape loops removed), RuntimeTranspilerStore.rs / jsc_hooks.rs / AsyncModule.rs / DevServer.rs (clone_latin1 → clone_utf8, plus is_all_ascii gates on the zero-copy ref_counted_resolved_source path), OutputFile.rs (to_bun_string{,_ref} gated on is_all_ascii), RuntimeTranspilerCache.rs (vtable put tags UTF8 for non-ASCII, cache version bumped 22→23), and ZigSourceProvider.cpp (bytecode build entry points now decode via Bun::toString so the SourceCodeKey matches runtime). Four test files updated/extended.
Security risks
None identified. No auth/crypto/permissions surface; the change is purely about string encoding fidelity between the printer and its consumers.
Level of scrutiny
High. This is not a mechanical change — it removes a global invariant ("printer output for target=bun is pure ASCII") that at least eight separate consumers across Rust and C++ were silently depending on, and replaces it with per-site is_all_ascii gates or unconditional UTF-8 decoding. Getting one site wrong yields mojibake or (as my earlier comment caught) silent bytecode-cache misses. The fix touches hot module-load paths, the on-disk transpiler cache format, zero-copy external-string construction, and an FFI boundary. Each individual edit is small, but the correctness argument is "I found and updated every consumer," which is exactly the class of change that benefits from a second pair of eyes.
Other factors
- My previous review flagged the
--bytecodeSourceCodeKeymismatch; the author confirmed it withBUN_JSC_verboseDiskCache=1, fixed it in 60c729b viaBun::toString, and added regression tests for both ESM and CJS. That thread is resolved. - Test coverage is thorough for the observable behaviour (
.source/.rawacross Latin-1/BMP/BOM/astral, round-trip throughbun build --target=bun, bytecode cache-hit assertion), and two pre-existing tests that encoded the old buggy behaviour were corrected. - The PR description explicitly notes a coordination point with #33447 on the
EXPECTED_VERSIONbump, and there are three prior/parallel PRs on the same fix — a maintainer should confirm this is the one to land. - Minor perf consideration: new
is_all_asciiscans on the watcher /OutputFilepaths are cheap (SIMD) and only add aclone_utf8copy in the rare non-ASCII case, but worth a glance from someone who owns those hot paths.
There was a problem hiding this comment.
Both earlier findings are addressed and I didn't spot anything new, but this removes the "target=bun output is pure ASCII" invariant and touches every module-loading consumer of it — worth a human pass to confirm the Latin-1-consumer audit is complete.
Extended reasoning...
Overview
Removes non-ASCII → \uXXXX escaping from print_reg_exp_literal and print_raw_template_literal in the JS printer, then updates every downstream consumer that assumed --target=bun output was pure ASCII/Latin-1: RuntimeTranspilerStore, jsc_hooks, AsyncModule, RuntimeTranspilerCache (both inherent and vtable put), OutputFile::to_bun_string{,_ref}, DevServer HMR patch, and the C++ bytecode-generation entry points in ZigSourceProvider.cpp. Bumps EXPECTED_VERSION to 23. 12 files changed with new tests in template-literal.test.ts and bundler_compile.test.ts, plus un-skipping/fixing two existing tests.
Security risks
None identified. No auth/crypto/permissions surface. The change affects string encoding of transpiler output; malformed UTF-8 would go through clone_utf8 → WTF's UTF-8 decoder, which is well-exercised.
Level of scrutiny
High. This removes a load-bearing invariant (all IS_BUN_PLATFORM printer output is ASCII) that multiple hot-path consumers relied on for zero-copy Latin-1 external strings. The PR itself found six such sites; my first review found a seventh (bytecode SourceCodeKey); the sibling PR #33867 found a subset. That iteration history is exactly why a human should confirm the audit — a missed site produces silent mojibake or a silent bytecode-cache miss, not a crash or test failure.
Other factors
- Both of my prior findings were fixed (60c729b, e607b51) and the latest bug-hunt run is clean.
- Test coverage is thorough: Latin-1/BMP/BOM/astral for both
.sourceand.raw,bun build --target=bunround-trip, and--compile --bytecodecache-hit assertions for both ESM and CJS. - The
is_all_asciigates preserve the zero-copy fast path for the overwhelmingly common all-ASCII case; only genuinely non-ASCII output pays theclone_utf8transcode. - The PR description notes a version-bump conflict with #33447 that whichever lands second must resolve.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@test/bundler/bundler_compile.test.ts`:
- Around line 141-167: The stderr assertion in the `itBundled` test for
`compile/BytecodeNonAsciiRegexAndRawTemplate` is checking the wrong verbose
bytecode message. Update the `validate({ stderr })` expectation to match the
actual `BUN_JSC_verboseDiskCache` output for each `format` case
(`generateModule` for `esm`, `generateCJS` for `cjs`) using the test’s `format`
branch, or remove the assertion if you do not want to pin this log string.
In `@test/bundler/transpiler/template-literal.test.ts`:
- Around line 124-127: The negative assertions in template-literal.test.ts are
using uppercase Unicode escape strings, which can miss the actual emitted output
from the printer. Update the checks around the out.js assertions in the template
literal test to use lowercase escape forms, or make the matching
case-insensitive, so the expectations correctly detect escaped output from the
transpiler.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 360ede32-ff9b-4eba-9a59-ea2a38e390d9
📒 Files selected for processing (12)
src/bundler/OutputFile.rssrc/js_printer/lib.rssrc/jsc/AsyncModule.rssrc/jsc/RuntimeTranspilerCache.rssrc/jsc/RuntimeTranspilerStore.rssrc/jsc/bindings/ZigSourceProvider.cppsrc/runtime/bake/DevServer.rssrc/runtime/jsc_hooks.rstest/bundler/bundler_compile.test.tstest/bundler/transpiler/template-literal.test.tstest/js/bun/shell/bunshell.test.tstest/regression/issue/14976/14976.test.ts
|
CI status: the changes here are green. Builds #71127 and #71149 both failed only on unrelated flakes:
None of the new or modified tests ( |
|
Issue #33930 reports the control-character variant of this bug: raw NUL bytes (0x00) inside a regex literal are rewritten into the literal 6-character text For reference, a minimal subset of this change covering only ASCII control characters (one range bound in |
e607b51 to
2a1a2d0
Compare
|
Rebased onto main at 5f7e62d (633 commits). Conflicts resolved: All 13 |
3b27ad1 to
3ea0607
Compare
0b4de69 to
39ee0bf
Compare
39ee0bf to
bf165d1
Compare
|
@claude I believe in you you can do it. |
…tten for the runtime Regex literals, tagged template raw text and preserved comments are observable by the program as written, so the printer no longer escapes them. Everything else it prints for the Bun target is still ASCII. The runtime's printer writes into a BufferWriter that holds one Latin-1 character per byte for the whole print. A code unit above U+00FF goes in as one placeholder byte, with the unit in a side list, and resolve_encoding() widens the buffer in place to UTF-16 once, before the first consumer reads it. The loaders copy the buffer into a string of that width, the --hot intern map owns either width, the transpiler cache stores entries under its LATIN1 and UTF16 tags (format version 44), the Node compile cache gives the bytecode generator a string of that width, and the source map builder counts one column per byte. Writers that produce files or Bun.Transpiler output stay UTF-8. They copy valid UTF-8 text. They store ill-formed text as the lexer decodes it, and a lone surrogate as an escape, which is how the string literals next to it are printed. The source map builder's look-ahead for CR LF tested the byte two after the CR, so "CR, one byte, LF" lost a generated line. With one byte per code unit that byte can be any character. It tests the next byte now. Co-authored-by: pfg <pfg@pfg.pw> Co-authored-by: kernoeb <kernoeb@protonmail.com>
3ea0607 to
b518e9a
Compare
bf165d1 to
297c680
Compare
|
@alii @claude @robobun It seems like this PR is superseded by #44833, namely by this commit, which down-levels template tags when targeting Bun. A much simpler solution for this impasse? |
Fixes #8207
Fixes #8745
Fixes #13853
Fixes #15492
Fixes #15929
Fixes #16763
Fixes #18115
Fixes #33930
Last of a stack (#38708, #38714). The diff is against #38714.
Problem
String.raw`日本語`returns"\u65E5\u672C\u8A9E"and/é/.sourcereturns"\u00E9". Node returns the text as written.print_raw_template_literalandprint_reg_exp_literal(src/js_printer/lib.rs) escape non-ASCII text for the Bun target, and a program can read both texts.Fix
BufferWriterholds Latin-1. It is widened in place to UTF-16 once, at the end of a print that had a character above U+00FF (the design of Preserve non-ASCII in tagged template literals and regex source #28214). Each consumer takes the buffer in its final width: the loaders, the--hotintern map, the transpiler cache,NODE_COMPILE_CACHE, the source map builder.test/bundler/transpiler/template-literal.test.ts(67 tests),test/cli/inspect/inspect-inline-sourcemap.test.ts, and the suites in Notes.Background
Downsides
bun build --target=bunoutput with a non-ASCII regex or tagged template now has raw UTF-8. A bun before Decode module text that comes from disk or bundler output as UTF-8 #38714 reads it as Latin-1.binary-size, builds 130457 and 130282).Notes
Rebase of 2026-10-23 (what changed against bf165d1)
react-compiler.test.ts,does not overflow the stack on a component that is nested deeply > JSXprintedMaximum call stack size exceeded while generating code for this file. Decode module text that comes from disk or bundler output as UTF-8 #38714 passed that lane on the same main (build 130282).BufferWriter::write_allhad adebug_assert!with a formatted message. The asan lane builds with assertions on. The assert was inlined at each print site, and ASAN gives each temporary of the message its own stack slot. So each frame of the printer grew.assert_accepts_printed_bytes, which is not inlined. A UTF-8 writer does not call it. A build without assertions has thewrite_byteandwrite_allof main, as before.--profile=release-asanbuilds. The frames are those of the printer for--target=browserwith no source map. The depth is the deepest nesting of<a>thatbun build --react-compilerprints. The test uses 600.print_exprframeprint_propertyframeprint_stmtframeifstatements 875.react-compiler.test.ts(121 pass, 1 skip, 1 todo) andtemplate-literal.test.ts(67 pass).bundler_bytecode_portable.test.ts: the same seven fingerprints move. They are computed again because the WebKit upgrade (Upgrade WebKit to 33ce3bf11a71 #46279) changed the.jscbytes on main. The.jshashes of the seven did not change.template-literal(67),inspect-inline-sourcemap(5),bundler_bun(26),transpiler.test.js(258),repl(131),coverage(44),transpiler-cache(24),14976(7),runtime-transpiler(18),inspect-error(46),bunshell(466),isolation(50),source-positions(325),source-too-large(3),hot(28),filesystem_router(50),production(23),bundler_compile(88),bundler_bytecode_portable(22),react-compiler(121),test/js/bun/sourcemap/(367), the 14 nodetest-compile-cache-*tests. No test failed.template-literal.test.tswith the released 1.4.3: 58 of 67 fail.binary-sizefor the heads of 2026-10-21 (bf165d1 in build 130457, Decode module text that comes from disk or bundler output as UTF-8 #38714's 3ea0607 in build 130282): linux-x64 +20,480 bytes, linux-x64-musl +20,480, linux-aarch64 0, darwin-x64 +16,400, darwin-aarch64 +16, windows-x64 +22,016, windows-aarch64 +10,752. Those builds have no assertions, so the change to the assert does not move them.Rebase of 2026-10-21 (what changed against 0b4de69)
VirtualMachine::ref_counted_string(Hardening: fix crashes across the runtime, bundler, dev server, shell and CLI #46021): it returnsResult<_, StringTooLong>, checks the length first, and setson_before_deinitaftercreate_external. The intern for two widths follows that shape in the same function, which now takes the encoding. The length check counts code units. Main's test for it inhot.test.tshas two more rows: a UTF-16 module over the limit, and one that is under the limit in code units and over it in bytes.--hotarm ofAsyncModule.rs(Hardening: fix crashes across the runtime, bundler, dev server, shell and CLI #46021). That file now only copies the buffer in its width.-e/-pexit code,.copy,_) #45267 now. This PR does not need it: a UTF-8 writer's output is valid UTF-8 for every input, and the.loadtest here passes without repl: evaluate input the REPL transform does not take through each caller's result path (-e/-pexit code,.copy,_) #45267.bundler_bytecode_portable.test.ts: the same seven fingerprints move as before (features, records and all, plain and minified, and libraries), computed again for main's.jscformat. In the 16.7 MBlibraries.jsbundle the only difference between main's printer and this one is two lines:\u{1f4bf}in tagged template text is💿again.Lone surrogates in a UTF-8 writer (new)
\uD800).\uD800, which is what main prints for the Bun target and what a string literal gets. After a backslash (an identity escape of the surrogate) that backslash starts the escape. The regex matches as before and the cooked text of a template is unchanged..sourceand.rawshow the escape, as on main.Self-review of this revision
update_generated_verbatim), with a test.String::clone_utf16_widehad a debug assertion that a transpiler cache file edited by hand could fail.sourceURL. Before, it also passed without the change.hot.test.tsstring limit test are labelled as guards.template-literal.test.tsalso pass without the change. Their comment says that they pin line accounting.bun build --target=bun outputs only asciiin14976.test.ts. Its fixture has no regex or template, and the assertion holds.Tests on a debug (ASAN) build of 17bb0534 (the head changes only comments after it)
template-literal(67),inspect-inline-sourcemap(5),bundler_bun(16),transpiler.test.js(255),repl(131),coverage(37),transpiler-cache(24),14976(7),runtime-transpiler(18),inspect-error(45),bunshell(466),isolation(47), main'ssource-positions(313),source-too-large(3), the two non-ASCII tests ofproduction.test.ts, the 37 bytecode and non-ASCII tests ofbundler_compile, the fingerprint snapshot ofbundler_bytecode_portable, the 14 nodetest-compile-cache-*tests.hot.test.ts: 22 of 23.does not take a promise of the program's, rejected and handled, for that of the entry pointprintscollectedtwice on this machine. The same failure is in CI build 130282 of Decode module text that comes from disk or bundler output as UTF-8 #38714 (passed on retry) and shows with a release build of 2026-09-18.filesystem_router.test.ts: 49 of 50.reload() while Bun.build() resolves the same directoryreached its 60 s limit on this build. The fixture alone prints the expected line after 71 s.Earlier history
Rebase of 2026-10-02 (what changed against the revision alii reviewed)
Result, nowrittencounter. The buffer has one byte per code unit until the print is over, so the positions that the printer compares are code units throughout.BunString(compile: resolve module record names through the bytecode string table #40677). TheBytecodeSourceEncodingenum and its C++ mirror are gone.NODE_COMPILE_CACHEbuilds the string from the printer's bytes and their width (bun_js_printer::clone_as_string) and gives it to the generator. The bundler's side is in Decode module text that comes from disk or bundler output as UTF-8 #38714.--compilestores each module in the width JSC loads it in on main (bun build --compile: embed text imports as pre-encoded strings #40177, Never hand one StringImpl to two threads' atom tables (object URLs, BroadcastChannel, --compile workers) #40436, compile: resolve module record names through the bytecode string table #40677), which was --compile loads any non-ASCII module as a 16-bit heap copy instead of the mmapped section #38771. Nothing here touchesStandaloneModuleGraphany more.Bun.Transpiler) writes verbatim text that is not valid UTF-8 as the UTF-8 form of the lexer's decode, and no longer as the raw bytes. Before, a source saved as Latin-1 gave a bundle whose regex did not match a string written with the same bytes. The cost is oneis_valid_utf8scan of each regex, raw template and preserved comment text in those writers.write_byte,write_all,reserve_nextandadvance_byare the code that main has. Added per print: oneis_emptycheck inresolve_encoding. Added per module in the--hotintern map: 8 bytes (RefStringholds a two-variant buffer). Added per regex, raw template and preserved comment in a UTF-8 writer: oneis_valid_utf8scan.binary-sizejob: the build of 0b4de69 (123298) is 77.12 MB on linux-x64, 60.58 MB on darwin-aarch64 and 83.79 MB on windows-x64. The build of Decode module text that comes from disk or bundler output as UTF-8 #38714 (122781) is 77.10, 60.56 and 83.77 MB. That is about +20 KB on each.resolve_encodingwidens the buffer in place once, after the source map builder and before the first consumer. U+2028 and U+2029 do not show in the 8-bit buffer, so the builder counts text that has one from its source (update_generated_verbatim). Its UTF-16 path is gone.bundler_bytecode_portable.test.ts: seven fingerprints move (features, records and all, plain and minified, and libraries). Their bundles have tagged template raw text with non-ASCII characters, which is printed as written now. The other 19 entries are the same on a debug build.template-literal(63),inspect-inline-sourcemap(5),bundler_bun(16),transpiler.test.js(238),bunshell(438),14976(7),runtime-transpiler(18),repl(130),coverage(29),transpiler-cache(20), the two non-ASCII tests ofproduction.test.ts, the 32 bytecode tests and 6 non-ASCII tests ofbundler_compile,bundler_bytecode_portable(22),hot.test.ts(16),isolation.test.ts(41),inspect-error(41), the 14 nodetest-compile-cache-*tests.The account of the previous revision
Problem
String.raw`日本語` returns"\u65E5\u672C\u8A9E"(18 characters) and/é/.sourcereturns"\u00E9"; Node returns"日本語"and"é". Same for any tag function's.raw,String(regex), and thebun build --target=bun/--compile/--bytecodeoutput of such code.new RegExp(..)is unaffected, so the same pattern reflects differently depending on how it was written.print_raw_template_literalandprint_reg_exp_literalinsrc/js_printer/lib.rsrewrite every non-ASCII code point (and, for regexes, every control byte, Transpiler rewrites raw NUL bytes (0x00) in regex literals: match failure before 1.3.12,RegExp.prototype.sourcestill corrupted in 1.3.14 #33930) to an escape when printing for the Bun target. Escaping is invisible for identifiers, string literals and cooked template text, but these two pieces of text are exposed verbatim to the program (ES2026 22.2.6.13.1 only allows/and line terminators to be escaped in.source; esbuild's--charset=asciiexempts both constructs for the same reason)./*! */comments already broke that assumption (a function containing/*! © */reports©fromFunction.prototype.toString()on every path that goes through the runtime printer today), so simply printing more text verbatim needs the consumers to know what width they are holding.Fix
print_verbatimand are never escaped. Everything else is still escaped, so output for code without such text is byte-for-byte what it was (test/regression/issue/14976"outputs only ascii" still holds). Writers for files and forBun.Transpilerare UTF-8 and copy the text through.BufferWriterstarts out as Latin-1 (init_latin1, one character per byte) and the first verbatim character above U+00FF rewrites the buffer in place as UTF-16 code units, after which printing continues in UTF-16. All printer offsets (prev_reg_exp_end,stmt_start, ...) count characters and the widening maps 1:1, so they stay valid;reserve/advanceregions are converted on commit. A module whose non-ASCII text fits in Latin-1 (/café/) stays 8-bit; a module with中costs one O(n) widening and then two bytes per character, which is what JSC needs for it anyway. The ASCII path is one predictable branch per write.update_generated_utf16_slow);print_verbatimflushes the pending bytes before widening and doubles the builder's byte cursor afterwards, so positions are right whether the widening happens before, after or in the middle of the line being mapped.jsc_hooks,RuntimeTranspilerStore,AsyncModule) copy it withclone_written_as_string(clone_latin1, or the newclone_utf16_wide);--hot/--watchintern map (VirtualMachine::ref_counted_resolved_source,RefString) owns either aBox<[u8]>or aBox<[u16]>, hands JSC an external string of the matching width (String::create_external_utf16is new), and seeds the content hash with the width, since the map is keyed by hash alone;putpasses the printer's bytes and the matching on-disk tag (LATIN1orUTF16; those arms were dead until now) straight through to the writer, andloadreads the entry back into a string of that width; the compile cache hook on the cache-hit path takes the width from the entry's header. TheUTF8tag is no longer written or accepted,Entry.output_codeis a plain string, andEXPECTED_VERSION25 -> 26 discards entries holding escaped output;NODE_COMPILE_CACHEhashes the bytes of either width (a cache hit hands over the same bytes the print path did) and passes the width to the bytecode generators asBytecodeSourceEncoding(arepr(u8)enum mirrored in C++), andZigSourceProvider.cppdecodesUtf8(bundler output on disk),Latin1orUtf16exactly as the corresponding loader does, so the bytecode's SourceCodeKey matches at load time. This is also what makes--bytecode/--compile --bytecodework for non-ASCII bundles, which--compilealready loaded as UTF-8;sourceMappingURLtrailer (on_source_map_chunk) and the debug source dump write through the width-aware buffer. The trailer no longer appends a//# sourceURL=comment: the script's URL comes from the module loader anyway (Bun's debug adapter only readsurl), on main the comment was mojibake for non-ASCII paths, and it was the only non-source text that could have widened a module;bake-codegen.tsasserts the server HMR runtime, whichDevServerstill embeds as a Latin-1 string, stays ASCII now that its regexes are no longer escaped.CodepointIteratorthe lexer builds string literal values with, so underbun x.jsa regex or raw template containing such bytes evaluates to the same string as a literal written with the same bytes; lexer: decode ill-formed UTF-8 in JS source as U+FFFD #38262 changes that shared decoder to U+FFFD replacement and this path follows it automatically. A file written bybun buildkeeps the bytes and is decoded with replacement by whoever loads it, which differs from the in-process result only for WTF-8 encoded lone surrogates (the lexer keeps those forBun.Transpilerstring input); that is noted onwrite_verbatim_utf8.StandaloneModuleGraphstill loads any non-ASCII module throughclone_utf8, i.e. as a 16-bit heap copy; this change makes that case more common, and --compile loads any non-ASCII module as a 16-bit heap copy instead of the mmapped section #38771 is about storing such modules in their final width.bun build --target=bunoutput (without--compile) that contains a non-ASCII regex or tagged template now carries the raw UTF-8, and Bun versions before Decode module text that comes from disk or bundler output as UTF-8 #38714 read// @bunfiles as Latin-1, so such a bundle run on an older Bun gets mojibake in those two places (a/café/built here does not match"café"on 1.3.x, where today's escaped output does). Output for code without those constructs is unchanged, and--compileand the runtime transpiler are unaffected because the runtime that reads the output is the one that wrote it. Worth a line in the release notes.test/bundler/transpiler/template-literal.test.ts:.source/.rawvalues for Latin-1, BMP, BOM, astral and control characters, the Puppeteer incompatibility using ::-p-xpath() selector #13853 and String.raw Iterator encoding error #18115 repros,bun build --target=bun; a matrix of four modules (regex/template text and preserved-comment text, each in an 8-bit variantcafé-©-ÿand a widening variantcafé-中-🐰) through entry point,--hot,// @bunentry point,import,importof a// @bunmodule andrequire(the// @bunrows pass since Decode module text that comes from disk or bundler output as UTF-8 #38714 and pin it; the other 16 fail before this change); transpiler cache write + read back for both widths through the sync and async paths, asserting the entry's width tag (LATIN1forcafé-©-ÿ,UTF16forcafé-中-🐰) and the text stored under it, and that the second run serves the entry untouched (inode, mtime and size compared); one process thatrequires a widening module and then an 8-bit one, whose two entries must be taggedUTF16andLATIN1(the printer is reused, so this pinsreset()restoring Latin-1);NODE_COMPILE_CACHEhit counts for both widths equal to an ASCII twin's, with the module transpiled again, served from the transpiler cache, and with the transpiler cache warmed before the compile cache is enabled (so the bytecode is generated from the cache-served bytes and the header's width, and the final run's hit is the SourceCodeKey match), plus the preserved-comment module; five ill-formed byte sequences (stray continuation byte, truncated sequence, overlong encoding, encoded surrogate, 0xFF) for which.sourceand.rawmust equal a string literal built from the same bytes; and stack positions ofnew Errorsites before and after the widening text (line accounting only; they pass before the change too). 43 of the file's 58 tests fail on the Decode module text that comes from disk or bundler output as UTF-8 #38714 build.test/cli/inspect/inspect-inline-sourcemap.test.ts: decodes the runtime source map the inspector reports for a line that the transpiler prints unchanged,const x = [/.../u, new Error("b"), String.raw..., f()];, in an ASCII, an 8-bit and a widening variant with the same number of code units, and requires every mapping on the line to have equal generated and original columns, including the ones atnew Error,String.rawandf(). That pins the builder's byte mode, its code unit mode and the cursor handoff at the point of widening; the 8-bit and widening variants fail on Decode module text that comes from disk or bundler output as UTF-8 #38714 (the escaped text shifts the columns). A further case runs a widening module from a non-ASCII directory and checksscriptParsed.urland the decoded map.test/bundler/bundler_bun.test.ts: the.jscsidecar accepted for a non-ASCII CJS bundle; a non-ASCII--target=bunbundle's error preview (regex shown as written next to a still-escaped string literal) and frame positions without source maps, and with--sourcemap=inline, where thesourceMappingURLcomment is located in the now 16-bit// @bunsource and positions remap toentry.ts.test/bundler/bundler_compile.test.ts:--compile --bytecodehits for ESM and CJS.test/cli/test/coverage.test.ts: lcov records for 8-bit and widened regex/template text equal the ASCII twin's (the preserved-comment case is in Build the coverage line table in UTF-16 code units #38710).test/bake/dev/production.test.ts: a prerendered page renders both values as written (the preserved-comment case is in Decode module text that comes from disk or bundler output as UTF-8 #38714).test/js/bun/repl/repl.test.ts:-ewith non-ASCII.source/.raw, and.loadof a file with an ill-formed byte inside a regex evaluating to U+FFFD (the preserved-comment case is in Build the source excerpt for errors from 16-bit module sources #38708).transpiler.test.js(Bun.Transpilerkeeps both constructs verbatim while still escaping string literals),bunshell.test.ts([Bun Shell]: ShellError with path string with NON-ASCII string in Bun shell #15929) and the previously skipped case in14976.runtime-transpiler,transpiler-cache,node-module-module(compile cache),test/js/bun/sourcemap/,hot.test.ts, the rest ofbundler_bun,bundler_compile's bytecode and source map cases,repl,coverage,bunshell, the rest oftest/cli/inspect/(debugger-buntranspiledmodulematches scripts onurl;inspect.test.ts's connection tests cannot bind in this environment with or without the change), clippy and fmt on the touched crates.Background
bun build --target=bun,--compile). Identifiers and string literals are printed as escapes so the common module has no bytes above 0x7F and can be handed to JSC as an 8-bit string without a copy of any kind.--hotand--watchthe runtime keeps each module's transpiled text in a refcounted map so reloads of unchanged text share one buffer; JSC is given an external string pointing at that buffer, and the map is keyed by a hash of the content alone.loadcan read it directly into a string of that width;EXPECTED_VERSIONinvalidates every entry when the format or the output changes.--bytecode,NODE_COMPILE_CACHE) are validated at load time by comparing a key derived from the source string against the one stored with the bytecode; a mismatch silently falls back to parsing, which is why the generator has to build the same string (same width, same content) the loader will.Related
no test proof · iteration 19 · platform-specific test(s) that do not run on this machine, deferring to CI, which covers all platforms: test/js/bun/shell/bunshell.test.ts, test/js/bun/repl/repl.test.ts, test/bundler/bundler_compile.test.ts, test/bundler/bundler_bytecode_portable.test.ts, test/bake/dev/production.test.ts