Skip to content
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 16 additions & 5 deletions scripts/build/cargo-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,10 @@ function linkerFor(triple: string, cfg: Config): string {
* Write `.cargo/config.toml` next to the workspace `Cargo.toml` (repo root).
* Returns the absolute path written.
*
* Windows-msvc targets are omitted: the MSVC linker isn't a clang driver and
* doesn't take `-fuse-ld=lld`; that path is handled entirely via env in
* `rust.ts` (`CARGO_TARGET_..._LINKER = cfg.msvcLinker`).
* Windows-msvc targets get a rustflags-only section (no `linker =` line):
* the MSVC linker isn't a clang driver and doesn't take `-fuse-ld=lld`;
* that path is handled entirely via env in `rust.ts`
* (`CARGO_TARGET_..._LINKER = cfg.msvcLinker`).
*/
export function generateCargoConfig(cfg: Config): string {
const outPath = resolve(cfg.cwd, ".cargo", "config.toml");
Expand All @@ -79,10 +80,20 @@ export function generateCargoConfig(cfg: Config): string {
"# file is correct on whatever machine ran configure.",
];

// `-Zpolonius=next` everywhere: workspace code relies on the polonius
// borrow checker (see the matching push in rust.ts), so every rustc
// invocation that type-checks workspace crates needs it or borrowck
// fails. Windows-msvc triples get a rustflags-only section (their linker
// is env-only, see the doc comment above) so `cargo check --target
// *-windows-msvc` / `rust:check-all` work.
const polonius = `"-Z", "polonius=next"`;
for (const triple of allRustTargets) {
if (tripleOs(triple) === "windows") continue;
lines.push("");
lines.push(`[target.${triple}]${triple === host ? " # host" : ""}`);
if (tripleOs(triple) === "windows") {
lines.push(`rustflags = [${polonius}]`);
continue;
}
Comment thread
robobun marked this conversation as resolved.
lines.push(`linker = ${JSON.stringify(linkerFor(triple, cfg))}`);
// -Qunused-arguments: rustc passes link args that don't apply to every
// artifact kind (e.g. `-no-pie` when it links a target cdylib; none
Expand All @@ -95,7 +106,7 @@ export function generateCargoConfig(cfg: Config): string {
// `cargo build`/`cargo check`, rust-analyzer); real linker errors still
// fail the link.
lines.push(
`rustflags = ["-C", "link-arg=-fuse-ld=lld", "-C", "link-arg=-Qunused-arguments", "-A", "linker_messages"]`,
`rustflags = ["-C", "link-arg=-fuse-ld=lld", "-C", "link-arg=-Qunused-arguments", "-A", "linker_messages", ${polonius}]`,
);
}
lines.push("");
Expand Down
9 changes: 9 additions & 0 deletions scripts/build/rust.ts
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,15 @@ export function cargoBuildInvocation(cfg: Config): CargoInvocation {
if (!cfg.ci) {
rustflags.push("-Zthreads=8");
}
// Polonius alpha borrow checker: accepts NLL "problem case 3" (a borrow
// returned/escaping on one path no longer blocks the other paths), which
// workspace code now relies on — e.g. `src/collections/linear_fifo.rs`
// no longer compiles under the stock checker. Nightly-only; the pinned
// toolchain is nightly. Must stay in sync with the generated
// `.cargo/config.toml` (cargo-config.ts) so plain `cargo check`,
// rust-analyzer, and `rust:check-all` accept the same code the ninja
// build does.
rustflags.push("-Zpolonius=next");
Comment thread
coderabbitai[bot] marked this conversation as resolved.
// rustc does not emit `.llvm_addrsig` by default on *any* target (verified
// empirically — Linux-gnu, musl, darwin, msvc all missing it). lld's
// `--icf=safe` (flags.ts:960) and lld-link's `/OPT:SAFEICF` (flags.ts:778)
Expand Down
23 changes: 9 additions & 14 deletions src/bun_core/fmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2337,29 +2337,24 @@ pub fn format_ip<'a>(
write!(cursor, "{}", address).map_err(|_| crate::CrateError::NoSpaceLeft)?;
let written = cursor.position() as usize;

// Reshaped for borrowck — compute (start, end) offsets against `into`
// instead of iteratively reborrowing a `result` slice, so the final
// returned `&mut into[start..end]` carries the caller's `'a` lifetime
// cleanly.
let mut start = 0usize;
let mut end = written;
let mut result = &mut into[..written];

// Strip `:<port>`
if let Some(colon) = strings::last_index_of_char(&into[start..end], b':') {
end = start + colon;
if let Some(colon) = strings::last_index_of_char(result, b':') {
result = &mut result[..colon];
}
// Strip brackets
if start < end && into[start] == b'[' && into[end - 1] == b']' {
start += 1;
end -= 1;
if result.first() == Some(&b'[') && result.last() == Some(&b']') {
let len = result.len();
result = &mut result[1..len - 1];
}
// Strip `%<zone>` — Node formats addresses via uv_inet_ntop on the bare
// in6_addr and never includes the zone identifier; the scope is exposed
// separately (e.g. `scopeid` in os.networkInterfaces()).
if let Some(percent) = strings::index_of_char_usize(&into[start..end], b'%') {
end = start + percent;
if let Some(percent) = strings::index_of_char_usize(result, b'%') {
result = &mut result[..percent];
}
Ok(&mut into[start..end])
Ok(result)
}

// ───────────────────────────────────────────────────────────────────────────
Expand Down
27 changes: 10 additions & 17 deletions src/bundler/Chunk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -308,27 +308,20 @@ impl Chunk {
// Look up the CSS chunk via the JS chunk's css_chunks indices.
// This correctly handles deduplicated CSS chunks that are shared
// across multiple HTML entry points (see issue #23668).
// Note: reshaped for borrowck — we scan immutably for the JS chunk, copy the
// css-chunk index into a local, drop the borrow, then re-borrow mutably.
let entry_point_id = self.entry_point.entry_point_id();
let css_idx: Option<usize> = 'find: {
for other in chunks.iter() {
if let Content::Javascript(js) = &other.content {
if other.entry_point.is_entry_point()
&& other.entry_point.entry_point_id() == entry_point_id
{
let css_chunk_indices = &js.css_chunks[..];
if !css_chunk_indices.is_empty() {
break 'find Some(css_chunk_indices[0] as usize);
}
break 'find None;
for other in chunks.iter() {
if let Content::Javascript(js) = &other.content {
if other.entry_point.is_entry_point()
&& other.entry_point.entry_point_id() == entry_point_id
{
let css_chunk_indices = &js.css_chunks[..];
if !css_chunk_indices.is_empty() {
let idx = css_chunk_indices[0] as usize;
return Some(&mut chunks[idx]);
}
break;
}
}
Comment on lines +312 to 324

@panstromek panstromek Aug 12, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code compiles on stable without Polonius. I also tried to test the refactor on the first version of this code in the repo and it also compiles on stable. Do you know what was the original motivation for this workaround? Did the code look differently when it was introduced (maybe that point is not in git history anymore)?

None
};
if let Some(idx) = css_idx {
return Some(&mut chunks[idx]);
}
// Fallback: match by entry_point_id for cases without a JS chunk.
for other in chunks.iter_mut() {
Expand Down
31 changes: 10 additions & 21 deletions src/bundler/transpiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -471,46 +471,35 @@ impl<'a> Transpiler<'a> {
let mut cache_bust_buf = bun_paths::PathBuffer::uninit();

// Bust directory cache and try again
// reshaped for borrowck — a single labelled block would
// return a slice that aliases either `entry_point` (via
// `dirname`) or `cache_bust_buf`. Rust can't unify the two
// disjoint mutable borrows of `cache_bust_buf` across `break`,
// so compute `busted` directly instead.
let busted: bool = 'name: {
let buster_name = 'name: {
if bun_paths::is_absolute(entry_point) {
let dir = bun_paths::resolve_path::dirname::<bun_paths::platform::Auto>(
entry_point,
);
if !dir.is_empty() {
// Normalized with trailing slash
let buster_name = bun_paths::string_paths::normalize_slashes_only(
break 'name bun_paths::string_paths::normalize_slashes_only(
&mut cache_bust_buf[..],
dir,
bun_paths::SEP,
);
break 'name self.resolver.bust_dir_cache(
bun_paths::string_paths::without_trailing_slash_windows_path(
buster_name,
),
);
}
}

// `".."` needs no platform separator rewrite.
let parts: [&[u8]; 2] = [entry_point, b".."];
let top_level_dir = self.fs().top_level_dir;

let buster_name = bun_paths::resolve_path::join_abs_string_buf_z::<
bun_paths::platform::Auto,
>(
top_level_dir, &mut cache_bust_buf[..], &parts
);
self.resolver.bust_dir_cache(
bun_paths::string_paths::without_trailing_slash_windows_path(
buster_name.as_bytes(),
),
bun_paths::resolve_path::join_abs_string_buf_z::<bun_paths::platform::Auto>(
top_level_dir,
&mut cache_bust_buf[..],
&parts,
)
.as_bytes()
};
let busted = self.resolver.bust_dir_cache(
bun_paths::string_paths::without_trailing_slash_windows_path(buster_name),
);

// Only re-query if we previously had something cached.
if busted {
Expand Down
8 changes: 1 addition & 7 deletions src/collections/array_hash_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1199,15 +1199,9 @@ impl<K, V: Default, C: ArrayHashContext<K>, A: MapAllocator> ArrayHashMap<K, V,
) -> Result<GetOrPutResult<'_, K, V>, AllocError> {
let gop = self.get_or_put(key)?;
if !gop.found_existing {
// SAFETY: re-borrow at same index — `gop` borrows `self` so go
// through the slot it already points at.
*gop.value_ptr = value;
}
// Can't return `gop` while it borrows in the branch above without
// NLL gymnastics; recompute via index.
let i = gop.index;
let found = gop.found_existing;
Ok(self.gop_at(i, found))
Ok(gop)
}
}

Expand Down
7 changes: 3 additions & 4 deletions src/collections/linear_fifo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -448,12 +448,11 @@ impl<T, B: LinearFifoBuffer<T>> LinearFifo<T, B> {
self.ensure_unused_capacity(size)?;

// try to avoid realigning buffer
// reshaped for borrowck — check len, drop borrow, maybe
// realign, then take the final borrow.
if self.writable_slice(0).len() < size {
let mut slice = self.writable_slice(0);
if slice.len() < size {
self.realign();
slice = self.writable_slice(0);
}
let slice = self.writable_slice(0);
debug_assert!(slice.len() >= size);
Ok(&mut slice[..size])
}
Expand Down
32 changes: 12 additions & 20 deletions src/dotenv/env_loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,20 +195,16 @@ impl Loader {
buf: &'b mut PathBuffer,
) -> Option<&'b ZStr> {
// Check NODE or npm_node_execpath env var, but only use it if the file actually exists.
// NLL workaround: compute the length in an inner scope so the borrow of `buf` for the
// executable check ends before we either return a fresh borrow or fall through to `which`.
let env_len = self
if let Some(node) = self
.get(b"NODE")
.or_else(|| self.get(b"npm_node_execpath"))
.filter(|n| !n.is_empty() && n.len() < MAX_PATH_BYTES)
.map(|node| {
buf[..node.len()].copy_from_slice(node);
buf[node.len()] = 0;
node.len()
});
if let Some(len) = env_len {
if bun_sys::is_executable_file_path(ZStr::from_buf(&buf[..], len)) {
return Some(ZStr::from_buf(&buf[..], len));
{
buf[..node.len()].copy_from_slice(node);
buf[node.len()] = 0;
let node_path = ZStr::from_buf(&buf[..], node.len());
if bun_sys::is_executable_file_path(node_path) {
return Some(node_path);
}
}

Expand Down Expand Up @@ -1105,17 +1101,13 @@ impl<'a> Parser<'a> {
if end >= self.src.len() {
return Ok(&self.src[self.src.len()..]);
}
// reshaped for borrowck — `parse_quoted` returns a borrow of
// `self.value_buffer`; capture only its length, then re-borrow the buffer
// after the match so the unquoted fallthrough can re-borrow `self`.
let quoted_len: Option<usize> = match self.src[end] {
b'`' => self.parse_quoted::<b'`'>()?.map(|v| v.len()),
b'"' => self.parse_quoted::<b'"'>()?.map(|v| v.len()),
b'\'' => self.parse_quoted::<b'\''>()?.map(|v| v.len()),
let quoted = match self.src[end] {
b'`' => self.parse_quoted::<b'`'>()?,
b'"' => self.parse_quoted::<b'"'>()?,
b'\'' => self.parse_quoted::<b'\''>()?,
_ => None,
};
if let Some(len) = quoted_len {
let value = &self.value_buffer[..len];
if let Some(value) = quoted {
return Ok(if IS_PROCESS {
value
} else {
Expand Down
9 changes: 3 additions & 6 deletions src/exe_format/elf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,17 +167,14 @@ impl ElfFile {
if strtab_end > self.data.len() as u64 {
return;
}
// reshaped for borrowck — copy strtab bounds out so we can
// re-borrow self.data mutably below.
let strtab_off = usize::try_from(strtab_shdr.sh_offset).expect("int cast");
let strtab_len = usize::try_from(strtab_shdr.sh_size).expect("int cast");
let strtab = &self.data[usize::try_from(strtab_shdr.sh_offset).expect("int cast")..]
[..usize::try_from(strtab_shdr.sh_size).expect("int cast")];

for i in 0..shnum as usize {
let shdr = self.read_shdr(ehdr.e_shoff, u16::try_from(i).expect("int cast"));
if shdr.sh_name as usize >= strtab_len {
if shdr.sh_name as usize >= strtab.len() {
continue;
}
let strtab = &self.data[strtab_off..][..strtab_len];
let name = slice_to_nul(&strtab[shdr.sh_name as usize..]);
if name != b".interp" {
continue;
Expand Down
8 changes: 2 additions & 6 deletions src/install/PackageManager/WorkspacePackageJSONCache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,12 +152,8 @@ impl WorkspacePackageJSONCache {
&buf[..abs_package_json_path.len()]
};

// reshaped for borrowck — we cannot hold an entry borrow across
// `self.map.remove`, so check
// membership up front and only insert into the map after a successful
// read+parse. Net map state is identical on every path.
if self.map.contains_key(path) {
return GetResult::Entry(self.map.get_mut(path).unwrap());
if let Some(entry) = self.map.get_mut(path) {
return GetResult::Entry(entry);
}

// Owned NUL-terminated copy reused
Expand Down
30 changes: 12 additions & 18 deletions src/install/PackageManager/patchPackage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1242,16 +1242,10 @@ fn overwrite_package_in_node_modules_folder(

type NodeModulesIterator<'a> = tree::Iterator<'a, { tree::IteratorPathStyle::NodeModules }>;

// reshaped for borrowck — `tree::Iterator::next` returns an
// `IteratorNext<'_>` borrowing the iterator's internal `path_buf`, so we
// cannot return it from inside a `while let` (borrowck rejects the next
// iteration's reborrow even though it's unreachable). Callers only need
// `relative_path`, so copy it out into an owned `Vec<u8>`.

fn node_modules_folder_for_dependency_ids(
iterator: &mut NodeModulesIterator<'_>,
fn node_modules_folder_for_dependency_ids<'a>(
iterator: &'a mut NodeModulesIterator<'_>,
ids: &[IdPair],
) -> Option<Vec<u8>> {
) -> Option<&'a ZStr> {
loop {
let node_modules = iterator.next(None)?;
let mut found = false;
Expand All @@ -1262,21 +1256,21 @@ fn node_modules_folder_for_dependency_ids(
}
}
if found {
return Some(node_modules.relative_path.as_bytes().to_vec());
return Some(node_modules.relative_path);
}
}
}

fn node_modules_folder_for_dependency_id(
iterator: &mut NodeModulesIterator<'_>,
fn node_modules_folder_for_dependency_id<'a>(
iterator: &'a mut NodeModulesIterator<'_>,
dependency_id: DependencyID,
) -> Option<Vec<u8>> {
) -> Option<&'a ZStr> {
loop {
let node_modules = iterator.next(None)?;
if !node_modules.dependencies.contains(&dependency_id) {
continue;
}
return Some(node_modules.relative_path.as_bytes().to_vec());
return Some(node_modules.relative_path);
}
}

Expand Down Expand Up @@ -1350,7 +1344,7 @@ fn pkg_info_for_name_and_version(
Global::crash();
}
};
return (pkg_id, folder);
return (pkg_id, folder.as_bytes().to_vec());
}

// we found multiple dependents of the supplied pkg + version
Expand All @@ -1368,7 +1362,7 @@ fn pkg_info_for_name_and_version(
}
};

return (pkg_id, folder);
return (pkg_id, folder.as_bytes().to_vec());
}

// Otherwise the user did not supply a version, just the pkg name
Expand All @@ -1386,7 +1380,7 @@ fn pkg_info_for_name_and_version(
Global::crash();
}
};
return (pkg_id, folder);
return (pkg_id, folder.as_bytes().to_vec());
}

// Otherwise we have multiple matches
Expand Down Expand Up @@ -1419,7 +1413,7 @@ fn pkg_info_for_name_and_version(
Global::crash();
}
};
return (pkg_id, folder);
return (pkg_id, folder.as_bytes().to_vec());
}

bun_core::pretty_errorln!(
Expand Down
Loading