Handle panics in main on android (#4328)

* Handle panics in main on android

* Clippy fix

* Run in context of main

* small cleanups

---------

Co-authored-by: Jonathan Kelley <jkelleyrtp@gmail.com>
This commit is contained in:
Moritz HedtkeandJonathan Kelley authored and GitHub committed 2025-06-30 17:38:15 -07:00
1 parent 192fbffc51
commit 60706a6c08
1 file changed
+30 -107
+30 -107
View File
@@ -5,7 +5,6 @@
pub use dioxus_desktop::*;
use dioxus_lib::prelude::*;
use std::any::Any;
use std::sync::Mutex;
pub mod launch_bindings {
@@ -33,62 +32,41 @@ pub fn launch_cfg(
contexts: Vec<Box<dyn Fn() -> Box<dyn Any> + Send + Sync>>,
platform_config: Vec<Box<dyn Any>>,
) {
#[cfg(target_os = "android")]
{
*APP_OBJECTS.lock().unwrap() = Some(BoundLaunchObjects {
root,
contexts,
platform_config,
});
}
#[cfg(not(target_os = "android"))]
{
dioxus_desktop::launch::launch(root, contexts, platform_config);
}
dioxus_desktop::launch::launch(root, contexts, platform_config);
}
/// We need to store the root function and contexts in a static so that when the tao bindings call
/// "start_app", that the original function arguments are still around.
///
/// If you look closely, you'll notice that we impl Send for this struct. This would normally be
/// unsound. However, we know that the thread that created these objects ("main()" - see JNI_OnLoad)
/// is finished once `start_app` is called. This is similar to how an `Rc<T>` is technically safe
/// to move between threads if you can prove that no other thread is using the `Rc<T>` at the same time.
/// Crates like <https://crates.io/crates/sendable> exist that build on this idea but with runtime
/// validation that the current thread is the one that created the object.
///
/// Since `main()` completes, the only reader of this data will be `start_app`, so it's okay to
/// impl this as Send/Sync.
///
/// Todo(jon): the visibility of functions in this module is too public. Make sure to hide them before
/// releasing 0.7.
struct BoundLaunchObjects {
root: fn() -> Element,
contexts: Vec<Box<dyn Fn() -> Box<dyn Any> + Send + Sync>>,
platform_config: Vec<Box<dyn Any>>,
}
unsafe impl Send for BoundLaunchObjects {}
unsafe impl Sync for BoundLaunchObjects {}
static APP_OBJECTS: Mutex<Option<BoundLaunchObjects>> = Mutex::new(None);
#[doc(hidden)]
pub fn root() {
let app = APP_OBJECTS
.lock()
.expect("APP_FN_PTR lock failed")
.take()
.expect("Android to have set the app trampoline");
fn stop_unwind<F: FnOnce() -> T, T>(f: F) -> T {
match std::panic::catch_unwind(std::panic::AssertUnwindSafe(f)) {
Ok(t) => t,
Err(err) => {
eprintln!("attempt to unwind out of `rust` with err: {:?}", err);
std::process::abort()
}
}
}
let BoundLaunchObjects {
root,
contexts,
platform_config,
} = app;
stop_unwind(|| unsafe {
let mut main_fn_ptr = libc::dlsym(libc::RTLD_DEFAULT, b"main\0".as_ptr() as _);
dioxus_desktop::launch::launch(root, contexts, platform_config);
if main_fn_ptr.is_null() {
main_fn_ptr = libc::dlsym(libc::RTLD_DEFAULT, b"_main\0".as_ptr() as _);
}
if main_fn_ptr.is_null() {
panic!("Failed to find main symbol");
}
// Set the env vars that rust code might expect, passed off to us by the android app
// Doing this before main emulates the behavior of a regular executable
if cfg!(target_os = "android") && cfg!(debug_assertions) {
load_env_file_from_session_cache();
}
let main_fn: extern "C" fn() = std::mem::transmute(main_fn_ptr);
main_fn();
});
}
/// Expose the `Java_dev_dioxus_main_WryActivity_create` function to the JNI layer.
@@ -108,47 +86,13 @@ pub extern "C" fn start_app() {
wry::android_binding!(dev_dioxus, main, wry);
}
/// Call our `main` function to initialize the rust runtime and set the launch binding trampoline
#[cfg(target_os = "android")]
#[no_mangle]
#[inline(never)]
pub extern "C" fn JNI_OnLoad(
_vm: *mut libc::c_void,
_reserved: *mut libc::c_void,
) -> jni::sys::jint {
// we're going to find the `main` symbol using dlsym directly and call it
unsafe {
let mut main_fn_ptr = libc::dlsym(libc::RTLD_DEFAULT, b"main\0".as_ptr() as _);
if main_fn_ptr.is_null() {
main_fn_ptr = libc::dlsym(libc::RTLD_DEFAULT, b"_main\0".as_ptr() as _);
}
if main_fn_ptr.is_null() {
panic!("Failed to find main symbol");
}
// Set the env vars that rust code might expect, passed off to us by the android app
// Doing this before main emulates the behavior of a regular executable
if cfg!(target_os = "android") && cfg!(debug_assertions) {
load_env_file_from_session_cache();
}
let main_fn: extern "C" fn() = std::mem::transmute(main_fn_ptr);
main_fn();
};
jni::sys::JNI_VERSION_1_6
}
/// Load the env file from the session cache if we're in debug mode and on android
///
/// This is a slightly hacky way of being able to use std::env::var code in android apps without
/// going through their custom java-based system.
#[cfg(target_os = "android")]
fn load_env_file_from_session_cache() {
let env_file = dioxus_cli_config::android_session_cache_dir().join(".env");
if let Some(env_file) = std::fs::read_to_string(&env_file).ok() {
if let Ok(env_file) = std::fs::read_to_string(&env_file) {
for line in env_file.lines() {
if let Some((key, value)) = line.trim().split_once('=') {
std::env::set_var(key, value);
@@ -156,24 +100,3 @@ fn load_env_file_from_session_cache() {
}
}
}
// #![doc = include_str!("../README.md")]
// #![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")]
// #![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
// pub use dioxus_desktop::*;
// use dioxus_lib::prelude::*;
// use std::any::Any;
// /// Launch via the binding API
// pub fn launch(root: fn() -> Element) {
// launch_cfg(root, vec![], vec![]);
// }
// pub fn launch_cfg(
// root: fn() -> Element,
// contexts: Vec<Box<dyn Fn() -> Box<dyn Any> + Send + Sync>>,
// platform_config: Vec<Box<dyn Any>>,
// ) {
// dioxus_desktop::launch::launch_cfg(root, contexts, platform_config);
// }