Conversation
Both FileMap import branches in bundle_v2.rs set up Source.path.pretty on their own instead of going through path_with_pretty_initialized. resolve_import_records kept the absolute key as the display path, and run_resolver (taken when an onResolve plugin matched the import and declined) set it on a copy that never made it into the ParseTask, so the linker relativized it later. The same virtual file therefore got a different filename comment, metafile key and chunk hash depending on whether a plugin had declined it, and a different one again from a files entry point or a disk file. Route both branches through path_with_pretty_initialized. The pointer assertion in generate_isolated_hash is replaced with the pretty-path validity check it was ported from: a relative key relativizes to itself and dupe_alloc re-slices pretty out of text, so the two may alias after initialization.
|
Warning Review limit reached
Next review available in: 28 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (3)
Comment |
|
Status: reproduced on the released 1.4.0 build with the probe in the description (same |
There was a problem hiding this comment.
I reviewed this PR and didn't find any bugs. Because it changes user-visible bundler output (metafile keys, filename comments, and chunk hashes for files imports switch from absolute to cwd-relative) and touches the core resolve loop in bundle_v2.rs, a human sign-off on the behavior change would still be worthwhile.
What was reviewed:
- Both FileMap branches now route through
path_with_pretty_initializedlike disk/entry-point/plugin paths already do;run_resolverwrites the result back intofile_map_resultsoParseTask::initreads the relativized path — matches the existing pattern atenqueue_entry_itemandon_resolve. - Confirmed
assert_pretty_is_valid()is defined onbun_paths::fs::Path(the typeSource::pathuses), so the stale comment about it being resolver-only was wrong; the relaxed assertion is sound becausedupe_allocre-slicesprettyat offset 0 oftextwhen they're byte-equal. - The dropped
unsafe { detach_lifetime(...) }is subsumed by the arena-lifetime erasure already insidepath_with_pretty_initialized. - Four new tests cover no-plugin / declining-plugin / resolving-plugin, bare and
./relative keys, disk-override byte-equality, and metafile key consistency.
Extended reasoning...
Overview
This PR fixes an inconsistency in how Bun.build({ files }) modules are named for display purposes. Two FileMap import branches in src/bundler/bundle_v2.rs (run_resolver for the plugin-declined path, resolve_import_records for the no-plugin path) were setting Path::pretty by hand — copying the absolute key verbatim — instead of calling path_with_pretty_initialized, which every other code path (disk imports, entry points, plugin-resolved paths) already uses to produce a cwd-relative display path. The fix routes both branches through the shared helper and, in run_resolver, writes the result back into the resolver::Result that ParseTask::init reads. A related over-strict debug assertion in generate_isolated_hash (pretty.ptr != text.ptr) is replaced with assert_pretty_is_valid(), since dupe_alloc can legitimately re-slice pretty at offset 0 of the interned text when a relative key relativizes to itself.
Security risks
None. This is display-path normalization for bundler output — filename comments, metafile input keys, and the isolated chunk hash. No untrusted input parsing, auth, crypto, or filesystem-write logic is touched.
Level of scrutiny
Medium-high. The change is small (~20 lines of production code) and is a convergence onto an existing, well-exercised helper rather than new logic. But it sits in bundle_v2.rs's core resolve loop, changes user-visible bundler output (metafile keys and // path comments for files imports switch from absolute to relative, and chunk hashes change accordingly), and interacts with FilenameStore interning via dupe_alloc. The PR description is exceptionally thorough — it traces the mechanism end-to-end, shows before/after probe output, references the sibling assertion relaxation in #32716, and confirms the four new tests fail on the released build and that metafile.test.ts, bun-build-api.test.ts, and bundler-plugin-onresolve-entrypoint.test.ts still pass.
Other factors
I verified: assert_pretty_is_valid is defined on bun_paths::fs::Path (src/paths/lib.rs:913), which is the type Source::path uses via re-export, so the removed comment claiming it was unavailable there was stale. The dropped unsafe { bun_ptr::detach_lifetime(...) } in resolve_import_records is subsumed by the arena-lifetime erasure already inside path_with_pretty_initialized (bundle_v2.rs:5776-5777). The get_or_put map key in run_resolver is captured before path_primary is shadowed by the re-interned path, so the map entry stays keyed on the original bytes — unchanged from before. The write-back to file_map_result.path_pair.primary in resolve_import_records was already present; only run_resolver gains it, mirroring the pattern at bundle_v2.rs:2641. The new tests use tempDir, assert deep equality of { outputs: [[path, text]], inputs } across resolution variants (covering hash, text, and metafile in one comparison), and include a positive content check so a matching-but-wrong pair would still fail.
Deferring because the user-visible behavior change to metafile keys and chunk hashes — though clearly a bugfix and clearly the right convention — is the kind of thing a maintainer should sign off on rather than an automated review.
|
On the behavior-change question raised in the review, to make the sign-off concrete:
Unrelated to this diff: the red |
|
Heads up: #38635 removes the |
Problem
Bun.build({ files })and reached through animportis displayed under three different names depending on how the import was resolved. The display name is what ends up in the// pathcomment above the module, in themetafileinput key andimports[].path, and in the chunk's isolated hash, so the hashed output filename changes with it:fileskey (// /tmp/proj/lib.js, metafile key/tmp/proj/lib.js)onResolveplugin matched the import and returnedundefined: comment still absolute, but the metafile key is relativized and the chunk hash differs from the no-plugin build. Declining is supposed to be a no-op.onResolveplugin returned the key itself, the same file used as an entry point, or the same file read from disk: relative to the working directory (proj/lib.js), like every other file.../app/entry.jsand/app/lib.jsside by side in one metafile, and overriding a disk file throughfileschanges the output bytes and the hashed filename even when the contents are identical, because the absolute path of the build machine is printed into the bundle.src/bundler/bundle_v2.rsset upprettyby hand instead of callingpath_with_pretty_initializedlike the disk, entry point and plugin paths do.resolve_import_records(no plugin) copies the absolute key intopretty.run_resolver(plugin declined) does the same on a local copy of the path and never writes it back into theresolver::Result, andParseTask::initreads the path from that result. The parsed source therefore hasprettyaliasingtext, whichLinkerContext::generate_isolated_hashtreats as "not initialized" and relativizes in place, after the filename comment was already printed. That is where the hash and metafile key diverge from the no-plugin build.Fix
path_with_pretty_initialized, andrun_resolverwrites the result back into theresolver::ResultthatParseTask::initreads. Afilesmodule gets the same display path as a disk file at the same location, whichever way the import reached it, and no absolute path of the build machine is written into the output.Path::prettyis documented as the cwd-relative display path,filesentry points (enqueue_entry_item), plugin-resolved paths (on_resolve) and disk imports all go through the same function, andgenerate_isolated_hashhashesprettyprecisely because it is supposed to be location independent. Thefilesoption keeps working as an override of disk files only if the override is invisible in the output. Builds that did not use a plugin will see the metafile key and filename comment offilesimports change from the absolute key to the relative form their entry points already used.generate_isolated_hashassertedpretty.ptr != text.ptrafter initializing the path. That is not an invariant: a relativefileskey such as"bare-key.js"relativizes to itself anddupe_allocre-slicesprettyout oftext, so once relative keys take this path too, debug builds tripped the assertion (release builds were unaffected; the recomputation is idempotent). The assertion is replaced withassert_pretty_is_valid(), which is the check this code had before the port. bundler: allow relative FileMap keys without tripping absolute-path debug asserts #32716 relaxes the same assertion for the entry point version of this situation; itsenqueue_entry_itemchange is independent of this PR.test/bundler/bundler_files.test.ts:filesimport built with no plugin, with a decliningonResolveplugin, and with a plugin returning the key produces identical output text, hashed filenames and metafile./key (this one also exercises the assertion in debug builds)fileswith identical contents produces byte-identical output, filename and metafile to the plain disk buildimports[].pathmatches itmetafile.test.ts,bun-build-api.test.tsandbundler-plugin-onresolve-entrypoint.test.tsstill pass.Background
Pathin the bundler carries two strings:text, the canonical location used as the identity of the module (forfiles, the map key), andpretty, the display form.prettyis what is printed in filename comments, used as metafile keys, and hashed into chunk names; forfile-namespace paths it is made relative to the working directory with forward slashes bypath_with_pretty_initialized.Pathhasprettypointing at the same bytes astext. Several places use that pointer equality as "display path not computed yet" and compute it on the spot, which is why the plugin-declined build ended up relativized late instead of never.FileMapis thefilesoption.FileMap::resolvereturns aresolver::Resultfor a key, andParseTask::inittakes its path from that result, so any display path set up for the module has to be stored back into it.Probe on the released build (1.4.0), same inputs, three ways to reach the import
With this change all of the above print
// p-FDpfUu/lib.js, key the module asp-FDpfUu/lib.js, and produce the same hashed filename.New tests on the released build