Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add concat_slice and mark everything as unsafe #7

Merged
merged 8 commits into from
Feb 5, 2020
137 changes: 129 additions & 8 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#![no_std]
use core::{slice, str};
use core::{mem, slice, str};

/// Error that can occur during [`concat`](fn.concat.html).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
Expand Down Expand Up @@ -55,16 +55,64 @@ pub fn concat<'a>(a: &'a str, b: &'a str) -> Result<&'a str, Error> {
}
}

fn concat_slice<'a>(a: &'a [u8], b: &'a [u8]) -> Result<&'a [u8], Error> {
/// Concatenate two slices if they are adjacent.
///
/// If two slices are adjacent to each other in memory, this function
/// concatenates both, creating a single longer slice. Note that slices of
/// zero-sized types (ZST) are never considered adjacent. Otherwise it would be
/// possible to concatenate a slice to itself.
///
/// # Errors
///
/// Returns `Err` if the two slices aren't adjacent, `a` is after `b`, or if the
/// result is too long to be represented as a slice (size in bytes is larger
/// than `isize::MAX`).
///
/// When T is a ZST then returns `Err(TooLong)` if the total length would overflow
HeroicKatora marked this conversation as resolved.
Show resolved Hide resolved
/// `usize` and `Err(NotAdjacent)` otherwise.
HeroicKatora marked this conversation as resolved.
Show resolved Hide resolved
///
/// # Examples
///
/// Correct usage:
///
/// ```rust
/// # use str_concat::concat_slice;
/// let s = b"0123456789";
HeroicKatora marked this conversation as resolved.
Show resolved Hide resolved
/// assert_eq!(b"0123456", concat_slice(&s[..5], &s[5..7]).unwrap());
/// ```
///
/// Non-adjacent byte slices:
///
/// ```rust
/// # use str_concat::{concat_slice, Error};
/// let s = b"0123456789";
HeroicKatora marked this conversation as resolved.
Show resolved Hide resolved
/// assert_eq!(Err(Error::NotAdjacent), concat_slice(&s[..5], &s[6..7]))
/// ```
///
pub fn concat_slice<'a, T>(a: &'a [T], b: &'a [T]) -> Result<&'a [T], Error> {
let a_ptr = a.as_ptr();
let b_ptr = b.as_ptr();

let a_len = a.len();
let b_len = b.len();

if mem::size_of::<T>() == 0 {
// Not safety critical but we check that the length is as expected.
if a_len.checked_add(b_len).is_none() {
oberien marked this conversation as resolved.
Show resolved Hide resolved
return Err(Error::TooLong)
}

// Never consider ZST slices adjacent. You could otherwise infinitely
// duplicate a non-zero length slice by concatenating it to itself.
return Err(Error::NotAdjacent)
HeroicKatora marked this conversation as resolved.
Show resolved Hide resolved
}

// `max_len <= isize::max_value()`
let max_len = isize::max_value() as usize / mem::size_of::<T>();
HeroicKatora marked this conversation as resolved.
Show resolved Hide resolved

// These should be guaranteed for the slices.
assert!(a_len <= isize::max_value() as usize);
assert!(b_len <= isize::max_value() as usize);
assert!(a_len <= max_len as usize);
assert!(b_len <= max_len as usize);

unsafe {
// https://doc.rust-lang.org/std/primitive.pointer.html#safety-1
Expand All @@ -82,14 +130,16 @@ fn concat_slice<'a>(a: &'a [u8], b: &'a [u8]) -> Result<&'a [u8], Error> {
// can always store the sum of two positive `isize`.
// [1]: https://doc.rust-lang.org/reference/types/numeric.html#machine-dependent-integer-types
let new_len = a_len.checked_add(b_len).unwrap();
if new_len > isize::max_value() as usize {
// Ensure the length is bounded. The bound is strict from the definition of `max_len`
// `new_len <= max_len` <=> `new_len * mem::size_of::<T>() <= isize::max_value()`
if !(new_len <= max_len) {
return Err(Error::TooLong);
}
// https://doc.rust-lang.org/std/slice/fn.from_raw_parts.html#safety
// * slices are adjacent (checked above)
// * no double-free / leak because we work on borrowed data
// * no use-after-free because `a` and `b` have same lifetime
// * the total size is smaller than isize bytes, len is and we have `u8`
// * the total size is smaller than `isize::MAX` bytes, as max_len is rounded down
Ok(slice::from_raw_parts(a_ptr, new_len))
}
}
Expand Down Expand Up @@ -123,7 +173,7 @@ pub fn concat_unordered<'a>(a: &'a str, b: &'a str) -> Result<&'a str, Error> {
let a_ptr = a.as_bytes().as_ptr() as usize;
let a_end_ptr = a_ptr + a.len();
let b_ptr = b.as_bytes().as_ptr() as usize;

// make the order of `a` and `b` not matter
let (a, b) = if a_ptr <= b_ptr && a_end_ptr <= b_ptr {
(a, b)
Expand All @@ -134,9 +184,50 @@ pub fn concat_unordered<'a>(a: &'a str, b: &'a str) -> Result<&'a str, Error> {
concat(a, b)
}

/// Concatenate two adjacent slices no matter their order.
///
/// This is the same as [`concat_slice`] except that it also concatenates `b` to
/// `a` if `b` is in front of `a` (in which case of [`concat_slice`] errors).
/// Keep in mind that slices of ZSTs will still not be concatenated.
///
/// # Examples
///
/// Reversed order:
///
/// ```rust
/// # use str_concat::concat_slice_unordered;
/// let s = b"0123456789";
/// assert_eq!(b"0123456", concat_slice_unordered(&s[5..7], &s[..5]).unwrap());
/// ```
///
/// Normal order:
///
/// ```rust
/// # use str_concat::{concat_slice_unordered, Error};
/// let s = b"0123456789";
/// assert_eq!(b"0123456", concat_slice_unordered(&s[..5], &s[5..7]).unwrap())
/// ```
///
/// [`concat_slice`]: fn.concat_slice.html
pub fn concat_slice_unordered<'a, T>(a: &'a [T], b: &'a [T]) -> Result<&'a [T], Error> {
// add lengths to handle empty cases correctly
let a_ptr = a.as_ptr() as usize;
let a_end_ptr = a_ptr + a.len() * mem::size_of::<T>();
oberien marked this conversation as resolved.
Show resolved Hide resolved
let b_ptr = b.as_ptr() as usize;

// make the order of `a` and `b` not matter
let (a, b) = if a_ptr <= b_ptr && a_end_ptr <= b_ptr {
(a, b)
} else {
(b, a)
};

concat_slice(a, b)
}

#[cfg(test)]
mod tests {
use super::{concat, concat_unordered, Error};
use super::{concat, concat_unordered, concat_slice, concat_slice_unordered, Error};

#[test]
fn simple_success() {
Expand Down Expand Up @@ -186,4 +277,34 @@ mod tests {
assert_eq!(Ok("0123"), concat_unordered(s, &s[4..]));
assert_eq!(Ok("0123"), concat_unordered(&s[4..], s));
}

#[test]
fn typed_slices() {
#[derive(Debug, PartialEq)]
struct T(usize);

let s: &[T] = &[T(0), T(1), T(2), T(3)][..];
assert_eq!(Ok(s), concat_slice(&s[..2], &s[2..]));
assert_eq!(Ok(s), concat_slice_unordered(&s[..2], &s[2..]));
assert_eq!(Ok(s), concat_slice_unordered(&s[2..], &s[..2]));

// One slice empty
assert_eq!(Ok(s), concat_slice(&s[..0], s));
assert_eq!(Ok(s), concat_slice_unordered(&s[..0], s));
assert_eq!(Ok(s), concat_slice_unordered(s, &s[..0]));
assert_eq!(Ok(s), concat_slice(s, &s[4..]));
assert_eq!(Ok(s), concat_slice_unordered(s, &s[4..]));
assert_eq!(Ok(s), concat_slice_unordered(&s[4..], s));
}

#[test]
fn typed_fail() {
#[derive(Debug, PartialEq)]
struct T(usize);

let s: &[T] = &[T(0), T(1), T(2), T(3)][..];
assert_eq!(Err(Error::NotAdjacent), concat_slice(&s[..1], &s[2..]));
assert_eq!(Err(Error::NotAdjacent), concat_slice_unordered(&s[..1], &s[2..]));
assert_eq!(Err(Error::NotAdjacent), concat_slice(&s[2..], &s[..2]));
}
}