mirror of
https://github.com/denoland/rusty_v8.git
synced 2024-11-21 15:04:33 -05:00
Add Isolate::set_prepare_stack_trace_callback() (#594)
Refs: https://github.com/denoland/deno/issues/7644
This commit is contained in:
parent
8809068f01
commit
64aa11e34d
3 changed files with 162 additions and 1 deletions
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -4,15 +4,20 @@ use crate::isolate_create_params::raw;
|
|||
use crate::isolate_create_params::CreateParams;
|
||||
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::CallbackScope;
|
||||
use crate::Context;
|
||||
use crate::FixedArray;
|
||||
use crate::Function;
|
||||
use crate::HandleScope;
|
||||
use crate::Local;
|
||||
use crate::Message;
|
||||
use crate::Module;
|
||||
|
@ -138,6 +143,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);
|
||||
|
@ -172,6 +194,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,
|
||||
|
@ -481,6 +507,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) {
|
||||
|
@ -900,3 +946,36 @@ impl Default for HeapStatistics {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s, F> MapFnFrom<F> for PrepareStackTraceCallback<'s>
|
||||
where
|
||||
F: UnitType
|
||||
+ Fn(
|
||||
&mut HandleScope<'s>,
|
||||
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 mut scope: CallbackScope = unsafe { CallbackScope::new(context) };
|
||||
let r = (F::get())(&mut scope, error, sites);
|
||||
unsafe { std::ptr::write(ret_ptr, &*r as *const _) };
|
||||
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 mut scope: CallbackScope = unsafe { CallbackScope::new(context) };
|
||||
let r = (F::get())(&mut scope, error, sites);
|
||||
&*r as *const _
|
||||
};
|
||||
f.to_c_fn()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4638,3 +4638,80 @@ 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>(
|
||||
scope: &mut v8::HandleScope<'s>,
|
||||
error: v8::Local<v8::Value>,
|
||||
sites: v8::Local<v8::Array>,
|
||||
) -> v8::Local<'s, v8::Value> {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue