pub use bun_collections::VecExt as _VecExtReexport;
pub mod parser;
pub use parser::*;
pub mod lexer;
pub mod fold;
pub mod lower;
pub mod p;
pub mod parse;
pub mod repl_transforms;
pub mod scan;
pub mod typescript;
pub mod visit;
pub use p::P;
pub use parse::parse_entry::{Options as ParserOptions, Parser};
#[allow(non_snake_case)]
pub mod Macro {
pub const NAMESPACE: &[u8] = b"macro";
pub const NAMESPACE_WITH_COLON: &[u8] = b"macro:";
#[inline]
pub fn is_macro_path(str_: &[u8]) -> bool {
str_.starts_with(NAMESPACE_WITH_COLON)
}
#[repr(transparent)]
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub struct MacroJSCtx(pub i64);
impl MacroJSCtx {
pub const ZERO: Self = MacroJSCtx(0);
}
impl Default for MacroJSCtx {
#[inline]
fn default() -> Self {
Self::ZERO
}
}
pub struct MacroContext {
pub javascript_object: MacroJSCtx,
pub data: *mut core::ffi::c_void,
}
impl Default for MacroContext {
#[inline]
fn default() -> Self {
Self {
javascript_object: MacroJSCtx::ZERO,
data: core::ptr::null_mut(),
}
}
}
unsafe extern "Rust" {
fn __bun_macro_context_init(transpiler: *mut core::ffi::c_void) -> MacroContext;
fn __bun_macro_context_deinit(data: *mut core::ffi::c_void);
safe fn __bun_macro_context_call(
ctx: &mut MacroContext,
import_record_path: &[u8],
source_dir: &[u8],
log: &mut bun_ast::Log,
source: &bun_ast::Source,
import_range: bun_ast::Range,
caller: bun_ast::Expr,
function_name: &[u8],
) -> Result<bun_ast::Expr, bun_core::Error>;
fn __bun_macro_context_get_remap(
data: *mut core::ffi::c_void,
path: &[u8],
) -> Option<&'static MacroRemapEntry>;
safe fn __bun_macro_collect_vm_garbage();
}
#[inline]
pub fn collect_vm_garbage() {
__bun_macro_collect_vm_garbage();
}
impl MacroContext {
#[inline]
pub fn call(
&mut self,
import_record_path: &[u8],
source_dir: &[u8],
log: &mut bun_ast::Log,
source: &bun_ast::Source,
import_range: bun_ast::Range,
caller: bun_ast::Expr,
function_name: &[u8],
) -> Result<bun_ast::Expr, bun_core::Error> {
__bun_macro_context_call(
self,
import_record_path,
source_dir,
log,
source,
import_range,
caller,
function_name,
)
}
#[inline]
pub fn init<T>(transpiler: &mut T) -> Self {
unsafe {
__bun_macro_context_init(
core::ptr::from_mut(transpiler).cast::<core::ffi::c_void>(),
)
}
}
#[inline]
pub fn deinit(self) {
unsafe { __bun_macro_context_deinit(self.data) }
}
#[inline]
pub fn get_remap(&self, path: &[u8]) -> Option<&'static MacroRemapEntry> {
if self.data.is_null() {
return None;
}
unsafe { __bun_macro_context_get_remap(self.data, path) }
}
}
pub type MacroRemapEntry = bun_collections::StringArrayHashMap<Box<[u8]>>;
}
use bun_ast::{Ast, Ref};
pub enum Result<'a> {
AlreadyBundled(AlreadyBundled),
Cached,
Ast(Box<Ast<'a>>),
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum AlreadyBundled {
Bun,
BunCjs,
Bytecode,
BytecodeCjs,
}
impl<'a, const IS_TS: bool, const SCAN: bool> bun_ast::expr::EqlParser
for crate::p::P<'a, IS_TS, SCAN>
{
#[inline]
fn arena(&self) -> &bun_alloc::Arena {
self.arena
}
#[inline]
fn module_ref(&self) -> Ref {
self.module_ref
}
}
pub mod defines_table;
pub mod defines {
use bun_collections::{StringArrayHashMap, StringHashMap};
use bun_core::strings;
use bun_ast::E;
use bun_ast::StoreRef;
use bun_ast::expr::Data as ExprData;
pub type RawDefines = StringArrayHashMap<Box<[u8]>>;
pub type UserDefines = StringHashMap<DefineData>;
pub type UserDefinesArray = StringArrayHashMap<DefineData>;
pub type IdentifierDefine = DefineData;
#[derive(Clone)]
pub struct DotDefine {
pub parts: Vec<Box<[u8]>>,
pub data: DefineData,
}
#[repr(transparent)]
#[derive(Clone, Copy, Default, PartialEq, Eq)]
pub struct Flags(u8);
impl Flags {
const VALUELESS_SHIFT: u8 = 3;
const CAN_BE_REMOVED_SHIFT: u8 = 4;
const CALL_UNWRAP_SHIFT: u8 = 5;
const CALL_UNWRAP_MASK: u8 = 0b11 << Self::CALL_UNWRAP_SHIFT;
const METHOD_CALL_UNDEF_SHIFT: u8 = 7;
#[inline]
pub const fn valueless(self) -> bool {
(self.0 >> Self::VALUELESS_SHIFT) & 1 != 0
}
#[inline]
pub fn set_valueless(&mut self, v: bool) {
self.0 =
(self.0 & !(1 << Self::VALUELESS_SHIFT)) | ((v as u8) << Self::VALUELESS_SHIFT);
}
#[inline]
pub const fn can_be_removed_if_unused(self) -> bool {
(self.0 >> Self::CAN_BE_REMOVED_SHIFT) & 1 != 0
}
#[inline]
pub fn set_can_be_removed_if_unused(&mut self, v: bool) {
self.0 = (self.0 & !(1 << Self::CAN_BE_REMOVED_SHIFT))
| ((v as u8) << Self::CAN_BE_REMOVED_SHIFT);
}
#[inline]
pub fn call_can_be_unwrapped_if_unused(self) -> E::CallUnwrap {
match (self.0 & Self::CALL_UNWRAP_MASK) >> Self::CALL_UNWRAP_SHIFT {
1 => E::CallUnwrap::IfUnused,
2 => E::CallUnwrap::IfUnusedAndToStringSafe,
_ => E::CallUnwrap::Never,
}
}
#[inline]
pub fn set_call_can_be_unwrapped_if_unused(&mut self, v: E::CallUnwrap) {
self.0 = (self.0 & !Self::CALL_UNWRAP_MASK)
| (((v as u8) & 0b11) << Self::CALL_UNWRAP_SHIFT);
}
#[inline]
pub const fn method_call_must_be_replaced_with_undefined(self) -> bool {
(self.0 >> Self::METHOD_CALL_UNDEF_SHIFT) & 1 != 0
}
#[inline]
pub fn set_method_call_must_be_replaced_with_undefined(&mut self, v: bool) {
self.0 = (self.0 & !(1 << Self::METHOD_CALL_UNDEF_SHIFT))
| ((v as u8) << Self::METHOD_CALL_UNDEF_SHIFT);
}
pub fn new(
valueless: bool,
can_be_removed_if_unused: bool,
call_can_be_unwrapped_if_unused: E::CallUnwrap,
method_call_must_be_replaced_with_undefined: bool,
) -> Self {
let mut f = Flags(0);
f.set_valueless(valueless);
f.set_can_be_removed_if_unused(can_be_removed_if_unused);
f.set_call_can_be_unwrapped_if_unused(call_can_be_unwrapped_if_unused);
f.set_method_call_must_be_replaced_with_undefined(
method_call_must_be_replaced_with_undefined,
);
f
}
}
#[derive(Clone)]
pub struct DefineData {
pub value: ExprData,
pub original_name: Option<Box<[u8]>>,
pub flags: Flags,
}
unsafe impl Send for DefineData {}
unsafe impl Sync for DefineData {}
impl Default for DefineData {
fn default() -> Self {
Self {
value: ExprData::EMissing(E::Missing),
original_name: None,
flags: Flags::default(),
}
}
}
#[derive(Clone, Copy)]
pub struct Options<'a> {
pub original_name: Option<&'a [u8]>,
pub value: ExprData,
pub valueless: bool,
pub can_be_removed_if_unused: bool,
pub call_can_be_unwrapped_if_unused: E::CallUnwrap,
pub method_call_must_be_replaced_with_undefined: bool,
}
impl<'a> Default for Options<'a> {
fn default() -> Self {
Self {
original_name: None,
value: ExprData::EMissing(E::Missing),
valueless: false,
can_be_removed_if_unused: false,
call_can_be_unwrapped_if_unused: E::CallUnwrap::Never,
method_call_must_be_replaced_with_undefined: false,
}
}
}
impl DefineData {
pub fn init(options: Options<'_>) -> DefineData {
DefineData {
value: options.value,
flags: Flags::new(
options.valueless,
options.can_be_removed_if_unused,
options.call_can_be_unwrapped_if_unused,
options.method_call_must_be_replaced_with_undefined,
),
original_name: options.original_name.map(Box::<[u8]>::from),
}
}
#[inline]
pub fn original_name(&self) -> Option<&[u8]> {
match &self.original_name {
Some(name) if !name.is_empty() => Some(name.as_ref()),
_ => None,
}
}
#[inline]
pub fn can_be_removed_if_unused(&self) -> bool {
self.flags.can_be_removed_if_unused()
}
#[inline]
pub fn call_can_be_unwrapped_if_unused(&self) -> E::CallUnwrap {
self.flags.call_can_be_unwrapped_if_unused()
}
#[inline]
pub fn method_call_must_be_replaced_with_undefined(&self) -> bool {
self.flags.method_call_must_be_replaced_with_undefined()
}
#[inline]
pub fn valueless(&self) -> bool {
self.flags.valueless()
}
pub fn init_boolean(value: bool) -> DefineData {
let mut flags = Flags::default();
flags.set_can_be_removed_if_unused(true);
DefineData {
value: ExprData::EBoolean(E::Boolean { value }),
flags,
..Default::default()
}
}
pub fn init_static_string(str: &'static E::EString) -> DefineData {
let mut flags = Flags::default();
flags.set_can_be_removed_if_unused(true);
DefineData {
value: ExprData::EString(StoreRef::from_static(str)),
flags,
..Default::default()
}
}
pub fn merge(a: &DefineData, b: DefineData) -> DefineData {
DefineData {
value: b.value,
flags: Flags::new(
a.method_call_must_be_replaced_with_undefined()
|| b.method_call_must_be_replaced_with_undefined(),
a.can_be_removed_if_unused(),
a.call_can_be_unwrapped_if_unused(),
a.method_call_must_be_replaced_with_undefined()
|| b.method_call_must_be_replaced_with_undefined(),
),
original_name: b.original_name,
}
}
}
#[derive(Default)]
pub struct Define {
pub identifiers: StringHashMap<IdentifierDefine>,
pub dots: StringHashMap<Vec<DotDefine>>,
pub drop_debugger: bool,
}
impl Define {
pub fn for_identifier(&self, name: &[u8]) -> Option<&IdentifierDefine> {
if let Some(data) = self.identifiers.get(name) {
return Some(data);
}
crate::defines_table::lookup_pure_global_identifier(name).map(|v| v.value())
}
pub fn insert_from_iterator<'a, I>(&mut self, iter: I) -> Result<(), bun_alloc::AllocError>
where
I: Iterator<Item = (&'a [u8], &'a DefineData)>,
{
for (key, value) in iter {
self.insert(key, value.clone())?;
}
Ok(())
}
pub fn insert(
&mut self,
key: &[u8],
value: DefineData,
) -> Result<(), bun_alloc::AllocError> {
if let Some(last_dot) = strings::last_index_of_char(key, b'.') {
let tail = &key[last_dot + 1..key.len()];
let remainder = &key[0..last_dot];
let count = remainder.iter().filter(|&&b| b == b'.').count() + 1;
let mut parts: Vec<Box<[u8]>> = Vec::with_capacity(count + 1);
for split in remainder.split(|b| *b == b'.') {
parts.push(Box::from(split));
}
parts.push(Box::from(tail));
let mut initial_values: &[DotDefine] = &[];
if let Some(existing) = self.dots.get_mut(tail) {
for part in existing.iter_mut() {
if are_parts_equal(&part.parts, &parts) {
part.data = DefineData::merge(&part.data, value);
return Ok(());
}
}
initial_values = existing.as_slice();
}
let mut list: Vec<DotDefine> = Vec::with_capacity(initial_values.len() + 1);
if !initial_values.is_empty() {
list.extend_from_slice(initial_values);
}
list.push(DotDefine { data: value, parts });
self.dots.put_assume_capacity(tail, list);
} else {
self.identifiers.put_assume_capacity(key, value);
}
Ok(())
}
}
pub fn are_parts_equal(a: &[Box<[u8]>], b: &[Box<[u8]>]) -> bool {
if a.len() != b.len() {
return false;
}
for i in 0..a.len() {
if !strings::eql(&a[i], &b[i]) {
return false;
}
}
true
}
}
pub use defines::{Define, DefineData};
pub mod defines_full_draft {
use bstr::BStr;
use bun_collections::{ArrayHashMap, StringHashMap};
use bun_core::strings;
use bun_ast::base::Ref;
use bun_ast::e as E;
use bun_ast::expr;
use crate::lexer as js_lexer;
use bun_ast::StoreRef;
pub type RawDefines = ArrayHashMap<Box<[u8]>, Box<[u8]>>;
pub type UserDefines = StringHashMap<DefineData>;
pub type UserDefinesArray = ArrayHashMap<Box<[u8]>, DefineData>;
pub type IdentifierDefine = DefineData;
#[derive(Clone)]
pub struct DotDefine {
pub parts: Vec<Box<[u8]>>,
pub data: DefineData,
}
bitflags::bitflags! {
#[derive(Copy, Clone, Default)]
pub struct DefineDataFlags: u8 {
const VALUELESS = 1 << 3;
const CAN_BE_REMOVED_IF_UNUSED = 1 << 4;
const METHOD_CALL_MUST_BE_REPLACED_WITH_UNDEFINED = 1 << 7;
}
}
const CALL_UNWRAP_SHIFT: u8 = 5;
const CALL_UNWRAP_MASK: u8 = 0b11 << CALL_UNWRAP_SHIFT;
#[derive(Clone)]
pub struct DefineData {
pub value: expr::Data,
pub original_name: Option<Box<[u8]>>,
pub flags: DefineDataFlags,
}
impl Default for DefineData {
fn default() -> Self {
Self {
value: expr::Data::EUndefined(E::Undefined {}),
original_name: None,
flags: DefineDataFlags::empty(),
}
}
}
impl DefineData {
#[inline]
pub fn original_name(&self) -> Option<&[u8]> {
match &self.original_name {
Some(name) if !name.is_empty() => Some(name.as_ref()),
_ => None,
}
}
#[inline]
pub fn can_be_removed_if_unused(&self) -> bool {
self.flags
.contains(DefineDataFlags::CAN_BE_REMOVED_IF_UNUSED)
}
#[inline]
pub fn call_can_be_unwrapped_if_unused(&self) -> E::CallUnwrap {
match (self.flags.bits() & CALL_UNWRAP_MASK) >> CALL_UNWRAP_SHIFT {
0 => E::CallUnwrap::Never,
1 => E::CallUnwrap::IfUnused,
2 => E::CallUnwrap::IfUnusedAndToStringSafe,
_ => E::CallUnwrap::Never,
}
}
#[inline]
pub fn method_call_must_be_replaced_with_undefined(&self) -> bool {
self.flags
.contains(DefineDataFlags::METHOD_CALL_MUST_BE_REPLACED_WITH_UNDEFINED)
}
#[inline]
pub fn valueless(&self) -> bool {
self.flags.contains(DefineDataFlags::VALUELESS)
}
pub fn init_boolean(value: bool) -> DefineData {
DefineData {
value: expr::Data::EBoolean(E::Boolean { value }),
flags: DefineDataFlags::CAN_BE_REMOVED_IF_UNUSED,
..Default::default()
}
}
pub fn init_static_string(str_: &'static E::String) -> DefineData {
DefineData {
value: expr::Data::EString(StoreRef::from_static(str_)),
flags: DefineDataFlags::CAN_BE_REMOVED_IF_UNUSED,
..Default::default()
}
}
pub fn merge(a: &DefineData, b: &DefineData) -> DefineData {
let mut flags = DefineDataFlags::empty();
if a.can_be_removed_if_unused() {
flags |= DefineDataFlags::CAN_BE_REMOVED_IF_UNUSED;
}
flags = DefineDataFlags::from_bits_retain(
flags.bits() | ((a.call_can_be_unwrapped_if_unused() as u8) << CALL_UNWRAP_SHIFT),
);
if a.method_call_must_be_replaced_with_undefined()
|| b.method_call_must_be_replaced_with_undefined()
{
flags |= DefineDataFlags::VALUELESS;
flags |= DefineDataFlags::METHOD_CALL_MUST_BE_REPLACED_WITH_UNDEFINED;
}
DefineData {
value: b.value,
flags,
original_name: b.original_name.clone(),
}
}
pub fn parse(
key: &[u8],
value_str: &[u8],
value_is_undefined: bool,
method_call_must_be_replaced_with_undefined: bool,
log: &mut bun_ast::Log,
bump: &bun_alloc::Arena,
parse_json: &dyn Fn(
&bun_ast::Source,
&mut bun_ast::Log,
&bun_alloc::Arena,
)
-> core::result::Result<bun_ast::Expr, bun_core::Error>,
) -> core::result::Result<DefineData, bun_core::Error> {
for part in key.split(|&c| c == b'.') {
if !js_lexer::is_identifier(part) {
if strings::eql(part, key) {
log.add_error_fmt(
None,
bun_ast::Loc::default(),
format_args!(
"define key \"{}\" must be a valid identifier",
BStr::new(key)
),
);
} else {
log.add_error_fmt(
None,
bun_ast::Loc::default(),
format_args!(
"define key \"{}\" contains invalid identifier \"{}\"",
BStr::new(part),
BStr::new(value_str)
),
);
}
break;
}
}
let mut is_ident = true;
for part in value_str.split(|&c| c == b'.') {
if !js_lexer::is_identifier(part) || js_lexer::keyword(part).is_some() {
is_ident = false;
break;
}
}
let mut flags = DefineDataFlags::empty();
if value_is_undefined {
flags |= DefineDataFlags::VALUELESS;
}
if method_call_must_be_replaced_with_undefined {
flags |= DefineDataFlags::METHOD_CALL_MUST_BE_REPLACED_WITH_UNDEFINED;
}
if is_ident {
let value = if value_is_undefined || value_str == b"undefined" {
expr::Data::EUndefined(E::Undefined {})
} else {
expr::Data::EIdentifier(
E::Identifier::init(Ref::NONE).with_can_be_removed_if_unused(true),
)
};
flags |= DefineDataFlags::CAN_BE_REMOVED_IF_UNUSED;
return Ok(DefineData {
value,
original_name: if value_str.is_empty() {
None
} else {
Some(Box::<[u8]>::from(value_str))
},
flags,
});
}
let source = bun_ast::Source {
contents: std::borrow::Cow::Owned(value_str.to_vec()),
path: bun_paths::fs::Path::init_with_namespace(b"defines.json", b"internal"),
..Default::default()
};
let expr = parse_json(&source, log, bump)?;
let cloned = expr.data.deep_clone(bump)?;
if expr.is_primitive_literal() {
flags |= DefineDataFlags::CAN_BE_REMOVED_IF_UNUSED;
}
Ok(DefineData {
value: cloned,
original_name: if value_str.is_empty() {
None
} else {
Some(Box::<[u8]>::from(value_str))
},
flags,
})
}
}
pub struct Define {
pub identifiers: StringHashMap<IdentifierDefine>,
pub dots: StringHashMap<Vec<DotDefine>>,
pub drop_debugger: bool,
}
impl Define {
pub fn for_identifier(&self, name: &[u8]) -> Option<&IdentifierDefine> {
if let Some(data) = self.identifiers.get(name) {
return Some(data);
}
None
}
pub fn insert(
&mut self,
bump: &bun_alloc::Arena,
key: &[u8],
value: DefineData,
) -> core::result::Result<(), bun_alloc::AllocError> {
let _ = bump;
if let Some(last_dot) = strings::last_index_of_char(key, b'.') {
let tail = &key[last_dot + 1..];
let remainder = &key[..last_dot];
let count = remainder.iter().filter(|&&c| c == b'.').count() + 1;
let mut parts: Vec<Box<[u8]>> = Vec::with_capacity(count + 1);
for split in remainder.split(|&c| c == b'.') {
parts.push(Box::from(split));
}
parts.push(Box::from(tail));
let entry = self.dots.get_or_put(tail).unwrap().value_ptr;
for part in entry.iter_mut() {
if are_parts_equal(&part.parts, &parts) {
part.data = DefineData::merge(&part.data, &value);
return Ok(());
}
}
entry.push(DotDefine { data: value, parts });
} else {
self.identifiers.put_assume_capacity(key, value);
}
Ok(())
}
pub fn init(
user_defines: Option<UserDefines>,
string_defines: Option<UserDefinesArray>,
drop_debugger: bool,
omit_unused_global_calls: bool,
bump: &bun_alloc::Arena,
) -> core::result::Result<Box<Define>, bun_alloc::AllocError> {
let _ = omit_unused_global_calls;
let mut define = Box::new(Define {
identifiers: StringHashMap::default(),
dots: StringHashMap::default(),
drop_debugger,
});
if let Some(mut user_defines) = user_defines {
for (k, v) in core::mem::take(&mut *user_defines).into_iter() {
define.insert(bump, &k, v)?;
}
}
if let Some(mut string_defines) = string_defines {
let mut it = string_defines.iterator();
while let Some(entry) = it.next() {
define.insert(bump, &**entry.key_ptr, entry.value_ptr.clone())?;
}
}
Ok(define)
}
}
fn are_parts_equal(a: &[Box<[u8]>], b: &[Box<[u8]>]) -> bool {
if a.len() != b.len() {
return false;
}
for i in 0..a.len() {
if !strings::eql(&a[i], &b[i]) {
return false;
}
}
true
}
}
pub mod renamer {
use bun_ast::SlotCounts;
use bun_ast::base::Ref;
use bun_ast::scope::Scope;
use bun_ast::symbol::{INVALID_NESTED_SCOPE_SLOT, SlotNamespace, Symbol};
use bun_collections::VecExt;
pub(crate) fn assign_nested_scope_slots(
_arena: &bun_alloc::Arena,
module_scope: &Scope,
symbols: &mut [Symbol],
) -> SlotCounts {
let mut slot_counts = SlotCounts::default();
let mut sorted_members: Vec<u32> = Vec::new();
const VALID_SLOT: u32 = 0;
for member in module_scope.members.values() {
symbols[member.ref_.inner_index() as usize].nested_scope_slot = VALID_SLOT;
}
for ref_ in module_scope.generated.slice() {
symbols[ref_.inner_index() as usize].nested_scope_slot = VALID_SLOT;
}
for child in module_scope.children.slice() {
slot_counts.union_max(assign_nested_scope_slots_helper(
&mut sorted_members,
child,
symbols,
SlotCounts::default(),
));
}
for member in module_scope.members.values() {
symbols[member.ref_.inner_index() as usize].nested_scope_slot =
INVALID_NESTED_SCOPE_SLOT;
}
for ref_ in module_scope.generated.slice() {
symbols[ref_.inner_index() as usize].nested_scope_slot = INVALID_NESTED_SCOPE_SLOT;
}
slot_counts
}
pub(crate) fn assign_nested_scope_slots_helper(
sorted_members: &mut Vec<u32>,
scope: &Scope,
symbols: &mut [Symbol],
slot_to_copy: SlotCounts,
) -> SlotCounts {
let mut slot = slot_to_copy;
{
sorted_members.clear();
sorted_members.reserve(scope.members.len());
for member in scope.members.values() {
sorted_members.push(member.ref_.inner_index());
}
sorted_members.sort_unstable();
for &inner_index in sorted_members.iter() {
let symbol = &mut symbols[inner_index as usize];
let ns = symbol.slot_namespace();
if ns != SlotNamespace::MustNotBeRenamed && symbol.nested_scope_slot().is_none() {
symbol.nested_scope_slot = slot.slots[ns];
slot.slots[ns] += 1;
}
}
}
for ref_ in scope.generated.slice() {
let symbol = &mut symbols[ref_.inner_index() as usize];
let ns = symbol.slot_namespace();
if ns != SlotNamespace::MustNotBeRenamed && symbol.nested_scope_slot().is_none() {
symbol.nested_scope_slot = slot.slots[ns];
slot.slots[ns] += 1;
}
}
if let Some(ref_) = scope.label_ref {
let symbol = &mut symbols[ref_.inner_index() as usize];
let ns = SlotNamespace::Label;
symbol.nested_scope_slot = slot.slots[ns];
slot.slots[ns] += 1;
}
let mut slot_counts = slot;
for child in scope.children.slice() {
slot_counts.union_max(assign_nested_scope_slots_helper(
sorted_members,
child,
symbols,
slot,
));
}
slot_counts
}
#[derive(Copy, Clone)]
pub struct StableSymbolCount {
pub stable_source_index: u32,
pub ref_: Ref,
pub count: u32,
}
impl StableSymbolCount {
pub fn less_than(i: &StableSymbolCount, j: &StableSymbolCount) -> bool {
if i.count > j.count {
return true;
}
if i.count < j.count {
return false;
}
if i.stable_source_index < j.stable_source_index {
return true;
}
if i.stable_source_index > j.stable_source_index {
return false;
}
i.ref_.inner_index() < j.ref_.inner_index()
}
}
}
#[cfg(test)]
mod stack_check_tests {
#[unsafe(no_mangle)]
extern "Rust" fn __bun_macro_context_get_remap(
data: *mut core::ffi::c_void,
path: &[u8],
) -> Option<&'static crate::Macro::MacroRemapEntry> {
unreachable!("test-only link seam: macro context data = {data:?}, path = {path:?}")
}
#[unsafe(no_mangle)]
extern "Rust" fn __bun_macro_collect_vm_garbage() {
unreachable!("test-only link seam: macro VM sweep is out of scope here")
}
bun_ast::link_noop_TranspilerCacheImpl!(Jsc);
#[unsafe(no_mangle)]
extern "Rust" fn __bun_macro_context_call(
_ctx: &mut crate::Macro::MacroContext,
_import_record_path: &[u8],
_source_dir: &[u8],
_log: &mut bun_ast::Log,
_source: &bun_ast::Source,
_import_range: bun_ast::Range,
_caller: bun_ast::Expr,
_function_name: &[u8],
) -> Result<bun_ast::Expr, bun_core::Error> {
unreachable!("test-only link seam: macro invocation is out of scope here")
}
#[unsafe(no_mangle)]
extern "C" fn Bun__linux_trace_init() -> core::ffi::c_int {
0
}
#[unsafe(no_mangle)]
extern "C" fn Bun__linux_trace_close() {}
#[unsafe(no_mangle)]
extern "C" fn Bun__linux_trace_emit(
_event_name: *const core::ffi::c_char,
_duration_ns: i64,
) -> core::ffi::c_int {
0
}
use crate::defines::Define;
use crate::{Parser, ParserOptions};
use bun_alloc::Arena;
use bun_ast::StoreResetGuard;
fn deep_parens(depth: usize) -> Vec<u8> {
let mut v = Vec::with_capacity(depth * 2 + 1);
v.resize(depth, b'(');
v.push(b'x');
v.resize(depth * 2 + 1, b')');
v
}
struct ParseOutcome {
clean: bool,
overflow_reported: bool,
}
fn parse_js(contents: Vec<u8>) -> ParseOutcome {
bun_ast::initialize_store();
let _store_scope = StoreResetGuard::new();
let source = bun_ast::Source::init_path_string_owned("deep.js", contents);
let mut log = bun_ast::Log::init();
let arena = Arena::new();
let define = Define::default();
let options = ParserOptions::init(
crate::parser::options::JSX::Pragma::default(),
crate::parser::options::Loader::Js,
);
let parsed = Parser::init(options, &mut log, &source, &define, &arena)
.and_then(Parser::parse);
let overflow_reported = log
.msgs
.iter()
.any(|m| m.data.text.as_ref() == b"Maximum call stack size exceeded");
ParseOutcome {
clean: parsed.is_ok() && log.errors == 0,
overflow_reported,
}
}
fn parse_on_thread(stack: usize, contents: Vec<u8>) -> ParseOutcome {
std::thread::Builder::new()
.stack_size(stack)
.spawn(move || {
bun_core::StackCheck::configure_thread();
parse_js(contents)
})
.expect("spawn")
.join()
.expect("join")
}
#[test]
fn deep_but_legal_nesting_parses_clean_with_headroom() {
let out = parse_on_thread(64 * 1024 * 1024, deep_parens(1_000));
assert!(out.clean, "depth-1000 parens must parse without diagnostics");
assert!(!out.overflow_reported);
}
#[test]
fn pathological_depth_reports_orderly_stack_overflow() {
let out = parse_on_thread(8 * 1024 * 1024, deep_parens(1_000_000));
assert!(
out.overflow_reported,
"1M-deep parens must report `Maximum call stack size exceeded`"
);
}
fn calibration_boundary(stack: usize) -> usize {
let fails = |depth: usize| {
parse_on_thread(stack, deep_parens(depth)).overflow_reported
};
let mut lo = 8usize;
assert!(!fails(lo), "depth {lo} must not trip the guard on {stack}-byte stack");
let mut hi = lo * 2;
while fails(hi) == false {
lo = hi;
hi = hi.saturating_mul(2);
assert!(hi < 1_000_000, "no boundary found — guard never fires?");
}
while hi - lo > 1 {
let mid = lo + (hi - lo) / 2;
if fails(mid) {
hi = mid;
} else {
lo = mid;
}
}
hi
}
#[test]
fn guard_boundary_scales_linearly_with_stack_size() {
let small = calibration_boundary(2 * 1024 * 1024);
let large = calibration_boundary(8 * 1024 * 1024);
eprintln!(
"StackCheck calibration: orderly-error boundary = {small} levels (2 MiB), {large} levels (8 MiB)"
);
assert!(small > 0, "boundary on 2 MiB must exist");
assert!(large > small, "8 MiB boundary ({large}) must exceed 2 MiB boundary ({small})");
let ratio = large as f64 / small as f64;
assert!(
(3.0..=5.0).contains(&ratio),
"boundary ratio {small} → {large} = {ratio} must be ~4x for a 4x stack"
);
}
#[test]
fn is_safe_to_recurse_trips_with_platform_headroom_remaining() {
const STACK: usize = 4 * 1024 * 1024;
#[derive(Debug)]
struct Trip {
consumed_from_top: usize,
}
fn recurse(check: &mut bun_core::StackCheck, top: usize, levels: &mut usize) -> Trip {
let probe = &levels as *const _ as usize;
check.update();
if !check.is_safe_to_recurse() {
return Trip {
consumed_from_top: top - probe,
};
}
*levels += 1;
recurse(check, top, levels)
}
let trip = std::thread::Builder::new()
.stack_size(STACK)
.spawn(move || {
bun_core::StackCheck::configure_thread();
let mut check = bun_core::StackCheck::init();
let mut levels = 0usize;
let top = &levels as *const _ as usize;
let trip = recurse(&mut check, top, &mut levels);
assert!(levels > 16, "guard tripped after only {levels} levels — spurious");
trip
})
.expect("spawn")
.join()
.expect("join");
assert!(
trip.consumed_from_top < STACK,
"guard must trip before the stack is exhausted"
);
assert!(
trip.consumed_from_top > STACK / 2,
"guard trip at {:?} bytes consumed is too early — check compares against the wrong bound",
trip
);
}
}