printf_wrap/lib.rs
1// Copyright (c) 2021-2022 The Pennsylvania State University and the project contributors.
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4//! Types and functions for the safe use of `printf(3)`-style format strings.
5//!
6//! `printf(3)` ([POSIX], [Linux], and [FreeBSD] man pages) and its variants
7//! present some challenges for memory-safe use from Rust:
8//! the passed-in arguments
9//! are interpreted as different types based on the content of the format
10//! string, with each conversion specification (e.g., `%s`) consuming up to
11//! three arguments (e.g, `%*.*d`), and the `%n` specification even writing
12//! to memory!
13//! For memory- and type-safe use, we must make sure a given format string
14//! is only used in invocations with the correct argument number and type.
15//!
16//! This crate contains mechanisms you can use to ensure such agreement.
17//! [`PrintfFmt`]`<(A, B, ...)>` wraps a format string, doing verification to ensure
18//! it can be safely used with the list of arguments corresponding to
19//! the tuple of types
20//! `(A: `[`PrintfArgument`]`, B: `[`PrintfArgument`]`, ...)`.
21//! This verification may be performed at
22//! compile time, allowing for safe wrappers with zero runtime overhead.
23//!
24//! A brief example of how this crate might be used:
25//!
26//! ```no_run
27//! use printf_wrap::{PrintfFmt, PrintfArgument};
28//! use libc::{c_int, printf};
29//!
30//! /// Safe wrapper for calling printf with two value arguments.
31//! pub fn printf_with_2_args<T, U>(fmt: PrintfFmt<(T, U)>, arg1: T, arg2: U) -> c_int
32//! where
33//! T: PrintfArgument,
34//! U: PrintfArgument,
35//! {
36//! unsafe { printf(fmt.as_ptr(), arg1.as_c_val(), arg2.as_c_val()) }
37//! }
38//!
39//! fn main() {
40//! const MY_FMT: PrintfFmt<(u32, i32)> =
41//! PrintfFmt::new_or_panic("unsigned = %u, signed = %d\0");
42//! printf_with_2_args(MY_FMT, 42, -7);
43//! }
44//! ```
45//!
46//! The
47#![cfg_attr(any(feature = "example", all(doc, feature = "doccfg")), doc = " [`example`]")]
48#![cfg_attr(not(any(feature = "example", all(doc, feature = "doccfg"))), doc = " `example`")]
49//! module has a more worked-out example of this crate's use, using
50//! `printf(3)` and `snprintf(3)` as the functions to wrap.
51//!
52//! Only a subset of all possible `printf` format strings are accepted:
53//!
54//! * Numbered argument conversion specifications (e.g., `%2$d`) are not
55//! supported.
56//! * `%lc`, `%ls`, `%C`, `%S`, and `%L[fFeEgGaA]` are not supported.
57//! * `%n` is not supported.
58//! * The `j`, `z`, and `t` length modifiers are only supported
59//! if crate feature **`libc`** is enabled.
60//!
61//! [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/printf.html
62//! [Linux]: https://man7.org/linux/man-pages/man3/printf.3.html
63//! [FreeBSD]: https://www.freebsd.org/cgi/man.cgi?printf%283%29
64
65#![no_std]
66#![cfg_attr(feature = "doccfg", feature(doc_cfg))]
67
68// We only aim for compatibility with printf(3) as specified in POSIX:
69#[cfg(unix)]
70/// Marker structure used to ensure this crate only sucessfully compiles for
71/// known-compatible systems.
72#[derive(Clone, Copy, Debug)]
73struct CompatibleSystem {}
74
75// We optionally use `libc` for types and (in the `example` module)
76// for functions from the C standard library.
77#[cfg(any(feature = "libc", feature = "example", test, all(doc, feature = "doccfg")))]
78extern crate libc;
79
80#[cfg(any(test, doc))]
81extern crate alloc;
82
83use core::ffi::{c_char, CStr};
84use core::marker::PhantomData;
85
86use crate::private::PrintfArgumentPrivate;
87use crate::validate::is_fmt_valid_for_args;
88
89/// Traits used to implement private details of [sealed traits].
90///
91/// [sealed traits]: https://rust-lang.github.io/api-guidelines/future-proofing.html#c-sealed
92pub(crate) mod private {
93 /// Marker trait for [`PrintfArgument`](`super::PrintfArgument`).
94 pub trait PrintfArgumentPrivate {}
95}
96
97mod larger_of;
98mod printf_arg_impls;
99mod validate;
100
101/// A wrapper for a `'static` null-terminated string.
102///
103/// Sometimes used in favor of
104/// [`CStr`](core::ffi::CStr) or [`CString`](alloc::ffi::CString),
105/// as [`NullString`]s can be made as compile-time constants.
106#[derive(Clone, Copy, Debug)]
107pub struct NullString {
108 /// A pointer to a `'static` null-terminated string.
109 s: *const c_char,
110}
111
112impl NullString {
113 /// Creates a [`NullString`] from `s`
114 /// or panics if `s` is not null-terminated.
115 ///
116 /// # Panics
117 ///
118 /// Panics if the string `s` does not end in the null character.
119 #[allow(unconditional_panic)]
120 #[deny(const_err)]
121 pub const fn new(s: &'static str) -> NullString {
122 let bytes = s.as_bytes();
123 if bytes.len() == 0 || bytes[bytes.len() - 1] != b'\0' {
124 panic!("string passed to NullString::new is not null-terminated!");
125 }
126
127 NullString { s: bytes.as_ptr() as *const c_char }
128 }
129
130 /// Returns a pointer to the beginning of the wrapped string.
131 #[inline]
132 pub const fn as_ptr(self) -> *const c_char {
133 self.s
134 }
135
136 /// Returns a `&`[`CStr`] pointing to the wrapped string.
137 #[inline]
138 pub fn as_cstr(self) -> &'static CStr {
139 unsafe { CStr::from_ptr(self.s) }
140 }
141}
142
143impl From<&'static CStr> for NullString {
144 #[inline]
145 fn from(cstr: &'static CStr) -> Self {
146 NullString { s: cstr.as_ptr() }
147 }
148}
149
150impl From<NullString> for &'static CStr {
151 #[inline]
152 fn from(nstr: NullString) -> Self {
153 nstr.as_cstr()
154 }
155}
156
157impl AsRef<CStr> for NullString {
158 #[inline]
159 fn as_ref(&self) -> &CStr {
160 self.as_cstr()
161 }
162}
163
164// As the contents of a NullString are simply a pointer to a 'static string,
165// it *is*, in fact, safe to share across multiple threads:
166unsafe impl Send for NullString {}
167unsafe impl Sync for NullString {}
168
169/// Convenience macro for creating a `const` [`NullString`],
170/// including appending a null character.
171#[macro_export]
172macro_rules! null_str {
173 ($str:expr) => {{
174 const STR: $crate::NullString = $crate::NullString::new(concat!($str, "\0"));
175 STR
176 }};
177}
178
179/// A Rust-side argument to a safe wrapper around a `printf(3)`-like function.
180///
181/// This is a [sealed trait]; consumers of this crate are not allowed
182/// to create their own `impl`s in order to unconditionally preserve
183/// safety.
184///
185/// [sealed trait]: https://rust-lang.github.io/api-guidelines/future-proofing.html#c-sealed
186pub trait PrintfArgument: PrintfArgumentPrivate + Copy {
187 /// The C type corresponding to `Self` that we should _really_ send
188 /// as an argument to a `printf(3)`-like function.
189 type CPrintfType;
190
191 /// Converts `self` to a value suitable for sending to `printf(3)`.
192 fn as_c_val(self) -> Self::CPrintfType;
193
194 /// Whether the type is consistent with C's `char`.
195 const IS_CHAR: bool = false;
196 /// Whether the type is consistent with C's `short int`.
197 const IS_SHORT: bool = false;
198 /// Whether the type is consistent with C's `int`.
199 const IS_INT: bool = false;
200 /// Whether the type is consistent with C's `long int`.
201 const IS_LONG: bool = false;
202 /// Whether the type is consistent with C's `long long int`.
203 const IS_LONG_LONG: bool = false;
204 /// Whether the type is consistent with C's `size_t`.
205 #[cfg(any(feature = "libc", test, all(doc, feature = "doccfg")))]
206 #[cfg_attr(feature = "doccfg", doc(cfg(feature = "libc")))]
207 const IS_SIZE: bool = false;
208 /// Whether the type is consistent with C's `intmax_t`.
209 #[cfg(any(feature = "libc", test, all(doc, feature = "doccfg")))]
210 #[cfg_attr(feature = "doccfg", doc(cfg(feature = "libc")))]
211 const IS_MAX: bool = false;
212 /// Whether the type is consistent with C's `ptrdiff_t`.
213 #[cfg(any(feature = "libc", test, all(doc, feature = "doccfg")))]
214 #[cfg_attr(feature = "doccfg", doc(cfg(feature = "libc")))]
215 const IS_PTRDIFF: bool = false;
216
217 /// Whether the type is a signed integer type, as opposed to unsigned.
218 const IS_SIGNED: bool = false;
219
220 /// Whether the type is floating-point.
221 const IS_FLOAT: bool = false;
222
223 /// Whether the type is a null-terminated string.
224 const IS_C_STRING: bool = false;
225
226 /// Whether the type is a pointer.
227 const IS_POINTER: bool = false;
228}
229
230/// Are types `T` and `U` ABI-compatible, in the sense that using
231/// one in the place of the other wouldn't affect structure layout,
232/// stack layout if used as arguments (assuming they're both integer-like),
233/// etc.?
234///
235/// Note that this doesn't check for whether substituting `T` with `U` (or vice
236/// versa) is sensible or even valid;
237/// the use-case is for types where any bit-pattern is
238/// sensible and the types don't have non-trivial drop behavior.
239const fn is_compat<T: Sized, U: Sized>() -> bool {
240 use core::mem::{align_of, size_of};
241
242 size_of::<T>() == size_of::<U>() && align_of::<T>() == align_of::<U>()
243}
244
245/// Utility trait for determining which of two integer types is larger.
246///
247/// The type alias [`LargerOf`] is usually more convenient to use outside
248/// of implementations of this trait.
249pub trait LargerOfOp<Rhs> {
250 /// If `Rhs` is a larger type than `Self`, this should be `Rhs`; otherwise
251 /// it should be `Self`.
252 type Output;
253}
254
255/// Type alias that better conveys [`LargerOfOp`]'s nature as a type-level
256/// function.
257pub type LargerOf<T, U> = <T as LargerOfOp<U>>::Output;
258
259/// A list of Rust-side arguments to a `printf(3)`-style function.
260pub trait PrintfArgs {
261 /// The [`PrintfArgsList`] equivalent to `Self`.
262 type AsList: PrintfArgsList;
263}
264
265/// A [`PrintfArgs`] in a form more amenable to recursive processing.
266pub trait PrintfArgsList {
267 /// Whether this type represents an empty list.
268 const IS_EMPTY: bool;
269
270 /// The first element of the list.
271 type First: PrintfArgument;
272 /// The elements of the list after the first.
273 type Rest: PrintfArgsList;
274}
275
276impl PrintfArgsList for () {
277 const IS_EMPTY: bool = true;
278
279 /// This isn't _really_ the first element of an empty list,
280 /// but to fulfil the type constraint, we need _something_ here.
281 type First = u8;
282 type Rest = ();
283}
284
285impl<CAR: PrintfArgument, CDR: PrintfArgsList> PrintfArgsList for (CAR, CDR) {
286 const IS_EMPTY: bool = false;
287
288 type First = CAR;
289 type Rest = CDR;
290}
291
292impl<T: PrintfArgument> PrintfArgs for T {
293 type AsList = (T, ());
294}
295
296impl PrintfArgs for () {
297 type AsList = ();
298}
299
300macro_rules! nested_list_from_flat {
301 ($t:ident $(, $u:ident )*) => { ($t, nested_list_from_flat!($( $u ),*)) };
302 () => { () };
303}
304
305macro_rules! make_printf_arguments_tuple {
306 ($( $t:ident ),+) => {
307 impl<$( $t ),+> PrintfArgs for ($( $t, )+)
308 where $( $t: PrintfArgument ),+ {
309 type AsList = nested_list_from_flat!($( $t ),+);
310 }
311 };
312}
313
314make_printf_arguments_tuple!(A);
315make_printf_arguments_tuple!(A, B);
316make_printf_arguments_tuple!(A, B, C);
317make_printf_arguments_tuple!(A, B, C, D);
318make_printf_arguments_tuple!(A, B, C, D, E);
319make_printf_arguments_tuple!(A, B, C, D, E, F);
320make_printf_arguments_tuple!(A, B, C, D, E, F, G);
321make_printf_arguments_tuple!(A, B, C, D, E, F, G, H);
322make_printf_arguments_tuple!(A, B, C, D, E, F, G, H, I);
323make_printf_arguments_tuple!(A, B, C, D, E, F, G, H, I, J);
324make_printf_arguments_tuple!(A, B, C, D, E, F, G, H, I, J, K);
325make_printf_arguments_tuple!(A, B, C, D, E, F, G, H, I, J, K, L);
326make_printf_arguments_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M);
327make_printf_arguments_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N);
328make_printf_arguments_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O);
329make_printf_arguments_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P);
330
331/// A type-safe wrapper around a C-style string verified to be compatible
332/// with use as a format string for `printf(3)`-style functions called with
333/// `T` as the varargs.
334#[derive(Debug)]
335pub struct PrintfFmt<T: PrintfArgs> {
336 /// This must be a pointer to a `'static` null-terminated string.
337 fmt: *const c_char,
338 _x: CompatibleSystem,
339 _y: PhantomData<T>,
340}
341
342/// Utility conversion from [`u8`] to [`c_char`].
343const fn c(x: u8) -> c_char {
344 x as c_char
345}
346
347/// The empty C string.
348const EMPTY_C_STRING: *const c_char = &c(b'\0') as *const c_char;
349
350impl<T: PrintfArgs> PrintfFmt<T> {
351 /// If `fmt` represents a valid, supported format string for `printf(3)`
352 /// when given Rust-side arguments `T`, returns a [`PrintfFmt`];
353 /// panics otherwise.
354 ///
355 /// # Panics
356 ///
357 /// See above.
358 #[allow(unconditional_panic)]
359 #[inline]
360 pub const fn new_or_panic(fmt: &'static str) -> Self {
361 if !is_compat::<u8, c_char>() {
362 panic!("u8 and c_char have different sizes/alignments, somehow");
363 }
364
365 let fmt_as_cstr: &'static [c_char] = unsafe {
366 // Following is safe, as (1) we've verified u8 has the same
367 // size and alignment as c_char and (2) references to T have the
368 // same layout as pointers to T
369 core::mem::transmute(fmt.as_bytes() as *const [u8] as *const [c_char])
370 };
371
372 let s = if is_fmt_valid_for_args::<T>(fmt_as_cstr, true) {
373 fmt_as_cstr.as_ptr()
374 } else {
375 EMPTY_C_STRING
376 };
377
378 PrintfFmt { fmt: s, _x: CompatibleSystem {}, _y: PhantomData }
379 }
380
381 /// If `fmt` represents a valid, supported format string for `printf(3)`
382 /// when given Rust-side arguments `T`, returns it as a [`PrintfFmt`].
383 ///
384 /// # Errors
385 ///
386 /// Returns `Err(())` if `fmt` is _not_ a valid, supported format string
387 /// corresponding to varargs `T`.
388 #[inline]
389 pub const fn new(fmt: &'static str) -> Result<Self, ()> {
390 if !is_compat::<u8, c_char>() {
391 return Err(());
392 }
393
394 let fmt_as_cstr: &'static [c_char] = unsafe {
395 // Following is safe, as (1) we've verified u8 has the same
396 // size and alignment as c_char and (2) references to T have the
397 // same layout as pointers to T
398 core::mem::transmute(fmt.as_bytes() as *const [u8] as *const [c_char])
399 };
400
401 if is_fmt_valid_for_args::<T>(fmt_as_cstr, false) {
402 Ok(PrintfFmt { fmt: fmt_as_cstr.as_ptr(), _x: CompatibleSystem {}, _y: PhantomData })
403 } else {
404 Err(())
405 }
406 }
407
408 /// Returns a pointer to the beginning of the format string.
409 #[inline]
410 pub const fn as_ptr(self) -> *const c_char {
411 self.fmt
412 }
413}
414
415impl<T: PrintfArgs> Clone for PrintfFmt<T> {
416 fn clone(&self) -> Self {
417 *self
418 }
419}
420
421impl<T: PrintfArgs> Copy for PrintfFmt<T> {}
422
423impl<T: PrintfArgs> AsRef<CStr> for PrintfFmt<T> {
424 fn as_ref(&self) -> &CStr {
425 unsafe { CStr::from_ptr(self.fmt) }
426 }
427}
428
429// As the contents of a PrintfFmt<T> are just a pointer to a 'static string
430// and some thread-safe ZSTs, it is actually thread-safe:
431unsafe impl<T: PrintfArgs> Send for PrintfFmt<T> {}
432unsafe impl<T: PrintfArgs> Sync for PrintfFmt<T> {}
433
434/// Returns whether `fmt` is (1) a valid C-style string and (2) a format
435/// string compatible with the tuple of arguments `T` when used in a
436/// `printf(3)`-like function.
437#[deny(unconditional_panic)]
438#[inline]
439pub const fn is_fmt_valid<T: PrintfArgs>(fmt: &[c_char]) -> bool {
440 is_fmt_valid_for_args::<T>(fmt, false)
441}
442
443#[cfg(any(feature = "example", all(doc, feature = "doccfg"), test))]
444#[cfg_attr(feature = "doccfg", doc(cfg(feature = "example")))]
445pub mod example;
446
447#[cfg(test)]
448mod tests;