d-unsed / ruru

Native Ruby extensions written in Rust
MIT License
832 stars 40 forks source link

RString needs to be panic safe std::panic::catch_unwind #73

Open danielpclark opened 7 years ago

danielpclark commented 7 years ago

Ruby has this particular string in it's own test suite:

filename = "\xff".force_encoding("UTF-8") # invalid byte sequence as UTF-8

https://github.com/ruby/ruby/blob/b67ead14521fb74bcf8ec28f8c78245dfb536b70/test/ruby/test_dir_m17n.rb#L72

This will cause ruru to panic:

thread '<unnamed>' panicked at 'called `Result::unwrap()` on an `Err`
value: Utf8Error { valid_up_to: 0, error_len: Some(1) }', /checkout/src/libcore/result.rs:906:4

I've tried wrapping it in a thread but that failed.

In Rust, panic is safe and per-thread. The boundaries between threads serve as a firewall for panic;

  • Programming Rust by Jim Blandy, Jason Orendorff

I then found Rust's std::panic::catch_unwind which according to the original RFC is meant to handle such panics from C method calls. The catch_unwind doc has this note:

Note that this function may not catch all panics in Rust. A panic in Rust is not always implemented via unwinding, but can be implemented by aborting the process as well. This function only catches unwinding panics, not those that abort the process.

I have thus far been unable to catch the panic from this and looking up the error Utf8Error it would seem that that is a side effect of the kind of methods used from libruby for RString and that error isn't written in ruru.

danielpclark commented 6 years ago

Looking at the Rust source code it looks like panics from C cannot be caught & unwound from the Rust side: https://github.com/rust-lang/rust/blob/d96cc6e2865ff2cc77f061ae97e58aae50f5a1e9/src/test/run-pass/abort-on-c-abi.rs

For this we'd likely need to use Ruby's rb_protect to implement this, or something like this. Thoughts?

danielpclark commented 6 years ago

Poossibly related. When calling the Exception#full_message method from Rust it panics.

The method:

fn full_string(&self) -> String {
    RString::from(binding_util::call_method(self.value(), "full_string", None)).to_string()
}

The example:

use ruru::{AnyException, Exception, Object, VM};
# VM::init();

assert_eq!(
    AnyException::new("StandardError", Some("something went wrong")).full_string(),
    "StandardError: something went wrong"
);

Here's what Ruby gives you when you're in irb:

"\e[1mTraceback \e[m(most recent call last):\n(irb):10:in `full_message': \e[1msomething went wrong (\e[4;1mStandardError\e[m\e[1m)\n\e[m"