Skip to content

Commit

Permalink
Allow iterating over the frames of a CapturedJSStack (#539)
Browse files Browse the repository at this point in the history
* Allow iterating over the frames in a SavedJSStack

Signed-off-by: Simon Wülker <[email protected]>

* Add test for CapturedJSStack::for_each_stack_frame

Signed-off-by: Simon Wülker <[email protected]>

* Move stack iteration test into its own file

This prevents other tests from interfering with the js engine
initialization

Signed-off-by: Simon Wülker <[email protected]>

---------

Signed-off-by: Simon Wülker <[email protected]>
  • Loading branch information
simonwuelker authored Dec 16, 2024
1 parent 8ac1958 commit bb560b6
Show file tree
Hide file tree
Showing 4 changed files with 148 additions and 23 deletions.
29 changes: 29 additions & 0 deletions mozjs/src/rust.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ use crate::jsval::ObjectValue;
use crate::panic::maybe_resume_unwind;
use lazy_static::lazy_static;
use log::{debug, warn};
use mozjs_sys::jsapi::JS::SavedFrameResult;
pub use mozjs_sys::jsgc::{GCMethods, IntoHandle, IntoMutableHandle};

use crate::rooted;
Expand Down Expand Up @@ -1027,6 +1028,34 @@ impl<'a> CapturedJSStack<'a> {
Some(jsstr_to_string(self.cx, string_handle.get()))
}
}

/// Executes the provided closure for each frame on the js stack
pub fn for_each_stack_frame<F>(&self, mut f: F)
where
F: FnMut(Handle<*mut JSObject>),
{
rooted!(in(self.cx) let mut current_element = self.stack.clone());
rooted!(in(self.cx) let mut next_element = ptr::null_mut::<JSObject>());

loop {
f(current_element.handle());

unsafe {
let result = jsapi::GetSavedFrameParent(
self.cx,
ptr::null_mut(),
current_element.handle().into_handle(),
next_element.handle_mut().into_handle_mut(),
jsapi::SavedFrameSelfHosted::Include,
);

if result != SavedFrameResult::Ok || next_element.is_null() {
return;
}
}
current_element.set(next_element.get());
}
}
}

#[macro_export]
Expand Down
36 changes: 18 additions & 18 deletions mozjs/tests/capture_stack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,24 @@ use mozjs::rust::{JSEngine, RealmOptions, Runtime, SIMPLE_GLOBAL_CLASS};

#[test]
fn capture_stack() {
unsafe extern "C" fn print_stack(context: *mut JSContext, argc: u32, vp: *mut Value) -> bool {
let args = CallArgs::from_vp(vp, argc);

capture_stack!(in(context) let stack);
let str_stack = stack
.unwrap()
.as_string(None, StackFormat::SpiderMonkey)
.unwrap();
println!("{}", str_stack);
assert_eq!(
"[email protected]:3:21\n[email protected]:5:17\n@test.js:8:16\n".to_string(),
str_stack
);

args.rval().set(UndefinedValue());
true
}

let engine = JSEngine::init().unwrap();
let runtime = Runtime::new(engine.handle());
let context = runtime.cx();
Expand Down Expand Up @@ -59,21 +77,3 @@ fn capture_stack() {
.is_ok());
}
}

unsafe extern "C" fn print_stack(context: *mut JSContext, argc: u32, vp: *mut Value) -> bool {
let args = CallArgs::from_vp(vp, argc);

capture_stack!(in(context) let stack);
let str_stack = stack
.unwrap()
.as_string(None, StackFormat::SpiderMonkey)
.unwrap();
println!("{}", str_stack);
assert_eq!(
"[email protected]:3:21\n[email protected]:5:17\n@test.js:8:16\n".to_string(),
str_stack
);

args.rval().set(UndefinedValue());
true
}
99 changes: 99 additions & 0 deletions mozjs/tests/iterate_stack.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
use std::ptr;

use mozjs::{
capture_stack,
jsapi::{self, JSAutoRealm, JSContext, OnNewGlobalHookOption, Value},
jsval::UndefinedValue,
rooted,
rust::{JSEngine, RealmOptions, Runtime, SIMPLE_GLOBAL_CLASS},
};

#[test]
fn iterate_stack_frames() {
unsafe extern "C" fn assert_stack_state(
context: *mut JSContext,
_argc: u32,
_vp: *mut Value,
) -> bool {
let mut function_names = vec![];
capture_stack!(in(context) let stack);
stack.unwrap().for_each_stack_frame(|frame| {
rooted!(in(context) let mut result: *mut jsapi::JSString = ptr::null_mut());

// Get function name
unsafe {
jsapi::GetSavedFrameFunctionDisplayName(
context,
ptr::null_mut(),
frame.into(),
result.handle_mut().into(),
jsapi::SavedFrameSelfHosted::Include,
);
}
let buffer = if !result.is_null() {
let mut buffer = vec![0; 3];
jsapi::JS_EncodeStringToBuffer(context, *result, buffer.as_mut_ptr(), 3);
Some(buffer.into_iter().map(|c| c as u8).collect())
} else {
None
};
function_names.push(buffer);
});

assert_eq!(function_names.len(), 4);
assert_eq!(function_names[0], Some(b"baz".to_vec()));
assert_eq!(function_names[1], Some(b"bar".to_vec()));
assert_eq!(function_names[2], Some(b"foo".to_vec()));
assert_eq!(function_names[3], None);

true
}

let engine = JSEngine::init().unwrap();
let runtime = Runtime::new(engine.handle());
let context = runtime.cx();
#[cfg(feature = "debugmozjs")]
unsafe {
mozjs::jsapi::SetGCZeal(context, 2, 1);
}
let h_option = OnNewGlobalHookOption::FireOnNewGlobalHook;
let c_option = RealmOptions::default();

unsafe {
rooted!(in(context) let global = jsapi::JS_NewGlobalObject(
context,
&SIMPLE_GLOBAL_CLASS,
ptr::null_mut(),
h_option,
&*c_option,
));
let _ac = JSAutoRealm::new(context, global.get());

let function = jsapi::JS_DefineFunction(
context,
global.handle().into(),
c"assert_stack_state".as_ptr(),
Some(assert_stack_state),
0,
0,
);
assert!(!function.is_null());

let javascript = "
function foo() {
function bar() {
function baz() {
assert_stack_state();
}
baz();
}
bar();
}
foo();
";
rooted!(in(context) let mut rval = UndefinedValue());
assert!(runtime
.evaluate_script(global.handle(), javascript, "test.js", 0, rval.handle_mut())
.is_ok());
}
}
7 changes: 2 additions & 5 deletions mozjs/tests/jsvalue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,8 @@

use std::ptr;

use mozjs::jsapi::{JSAutoRealm, JSObject, JS_NewGlobalObject, OnNewGlobalHookOption, Type};
use mozjs::jsval::{
BigIntValue, BooleanValue, DoubleValue, Int32Value, NullValue, ObjectValue, StringValue,
UndefinedValue,
};
use mozjs::jsapi::{JS_NewGlobalObject, OnNewGlobalHookOption};
use mozjs::jsval::{BooleanValue, DoubleValue, Int32Value, NullValue, UndefinedValue};
use mozjs::rooted;
use mozjs::rust::{
HandleObject, JSEngine, RealmOptions, RootedGuard, Runtime, SIMPLE_GLOBAL_CLASS,
Expand Down

0 comments on commit bb560b6

Please sign in to comment.