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}