Skip to content
This repository was archived by the owner on Nov 12, 2022. It is now read-only.

Commit ba52ca8

Browse files
author
bors-servo
authored
Auto merge of #381 - Mrowqa:capturing-js-stacks, r=jdm
Safe, straightforward API for capturing JS stacks PR for servo/servo#14987 Can someone take a look if it goes in the right direction and also look through my comments? Maybe, I have left some "stupid questions", but I wanted to post my changes and get feedback before weekend - I work from Europe, so here is middle of the night. CC: @jdm <!-- Reviewable:start --> --- This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/rust-mozjs/381) <!-- Reviewable:end -->
2 parents 0551c14 + c04d888 commit ba52ca8

File tree

4 files changed

+133
-9
lines changed

4 files changed

+133
-9
lines changed

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ cmake = "0.1"
1313
[[test]]
1414
name = "callback"
1515
[[test]]
16+
name = "capture_stack"
17+
[[test]]
1618
name = "custom_auto_rooter"
1719
[[test]]
1820
name = "custom_auto_rooter_macro"

src/conversions.rs

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,19 @@ pub unsafe fn latin1_to_string(cx: *mut JSContext, s: *mut JSString) -> String {
475475
s
476476
}
477477

478+
/// Converts a `JSString` into a `String`, regardless of used encoding.
479+
pub unsafe fn jsstr_to_string(cx: *mut JSContext, jsstr: *mut JSString) -> String {
480+
if JS_StringHasLatin1Chars(jsstr) {
481+
return latin1_to_string(cx, jsstr);
482+
}
483+
484+
let mut length = 0;
485+
let chars = JS_GetTwoByteStringCharsAndLength(cx, ptr::null(), jsstr, &mut length);
486+
assert!(!chars.is_null());
487+
let char_vec = slice::from_raw_parts(chars, length as usize);
488+
String::from_utf16_lossy(char_vec)
489+
}
490+
478491
// https://heycam.github.io/webidl/#es-USVString
479492
impl ToJSValConvertible for str {
480493
#[inline]
@@ -508,15 +521,7 @@ impl FromJSValConvertible for String {
508521
debug!("ToString failed");
509522
return Err(());
510523
}
511-
if JS_StringHasLatin1Chars(jsstr) {
512-
return Ok(latin1_to_string(cx, jsstr)).map(ConversionResult::Success);
513-
}
514-
515-
let mut length = 0;
516-
let chars = JS_GetTwoByteStringCharsAndLength(cx, ptr::null(), jsstr, &mut length);
517-
assert!(!chars.is_null());
518-
let char_vec = slice::from_raw_parts(chars, length as usize);
519-
Ok(String::from_utf16_lossy(char_vec)).map(ConversionResult::Success)
524+
Ok(jsstr_to_string(cx, jsstr)).map(ConversionResult::Success)
520525
}
521526
}
522527

src/rust.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use std::marker::PhantomData;
1818
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
1919
use consts::{JSCLASS_RESERVED_SLOTS_MASK, JSCLASS_GLOBAL_SLOT_COUNT};
2020
use consts::{JSCLASS_IS_DOMJSCLASS, JSCLASS_IS_GLOBAL};
21+
use conversions::jsstr_to_string;
2122
use jsapi;
2223
use jsapi::{AutoGCRooter, AutoIdVector, AutoObjectVector, CallArgs, CompartmentOptions, ContextFriendFields};
2324
use jsapi::{Evaluate2, Handle, HandleBase, HandleObject, HandleValue, HandleValueArray, Heap};
@@ -36,6 +37,7 @@ use jsapi::{RootedBase, RuntimeOptionsRef, SetWarningReporter, Symbol, ToBoolean
3637
use jsapi::{ToInt32Slow, ToInt64Slow, ToNumberSlow, ToStringSlow, ToUint16Slow};
3738
use jsapi::{ToUint32Slow, ToUint64Slow, ToWindowProxyIfWindow, UndefinedHandleValue};
3839
use jsapi::{Value, jsid, PerThreadDataFriendFields, PerThreadDataFriendFields_RuntimeDummy};
40+
use jsapi::{CaptureCurrentStack, BuildStackString, IsSavedFrame};
3941
use jsapi::{AutoGCRooter_jspubtd_h_unnamed_1 as AutoGCRooterTag, _vftable_CustomAutoRooter as CustomAutoRooterVFTable};
4042
use jsval::{ObjectValue, UndefinedValue};
4143
use glue::{AppendToAutoObjectVector, CallFunctionTracer, CallIdTracer, CallObjectTracer};
@@ -1449,3 +1451,56 @@ macro_rules! new_jsjitinfo_bitfield_1 {
14491451
(($slotIndex as u32) << 22u32)
14501452
}
14511453
}
1454+
1455+
pub struct CapturedJSStack<'a> {
1456+
cx: *mut JSContext,
1457+
stack: RootedGuard<'a, *mut JSObject>,
1458+
}
1459+
1460+
impl<'a> CapturedJSStack<'a> {
1461+
pub unsafe fn new(cx: *mut JSContext,
1462+
mut guard: RootedGuard<'a, *mut JSObject>,
1463+
max_frame_count: Option<u32>) -> Option<Self> {
1464+
let obj_handle = guard.handle_mut();
1465+
1466+
if !CaptureCurrentStack(cx, obj_handle, max_frame_count.unwrap_or(0)) {
1467+
None
1468+
}
1469+
else {
1470+
Some(CapturedJSStack {
1471+
cx: cx,
1472+
stack: guard,
1473+
})
1474+
}
1475+
}
1476+
1477+
pub fn as_string(&self, indent: Option<usize>) -> Option<String> {
1478+
unsafe {
1479+
let stack_handle = self.stack.handle();
1480+
rooted!(in(self.cx) let mut js_string = ptr::null_mut());
1481+
let string_handle = js_string.handle_mut();
1482+
1483+
if !IsSavedFrame(stack_handle.get()) {
1484+
return None;
1485+
}
1486+
1487+
if !BuildStackString(self.cx, stack_handle, string_handle, indent.unwrap_or(0)) {
1488+
return None;
1489+
}
1490+
1491+
Some(jsstr_to_string(self.cx, string_handle.get()))
1492+
}
1493+
}
1494+
}
1495+
1496+
#[macro_export]
1497+
macro_rules! capture_stack {
1498+
(in($cx:expr) let $name:ident = with max depth($max_frame_count:expr)) => {
1499+
rooted!(in($cx) let mut __obj = ::std::ptr::null_mut());
1500+
let $name = $crate::rust::CapturedJSStack::new($cx, __obj, Some($max_frame_count));
1501+
};
1502+
(in($cx:expr) let $name:ident ) => {
1503+
rooted!(in($cx) let mut __obj = ::std::ptr::null_mut());
1504+
let $name = $crate::rust::CapturedJSStack::new($cx, __obj, None);
1505+
}
1506+
}

tests/capture_stack.rs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/* This Source Code Form is subject to the terms of the Mozilla Public
2+
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
3+
* You can obtain one at http://mozilla.org/MPL/2.0/. */
4+
5+
#[macro_use]
6+
extern crate mozjs;
7+
extern crate libc;
8+
9+
use mozjs::jsapi::CallArgs;
10+
use mozjs::jsapi::CompartmentOptions;
11+
use mozjs::jsapi::JSAutoCompartment;
12+
use mozjs::jsapi::JSContext;
13+
use mozjs::jsapi::JS_DefineFunction;
14+
use mozjs::jsapi::JS_NewGlobalObject;
15+
use mozjs::jsapi::OnNewGlobalHookOption;
16+
use mozjs::jsapi::Value;
17+
use mozjs::jsval::UndefinedValue;
18+
use mozjs::rust::{Runtime, SIMPLE_GLOBAL_CLASS};
19+
20+
use std::ptr;
21+
22+
#[test]
23+
fn capture_stack() {
24+
let runtime = Runtime::new().unwrap();
25+
let context = runtime.cx();
26+
let h_option = OnNewGlobalHookOption::FireOnNewGlobalHook;
27+
let c_option = CompartmentOptions::default();
28+
29+
unsafe {
30+
let global = JS_NewGlobalObject(context, &SIMPLE_GLOBAL_CLASS, ptr::null_mut(), h_option, &c_option);
31+
rooted!(in(context) let global_root = global);
32+
let global = global_root.handle();
33+
let _ac = JSAutoCompartment::new(context, global.get());
34+
let function = JS_DefineFunction(context, global, b"print_stack\0".as_ptr() as *const libc::c_char,
35+
Some(print_stack), 0, 0);
36+
assert!(!function.is_null());
37+
let javascript = "
38+
function foo(arg1) {
39+
var bar = function() {
40+
print_stack();
41+
};
42+
bar();
43+
}
44+
45+
foo(\"arg1-value\");
46+
";
47+
rooted!(in(context) let mut rval = UndefinedValue());
48+
let _ = runtime.evaluate_script(global, javascript, "test.js", 0, rval.handle_mut());
49+
}
50+
}
51+
52+
unsafe extern "C" fn print_stack(context: *mut JSContext, argc: u32, vp: *mut Value) -> bool {
53+
let args = CallArgs::from_vp(vp, argc);
54+
55+
capture_stack!(in(context) let stack);
56+
let str_stack = stack.unwrap().as_string(None).unwrap();
57+
println!("{}", str_stack);
58+
assert_eq!("foo/[email protected]:3:21\n[email protected]:5:17\n@test.js:8:13\n".to_string(), str_stack);
59+
60+
args.rval().set(UndefinedValue());
61+
return true;
62+
}

0 commit comments

Comments
 (0)