Skip to content

Commit

Permalink
Remove undefined behavior in value method of boolean and primitive …
Browse files Browse the repository at this point in the history
…arrays (#644) (#668)

* Remove UB in `value`

* Add safety note

Co-authored-by: Daniël Heres <[email protected]>
  • Loading branch information
alamb and Dandandan authored Aug 8, 2021
1 parent 107a604 commit ead64b7
Show file tree
Hide file tree
Showing 4 changed files with 19 additions and 29 deletions.
6 changes: 4 additions & 2 deletions arrow/src/array/array_boolean.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,11 @@ impl BooleanArray {

/// Returns the boolean value at index `i`.
///
/// Note this doesn't do any bound checking, for performance reason.
/// Panics of offset `i` is out of bounds
pub fn value(&self, i: usize) -> bool {
debug_assert!(i < self.len());
assert!(i < self.len());
// Safety:
// `i < self.len()
unsafe { self.value_unchecked(i) }
}
}
Expand Down
6 changes: 2 additions & 4 deletions arrow/src/array/array_primitive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,12 +101,10 @@ impl<T: ArrowPrimitiveType> PrimitiveArray<T> {

/// Returns the primitive value at index `i`.
///
/// Note this doesn't do any bound checking, for performance reason.
/// # Safety
/// caller must ensure that the passed in offset is less than the array len()
/// Panics of offset `i` is out of bounds
#[inline]
pub fn value(&self, i: usize) -> T::Native {
debug_assert!(i < self.len());
assert!(i < self.len());
unsafe { self.value_unchecked(i) }
}

Expand Down
25 changes: 5 additions & 20 deletions arrow/src/array/array_string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ impl<OffsetSize: StringOffsetSizeTrait> GenericStringArray<OffsetSize> {
/// Returns the element at index
/// # Safety
/// caller is responsible for ensuring that index is within the array bounds
#[inline]
pub unsafe fn value_unchecked(&self, i: usize) -> &str {
let end = self.value_offsets().get_unchecked(i + 1);
let start = self.value_offsets().get_unchecked(i);
Expand All @@ -103,28 +104,12 @@ impl<OffsetSize: StringOffsetSizeTrait> GenericStringArray<OffsetSize> {
}

/// Returns the element at index `i` as &str
#[inline]
pub fn value(&self, i: usize) -> &str {
assert!(i < self.data.len(), "StringArray out of bounds access");
//Soundness: length checked above, offset buffer length is 1 larger than logical array length
let end = unsafe { self.value_offsets().get_unchecked(i + 1) };
let start = unsafe { self.value_offsets().get_unchecked(i) };

// Soundness
// pointer alignment & location is ensured by RawPtrBox
// buffer bounds/offset is ensured by the value_offset invariants
// ISSUE: utf-8 well formedness is not checked
unsafe {
// Safety of `to_isize().unwrap()`
// `start` and `end` are &OffsetSize, which is a generic type that implements the
// OffsetSizeTrait. Currently, only i32 and i64 implement OffsetSizeTrait,
// both of which should cleanly cast to isize on an architecture that supports
// 32/64-bit offsets
let slice = std::slice::from_raw_parts(
self.value_data.as_ptr().offset(start.to_isize().unwrap()),
(*end - *start).to_usize().unwrap(),
);
std::str::from_utf8_unchecked(slice)
}
// Safety:
// `i < self.data.len()
unsafe { self.value_unchecked(i) }
}

fn from_list(v: GenericListArray<OffsetSize>) -> Self {
Expand Down
11 changes: 8 additions & 3 deletions arrow/src/compute/kernels/comparison.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,10 @@ macro_rules! compare_op {
let null_bit_buffer =
combine_option_bitmap($left.data_ref(), $right.data_ref(), $left.len())?;

let comparison = (0..$left.len()).map(|i| $op($left.value(i), $right.value(i)));
// Safety:
// `i < $left.len()` and $left.len() == $right.len()
let comparison = (0..$left.len())
.map(|i| unsafe { $op($left.value_unchecked(i), $right.value_unchecked(i)) });
// same size as $left.len() and $right.len()
let buffer = unsafe { MutableBuffer::from_trusted_len_iter_bool(comparison) };

Expand Down Expand Up @@ -121,8 +124,10 @@ macro_rules! compare_op_primitive {
macro_rules! compare_op_scalar {
($left: expr, $right:expr, $op:expr) => {{
let null_bit_buffer = $left.data().null_buffer().cloned();

let comparison = (0..$left.len()).map(|i| $op($left.value(i), $right));
// Safety:
// `i < $left.len()`
let comparison =
(0..$left.len()).map(|i| unsafe { $op($left.value_unchecked(i), $right) });
// same as $left.len()
let buffer = unsafe { MutableBuffer::from_trusted_len_iter_bool(comparison) };

Expand Down

0 comments on commit ead64b7

Please sign in to comment.