Add NulTerminatedInclusive iterator, rewrite/fix strchr/wcschr

This commit is contained in:
Peter Limkilde Svendsen
2024-10-22 22:12:24 +00:00
committed by Jeremy Soller
parent 95affabb18
commit 41264dc8b1
9 changed files with 107 additions and 21 deletions
+19 -9
View File
@@ -6,7 +6,7 @@ use cbitset::BitSet256;
use crate::{
header::{errno::*, signal},
iter::{NulTerminated, SrcDstPtrIter},
iter::{NulTerminated, NulTerminatedInclusive, SrcDstPtrIter},
platform::{self, types::*},
};
@@ -130,16 +130,26 @@ pub unsafe extern "C" fn memset(s: *mut c_void, c: c_int, n: size_t) -> *mut c_v
s
}
/// See <https://pubs.opengroup.org/onlinepubs/7908799/xsh/strchr.html>.
///
/// # Safety
/// The caller is required to ensure that `s` is a valid pointer to a buffer
/// containing at least one nul value. The pointed-to buffer must not be
/// modified for the duration of the call.
#[no_mangle]
pub unsafe extern "C" fn strchr(mut s: *const c_char, c: c_int) -> *mut c_char {
let c = c as c_char;
while *s != 0 {
if *s == c {
return s as *mut c_char;
}
s = s.offset(1);
}
ptr::null_mut()
let c_as_c_char = c as c_char;
// We iterate over non-mut references and thus need to coerce the
// resulting reference via a *const pointer before we can get our *mut.
// SAFETY: the caller is required to ensure that s points to a valid
// nul-terminated buffer.
let ptr: *const c_char =
match unsafe { NulTerminatedInclusive::new(s) }.find(|&&sc| sc == c_as_c_char) {
Some(sc_ref) => sc_ref,
None => ptr::null(),
};
ptr.cast_mut()
}
#[no_mangle]
+17 -10
View File
@@ -12,7 +12,7 @@ use crate::{
time::*,
wctype::*,
},
iter::NulTerminated,
iter::{NulTerminated, NulTerminatedInclusive},
platform::{self, types::*, ERRNO},
};
@@ -435,17 +435,24 @@ pub unsafe extern "C" fn wcscat(ws1: *mut wchar_t, ws2: *const wchar_t) -> *mut
wcsncat(ws1, ws2, usize::MAX)
}
/// See <https://pubs.opengroup.org/onlinepubs/7908799/xsh/wcschr.html>.
///
/// # Safety
/// The caller is required to ensure that `ws` is a valid pointer to a buffer
/// containing at least one nul value. The pointed-to buffer must not be
/// modified for the duration of the call.
#[no_mangle]
pub unsafe extern "C" fn wcschr(ws: *const wchar_t, wc: wchar_t) -> *mut wchar_t {
let mut i = 0;
loop {
if *ws.add(i) == wc {
return ws.add(i) as *mut wchar_t;
} else if *ws.add(i) == 0 {
return ptr::null_mut();
}
i += 1;
}
// We iterate over non-mut references and thus need to coerce the
// resulting reference via a *const pointer before we can get our *mut.
// SAFETY: the caller is required to ensure that ws points to a valid
// nul-terminated buffer.
let ptr: *const wchar_t =
match unsafe { NulTerminatedInclusive::new(ws) }.find(|&&wsc| wsc == wc) {
Some(wsc_ref) => wsc_ref,
None => ptr::null(),
};
ptr.cast_mut()
}
#[no_mangle]
+53
View File
@@ -77,6 +77,59 @@ impl<'a, T: Zero> NulTerminated<'a, T> {
}
}
/// An iterator over a nul-terminated buffer, including the terminating nul.
///
/// Similar to [`NulTerminated`], but includes the terminating nul.
pub struct NulTerminatedInclusive<'a, T: Zero> {
ptr_opt: Option<NonNull<T>>,
phantom: PhantomData<&'a T>,
}
impl<'a, T: Zero> Iterator for NulTerminatedInclusive<'a, T> {
type Item = &'a T;
fn next(&mut self) -> Option<Self::Item> {
if let Some(old_ptr) = self.ptr_opt {
// SAFETY: the caller is required to ensure a valid pointer to a
// 0-terminated buffer is provided, and the zero-check below
// ensures that iteration and pointer increments will stop in
// time.
let val_ref = unsafe { old_ptr.as_ref() };
self.ptr_opt = if val_ref.is_zero() {
None
} else {
// SAFETY: if a terminating nul value has been encountered,
// this will not be called
Some(unsafe { old_ptr.add(1) })
};
Some(val_ref)
} else {
None
}
}
}
impl<'a, T: Zero> NulTerminatedInclusive<'a, T> {
/// Constructs a new iterator, starting at `ptr`, yielding elements of
/// type `&T` up to and including the terminating nul.
///
/// The iterator returns `None` after the terminating nul has been
/// encountered.
///
/// # Safety
/// The provided pointer must be a valid pointer to a buffer of contiguous
/// elements of type `T`, and the value 0 must be present within the
/// buffer at or after `ptr` (not necessarily at the end). The buffer must
/// not be written to for the lifetime of the iterator.
pub unsafe fn new(ptr: *const T) -> Self {
NulTerminatedInclusive {
// NonNull can only wrap only *mut pointers...
ptr_opt: NonNull::new(ptr.cast_mut()),
phantom: PhantomData,
}
}
}
/// A zipped iterator mapping an input iterator to an "out" pointer.
///
/// This is intended to allow safe, iterative writing to an "out pointer".
+1
View File
@@ -127,6 +127,7 @@ EXPECT_NAMES=\
wchar/wcrtomb \
wchar/wcpcpy \
wchar/wcpncpy \
wchar/wcschr \
wchar/wcscspn \
wchar/wcsdup \
wchar/wcsrchr \
@@ -1,3 +1,4 @@
ello
ld
1
(nil)
+2 -1
View File
@@ -6,5 +6,6 @@
int main(void) {
printf("%s\n", strchr("hello", 'e')); // should be ello
printf("%s\n", strchr("world", 'l')); // should be ld
printf("%i\n", strchr("world", 0) == NULL); // should be 1
printf("%s\n", strchr("world", '\0')); // should be an empty, nul-terminated string
printf("%p\n", strchr("world", 'x')); // should be a null pointer
}
+13
View File
@@ -0,0 +1,13 @@
#include <assert.h>
#include <wchar.h>
int main(void) {
wchar_t *haystack = L"Hello World!";
assert(wcschr(haystack, L'H') == haystack);
assert(wcschr(haystack, L'W') == &haystack[6]);
assert(wcschr(haystack, L'\0') == &haystack[12]); // the terminating nul is considered part of the string
assert(wcschr(haystack, L'X') == NULL);
return 0;
}