semihosting/c_str.rs
1// SPDX-License-Identifier: Apache-2.0 OR MIT
2
3// Provide safe abstraction (c! macro) for creating static C strings without runtime checks.
4// (c"..." requires Rust 1.77)
5
6/// [`CStr`] literal macro.
7///
8/// **Note:** Since Rust 1.77, this macro is soft-deprecated in favor of C string literals (`c"..."`).
9///
10/// [`Path`] is not available in `core`, so this crate uses [`CStr`] instead in the API where
11/// `std` uses [`Path`]. This macro makes it safe and zero-cost to create a [`CStr`] from a literal.
12///
13/// ```no_run
14/// use semihosting::{c, fs};
15///
16/// fs::write(c!("a.txt"), "abc")?;
17/// // concat! in c! is also supported
18/// fs::write(c!(concat!("b", ".txt")), "def")?;
19/// # Ok::<(), semihosting::io::Error>(())
20/// ```
21///
22/// This macro guarantees the correctness of the input by compile-time validation.
23/// Incorrect input will cause compile-time errors.
24///
25/// ```compile_fail,E0080
26/// use semihosting::c;
27///
28/// let s = c!("ab\0c"); // CStr must not contain any interior nul bytes.
29/// ```
30///
31/// ```text
32/// error[E0080]: evaluation of constant value failed
33/// --> semihosting/src/c_str.rs:48:9
34/// |
35/// 48 | assert!(byte != 0, "input contained interior nul");
36/// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'input contained interior nul', semihosting/src/c_str.rs:48:9
37/// |
38/// note: inside `const_c_str_check`
39/// --> semihosting/src/c_str.rs:48:9
40/// |
41/// 48 | assert!(byte != 0, "input contained interior nul");
42/// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
43/// note: inside `_`
44/// --> src/c_str.rs:18:9
45/// |
46/// 5 | let s = c!("ab\0c"); // CStr must not contain any interior nul bytes.
47/// | ^^^^^^^^^^^
48/// ```
49///
50/// [`CStr`]: core::ffi::CStr
51/// [`Path`]: https://doc.rust-lang.org/std/path/struct.Path.html
52#[macro_export]
53macro_rules! c {
54 ($s:expr) => {{
55 const BYTES: &[u8] = concat!($s, "\0").as_bytes();
56 const _: () = $crate::__private::const_c_str_check(BYTES);
57 #[allow(unused_unsafe)]
58 // SAFETY: we've checked `BYTES` is a valid C string
59 unsafe {
60 $crate::__private::CStr::from_bytes_with_nul_unchecked(BYTES)
61 }
62 }};
63}
64
65// Based on https://github.com/rust-lang/rust/blob/1.84.0/library/core/src/ffi/c_str.rs#L417
66// - bytes must be nul-terminated.
67// - bytes must not contain any interior nul bytes.
68#[doc(hidden)]
69pub const fn const_c_str_check(bytes: &[u8]) {
70 // Saturating so that an empty slice panics in the assert with a good
71 // message, not here due to underflow.
72 let mut i = bytes.len().saturating_sub(1);
73 assert!(!bytes.is_empty() && bytes[i] == 0, "input was not nul-terminated");
74
75 // Ending null byte exists, skip to the rest.
76 while i != 0 {
77 i -= 1;
78 let byte = bytes[i];
79 assert!(byte != 0, "input contained interior nul");
80 }
81}
82
83#[allow(
84 clippy::alloc_instead_of_core,
85 clippy::std_instead_of_alloc,
86 clippy::std_instead_of_core,
87 clippy::undocumented_unsafe_blocks,
88 clippy::wildcard_imports
89)]
90#[cfg(test)]
91mod tests {
92 use core::ffi::CStr;
93
94 #[test]
95 fn test_c_macro() {
96 #[track_caller]
97 fn t(s: &CStr, raw: &[u8]) {
98 assert_eq!(s.to_bytes_with_nul(), raw);
99 }
100 t(c!(""), b"\0");
101 t(c!("a"), b"a\0");
102 t(c!("abc"), b"abc\0");
103 t(c!(concat!("abc", "d")), b"abcd\0");
104 }
105
106 #[test]
107 fn test_is_c_str() {
108 #[track_caller]
109 fn t(bytes: &[u8]) {
110 assert_eq!(
111 std::panic::catch_unwind(|| super::const_c_str_check(bytes)).is_ok(),
112 CStr::from_bytes_with_nul(bytes).is_ok()
113 );
114 }
115 t(b"\0");
116 t(b"a\0");
117 t(b"abc\0");
118 t(b"");
119 t(b"a");
120 t(b"abc");
121 t(b"\0a");
122 t(b"\0a\0");
123 t(b"ab\0c\0");
124 t(b"\0\0");
125 }
126}