Skip to content
Closed
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: 38 additions & 0 deletions src/jsc/CallFrame.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,23 @@ impl CallFrame {
CallerSrcLoc { str, line, column }
}

/// Like `get_caller_src_loc` but skips the sourcemap remap so it's cheap
/// on hot paths; call `CallerSrcLoc::remap` later when a mapped location
/// is actually needed.
pub fn get_caller_src_loc_unmapped(&self, global_this: &JSGlobalObject) -> CallerSrcLoc {
let mut str = bun_core::String::default();
let mut line: c_uint = 0;
let mut column: c_uint = 0;
Bun__CallFrame__getCallerSrcLocUnmapped(
self,
global_this,
&mut str,
&mut line,
&mut column,
);
CallerSrcLoc { str, line, column }
}

#[cfg(debug_assertions)]
pub fn describe_frame(&self) -> &ZStr {
// SAFETY: FFI returns a NUL-terminated C string with lifetime tied to the frame.
Expand Down Expand Up @@ -262,6 +279,14 @@ pub struct CallerSrcLoc {
pub column: c_uint,
}

impl CallerSrcLoc {
/// Apply sourcemap remapping in place. No-op for an already-mapped
/// location (the remap is idempotent for URLs with no saved mapping).
pub fn remap(&mut self, global_this: &JSGlobalObject) {
Bun__remapSrcLoc(global_this, &mut self.str, &mut self.line, &mut self.column);
}
}

pub struct Iterator<'a> {
pub rest: &'a [JSValue],
}
Expand Down Expand Up @@ -416,6 +441,19 @@ unsafe extern "C" {
out_line: &mut c_uint,
out_column: &mut c_uint,
);
safe fn Bun__CallFrame__getCallerSrcLocUnmapped(
cf: &CallFrame,
global: &JSGlobalObject,
out_str: &mut bun_core::String,
out_line: &mut c_uint,
out_column: &mut c_uint,
);
safe fn Bun__remapSrcLoc(
global: &JSGlobalObject,
io_str: &mut bun_core::String,
io_line: &mut c_uint,
io_column: &mut c_uint,
);
#[cfg(debug_assertions)]
fn Bun__CallFrame__describeFrame(cf: *const CallFrame) -> *const core::ffi::c_char;
}
37 changes: 35 additions & 2 deletions src/jsc/bindings/bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6090,7 +6090,7 @@ CPP_DECL bool Bun__CallFrame__isFromBunMain(JSC::CallFrame* callFrame, JSC::VM*
return source.string() == "builtin://bun/main"_s;
}

CPP_DECL void Bun__CallFrame__getCallerSrcLoc(JSC::CallFrame* callFrame, JSC::JSGlobalObject* globalObject, BunString* outSourceURL, unsigned int* outLine, unsigned int* outColumn)
static ALWAYS_INLINE void getCallerSrcLocImpl(JSC::CallFrame* callFrame, JSC::JSGlobalObject* globalObject, BunString* outSourceURL, unsigned int* outLine, unsigned int* outColumn, bool remap)
{
auto& vm = JSC::getVM(globalObject);
JSC::LineColumn lineColumn;
Expand All @@ -6114,7 +6114,7 @@ CPP_DECL void Bun__CallFrame__getCallerSrcLoc(JSC::CallFrame* callFrame, JSC::JS
return WTF::IterationStatus::Continue;
});

if (!sourceURL.isEmpty() and lineColumn.line > 0) {
if (remap and !sourceURL.isEmpty() and lineColumn.line > 0) {
OrdinalNumber originalLine = OrdinalNumber::fromOneBasedInt(lineColumn.line);
OrdinalNumber originalColumn = OrdinalNumber::fromOneBasedInt(lineColumn.column);

Expand All @@ -6134,6 +6134,39 @@ CPP_DECL void Bun__CallFrame__getCallerSrcLoc(JSC::CallFrame* callFrame, JSC::JS
*outColumn = lineColumn.column;
}

CPP_DECL void Bun__CallFrame__getCallerSrcLoc(JSC::CallFrame* callFrame, JSC::JSGlobalObject* globalObject, BunString* outSourceURL, unsigned int* outLine, unsigned int* outColumn)
{
getCallerSrcLocImpl(callFrame, globalObject, outSourceURL, outLine, outColumn, true);
}

// Same as above but skips the sourcemap remap (two mutex acquisitions + hashmap
// lookup + VLQ binary search). Used on the `expect()` hot path, where the
// caller defers remapping to the rare inline-snapshot writeback path.
CPP_DECL void Bun__CallFrame__getCallerSrcLocUnmapped(JSC::CallFrame* callFrame, JSC::JSGlobalObject* globalObject, BunString* outSourceURL, unsigned int* outLine, unsigned int* outColumn)
{
getCallerSrcLocImpl(callFrame, globalObject, outSourceURL, outLine, outColumn, false);
}

// Sourcemap-remap a (url, line, col) captured by `getCallerSrcLocUnmapped`.
// `*ioSourceURL` is in/out +1: caller passes a +1, this may swap it for a new
// +1, and the caller releases whichever comes back.
CPP_DECL void Bun__remapSrcLoc(JSC::JSGlobalObject* globalObject, BunString* ioSourceURL, unsigned int* ioLine, unsigned int* ioColumn)
{
if (ioSourceURL->tag == BunStringTag::Empty or ioSourceURL->tag == BunStringTag::Dead or *ioLine == 0)
return;

ZigStackFrame remappedFrame = {};
remappedFrame.position.line_zero_based = OrdinalNumber::fromOneBasedInt(*ioLine).zeroBasedInt();
remappedFrame.position.column_zero_based = OrdinalNumber::fromOneBasedInt(*ioColumn).zeroBasedInt();
remappedFrame.source_url = *ioSourceURL;

Bun__remapStackFramePositions(Bun::vm(globalObject), &remappedFrame, 1);

*ioSourceURL = remappedFrame.source_url;
*ioLine = OrdinalNumber::fromZeroBasedInt(remappedFrame.position.line_zero_based).oneBasedInt();
*ioColumn = OrdinalNumber::fromZeroBasedInt(remappedFrame.position.column_zero_based).oneBasedInt();
}

Comment thread
coderabbitai[bot] marked this conversation as resolved.
extern "C" EncodedJSValue Bun__JSObject__getCodePropertyVMInquiry(JSC::JSGlobalObject* global, JSC::JSObject* object)
{
if (!object) [[unlikely]] {
Expand Down
38 changes: 38 additions & 0 deletions src/runtime/test_runner/expect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ pub struct Expect {
pub flags: Cell<Flags>,
pub parent: Option<bun_test::RefDataPtr>,
pub custom_label: bun_core::String,
// `expect(...)` call site, captured before a tail-position matcher (e.g.
// `return expect(v).toMatchInlineSnapshot()`) loses this frame to JSC's
// proper tail calls. Un-remapped; see `inline_snapshot`.
pub expect_src_file: bun_core::String,
pub expect_src_line: core::ffi::c_uint,
pub expect_src_col: core::ffi::c_uint,
}


Expand Down Expand Up @@ -688,6 +694,7 @@ impl Expect {
#[allow(clippy::boxed_local)]
pub fn finalize(mut self: Box<Self>) {
self.custom_label.deref();
self.expect_src_file.deref();
// RefDataPtr = RefPtr<RefData> has NO `Drop` impl (src/ptr/ref_count.rs)
// so the Box drop below would leak the +1 — release explicitly.
if let Some(parent) = self.parent.take() {
Expand Down Expand Up @@ -724,10 +731,18 @@ impl Expect {
// error path between ref creation and the wrapper taking ownership; from
// then on `Expect::finalize` derefs `parent` (RefDataPtr has no Drop).

// Capture the `expect(...)` call site now, before a tail-position
// matcher loses this frame. Unmapped to keep the per-`expect()` cost
// low; `inline_snapshot` remaps on demand.
let expect_srcloc = callframe.get_caller_src_loc_unmapped(global_this);

let expect = Expect {
flags: Cell::new(Flags::default()),
custom_label,
parent: active_execution_entry_ref,
expect_src_file: expect_srcloc.str,
expect_src_line: expect_srcloc.line,
expect_src_col: expect_srcloc.column,
};
// `JsClass::to_js` boxes `self` and hands the pointer to `${T}__create`.
let expect_js_value = expect.to_js(global_this);
Expand Down Expand Up @@ -1149,10 +1164,33 @@ impl Expect {
);
}

// Fallback: the `expect(...)` call site (stored un-remapped). Remap
// here; `remap` is in/out +1 on `str`, so give it a fresh ref and
// release whatever comes back. Only usable when it's the same file.
let (fallback_line, fallback_col) = {
let mut expect_loc = bun_jsc::call_frame::CallerSrcLoc {
str: this.expect_src_file.dupe_ref(),
line: this.expect_src_line,
column: this.expect_src_col,
};
expect_loc.remap(global_this);
let _expect_loc_str_guard = bun_core::OwnedString::new(expect_loc.str);
if expect_loc.str.eql_utf8(fget_source_path_text) {
(
core::ffi::c_ulong::from(expect_loc.line),
core::ffi::c_ulong::from(expect_loc.column),
)
} else {
(0, 0)
}
};

// 2. save to write later
runner.snapshots.add_inline_snapshot_to_write(file_id, super::snapshot::InlineSnapshotToWrite {
line: core::ffi::c_ulong::from(srcloc.line),
col: core::ffi::c_ulong::from(srcloc.column),
fallback_line,
fallback_col,
value: core::mem::take(&mut pretty_value).into_boxed_slice(),
has_matchers: property_matchers.is_some(),
is_added: result.is_none(),
Expand Down
Loading
Loading