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}