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
1 change: 1 addition & 0 deletions src/bundler/transpiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ impl<'a> Transpiler<'a> {
if let Some(ctx) = self.macro_context.take() {
ctx.deinit();
}
self.resolver.release_custom_dir_scopes();
// SAFETY: `options`, `result`, and `resolver.opts` are init'd and never
// read past `destroy()` / the `--changed` scan teardown. Caller upholds
// the no-auto-drop contract above.
Expand Down
9 changes: 9 additions & 0 deletions src/jsc/VirtualMachine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4235,6 +4235,15 @@ fn specifier_cache_resolver_buf() -> *mut bun_paths::PathBuffer {
p
}

/// For a thread that ends before the process does: the slot has no destructor.
pub(crate) fn free_specifier_cache_resolver_buf() {
let p = SPECIFIER_CACHE_RESOLVER_BUF.replace(core::ptr::null_mut());
if !p.is_null() {
// SAFETY: `heap::into_raw`'d on this thread; `_resolve`, its only user, does not run now.
unsafe { bun_core::heap::destroy(p) };
}
}

fn ensure_source_code_printer() {
if SOURCE_CODE_PRINTER.get().is_none() {
let writer = bun_js_printer::BufferWriter::init();
Expand Down
34 changes: 33 additions & 1 deletion src/jsc/bindings/ImportMetaObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,35 @@ static void throwIfResolvedFileIsRefused(JSC::JSGlobalObject* globalObject, JSC:
JSC::throwException(globalObject, scope, error);
}

// Node's trySelfParentPath(): a truthy `parent.filename`, or the id `<repl>` or `internal/preload`.
BunResolvePathsParent pathsParentOf(JSC::JSGlobalObject* globalObject, JSC::JSValue parent, JSC::JSValue filename, JSC::JSValue id)
{
auto& vm = JSC::getVM(globalObject);
auto scope = DECLARE_THROW_SCOPE(vm);

if (filename && filename.isString() && asString(filename)->length() > 0)
return BunResolvePathsParent::Filename;

if (!id) {
if (auto* cjs = dynamicDowncast<Bun::JSCommonJSModule>(parent)) {
id = cjs->m_id.get();
} else if (parent && parent.isObject()) {
id = parent.getObject()->getIfPropertyExists(globalObject, vm.propertyNames->id);
RETURN_IF_EXCEPTION(scope, BunResolvePathsParent::None);
}
}
if (!id || !id.isString())
return BunResolvePathsParent::None;

auto view = asString(id)->view(globalObject);
RETURN_IF_EXCEPTION(scope, BunResolvePathsParent::None);
if (view == "<repl>"_s)
return BunResolvePathsParent::Repl;
if (view == "internal/preload"_s)
return BunResolvePathsParent::Preload;
return BunResolvePathsParent::None;
}

extern "C" JSC::EncodedJSValue functionImportMeta__resolveSyncPrivate(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame)
{
auto& vm = JSC::getVM(lexicalGlobalObject);
Expand Down Expand Up @@ -320,6 +349,9 @@ extern "C" JSC::EncodedJSValue functionImportMeta__resolveSyncPrivate(JSC::JSGlo

JSC::EncodedJSValue result = {};
WTF::Vector<BunString> paths;
auto parent = pathsParentOf(lexicalGlobalObject, parentModule, from, {});
if (scope.exception()) [[unlikely]]
goto cleanup;
for (size_t i = 0; i < userPathListArray->length(); ++i) {
JSValue path = userPathListArray->getIndex(globalObject, i);
if (scope.exception()) [[unlikely]]
Expand All @@ -339,7 +371,7 @@ extern "C" JSC::EncodedJSValue functionImportMeta__resolveSyncPrivate(JSC::JSGlo
}
}

result = Bun__resolveSyncWithPaths(lexicalGlobalObject, JSC::JSValue::encode(moduleName), JSValue::encode(from), isESM, isRequireDotResolve, paths.begin(), paths.size());
result = Bun__resolveSyncWithPaths(lexicalGlobalObject, JSC::JSValue::encode(moduleName), JSValue::encode(from), isESM, isRequireDotResolve, paths.begin(), paths.size(), parent);
if (scope.exception()) [[unlikely]]
goto cleanup;

Expand Down
16 changes: 15 additions & 1 deletion src/jsc/bindings/ImportMetaObject.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,18 @@
extern "C" JSC_DECLARE_HOST_FUNCTION(functionImportMeta__resolveSync);
extern "C" JSC_DECLARE_HOST_FUNCTION(functionImportMeta__resolveSyncPrivate);
extern "C" JSC::EncodedJSValue Bun__resolveSync(JSC::JSGlobalObject* global, JSC::EncodedJSValue specifier, JSC::EncodedJSValue from, bool is_esm, bool isUserRequireResolve);
extern "C" JSC::EncodedJSValue Bun__resolveSyncWithPaths(JSC::JSGlobalObject* global, JSC::EncodedJSValue specifier, JSC::EncodedJSValue from, bool is_esm, bool isUserRequireResolve, const BunString* paths, size_t paths_len);
// Kept in sync with `PathsParent` in src/resolver/resolver.rs.
enum class BunResolvePathsParent : uint8_t {
// A string, an `{ id }`, or nothing.
None = 0,
// `from` is `parent.filename`.
Filename = 1,
// The parent has no filename and the id `<repl>`.
Repl = 2,
// The parent has no filename and the id `internal/preload`.
Preload = 3,
};
extern "C" JSC::EncodedJSValue Bun__resolveSyncWithPaths(JSC::JSGlobalObject* global, JSC::EncodedJSValue specifier, JSC::EncodedJSValue from, bool is_esm, bool isUserRequireResolve, const BunString* paths, size_t paths_len, BunResolvePathsParent parent);
extern "C" JSC::EncodedJSValue Bun__resolveSyncWithSourceIfExists(JSC::JSGlobalObject* global, JSC::EncodedJSValue specifier, BunString* from, bool is_esm);
extern "C" JSC::EncodedJSValue Bun__resolveSyncWithStrings(JSC::JSGlobalObject* global, BunString* specifier, BunString* from, bool is_esm);

Expand All @@ -26,6 +37,9 @@ using namespace WebCore;

JSC_DECLARE_HOST_FUNCTION(functionImportMeta__resolve);

// `filename` is what `parent.filename` gave. `id` is what `parent.id` gave, when it was read.
BunResolvePathsParent pathsParentOf(JSC::JSGlobalObject*, JSC::JSValue parent, JSC::JSValue filename, JSC::JSValue id);

class ImportMetaObject final : public JSC::JSDestructibleObject {
public:
using Base = JSC::JSDestructibleObject;
Expand Down
12 changes: 11 additions & 1 deletion src/jsc/modules/NodeModuleModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -306,22 +306,29 @@ JSC_DEFINE_HOST_FUNCTION(jsFunctionResolveFileName,

// Extract filename string from fromValue
// Follows pattern: typeof this === "string" ? this : (this?.filename ?? this?.id ?? "")
// `options.paths` reads `filename` and `id` of the parent too: Zig::pathsParentOf().
JSC::JSValue parentValue = fromValue;
JSC::JSValue parentFilename;
JSC::JSValue parentId;
if (!fromValue.isString()) {
if (
// fast path: it's a real CommonJS module object.
auto* cjs = dynamicDowncast<Bun::JSCommonJSModule>(fromValue)) {
fromValue = cjs->filename();
parentFilename = fromValue;
} else if (fromValue.isObject()) {
// slow path: userland code did something weird. Try filename first, then id
auto* obj = fromValue.getObject();
auto filenameValue = obj->getIfPropertyExists(globalObject, names.filenamePublicName());
RETURN_IF_EXCEPTION(scope, {});
parentFilename = filenameValue;

if (filenameValue && filenameValue.isString()) {
fromValue = filenameValue;
} else {
auto idValue = obj->getIfPropertyExists(globalObject, vm.propertyNames->id);
RETURN_IF_EXCEPTION(scope, {});
parentId = idValue ? idValue : jsUndefined();

if (idValue && idValue.isString()) {
fromValue = idValue;
Expand Down Expand Up @@ -357,6 +364,9 @@ JSC_DEFINE_HOST_FUNCTION(jsFunctionResolveFileName,
return {};
}

auto pathsParent = Zig::pathsParentOf(globalObject, parentValue, parentFilename, parentId);
RETURN_IF_EXCEPTION(scope, {});

WTF::Vector<BunString> paths;

// Iterate through the array using forEachInIterable
Expand All @@ -383,7 +393,7 @@ JSC_DEFINE_HOST_FUNCTION(jsFunctionResolveFileName,
return {};
}

result = Bun__resolveSyncWithPaths(globalObject, JSC::JSValue::encode(moduleName), JSValue::encode(fromValue), false, true, paths.begin(), paths.size());
result = Bun__resolveSyncWithPaths(globalObject, JSC::JSValue::encode(moduleName), JSValue::encode(fromValue), false, true, paths.begin(), paths.size(), pathsParent);

// Clean up BunStrings to avoid leaking
for (auto& path : paths) {
Expand Down
2 changes: 2 additions & 0 deletions src/jsc/web_worker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1415,6 +1415,8 @@ impl WebWorker {
// before the process exits, so free the HPACK scratch buffer that any
// http2 session on this thread allocated.
Bun__freeSharedHeaderBufferForThreadExit();
// The path buffer that a resolve that found nothing allocated on this thread.
virtual_machine::free_specifier_cache_resolver_buf();
drop(arena.take());
log!(
"[{}] shutdown: thread state freed",
Expand Down
2 changes: 1 addition & 1 deletion src/resolver/dir_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ impl Default for DirInfo {
/// PackageJSON / TSConfigJSON arena (see `intern_package_json` / the tsconfig
/// merge loop); never freed for the life of the process.
#[inline]
fn arena_ref<T>(p: NonNull<T>) -> &'static T {
pub(crate) fn arena_ref<T>(p: NonNull<T>) -> &'static T {
// SAFETY: ARENA — see fn doc; pointee is process-lifetime, never freed
// while a `DirInfo` reader is live.
unsafe { &*p.as_ptr() }
Expand Down
3 changes: 2 additions & 1 deletion src/resolver/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ pub use tsconfig_json::TSConfigJSON;
/// Re-export so dependents can spell `bun_resolver::install_types::AutoInstaller`.
pub use ::bun_install_types::resolver_hooks as install_types;
pub use resolver::{
AnyResolveWatcher, BrowserMapPathKind, Bufs, Dirname, Resolver, module_type_from_ext,
AnyResolveWatcher, BrowserMapPathKind, Bufs, CustomDirPaths, Dirname, PackageScope,
PathsParent, Resolver, module_type_from_ext,
};
pub use result::{
DebugLogs, DirEntryResolveQueueItem, ExternalKind, FlushMode, LoadResult, MatchResult,
Expand Down
23 changes: 23 additions & 0 deletions src/resolver/package_json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,29 @@ impl PackageJSON {
.any(|rows| !rows.is_empty())
}

/// Whether `import_path` is a `#` request that the "imports" of this package answer.
#[inline]
pub(crate) fn answers_import(&self, import_path: &[u8]) -> bool {
import_path.starts_with(b"#") && self.imports.is_some()
}

/// https://nodejs.org/api/packages.html#packages_self_referencing_a_package_using_its_name
#[inline]
pub(crate) fn is_self_reference(&self, import_path: &[u8]) -> bool {
Package::parse_name(import_path).is_some_and(|name| name == self.name.as_ref())
&& self.exports.is_some()
}

/// Node reads no "imports" or "exports" of a parent's package that is a number or a boolean.
pub(crate) fn claims_as_parent(&self, import_path: &[u8]) -> bool {
let read_by_node = |map: &Option<ExportsMap>| {
map.as_ref()
.is_some_and(|map| !matches!(map.root.data, EntryData::Invalid))
};
(self.answers_import(import_path) && read_by_node(&self.imports))
|| (self.is_self_reference(import_path) && read_by_node(&self.exports))
}

/// The versions that a package in node_modules declares for `name`, highest precedence first.
pub(crate) fn installed_dependency_versions<'a>(
&'a self,
Expand Down
Loading
Loading