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
38 changes: 16 additions & 22 deletions src/jsc/ConsoleObject.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1910,6 +1910,10 @@ pub mod formatter {
| Tag::Event
)
}

pub(crate) fn is_recorded_by_its_printer(self) -> bool {
matches!(self, Tag::Error)
}
}

/// Only `CustomFormattedObject` carries a payload.
Expand Down Expand Up @@ -3226,6 +3230,11 @@ pub mod formatter {
let _ = self.map.remove(&value);
}

#[inline]
pub(crate) fn visited_contains(&self, value: JSValue) -> bool {
self.map_node.is_some() && self.map.contains(&value)
}

/// Circular-reference / stack-overflow / visited-map prelude for
/// `print_as`. Outlined so its locals (the pool node, the
/// `get_or_put` result, the `[Circular]` write path) live in a leaf
Expand All @@ -3240,7 +3249,7 @@ pub mod formatter {
&mut self,
writer_: &mut dyn bun_io::Write,
value: JSValue,
can_circ: bool,
format: Tag,
remove_before_recurse: &mut bool,
) -> JsResult<bool> {
if self.failed {
Expand All @@ -3249,7 +3258,7 @@ pub mod formatter {
if self.global_this.has_exception() {
return Err(jsc::JsError::Thrown);
}
if !can_circ {
if !format.can_have_circular_references() {
return Ok(true);
}

Expand All @@ -3261,6 +3270,10 @@ pub mod formatter {
return Ok(false);
}

if format.is_recorded_by_its_printer() {
return Ok(true);
}

if !self.visited_insert(value) {
if writer_
.write_all(pfmt!("<r><cyan>[Circular]<r>", C).as_bytes())
Expand Down Expand Up @@ -3289,7 +3302,7 @@ pub mod formatter {
if !self.print_as_prelude::<ENABLE_ANSI_COLORS>(
writer_,
value,
format.can_have_circular_references(),
format,
&mut remove_before_recurse,
)? {
return Ok(());
Expand Down Expand Up @@ -3861,25 +3874,6 @@ pub mod formatter {
writer_: &mut dyn bun_io::Write,
value: JSValue,
) -> JsResult<()> {
// Temporarily remove from the visited map to allow
// printErrorlikeObject to process it. The circular reference
// check is already done in print_as, so we know it's safe.
let was_in_map = if self.map_node.is_some() {
self.map.remove(&value).is_some()
} else {
false
};
let map_restore_ptr: *mut visited::Map = &raw mut self.map;
scopeguard::defer! {
// SAFETY: `self.map` outlives this guard; no other borrow is
// live at the drop point.
unsafe {
if was_in_map {
let _ = (*map_restore_ptr).insert(value, ());
}
}
}

let mut adapter = DynWriteAdapter::new(&mut *writer_);
// SAFETY: per-thread VM.
let vm = VirtualMachine::get().as_mut();
Expand Down
79 changes: 63 additions & 16 deletions src/jsc/VirtualMachine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5944,7 +5944,20 @@ impl VirtualMachine {
let writer = unsafe { &mut *ctx.writer };
ctx.printed_member = true;
formatter.depth = formatter.depth.saturating_add(1);
if formatter.depth > formatter.error_chain_max_depth()
if next_value.is_cell()
&& next_value.js_type() == crate::JSType::ErrorInstance
&& formatter.visited_contains(next_value)
{
let _ = if ctx.allow_ansi_color {
writer.write_all(
bun_core::pretty_fmt!("<r><cyan>[Circular]<r>\n", true).as_bytes(),
)
} else {
writer.write_all(
bun_core::pretty_fmt!("<r><cyan>[Circular]<r>\n", false).as_bytes(),
)
};
} else if formatter.depth > formatter.error_chain_max_depth()
|| !formatter.stack_check.is_safe_to_recurse()
{
let _ = if ctx.allow_ansi_color {
Expand Down Expand Up @@ -6611,6 +6624,7 @@ impl VirtualMachine {
) -> crate::CrateResult<()> {
let mut default_formatter = crate::console_object::Formatter::new(self.global());
let f = formatter.unwrap_or(&mut default_formatter);
let mut recorded = false;
self.print_error_instance_body(
zig_exception,
JSValue::ZERO,
Expand All @@ -6619,6 +6633,7 @@ impl VirtualMachine {
writer,
allow_ansi_color,
allow_side_effects,
&mut recorded,
)
// `defer default_formatter.deinit()` → Drop.
}
Expand Down Expand Up @@ -6667,6 +6682,18 @@ impl VirtualMachine {
return Ok(());
}

if error_instance.is_cell()
&& error_instance.js_type() == crate::JSType::ErrorInstance
&& formatter.visited_contains(error_instance)
{
writer.write_all(if allow_ansi_color {
bun_core::pretty_fmt!("<r><cyan>[Circular]<r>", true).as_bytes()
} else {
bun_core::pretty_fmt!("<r><cyan>[Circular]<r>", false).as_bytes()
})?;
return Ok(());
}
Comment thread
robobun marked this conversation as resolved.

// Note: `Holder` is ~4 KB (32 ZigStackFrames + 6 source lines +
// ZigException). It sits next to the large runtime-dispatched body, so
// box it to keep the per-level recursion frame small enough for the
Expand All @@ -6690,6 +6717,7 @@ impl VirtualMachine {
);
error_instance.ensure_still_alive();

let mut recorded = false;
let result = self.print_error_instance_body(
// SAFETY: see above.
unsafe { &mut *exception },
Expand All @@ -6700,7 +6728,11 @@ impl VirtualMachine {
writer,
allow_ansi_color,
allow_side_effects,
&mut recorded,
);
if recorded {
formatter.visited_remove(error_instance);
}

drop(source_code_slice);
exception_holder.deinit(self);
Expand All @@ -6722,6 +6754,7 @@ impl VirtualMachine {
writer: &mut bun_core::io::Writer,
allow_ansi_color: bool,
allow_side_effects: bool,
recorded: &mut bool,
) -> crate::CrateResult<()> {
use crate::JSType;
use crate::console_object::formatter::TagOptions;
Expand Down Expand Up @@ -7065,7 +7098,9 @@ impl VirtualMachine {
}

let kind = value.js_type();
if kind == JSType::ErrorInstance && !prev_had_errors {
let circular = kind == JSType::ErrorInstance
&& (value == error_instance || formatter.visited_contains(value));
if kind == JSType::ErrorInstance && !prev_had_errors && !circular {
if field.eq_ascii(b"cause") {
saw_cause = true;
}
Expand All @@ -7076,6 +7111,12 @@ impl VirtualMachine {
|| value.is_primitive()
|| kind.is_string_like()
{
if circular && field.eq_ascii(b"cause") {
saw_cause = true;
}
Comment thread
robobun marked this conversation as resolved.
if !*recorded && !value.is_primitive() {
*recorded = formatter.visited_insert(error_instance);
}
let prev_disable_inspect_custom = formatter.disable_inspect_custom;
let prev_quote_strings = formatter.quote_strings;
let prev_max_depth = formatter.max_depth;
Expand Down Expand Up @@ -7159,20 +7200,30 @@ impl VirtualMachine {
)?;
}

if !is_first_property {
writer.write_all(b"\n")?;
}

// "cause" is not enumerable, so the above loop won't see it.
if !saw_cause {
let key = bun_core::String::static_("cause");
if let Some(cause) = error_instance.get_own(global_ref, &key)? {
if cause.is_cell() && cause.js_type() == JSType::ErrorInstance {
cause.protect();
errors_to_append.push(cause);
if cause == error_instance || formatter.visited_contains(cause) {
let pad_left = longest_name.saturating_sub(b"cause".len());
is_first_property = false;
splat_space(writer, pad_left as u64)?;
pretty_write!(
writer,
" cause<r><d>:<r> <r><cyan>[Circular]<r><r><d>,<r>\n"
)?;
} else {
cause.protect();
errors_to_append.push(cause);
}
}
}
}

if !is_first_property {
writer.write_all(b"\n")?;
}
} else if error_instance != JSValue::ZERO {
// If you do `reportError([1,2,3])` we should still show something.
let tag = Tag::get_advanced(
Expand Down Expand Up @@ -7200,15 +7251,12 @@ impl VirtualMachine {
)?;
}

if !*recorded && !errors_to_append.is_empty() {
*recorded = formatter.visited_insert(error_instance);
}

let mut exception_list = exception_list;
for &err in &errors_to_append {
// Circular-ref guard for cause chains.
if !formatter.visited_insert(err) {
writer.write_all(b"\n")?;
pretty_write!(writer, "<r><cyan>[Circular]<r>")?;
continue;
}

writer.write_all(b"\n")?;
let prev_depth = formatter.depth;
formatter.depth = formatter.depth.saturating_add(1);
Expand All @@ -7226,7 +7274,6 @@ impl VirtualMachine {
)
};
formatter.depth = prev_depth;
formatter.visited_remove(err);
result?;
}

Expand Down
Loading
Loading