Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
5 changes: 0 additions & 5 deletions src/ast/loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,11 +109,6 @@ impl Loader {
self == Loader::Css
}

#[inline]
pub fn is_js_like(self) -> bool {
matches!(self, Loader::Jsx | Loader::Js | Loader::Ts | Loader::Tsx)
}

pub fn should_copy_for_bundling(self) -> bool {
matches!(
self,
Expand Down
1 change: 1 addition & 0 deletions src/bundler/ParseTask.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1540,6 +1540,7 @@ pub mod parse_worker {
false,
contents_file.unwrap_valid(),
read_arena,
bun_resolver::cache::NonRegularFile::Read,
) {
Ok(e) => {
// `bun_resolver::cache::Entry` ↔ `crate::cache::Entry`
Expand Down
1 change: 0 additions & 1 deletion src/bundler/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,6 @@ bun_dispatch::link_interface! {
fn loaders() -> *const bun_collections::StringArrayHashMap<bun_ast::Loader>;
fn eval_source() -> Option<*const bun_ast::Source>;
fn main() -> &'static [u8];
fn read_dir_info_package_json(dir: &[u8]) -> Option<*const bun_resolver::PackageJSON>;
fn is_blob_url(specifier: &[u8]) -> bool;
fn resolve_blob(specifier: &[u8]) -> Option<options::OpaqueBlob>;
fn blob_loader(blob: options::OpaqueBlob) -> Option<bun_ast::Loader>;
Expand Down
20 changes: 1 addition & 19 deletions src/bundler/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use bun_js_parser::parser::Runtime;
use bun_options_types::schema::api;
use bun_resolver::fs as Fs;
use bun_resolver::fs::PathResolverExt as _;
use bun_resolver::package_json::{MacroMap as MacroRemap, PackageJSON};
use bun_resolver::package_json::MacroMap as MacroRemap;
use enum_map::EnumMap;
use std::borrow::Cow;

Expand Down Expand Up @@ -467,9 +467,6 @@ pub struct LoaderResult<'a> {
pub path: Fs::Path<'a>,
pub is_main: bool,
pub specifier: &'a [u8],
/// NOTE: This is always `null` for non-js-like loaders since it's not
/// needed for them.
pub package_json: Option<&'a PackageJSON>,
}

pub fn get_loader_and_virtual_source<'a>(
Expand Down Expand Up @@ -556,27 +553,12 @@ pub fn get_loader_and_virtual_source<'a>(

let is_main = strings::eql_long(specifier, jsc_vm.main(), true);

let dir = path.name().dir;
// NOTE: we cannot trust `path.isFile()` since it's not always correct
// NOTE: assume we may need a package.json when no loader is specified
let is_js_like = loader.map(|l| l.is_js_like()).unwrap_or(true);
let package_json: Option<&PackageJSON> = if is_js_like && bun_paths::is_absolute(dir) {
jsc_vm
.read_dir_info_package_json(dir)
// SAFETY: the vtable returns a pointer into the resolver's DirInfo
// cache owned by `jsc_vm.owner`, which outlives `'a`.
.map(|p| unsafe { &*p })
} else {
None
};

Ok(LoaderResult {
loader,
virtual_source,
path,
is_main,
specifier,
package_json,
})
}

Expand Down
4 changes: 4 additions & 0 deletions src/bundler/transpiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -980,6 +980,7 @@ pub struct ParseOptions<'a, 'b> {
pub arena: &'a Arena,
pub dirname_fd: FD,
pub file_descriptor: Option<FD>,
pub non_regular_file: resolver::cache::NonRegularFile,

/// On exception, we might still want to watch the file.
pub file_fd_ptr: Option<&'b mut FD>,
Expand Down Expand Up @@ -1478,6 +1479,7 @@ impl<'a> Transpiler<'a> {
USE_SHARED_BUFFER,
file_descriptor,
if USE_SHARED_BUFFER { None } else { Some(arena) },
this_parse.non_regular_file,
) {
Ok(e) => e,
Err(err) => {
Expand Down Expand Up @@ -2964,6 +2966,7 @@ impl<'a> Transpiler<'a> {
loader,
dirname_fd,
file_descriptor: None,
non_regular_file: resolver::cache::NonRegularFile::Read,
file_fd_ptr: None,
macro_remappings,
macro_js_ctx: default_macro_js_value(),
Expand Down Expand Up @@ -3137,6 +3140,7 @@ impl<'a> Transpiler<'a> {
false,
None,
None,
resolver::cache::NonRegularFile::Read,
) {
Ok(e) => e,
Err(err) => {
Expand Down
1 change: 1 addition & 0 deletions src/jsc/RuntimeTranspilerStore.rs
Original file line number Diff line number Diff line change
Expand Up @@ -808,6 +808,7 @@ impl TranspilerJob {
loader,
dirname_fd: Fd::INVALID,
file_descriptor: None,
non_regular_file: bun_resolver::cache::NonRegularFile::Read,
// SAFETY: `input_file_fd` is a stack local declared above and
// outlives `parse_options`; `addr_of_mut!` avoids forming an
// intermediate `&mut` so the close-guard's later borrow stays sound.
Expand Down
8 changes: 2 additions & 6 deletions src/jsc/VirtualMachine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4947,11 +4947,6 @@ impl VirtualMachine {
Ok(lr) => lr,
Err(_) => return Err(crate::CrateError::ModuleNotFound),
};
let module_type = lr
.package_json
.map(|pkg| pkg.module_type)
.unwrap_or(bun_bundler::options::ModuleType::Unknown);

// A drop-guard so both the normal and error paths reset the arena on
// the right edge.
struct ArenaReset<'a>(&'a mut VirtualMachine, bool);
Expand Down Expand Up @@ -4985,7 +4980,8 @@ impl VirtualMachine {
} else {
bun_ast::Loader::File
}),
module_type,
// Unused: the one caller fetches with `PrintSource`, which does not parse.
module_type: bun_bundler::options::ModuleType::Unknown,
source_code_printer: printer.as_ptr(),
// `fetchWithoutOnLoadPlugins` forbids the async path.
promise_ptr: core::ptr::null_mut(),
Expand Down
32 changes: 21 additions & 11 deletions src/resolver/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2094,6 +2094,15 @@ pub mod cache {
}
}

/// What [`Fs::read_file_with_allocator`] does with a path that is not a regular file.
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum NonRegularFile {
/// Open and read it like a file. A FIFO blocks until it has a writer.
Read,
/// Fail through [`bun_sys::File::open_regular_at`]: no blocking open, no byte read.
Reject,
}

/// File-read cache: shared read buffers plus flags controlling buffer
/// reuse and streaming reads.
pub struct Fs {
Expand Down Expand Up @@ -2335,27 +2344,29 @@ pub mod cache {
use_shared_buffer: bool,
_file_handle: Option<Fd>,
arena: Option<&bun_alloc::Arena>,
non_regular_file: NonRegularFile,
) -> crate::CrateResult<Entry> {
let rfs = &_fs.fs;

let will_close = rfs.need_to_close_files() && _file_handle.is_none();

let open_at = |dir: Fd, path: &[u8], flags: i32| match non_regular_file {
NonRegularFile::Read => bun_sys::File::openat(dir, path, flags, 0),
NonRegularFile::Reject => bun_sys::File::open_regular_at(dir, path),
};
let open_path =
|path: &[u8]| open_at(Fd::cwd(), path, bun_sys::O::RDONLY | bun_sys::O::CLOEXEC);

// A single let-expression avoids `mem::zeroed()` on a
// type that may have niche (NonZero) fields.
let file_handle: bun_sys::File = if let Some(f) = _file_handle {
bun_sys::lseek(f, 0, libc::SEEK_SET).map_err(crate::Error::from)?;
bun_sys::File::from_fd(f)
} else if feature_flags::STORE_FILE_DESCRIPTORS && dirname_fd.is_valid() {
match bun_sys::openat_a(
dirname_fd,
bun_paths::basename(path),
bun_sys::O::RDONLY,
0,
) {
Ok(fd) => bun_sys::File::from_fd(fd),
match open_at(dirname_fd, bun_paths::basename(path), bun_sys::O::RDONLY) {
Ok(file) => file,
Err(err) if err.get_errno() == bun_sys::E::ENOENT => {
let handle = bun_sys::open_file(path, bun_sys::OpenFlags::READ_ONLY)
.map_err(crate::Error::from)?;
let handle = open_path(path).map_err(crate::Error::from)?;
bun_core::pretty_errorln!(
"<r><d>Internal error: directory mismatch for directory \"{}\", fd {}<r>. You don't need to do anything, but this indicates a bug.",
bstr::BStr::new(path),
Expand All @@ -2366,8 +2377,7 @@ pub mod cache {
Err(err) => return Err(err.into()),
}
} else {
bun_sys::open_file(path, bun_sys::OpenFlags::READ_ONLY)
.map_err(crate::Error::from)?
open_path(path).map_err(crate::Error::from)?
};

let mut owned: Option<bun_sys::File> = None;
Expand Down
1 change: 1 addition & 0 deletions src/resolver/package_json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,7 @@ impl PackageJSON {
false,
None,
None,
crate::cache::NonRegularFile::Read,
) {
Ok(e) => e,
Err(err) => {
Expand Down
1 change: 1 addition & 0 deletions src/resolver/resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4009,6 +4009,7 @@ impl<'a> Resolver<'a> {
false,
None,
None,
crate::cache::NonRegularFile::Read,
)?;
// NOTE: reshaped for borrowck — `mem::take` the contents (leaving
// `Contents::Empty` behind) so `entry` stays whole for the close-guard.
Expand Down
2 changes: 2 additions & 0 deletions src/runtime/api/JSTranspiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -770,6 +770,7 @@ impl TransformTask {
macro_remappings: clone_macro_map(&self.macro_map),
dirname_fd: bun_sys::Fd::INVALID,
file_descriptor: None,
non_regular_file: bun_resolver::cache::NonRegularFile::Read,
loader: self.loader,
jsx,
path: source.path,
Expand Down Expand Up @@ -1225,6 +1226,7 @@ impl JSTranspiler {
macro_remappings: clone_macro_map(&config.macro_map),
dirname_fd: bun_sys::Fd::INVALID,
file_descriptor: None,
non_regular_file: bun_resolver::cache::NonRegularFile::Read,
loader: loader.unwrap_or(config.default_loader),
jsx,
path: source.path,
Expand Down
31 changes: 11 additions & 20 deletions src/runtime/jsc_hooks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1407,7 +1407,6 @@ mod vm_loader_ctx {
use super::*;
use crate::webcore::Blob;
use bun_bundler::options::OpaqueBlob;
use bun_resolver::package_json::PackageJSON;

/// Recover an [`OpaqueBlob`] as a shared `&Blob` (live until `blob_deinit`).
///
Expand All @@ -1421,12 +1420,9 @@ mod vm_loader_ctx {

// `this: *mut VirtualMachine`. Bodies use raw place projections —
// `(*this).field` — so no `&VirtualMachine` retag is materialized for the
// simple field reads. This matters because `read_dir_info_package_json`
// holds a live `&mut transpiler.resolver` across a re-entrant `read_dir_info`
// that can call back into these hooks; a `&VirtualMachine` formed here would
// alias that `&mut` (SB/TB UB). The two accessors that call `&self` methods
// simple field reads. The two accessors that call `&self` methods
// (`main`, `blob_loader`) form a transient `&VirtualMachine` scoped to the
// single call, which never spans the re-entrant path.
// single call.
bun_bundler::link_impl_VmLoaderCtx! {
Runtime for extern VirtualMachine => |this| {
origin_host() => (*this).origin.host,
Expand All @@ -1438,19 +1434,6 @@ mod vm_loader_ctx {
.as_deref()
.map(core::ptr::from_ref::<bun_ast::Source>),
main() => &*core::ptr::from_ref::<[u8]>((*this).main()),
read_dir_info_package_json(dir) => {
// Short-lived `&mut Resolver` (not `&mut VirtualMachine`) for
// the call — narrows the borrow re-entrant JS could alias.
match (*this).transpiler.resolver.read_dir_info(dir) {
Ok(Some(dir_info)) => {
dir_info
.package_json()
.or(dir_info.enclosing_package_json)
.map(core::ptr::from_ref::<PackageJSON>)
}
_ => None,
}
},
is_blob_url(spec) => crate::webcore::object_url_registry::is_blob_url(spec),
resolve_blob(spec) => {
crate::webcore::object_url_registry::ObjectURLRegistry::singleton()
Expand Down Expand Up @@ -2548,6 +2531,12 @@ fn transpile_source_code_inner(
loader,
dirname_fd: bun_sys::Fd::INVALID,
file_descriptor: None,
// The error printer reads after the fact: the path can name a FIFO by now.
non_regular_file: if disable_transpilying {
bun_resolver::cache::NonRegularFile::Reject
} else {
bun_resolver::cache::NonRegularFile::Read
},
// 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
Expand Down Expand Up @@ -2633,7 +2622,9 @@ fn transpile_source_code_inner(
arena_guard.2 = false; // give_back_arena = false
// Node compile cache: record the failed module so exit-time
// persist logs the "was not initialized" skip (Node parity).
note_compile_cache_parse_failure(path, loader, module_type);
if !disable_transpilying {
note_compile_cache_parse_failure(path, loader, module_type);
}
return Err(crate::Error::ParseError);
};

Expand Down
2 changes: 1 addition & 1 deletion src/sourcemap/Mapping.rs
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,7 @@ impl Lookup {
let normalized = bun_paths::resolve_path::join_abs_string_buf_z::<
bun_paths::platform::Loose,
>(dir, &mut buf, &[name]);
match bun_sys::File::read_from(bun_sys::Fd::cwd(), normalized) {
match bun_sys::File::read_regular_from(bun_sys::Fd::cwd(), normalized) {
Ok(r) => break 'bytes r,
Err(_) => return None,
}
Expand Down
5 changes: 2 additions & 3 deletions src/sourcemap/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -526,9 +526,8 @@ pub(crate) fn get_source_map_impl<P: SourceProvider + ?Sized>(
let load_path =
bun_core::ZStr::from_buf(&load_path_buf[..], source_filename.len() + 4);

// `bun_sys::File::read_from` returns an owned `Vec<u8>`,
// freed on scope exit by `Vec`'s Drop.
let data = match bun_sys::File::read_from(bun_core::Fd::cwd(), load_path) {
// `error.stack` and the error printer get here: a FIFO must not block them.
let data = match bun_sys::File::read_regular_from(bun_core::Fd::cwd(), load_path) {
Ok(data) => data,
Err(_) => break 'try_external,
};
Expand Down
20 changes: 20 additions & 0 deletions src/sys/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,26 @@ impl File {
}

// ── one-shot path helpers (open + io + close) ───────────────────────
/// Open `path` for reading; `EISDIR` for a directory, `ENODEV` for any other non-regular file.
pub fn open_regular_at(dir: impl AsFd, path: &[u8]) -> Maybe<Self> {
let dir = dir.as_fd();
// On Windows `O_NONBLOCK` would make the handle overlapped; the fstat still rejects there.
#[cfg(unix)]
let flags = O::RDONLY | O::CLOEXEC | O::NONBLOCK;
#[cfg(not(unix))]
let flags = O::RDONLY | O::CLOEXEC;
let file = Self::openat(dir, path, flags, 0)?;
let mode = file.stat().map_err(|e| e.with_path(path))?.st_mode as Mode;
if !S::ISREG(mode) {
let errno = if S::ISDIR(mode) { E::EISDIR } else { E::ENODEV };
return Err(Error::new(errno, Tag::open).with_path(path));
}
Ok(file)
}
/// [`File::read_from`] of a regular file only ([`File::open_regular_at`]).
pub fn read_regular_from(dir: impl AsFd, path: &[u8]) -> Maybe<Vec<u8>> {
Self::open_regular_at(dir, path)?.read_to_end()
}
/// Open + read + close. Accepts `&[u8]`; `&ZStr` callers deref-coerce.
pub fn read_from(dir: impl AsFd, path: &[u8]) -> Maybe<Vec<u8>> {
let dir = dir.as_fd();
Expand Down
Loading
Loading