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
// =========================================================================