diff --git a/src/binding.cc b/src/binding.cc index 40f215e081..6150af600d 100644 --- a/src/binding.cc +++ b/src/binding.cc @@ -1658,6 +1658,12 @@ const v8::Value* v8__ReturnValue__Get(const v8::ReturnValue& self) { return local_to_ptr(self.Get()); } +// Note: StackTraceOptions is deprecated, kDetailed is always used +const v8::StackTrace* v8__StackTrace__CurrentStackTrace(v8::Isolate* isolate, + int frame_limit) { + return local_to_ptr(v8::StackTrace::CurrentStackTrace(isolate, frame_limit)); +} + int v8__StackTrace__GetFrameCount(const v8::StackTrace& self) { return self.GetFrameCount(); } diff --git a/src/exception.rs b/src/exception.rs index 9606c2c06c..7fa65c9d14 100644 --- a/src/exception.rs +++ b/src/exception.rs @@ -1,5 +1,7 @@ #![allow(non_snake_case)] +use std::convert::TryInto; + use crate::isolate::Isolate; use crate::support::int; use crate::Context; @@ -32,6 +34,10 @@ extern "C" { fn v8__Message__IsOpaque(this: *const Message) -> bool; fn v8__Message__GetStackTrace(this: *const Message) -> *const StackTrace; + fn v8__StackTrace__CurrentStackTrace( + isolate: *mut Isolate, + frame_limit: int, + ) -> *const StackTrace; fn v8__StackTrace__GetFrameCount(this: *const StackTrace) -> int; fn v8__StackTrace__GetFrame( this: *const StackTrace, @@ -67,6 +73,19 @@ extern "C" { } impl StackTrace { + /// Grab a snapshot of the current JavaScript execution stack. + pub fn current_stack_trace<'s>( + scope: &mut HandleScope<'s>, + frame_limit: usize, + ) -> Option> { + let frame_limit = frame_limit.try_into().ok()?; + unsafe { + scope.cast_local(|sd| { + v8__StackTrace__CurrentStackTrace(sd.get_isolate_ptr(), frame_limit) + }) + } + } + /// Returns the number of StackFrames. pub fn get_frame_count(&self) -> usize { unsafe { v8__StackTrace__GetFrameCount(self) as usize } diff --git a/tests/test_api.rs b/tests/test_api.rs index 8154dd7e95..ffb249a1d7 100644 --- a/tests/test_api.rs +++ b/tests/test_api.rs @@ -5675,3 +5675,50 @@ fn backing_store_from_empty_boxed_slice() { .make_shared(); let _ = v8::ArrayBuffer::with_backing_store(&mut scope, &store); } + +#[test] +fn current_stack_trace() { + // Setup isolate + let isolate = &mut v8::Isolate::new(Default::default()); + let scope = &mut v8::HandleScope::new(isolate); + let context = v8::Context::new(scope); + let scope = &mut v8::ContextScope::new(scope, context); + + // A simple JS-facing function that returns its call depth, max of 5 + fn call_depth( + scope: &mut v8::HandleScope, + _args: v8::FunctionCallbackArguments, + mut rv: v8::ReturnValue, + ) { + let stack = v8::StackTrace::current_stack_trace(scope, 5).unwrap(); + let count = stack.get_frame_count(); + rv.set(v8::Integer::new(scope, count as i32).into()) + } + + let key = v8::String::new(scope, "callDepth").unwrap(); + let tmpl = v8::FunctionTemplate::new(scope, call_depth); + let func = tmpl.get_function(scope).unwrap(); + let global = context.global(scope); + global.set(scope, key.into(), func.into()); + + let top_level = eval(scope, "callDepth()") + .unwrap() + .uint32_value(scope) + .unwrap(); + assert_eq!(top_level, 1); + + let nested = eval(scope, "(_ => (_ => callDepth())())()") + .unwrap() + .uint32_value(scope) + .unwrap(); + assert_eq!(nested, 3); + + let too_deep = eval( + scope, + "(_ => (_ => (_ => (_ => (_ => (_ => (_ => callDepth())())())())())())())()", + ) + .unwrap() + .uint32_value(scope) + .unwrap(); + assert_eq!(too_deep, 5); +}