Skip to content

Commit bb560b6

Browse files
authored
Allow iterating over the frames of a CapturedJSStack (#539)
* Allow iterating over the frames in a SavedJSStack Signed-off-by: Simon Wülker <simon.wuelker@arcor.de> * Add test for CapturedJSStack::for_each_stack_frame Signed-off-by: Simon Wülker <simon.wuelker@arcor.de> * 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 <simon.wuelker@arcor.de> --------- Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>
1 parent 8ac1958 commit bb560b6

File tree

4 files changed

+148
-23
lines changed

4 files changed

+148
-23
lines changed

mozjs/src/rust.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ use crate::jsval::ObjectValue;
6565
use crate::panic::maybe_resume_unwind;
6666
use lazy_static::lazy_static;
6767
use log::{debug, warn};
68+
use mozjs_sys::jsapi::JS::SavedFrameResult;
6869
pub use mozjs_sys::jsgc::{GCMethods, IntoHandle, IntoMutableHandle};
6970

7071
use crate::rooted;
@@ -1027,6 +1028,34 @@ impl<'a> CapturedJSStack<'a> {
10271028
Some(jsstr_to_string(self.cx, string_handle.get()))
10281029
}
10291030
}
1031+
1032+
/// Executes the provided closure for each frame on the js stack
1033+
pub fn for_each_stack_frame<F>(&self, mut f: F)
1034+
where
1035+
F: FnMut(Handle<*mut JSObject>),
1036+
{
1037+
rooted!(in(self.cx) let mut current_element = self.stack.clone());
1038+
rooted!(in(self.cx) let mut next_element = ptr::null_mut::<JSObject>());
1039+
1040+
loop {
1041+
f(current_element.handle());
1042+
1043+
unsafe {
1044+
let result = jsapi::GetSavedFrameParent(
1045+
self.cx,
1046+
ptr::null_mut(),
1047+
current_element.handle().into_handle(),
1048+
next_element.handle_mut().into_handle_mut(),
1049+
jsapi::SavedFrameSelfHosted::Include,
1050+
);
1051+
1052+
if result != SavedFrameResult::Ok || next_element.is_null() {
1053+
return;
1054+
}
1055+
}
1056+
current_element.set(next_element.get());
1057+
}
1058+
}
10301059
}
10311060

10321061
#[macro_export]

mozjs/tests/capture_stack.rs

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,24 @@ use mozjs::rust::{JSEngine, RealmOptions, Runtime, SIMPLE_GLOBAL_CLASS};
1313

1414
#[test]
1515
fn capture_stack() {
16+
unsafe extern "C" fn print_stack(context: *mut JSContext, argc: u32, vp: *mut Value) -> bool {
17+
let args = CallArgs::from_vp(vp, argc);
18+
19+
capture_stack!(in(context) let stack);
20+
let str_stack = stack
21+
.unwrap()
22+
.as_string(None, StackFormat::SpiderMonkey)
23+
.unwrap();
24+
println!("{}", str_stack);
25+
assert_eq!(
26+
"bar@test.js:3:21\nfoo@test.js:5:17\n@test.js:8:16\n".to_string(),
27+
str_stack
28+
);
29+
30+
args.rval().set(UndefinedValue());
31+
true
32+
}
33+
1634
let engine = JSEngine::init().unwrap();
1735
let runtime = Runtime::new(engine.handle());
1836
let context = runtime.cx();
@@ -59,21 +77,3 @@ fn capture_stack() {
5977
.is_ok());
6078
}
6179
}
62-
63-
unsafe extern "C" fn print_stack(context: *mut JSContext, argc: u32, vp: *mut Value) -> bool {
64-
let args = CallArgs::from_vp(vp, argc);
65-
66-
capture_stack!(in(context) let stack);
67-
let str_stack = stack
68-
.unwrap()
69-
.as_string(None, StackFormat::SpiderMonkey)
70-
.unwrap();
71-
println!("{}", str_stack);
72-
assert_eq!(
73-
"bar@test.js:3:21\nfoo@test.js:5:17\n@test.js:8:16\n".to_string(),
74-
str_stack
75-
);
76-
77-
args.rval().set(UndefinedValue());
78-
true
79-
}

mozjs/tests/iterate_stack.rs

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
use std::ptr;
2+
3+
use mozjs::{
4+
capture_stack,
5+
jsapi::{self, JSAutoRealm, JSContext, OnNewGlobalHookOption, Value},
6+
jsval::UndefinedValue,
7+
rooted,
8+
rust::{JSEngine, RealmOptions, Runtime, SIMPLE_GLOBAL_CLASS},
9+
};
10+
11+
#[test]
12+
fn iterate_stack_frames() {
13+
unsafe extern "C" fn assert_stack_state(
14+
context: *mut JSContext,
15+
_argc: u32,
16+
_vp: *mut Value,
17+
) -> bool {
18+
let mut function_names = vec![];
19+
capture_stack!(in(context) let stack);
20+
stack.unwrap().for_each_stack_frame(|frame| {
21+
rooted!(in(context) let mut result: *mut jsapi::JSString = ptr::null_mut());
22+
23+
// Get function name
24+
unsafe {
25+
jsapi::GetSavedFrameFunctionDisplayName(
26+
context,
27+
ptr::null_mut(),
28+
frame.into(),
29+
result.handle_mut().into(),
30+
jsapi::SavedFrameSelfHosted::Include,
31+
);
32+
}
33+
let buffer = if !result.is_null() {
34+
let mut buffer = vec![0; 3];
35+
jsapi::JS_EncodeStringToBuffer(context, *result, buffer.as_mut_ptr(), 3);
36+
Some(buffer.into_iter().map(|c| c as u8).collect())
37+
} else {
38+
None
39+
};
40+
function_names.push(buffer);
41+
});
42+
43+
assert_eq!(function_names.len(), 4);
44+
assert_eq!(function_names[0], Some(b"baz".to_vec()));
45+
assert_eq!(function_names[1], Some(b"bar".to_vec()));
46+
assert_eq!(function_names[2], Some(b"foo".to_vec()));
47+
assert_eq!(function_names[3], None);
48+
49+
true
50+
}
51+
52+
let engine = JSEngine::init().unwrap();
53+
let runtime = Runtime::new(engine.handle());
54+
let context = runtime.cx();
55+
#[cfg(feature = "debugmozjs")]
56+
unsafe {
57+
mozjs::jsapi::SetGCZeal(context, 2, 1);
58+
}
59+
let h_option = OnNewGlobalHookOption::FireOnNewGlobalHook;
60+
let c_option = RealmOptions::default();
61+
62+
unsafe {
63+
rooted!(in(context) let global = jsapi::JS_NewGlobalObject(
64+
context,
65+
&SIMPLE_GLOBAL_CLASS,
66+
ptr::null_mut(),
67+
h_option,
68+
&*c_option,
69+
));
70+
let _ac = JSAutoRealm::new(context, global.get());
71+
72+
let function = jsapi::JS_DefineFunction(
73+
context,
74+
global.handle().into(),
75+
c"assert_stack_state".as_ptr(),
76+
Some(assert_stack_state),
77+
0,
78+
0,
79+
);
80+
assert!(!function.is_null());
81+
82+
let javascript = "
83+
function foo() {
84+
function bar() {
85+
function baz() {
86+
assert_stack_state();
87+
}
88+
baz();
89+
}
90+
bar();
91+
}
92+
foo();
93+
";
94+
rooted!(in(context) let mut rval = UndefinedValue());
95+
assert!(runtime
96+
.evaluate_script(global.handle(), javascript, "test.js", 0, rval.handle_mut())
97+
.is_ok());
98+
}
99+
}

mozjs/tests/jsvalue.rs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,8 @@
44

55
use std::ptr;
66

7-
use mozjs::jsapi::{JSAutoRealm, JSObject, JS_NewGlobalObject, OnNewGlobalHookOption, Type};
8-
use mozjs::jsval::{
9-
BigIntValue, BooleanValue, DoubleValue, Int32Value, NullValue, ObjectValue, StringValue,
10-
UndefinedValue,
11-
};
7+
use mozjs::jsapi::{JS_NewGlobalObject, OnNewGlobalHookOption};
8+
use mozjs::jsval::{BooleanValue, DoubleValue, Int32Value, NullValue, UndefinedValue};
129
use mozjs::rooted;
1310
use mozjs::rust::{
1411
HandleObject, JSEngine, RealmOptions, RootedGuard, Runtime, SIMPLE_GLOBAL_CLASS,

0 commit comments

Comments
 (0)