fix(lite-backend): catch panics at the wallet-open C entry points
litelib_initialize_new, litelib_initialize_new_from_phrase, litelib_initialize_existing and litelib_check_server_online had no catch_unwind (only litelib_execute did), so any panic below them unwound across the C ABI into the GUI. With rustc 1.63 that is undefined behaviour; in practice the process aborts with "fatal runtime error: failed to initiate panic, error 5". The simplest trigger is a malformed user-entered server URL: get_server_or_default unwraps the http::Uri parse before anything else runs. - ffi_guard (next to error_cstring) runs a body under catch_unwind and turns a panic into "Error: Rust panic in <name>", which the C++ bridge already treats as a failed call. The three init entry points keep their extern signatures and #[no_mangle] and call ffi_guard; their old bodies are now private *_impl functions. - litelib_check_server_online wraps its body the same way and returns false on a panic. - litelib_initialize_existing opens the wallet through read_from_disk_reporting. After a recovery from the .bak it returns "OK: recovered from backup; damaged file kept as <name>" instead of "OK". The C++ side treats any non-empty, non-error string as a successful open, so this is ABI-compatible. No ABI or symbol change: the exported symbol inventory (lite-backend-symbols.txt) is identical. panic = "unwind" stays in the release profile, and every guard depends on it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+36
-3
@@ -22,6 +22,15 @@ fn error_cstring(msg: &str) -> *mut c_char {
|
||||
safe_cstring(&format!("Error: {}", msg)).into_raw()
|
||||
}
|
||||
|
||||
/// Run a C entry point's body and turn a Rust panic into an "Error: ..." string (C244): a panic must never unwind
|
||||
/// across the C ABI. This relies on panic = "unwind" (lib/Cargo.toml [profile.release]).
|
||||
fn ffi_guard<F: FnOnce() -> *mut c_char>(name: &str, f: F) -> *mut c_char {
|
||||
match panic::catch_unwind(panic::AssertUnwindSafe(f)) {
|
||||
Ok(p) => p,
|
||||
Err(_) => error_cstring(&format!("Rust panic in {}", name)),
|
||||
}
|
||||
}
|
||||
|
||||
// We'll use a MUTEX to store a global lightclient instance,
|
||||
// so we don't have to keep creating it. We need to store it here, in rust
|
||||
// because we can't return such a complex structure back to C++
|
||||
@@ -70,6 +79,10 @@ pub extern fn blake3_PW(pw: *const c_char) -> *mut c_char{
|
||||
/// Create a new wallet and return the seed for the newly created wallet.
|
||||
#[no_mangle]
|
||||
pub extern fn litelib_initialize_new(dangerous: bool,server: *const c_char) -> *mut c_char {
|
||||
ffi_guard("litelib_initialize_new", || litelib_initialize_new_impl(dangerous, server))
|
||||
}
|
||||
|
||||
fn litelib_initialize_new_impl(dangerous: bool, server: *const c_char) -> *mut c_char {
|
||||
let server_str = unsafe {
|
||||
assert!(!server.is_null());
|
||||
|
||||
@@ -126,6 +139,12 @@ pub extern fn litelib_initialize_new(dangerous: bool,server: *const c_char) -> *
|
||||
#[no_mangle]
|
||||
pub extern "C" fn litelib_initialize_new_from_phrase(dangerous: bool, server: *const c_char,
|
||||
seed: *const c_char, birthday: u64, number: u64, overwrite: bool) -> *mut c_char {
|
||||
ffi_guard("litelib_initialize_new_from_phrase",
|
||||
|| litelib_initialize_new_from_phrase_impl(dangerous, server, seed, birthday, number, overwrite))
|
||||
}
|
||||
|
||||
fn litelib_initialize_new_from_phrase_impl(dangerous: bool, server: *const c_char,
|
||||
seed: *const c_char, birthday: u64, number: u64, overwrite: bool) -> *mut c_char {
|
||||
if server.is_null() || seed.is_null() {
|
||||
println!("Server or seed is null");
|
||||
return ptr::null_mut();
|
||||
@@ -197,6 +216,10 @@ pub extern "C" fn litelib_initialize_new_from_phrase(dangerous: bool, server: *c
|
||||
// Initialize a new lightclient and store its value
|
||||
#[no_mangle]
|
||||
pub extern fn litelib_initialize_existing(dangerous: bool, server: *const c_char) -> *mut c_char {
|
||||
ffi_guard("litelib_initialize_existing", || litelib_initialize_existing_impl(dangerous, server))
|
||||
}
|
||||
|
||||
fn litelib_initialize_existing_impl(dangerous: bool, server: *const c_char) -> *mut c_char {
|
||||
let server_str = unsafe {
|
||||
assert!(!server.is_null());
|
||||
|
||||
@@ -212,8 +235,10 @@ pub extern fn litelib_initialize_existing(dangerous: bool, server: *const c_char
|
||||
}
|
||||
};
|
||||
|
||||
let lightclient = match LightClient::read_from_disk(&config) {
|
||||
Ok(l) => l,
|
||||
// s2-D14: the core reports a recovery from the backup; pass it on (the C++ side treats any non-error string as
|
||||
// a successful open).
|
||||
let (lightclient, recovered) = match LightClient::read_from_disk_reporting(&config) {
|
||||
Ok(r) => r,
|
||||
Err(e) => {
|
||||
let e_str = CString::new(format!("Error: {}", e)).unwrap();
|
||||
return e_str.into_raw();
|
||||
@@ -236,7 +261,10 @@ pub extern fn litelib_initialize_existing(dangerous: bool, server: *const c_char
|
||||
Err(poisoned) => { poisoned.into_inner().replace(Some(lc)); },
|
||||
};
|
||||
|
||||
let c_str = safe_cstring("OK");
|
||||
let c_str = match recovered {
|
||||
Some(note) => safe_cstring(&format!("OK: {}", note)),
|
||||
None => safe_cstring("OK"),
|
||||
};
|
||||
return c_str.into_raw();
|
||||
}
|
||||
|
||||
@@ -286,6 +314,11 @@ pub extern fn litelib_execute(cmd: *const c_char, args: *const c_char) -> *mut c
|
||||
// Check is Server Connection is fine
|
||||
#[no_mangle]
|
||||
pub extern "C" fn litelib_check_server_online(server: *const c_char) -> bool {
|
||||
// A malformed server URL panics in get_server_or_default (and get_client); never unwind across the C ABI.
|
||||
panic::catch_unwind(move || litelib_check_server_online_impl(server)).unwrap_or(false)
|
||||
}
|
||||
|
||||
fn litelib_check_server_online_impl(server: *const c_char) -> bool {
|
||||
let server_str = unsafe {
|
||||
assert!(!server.is_null());
|
||||
|
||||
|
||||
Reference in New Issue
Block a user