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;