-
Notifications
You must be signed in to change notification settings - Fork 117
Safe, straightforward API for capturing JS stacks #381
Conversation
src/rust.rs
Outdated
// do we want to do the actual logic of conversion in this method or maybe in constructor and just return cached result? | ||
unsafe { | ||
let stack_handle = self.stack.handle(); | ||
let nullptr = 0 as *const ::std::os::raw::c_char; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: rust has ptr::null()
and is_null
, so you should use them instead probably.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good work!
src/rust.rs
Outdated
} | ||
|
||
impl<'a> CapturedJSStack<'a> { | ||
pub fn new(cx: *mut JSContext, mut guard: RootedGuard<'a, *mut JSObject>, max_frame_count: u32) -> Option<Self> { // or maybe another name, for example "capture"? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs to be unsafe
, because it accepts a raw pointer argument. I think new
is a fine name. However, let's make max_frame_count an Option value and use unwrap_or(0)
to make it easier to use.
src/rust.rs
Outdated
|
||
impl<'a> CapturedJSStack<'a> { | ||
pub fn new(cx: *mut JSContext, mut guard: RootedGuard<'a, *mut JSObject>, max_frame_count: u32) -> Option<Self> { // or maybe another name, for example "capture"? | ||
let obj_handle = guard.handle_mut(); // if nullptr, return None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if obj_handle.get().is_null()
src/rust.rs
Outdated
// TODO where errors can occur? some example of non trivial error handling? | ||
// TODO how to write tests for this code? Including the problem of obtaining some stack trace | ||
// do we want to do the actual logic of conversion in this method or maybe in constructor and just return cached result? | ||
unsafe { // since almost everything here is unsafe, is it okay to wrap it like this? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes.
src/rust.rs
Outdated
} | ||
|
||
// wanted somehow to just get the utf-16 string and use String::from_utf16_lossy | ||
// but JS_GetTwoByteStringCharsAndLength requires some object of strange type (AutoCheckCannotGC) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lines 515 to 519 in 2b49303
let mut length = 0; | |
let chars = JS_GetTwoByteStringCharsAndLength(cx, ptr::null(), jsstr, &mut length); | |
assert!(!chars.is_null()); | |
let char_vec = slice::from_raw_parts(chars, length as usize); | |
Ok(String::from_utf16_lossy(char_vec)).map(ConversionResult::Success) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you sure about passing null pointer to the JS_GetTwoByteStringCharsAndLength
? In C++ it requires a reference: https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/JSAPI_Reference/JS_GetLatin1StringCharsAndLength. Maybe it doesn't have any internal fields, but isn't it undefined behavior?
I read the comment before definition (https://dxr.mozilla.org/mozilla-central/source/js/public/GCAPI.h?q=AutoCheckCannotGC&redirect_type=direct#852-876), but I am still not sure when we can use it. Does it mean that we can use it when we have rooted all the objects whose handles we are passing to the function?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you look at where the nogc
parameter is actually used in the function body, you'll see that it's passed as a parameter to twoByteChars, and that function doesn't do anything with it, so I'm quite confident that there's no UB going on here.
In fact, the comment before the definition of AutoCheckCannotGC states that this is only used for static rooting hazard analysis, so I don't see how it can go wrong when you send in a null pointer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@KiChjang thank you for the explanation. I wasn't sure.
src/rust.rs
Outdated
} | ||
|
||
let mut size = 0 as usize; | ||
while *str_contents.offset(size as isize) != 0 { // what is some better way of calculating length on raw pointers? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For future reference, CStr::from_ptr is useful here.
src/rust.rs
Outdated
// do we want to do the actual logic of conversion in this method or maybe in constructor and just return cached result? | ||
unsafe { // since almost everything here is unsafe, is it okay to wrap it like this? | ||
let stack_handle = self.stack.handle(); | ||
rooted!(in(self.cx) let mut js_string = JS_NewStringCopyZ(self.cx, ptr::null())); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should be possible to just use rooted!(in(self.cx) let mut js_string);
.
src/rust.rs
Outdated
}) | ||
} | ||
|
||
pub fn as_string(&self, indent: usize) -> Option<String> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use an Option for indent, and unwrap_or(0).
src/rust.rs
Outdated
#[macro_export] | ||
macro_rules! capture_stack { | ||
($cx:expr, $max_frame_count:expr) => { | ||
rooted!(in($cx) let mut __obj = JS_NewPlainObject($cx)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should be able to do rooted!(in($cx) let mut __obj);
instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right now, we can't. Please take a look at the definition of the macro: https://github.com/servo/rust-mozjs/blob/master/src/rust.rs#L454-L464.
Should I expand it and if yes, would you prefer to do it in this PR or separate one? Another problem is that JS_NewPlainObject
returns *mut JSObject
and JS_NewCopyStringZ
, which I used somewhere else, returns *mut JSString
. So, what should be the type of the new binding created within the macro?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, my mistake. It should be possible to use a null pointer instead, though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, if some function expects a mutable handle for an output argument, we can just pass a handle to a null pointer? Did I get it right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. The mutable handle allows the function to initialize it with a meaningful value.
src/rust.rs
Outdated
} | ||
|
||
pub fn as_string(&self, indent: usize) -> Option<String> { | ||
// TODO where errors can occur? some example of non trivial error handling? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should verify that IsSavedFrame returns true.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't it guaranted by CaptureCurrentStack after returning true
? Is it bad to assume that it will not change since we own the RootedGuard
to it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, but if a consumer does not use the macros and overwrites the value in the Rooted<*mut JSObject>
before calling as_string, it won't work. This is probably an instance where we would get a false
value from the code in as_string.
src/rust.rs
Outdated
|
||
pub fn as_string(&self, indent: usize) -> Option<String> { | ||
// TODO where errors can occur? some example of non trivial error handling? | ||
// TODO how to write tests for this code? Including the problem of obtaining some stack trace |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can create an automated test that evaluates some JS that creates several JS stack frames (by defining and invoking functions), then invokes a function that calls into Rust and creates a stack trace. https://github.com/servo/rust-mozjs/blob/master/tests/callback.rs is a good model for this.
Now it should be almost complete. When I use |
@jdm I have just removed the commented out code with So, since I am really confused what is going on, I have left the version with JS_EncodeStringToUTF8 since it works as expected. The drawback of this is that we have this one extra We can merge this, if it is okay with you. And I am willing to learn more about what is going on internally, what these functions are supposed to do and so on :) |
@jdm Travis failed to have installed nightly toolchain, but the code actually works and passes the tests. |
With respect to the unexpected output of JS_GetTwoByteStringCharsAndLength, that sounds like what the code at https://github.com/Mrowqa/rust-mozjs/blob/e654bb6c238b659e85ac087adf6568eda20e8648/src/conversions.rs#L510-L513 is intended to address. Perhaps we can try extracting https://github.com/Mrowqa/rust-mozjs/blob/e654bb6c238b659e85ac087adf6568eda20e8648/src/conversions.rs#L511-L519 into a method that we can reuse here? |
☔ The latest upstream changes (presumably #382) made this pull request unmergeable. Please resolve the merge conflicts. |
…to capturing-js-stacks
@bors-servo: r+ |
📌 Commit c04d888 has been approved by |
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 -->
☀️ Test successful - status-appveyor, status-travis |
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
This change is