1515// specific language governing permissions and limitations
1616// under the License.
1717
18+ use std:: any:: Any ;
1819use std:: cell:: RefCell ;
1920use std:: ffi:: { c_char, CString } ;
21+ use std:: panic:: { catch_unwind, AssertUnwindSafe } ;
2022
2123thread_local ! {
2224 static LAST_ERROR : RefCell <CString > =
@@ -38,7 +40,77 @@ pub(crate) fn fail(message: impl Into<String>) -> i32 {
3840 -1
3941}
4042
43+ pub ( crate ) fn panic_message ( payload : Box < dyn Any + Send > ) -> String {
44+ let message = payload
45+ . downcast_ref :: < String > ( )
46+ . map ( String :: as_str)
47+ . or_else ( || payload. downcast_ref :: < & str > ( ) . copied ( ) )
48+ . unwrap_or ( "unknown panic" ) ;
49+ format ! ( "Lance panicked: {message}" )
50+ }
51+
52+ // Catch before returning through the C ABI, including panics during cleanup.
53+ pub ( crate ) fn catch_panic < T > ( fallback : T , operation : impl FnOnce ( ) -> T ) -> T {
54+ match catch_unwind ( AssertUnwindSafe ( operation) ) {
55+ Ok ( result) => result,
56+ Err ( payload) => {
57+ fail ( panic_message ( payload) ) ;
58+ fallback
59+ }
60+ }
61+ }
62+
4163#[ no_mangle]
4264pub extern "C" fn paimon_lance_last_error ( ) -> * const c_char {
43- LAST_ERROR . with ( |cell| cell. borrow ( ) . as_ptr ( ) )
65+ // Do not try to record another error if accessing the error slot panics.
66+ catch_unwind ( || LAST_ERROR . with ( |cell| cell. borrow ( ) . as_ptr ( ) ) ) . unwrap_or ( std:: ptr:: null ( ) )
67+ }
68+
69+ #[ cfg( test) ]
70+ mod tests {
71+ use super :: * ;
72+ use std:: ffi:: CStr ;
73+
74+ pub ( crate ) fn last_error ( ) -> String {
75+ unsafe { CStr :: from_ptr ( paimon_lance_last_error ( ) ) }
76+ . to_str ( )
77+ . unwrap ( )
78+ . to_owned ( )
79+ }
80+
81+ #[ test]
82+ fn panic_becomes_error_and_unwinds_locals ( ) {
83+ let dropped = std:: cell:: Cell :: new ( false ) ;
84+ struct OnDrop < ' a > ( & ' a std:: cell:: Cell < bool > ) ;
85+ impl Drop for OnDrop < ' _ > {
86+ fn drop ( & mut self ) {
87+ self . 0 . set ( true ) ;
88+ }
89+ }
90+ assert_eq ! (
91+ catch_panic( -1 , || {
92+ let _guard = OnDrop ( & dropped) ;
93+ panic!( "decode failed" ) ;
94+ } ) ,
95+ -1
96+ ) ;
97+ assert ! ( dropped. get( ) ) ;
98+ assert_eq ! ( last_error( ) , "Lance panicked: decode failed" ) ;
99+ assert_eq ! (
100+ catch_panic( -1 , || {
101+ clear_last_error( ) ;
102+ 42
103+ } ) ,
104+ 42
105+ ) ;
106+ assert_eq ! ( last_error( ) , "" ) ;
107+ }
108+
109+ #[ test]
110+ fn supports_owned_and_non_string_panics ( ) {
111+ catch_panic ( ( ) , || std:: panic:: panic_any ( String :: from ( "owned message" ) ) ) ;
112+ assert_eq ! ( last_error( ) , "Lance panicked: owned message" ) ;
113+ catch_panic ( ( ) , || std:: panic:: panic_any ( 42 ) ) ;
114+ assert_eq ! ( last_error( ) , "Lance panicked: unknown panic" ) ;
115+ }
44116}
0 commit comments