mbox/
sentinel.rs

1//! Sentinel-terminated types.
2
3use libc::{c_char, strlen};
4#[cfg(feature = "stable_deref_trait")]
5use stable_deref_trait::StableDeref;
6
7use std::borrow::{Borrow, BorrowMut};
8use std::convert::{AsMut, AsRef};
9use std::default::Default;
10#[cfg(feature = "std")]
11use std::ffi::CStr;
12use std::hash::{Hash, Hasher};
13use std::iter::once;
14use std::ops::{Deref, DerefMut};
15use std::ptr::{copy_nonoverlapping, null, null_mut, write};
16use std::str::Utf8Error;
17
18use crate::internal::gen_malloc;
19use crate::mbox::MBox;
20
21#[cfg(all(test, not(windows)))]
22use crate::internal::DropCounter;
23
24/// Implemented for types which has a sentinel value.
25pub trait Sentinel: Eq {
26    /// Obtains the sentinel value.
27    const SENTINEL: Self;
28}
29
30impl<T> Sentinel for *const T {
31    const SENTINEL: Self = null();
32}
33
34impl<T> Sentinel for *mut T {
35    const SENTINEL: Self = null_mut();
36}
37
38impl<T: Eq> Sentinel for Option<T> {
39    const SENTINEL: Self = None;
40}
41
42macro_rules! impl_zero_for_sentinel {
43    ($($ty:ty)+) => {
44        $(impl Sentinel for $ty {
45            const SENTINEL: Self = 0;
46        })+
47    }
48}
49
50impl_zero_for_sentinel!(u8 i8 u16 i16 u32 i32 u64 i64 u128 i128 usize isize);
51
52/// A `malloc`-backed array with an explicit sentinel at the end.
53#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
54pub struct MArray<T: Sentinel>(MBox<[T]>);
55
56/// A `malloc`-backed null-terminated string (similar to `CString`).
57#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
58pub struct MString(MBox<str>);
59
60impl<T: Sentinel> MArray<T> {
61    /// Constructs a new malloc-backed slice from a pointer to the null-terminated array.
62    ///
63    /// # Safety
64    ///
65    /// The `ptr` must be allocated via `malloc()`, `calloc()` or similar C functions that is
66    /// expected to be deallocated using `free()`. It must not be null. The content of the pointer
67    /// must be already initialized, and terminated by `T::SENTINEL`. The array's ownership is
68    /// passed into the result, and thus should not be used after this function returns.
69    pub unsafe fn from_raw(base: *mut T) -> MArray<T> {
70        let mut len = 0;
71        while *base.add(len) != T::SENTINEL {
72            len += 1;
73        }
74        MArray(MBox::from_raw_parts(base, len + 1))
75    }
76
77    /// Converts into an `MBox` including the sentinel.
78    pub fn into_mbox_with_sentinel(self) -> MBox<[T]> {
79        self.0
80    }
81
82    /// Converts into an `MBox` excluding the sentinel.
83    pub fn into_mbox(self) -> MBox<[T]> {
84        let (ptr, len) = self.0.into_raw_parts();
85        unsafe { MBox::from_raw_parts(ptr, len - 1) }
86    }
87}
88
89impl<T: Sentinel + Clone> MArray<T> {
90    /// Creates a null-terminated array from the clone of a slice.
91    pub fn from_slice(slice: &[T]) -> MArray<T> {
92        MArray(slice.iter().cloned().chain(once(T::SENTINEL)).collect())
93    }
94}
95
96impl MString {
97    /// Constructs a new malloc-backed string from a null-terminated C string.
98    ///
99    /// # Safety
100    ///
101    /// The `base` must be allocated via `malloc()`, `calloc()` or similar C functions that is
102    /// expected to be deallocated using `free()`. It must not be null. The content of the string
103    /// must be already initialized, and terminated by `'\0'`. The string's ownership is passed into
104    /// the result, and thus should not be used after this function returns.
105    ///
106    /// The string must be valid UTF-8.
107    pub unsafe fn from_raw_unchecked(base: *mut c_char) -> MString {
108        let len = strlen(base);
109        MString(MBox::from_raw_utf8_parts_unchecked(
110            base as *mut u8,
111            len + 1,
112        ))
113    }
114
115    /// Constructs a new malloc-backed string from a null-terminated C string. Errors with
116    /// `Utf8Error` if the string is not in valid UTF-8.
117    ///
118    /// # Safety
119    ///
120    /// The `base` must be allocated via `malloc()`, `calloc()` or similar C functions that is
121    /// expected to be deallocated using `free()`. It must not be null. The content of the string
122    /// must be already initialized, and terminated by `'\0'`. The string's ownership is passed into
123    /// the result, and thus should not be used after this function returns.
124    pub unsafe fn from_raw(base: *mut c_char) -> Result<MString, Utf8Error> {
125        let len = strlen(base);
126        let mbox = MBox::from_raw_utf8_parts(base as *mut u8, len + 1)?;
127        Ok(MString(mbox))
128    }
129
130    pub fn into_bytes(self) -> MArray<u8> {
131        MArray(self.0.into_bytes())
132    }
133
134    /// Converts into an `MBox` including the sentinel.
135    pub fn into_mbox_with_sentinel(self) -> MBox<str> {
136        self.0
137    }
138
139    /// Converts into an `MBox` excluding the sentinel.
140    pub fn into_mbox(self) -> MBox<str> {
141        unsafe { MBox::from_utf8_unchecked(self.into_bytes().into_mbox()) }
142    }
143
144    /// Converts to a C string. This allows users to borrow an MString in FFI code.
145    #[cfg(all(feature = "std"))]
146    pub fn as_c_str(&self) -> &CStr {
147        unsafe { CStr::from_bytes_with_nul_unchecked(self.0.as_bytes()) }
148    }
149
150    /// Obtains the raw bytes including the sentinel.
151    pub fn as_bytes_with_sentinel(&self) -> &[u8] {
152        self.0.as_bytes()
153    }
154}
155
156impl From<&str> for MString {
157    /// Creates a null-terminated string from the clone of a string.
158    fn from(string: &str) -> MString {
159        unsafe {
160            let len = string.len();
161            let ptr = gen_malloc(len + 1).as_ptr();
162            copy_nonoverlapping(string.as_ptr(), ptr, len);
163            write(ptr.add(len), 0);
164            MString(MBox::from_raw_utf8_parts_unchecked(ptr, len + 1))
165        }
166    }
167}
168
169impl<T: Sentinel> Deref for MArray<T> {
170    type Target = [T];
171    fn deref(&self) -> &[T] {
172        let actual_len = self.0.len() - 1;
173        &self.0[..actual_len]
174    }
175}
176
177impl Deref for MString {
178    type Target = str;
179    fn deref(&self) -> &str {
180        let actual_len = self.0.len() - 1;
181        &self.0[..actual_len]
182    }
183}
184
185#[cfg(feature = "stable_deref_trait")]
186unsafe impl<T: Sentinel> StableDeref for MArray<T> {}
187#[cfg(feature = "stable_deref_trait")]
188unsafe impl StableDeref for MString {}
189
190impl<T: Sentinel> DerefMut for MArray<T> {
191    fn deref_mut(&mut self) -> &mut [T] {
192        let actual_len = self.0.len() - 1;
193        &mut self.0[..actual_len]
194    }
195}
196
197#[allow(clippy::derive_hash_xor_eq)]
198impl Hash for MString {
199    fn hash<H: Hasher>(&self, state: &mut H) {
200        self.deref().hash(state);
201    }
202}
203
204#[allow(clippy::derive_hash_xor_eq)]
205impl<T: Sentinel + Hash> Hash for MArray<T> {
206    fn hash<H: Hasher>(&self, state: &mut H) {
207        self.deref().hash(state);
208    }
209}
210
211impl DerefMut for MString {
212    fn deref_mut(&mut self) -> &mut str {
213        let actual_len = self.0.len() - 1;
214        &mut self.0[..actual_len]
215    }
216}
217
218impl<T: Sentinel> Default for MArray<T> {
219    fn default() -> Self {
220        unsafe {
221            let arr = gen_malloc(1).as_ptr();
222            write(arr, T::SENTINEL);
223            MArray(MBox::from_raw_parts(arr, 1))
224        }
225    }
226}
227
228impl Default for MString {
229    fn default() -> Self {
230        unsafe {
231            let arr = gen_malloc(1).as_ptr();
232            write(arr, 0);
233            MString(MBox::from_raw_utf8_parts_unchecked(arr, 1))
234        }
235    }
236}
237
238impl<T: Sentinel> AsRef<[T]> for MArray<T> {
239    fn as_ref(&self) -> &[T] {
240        self
241    }
242}
243
244impl<T: Sentinel> AsMut<[T]> for MArray<T> {
245    fn as_mut(&mut self) -> &mut [T] {
246        self
247    }
248}
249
250impl<T: Sentinel> Borrow<[T]> for MArray<T> {
251    fn borrow(&self) -> &[T] {
252        self
253    }
254}
255
256impl<T: Sentinel> BorrowMut<[T]> for MArray<T> {
257    fn borrow_mut(&mut self) -> &mut [T] {
258        self
259    }
260}
261
262impl AsRef<str> for MString {
263    fn as_ref(&self) -> &str {
264        self
265    }
266}
267
268impl AsMut<str> for MString {
269    fn as_mut(&mut self) -> &mut str {
270        self
271    }
272}
273
274impl Borrow<str> for MString {
275    fn borrow(&self) -> &str {
276        self
277    }
278}
279
280impl BorrowMut<str> for MString {
281    fn borrow_mut(&mut self) -> &mut str {
282        self
283    }
284}
285
286#[cfg(feature = "std")]
287impl AsRef<CStr> for MString {
288    fn as_ref(&self) -> &CStr {
289        self.as_c_str()
290    }
291}
292
293#[test]
294fn test_array() {
295    unsafe {
296        let src = gen_malloc::<u8>(6).as_ptr();
297        *src.offset(0) = 56;
298        *src.offset(1) = 18;
299        *src.offset(2) = 200;
300        *src.offset(3) = 0;
301        *src.offset(4) = 105;
302        *src.offset(5) = 0;
303
304        let mut array = MArray::from_raw(src);
305        assert_eq!(&*array, &[56u8, 18, 200]);
306        array[1] = 19;
307        assert_eq!(*src.offset(1), 19);
308    }
309}
310
311#[cfg(not(windows))]
312#[test]
313fn test_array_with_drop() {
314    let counter = DropCounter::default();
315    unsafe {
316        let src = gen_malloc::<Option<DropCounter>>(3).as_ptr();
317        write(src.offset(0), Some(counter.clone()));
318        write(src.offset(1), Some(counter.clone()));
319        write(src.offset(2), None);
320
321        counter.assert_eq(0);
322        let array = MArray::from_raw(src);
323        assert_eq!(array.len(), 2);
324        array[0].as_ref().unwrap().assert_eq(0);
325        array[1].as_ref().unwrap().assert_eq(0);
326    }
327    counter.assert_eq(2);
328}
329
330#[test]
331fn test_string() {
332    unsafe {
333        let src = gen_malloc::<c_char>(5).as_ptr();
334        *src.offset(0) = 0x61;
335        *src.offset(1) = -0x19i8 as c_char;
336        *src.offset(2) = -0x6ci8 as c_char;
337        *src.offset(3) = -0x4ei8 as c_char;
338        *src.offset(4) = 0;
339
340        let string = MString::from_raw_unchecked(src);
341        assert_eq!(&*string, "a甲");
342    }
343}
344
345#[test]
346fn test_non_utf8_string() {
347    unsafe {
348        let src = gen_malloc::<c_char>(2).as_ptr();
349        *src.offset(0) = -1i8 as c_char;
350        *src.offset(1) = 0;
351
352        let string = MString::from_raw(src);
353        assert!(string.is_err());
354
355        let src2 = gen_malloc::<c_char>(2).as_ptr();
356        *src2.offset(0) = 1;
357        *src2.offset(1) = 0;
358
359        let string2 = MString::from_raw(src2);
360        assert_eq!(string2.unwrap().deref(), "\u{1}");
361    }
362}
363
364#[cfg(feature = "std")]
365#[test]
366fn test_c_str() {
367    unsafe {
368        let src = gen_malloc::<c_char>(2).as_ptr();
369        *src.offset(0) = 1;
370        *src.offset(1) = 0;
371        let string = MString::from_raw_unchecked(src);
372        let c_str = string.as_c_str();
373        assert_eq!(c_str, CStr::from_ptr(b"\x01\x00".as_ptr() as *const c_char));
374    }
375}
376
377#[cfg(not(windows))]
378#[test]
379fn test_array_into_mbox() {
380    let first = MArray::from_slice(&[123, 456, 789]);
381    let second = first.clone();
382
383    assert_eq!(&*first.into_mbox(), &[123, 456, 789]);
384    assert_eq!(&*second.into_mbox_with_sentinel(), &[123, 456, 789, 0]);
385}
386
387#[test]
388fn test_string_into_mbox() {
389    let first = MString::from("abcde");
390    let second = first.clone();
391
392    assert_eq!(first.as_bytes(), b"abcde");
393    assert_eq!(&*first.into_mbox(), "abcde");
394    assert_eq!(second.as_bytes_with_sentinel(), b"abcde\0");
395    assert_eq!(&*second.into_mbox_with_sentinel(), "abcde\0");
396}
397
398#[cfg(not(windows))]
399#[test]
400fn test_default_array() {
401    let arr = MArray::<u64>::default();
402    assert_eq!(arr.into_mbox_with_sentinel(), MBox::from_slice(&[0u64]));
403}
404
405#[test]
406fn test_default_string() {
407    let string = MString::default();
408    assert_eq!(string.into_mbox_with_sentinel(), MBox::<str>::from("\0"));
409}
410
411#[cfg(feature = "std")]
412#[test]
413fn test_hash_string() {
414    use std::collections::HashSet;
415
416    let mut hs: HashSet<MString> = HashSet::new();
417    hs.insert(MString::from("a"));
418    hs.insert(MString::from("bcd"));
419
420    let hs = hs;
421    assert!(hs.contains("bcd"));
422    assert!(!hs.contains("ef"));
423    assert!(!hs.contains("bcd\0"));
424    assert!(hs.contains("a"));
425    assert!(hs.contains(&MString::from("bcd")));
426    assert!(!hs.contains(&MString::from("ef")));
427    assert!(hs.contains(&MString::from("a")));
428}
429
430#[cfg(feature = "std")]
431#[test]
432fn test_hash_array() {
433    use std::collections::HashSet;
434
435    let mut hs: HashSet<MArray<u8>> = HashSet::new();
436    hs.insert(MArray::from_slice(b"a"));
437    hs.insert(MArray::from_slice(b"bcd"));
438
439    let hs = hs;
440    assert!(hs.contains(&b"bcd"[..]));
441    assert!(!hs.contains(&b"ef"[..]));
442    assert!(!hs.contains(&b"bcd\0"[..]));
443    assert!(hs.contains(&b"a"[..]));
444    assert!(hs.contains(&MArray::from_slice(b"bcd")));
445    assert!(!hs.contains(&MArray::from_slice(b"ef")));
446    assert!(hs.contains(&MArray::from_slice(b"a")));
447}