taskchampion_lib/
string.rs

1use crate::traits::*;
2use crate::util::{string_into_raw_parts, vec_into_raw_parts};
3use std::ffi::{CStr, CString, OsString};
4use std::os::raw::c_char;
5use std::path::PathBuf;
6
7#[ffizz_header::item]
8#[ffizz(order = 200)]
9/// ***** TCString *****
10///
11/// TCString supports passing strings into and out of the TaskChampion API.
12///
13/// # Rust Strings and C Strings
14///
15/// A Rust string can contain embedded NUL characters, while C considers such a character to mark
16/// the end of a string.  Strings containing embedded NULs cannot be represented as a "C string"
17/// and must be accessed using `tc_string_content_and_len` and `tc_string_clone_with_len`.  In
18/// general, these two functions should be used for handling arbitrary data, while more convenient
19/// forms may be used where embedded NUL characters are impossible, such as in static strings.
20///
21/// # UTF-8
22///
23/// TaskChampion expects all strings to be valid UTF-8. `tc_string_…` functions will fail if given
24/// a `*TCString` containing invalid UTF-8.
25///
26/// # Safety
27///
28/// The `ptr` field may be checked for NULL, where documentation indicates this is possible.  All
29/// other fields in a TCString are private and must not be used from C.  They exist in the struct
30/// to ensure proper allocation and alignment.
31///
32/// When a `TCString` appears as a return value or output argument, ownership is passed to the
33/// caller.  The caller must pass that ownership back to another function or free the string.
34///
35/// Any function taking a `TCString` requires:
36///  - the pointer must not be NUL;
37///  - the pointer must be one previously returned from a tc_… function; and
38///  - the memory referenced by the pointer must never be modified by C code.
39///
40/// Unless specified otherwise, TaskChampion functions take ownership of a `TCString` when it is
41/// given as a function argument, and the caller must not use or free TCStrings after passing them
42/// to such API functions.
43///
44/// A TCString with a NULL `ptr` field need not be freed, although tc_free_string will not fail
45/// for such a value.
46///
47/// TCString is not threadsafe.
48///
49/// ```c
50/// typedef struct TCString {
51///   void *ptr;   // opaque, but may be checked for NULL
52///   size_t _u1;  // reserved
53///   size_t _u2;  // reserved
54///   uint8_t _u3; // reserved
55/// } TCString;
56/// ```
57#[repr(C)]
58#[derive(Debug)]
59pub struct TCString {
60    // defined based on the type
61    ptr: *mut libc::c_void,
62    len: usize,
63    cap: usize,
64
65    // type of TCString this represents
66    ty: u8,
67}
68
69// TODO: figure out how to ignore this but still use it in TCString
70/// A discriminator for TCString
71#[repr(u8)]
72enum TCStringType {
73    /// Null.  Nothing is contained in this string.
74    ///
75    /// * `ptr` is NULL.
76    /// * `len` and `cap` are zero.
77    Null = 0,
78
79    /// A CString.
80    ///
81    /// * `ptr` is the result of CString::into_raw, containing a terminating NUL.  It may not be
82    ///   valid UTF-8.
83    /// * `len` and `cap` are zero.
84    CString,
85
86    /// A CStr, referencing memory borrowed from C
87    ///
88    /// * `ptr` points to the string, containing a terminating NUL.  It may not be valid UTF-8.
89    /// * `len` and `cap` are zero.
90    CStr,
91
92    /// A String.
93    ///
94    /// * `ptr`, `len`, and `cap` are as would be returned from String::into_raw_parts.
95    String,
96
97    /// A byte sequence.
98    ///
99    /// * `ptr`, `len`, and `cap` are as would be returned from Vec::into_raw_parts.
100    Bytes,
101}
102
103impl Default for TCString {
104    fn default() -> Self {
105        TCString {
106            ptr: std::ptr::null_mut(),
107            len: 0,
108            cap: 0,
109            ty: TCStringType::Null as u8,
110        }
111    }
112}
113
114impl TCString {
115    pub(crate) fn is_null(&self) -> bool {
116        self.ptr.is_null()
117    }
118}
119
120#[derive(PartialEq, Eq, Debug, Default)]
121pub enum RustString<'a> {
122    #[default]
123    Null,
124    CString(CString),
125    CStr(&'a CStr),
126    String(String),
127    Bytes(Vec<u8>),
128}
129
130impl PassByValue for TCString {
131    type RustType = RustString<'static>;
132
133    unsafe fn from_ctype(self) -> Self::RustType {
134        match self.ty {
135            ty if ty == TCStringType::CString as u8 => {
136                // SAFETY:
137                //  - ptr was derived from CString::into_raw
138                //  - data was not modified since that time (caller promises)
139                RustString::CString(unsafe { CString::from_raw(self.ptr as *mut c_char) })
140            }
141            ty if ty == TCStringType::CStr as u8 => {
142                // SAFETY:
143                //  - ptr was created by CStr::as_ptr
144                //  - data was not modified since that time (caller promises)
145                RustString::CStr(unsafe { CStr::from_ptr(self.ptr as *mut c_char) })
146            }
147            ty if ty == TCStringType::String as u8 => {
148                // SAFETY:
149                //  - ptr was created by string_into_raw_parts
150                //  - data was not modified since that time (caller promises)
151                RustString::String(unsafe {
152                    String::from_raw_parts(self.ptr as *mut u8, self.len, self.cap)
153                })
154            }
155            ty if ty == TCStringType::Bytes as u8 => {
156                // SAFETY:
157                //  - ptr was created by vec_into_raw_parts
158                //  - data was not modified since that time (caller promises)
159                RustString::Bytes(unsafe {
160                    Vec::from_raw_parts(self.ptr as *mut u8, self.len, self.cap)
161                })
162            }
163            _ => RustString::Null,
164        }
165    }
166
167    fn as_ctype(arg: Self::RustType) -> Self {
168        match arg {
169            RustString::Null => Self {
170                ty: TCStringType::Null as u8,
171                ..Default::default()
172            },
173            RustString::CString(cstring) => Self {
174                ty: TCStringType::CString as u8,
175                ptr: cstring.into_raw() as *mut libc::c_void,
176                ..Default::default()
177            },
178            RustString::CStr(cstr) => Self {
179                ty: TCStringType::CStr as u8,
180                ptr: cstr.as_ptr() as *mut libc::c_void,
181                ..Default::default()
182            },
183            RustString::String(string) => {
184                let (ptr, len, cap) = string_into_raw_parts(string);
185                Self {
186                    ty: TCStringType::String as u8,
187                    ptr: ptr as *mut libc::c_void,
188                    len,
189                    cap,
190                }
191            }
192            RustString::Bytes(bytes) => {
193                let (ptr, len, cap) = vec_into_raw_parts(bytes);
194                Self {
195                    ty: TCStringType::Bytes as u8,
196                    ptr: ptr as *mut libc::c_void,
197                    len,
198                    cap,
199                }
200            }
201        }
202    }
203}
204
205impl<'a> RustString<'a> {
206    /// Get a regular Rust &str for this value.
207    pub(crate) fn as_str(&mut self) -> Result<&str, std::str::Utf8Error> {
208        match self {
209            RustString::CString(cstring) => cstring.as_c_str().to_str(),
210            RustString::CStr(cstr) => cstr.to_str(),
211            RustString::String(ref string) => Ok(string.as_ref()),
212            RustString::Bytes(_) => {
213                self.bytes_to_string()?;
214                self.as_str() // now the String variant, so won't recurse
215            }
216            RustString::Null => unreachable!(),
217        }
218    }
219
220    /// Consume this RustString and return an equivalent String, or an error if not
221    /// valid UTF-8.  In the error condition, the original data is lost.
222    pub(crate) fn into_string(mut self) -> Result<String, std::str::Utf8Error> {
223        match self {
224            RustString::CString(cstring) => cstring.into_string().map_err(|e| e.utf8_error()),
225            RustString::CStr(cstr) => cstr.to_str().map(|s| s.to_string()),
226            RustString::String(string) => Ok(string),
227            RustString::Bytes(_) => {
228                self.bytes_to_string()?;
229                self.into_string() // now the String variant, so won't recurse
230            }
231            RustString::Null => unreachable!(),
232        }
233    }
234
235    pub(crate) fn as_bytes(&self) -> &[u8] {
236        match self {
237            RustString::CString(cstring) => cstring.as_bytes(),
238            RustString::CStr(cstr) => cstr.to_bytes(),
239            RustString::String(string) => string.as_bytes(),
240            RustString::Bytes(bytes) => bytes.as_ref(),
241            RustString::Null => unreachable!(),
242        }
243    }
244
245    /// Convert the RustString, in place, from the Bytes to String variant.  On successful return,
246    /// the RustString has variant RustString::String.
247    fn bytes_to_string(&mut self) -> Result<(), std::str::Utf8Error> {
248        let mut owned = RustString::Null;
249        // temporarily swap a Null value into self; we'll swap that back
250        // shortly.
251        std::mem::swap(self, &mut owned);
252        match owned {
253            RustString::Bytes(bytes) => match String::from_utf8(bytes) {
254                Ok(string) => {
255                    *self = RustString::String(string);
256                    Ok(())
257                }
258                Err(e) => {
259                    let (e, bytes) = (e.utf8_error(), e.into_bytes());
260                    // put self back as we found it
261                    *self = RustString::Bytes(bytes);
262                    Err(e)
263                }
264            },
265            _ => {
266                // not bytes, so just swap back
267                std::mem::swap(self, &mut owned);
268                Ok(())
269            }
270        }
271    }
272
273    /// Convert the RustString, in place, into one of the C variants.  If this is not
274    /// possible, such as if the string contains an embedded NUL, then the string
275    /// remains unchanged.
276    fn string_to_cstring(&mut self) {
277        let mut owned = RustString::Null;
278        // temporarily swap a Null value into self; we'll swap that back shortly
279        std::mem::swap(self, &mut owned);
280        match owned {
281            RustString::String(string) => {
282                match CString::new(string) {
283                    Ok(cstring) => {
284                        *self = RustString::CString(cstring);
285                    }
286                    Err(nul_err) => {
287                        // recover the underlying String from the NulError and restore
288                        // the RustString
289                        let original_bytes = nul_err.into_vec();
290                        // SAFETY: original_bytes came from a String moments ago, so still valid utf8
291                        let string = unsafe { String::from_utf8_unchecked(original_bytes) };
292                        *self = RustString::String(string);
293                    }
294                }
295            }
296            _ => {
297                // not a CString, so just swap back
298                std::mem::swap(self, &mut owned);
299            }
300        }
301    }
302
303    pub(crate) fn to_path_buf_mut(&mut self) -> Result<PathBuf, std::str::Utf8Error> {
304        #[cfg(unix)]
305        let path: OsString = {
306            // on UNIX, we can use the bytes directly, without requiring that they
307            // be valid UTF-8.
308            use std::ffi::OsStr;
309            use std::os::unix::ffi::OsStrExt;
310            OsStr::from_bytes(self.as_bytes()).to_os_string()
311        };
312        #[cfg(windows)]
313        let path: OsString = {
314            // on Windows, we assume the filename is valid Unicode, so it can be
315            // represented as UTF-8.
316            OsString::from(self.as_str()?.to_string())
317        };
318        Ok(path.into())
319    }
320}
321
322impl<'a> From<String> for RustString<'a> {
323    fn from(string: String) -> RustString<'a> {
324        RustString::String(string)
325    }
326}
327
328impl From<&str> for RustString<'static> {
329    fn from(string: &str) -> RustString<'static> {
330        RustString::String(string.to_string())
331    }
332}
333
334/// Utility function to borrow a TCString from a pointer arg, modify it,
335/// and restore it.
336///
337/// This implements a kind of "interior mutability", relying on the
338/// single-threaded use of all TC* types.
339///
340/// # SAFETY
341///
342///  - tcstring must not be NULL
343///  - *tcstring must be a valid TCString
344///  - *tcstring must not be accessed by anything else, despite the *const
345unsafe fn wrap<T, F>(tcstring: *const TCString, f: F) -> T
346where
347    F: FnOnce(&mut RustString) -> T,
348{
349    debug_assert!(!tcstring.is_null());
350
351    // SAFETY:
352    //  - we have exclusive to *tcstring (promised by caller)
353    let tcstring = tcstring as *mut TCString;
354
355    // SAFETY:
356    //  - tcstring is not NULL
357    //  - *tcstring is a valid string (promised by caller)
358    let mut rstring = unsafe { TCString::take_val_from_arg(tcstring, TCString::default()) };
359
360    let rv = f(&mut rstring);
361
362    // update the caller's TCString with the updated RustString
363    // SAFETY:
364    //  - tcstring is not NULL (we just took from it)
365    //  - tcstring points to valid memory (we just took from it)
366    unsafe { TCString::val_to_arg_out(rstring, tcstring) };
367
368    rv
369}
370
371#[ffizz_header::item]
372#[ffizz(order = 210)]
373/// ***** TCStringList *****
374///
375/// TCStringList represents a list of strings.
376///
377/// The content of this struct must be treated as read-only.
378///
379/// ```c
380/// typedef struct TCStringList {
381///   // number of strings in items
382///   size_t len;
383///   // reserved
384///   size_t _u1;
385///   // TCStringList representing each string. These remain owned by the TCStringList instance and will
386///   // be freed by tc_string_list_free.  This pointer is never NULL for a valid TCStringList, and the
387///   // *TCStringList at indexes 0..len-1 are not NULL.
388///   struct TCString *items;
389/// } TCStringList;
390/// ```
391#[repr(C)]
392pub struct TCStringList {
393    len: libc::size_t,
394    /// total size of items (internal use only)
395    capacity: libc::size_t,
396    items: *mut TCString,
397}
398
399impl CList for TCStringList {
400    type Element = TCString;
401
402    unsafe fn from_raw_parts(items: *mut Self::Element, len: usize, cap: usize) -> Self {
403        TCStringList {
404            len,
405            capacity: cap,
406            items,
407        }
408    }
409
410    fn slice(&mut self) -> &mut [Self::Element] {
411        // SAFETY:
412        //  - because we have &mut self, we have read/write access to items[0..len]
413        //  - all items are properly initialized Element's
414        //  - return value lifetime is equal to &mmut self's, so access is exclusive
415        //  - items and len came from Vec, so total size is < isize::MAX
416        unsafe { std::slice::from_raw_parts_mut(self.items, self.len) }
417    }
418
419    fn into_raw_parts(self) -> (*mut Self::Element, usize, usize) {
420        (self.items, self.len, self.capacity)
421    }
422}
423
424#[ffizz_header::item]
425#[ffizz(order = 201)]
426/// Create a new TCString referencing the given C string.  The C string must remain valid and
427/// unchanged until after the TCString is freed.  It's typically easiest to ensure this by using a
428/// static string.
429///
430/// NOTE: this function does _not_ take responsibility for freeing the given C string.  The
431/// given string can be freed once the TCString referencing it has been freed.
432///
433/// For example:
434///
435/// ```text
436/// char *url = get_item_url(..); // dynamically allocate C string
437/// tc_task_annotate(task, tc_string_borrow(url)); // TCString created, passed, and freed
438/// free(url); // string is no longer referenced and can be freed
439/// ```
440///
441/// ```c
442/// EXTERN_C struct TCString tc_string_borrow(const char *cstr);
443/// ```
444#[no_mangle]
445pub unsafe extern "C" fn tc_string_borrow(cstr: *const libc::c_char) -> TCString {
446    debug_assert!(!cstr.is_null());
447    // SAFETY:
448    //  - cstr is not NULL (promised by caller, verified by assertion)
449    //  - cstr's lifetime exceeds that of the TCString (promised by caller)
450    //  - cstr contains a valid NUL terminator (promised by caller)
451    //  - cstr's content will not change before it is destroyed (promised by caller)
452    let cstr: &CStr = unsafe { CStr::from_ptr(cstr) };
453    // SAFETY:
454    //  - caller promises to free this string
455    unsafe { TCString::return_val(RustString::CStr(cstr)) }
456}
457
458#[ffizz_header::item]
459#[ffizz(order = 201)]
460/// Create a new TCString by cloning the content of the given C string.  The resulting TCString
461/// is independent of the given string, which can be freed or overwritten immediately.
462///
463/// ```c
464/// EXTERN_C struct TCString tc_string_clone(const char *cstr);
465/// ```
466#[no_mangle]
467pub unsafe extern "C" fn tc_string_clone(cstr: *const libc::c_char) -> TCString {
468    debug_assert!(!cstr.is_null());
469    // SAFETY:
470    //  - cstr is not NULL (promised by caller, verified by assertion)
471    //  - cstr's lifetime exceeds that of this function (by C convention)
472    //  - cstr contains a valid NUL terminator (promised by caller)
473    //  - cstr's content will not change before it is destroyed (by C convention)
474    let cstr: &CStr = unsafe { CStr::from_ptr(cstr) };
475    let cstring: CString = cstr.into();
476    // SAFETY:
477    //  - caller promises to free this string
478    unsafe { TCString::return_val(RustString::CString(cstring)) }
479}
480
481#[ffizz_header::item]
482#[ffizz(order = 201)]
483/// Create a new TCString containing the given string with the given length. This allows creation
484/// of strings containing embedded NUL characters.  As with `tc_string_clone`, the resulting
485/// TCString is independent of the passed buffer, which may be reused or freed immediately.
486///
487/// The length should _not_ include any trailing NUL.
488///
489/// The given length must be less than half the maximum value of usize.
490///
491/// ```c
492/// EXTERN_C struct TCString tc_string_clone_with_len(const char *buf, size_t len);
493/// ```
494#[no_mangle]
495pub unsafe extern "C" fn tc_string_clone_with_len(
496    buf: *const libc::c_char,
497    len: usize,
498) -> TCString {
499    debug_assert!(!buf.is_null());
500    debug_assert!(len < isize::MAX as usize);
501    // SAFETY:
502    //  - buf is valid for len bytes (by C convention)
503    //  - (no alignment requirements for a byte slice)
504    //  - content of buf will not be mutated during the lifetime of this slice (lifetime
505    //    does not outlive this function call)
506    //  - the length of the buffer is less than isize::MAX (promised by caller)
507    let slice = unsafe { std::slice::from_raw_parts(buf as *const u8, len) };
508
509    // allocate and copy into Rust-controlled memory
510    let vec = slice.to_vec();
511
512    // SAFETY:
513    //  - caller promises to free this string
514    unsafe { TCString::return_val(RustString::Bytes(vec)) }
515}
516
517#[ffizz_header::item]
518#[ffizz(order = 201)]
519/// Get the content of the string as a regular C string.  The given string must be valid.  The
520/// returned value is NULL if the string contains NUL bytes or (in some cases) invalid UTF-8.  The
521/// returned C string is valid until the TCString is freed or passed to another TC API function.
522///
523/// In general, prefer [`tc_string_content_with_len`] except when it's certain that the string is
524/// valid and NUL-free.
525///
526/// This function takes the TCString by pointer because it may be modified in-place to add a NUL
527/// terminator.  The pointer must not be NULL.
528///
529/// This function does _not_ take ownership of the TCString.
530///
531/// ```c
532/// EXTERN_C const char *tc_string_content(const struct TCString *tcstring);
533/// ```
534#[no_mangle]
535pub unsafe extern "C" fn tc_string_content(tcstring: *const TCString) -> *const libc::c_char {
536    // SAFETY;
537    //  - tcstring is not NULL (promised by caller)
538    //  - *tcstring is valid (promised by caller)
539    //  - *tcstring is not accessed concurrently (single-threaded)
540    unsafe {
541        wrap(tcstring, |rstring| {
542            // try to eliminate the Bytes variant.  If this fails, we'll return NULL
543            // below, so the error is ignorable.
544            let _ = rstring.bytes_to_string();
545
546            // and eliminate the String variant
547            rstring.string_to_cstring();
548
549            match &rstring {
550                RustString::CString(cstring) => cstring.as_ptr(),
551                RustString::String(_) => std::ptr::null(), // string_to_cstring failed
552                RustString::CStr(cstr) => cstr.as_ptr(),
553                RustString::Bytes(_) => std::ptr::null(), // already returned above
554                RustString::Null => unreachable!(),
555            }
556        })
557    }
558}
559
560#[ffizz_header::item]
561#[ffizz(order = 201)]
562/// Get the content of the string as a pointer and length.  The given string must not be NULL.
563/// This function can return any string, even one including NUL bytes or invalid UTF-8.  The
564/// returned buffer is valid until the TCString is freed or passed to another TaskChampio
565/// function.
566///
567/// This function takes the TCString by pointer because it may be modified in-place to add a NUL
568/// terminator.  The pointer must not be NULL.
569///
570/// This function does _not_ take ownership of the TCString.
571///
572/// ```c
573/// EXTERN_C const char *tc_string_content_with_len(const struct TCString *tcstring, size_t *len_out);
574/// ```
575#[no_mangle]
576pub unsafe extern "C" fn tc_string_content_with_len(
577    tcstring: *const TCString,
578    len_out: *mut usize,
579) -> *const libc::c_char {
580    // SAFETY;
581    //  - tcstring is not NULL (promised by caller)
582    //  - *tcstring is valid (promised by caller)
583    //  - *tcstring is not accessed concurrently (single-threaded)
584    unsafe {
585        wrap(tcstring, |rstring| {
586            let bytes = rstring.as_bytes();
587
588            // SAFETY:
589            //  - len_out is not NULL (promised by caller)
590            //  - len_out points to valid memory (promised by caller)
591            //  - len_out is properly aligned (C convention)
592            usize::val_to_arg_out(bytes.len(), len_out);
593            bytes.as_ptr() as *const libc::c_char
594        })
595    }
596}
597
598#[ffizz_header::item]
599#[ffizz(order = 201)]
600/// Free a TCString.  The given string must not be NULL.  The string must not be used
601/// after this function returns, and must not be freed more than once.
602///
603/// ```c
604/// EXTERN_C void tc_string_free(struct TCString *tcstring);
605/// ```
606#[no_mangle]
607pub unsafe extern "C" fn tc_string_free(tcstring: *mut TCString) {
608    // SAFETY:
609    //  - tcstring is not NULL (promised by caller)
610    //  - caller is exclusive owner of tcstring (promised by caller)
611    drop(unsafe { TCString::take_val_from_arg(tcstring, TCString::default()) });
612}
613
614#[ffizz_header::item]
615#[ffizz(order = 211)]
616/// Free a TCStringList instance.  The instance, and all TCStringList it contains, must not be used after
617/// this call.
618///
619/// When this call returns, the `items` pointer will be NULL, signalling an invalid TCStringList.
620///
621/// ```c
622/// EXTERN_C void tc_string_list_free(struct TCStringList *tcstrings);
623/// ```
624#[no_mangle]
625pub unsafe extern "C" fn tc_string_list_free(tcstrings: *mut TCStringList) {
626    // SAFETY:
627    //  - tcstrings is not NULL and points to a valid TCStringList (caller is not allowed to
628    //    modify the list)
629    //  - caller promises not to use the value after return
630    unsafe { drop_value_list(tcstrings) };
631}
632
633#[cfg(test)]
634mod test {
635    use super::*;
636    use pretty_assertions::assert_eq;
637
638    #[test]
639    fn empty_list_has_non_null_pointer() {
640        let tcstrings = unsafe { TCStringList::return_val(Vec::new()) };
641        assert!(!tcstrings.items.is_null());
642        assert_eq!(tcstrings.len, 0);
643        assert_eq!(tcstrings.capacity, 0);
644    }
645
646    #[test]
647    fn free_sets_null_pointer() {
648        let mut tcstrings = unsafe { TCStringList::return_val(Vec::new()) };
649        // SAFETY: testing expected behavior
650        unsafe { tc_string_list_free(&mut tcstrings) };
651        assert!(tcstrings.items.is_null());
652        assert_eq!(tcstrings.len, 0);
653        assert_eq!(tcstrings.capacity, 0);
654    }
655
656    const INVALID_UTF8: &[u8] = b"abc\xf0\x28\x8c\x28";
657
658    fn make_cstring() -> RustString<'static> {
659        RustString::CString(CString::new("a string").unwrap())
660    }
661
662    fn make_cstr() -> RustString<'static> {
663        let cstr = CStr::from_bytes_with_nul(b"a string\0").unwrap();
664        RustString::CStr(cstr)
665    }
666
667    fn make_string() -> RustString<'static> {
668        RustString::String("a string".into())
669    }
670
671    fn make_string_with_nul() -> RustString<'static> {
672        RustString::String("a \0 nul!".into())
673    }
674
675    fn make_invalid_bytes() -> RustString<'static> {
676        RustString::Bytes(INVALID_UTF8.to_vec())
677    }
678
679    fn make_bytes() -> RustString<'static> {
680        RustString::Bytes(b"bytes".to_vec())
681    }
682
683    #[test]
684    fn cstring_as_str() {
685        assert_eq!(make_cstring().as_str().unwrap(), "a string");
686    }
687
688    #[test]
689    fn cstr_as_str() {
690        assert_eq!(make_cstr().as_str().unwrap(), "a string");
691    }
692
693    #[test]
694    fn string_as_str() {
695        assert_eq!(make_string().as_str().unwrap(), "a string");
696    }
697
698    #[test]
699    fn string_with_nul_as_str() {
700        assert_eq!(make_string_with_nul().as_str().unwrap(), "a \0 nul!");
701    }
702
703    #[test]
704    fn invalid_bytes_as_str() {
705        let as_str_err = make_invalid_bytes().as_str().unwrap_err();
706        assert_eq!(as_str_err.valid_up_to(), 3); // "abc" is valid
707    }
708
709    #[test]
710    fn valid_bytes_as_str() {
711        assert_eq!(make_bytes().as_str().unwrap(), "bytes");
712    }
713
714    #[test]
715    fn cstring_as_bytes() {
716        assert_eq!(make_cstring().as_bytes(), b"a string");
717    }
718
719    #[test]
720    fn cstr_as_bytes() {
721        assert_eq!(make_cstr().as_bytes(), b"a string");
722    }
723
724    #[test]
725    fn string_as_bytes() {
726        assert_eq!(make_string().as_bytes(), b"a string");
727    }
728
729    #[test]
730    fn string_with_nul_as_bytes() {
731        assert_eq!(make_string_with_nul().as_bytes(), b"a \0 nul!");
732    }
733
734    #[test]
735    fn invalid_bytes_as_bytes() {
736        assert_eq!(make_invalid_bytes().as_bytes(), INVALID_UTF8);
737    }
738
739    #[test]
740    fn cstring_string_to_cstring() {
741        let mut tcstring = make_cstring();
742        tcstring.string_to_cstring();
743        assert_eq!(tcstring, make_cstring()); // unchanged
744    }
745
746    #[test]
747    fn cstr_string_to_cstring() {
748        let mut tcstring = make_cstr();
749        tcstring.string_to_cstring();
750        assert_eq!(tcstring, make_cstr()); // unchanged
751    }
752
753    #[test]
754    fn string_string_to_cstring() {
755        let mut tcstring = make_string();
756        tcstring.string_to_cstring();
757        assert_eq!(tcstring, make_cstring()); // converted to CString, same content
758    }
759
760    #[test]
761    fn string_with_nul_string_to_cstring() {
762        let mut tcstring = make_string_with_nul();
763        tcstring.string_to_cstring();
764        assert_eq!(tcstring, make_string_with_nul()); // unchanged
765    }
766
767    #[test]
768    fn bytes_string_to_cstring() {
769        let mut tcstring = make_bytes();
770        tcstring.string_to_cstring();
771        assert_eq!(tcstring, make_bytes()); // unchanged
772    }
773}