Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
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
28 changes: 28 additions & 0 deletions src/jsc/BuildMessage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,25 @@ impl BuildMessage {
Ok(build_error.to_js(global))
}

/// The `SyntaxError` this diagnostic becomes when it has to leave the realm
/// it was created in (structured clone / worker error reporting): a
/// `BuildMessage` wraps parser state that can't cross, but its message and
/// file/line/column can.
pub fn to_error_instance(&self, global: &JSGlobalObject) -> JSValue {
let (file, line, column): (&[u8], i32, i32) = match &self.msg.data.location {
Some(loc) => (&loc.file, loc.line, loc.column),
None => (b"", 0, 0),
};
crate::bun_string_jsc::error_instance_from_location(
global,
crate::JSErrorCode::SyntaxError,
&self.msg.data.text,
file,
line,
column,
)
}

#[crate::host_fn(method)]
pub fn to_string(&self, global: &JSGlobalObject, _frame: &CallFrame) -> JsResult<JSValue> {
Ok(self.to_string_fn(global))
Expand Down Expand Up @@ -198,3 +217,12 @@ impl BuildMessage {
Ok(ZigString::init(self.msg.kind.string()).to_js(global))
}
}

// SerializedScriptValue.cpp
#[unsafe(no_mangle)]
extern "C" fn BuildMessage__toErrorInstance(
this: &BuildMessage,
global: &JSGlobalObject,
) -> JSValue {
this.to_error_instance(global)
}
7 changes: 7 additions & 0 deletions src/jsc/Exception.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,21 @@ unsafe extern "C" {
stack: &mut ZigStackTrace,
);
safe fn JSC__Exception__asJSValue(this: &Exception) -> JSValue;
safe fn JSC__Exception__thrownValue(this: &Exception) -> JSValue;
}

impl Exception {
pub(crate) fn get_stack_trace(&self, global: &JSGlobalObject, stack: &mut ZigStackTrace) {
JSC__Exception__getStackTrace(self, global, stack);
}

/// The `JSC::Exception` cell itself, as a JSValue.
pub fn value(&self) -> JSValue {
JSC__Exception__asJSValue(self)
}

/// The value that was thrown (what the `JSC::Exception` wraps).
pub fn thrown_value(&self) -> JSValue {
JSC__Exception__thrownValue(self)
}
}
8 changes: 8 additions & 0 deletions src/jsc/JSErrorCode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,15 @@ pub struct JSErrorCode(pub u8);

#[allow(non_upper_case_globals)]
impl JSErrorCode {
// JSC::ErrorType (JavaScriptCore/ErrorType.h)
pub const Error: Self = Self(0);
pub const EvalError: Self = Self(1);
pub const RangeError: Self = Self(2);
pub const ReferenceError: Self = Self(3);
pub const SyntaxError: Self = Self(4);
pub const TypeError: Self = Self(5);
pub const URIError: Self = Self(6);
pub const AggregateError: Self = Self(7);

// StackOverflow & OutOfMemoryError is not an ErrorType in "JavaScriptCore/ErrorType.h" within JSC, so the number here is just totally made up
}
Expand Down
28 changes: 28 additions & 0 deletions src/jsc/ResolveMessage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -494,8 +494,36 @@ impl ResolveMessage {
})
}

/// See `BuildMessage::to_error_instance`.
pub fn to_error_instance(&self, global: &JSGlobalObject) -> JSValue {
let node_message = self.node_message();
// Runtime resolution failures know the importing file (`referrer`) but
// carry no `location`; either way the importer is where this points.
let (file, line, column): (&[u8], i32, i32) = match &self.msg.data.location {
Some(loc) => (&loc.file, loc.line, loc.column),
None => (self.referrer.as_deref().unwrap_or(b""), 0, 0),
};
crate::bun_string_jsc::error_instance_from_location(
global,
crate::JSErrorCode::Error,
node_message.as_deref().unwrap_or(&self.msg.data.text),
file,
line,
column,
)
}

pub fn finalize(self: Box<Self>) {
// Dropping the Box drops `msg` and the owned `referrer` buffer.
drop(self);
}
}

// SerializedScriptValue.cpp
#[unsafe(no_mangle)]
extern "C" fn ResolveMessage__toErrorInstance(
this: &ResolveMessage,
global: &JSGlobalObject,
) -> JSValue {
this.to_error_instance(global)
}
32 changes: 23 additions & 9 deletions src/jsc/VirtualMachine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5235,7 +5235,15 @@ impl VirtualMachine {
};
}

if value.js_type() == jsc::JSType::DOMWrapper {
// A thrown (rather than rejected) diagnostic arrives wrapped in its
// JSC::Exception; look through it so it still prints as a diagnostic.
let thrown = match value.as_exception(self.jsc_vm) {
// SAFETY: `as_exception` proved `value` is a live `JSC::Exception` cell.
Some(exception) => unsafe { &*exception }.thrown_value(),
None => value,
};
if thrown.js_type() == jsc::JSType::DOMWrapper {
let value = thrown;
// `as_class_ref` is the audited `as_::<T>() → &T` backref-deref;
// R-2: shared borrow — `logged` is `Cell<bool>`.
if let Some(build_error) = value.as_class_ref::<crate::BuildMessage>() {
Expand Down Expand Up @@ -5543,7 +5551,12 @@ impl VirtualMachine {
f.source_url.eql_comptime("bun:wrap") || f.function_name.eql_comptime("::bunternal::")
}
fn is_unknown_source(url: &bun_core::String) -> bool {
url.is_empty() || url.eql_comptime("[unknown]") || url.has_prefix_comptime(b"[source:")
url.is_empty()
|| url.eql_comptime("[unknown]")
// FormatStackTraceForJS spells them this way in `.stack` strings.
|| url.eql_comptime("unknown")
|| url.eql_comptime("native")
|| url.has_prefix_comptime(b"[source:")
}

let mut frames_len = exception.stack.frames_len as usize;
Expand Down Expand Up @@ -5578,13 +5591,14 @@ impl VirtualMachine {
{
continue;
}
// Note: `frames[j] = frame`. `ZigStackFrame` impls
// `Drop` so `copy_within` is unavailable; swap instead —
// the discarded tail past `j` is never read after we
// truncate `frames_len` below.
// Swap rather than copy so each frame's refs stay owned
// exactly once; the discarded frames collect past `j`.
frames_buf.swap(j, i);
j += 1;
}
for discarded in &mut frames_buf[j..frames_len] {
discarded.deinit();
}
exception.stack.frames_len = j as u8;
frames_len = j;
}
Expand Down Expand Up @@ -5724,7 +5738,7 @@ impl VirtualMachine {
};

if enable_source_code_preview.get() && code.slice().is_empty() {
exception.collect_source_lines(error_instance, global);
exception.collect_source_lines();
}

// Direct copy; both sides are `bun_core::Ordinal`.
Expand Down Expand Up @@ -5768,14 +5782,14 @@ impl VirtualMachine {
*source_code_slice = Some(code);
}
} else if enable_source_code_preview.get() {
exception.collect_source_lines(error_instance, global);
exception.collect_source_lines();
}

drop(top_source_url);

if frames.len() > 1 {
for i in 0..frames.len() {
if i == top || frames[i].position.is_invalid() {
if i == top || frames[i].position.is_invalid() || frames[i].remapped {
continue;
}
let source_url = frames[i].source_url.to_utf8();
Expand Down
19 changes: 8 additions & 11 deletions src/jsc/ZigException.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,12 @@ use bun_url::URL as ZigURL;

use crate::module_loader::ModuleLoader;
use crate::virtual_machine::VirtualMachine;
use crate::{JSErrorCode, JSGlobalObject, JSRuntimeType, JSValue, ZigStackFrame, ZigStackTrace};
use crate::{JSErrorCode, JSRuntimeType, ZigStackFrame, ZigStackTrace};

// SAFETY (safe fn): `JSValue` is a by-value scalar; `JSGlobalObject` is an
// opaque `UnsafeCell`-backed handle (`&` is ABI-identical to non-null `*mut`);
// `ZigException` is a `#[repr(C)]` out-param the C++ side fills in-place.
// SAFETY (safe fn): `ZigException` is a `#[repr(C)]` out-param the C++ side
// fills in-place.
unsafe extern "C" {
pub(crate) safe fn ZigException__collectSourceLines(
js_value: JSValue,
global: &JSGlobalObject,
exception: &mut ZigException,
);
pub(crate) safe fn ZigException__collectSourceLines(exception: &mut ZigException);
}

/// Represents a JavaScript exception with additional information
Expand Down Expand Up @@ -50,8 +45,10 @@ pub struct ZigException {
}

impl ZigException {
pub(crate) fn collect_source_lines(&mut self, value: JSValue, global: &JSGlobalObject) {
ZigException__collectSourceLines(value, global, self);
/// Slice the top frame's source lines out of the SourceProvider it was
/// populated from (for sources the printer can't read from disk).
pub(crate) fn collect_source_lines(&mut self) {
ZigException__collectSourceLines(self);
}

// Kept as explicit `deinit` (not `Drop`) — this is a #[repr(C)] FFI
Expand Down
12 changes: 9 additions & 3 deletions src/jsc/ZigStackFrame.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use core::fmt;
use core::ptr::NonNull;
use std::io::Write as _;

use bstr::BStr;
Expand All @@ -23,8 +24,10 @@ pub struct ZigStackFrame {
/// This informs formatters whether to display as a blob URL or not
pub remapped: bool,

/// -1 means not set.
pub jsc_stack_frame_index: i32,
/// Ref'd by C++ when `position` was computed from a JSC frame: the
/// `SourceProvider` that position indexes into, so
/// `ZigException::collect_source_lines` can slice the preview out of it.
pub source_provider: Option<NonNull<crate::SourceProvider>>,
}

impl ZigStackFrame {
Expand All @@ -40,6 +43,9 @@ impl ZigStackFrame {
pub(crate) fn deinit(&mut self) {
self.function_name.deref();
self.source_url.deref();
if let Some(provider) = self.source_provider.take() {
crate::SourceProvider::opaque_mut(provider.as_ptr()).deref();
}
}

pub(crate) fn snapshot(
Expand Down Expand Up @@ -72,7 +78,7 @@ impl ZigStackFrame {
position: ZigStackFramePosition::INVALID,
is_async: false,
remapped: false,
jsc_stack_frame_index: -1,
source_provider: None,
};

pub fn name_formatter(&self, enable_color: bool) -> NameFormatter {
Expand Down
Loading