diff --git a/src/bundler/LinkerContext.rs b/src/bundler/LinkerContext.rs index 3653bbffea6e..469aab3fc475 100644 --- a/src/bundler/LinkerContext.rs +++ b/src/bundler/LinkerContext.rs @@ -2462,7 +2462,7 @@ impl<'a> LinkerContext<'a> { pub fn append_isolated_hashes_for_imported_chunks( &self, hash: &mut ContentHasher, - chunks: &mut [Chunk], + chunks: &[Chunk], index: u32, chunk_visit_map: &mut AutoBitSet, ) { @@ -2475,19 +2475,14 @@ impl<'a> LinkerContext<'a> { } chunk_visit_map.set(index as usize); + let chunk = &chunks[index as usize]; + // Visit the other chunks that this chunk imports before visiting this chunk - // Note: reshaped for borrowck — collect imports first to avoid aliasing &chunks[index] with recursive &mut chunks - let cross_chunk_imports: Vec = chunks[index as usize] - .cross_chunk_imports - .slice() - .iter() - .map(|import| import.chunk_index) - .collect(); - for chunk_index in cross_chunk_imports { + for import in chunk.cross_chunk_imports.slice() { self.append_isolated_hashes_for_imported_chunks( hash, chunks, - chunk_index, + import.chunk_index, chunk_visit_map, ); } @@ -2496,76 +2491,59 @@ impl<'a> LinkerContext<'a> { // express cross-chunk dependencies via `cross_chunk_imports` above, but // HTML (and CSS) chunks only reference other chunks through pieces, so // recurse on those too. - // Note: reshaped for borrowck — collect piece queries first so the - // `&chunks[index]` borrow is dropped before the recursive `&mut chunks` - // calls in the Chunk/Scb arms below. `final_rel_path` is re-indexed per - // Asset arm (not hoisted) because it is now `Box<[u8]>` (not `Copy`). - let piece_queries: Vec<(crate::chunk::QueryKind, u32)> = - if let crate::chunk::IntermediateOutput::Pieces(pieces) = - &chunks[index as usize].intermediate_output - { - pieces - .slice() - .iter() - .map(|p| (p.query.kind(), p.query.index())) - .collect() - } else { - Vec::new() - }; - - for (kind, piece_index) in piece_queries { - match kind { - crate::chunk::QueryKind::Asset => { - let mut from_chunk_dir = bun_paths::resolve_path::dirname::< - bun_paths::resolve_path::platform::Posix, - >( - &chunks[index as usize].final_rel_path - ); - if from_chunk_dir == b"." { - from_chunk_dir = b""; - } + if let crate::chunk::IntermediateOutput::Pieces(pieces) = &chunk.intermediate_output { + for p in pieces.slice() { + match p.query.kind() { + crate::chunk::QueryKind::Asset => { + let mut from_chunk_dir = bun_paths::resolve_path::dirname::< + bun_paths::resolve_path::platform::Posix, + >(&chunk.final_rel_path); + if from_chunk_dir == b"." { + from_chunk_dir = b""; + } - let source_index = piece_index; - let parse_graph = self.parse_graph(); - let additional_files: &[AdditionalFile] = - parse_graph.input_files.items_additional_files()[source_index as usize] - .slice(); - debug_assert!(!additional_files.is_empty()); - match &additional_files[0] { - AdditionalFile::OutputFile(output_file_id) => { - let path = &parse_graph.additional_output_files - [*output_file_id as usize] - .dest_path; - hash.write(bun_paths::resolve_path::relative_platform::< - bun_paths::resolve_path::platform::Posix, - false, - >(from_chunk_dir, path)); + let source_index = p.query.index(); + let parse_graph = self.parse_graph(); + let additional_files: &[AdditionalFile] = + parse_graph.input_files.items_additional_files()[source_index as usize] + .slice(); + debug_assert!(!additional_files.is_empty()); + match &additional_files[0] { + AdditionalFile::OutputFile(output_file_id) => { + let path = &parse_graph.additional_output_files + [*output_file_id as usize] + .dest_path; + hash.write(bun_paths::resolve_path::relative_platform::< + bun_paths::resolve_path::platform::Posix, + false, + >(from_chunk_dir, path)); + } + AdditionalFile::SourceIndex(_) => {} } - AdditionalFile::SourceIndex(_) => {} } + crate::chunk::QueryKind::Chunk => { + self.append_isolated_hashes_for_imported_chunks( + hash, + chunks, + p.query.index(), + chunk_visit_map, + ); + } + crate::chunk::QueryKind::Scb => { + self.append_isolated_hashes_for_imported_chunks( + hash, + chunks, + self.graph.files.items_entry_point_chunk_index() + [p.query.index() as usize], + chunk_visit_map, + ); + } + crate::chunk::QueryKind::None | crate::chunk::QueryKind::HtmlImport => {} } - crate::chunk::QueryKind::Chunk => { - self.append_isolated_hashes_for_imported_chunks( - hash, - chunks, - piece_index, - chunk_visit_map, - ); - } - crate::chunk::QueryKind::Scb => { - self.append_isolated_hashes_for_imported_chunks( - hash, - chunks, - self.graph.files.items_entry_point_chunk_index()[piece_index as usize], - chunk_visit_map, - ); - } - crate::chunk::QueryKind::None | crate::chunk::QueryKind::HtmlImport => {} } } // Mix in the hash for this chunk - let chunk = &chunks[index as usize]; hash.write(&chunk.isolated_hash.to_ne_bytes()); } diff --git a/src/bundler/bundle_v2.rs b/src/bundler/bundle_v2.rs index 49d092acb299..41f9a77d1f13 100644 --- a/src/bundler/bundle_v2.rs +++ b/src/bundler/bundle_v2.rs @@ -2132,21 +2132,16 @@ pub mod bv2_impl { ) { let file_map_result = _file_map_result; let mut path_primary = file_map_result.path_pair.primary; - // reshaped for borrowck — `get_or_put` borrows `*self` mutably via - // `self.graph`; capture the slot as `*mut u32` so subsequent `self.*` calls - // type-check. SAFETY: `path_to_source_index_map(target)` is not mutated again - // until after the last `*value_ptr` access below. - let (found_existing, value_ptr): (bool, *mut u32) = { - let entry = self - .path_to_source_index_map(target) - .get_or_put(path_primary.text) - .expect("oom"); - ( - entry.found_existing, - std::ptr::from_mut::(entry.value_ptr), - ) - }; - if !found_existing { + if let Some(existing) = + self.path_to_source_index_map(target).get(path_primary.text) + { + let record: &mut ImportRecord = + &mut self.graph.ast.items_import_records_mut() + [import_record.importer_source_index as usize] + .as_mut_slice() + [import_record.import_record_index as usize]; + record.source_index = Index::init(existing); + } else { let loader: Loader = 'brk: { let record: &mut ImportRecord = &mut self.graph.ast.items_import_records_mut() @@ -2176,22 +2171,15 @@ pub mod bv2_impl { import_record.original_target, ) .expect("oom"); - // SAFETY: see `value_ptr` note above. - unsafe { *value_ptr = idx }; + self.path_to_source_index_map(target) + .put(path_primary.text, idx) + .expect("oom"); let record: &mut ImportRecord = &mut self.graph.ast.items_import_records_mut() [import_record.importer_source_index as usize] .as_mut_slice() [import_record.import_record_index as usize]; record.source_index = Index::init(idx); - } else { - let record: &mut ImportRecord = - &mut self.graph.ast.items_import_records_mut() - [import_record.importer_source_index as usize] - .as_mut_slice() - [import_record.import_record_index as usize]; - // SAFETY: see `value_ptr` note above. - record.source_index = Index::init(unsafe { *value_ptr }); } return; } @@ -2437,9 +2425,6 @@ pub mod bv2_impl { // For example, it is silly to bundle index.css depended on by client+server twice. // It makes sense to separate these for JS because the target affects DCE if self.transpiler.options.server_components && !loader.is_javascript_like() { - // reshaped for borrowck — cannot hold two `&mut` into - // `self.graph` simultaneously, so re-derive the map per insert. - let key_text: Box<[u8]> = path.text.to_vec().into_boxed_slice(); let main_target = self.transpiler.options.target; let separate_ssr = self .framework @@ -2455,11 +2440,11 @@ pub mod bv2_impl { _ => (Target::Browser, Target::ServerComponentsSsr), }; self.path_to_source_index_map(ta) - .put(&key_text, idx) + .put(path.text, idx) .expect("oom"); if separate_ssr { self.path_to_source_index_map(tb) - .put(&key_text, idx) + .put(path.text, idx) .expect("oom"); } } diff --git a/src/bundler/linker_context/computeChunks.rs b/src/bundler/linker_context/computeChunks.rs index be942b837711..749dd724ffad 100644 --- a/src/bundler/linker_context/computeChunks.rs +++ b/src/bundler/linker_context/computeChunks.rs @@ -85,14 +85,7 @@ pub fn compute_chunks(this: &mut LinkerContext, unique_key: u64) -> crate::Resul for (entry_id_, &source_index) in entry_source_indices.iter().enumerate() { let entry_bit = entry_id_ as chunk::EntryPointId; - // reshaped for borrowck — set the bit through a scoped &mut, then keep an - // owned clone so the `this.graph.files` borrow does not span the helper calls below - // that need `&LinkerContext` / `&mut LinkerContext`. - let entry_bits: AutoBitSet = { - let eb = &mut this.graph.files.items_entry_bits_mut()[source_index as usize]; - eb.set(entry_bit as usize); - eb.clone()? - }; + this.graph.files.items_entry_bits_mut()[source_index as usize].set(entry_bit as usize); let has_html_chunk = loaders[source_index as usize] == Loader::Html; @@ -114,7 +107,10 @@ pub fn compute_chunks(this: &mut LinkerContext, unique_key: u64) -> crate::Resul // entry_bits is arbitrary bytes (not UTF-8) and cannot go through fmt::Display. let mut v = bun_alloc::ArenaVec::new_in(temp); v.push((!has_html_chunk) as u8); - v.extend_from_slice(entry_bits.bytes(this.graph.entry_points.len())); + v.extend_from_slice( + this.graph.files.items_entry_bits()[source_index as usize] + .bytes(this.graph.entry_points.len()), + ); break 'brk v.into_bump_slice(); } }; @@ -150,7 +146,10 @@ pub fn compute_chunks(this: &mut LinkerContext, unique_key: u64) -> crate::Resul // always generated even if the resulting file is empty let hash_to_use = if !this.options.css_chunking { bun_wyhash::hash( - temp.alloc_slice_copy(entry_bits.bytes(this.graph.entry_points.len())), + temp.alloc_slice_copy( + this.graph.files.items_entry_bits()[source_index as usize] + .bytes(this.graph.entry_points.len()), + ), ) } else { let mut hasher = Wyhash::init(5); @@ -257,7 +256,8 @@ pub fn compute_chunks(this: &mut LinkerContext, unique_key: u64) -> crate::Resul } *css_chunk_entry.value_ptr = Chunk { entry_point: chunk::EntryPoint::new(source_index, entry_bit, true, false), - entry_bits: entry_bits.clone()?, + entry_bits: this.graph.files.items_entry_bits()[source_index as usize] + .clone()?, content: chunk::Content::Css(chunk::CssChunk { imports_in_chunk_in_order: order, asts: (0..order_len) diff --git a/src/bundler/linker_context/computeCrossChunkDependencies.rs b/src/bundler/linker_context/computeCrossChunkDependencies.rs index 95a9b639bfb0..fa17e3e24b11 100644 --- a/src/bundler/linker_context/computeCrossChunkDependencies.rs +++ b/src/bundler/linker_context/computeCrossChunkDependencies.rs @@ -335,10 +335,8 @@ fn compute_cross_chunk_dependencies_with_chunk_metas( } // Find all uses in this chunk of symbols from other chunks - // reshaped for borrowck — collect keys first to avoid holding a borrow on - // chunk_metas[chunk_index] while mutating chunk_metas[other_chunk_index]. - let import_refs: Vec = chunk_metas[chunk_index].imports.keys().to_vec(); - for import_ref in import_refs { + let imports = core::mem::take(&mut chunk_metas[chunk_index].imports); + for &import_ref in imports.keys() { let symbol = c.graph.symbols.get_const(import_ref).unwrap(); // Ignore uses that aren't top-level symbols @@ -385,6 +383,7 @@ fn compute_cross_chunk_dependencies_with_chunk_metas( } } } + chunk_metas[chunk_index].imports = imports; // If this is an entry point, make sure we import all chunks belonging to // this entry point, even if there are no imports. We need to make sure diff --git a/src/css/properties/flex.rs b/src/css/properties/flex.rs index fb635d65abe8..387c0ef323f9 100644 --- a/src/css/properties/flex.rs +++ b/src/css/properties/flex.rs @@ -876,11 +876,11 @@ impl FlexHandler { } if let (Some(g_val), Some(s_val), Some(b_val)) = (&mut grow, &mut shrink, &mut basis) { - // reshaped for borrowck let g = g_val.0; let g_prefix: &mut VendorPrefix = &mut g_val.1; let s = s_val.0; let s_prefix: &mut VendorPrefix = &mut s_val.1; + // basis is emitted again below as the FlexBasis longhand, so the shorthand needs its own copy. let b = b_val.0.clone(); let b_prefix: &mut VendorPrefix = &mut b_val.1; diff --git a/src/dotenv/env_loader.rs b/src/dotenv/env_loader.rs index b741cb9f2b47..ac392dd398e0 100644 --- a/src/dotenv/env_loader.rs +++ b/src/dotenv/env_loader.rs @@ -1218,8 +1218,6 @@ impl<'a> Parser<'a> { continue; }; let value = self.parse_value::()?; - // reshaped for borrowck — value borrows self.value_buffer; copy before map mut. - let value_owned: Box<[u8]> = Box::from(value); let entry = map.map.get_or_put(key)?; if entry.found_existing { if entry.index < count { @@ -1231,7 +1229,9 @@ impl<'a> Parser<'a> { } // else: previous value freed by Drop on assignment below } - *entry.value_ptr = HashTableValue { value: value_owned }; + *entry.value_ptr = HashTableValue { + value: Box::from(value), + }; } if !IS_PROCESS && EXPAND { // borrowck — index-based iteration: clone the value bytes, run diff --git a/src/install/PackageInstaller.rs b/src/install/PackageInstaller.rs index a2b8cbf6ea1f..582e31a8e4a1 100644 --- a/src/install/PackageInstaller.rs +++ b/src/install/PackageInstaller.rs @@ -297,48 +297,6 @@ pub(crate) type TreeContextId = lockfile::tree::Id; // TreeContext::deinit dropped — Vec and Bin::PriorityQueue impl Drop. -pub(crate) enum LazyPackageDestinationDir<'a> { - /// Non-owning view of a directory handle the caller owns. - #[allow(dead_code)] - Dir(Fd), - NodeModulesPath { - #[allow(dead_code)] - node_modules: &'a NodeModulesFolder, - /// Non-owning view; the owning `Dir` lives on `PackageInstaller`. - root_node_modules_dir: Fd, - }, - Owned(Dir), - Closed, -} - -impl<'a> LazyPackageDestinationDir<'a> { - #[allow(dead_code)] - pub(crate) fn get_dir(&mut self) -> crate::Result { - match self { - LazyPackageDestinationDir::Dir(fd) => Ok(*fd), - LazyPackageDestinationDir::Owned(dir) => Ok(dir.fd()), - LazyPackageDestinationDir::NodeModulesPath { - node_modules, - root_node_modules_dir, - } => { - let dir = node_modules.open_dir(Dir::borrow(root_node_modules_dir))?; - let fd = dir.fd(); - *self = LazyPackageDestinationDir::Owned(dir); - Ok(fd) - } - LazyPackageDestinationDir::Closed => { - panic!( - "LazyPackageDestinationDir is closed! This should never happen. Why did this happen?! It's not your fault. Its our fault. We're sorry." - ) - } - } - } - - pub(crate) fn close(&mut self) { - *self = LazyPackageDestinationDir::Closed; - } -} - /// A dependency alias becomes the install destination inside `node_modules` /// (the existing entry is renamed aside, deleted, and re-created). Reject /// anything that could escape `node_modules`: empty names, `.`/`..` @@ -1711,9 +1669,6 @@ impl<'a> PackageInstaller<'a> { } }; - #[cfg(not(windows))] - let mut lazy_package_dir = LazyPackageDestinationDir::Dir(destination_dir.fd()); - let install_result: package_install::InstallResult = match resolution.tag { resolution::Tag::Symlink | resolution::Tag::Workspace => { installer.install_from_link(self.skip_delete, &destination_dir) @@ -2016,25 +1971,7 @@ impl<'a> PackageInstaller<'a> { if !NODE_MODULES_IS_OK.load(Ordering::Relaxed) { #[cfg(not(windows))] { - let dir = match lazy_package_dir.get_dir() { - Ok(d) => d, - Err(err) => { - Output::err( - "EACCES", - "Permission denied while installing {}", - (bstr::BStr::new( - self.names[package_id as usize].slice( - self.lockfile().buffers.string_bytes.as_slice(), - ), - ),), - ); - if cfg!(debug_assertions) { - Output::err(err, "Failed to stat node_modules", ()); - } - Global::exit(1); - } - }; - let stat = match bun_sys::fstat(dir) { + let stat = match bun_sys::fstat(destination_dir.fd()) { Ok(s) => s, Err(err) => { Output::err( @@ -2116,19 +2053,6 @@ impl<'a> PackageInstaller<'a> { .unwrap_or_oom(); } - // reshaped for borrowck — `LazyPackageDestinationDir` borrows - // `&self.node_modules`, but this else-branch never reads `destination_dir` - // (it only `close()`s it at the end, which is a no-op for `NodeModulesPath`). - // Detach via raw ptr so subsequent `&mut self` calls type-check. - // BACKREF — `self.node_modules` is not moved/dropped in this branch. - let mut destination_dir = LazyPackageDestinationDir::NodeModulesPath { - node_modules: node_modules_ref.get(), - root_node_modules_dir: self.root_node_modules_folder.fd(), - }; - - // `defer { destination_dir.close(); }` + `defer increment_tree_install_count`. - // No early returns in this branch, so manual calls at end are equivalent. - let dep = &self.lockfile().buffers.dependencies.as_slice()[dependency_id as usize]; let dep_behavior = dep.behavior; let truncated_dep_name_hash: TruncatedPackageNameHash = @@ -2227,13 +2151,6 @@ impl<'a> PackageInstaller<'a> { } } - // `destination_dir` is `LazyPackageDestinationDir::NodeModulesPath` - // holding `&self.node_modules`. `increment_tree_install_count` takes - // `&mut self` and (via `link_tree_bins`) reads `self.node_modules.path`, - // which would alias the borrow held by `destination_dir`. Close it first - // — `destination_dir` is never read in this else-branch (`get_dir()` is - // only used in the `needs_install` branch's EACCES handler). - destination_dir.close(); self.increment_tree_install_count( !IS_PENDING_PACKAGE_INSTALL, self.current_tree_id, diff --git a/src/install/PackageManager/PackageManagerEnqueue.rs b/src/install/PackageManager/PackageManagerEnqueue.rs index b21bc8ff9788..cb9e6e967ff2 100644 --- a/src/install/PackageManager/PackageManagerEnqueue.rs +++ b/src/install/PackageManager/PackageManagerEnqueue.rs @@ -962,14 +962,6 @@ pub fn enqueue_dependency_with_main_and_success_fn( ); } } else if version.tag.is_npm() { - // reshaped for borrowck — `name_str` borrows - // `this.lockfile.buffers.string_bytes`. Route the whole - // branch through a raw root so the slice and the - // `&mut PackageManager` calls below can coexist. - // Snapshot the manifest disk-cache scalars while we - // still hold `&mut this` exclusively — taking it via - // `&mut *this_ptr` after `name_str`/`scope` exist - // would pop their borrow-stack tags under SB. let cache_ctx = this.manifest_disk_cache_ctx(); let this_ptr: *mut PackageManager = this; // Owned copy: `get_or_put_resolved_package_with_find_result` diff --git a/src/install/PackageManager/WorkspacePackageJSONCache.rs b/src/install/PackageManager/WorkspacePackageJSONCache.rs index dd207af3f853..91728bb3aebe 100644 --- a/src/install/PackageManager/WorkspacePackageJSONCache.rs +++ b/src/install/PackageManager/WorkspacePackageJSONCache.rs @@ -152,10 +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. + // Probe first: an up-front entry borrow cannot span the read/parse + // error returns below, so insert only after a successful parse. if self.map.contains_key(path) { return GetResult::Entry(self.map.get_mut(path).unwrap()); } diff --git a/src/install/PackageManager/install_with_manager.rs b/src/install/PackageManager/install_with_manager.rs index a06d651a152c..06387b7fc57c 100644 --- a/src/install/PackageManager/install_with_manager.rs +++ b/src/install/PackageManager/install_with_manager.rs @@ -1322,12 +1322,10 @@ pub(crate) fn get_workspace_filters( #[cold] #[inline(never)] fn add_dependency_error(manager: &mut PackageManager, dependency: &Dependency, err: crate::Error) { - // reshaped for borrowck — capture the realname slice before - // taking `&mut` on `manager.log`. let realname = dependency.realname(); - let path = manager.lockfile.str(&realname).to_vec(); + let path = manager.lockfile.str(&realname); let path_fmt = bun_core::fmt::fmt_path( - &path, + path, bun_core::fmt::PathFormatOptions { path_sep: match dependency.version.tag { DependencyVersionTag::Folder => bun_core::fmt::PathSep::Auto, diff --git a/src/install/PackageManager/patchPackage.rs b/src/install/PackageManager/patchPackage.rs index 04dfd2bc0100..cc33f2010948 100644 --- a/src/install/PackageManager/patchPackage.rs +++ b/src/install/PackageManager/patchPackage.rs @@ -140,10 +140,6 @@ pub fn do_patch_commit( let mut iterator = tree::Iterator::<{ tree::IteratorPathStyle::NodeModules }>::init(&lockfile); let mut resolution_buf = [0u8; 1024]; - // reshaped for borrowck — `compute_cache_dir_and_subpath` borrows - // `manager` mutably while the package name/resolution borrow `lockfile` - // (which itself sometimes aliases `manager.lockfile`). Clone the slice/ - // resolution out first, then compute, then assemble the result tuple. let (cache_dir, cache_dir_subpath, changes_dir, pkg): (Fd, &ZStr, Vec, Package) = match arg_kind { PatchArgKind::Path => 'result: { @@ -241,11 +237,11 @@ pub fn do_patch_commit( } }; - let name = lockfile.str(&package.name).to_vec(); + let name = lockfile.str(&package.name); let resolution_clone = actual_package.resolution; let cache_result = compute_cache_dir_and_subpath( manager, - &name, + name, &resolution_clone, &mut folder_path_buf, None, @@ -275,14 +271,11 @@ pub fn do_patch_commit( .to_vec(); let pkg = *lockfile.packages.get(pkg_id as usize); - let pkg_name_slice = pkg - .name - .slice(lockfile.buffers.string_bytes.as_slice()) - .to_vec(); + let pkg_name_slice = pkg.name.slice(lockfile.buffers.string_bytes.as_slice()); let resolution_clone = pkg.resolution; let cache_result = compute_cache_dir_and_subpath( manager, - &pkg_name_slice, + pkg_name_slice, &resolution_clone, &mut folder_path_buf, None, diff --git a/src/install/PackageManager/runTasks.rs b/src/install/PackageManager/runTasks.rs index d7cd2a600042..e4372f420462 100644 --- a/src/install/PackageManager/runTasks.rs +++ b/src/install/PackageManager/runTasks.rs @@ -344,14 +344,10 @@ pub fn run_tasks( name, is_extended_manifest, } => { - // reshaped for borrowck — capture the name's slice - // pointer (`StringOrTinyString` is self-referential and not - // `Clone`) so the loop body can read `name` after the - // `&mut task.callback` borrow ends. - // SAFETY: `name` lives in `task.callback` which outlives this - // match arm (the task is only `put` back to the pool by a later - // resolve-task pass, never inside this loop iteration). - let name = unsafe { bun_ptr::detach_lifetime(name.slice()) }; + // Package names are short; copy once so later `&mut task` uses + // in this arm do not conflict with the `&task.callback` borrow. + let name_buf: bun_collections::smallvec::SmallVec<[u8; 64]> = name.slice().into(); + let name: &[u8] = &name_buf; let is_extended_manifest = *is_extended_manifest; if log_level.show_progress() { if !has_updated_this_run.get() { @@ -541,10 +537,6 @@ pub fn run_tasks( manifest.pkg.public_max_age = timestamp_this_tick.unwrap(); - // reshaped for borrowck — - // `bun_collections::HashMap` lacks `get_or_put` for - // non-`Default` values, so insert by-value (overwriting - // any prior entry) and reborrow. let name_hash = manifest.pkg.name.hash; manager .manifests diff --git a/src/js_parser/fold.rs b/src/js_parser/fold.rs index 08e2f4378ae0..c50d638aca84 100644 --- a/src/js_parser/fold.rs +++ b/src/js_parser/fold.rs @@ -381,11 +381,13 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O return None; } - // Note: lookup is split from insertion for borrowck. - let ref_ = if let Some(existing) = - p.commonjs_named_exports.get(name) - { - existing.loc_ref.ref_ + let gop = p + .commonjs_named_exports + .get_or_put(name) + .expect("unreachable"); + let index = gop.index; + let ref_ = if gop.found_existing { + gop.value_ptr.loc_ref.ref_ } else { let sym_name: &'a [u8] = p.arena.alloc_slice_copy( format!("${}", bun_core::fmt::fmt_identifier(name)) @@ -395,21 +397,16 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O p.new_symbol(js_ast::symbol::Kind::Other, sym_name); // SAFETY: module_scope is arena-owned and valid for 'a. VecExt::append(&mut p.module_scope_mut().generated, new_ref); - p.commonjs_named_exports - .put( - name, - CommonJSNamedExport { - loc_ref: LocRef { - loc: name_loc, - ref_: new_ref, - }, - needs_decl: true, + p.commonjs_named_exports.values_mut()[index] = + CommonJSNamedExport { + loc_ref: LocRef { + loc: name_loc, + ref_: new_ref, }, - ) - .expect("unreachable"); + needs_decl: true, + }; if p.commonjs_named_exports_needs_conversion == u32::MAX { - p.commonjs_named_exports_needs_conversion = - (p.commonjs_named_exports.count() - 1) as u32; + p.commonjs_named_exports_needs_conversion = index as u32; } new_ref }; @@ -609,11 +606,13 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O return None; } - // Note: reshaped for borrowck — see exports_ref arm above. - let ref_ = if let Some(existing) = - p.commonjs_named_exports.get(name) - { - existing.loc_ref.ref_ + let gop = p + .commonjs_named_exports + .get_or_put(name) + .expect("unreachable"); + let index = gop.index; + let ref_ = if gop.found_existing { + gop.value_ptr.loc_ref.ref_ } else { let sym_name: &'a [u8] = p.arena.alloc_slice_copy( format!("${}", bun_core::fmt::fmt_identifier(name)) @@ -626,21 +625,17 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O &mut p.module_scope_mut().generated, new_ref, ); - p.commonjs_named_exports - .put( - name, - CommonJSNamedExport { - loc_ref: LocRef { - loc: name_loc, - ref_: new_ref, - }, - needs_decl: true, + p.commonjs_named_exports.values_mut()[index] = + CommonJSNamedExport { + loc_ref: LocRef { + loc: name_loc, + ref_: new_ref, }, - ) - .expect("unreachable"); + needs_decl: true, + }; if p.commonjs_named_exports_needs_conversion == u32::MAX { p.commonjs_named_exports_needs_conversion = - (p.commonjs_named_exports.count() - 1) as u32; + index as u32; } new_ref }; diff --git a/src/js_parser/lexer.rs b/src/js_parser/lexer.rs index 5516c5770930..7070c73193cc 100644 --- a/src/js_parser/lexer.rs +++ b/src/js_parser/lexer.rs @@ -1069,10 +1069,6 @@ lexer_impl_header! { Ok(()) } - fn remaining(&self) -> &[u8] { - &self.contents[self.current..] - } - /// Note: split into an `#[inline(always)]` ASCII/EOF fast path plus /// an outlined multibyte tail. `step()` is called from ~50 sites inside /// the giant `next()` switch and inlines into it; with the multibyte @@ -2400,13 +2396,10 @@ lexer_impl_header! { 0x23 | 0x40 => { if !IS_JSON { let pragma_trigger_pos = self.end; // Position OF #/@ - // Use remaining() which starts *after* the consumed #/@ - // Note: reshaped for borrowck — `remaining()` borrows - // `self.contents`; `scan_pragma` needs `&mut self`. - // Detach via `StoreStr` (arena-owned, lives for parse). - let chunk = js_ast::StoreStr::new(self.remaining()); + // `chunk` starts *after* the consumed #/@ + let chunk = &contents[self.current..]; let offset = - self.scan_pragma(pragma_trigger_pos, chunk.slice(), true); + self.scan_pragma(pragma_trigger_pos, chunk, true); if offset > 0 { // Pragma found (e.g., __PURE__). diff --git a/src/js_parser/visit/visit_stmt.rs b/src/js_parser/visit/visit_stmt.rs index 11aaff8887ef..b661112d024f 100644 --- a/src/js_parser/visit/visit_stmt.rs +++ b/src/js_parser/visit/visit_stmt.rs @@ -183,13 +183,19 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O let symbol = p.find_symbol(items[i].alias_loc, name)?; let ref_ = symbol.r#ref; - // reshaped for borrowck — get_ptr borrows options; clone the - // small enum payload so `inject_replacement_export(&mut self, ...)` can run. - if let Some(entry) = p.options.features.replace_exports.get_ptr(name).cloned() { + if let Some(entry) = p + .options + .features + .replace_exports + .get_ptr(name) + .map(bun_ptr::BackRef::new) + { + // `BackRef::get` — entry lives in `self.options.features.replace_exports`, + // which is not mutated during the visit pass. if !entry.is_replace() { p.ignore_usage(symbol.r#ref); } - let _ = p.inject_replacement_export(stmts, symbol.r#ref, stmt.loc, &entry); + let _ = p.inject_replacement_export(stmts, symbol.r#ref, stmt.loc, entry.get()); any_replaced = true; continue; } diff --git a/src/jsc/AsyncModule.rs b/src/jsc/AsyncModule.rs index 18d2be1e5dba..6ea68b3cbfb7 100644 --- a/src/jsc/AsyncModule.rs +++ b/src/jsc/AsyncModule.rs @@ -328,6 +328,12 @@ impl Queue { bstr::BStr::new(this.vm().package_manager().lockfile.str(&dependency.name)) ); + let name: Vec = this + .vm() + .package_manager() + .lockfile + .str(&dependency.name) + .to_vec(); // retain_mut lets Drop free removed modules. this.map.retain_mut(|module| { for pending in module.parse_result.pending_imports.iter() { @@ -339,19 +345,12 @@ impl Queue { // `container_of`-derived `*mut`; provenance is the original // allocation, disjoint from the `&mut module` borrow above. let vm = VirtualMachine::get().as_mut(); - // reshaped for borrowck — `lockfile.str()` ties the - // returned slice to `&vm`, which conflicts with passing - // `&mut vm` to `resolve_error`. The lockfile string buffer is - // stable across `resolve_error` (no realloc on the error - // path); detach the borrow via raw ptr. - let name = - bun_ptr::RawSlice::new(vm.package_manager().lockfile.str(&dependency.name)); module .resolve_error( vm, import_record_id, &PackageResolveError { - name: name.slice(), + name: &name[..], err, url: b"", version: dependency.version.clone(), diff --git a/src/jsc/ConsoleObject.rs b/src/jsc/ConsoleObject.rs index e9f3b7261673..b3bf6942db96 100644 --- a/src/jsc/ConsoleObject.rs +++ b/src/jsc/ConsoleObject.rs @@ -824,8 +824,6 @@ impl<'a> TablePrinter<'a> { // find or create the column for the property let col_idx: usize = 'brk: { let col_str = BunString::init(col_key); - - // reshaped for borrowck — split find/append. if let Some(idx) = columns[1..].iter().position(|col| col.name.eql(&col_str)) { diff --git a/src/jsc/SavedSourceMap.rs b/src/jsc/SavedSourceMap.rs index 7a045c1d4839..90ac3ec379c0 100644 --- a/src/jsc/SavedSourceMap.rs +++ b/src/jsc/SavedSourceMap.rs @@ -151,10 +151,6 @@ impl SavedSourceMap { /// as a `ParsedSourceMap` materialized from that provider. pub fn remove_source_provider(&mut self, opaque_source_provider: *mut c_void, path: &[u8]) { self.lock(); - // Note: reshaped for borrowck — explicit unlock paired manually. - // `get`+`remove(&key)`: the std - // backing has no key-slot pointer to hand out, and the key is a u64 hash - // we already have in hand. let key = hash(path); let Some(&ptr) = self.map_mut().get(&key) else { self.unlock(); diff --git a/src/jsc/VirtualMachine.rs b/src/jsc/VirtualMachine.rs index ed4cbfb877ad..76bfca2d3196 100644 --- a/src/jsc/VirtualMachine.rs +++ b/src/jsc/VirtualMachine.rs @@ -4310,10 +4310,6 @@ impl VirtualMachine { vm: bun_ptr::BackRef::from(NonNull::new(jsc_vm_ptr).expect("vm non-null")), old_log, }; - // Note: reshaped for borrowck — re-derive from raw so the unique - // borrow doesn't span the guard's drop. - // SAFETY: per-thread VM is live for this synchronous call. - let jsc_vm = unsafe { &mut *jsc_vm_ptr }; let resolve_result = jsc_vm._resolve( &mut result, @@ -5524,16 +5520,12 @@ impl VirtualMachine { // box it to keep the per-level recursion frame small enough for the // 16K-deep `bun-inspect.test.ts` Error chain on Windows debug. let mut exception_holder = Box::new(crate::zig_exception::Holder::init()); - // Note: reshaped for borrowck — `zig_exception()` returns a - // `&mut` into the holder; we need to also borrow - // `need_to_clear_parser_arena_on_deinit` disjointly. Route through a - // raw pointer (the holder is heap-pinned for the call). - let exception: *mut ZigException = exception_holder.zig_exception(); + exception_holder.zig_exception(); let mut source_code_slice: Option = None; self.remap_zig_exception( - // SAFETY: `exception` points into stack-local `exception_holder`. - unsafe { &mut *exception }, + // SAFETY: `zig_exception()` above initialized this slot. + unsafe { exception_holder.zig_exception.assume_init_mut() }, error_instance, exception_list, &mut exception_holder.need_to_clear_parser_arena_on_deinit, @@ -5544,7 +5536,7 @@ impl VirtualMachine { let result = self.print_error_instance_body( // SAFETY: see above. - unsafe { &mut *exception }, + unsafe { exception_holder.zig_exception.assume_init_mut() }, error_instance, None, // Note: `exception_list` was already // consumed by `remap_zig_exception` above (only writer). diff --git a/src/paths/resolve_path.rs b/src/paths/resolve_path.rs index 60abb6dc25eb..2e4d1162a8c9 100644 --- a/src/paths/resolve_path.rs +++ b/src/paths/resolve_path.rs @@ -1432,20 +1432,16 @@ pub fn join_z_spill<'a, P: PlatformT>(spill: &'a mut Vec, parts: &[&[u8]]) - } pub fn join_z_buf<'a, P: PlatformT>(buf: &'a mut [u8], parts: &[&[u8]]) -> &'a ZStr { - // reshaped for borrowck — capture buf base ptr before sub-borrow - let buf_base = buf.as_mut_ptr(); + let buf_base = buf.as_ptr() as usize; let buf_len = buf.len(); let (start_offset, len) = { let joined = join_string_buf::

(&mut buf[..buf_len - 1], parts); - ( - (joined.as_ptr() as usize) - (buf_base as usize), - joined.len(), - ) + ((joined.as_ptr() as usize) - buf_base, joined.len()) }; debug_assert!(start_offset + len < buf_len); buf[start_offset + len] = 0; // SAFETY: NUL written at buf[start_offset + len]; slice is within buf - unsafe { ZStr::from_raw(buf_base.add(start_offset), len) } + unsafe { ZStr::from_raw(buf[start_offset..].as_ptr(), len) } } pub fn join_string_buf<'a, P: PlatformT>(buf: &'a mut [u8], parts: &[&[u8]]) -> &'a [u8] { @@ -1508,20 +1504,16 @@ pub(crate) fn join_string_buf_t_same<'a, T: PathChar, P: PlatformT>( } pub fn join_string_buf_z<'a, P: PlatformT>(buf: &'a mut [u8], parts: &[&[u8]]) -> &'a ZStr { - // reshaped for borrowck — capture buf base ptr before sub-borrow - let buf_base = buf.as_mut_ptr(); + let buf_base = buf.as_ptr() as usize; let buf_len = buf.len(); let (start_offset, len) = { let joined = join_string_buf_t::(&mut buf[..buf_len - 1], parts); - ( - (joined.as_ptr() as usize) - (buf_base as usize), - joined.len(), - ) + ((joined.as_ptr() as usize) - buf_base, joined.len()) }; debug_assert!(start_offset + len < buf_len); buf[start_offset + len] = 0; // SAFETY: NUL written at buf[start_offset + len]; slice is within buf - unsafe { ZStr::from_raw(buf_base.add(start_offset), len) } + unsafe { ZStr::from_raw(buf[start_offset..].as_ptr(), len) } } pub(crate) fn join_string_buf_t<'a, T: PathChar, P: PlatformT>( diff --git a/src/runtime/api/BunObject.rs b/src/runtime/api/BunObject.rs index 6c3911db557a..b193e92109a8 100644 --- a/src/runtime/api/BunObject.rs +++ b/src/runtime/api/BunObject.rs @@ -1896,20 +1896,17 @@ pub(crate) fn get_s3_default_client(global_this: &JSGlobalObject, _: &JSObject) use bun_jsc::StrongOptional; // SAFETY: bun_vm() returns the live thread-local VM for a Bun-owned global. let vm = global_this.bun_vm().as_mut(); - // NOTE: reshaped for borrowck — capture the raw env loader pointer - // before `rare_data()` takes the long-lived `&mut` of `vm`. - let env_ptr = vm.transpiler.env; - let rare = vm.rare_data(); - if let Some(v) = rare.s3_default_client.get() { + if let Some(v) = vm.rare_data().s3_default_client.get() { return v; } // NOTE (layering): `bun_dotenv::Loader::get_s3_credentials` returns the // T2 POD mirror; lift it into the refcounted `bun_s3_signing::S3Credentials` // here at the high-tier call site (dotenv ≤T2 may not name s3_signing T5). - // SAFETY: `transpiler.env` is the process-lifetime dotenv loader; disjoint - // from `rare_data` storage. - let env_creds = - crate::webcore::fetch::s3_credentials_from_env(unsafe { (*env_ptr).get_s3_credentials() }); + // SAFETY: `transpiler.env` is the process-lifetime dotenv loader. + let env_creds = crate::webcore::fetch::s3_credentials_from_env(unsafe { + (*vm.transpiler.env).get_s3_credentials() + }); + let rare = vm.rare_data(); let aws_options = match crate::webcore::s3::credentials_jsc::get_credentials_with_options( &env_creds, Default::default(), diff --git a/src/runtime/api/bun/h2_frame_parser.rs b/src/runtime/api/bun/h2_frame_parser.rs index 7f77c86bebdd..d7cadb26f8c2 100644 --- a/src/runtime/api/bun/h2_frame_parser.rs +++ b/src/runtime/api/bun/h2_frame_parser.rs @@ -2285,8 +2285,9 @@ impl H2FrameParser { } fn increment_window_size_if_needed(&self) { - // Note: reshaped for borrowck — collect actions then apply - let mut updates: Vec<(u32, u64)> = Vec::new(); + use bun_collections::smallvec::SmallVec; + // send_window_update can re-enter JS which may mutate self.streams, so collect first. + let mut updates: SmallVec<[(u32, u64); 8]> = SmallVec::new(); for (_, item) in self.streams.get().iter() { // SAFETY: item is &*mut Stream from streams.iter(); the boxed Stream outlives the iteration let stream = unsafe { &mut **item }; diff --git a/src/runtime/api/bun/js_bun_spawn_bindings.rs b/src/runtime/api/bun/js_bun_spawn_bindings.rs index c255615739ad..59236b1678d8 100644 --- a/src/runtime/api/bun/js_bun_spawn_bindings.rs +++ b/src/runtime/api/bun/js_bun_spawn_bindings.rs @@ -1095,23 +1095,17 @@ pub(crate) fn spawn_maybe_sync( jsc_vm.event_loop() }; - // Note: reshaped for borrowck — `defer!` is non-`move`, so the closure - // would capture the *place* `*jsc_vm_ptr` and conflict with later - // `&mut *jsc_vm_ptr` re-borrows below. Copy the raw pointer into a sibling - // local so the closure's captured place is disjoint. - let jsc_vm_ptr_cleanup = jsc_vm_ptr; - scopeguard::defer! { + let _cleanup = scopeguard::guard(jsc_vm_ptr, |p| { if IS_SYNC { // SAFETY: defer runs while `jsc_vm` (the thread VM) is still live. unsafe { - let main_loop = (*jsc_vm_ptr_cleanup).event_loop(); - (*jsc_vm_ptr_cleanup) - .rare_data() - .spawn_sync_event_loop(&mut *jsc_vm_ptr_cleanup) - .cleanup(jsc_vm_ptr_cleanup.cast(), main_loop.cast()); + let main_loop = (*p).event_loop(); + (*p).rare_data() + .spawn_sync_event_loop(&mut *p) + .cleanup(p.cast(), main_loop.cast()); } } - } + }); let loop_handle = EventLoopHandle::init(event_loop.cast::<()>()); @@ -1708,14 +1702,10 @@ pub(crate) fn spawn_maybe_sync( } } - // Note: reshaped for borrowck — copy `subprocess_ptr` so the - // non-`move` `defer!` closure captures a disjoint place from the - // `(*subprocess_ptr).abort_signal = …` writes that follow. - let subprocess_ptr_exit = subprocess_ptr; - scopeguard::defer! { + let _exit_guard = scopeguard::guard(subprocess_ptr, |p| { if send_exit_notification { // SAFETY: subprocess_ptr is live for the lifetime of this defer. - let proc = unsafe { &*subprocess_ptr_exit }.process_mut(); + let proc = unsafe { &*p }.process_mut(); if proc.has_exited() { // process has already exited, we called wait4(), but we did not call onProcessExit() // SAFETY: all-zero is a valid Rusage (POD). @@ -1727,7 +1717,7 @@ pub(crate) fn spawn_maybe_sync( proc.wait(IS_SYNC); } } - } + }); // Start the readers before the Writable::Buffer stdin writer so that if // the writer's start() throws below, both PipeReaders have taken their diff --git a/src/runtime/api/filesystem_router.rs b/src/runtime/api/filesystem_router.rs index 3557270fa979..5b48a93bbade 100644 --- a/src/runtime/api/filesystem_router.rs +++ b/src/runtime/api/filesystem_router.rs @@ -440,12 +440,12 @@ impl FileSystemRouter { } pub fn bust_dir_cache(&self, global_this: &JSGlobalObject) { + // Copy out: `bust_dir_cache_recursive` races the bundler thread on the + // process-global entry cache (see the `reload() while Bun.build()` test), + // so keep this slice independent of any JsCell borrow across that call. let dir = - strings::paths::without_trailing_slash_windows_path(&self.router.get().config.dir); - // Note: reshaped for borrowck — `dir` borrows `self.router.config.dir`; the - // recursive walk re-derives the path from the resolver per-iteration so a one-time - // copy is sufficient. - let dir = dir.to_vec(); + strings::paths::without_trailing_slash_windows_path(&self.router.get().config.dir) + .to_vec(); self.bust_dir_cache_recursive(global_this, &dir); } diff --git a/src/runtime/bake/production.rs b/src/runtime/bake/production.rs index 4c888ecbce0e..194b9534a89e 100644 --- a/src/runtime/bake/production.rs +++ b/src/runtime/bake/production.rs @@ -91,9 +91,6 @@ pub fn build_command(ctx: Context) -> crate::Result<()> { Global::crash(); } }; - // Note: reshaped for borrowck — clone the cwd slice so the PathBuffer - // borrow doesn't span the rest of the function (the buffer is never reused). - let cwd: Box<[u8]> = Box::from(cwd); // Create a VM + global for loading the config file, plugins, and // performing build time prerendering. @@ -217,7 +214,7 @@ pub fn build_command(ctx: Context) -> crate::Result<()> { // LIFO order — under the API lock, before the VM is destroyed. let mut pt = PerThread::placeholder(vm_ptr); - match build_with_vm(ctx, &cwd, &mut pt) { + match build_with_vm(ctx, cwd, &mut pt) { Ok(()) => {} Err(crate::Error::JSError) => { // SAFETY: vm.global is live for VM lifetime. @@ -535,8 +532,6 @@ pub(super) fn build_with_vm(ctx: Context, cwd: &[u8], pt: &mut PerThread) -> cra let mut root_dir_buf = PathBuffer::uninit(); let root_dir_path = resolve_path::join_abs_string_buf::(cwd, &mut root_dir_buf.0, &[b"dist"]); - // Note: reshaped for borrowck — copy out so root_dir_buf can drop. - let root_dir_path: Box<[u8]> = Box::from(root_dir_path); // Note: borrowck — `framework` is `&mut options.framework`; reborrow // through it instead of `options.framework` to avoid stacking borrows. @@ -1188,7 +1183,7 @@ pub(super) fn build_with_vm(ctx: Context, cwd: &[u8], pt: &mut PerThread) -> cra let render_promise = unsafe { &mut *BakeRenderRoutesForProdStatic( global, - BunString::init(&*root_dir_path), + BunString::init(root_dir_path), pt.all_server_files.as_ref().unwrap().get(), server_render_funcs, server_param_funcs, diff --git a/src/runtime/cli/publish_command.rs b/src/runtime/cli/publish_command.rs index 5e374af7599b..c7e5c62983eb 100644 --- a/src/runtime/cli/publish_command.rs +++ b/src/runtime/cli/publish_command.rs @@ -715,23 +715,19 @@ impl PublishCommand { .put(b"npm_command", b"publish") .map_err(|_| crate::Error::Alloc(bun_alloc::AllocError))?; - // Note: reshaped for borrowck — `command_ctx: &mut ContextData` - // is held by `context`; `run_package_script_foreground` needs - // `&mut ContextData` too. Re-derive from the raw pointer. - let cmd_ctx_ptr: *mut crate::cli::command::ContextData = context.command_ctx; + let use_system_shell = context.command_ctx.debug.use_system_shell; + let silent = context.manager.options.log_level == LogLevel::Silent; if let Some(publish_script) = &context.publish_script { if let Err(e) = Run::run_package_script_foreground( - // SAFETY: see above. - unsafe { &mut *cmd_ctx_ptr }, + &mut *context.command_ctx, publish_script, b"publish", &abs_workspace_path, script_env, &[], - context.manager.options.log_level == LogLevel::Silent, - // SAFETY: see above. - unsafe { &*cmd_ctx_ptr }.debug.use_system_shell, + silent, + use_system_shell, ) { if matches!(e, crate::Error::MissingShell) { Output::err_generic( @@ -746,16 +742,14 @@ impl PublishCommand { if let Some(postpublish_script) = &context.postpublish_script { if let Err(e) = Run::run_package_script_foreground( - // SAFETY: see above. - unsafe { &mut *cmd_ctx_ptr }, + &mut *context.command_ctx, postpublish_script, b"postpublish", &abs_workspace_path, script_env, &[], - context.manager.options.log_level == LogLevel::Silent, - // SAFETY: see above. - unsafe { &*cmd_ctx_ptr }.debug.use_system_shell, + silent, + use_system_shell, ) { if matches!(e, crate::Error::MissingShell) { Output::err_generic( diff --git a/src/runtime/cli/repl.rs b/src/runtime/cli/repl.rs index f8f8191bfeac..38aabac509f7 100644 --- a/src/runtime/cli/repl.rs +++ b/src/runtime/cli/repl.rs @@ -2090,18 +2090,14 @@ impl<'a> Repl<'a> { self.refresh_line(); } Key::ArrowUp | Key::CtrlP => { - // Note: reshaped for borrowck — copy line before mutating history - let cur = self.line_editor.get_line().to_vec(); - if let Some(prev_line) = self.history.prev(&cur) { - let prev_line = prev_line.to_vec(); - let _ = self.line_editor.set(&prev_line); + if let Some(prev_line) = self.history.prev(self.line_editor.get_line()) { + let _ = self.line_editor.set(prev_line); self.refresh_line(); } } Key::ArrowDown | Key::CtrlN => { if let Some(next_line) = self.history.next() { - let next_line = next_line.to_vec(); - let _ = self.line_editor.set(&next_line); + let _ = self.line_editor.set(next_line); } else { self.line_editor.clear(); } @@ -2136,8 +2132,8 @@ impl<'a> Repl<'a> { fn handle_enter(&mut self) -> Result<(), crate::Error> { self.print(format_args!("\n")); - // Note: reshaped for borrowck — copy line out so we can call &mut self methods - let line: Vec = self.line_editor.get_line().to_vec(); + let line = std::mem::take(&mut self.line_editor.buffer); + self.line_editor.cursor = 0; if self.editor_mode { if strings::trim(&line, b" \t").is_empty() { @@ -2272,15 +2268,14 @@ impl<'a> Repl<'a> { } fn handle_tab(&mut self) { - // Note: reshaped for borrowck — copy line out - let line: Vec = self.line_editor.get_line().to_vec(); + let line = self.line_editor.get_line(); // Complete REPL commands if !line.is_empty() && line[0] == b'.' { let mut matches: Vec<&'static [u8]> = Vec::new(); for cmd in &ReplCommand::ALL { - if cmd.name.starts_with(&line[..]) { + if cmd.name.starts_with(line) { matches.push(cmd.name); } } diff --git a/src/runtime/jsc_hooks.rs b/src/runtime/jsc_hooks.rs index 3c20e0cea402..a4e515a1b915 100644 --- a/src/runtime/jsc_hooks.rs +++ b/src/runtime/jsc_hooks.rs @@ -2356,47 +2356,24 @@ fn transpile_source_code_inner( } }; - let mut should_close_input_file_fd = fd.is_none(); - // Only JS-like loaders get the cjs/esm wrapper hint. let module_type_only_for_wrappables = match loader { L::Js | L::Jsx | L::Ts | L::Tsx => module_type, _ => ModuleType::Unknown, }; - let mut input_file_fd = bun_sys::Fd::INVALID; // The deferred fd close is independent of `give_back_arena` // and must fire on every exit path: parse failure, JSON early // return, `disable_transpilying`, already_bundled, empty `.cjs`, // cache-hit, AsyncModule, the wasm recurse, and the print error. - // Note: reshaped for borrowck — capture raw pointers so the - // guard does not alias the parser's `file_fd_ptr` / - // `maybe_watch_file` borrows. **All** later access to - // `should_close_input_file_fd` / `input_file_fd` MUST go through - // these raw pointers — taking a fresh `&mut` to either local would - // invalidate the guard's tag under Stacked Borrows, making the - // deferred `.close()` (which the parse path always reaches) UB. - let should_close_ptr: *mut bool = &raw mut should_close_input_file_fd; - let input_file_fd_ptr: *mut bun_sys::Fd = &raw mut input_file_fd; - // Note: `scopeguard::defer!` would capture the two `*mut` - // locals by-ref in its non-`move` closure, which borrowck then - // treats as conflicting with the later `&mut *ptr` reborrows below - // (edition-2021 capture analysis). Thread the raw pointers through - // the guard *payload* instead so nothing is captured. - let _fd_guard = scopeguard::guard( - (should_close_ptr, input_file_fd_ptr), - |(should_close_ptr, input_file_fd_ptr)| { - // SAFETY: `should_close_input_file_fd` / `input_file_fd` - // are declared earlier in this stack frame and outlive - // this guard (locals drop in reverse declaration order); - // the guard runs on the same thread before either is - // destroyed. - unsafe { - if *should_close_ptr && (*input_file_fd_ptr).is_valid() { - use bun_sys::FdExt as _; - (*input_file_fd_ptr).close(); - *input_file_fd_ptr = bun_sys::Fd::INVALID; - } + // Payload is `(should_close_input_file_fd, input_file_fd)`; access + // via `fd_guard.0` / `fd_guard.1`. + let mut fd_guard = scopeguard::guard( + (fd.is_none(), bun_sys::Fd::INVALID), + |(should_close, input_file_fd): (bool, bun_sys::Fd)| { + if should_close && input_file_fd.is_valid() { + use bun_sys::FdExt as _; + input_file_fd.close(); } }, ); @@ -2516,11 +2493,7 @@ fn transpile_source_code_inner( loader, dirname_fd: bun_sys::Fd::INVALID, file_descriptor: fd, - // SAFETY: `input_file_fd_ptr` points at this frame's - // `input_file_fd`; reborrow through the raw pointer so the - // `_fd_guard` scopeguard's tag is not invalidated by a - // fresh `&mut` (see Note on `_fd_guard`). - file_fd_ptr: Some(unsafe { &mut *input_file_fd_ptr }), + file_fd_ptr: Some(&mut fd_guard.1), macro_remappings, // SAFETY: per fn contract — `jsc_vm` is the live per-thread VM. jsx: unsafe { &*jsc_vm }.transpiler.options.jsx.clone(), @@ -2582,12 +2555,11 @@ fn transpile_source_code_inner( let Some(mut parse_result) = parse_result else { // Register with watcher even on parse failure. if !disable_transpilying { - // SAFETY: see Note on `_fd_guard` — reborrow via - // the raw pointers so the guard stays valid. + let (should_close, input_file_fd) = &mut *fd_guard; maybe_watch_file( jsc_vm, - unsafe { &mut *should_close_ptr }, - unsafe { *input_file_fd_ptr }, + should_close, + *input_file_fd, is_node_override, path, hash, @@ -2631,12 +2603,11 @@ fn transpile_source_code_inner( // Register with watcher on success too. if !disable_transpilying { - // SAFETY: see Note on `_fd_guard` — reborrow via the - // raw pointers so the guard stays valid. + let (should_close, input_file_fd) = &mut *fd_guard; maybe_watch_file( jsc_vm, - unsafe { &mut *should_close_ptr }, - unsafe { *input_file_fd_ptr }, + should_close, + *input_file_fd, is_node_override, path, hash, @@ -3166,7 +3137,7 @@ fn transpile_source_code_inner( printer.ctx.append_null_byte = false; } - // (fd close handled by `_fd_guard` registered above; spec + // (fd close handled by `fd_guard` registered above; spec // :251-256 `defer` fires on every exit path.) return Ok(OwnedResolvedSource::from(ResolvedSource { diff --git a/src/runtime/server/mod.rs b/src/runtime/server/mod.rs index f50f6ce8b23d..306048adc15e 100644 --- a/src/runtime/server/mod.rs +++ b/src/runtime/server/mod.rs @@ -864,7 +864,7 @@ impl NewServer { /// the live response handle for the request being resumed. pub(crate) fn on_saved_request( this: *mut Self, - req: SavedRequestUnion<'_>, + mut req: SavedRequestUnion<'_>, resp: *mut uws_sys::NewAppResponse, callback: JSValue, extra_args: [JSValue; ARG_COUNT], @@ -879,15 +879,11 @@ impl NewServer { server_body::respond_stopped_503(bun_opaque::opaque_deref_mut(resp)); return; }; - let prepared: PreparedRequest = match &req { + let prepared: PreparedRequest = match &mut req { SavedRequestUnion::Stack(r) => { - // reshaped for borrowck — decouple the inner - // `&mut uws::Request` lifetime from the `req` match guard. - let r = std::ptr::from_ref::(*r).cast_mut(); match Self::prepare_js_request_context( this, - // S008: `uws::Request` is an `opaque_ffi!` ZST — safe deref. - bun_opaque::opaque_deref_mut(r), + &mut **r, resp, None, CreateJsRequest::Bake, diff --git a/src/runtime/server/server_body.rs b/src/runtime/server/server_body.rs index 560b52bc0269..3e9303a63edc 100644 --- a/src/runtime/server/server_body.rs +++ b/src/runtime/server/server_body.rs @@ -999,11 +999,11 @@ impl ServePlugins { fn load_and_resolve_plugins(&mut self, global: &JSGlobalObject) -> JsResult<()> { debug_assert!(matches!(self.state, ServePluginsState::Unqueued(_))); - let ServePluginsState::Unqueued(plugin_list) = &self.state else { + let ServePluginsState::Unqueued(plugin_list) = + mem::replace(&mut self.state, ServePluginsState::Err) + else { unreachable!() }; - // NOTE: reshaped for borrowck — clone the slice refs so we can mutate self.state below - let plugin_list: Vec<_> = plugin_list.iter().collect(); let bunfig_path: &[u8] = &global.bun_vm().transpiler.options.bunfig_path; let bunfig_folder: &[u8] = bun_paths::resolve_path::dirname::< bun_paths::resolve_path::platform::Auto, @@ -1019,8 +1019,8 @@ impl ServePlugins { // SAFETY: `Plugin::create` returns a freshly-boxed `*mut Plugin` (single owner). let plugin: Box = unsafe { bun_core::heap::take(plugin) }; let mut bunstring_array: Vec = Vec::with_capacity(plugin_list.len()); - for raw_plugin in &plugin_list { - bunstring_array.push(BunString::init(&***raw_plugin)); + for raw_plugin in plugin_list.iter() { + bunstring_array.push(BunString::init(&**raw_plugin)); } let plugin_js_array = bun_string_jsc::to_js_array(global, &bunstring_array)?; let bunfig_folder_bunstr = jsc::bun_string_jsc::create_utf8_for_js(global, bunfig_folder)?; diff --git a/src/runtime/shell/IOReader.rs b/src/runtime/shell/IOReader.rs index d6c807eeb79f..3d634cc925ef 100644 --- a/src/runtime/shell/IOReader.rs +++ b/src/runtime/shell/IOReader.rs @@ -315,8 +315,7 @@ impl IOReader { self.set_reading(false); let s = self.state(); s.raw_err = Some(err.clone()); - // NOTE: reshaped for borrowck — copy out before dispatching. - let readers: Vec = s.readers.clone(); + let readers = std::mem::take(&mut s.readers); let interp = s.interp; for r in readers { // Re-derive a fresh SystemError per callee (see @@ -334,7 +333,7 @@ impl IOReader { let _keepalive = self.keepalive(); self.set_reading(false); let s = self.state(); - let readers: Vec = s.readers.clone(); + let readers = std::mem::take(&mut s.readers); let interp = s.interp; // `SystemError` isn't `Clone` yet, so we keep the source `sys::Error` // (which IS `Clone`) and re-derive a fresh `SystemError` per callee — diff --git a/src/runtime/shell/IOWriter.rs b/src/runtime/shell/IOWriter.rs index b50e3cbca8f7..a89bf434ed55 100644 --- a/src/runtime/shell/IOWriter.rs +++ b/src/runtime/shell/IOWriter.rs @@ -13,6 +13,7 @@ //! this simplifies management of the file descriptor. use bun_collections::VecExt; +use bun_collections::smallvec::SmallVec; use core::cell::UnsafeCell; #[cfg(not(windows))] use core::ffi::c_void; @@ -830,9 +831,9 @@ impl IOWriter { fn broken_pipe_for_writers(&self) { let s = self.state(); debug_assert!(s.flags.broken_pipe); - // NOTE: reshaped for borrowck — collect targets first so we don't - // hold `&mut s.writers` across `cancel_chunks`/`run_yield`. - let mut targets: Vec = Vec::new(); + // Snapshot targets first: run_yield/cancel_chunks re-enter state() and + // mutate s.writers, so we cannot iterate it across those calls. + let mut targets: SmallVec<[ChildPtr; 4]> = SmallVec::new(); for w in &s.writers[s.writer_idx..] { if w.is_dead() { continue; diff --git a/src/runtime/shell/interpreter.rs b/src/runtime/shell/interpreter.rs index 53b922deb56c..6105bdf1b9bf 100644 --- a/src/runtime/shell/interpreter.rs +++ b/src/runtime/shell/interpreter.rs @@ -1918,9 +1918,6 @@ impl ShellExecEnv { // Only `OLDPWD` is gated on `!in_init`; // `PWD` is written unconditionally so the very first env (built during // `init()` with `in_init = true`) still exports the resolved cwd. - // Note: reshaped for borrowck — materialize the EnvStr (which - // erases the slice lifetime into a packed ptr) before taking - // `&mut self.export_env`. use crate::shell::env_str::EnvStr; if !in_init { let oldpwd = EnvStr::init_slice(self.prev_cwd()); diff --git a/src/runtime/test_runner/diff/diff_match_patch.rs b/src/runtime/test_runner/diff/diff_match_patch.rs index 61e01bb4e804..3eef2bab5e9a 100644 --- a/src/runtime/test_runner/diff/diff_match_patch.rs +++ b/src/runtime/test_runner/diff/diff_match_patch.rs @@ -982,8 +982,7 @@ pub(crate) fn diff_cleanup_semantic( // Stack of indices where equalities are found. let mut equalities: Vec = Vec::new(); // Always equal to equalities[equalitiesLength-1][1] - // reshaped for borrowck — owned copy of last_equality - let mut last_equality: Option> = None; + let mut last_equality_len: Option = None; let mut pointer: isize = 0; // Index of current position. // Number of characters that changed prior to the equality. let mut length_insertions1: usize = 0; @@ -1000,7 +999,7 @@ pub(crate) fn diff_cleanup_semantic( length_deletions1 = length_deletions2; length_insertions2 = 0; length_deletions2 = 0; - last_equality = Some(dupe(&diffs[p].text)); + last_equality_len = Some(diffs[p].text.len()); } else { // an insertion or deletion if diffs[p].operation == Operation::Insert { @@ -1010,17 +1009,18 @@ pub(crate) fn diff_cleanup_semantic( } // Eliminate an equality that is smaller or equal to the edits on both // sides of it. - if let Some(le) = &last_equality { - if le.len() <= length_insertions1.max(length_deletions1) - && le.len() <= length_insertions2.max(length_deletions2) + if let Some(le_len) = last_equality_len { + if le_len <= length_insertions1.max(length_deletions1) + && le_len <= length_insertions2.max(length_deletions2) { let eq_idx = usize::try_from(equalities[equalities.len() - 1]).unwrap(); // Duplicate record. + let text = dupe(&diffs[eq_idx].text); diffs.insert( eq_idx, Diff { operation: Operation::Delete, - text: dupe(le), + text, }, ); // Change second copy to insert. @@ -1039,7 +1039,7 @@ pub(crate) fn diff_cleanup_semantic( length_deletions1 = 0; length_insertions2 = 0; length_deletions2 = 0; - last_equality = None; + last_equality_len = None; changes = true; } } diff --git a/src/runtime/test_runner/snapshot.rs b/src/runtime/test_runner/snapshot.rs index 80a54546f0b7..ee1936460c50 100644 --- a/src/runtime/test_runner/snapshot.rs +++ b/src/runtime/test_runner/snapshot.rs @@ -157,12 +157,12 @@ impl<'a> Snapshots<'a> { name_with_counter.extend_from_slice(counter_string); let name_hash: u64 = hash(&name_with_counter); - // reshaped for borrowck — `get` then early-return borrows `*self.values` - // immutably for the whole fn body (NLL limitation with returned borrows), preventing - // the later `insert`. Probe with `contains_key` first; re-lookup on hit. - if self.values.contains_key(&name_hash) { - return Ok(Some(&**self.values.get(&name_hash).unwrap())); - } + let slot = match self.values.entry(name_hash) { + bun_collections::zig_hash_map::MapEntry::Occupied(e) => { + return Ok(Some(&**e.into_mut())); + } + bun_collections::zig_hash_map::MapEntry::Vacant(v) => v, + }; // doesn't exist. append to file bytes and add to hashmap. // Prevent snapshot creation in CI environments unless --update-snapshots is used @@ -201,8 +201,7 @@ impl<'a> Snapshots<'a> { .map_err(|_| crate::Error::WriteError)?; self.added += 1; - self.values - .insert(name_hash, Box::<[u8]>::from(target_value)); + slot.insert(Box::<[u8]>::from(target_value)); Ok(None) } @@ -368,14 +367,10 @@ impl<'a> Snapshots<'a> { // The arena is reset() inside the loop, bulk-freeing per-iteration scratch. let mut arena = bun_alloc::Arena::new(); - // reshaped for borrowck — iterate by index to allow &mut access to values while reading keys. - let file_ids: Vec = self.inline_snapshots_to_write.keys().to_vec(); - for file_id in file_ids { + for entry in self.inline_snapshots_to_write.iterator() { arena.reset(); - let ils_info = self - .inline_snapshots_to_write - .get_mut(&file_id) - .expect("unreachable"); + let file_id = *entry.key_ptr; + let ils_info = entry.value_ptr; // The guard runs on every exit of the loop body (continue, // fall-through, AND `?` early-return). diff --git a/src/runtime/webcore/Blob.rs b/src/runtime/webcore/Blob.rs index 48af5644a8bf..f532a5a5546c 100644 --- a/src/runtime/webcore/Blob.rs +++ b/src/runtime/webcore/Blob.rs @@ -605,25 +605,14 @@ impl BlobExt for Blob { }); t.poll.ref_(bun_io::js_vm_ctx()); let proxy = http_proxy_href(global); - // reshaped for borrowck — `heap::alloc(t)` moves `t`, - // so clone the `Rc` out (cheap ref bump) - // and stash `path` as a raw `*const [u8]` whose backing store is - // kept alive by the same `t.blob` now owned by the heap task. - let (cred, path, payer); - { - let s3 = t - .blob - .store() - .expect("infallible: store present") - .data - .as_s3(); - cred = std::rc::Rc::clone(s3.get_credentials()); - path = std::ptr::from_ref::<[u8]>(s3.path()); - payer = s3.request_payer; - } - // SAFETY: `path` borrows the store held by `t.blob` (a fresh +1 ref); - // it stays valid until `Task::done` deinits the blob in the callback. - let path = unsafe { &*path }; + let s3 = self + .store() + .expect("infallible: store present") + .data + .as_s3(); + let cred = s3.get_credentials(); + let path = s3.path(); + let payer = s3.request_payer; let t_ptr = bun_core::heap::into_raw(t).cast::(); if self.offset.get() > 0 || self.size.get() != MAX_SIZE { let len: Option = if self.size.get() != MAX_SIZE { @@ -632,7 +621,7 @@ impl BlobExt for Blob { None }; crate::webcore::__s3_client::download_slice( - &cred, + cred, path, self.offset.get() as usize, len, @@ -643,7 +632,7 @@ impl BlobExt for Blob { )?; } else { crate::webcore::__s3_client::download( - &cred, + cred, path, Task::::cb, t_ptr, diff --git a/src/sql_jsc/mysql/MySQLConnection.rs b/src/sql_jsc/mysql/MySQLConnection.rs index db9bf2dbe1e1..55a8e50c8593 100644 --- a/src/sql_jsc/mysql/MySQLConnection.rs +++ b/src/sql_jsc/mysql/MySQLConnection.rs @@ -1697,15 +1697,16 @@ impl ReaderContext for Reader { } fn read(self, count: usize) -> Result { - let remaining = self.read_buffer().remaining(); - if remaining.len() < count { + let rb = self.read_buffer(); + if rb.remaining().len() < count { return Err(AnyMySQLError::ShortRead); } - // reshaped for borrowck — capture detached slice before skip(). - let slice = bun_ptr::RawSlice::new(&remaining[0..count]); - self.skip(isize::try_from(count).expect("int cast")); - Ok(Data::Temporary(slice)) + let head = rb.head as usize; + rb.head += u32::try_from(count).expect("int cast"); + Ok(Data::Temporary(bun_ptr::RawSlice::new( + &rb.byte_list[head..head + count], + ))) } fn read_z(self) -> Result { diff --git a/src/sql_jsc/postgres/PostgresSQLConnection.rs b/src/sql_jsc/postgres/PostgresSQLConnection.rs index a996a5047bb0..f02e33d15e91 100644 --- a/src/sql_jsc/postgres/PostgresSQLConnection.rs +++ b/src/sql_jsc/postgres/PostgresSQLConnection.rs @@ -1740,11 +1740,10 @@ impl Reader { return Err(AnyPostgresError::ShortRead); } - // reshaped for borrowck — capture as `RawSlice` before calling - // skip(); the read_buffer backing storage is not reallocated by skip(). - let slice = bun_ptr::RawSlice::new(&remaining[..count]); + let head = self.read_buffer().head as usize; self.skip(count); - Ok(Data::Temporary(slice)) + let slice = &self.read_buffer().byte_list[head..head + count]; + Ok(Data::Temporary(bun_ptr::RawSlice::new(slice))) } pub(crate) fn read_z(&mut self) -> Result { @@ -2404,21 +2403,18 @@ impl PostgresSQLConnection { .statement_mut() .ok_or(AnyPostgresError::ExpectedStatement)?; let mut structure: JSValue = JSValue::UNDEFINED; - // reshaped for borrowck — `statement.structure()` borrows - // `&mut *statement` and returns `&CachedStructure`; capture it as a - // `ParentRef` (lifetime-erased `&T`) so `&statement.fields` below - // does not conflict, and `as_deref` for `to_js` at the call site. - // `*statement` outlives this arm (held via `request.statement`'s - // intrusive ref), satisfying the `ParentRef` liveness invariant. - let mut cached_structure: Option> = None; + let mut cached_structure: Option<&PostgresCachedStructure> = None; let request_flags = request.flags.get(); // explicit use switch without else so if new modes are added, we don't forget to check for duplicate fields match request_flags.result_mode { SQLQueryResultMode::Objects => { let owner = self.js_value.get().try_get().unwrap_or(JSValue::ZERO); - let cs = statement.structure(owner, self.global()); - structure = cs.js_value().unwrap_or(JSValue::UNDEFINED); - cached_structure = Some(ParentRef::new(cs)); + statement.structure(owner, self.global()); + structure = statement + .cached_structure + .js_value() + .unwrap_or(JSValue::UNDEFINED); + cached_structure = Some(&statement.cached_structure); } SQLQueryResultMode::Raw | SQLQueryResultMode::Values => { // no need to check for duplicate fields or structure @@ -2495,9 +2491,7 @@ impl PostgresSQLConnection { structure, statement.fields_flags, request_flags.result_mode, - // `ParentRef::Deref` recovers `&CachedStructure`; statement - // outlives this call (held via `request.statement` ref). - cached_structure.as_deref(), + cached_structure, )?; if pending_value.is_empty() { diff --git a/test/internal/source-lints/borrowck-reshape-markers.test.ts b/test/internal/source-lints/borrowck-reshape-markers.test.ts new file mode 100644 index 000000000000..4bd0665f43b9 --- /dev/null +++ b/test/internal/source-lints/borrowck-reshape-markers.test.ts @@ -0,0 +1,61 @@ +// Ratchet for `reshaped for borrowck` comment markers in src/**/*.rs. +// +// Each marker flags code the Zig->Rust port restructured to satisfy the borrow +// checker: an extra allocation, a double hash lookup, a raw-pointer launder, or +// a split borrow. The cleanup effort removes them by putting each site into its +// idiomatic Rust form (see docs/dev/borrowck-audit on the audit branch). This +// test pins the current count so it only moves down. +// +// If this fails because the count went UP: you added a new workaround. Prefer +// writing the idiomatic form directly (disjoint borrows, mem::take, index + +// reborrow, entry API). If a reshape is genuinely unavoidable, bump the limit +// below and explain why in the comment at the site. +// +// If this fails because the count went DOWN: you removed workarounds. Lower the +// limit to the new count. + +import { file } from "bun"; +import { expect, test } from "bun:test"; +import { realpathSync } from "node:fs"; +import path from "node:path"; +import { globAllSources } from "../../../scripts/glob-sources.ts"; + +const LIMIT = 293; + +const root = path.resolve(import.meta.dir, "..", "..", ".."); +const rustSources = globAllSources().rust.filter(p => p.endsWith(".rs")); + +let count = 0; +const sample: string[] = []; +for (const abs of rustSources) { + const rel = path.relative(root, abs).replaceAll(path.sep, "/"); + // `src/cli` is a symlink into `src/runtime/cli`; count each file once + // under its canonical path. + if (path.relative(root, realpathSync(abs)).replaceAll(path.sep, "/") !== rel) continue; + const content = await file(abs).text(); + const lines = content.split("\n"); + for (let i = 0; i < lines.length; i++) { + if (lines[i].includes("reshaped for borrowck")) { + count++; + if (sample.length < 20) sample.push(`${rel}:${i + 1}`); + } + } +} + +test(`'reshaped for borrowck' markers are at or below the ratchet (${LIMIT})`, () => { + if (count > LIMIT) { + throw new Error( + `Found ${count} 'reshaped for borrowck' markers in src/**/*.rs, up from ${LIMIT}.\n` + + `Prefer the idiomatic Rust form over adding a new workaround; if unavoidable, bump LIMIT in this file.\n` + + `First ${sample.length}:\n` + + sample.map(l => ` ${l}`).join("\n"), + ); + } + if (count < LIMIT) { + throw new Error( + `Found ${count} 'reshaped for borrowck' markers in src/**/*.rs, down from ${LIMIT}.\n` + + `Lower LIMIT in test/internal/source-lints/borrowck-reshape-markers.test.ts to ${count}.`, + ); + } + expect(count).toBe(LIMIT); +}); diff --git a/test/internal/source-lints/dead-code-escape-limits.json b/test/internal/source-lints/dead-code-escape-limits.json index a14b0cb7065b..c9088a63f004 100644 --- a/test/internal/source-lints/dead-code-escape-limits.json +++ b/test/internal/source-lints/dead-code-escape-limits.json @@ -7,7 +7,6 @@ "src/collections/multi_array_list.rs": 8, "src/crash_handler/lib.rs": 1, "src/css_derive/lib.rs": 3, - "src/install/PackageInstaller.rs": 3, "src/install/isolated_install/FileCloner.rs": 3, "src/install/lockfile/Package.rs": 1, "src/io/lib.rs": 2,