From 13768f860a0e05b483a4e2d2f60d8164db58f461 Mon Sep 17 00:00:00 2001 From: hexadexxa Date: Fri, 24 Apr 2026 22:01:51 +0000 Subject: [PATCH 1/3] Prevent JVM abort from Rapier internal panics via catch_unwind at FFI boundary Wrap Rapier tick/step in catch_unwind to prevent JVM abort --- common/src/main/rust/rapier/src/lib.rs | 54 +++++++++++++++----------- 1 file changed, 31 insertions(+), 23 deletions(-) diff --git a/common/src/main/rust/rapier/src/lib.rs b/common/src/main/rust/rapier/src/lib.rs index 6e3f5eb9..4dda6dc1 100644 --- a/common/src/main/rust/rapier/src/lib.rs +++ b/common/src/main/rust/rapier/src/lib.rs @@ -428,18 +428,20 @@ pub extern "system" fn Java_dev_ryanhcode_sable_physics_impl_rapier_Rapier3D_tic scene_id: jint, _time_step: jdouble, ) { - unsafe { - if let Some(state) = &mut PHYSICS_STATE { - rope::tick(scene_id); - joints::tick(scene_id); + let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + unsafe { + if let Some(state) = &mut PHYSICS_STATE { + rope::tick(scene_id); + joints::tick(scene_id); - let Some(scene) = state.scenes.get_mut(&scene_id) else { - panic!("No scene with given ID!"); - }; + let Some(scene) = state.scenes.get_mut(&scene_id) else { + return; + }; - compute_buoyancy(scene); + compute_buoyancy(scene); + } } - } + })); } /// Steps physics @@ -463,20 +465,26 @@ pub extern "system" fn Java_dev_ryanhcode_sable_physics_impl_rapier_Rapier3D_ste scene.manifold_info_map = SableManifoldInfoMap::default(); - scene.pipeline.step( - scene.gravity, - &state.integration_parameters, - &mut scene.island_manager, - &mut scene.broad_phase, - &mut scene.narrow_phase, - &mut scene.rigid_body_set, - &mut scene.collider_set, - &mut scene.impulse_joint_set, - &mut scene.multibody_joint_set, - &mut scene.ccd_solver, - &scene.physics_hooks, - &scene.event_handler, - ); + let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + scene.pipeline.step( + scene.gravity, + &state.integration_parameters, + &mut scene.island_manager, + &mut scene.broad_phase, + &mut scene.narrow_phase, + &mut scene.rigid_body_set, + &mut scene.collider_set, + &mut scene.impulse_joint_set, + &mut scene.multibody_joint_set, + &mut scene.ccd_solver, + &scene.physics_hooks, + &scene.event_handler, + ); + })); + + if result.is_err() { + log::error!("Rapier physics step panicked, skipping this tick"); + } } } } From 2393381a84f3e68c1e7a53c92c5bc8170cb70a28 Mon Sep 17 00:00:00 2001 From: hexadexxa Date: Sat, 25 Apr 2026 13:33:18 +0000 Subject: [PATCH 2/3] Catch Rapier panics at FFI boundary and throw JVM RuntimeException Catch Rapier panics at FFI boundary and throw JVM RuntimeException --- common/src/main/rust/rapier/src/lib.rs | 31 ++++++++++++++++++++------ 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/common/src/main/rust/rapier/src/lib.rs b/common/src/main/rust/rapier/src/lib.rs index 4dda6dc1..a70c5660 100644 --- a/common/src/main/rust/rapier/src/lib.rs +++ b/common/src/main/rust/rapier/src/lib.rs @@ -421,14 +421,33 @@ pub extern "system" fn Java_dev_ryanhcode_sable_physics_impl_rapier_Rapier3D_ini } /// Computes buoyancy +/// Extracts a message from a caught panic payload +fn panic_message(payload: &Box) -> String { + if let Some(s) = payload.downcast_ref::<&str>() { + s.to_string() + } else if let Some(s) = payload.downcast_ref::() { + s.clone() + } else { + "unknown panic".to_string() + } +} + +/// Catches a panic and throws a JVM RuntimeException with the panic message +fn throw_on_panic(env: &mut JNIEnv, result: Result<(), Box>) { + if let Err(payload) = result { + let msg = format!("Rapier native panic: {}", panic_message(&payload)); + let _ = env.throw_new("java/lang/RuntimeException", &msg); + } +} + #[unsafe(no_mangle)] pub extern "system" fn Java_dev_ryanhcode_sable_physics_impl_rapier_Rapier3D_tick<'local>( - _env: JNIEnv<'local>, + mut env: JNIEnv<'local>, _class: JClass<'local>, scene_id: jint, _time_step: jdouble, ) { - let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { unsafe { if let Some(state) = &mut PHYSICS_STATE { rope::tick(scene_id); @@ -442,12 +461,13 @@ pub extern "system" fn Java_dev_ryanhcode_sable_physics_impl_rapier_Rapier3D_tic } } })); + throw_on_panic(&mut env, result); } /// Steps physics #[unsafe(no_mangle)] pub extern "system" fn Java_dev_ryanhcode_sable_physics_impl_rapier_Rapier3D_step<'local>( - _env: JNIEnv<'local>, + mut env: JNIEnv<'local>, _class: JClass<'local>, scene_id: jint, time_step: jdouble, @@ -481,10 +501,7 @@ pub extern "system" fn Java_dev_ryanhcode_sable_physics_impl_rapier_Rapier3D_ste &scene.event_handler, ); })); - - if result.is_err() { - log::error!("Rapier physics step panicked, skipping this tick"); - } + throw_on_panic(&mut env, result); } } } From 3df88781c2c915b32860461278f3c29be46ff167 Mon Sep 17 00:00:00 2001 From: hexadexxa Date: Mon, 27 Apr 2026 15:31:13 +0000 Subject: [PATCH 3/3] Replace return with panic --- common/src/main/rust/rapier/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/src/main/rust/rapier/src/lib.rs b/common/src/main/rust/rapier/src/lib.rs index a70c5660..18449e1c 100644 --- a/common/src/main/rust/rapier/src/lib.rs +++ b/common/src/main/rust/rapier/src/lib.rs @@ -454,9 +454,9 @@ pub extern "system" fn Java_dev_ryanhcode_sable_physics_impl_rapier_Rapier3D_tic joints::tick(scene_id); let Some(scene) = state.scenes.get_mut(&scene_id) else { - return; + panic!("No scene with given ID!"); }; - + compute_buoyancy(scene); } }