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

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


The following commit(s) were added to refs/heads/main by this push:
     new 3e89669636 fix(arrow-array): align FFI buffers before validation under 
force_validate (#10798)
3e89669636 is described below

commit 3e896696360d1b87f0b3a68efc9bf4d427461bd0
Author: Aditya Mishra <[email protected]>
AuthorDate: Fri Sep 4 04:12:41 2026 +0530

    fix(arrow-array): align FFI buffers before validation under force_validate 
(#10798)
    
    # Which issue does this PR close?
    
    - Closes #10034.
    
    # Rationale for this change
    
    `from_ffi` realigned under-aligned C Data Interface buffers (e.g. an
    8-byte aligned `Decimal128` from a JVM producer) *after* `consume()`.
    under `force_validate`, `consume()`'s `build()` validates first and
    rejects the buffer before the realign runs, so spec-legal input errors.
    reachable via the `arrow` crate with `features = ["force_validate",
    "ffi"]` calling `arrow::ffi::from_ffi`.
    
    # What changes are included in this PR?
    
    - `ImportedArrowArray::consume` builds through `ArrayDataBuilder` with
    `align_buffers(true)` before validation, matching `arrow-ipc`'s
    `create_array_from_builder`
    - dropped the now-redundant outer `align_buffers()` calls in `from_ffi`
    / `from_ffi_and_data_type`.
    
    # Are these changes tested?
    
    covered by `test_decimal128_under_aligned_round_trip`. the issue
    suggested ungating it under `force_validate`, but that isn't possible as
    its fixture is a misaligned `ArrayData` built with `build_unchecked`,
    which validates under `force_validate` and so rejects the input at
    construction, before `from_ffi` runs. the gate stays with a comment
    explaining why
    
    # Are there any user-facing changes?
    
    no public API change. behavior only changes under `force_validate`,
    where spec-legal under-aligned input is realigned instead of erroring.
---
 arrow-array/Cargo.toml                           |  2 +-
 arrow-array/src/array/fixed_size_binary_array.rs |  3 +
 arrow-array/src/ffi.rs                           | 78 +++++++++---------------
 3 files changed, 34 insertions(+), 49 deletions(-)

diff --git a/arrow-array/Cargo.toml b/arrow-array/Cargo.toml
index f5d26b5356..b7c7cfabc6 100644
--- a/arrow-array/Cargo.toml
+++ b/arrow-array/Cargo.toml
@@ -69,7 +69,7 @@ all-features = true
 [features]
 async = ["dep:futures"]
 ffi = ["arrow-schema/ffi", "arrow-data/ffi", "dep:libc"]
-force_validate = []
+force_validate = ["arrow-data/force_validate"]
 # Enable memory tracking support
 pool = ["arrow-buffer/pool", "arrow-data/pool"]
 
diff --git a/arrow-array/src/array/fixed_size_binary_array.rs 
b/arrow-array/src/array/fixed_size_binary_array.rs
index c865c2b342..39bb0f52ff 100644
--- a/arrow-array/src/array/fixed_size_binary_array.rs
+++ b/arrow-array/src/array/fixed_size_binary_array.rs
@@ -1029,6 +1029,9 @@ mod tests {
     }
 
     #[test]
+    // Under force_validate `build_unchecked` panics on the invalid child data
+    // before we reach the `FixedSizeBinaryArray::from` path we want to test.
+    #[cfg(not(feature = "force_validate"))]
     #[should_panic(expected = "The child array cannot contain null values.")]
     fn 
test_fixed_size_binary_array_from_fixed_size_list_array_with_child_nulls_failed()
 {
         let values = [0_u8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
diff --git a/arrow-array/src/ffi.rs b/arrow-array/src/ffi.rs
index 07707a641a..1ee6ee1a9c 100644
--- a/arrow-array/src/ffi.rs
+++ b/arrow-array/src/ffi.rs
@@ -253,14 +253,8 @@ pub unsafe fn from_ffi(array: FFI_ArrowArray, schema: 
&FFI_ArrowSchema) -> Resul
         data_type: dt,
         owner: &array,
     };
-    let mut data = tmp.consume()?;
-    // arrow-rs has stricter alignment requirements than the C Data Interface 
spec;
-    // a no-op when buffers are already aligned. Unreachable under
-    // `cfg(feature = "force_validate")`; tracked in #10034.
-    // See https://github.com/apache/arrow/issues/43552 and
-    // https://github.com/apache/arrow-rs/issues/10028 for context.
-    data.align_buffers();
-    Ok(data)
+    // `consume` aligns under-aligned buffers before validating them.
+    tmp.consume()
 }
 
 /// Import [ArrayData] from the C Data Interface
@@ -278,14 +272,8 @@ pub unsafe fn from_ffi_and_data_type(
         data_type,
         owner: &array,
     };
-    let mut data = tmp.consume()?;
-    // arrow-rs has stricter alignment requirements than the C Data Interface 
spec;
-    // a no-op when buffers are already aligned. Unreachable under
-    // `cfg(feature = "force_validate")`; tracked in #10034.
-    // See https://github.com/apache/arrow/issues/43552 and
-    // https://github.com/apache/arrow-rs/issues/10028 for context.
-    data.align_buffers();
-    Ok(data)
+    // `consume` aligns under-aligned buffers before validating them.
+    tmp.consume()
 }
 
 #[derive(Debug)]
@@ -322,20 +310,22 @@ impl ImportedArrowArray<'_> {
             child_data.push(d.consume()?);
         }
 
-        // Safety: all fields (length, null_count, null buffer, data buffers, 
child data) were
-        // derived from the C Data Interface schema and array, which the 
caller of `from_ffi`
-        // guarantees follow the spec; the constructed `ArrayData` satisfies 
its invariants.
-        Ok(unsafe {
-            ArrayData::new_unchecked(
-                self.data_type,
-                len,
-                null_count,
-                null_bit_buffer,
-                offset,
-                buffers,
-                child_data,
-            )
-        })
+        // Align before validate: spec-legal 8-byte-aligned buffers (e.g. 
Decimal128
+        // from JVM) get realigned rather than rejected, even under 
`force_validate`.
+        // Mirrors the IPC reader. See #10034.
+        let mut builder = ArrayData::builder(self.data_type)
+            .len(len)
+            .offset(offset)
+            .null_bit_buffer(null_bit_buffer)
+            .buffers(buffers)
+            .child_data(child_data)
+            .align_buffers(true);
+        // Only set the count if the producer reported one; else `build` 
recomputes.
+        if let Some(null_count) = null_count {
+            builder = builder.null_count(null_count);
+        }
+        // SAFETY: the caller guarantees the data agrees with the C Data 
Interface.
+        unsafe { builder.skip_validation(true) }.build()
     }
 
     fn consume_children(&self) -> Result<Vec<ArrayData>> {
@@ -672,33 +662,25 @@ mod tests_to_then_from_ffi {
     // case with nulls is tested in the docs, through the example on this 
module.
 
     #[test]
-    #[cfg(not(feature = "force_validate"))]
-    fn test_decimal128_under_aligned_round_trip() -> Result<()> {
-        // Construct an 8-aligned-but-not-16-aligned i128 data buffer to model
-        // an FFI producer that only guarantees the C Data Interface's
-        // recommended 8-byte alignment (e.g. arrow-java).
+    fn test_decimal128_under_aligned_import() -> Result<()> {
+        // FixedSizeBinary(16) needs only 1-byte alignment so it builds cleanly
+        // even under force_validate; imported as Decimal128 it triggers the
+        // realignment path. Regression test for #10034.
         let aligned = Buffer::from_vec(vec![0_i128, 1_i128, 2_i128]);
         let under_aligned = aligned.slice(8);
         assert_eq!(under_aligned.as_ptr().align_offset(8), 0);
         assert_ne!(under_aligned.as_ptr().align_offset(16), 0);
 
-        // SAFETY: buffer is large enough for 2 i128 elements; misaligned
-        // input is the condition under test.
-        let data = unsafe {
-            ArrayData::builder(DataType::Decimal128(10, 2))
-                .len(2)
-                .add_buffer(under_aligned)
-                .build_unchecked()
-        };
+        let data = ArrayData::builder(DataType::FixedSizeBinary(16))
+            .len(2)
+            .add_buffer(under_aligned)
+            .build()?;
 
-        let schema = FFI_ArrowSchema::try_from(data.data_type()).unwrap();
         let array = FFI_ArrowArray::new(&data);
-
-        let imported = unsafe { from_ffi(array, &schema) }?;
+        let imported = unsafe { from_ffi_and_data_type(array, 
DataType::Decimal128(10, 2)) }?;
         let array = Decimal128Array::from(imported);
 
-        // The little-endian byte layout of [0i128, 1, 2] sliced 8 bytes in
-        // yields elements `1 << 64` and `2 << 64`.
+        // slicing at byte 8 into [0i128, 1, 2] yields elements 1<<64 and 
2<<64.
         assert_eq!(array.len(), 2);
         assert_eq!(array.value(0), 1_i128 << 64);
         assert_eq!(array.value(1), 2_i128 << 64);

Reply via email to