Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Safe, straightforward API for capturing JS stacks #381

Merged
merged 12 commits into from Dec 2, 2017

Conversation

@mrowqa
Copy link
Contributor

mrowqa commented Nov 24, 2017

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 Reviewable

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;

This comment has been minimized.

@emilio

emilio Nov 25, 2017

Member

nit: rust has ptr::null() and is_null, so you should use them instead probably.

@mrowqa
Copy link
Contributor Author

mrowqa commented Nov 27, 2017

Thank you, @emilio.

I met with @Xanewok and the rest of guys and we came up with the idea of capture_stack!(cx) macro. I have also left some other comments waiting for answers.

Copy link
Member

jdm left a comment

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"?

This comment has been minimized.

@jdm

jdm Nov 27, 2017

Member

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

This comment has been minimized.

@jdm

jdm Nov 27, 2017

Member

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?

This comment has been minimized.

@jdm

jdm Nov 27, 2017

Member

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)

This comment has been minimized.

@jdm

jdm Nov 27, 2017

Member

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)

This comment has been minimized.

@mrowqa

mrowqa Nov 28, 2017

Author Contributor

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?

This comment has been minimized.

@KiChjang

KiChjang Nov 28, 2017

Member

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.

This comment has been minimized.

@mrowqa

mrowqa Nov 28, 2017

Author Contributor

@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?

This comment has been minimized.

@jdm

jdm Nov 27, 2017

Member

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()));

This comment has been minimized.

@jdm

jdm Nov 27, 2017

Member

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> {

This comment has been minimized.

@jdm

jdm Nov 27, 2017

Member

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));

This comment has been minimized.

@jdm

jdm Nov 27, 2017

Member

We should be able to do rooted!(in($cx) let mut __obj); instead.

This comment has been minimized.

@mrowqa

mrowqa Nov 28, 2017

Author Contributor

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?

This comment has been minimized.

@jdm

jdm Nov 28, 2017

Member

Oh, my mistake. It should be possible to use a null pointer instead, though.

This comment has been minimized.

@mrowqa

mrowqa Nov 28, 2017

Author Contributor

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?

This comment has been minimized.

@jdm

jdm Nov 28, 2017

Member

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?

This comment has been minimized.

@jdm

jdm Nov 27, 2017

Member

We should verify that IsSavedFrame returns true.

This comment has been minimized.

@mrowqa

mrowqa Nov 28, 2017

Author Contributor

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?

This comment has been minimized.

@jdm

jdm Nov 28, 2017

Member

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

This comment has been minimized.

@jdm

jdm Nov 27, 2017

Member

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.

@mrowqa
Copy link
Contributor Author

mrowqa commented Nov 29, 2017

Now it should be almost complete. When I use JS_GetTwoByteStringCharsAndLength in as_string(), the method returns some trash instead of actual stack trace. Probably some problem with text encoding. I left this code commented out. Version with JS_EncodeStringToUTF8 works.

@mrowqa
Copy link
Contributor Author

mrowqa commented Nov 29, 2017

@jdm I have just removed the commented out code with JS_GetTwoByteStringCharsAndLength. To be honest, I am really confused what it does. I thought JS Engine should keep internally strings encoded in UTF-16 and this function looked like getting a pointer to the internal buffer since it doesn't allocate anything and return const char16_t*. I have printed out the memory and it contains the expected stack, but it looks like it is encoded in UTF-8.

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 JS_Free (and associated memory allocation). Moreover, it just cuts out the higher byte of character instead of converting it to, for instance, '?'.

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 :)

@mrowqa
Copy link
Contributor Author

mrowqa commented Nov 30, 2017

@jdm Travis failed to have installed nightly toolchain, but the code actually works and passes the tests.

@jdm
Copy link
Member

jdm commented Dec 1, 2017

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?

@bors-servo
Copy link
Contributor

bors-servo commented Dec 1, 2017

The latest upstream changes (presumably #382) made this pull request unmergeable. Please resolve the merge conflicts.

mrowqa added 8 commits Nov 24, 2017
…to capturing-js-stacks
@jdm
Copy link
Member

jdm commented Dec 1, 2017

@bors-servo
Copy link
Contributor

bors-servo commented Dec 1, 2017

📌 Commit c04d888 has been approved by jdm

@bors-servo
Copy link
Contributor

bors-servo commented Dec 1, 2017

Testing commit c04d888 with merge ba52ca8...

bors-servo added a commit that referenced this pull request Dec 1, 2017
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 -->
@bors-servo
Copy link
Contributor

bors-servo commented Dec 2, 2017

☀️ Test successful - status-appveyor, status-travis
Approved by: jdm
Pushing ba52ca8 to master...

@bors-servo bors-servo merged commit c04d888 into servo:master Dec 2, 2017
3 checks passed
3 checks passed
continuous-integration/appveyor/pr AppVeyor build succeeded
Details
continuous-integration/travis-ci/pr The Travis CI build passed
Details
homu Test successful
Details
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked issues

Successfully merging this pull request may close these issues.

None yet

5 participants
You can’t perform that action at this time.