This is an automated email from the ASF dual-hosted git repository.

lidavidm pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-adbc.git


The following commit(s) were added to refs/heads/main by this push:
     new c4f286c4f fix(rust/ffi): fix release_ffi_error to properly null values 
(#4181)
c4f286c4f is described below

commit c4f286c4f0cedbb9426d14bf7b55be51a0fbc52e
Author: Matt Topol <[email protected]>
AuthorDate: Sat Apr 4 04:10:48 2026 -0400

    fix(rust/ffi): fix release_ffi_error to properly null values (#4181)
    
    Fixes #4178
---
 rust/ffi/src/types.rs | 71 ++++++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 68 insertions(+), 3 deletions(-)

diff --git a/rust/ffi/src/types.rs b/rust/ffi/src/types.rs
index 716114d2d..7e0e6a2e8 100644
--- a/rust/ffi/src/types.rs
+++ b/rust/ffi/src/types.rs
@@ -560,9 +560,12 @@ unsafe extern "C" fn release_ffi_error(error: *mut 
FFI_AdbcError) {
     match error.as_mut() {
         None => (),
         Some(error) => {
-            // SAFETY: `error.message` was necessarily obtained with 
`CString::into_raw`.
-            // Additionally, C should not modify the string's length.
-            drop(CString::from_raw(error.message));
+            if !error.message.is_null() {
+                // SAFETY: `error.message` was necessarily obtained with 
`CString::into_raw`.
+                // Additionally, C should not modify the string's length.
+                drop(CString::from_raw(error.message));
+                error.message = null_mut();
+            }
 
             if !error.private_data.is_null() {
                 // SAFETY: `error.private_data` was necessarily obtained with 
`Box::into_raw`.
@@ -570,7 +573,9 @@ unsafe extern "C" fn release_ffi_error(error: *mut 
FFI_AdbcError) {
                 // Finally, C should call the release function only once.
                 let private_data = Box::from_raw(error.private_data as *mut 
ErrorPrivateData);
                 drop(private_data);
+                error.private_data = null_mut();
             }
+            error.release = None;
         }
     }
 }
@@ -626,4 +631,64 @@ mod tests {
         let partitions_actual: Partitions = partitions_ffi.into();
         assert_eq!(partitions_expected, partitions_actual);
     }
+
+    #[test]
+    fn test_release_ffi_error_nulls_fields() {
+        // Use details to ensure private_data is non-null before release.
+        let error = Error {
+            message: "test error".into(),
+            status: Status::Unknown,
+            vendor_code: 0,
+            sqlstate: [0; 5],
+            details: Some(vec![("key".to_string(), b"value".to_vec())]),
+        };
+        let mut ffi_error: FFI_AdbcError = error.try_into().unwrap();
+        assert!(!ffi_error.message.is_null());
+        assert!(!ffi_error.private_data.is_null());
+
+        unsafe { release_ffi_error(&mut ffi_error) };
+
+        assert!(ffi_error.message.is_null());
+        assert!(ffi_error.private_data.is_null());
+        assert!(ffi_error.release.is_none());
+        // Drop here is a no-op because release is None.
+    }
+
+    #[test]
+    fn test_release_ffi_error_idempotent() {
+        // Calling release twice must not double-free the message pointer.
+        let error = Error {
+            message: "test error".into(),
+            status: Status::Unknown,
+            vendor_code: 0,
+            sqlstate: [0; 5],
+            details: None,
+        };
+        let mut ffi_error: FFI_AdbcError = error.try_into().unwrap();
+
+        unsafe {
+            release_ffi_error(&mut ffi_error);
+            release_ffi_error(&mut ffi_error);
+        }
+        // Drop here is a no-op because release is None.
+    }
+
+    #[test]
+    fn test_release_ffi_error_null_message() {
+        // A null message must not cause UB or a panic during release.
+        let mut ffi_error = FFI_AdbcError {
+            message: null_mut(),
+            vendor_code: 0,
+            sqlstate: [0; 5],
+            release: Some(release_ffi_error),
+            private_data: null_mut(),
+            private_driver: null(),
+        };
+
+        unsafe { release_ffi_error(&mut ffi_error) };
+
+        assert!(ffi_error.message.is_null());
+        assert!(ffi_error.release.is_none());
+        // Drop here is a no-op because release is None.
+    }
 }

Reply via email to