catch_panic/
lib.rs

1//! # #\[catch_panic\]
2//!
3//! A helper macro for safe Java-Rust interop that "catches" Rust panics
4//! and rethrows them as Java exceptions.
5//!
6//! ## Usage
7//!
8//! Attach `#[catch_panic]` to a JNI callback to have panics converted into `RuntimeException`s:
9//!
10//! ```
11//! # use jni::JNIEnv;
12//! # use catch_panic::catch_panic;
13//! #
14//! #[no_mangle]
15//! #[catch_panic]
16//! pub extern "C" fn Java_com_example_Example_panic(_env: JNIEnv) {
17//!     panic!("everything is not fine");
18//! }
19//!
20//! # catch_panic::test::check_callback(|env| Java_com_example_Example_panic(env), true);
21//! ```
22//!
23//! Since `#[catch_panic]` internally relies on Rust's [catch_unwind] function,
24//! you **must** build your library with [`panic = "unwind"`] for panics to be caught.
25//!
26//! [catch_unwind]: std::panic::catch_unwind
27//! [`panic = "unwind"`]: https://doc.rust-lang.org/cargo/reference/profiles.html#panic
28//!
29//! ### Dummy return values
30//!
31//! Even if an exception is thrown, the native callback must return some value to the JVM.
32//! By default, `#[catch_panic]` will attempt to use the [Default][std::default::Default]
33//! value of a type as the dummy value to return. This however does not work for `jobject`s
34//! and related types, so an explicit default must be specified:
35//!
36//! ```
37//! # use jni::JNIEnv;
38//! # use jni::sys::jobject;
39//! # use catch_panic::catch_panic;
40//! #
41//! #[no_mangle]
42//! #[catch_panic(default = "std::ptr::null_mut()")]
43//! pub extern "C" fn Java_com_example_Example_gimmeAnObject(env: JNIEnv) -> jobject {
44//!     env.alloc_object("java/lang/Object").unwrap().into_inner()
45//! }
46//!
47//! # catch_panic::test::check_callback(|env| Java_com_example_Example_gimmeAnObject(env), false);
48//! ```
49//!
50//! Any valid expression can be used for the default value.
51//!
52//! ### Custom panic payload handlers
53//!
54//! For throwing custom exception types or processing the panic payload,
55//! you can use your own panic payload handler:
56//!
57//! ```
58//! # use std::any::Any;
59//! # use jni::JNIEnv;
60//! # use catch_panic::catch_panic;
61//! #
62//! pub fn enterprise_certified_handler(env: JNIEnv, err: Box<dyn Any + Send + 'static>) {
63//!     let msg = match err.downcast_ref::<&'static str>() {
64//!         Some(s) => *s,
65//!         None => match err.downcast_ref::<String>() {
66//!             Some(s) => &s[..],
67//!             None => "this is a certified `std::panic::panic_any` moment",
68//!         },
69//!     };
70//!     env.throw_new("com/example/ExampleEnterpriseException", msg).unwrap();
71//! }
72//!
73//! #[no_mangle]
74//! #[catch_panic(handler = "enterprise_certified_handler")]
75//! pub extern "C" fn Java_com_example_Example_makeFactoryNoises(env: JNIEnv) {
76//!     panic!("<insert factory noises>");
77//! }
78//!
79//! # catch_panic::test::check_callback_with_setup(
80//! #     |env| {
81//! #         let src = std::fs::read("testlib/ExampleEnterpriseException.class")
82//! #             .expect("ExampleEnterpriseException test class file missing!");
83//! #
84//! #         let object_class = env.find_class("java/lang/Object").unwrap();
85//! #         let object_class_loader = env
86//! #             .call_method(object_class, "getClassLoader", "()Ljava/lang/ClassLoader;", &[])
87//! #             .unwrap();
88//! #
89//! #         env.define_class(
90//! #             "com/example/ExampleEnterpriseException",
91//! #             object_class_loader.try_into().unwrap(),
92//! #             &src,
93//! #         )
94//! #         .unwrap();
95//! #     },
96//! #     |env| Java_com_example_Example_makeFactoryNoises(env),
97//! #     true,
98//! # );
99//! ```
100
101pub mod handler;
102pub use catch_panic_macros::catch_panic;
103
104#[cfg(feature = "internal-doctests")]
105#[doc(hidden)]
106pub mod test {
107    use jni::JNIEnv;
108
109    // Rust doesn't have a conventional way to depend on other crates'
110    // test code, so we pull in jni-rs' testing utils through a git submodule
111    #[path = "../../testlib/jni-rs/tests/util/mod.rs"]
112    mod util;
113
114    pub fn check_callback<C, R>(callback: C, should_throw: bool)
115    where
116        C: Fn(JNIEnv) -> R,
117    {
118        check_callback_with_setup(|_| {}, callback, should_throw)
119    }
120
121    pub fn check_callback_with_setup<S, C, R>(setup: S, callback: C, should_throw: bool)
122    where
123        S: FnOnce(JNIEnv),
124        C: Fn(JNIEnv) -> R,
125    {
126        let env = util::attach_current_thread();
127        setup(*env);
128        callback(*env);
129        assert_eq!(
130            env.exception_check().expect("Couldn't check if there was an exception"),
131            should_throw,
132            "Expected callback to {}throw when it {} throw",
133            if should_throw { "" } else { "not " },
134            if should_throw { "did not" } else { "did" },
135        );
136    }
137}
138
139#[cfg(test)]
140mod ui_tests {
141    #[test]
142    pub fn ui_tests() {
143        let t = trybuild::TestCases::new();
144        t.compile_fail("ui_tests/fail/*.rs");
145    }
146}