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()
|
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,
|
// 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
|
// 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++
|
// 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.
|
/// Create a new wallet and return the seed for the newly created wallet.
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern fn litelib_initialize_new(dangerous: bool,server: *const c_char) -> *mut c_char {
|
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 {
|
let server_str = unsafe {
|
||||||
assert!(!server.is_null());
|
assert!(!server.is_null());
|
||||||
|
|
||||||
@@ -126,6 +139,12 @@ pub extern fn litelib_initialize_new(dangerous: bool,server: *const c_char) -> *
|
|||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn litelib_initialize_new_from_phrase(dangerous: bool, server: *const c_char,
|
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 {
|
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() {
|
if server.is_null() || seed.is_null() {
|
||||||
println!("Server or seed is null");
|
println!("Server or seed is null");
|
||||||
return ptr::null_mut();
|
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
|
// Initialize a new lightclient and store its value
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern fn litelib_initialize_existing(dangerous: bool, server: *const c_char) -> *mut c_char {
|
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 {
|
let server_str = unsafe {
|
||||||
assert!(!server.is_null());
|
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) {
|
// s2-D14: the core reports a recovery from the backup; pass it on (the C++ side treats any non-error string as
|
||||||
Ok(l) => l,
|
// a successful open).
|
||||||
|
let (lightclient, recovered) = match LightClient::read_from_disk_reporting(&config) {
|
||||||
|
Ok(r) => r,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let e_str = CString::new(format!("Error: {}", e)).unwrap();
|
let e_str = CString::new(format!("Error: {}", e)).unwrap();
|
||||||
return e_str.into_raw();
|
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)); },
|
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();
|
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
|
// Check is Server Connection is fine
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn litelib_check_server_online(server: *const c_char) -> bool {
|
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 {
|
let server_str = unsafe {
|
||||||
assert!(!server.is_null());
|
assert!(!server.is_null());
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user