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

JingsongLi pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/paimon-rust.git


The following commit(s) were added to refs/heads/main by this push:
     new 944e3893 fix(c): guard predicate AND/OR/NOT against null inputs (#923)
944e3893 is described below

commit 944e3893529d5a8eac6618655f90ddb5998b9c89
Author: jackylee <[email protected]>
AuthorDate: Thu Sep 24 10:33:53 2026 +0800

    fix(c): guard predicate AND/OR/NOT against null inputs (#923)
---
 bindings/c/src/table.rs | 36 +++++++++++++++++++++++++++++++---
 bindings/c/src/tests.rs | 51 +++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 84 insertions(+), 3 deletions(-)

diff --git a/bindings/c/src/table.rs b/bindings/c/src/table.rs
index 598cc63d..26b7bbba 100644
--- a/bindings/c/src/table.rs
+++ b/bindings/c/src/table.rs
@@ -1925,13 +1925,24 @@ unsafe fn build_leaf_predicate_datums(
 
 /// Combine two predicates with AND. Consumes both inputs.
 ///
+/// If either input is null — e.g. forwarded straight from a leaf constructor
+/// that failed and returned a null predicate — both inputs are freed and null
+/// is returned instead of dereferencing the null pointer (which is undefined
+/// behavior and crashes the host). This mirrors the null tolerance of
+/// `paimon_predicate_free` and `paimon_read_builder_with_filter`.
+///
 /// # Safety
-/// `a` and `b` must be valid pointers from predicate functions.
+/// `a` and `b` must each be a valid pointer from a predicate function, or 
null.
 #[no_mangle]
 pub unsafe extern "C" fn paimon_predicate_and(
     a: *mut paimon_predicate,
     b: *mut paimon_predicate,
 ) -> *mut paimon_predicate {
+    if a.is_null() || b.is_null() {
+        paimon_predicate_free(a);
+        paimon_predicate_free(b);
+        return std::ptr::null_mut();
+    }
     let pred_a = *Box::from_raw(Box::from_raw(a).inner as *mut Predicate);
     let pred_b = *Box::from_raw(Box::from_raw(b).inner as *mut Predicate);
     let combined = Predicate::and(vec![pred_a, pred_b]);
@@ -1941,13 +1952,24 @@ pub unsafe extern "C" fn paimon_predicate_and(
 
 /// Combine two predicates with OR. Consumes both inputs.
 ///
+/// If either input is null — e.g. forwarded straight from a leaf constructor
+/// that failed and returned a null predicate — both inputs are freed and null
+/// is returned instead of dereferencing the null pointer (which is undefined
+/// behavior and crashes the host). This mirrors the null tolerance of
+/// `paimon_predicate_free` and `paimon_read_builder_with_filter`.
+///
 /// # Safety
-/// `a` and `b` must be valid pointers from predicate functions.
+/// `a` and `b` must each be a valid pointer from a predicate function, or 
null.
 #[no_mangle]
 pub unsafe extern "C" fn paimon_predicate_or(
     a: *mut paimon_predicate,
     b: *mut paimon_predicate,
 ) -> *mut paimon_predicate {
+    if a.is_null() || b.is_null() {
+        paimon_predicate_free(a);
+        paimon_predicate_free(b);
+        return std::ptr::null_mut();
+    }
     let pred_a = *Box::from_raw(Box::from_raw(a).inner as *mut Predicate);
     let pred_b = *Box::from_raw(Box::from_raw(b).inner as *mut Predicate);
     let combined = Predicate::or(vec![pred_a, pred_b]);
@@ -1957,10 +1979,18 @@ pub unsafe extern "C" fn paimon_predicate_or(
 
 /// Negate a predicate with NOT. Consumes the input.
 ///
+/// If the input is null — e.g. forwarded straight from a leaf constructor that
+/// failed and returned a null predicate — null is returned instead of
+/// dereferencing the null pointer (which is undefined behavior and crashes the
+/// host). This mirrors the null tolerance of `paimon_predicate_free`.
+///
 /// # Safety
-/// `p` must be a valid pointer from a predicate function.
+/// `p` must be a valid pointer from a predicate function, or null.
 #[no_mangle]
 pub unsafe extern "C" fn paimon_predicate_not(p: *mut paimon_predicate) -> 
*mut paimon_predicate {
+    if p.is_null() {
+        return std::ptr::null_mut();
+    }
     let pred = *Box::from_raw(Box::from_raw(p).inner as *mut Predicate);
     let negated = Predicate::negate(pred);
     let inner = Box::into_raw(Box::new(negated)) as *mut c_void;
diff --git a/bindings/c/src/tests.rs b/bindings/c/src/tests.rs
index 570c85a8..a5b1acbf 100644
--- a/bindings/c/src/tests.rs
+++ b/bindings/c/src/tests.rs
@@ -1410,6 +1410,57 @@ fn test_predicate_and_or_not() {
     unsafe { unwrap_table(handle) };
 }
 
+#[test]
+fn test_predicate_combinators_reject_null_without_crashing() {
+    // Leaf constructors return a null predicate on error (bad column, bad
+    // UTF-8, bad datum). Forwarding that null into a combinator must not
+    // dereference it: before the null guards these calls were UB / a host
+    // crash. Now they free any non-null sibling (honoring "consumes both
+    // inputs") and return null.
+    let path = "memory:/test_predicate_null_combinators";
+    let file_io = memory_file_io();
+    setup_table_dirs(&file_io, path);
+    let table = Table::new(
+        file_io.clone(),
+        Identifier::new("default", "test"),
+        path.to_string(),
+        simple_table_schema(),
+        None,
+    );
+    let handle = unsafe { wrap_table(table) };
+
+    unsafe {
+        // Both-null: no dereference, null out.
+        assert!(paimon_predicate_and(ptr::null_mut(), 
ptr::null_mut()).is_null());
+        assert!(paimon_predicate_or(ptr::null_mut(), 
ptr::null_mut()).is_null());
+        assert!(paimon_predicate_not(ptr::null_mut()).is_null());
+
+        let col = CString::new("id").unwrap();
+        let mk_datum = || paimon_datum {
+            tag: 3,
+            int_val: 1,
+            double_val: 0.0,
+            str_data: ptr::null(),
+            str_len: 0,
+            int_val2: 0,
+            uint_val: 0,
+            uint_val2: 0,
+        };
+
+        // One-null: the valid sibling is consumed (freed) and null returned.
+        // Do not free the survivor again — the combinator already did.
+        let p = paimon_predicate_greater_than(handle, col.as_ptr(), 
mk_datum());
+        assert!(p.error.is_null() && !p.predicate.is_null());
+        assert!(paimon_predicate_and(p.predicate, ptr::null_mut()).is_null());
+
+        let p2 = paimon_predicate_greater_than(handle, col.as_ptr(), 
mk_datum());
+        assert!(p2.error.is_null() && !p2.predicate.is_null());
+        assert!(paimon_predicate_or(ptr::null_mut(), p2.predicate).is_null());
+    }
+
+    unsafe { unwrap_table(handle) };
+}
+
 // =========================================================================
 //  Write path tests
 // =========================================================================

Reply via email to