Skip to content

Commit

Permalink
Add Isolate::set_prepare_stack_trace_callback()
Browse files Browse the repository at this point in the history
  • Loading branch information
bnoordhuis committed Feb 7, 2021
1 parent 5cf215e commit 8a78355
Show file tree
Hide file tree
Showing 3 changed files with 160 additions and 0 deletions.
5 changes: 5 additions & 0 deletions src/binding.cc
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,11 @@ void v8__Isolate__RequestInterrupt(v8::Isolate* isolate,
isolate->RequestInterrupt(callback, data);
}

void v8__Isolate__SetPrepareStackTraceCallback(
v8::Isolate* isolate, v8::PrepareStackTraceCallback callback) {
isolate->SetPrepareStackTraceCallback(callback);
}

void v8__Isolate__SetPromiseHook(v8::Isolate* isolate, v8::PromiseHook hook) {
isolate->SetPromiseHook(hook);
}
Expand Down
76 changes: 76 additions & 0 deletions src/isolate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,14 @@ use crate::promise::PromiseRejectMessage;
use crate::scope::data::ScopeData;
use crate::scope::HandleScope;
use crate::support::BuildTypeIdHasher;
use crate::support::MapFnFrom;
use crate::support::MapFnTo;
use crate::support::Opaque;
use crate::support::ToCFn;
use crate::support::UnitType;
use crate::wasm::trampoline;
use crate::wasm::WasmStreaming;
use crate::Array;
use crate::Context;
use crate::FixedArray;
use crate::Function;
Expand Down Expand Up @@ -138,6 +142,23 @@ pub type OOMErrorCallback =
#[derive(Debug)]
pub struct HeapStatistics([usize; 16]);

// Windows x64 ABI: MaybeLocal<Value> returned on the stack.
#[cfg(target_os = "windows")]
pub type PrepareStackTraceCallback<'s> = extern "C" fn(
*mut *const Value,
Local<'s, Context>,
Local<'s, Value>,
Local<'s, Array>,
) -> *mut *const Value;

// System V ABI: MaybeLocal<Value> returned in a register.
#[cfg(not(target_os = "windows"))]
pub type PrepareStackTraceCallback<'s> = extern "C" fn(
Local<'s, Context>,
Local<'s, Value>,
Local<'s, Array>,
) -> *const Value;

extern "C" {
fn v8__Isolate__New(params: *const raw::CreateParams) -> *mut Isolate;
fn v8__Isolate__Dispose(this: *mut Isolate);
Expand Down Expand Up @@ -172,6 +193,10 @@ extern "C" {
isolate: *mut Isolate,
callback: OOMErrorCallback,
);
fn v8__Isolate__SetPrepareStackTraceCallback(
isolate: *mut Isolate,
callback: PrepareStackTraceCallback,
);
fn v8__Isolate__SetPromiseHook(isolate: *mut Isolate, hook: PromiseHook);
fn v8__Isolate__SetPromiseRejectCallback(
isolate: *mut Isolate,
Expand Down Expand Up @@ -481,6 +506,26 @@ impl Isolate {
unsafe { v8__Isolate__AddMessageListener(self, callback) }
}

/// This specifies the callback called when the stack property of Error
/// is accessed.
///
/// PrepareStackTraceCallback is called when the stack property of an error is
/// first accessed. The return value will be used as the stack value. If this
/// callback is registed, the |Error.prepareStackTrace| API will be disabled.
/// |sites| is an array of call sites, specified in
/// https://v8.dev/docs/stack-trace-api
pub fn set_prepare_stack_trace_callback<'s>(
&mut self,
callback: impl MapFnTo<PrepareStackTraceCallback<'s>>,
) {
// Note: the C++ API returns a MaybeLocal but V8 asserts at runtime when
// it's empty. That is, you can't return None and that's why the Rust API
// expects Local<Value> instead of Option<Local<Value>>.
unsafe {
v8__Isolate__SetPrepareStackTraceCallback(self, callback.map_fn_to())
};
}

/// Set the PromiseHook callback for various promise lifecycle
/// events.
pub fn set_promise_hook(&mut self, hook: PromiseHook) {
Expand Down Expand Up @@ -900,3 +945,34 @@ impl Default for HeapStatistics {
}
}
}

impl<'s, F> MapFnFrom<F> for PrepareStackTraceCallback<'s>
where
F: UnitType
+ Fn(
Local<'s, Context>,
Local<'s, Value>,
Local<'s, Array>,
) -> Local<'s, Value>,
{
// Windows x64 ABI: MaybeLocal<Value> returned on the stack.
#[cfg(target_os = "windows")]
fn mapping() -> Self {
let f = |ret_ptr, context, error, sites| {
let r = (F::get())(context, error, sites);
unsafe { std::ptr::write(ret_ptr, r) };
ret_ptr
};
f.to_c_fn()
}

// System V ABI: MaybeLocal<Value> returned in a register.
#[cfg(not(target_os = "windows"))]
fn mapping() -> Self {
let f = |context, error, sites| {
let r = (F::get())(context, error, sites);
&*r as *const _
};
f.to_c_fn()
}
}
79 changes: 79 additions & 0 deletions tests/test_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4638,3 +4638,82 @@ fn oom_callback() {
// Don't attempt to trigger the OOM callback since we don't have a safe way to
// recover from it.
}

#[test]
fn prepare_stack_trace_callback() {
thread_local! {
static SITES: RefCell<Option<v8::Global<v8::Array>>> = RefCell::new(None);
}

let script = r#"
function g() { throw new Error("boom") }
function f() { g() }
try {
f()
} catch (e) {
e.stack
}
"#;

let _setup_guard = setup();
let isolate = &mut v8::Isolate::new(Default::default());
isolate.set_prepare_stack_trace_callback(callback);

let scope = &mut v8::HandleScope::new(isolate);
let context = v8::Context::new(scope);
let scope = &mut v8::ContextScope::new(scope, context);
let scope = &mut v8::TryCatch::new(scope);

let result = eval(scope, script).unwrap();
assert_eq!(Some(42), result.uint32_value(scope));

let sites = SITES.with(|slot| slot.borrow_mut().take()).unwrap();
let sites = v8::Local::new(scope, sites);
assert_eq!(3, sites.length());

let scripts = [
r#"
if ("g" !== site.getFunctionName()) throw "fail";
if (2 !== site.getLineNumber()) throw "fail";
"#,
r#"
if ("f" !== site.getFunctionName()) throw "fail";
if (3 !== site.getLineNumber()) throw "fail";
"#,
r#"
if (null !== site.getFunctionName()) throw "fail";
if (5 !== site.getLineNumber()) throw "fail";
"#,
];

let global = context.global(scope);
let name = v8::String::new(scope, "site").unwrap().into();

for i in 0..3 {
let site = sites.get_index(scope, i).unwrap();
global.set(scope, name, site).unwrap();
let script = scripts[i as usize];
let result = eval(scope, script);
assert!(result.is_some());
}

fn callback<'s>(
context: v8::Local<'s, v8::Context>,
error: v8::Local<v8::Value>,
sites: v8::Local<v8::Array>,
) -> v8::Local<'s, v8::Value> {
let scope = &mut unsafe { v8::CallbackScope::new(context) };

let message = v8::Exception::create_message(scope, error);
let actual = message.get(scope).to_rust_string_lossy(scope);
assert_eq!(actual, "Uncaught Error: boom");

SITES.with(|slot| {
let mut slot = slot.borrow_mut();
assert!(slot.is_none());
*slot = Some(v8::Global::new(scope, sites));
});

v8::Integer::new(scope, 42).into()
}
}

0 comments on commit 8a78355

Please sign in to comment.