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);
330make_printf_arguments_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q);
331make_printf_arguments_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R);
332make_printf_arguments_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S);
333make_printf_arguments_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T);
334make_printf_arguments_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U);
335make_printf_arguments_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V);
336make_printf_arguments_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W);
337make_printf_arguments_tuple!(
338    A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X
339);
340make_printf_arguments_tuple!(
341    A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y
342);
343make_printf_arguments_tuple!(
344    A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z
345);
346make_printf_arguments_tuple!(
347    A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, AA
348);
349make_printf_arguments_tuple!(
350    A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, AA, BB
351);
352make_printf_arguments_tuple!(
353    A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, AA, BB, CC
354);
355make_printf_arguments_tuple!(
356    A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, AA, BB, CC, DD
357);
358make_printf_arguments_tuple!(
359    A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, AA, BB, CC, DD,
360    EE
361);
362make_printf_arguments_tuple!(
363    A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, AA, BB, CC, DD,
364    EE, FF
365);
366
367/// A type-safe wrapper around a C-style string verified to be compatible
368/// with use as a format string for `printf(3)`-style functions called with
369/// `T` as the varargs.
370#[derive(Debug)]
371pub struct PrintfFmt<T: PrintfArgs> {
372    /// This must be a pointer to a `'static` null-terminated string.
373    fmt: *const c_char,
374    _x: CompatibleSystem,
375    _y: PhantomData<T>,
376}
377
378/// Utility conversion from [`u8`] to [`c_char`].
379const fn c(x: u8) -> c_char {
380    x as c_char
381}
382
383/// The empty C string.
384const EMPTY_C_STRING: *const c_char = &c(b'\0') as *const c_char;
385
386impl<T: PrintfArgs> PrintfFmt<T> {
387    /// If `fmt` represents a valid, supported format string for `printf(3)`
388    /// when given Rust-side arguments `T`, returns a [`PrintfFmt`];
389    /// panics otherwise.
390    ///
391    /// # Panics
392    ///
393    /// See above.
394    #[allow(unconditional_panic)]
395    #[inline]
396    pub const fn new_or_panic(fmt: &'static str) -> Self {
397        if !is_compat::<u8, c_char>() {
398            panic!("u8 and c_char have different sizes/alignments, somehow");
399        }
400
401        let fmt_as_cstr: &'static [c_char] = unsafe {
402            // Following is safe, as (1) we've verified u8 has the same
403            // size and alignment as c_char and (2) references to T have the
404            // same layout as pointers to T
405            core::mem::transmute(fmt.as_bytes() as *const [u8] as *const [c_char])
406        };
407
408        let s = if is_fmt_valid_for_args::<T>(fmt_as_cstr, true) {
409            fmt_as_cstr.as_ptr()
410        } else {
411            EMPTY_C_STRING
412        };
413
414        PrintfFmt { fmt: s, _x: CompatibleSystem {}, _y: PhantomData }
415    }
416
417    /// If `fmt` represents a valid, supported format string for `printf(3)`
418    /// when given Rust-side arguments `T`, returns it as a [`PrintfFmt`].
419    ///
420    /// # Errors
421    ///
422    /// Returns `Err(())` if `fmt` is _not_ a valid, supported format string
423    /// corresponding to varargs `T`.
424    #[inline]
425    pub const fn new(fmt: &'static str) -> Result<Self, ()> {
426        if !is_compat::<u8, c_char>() {
427            return Err(());
428        }
429
430        let fmt_as_cstr: &'static [c_char] = unsafe {
431            // Following is safe, as (1) we've verified u8 has the same
432            // size and alignment as c_char and (2) references to T have the
433            // same layout as pointers to T
434            core::mem::transmute(fmt.as_bytes() as *const [u8] as *const [c_char])
435        };
436
437        if is_fmt_valid_for_args::<T>(fmt_as_cstr, false) {
438            Ok(PrintfFmt { fmt: fmt_as_cstr.as_ptr(), _x: CompatibleSystem {}, _y: PhantomData })
439        } else {
440            Err(())
441        }
442    }
443
444    /// Returns a pointer to the beginning of the format string.
445    #[inline]
446    pub const fn as_ptr(self) -> *const c_char {
447        self.fmt
448    }
449}
450
451impl<T: PrintfArgs> Clone for PrintfFmt<T> {
452    fn clone(&self) -> Self {
453        *self
454    }
455}
456
457impl<T: PrintfArgs> Copy for PrintfFmt<T> {}
458
459impl<T: PrintfArgs> AsRef<CStr> for PrintfFmt<T> {
460    fn as_ref(&self) -> &CStr {
461        unsafe { CStr::from_ptr(self.fmt) }
462    }
463}
464
465// As the contents of a PrintfFmt<T> are just a pointer to a 'static string
466// and some thread-safe ZSTs, it is actually thread-safe:
467unsafe impl<T: PrintfArgs> Send for PrintfFmt<T> {}
468unsafe impl<T: PrintfArgs> Sync for PrintfFmt<T> {}
469
470/// Returns whether `fmt` is (1) a valid C-style string and (2) a format
471/// string compatible with the tuple of arguments `T` when used in a
472/// `printf(3)`-like function.
473#[deny(unconditional_panic)]
474#[inline]
475pub const fn is_fmt_valid<T: PrintfArgs>(fmt: &[c_char]) -> bool {
476    is_fmt_valid_for_args::<T>(fmt, false)
477}
478
479#[cfg(any(feature = "example", all(doc, feature = "doccfg"), test))]
480#[cfg_attr(feature = "doccfg", doc(cfg(feature = "example")))]
481pub mod example;
482
483#[cfg(test)]
484mod tests;