Skip to main content

taproot_c_scape/printf_impl/
mod.rs

1//! The `printf` formatting engine, walking the va.rs tag on stable Rust.
2//!
3//! Vendored from the printf-compat crate, version 0.4.0, by lights0123
4//! (<https://github.com/lights0123/printf-compat>), licensed MIT OR
5//! Apache-2.0. The port replaces the crate's nightly `VaList` argument
6//! source with `&mut crate::va::VaListTag` (each `next_arg` call site
7//! becomes a `tag.arg::<T>()` call), drops the `VaList`-holding `display`
8//! adapter and the std-only `io_write` adapter that c-scape never used,
9//! and replaces the parser's `itertools::tuple_windows` walk with a
10//! peekable iterator. The parsing and formatting behavior is otherwise
11//! unchanged, including its documented differences from glibc (see
12//! [`output::fmt_write`]).
13//!
14//! This module is x86_64-only, like the walker it consumes; other
15//! architectures keep formatting through the printf-compat crate itself
16//! on the nightly `VaList` path.
17
18use core::{ffi::*, fmt};
19
20pub mod output;
21mod parser;
22use argument::*;
23pub use parser::format;
24
25pub mod argument {
26    use super::*;
27
28    bitflags::bitflags! {
29        /// Flags field.
30        ///
31        /// Definitions from
32        /// [Wikipedia](https://en.wikipedia.org/wiki/Printf_format_string#Flags_field).
33        #[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
34        pub struct Flags: u8 {
35            /// Left-align the output of this placeholder. (The default is to
36            /// right-align the output.)
37            const LEFT_ALIGN = 0b00000001;
38            /// Prepends a plus for positive signed-numeric types. positive =
39            /// `+`, negative = `-`.
40            ///
41            /// (The default doesn't prepend anything in front of positive
42            /// numbers.)
43            const PREPEND_PLUS = 0b00000010;
44            /// Prepends a space for positive signed-numeric types. positive = `
45            /// `, negative = `-`. This flag is ignored if the
46            /// [`PREPEND_PLUS`][Flags::PREPEND_PLUS] flag exists.
47            ///
48            /// (The default doesn't prepend anything in front of positive
49            /// numbers.)
50            const PREPEND_SPACE = 0b00000100;
51            /// When the 'width' option is specified, prepends zeros for numeric
52            /// types. (The default prepends spaces.)
53            ///
54            /// For example, `printf("%4X",3)` produces `   3`, while
55            /// `printf("%04X",3)` produces `0003`.
56            const PREPEND_ZERO = 0b00001000;
57            /// The integer or exponent of a decimal has the thousands grouping
58            /// separator applied.
59            const THOUSANDS_GROUPING = 0b00010000;
60            /// Alternate form:
61            ///
62            /// For `g` and `G` types, trailing zeros are not removed. \
63            /// For `f`, `F`, `e`, `E`, `g`, `G` types, the output always
64            /// contains a decimal point. \ For `o`, `x`, `X` types,
65            /// the text `0`, `0x`, `0X`, respectively, is prepended
66            /// to non-zero numbers.
67            const ALTERNATE_FORM = 0b00100000;
68        }
69    }
70
71    #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
72    pub enum DoubleFormat {
73        /// `f`
74        Normal,
75        /// `F`
76        UpperNormal,
77        /// `e`
78        Scientific,
79        /// `E`
80        UpperScientific,
81        /// `g`
82        Auto,
83        /// `G`
84        UpperAuto,
85        /// `a`
86        Hex,
87        /// `A`
88        UpperHex,
89    }
90
91    impl DoubleFormat {
92        /// If the format is uppercase.
93        pub fn is_upper(self) -> bool {
94            use DoubleFormat::*;
95            matches!(self, UpperNormal | UpperScientific | UpperAuto | UpperHex)
96        }
97
98        pub fn set_upper(self, upper: bool) -> Self {
99            use DoubleFormat::*;
100            match self {
101                Normal | UpperNormal => {
102                    if upper {
103                        UpperNormal
104                    } else {
105                        Normal
106                    }
107                }
108                Scientific | UpperScientific => {
109                    if upper {
110                        UpperScientific
111                    } else {
112                        Scientific
113                    }
114                }
115                Auto | UpperAuto => {
116                    if upper {
117                        UpperAuto
118                    } else {
119                        Auto
120                    }
121                }
122                Hex | UpperHex => {
123                    if upper {
124                        UpperHex
125                    } else {
126                        Hex
127                    }
128                }
129            }
130        }
131    }
132
133    #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
134    #[non_exhaustive]
135    pub enum SignedInt {
136        Int(c_int),
137        Char(c_schar),
138        Short(c_short),
139        Long(c_long),
140        LongLong(c_longlong),
141        Isize(isize),
142    }
143
144    impl From<SignedInt> for i64 {
145        fn from(num: SignedInt) -> Self {
146            // Some casts are only needed on some platforms.
147            #[allow(clippy::unnecessary_cast)]
148            match num {
149                SignedInt::Int(x) => x as i64,
150                SignedInt::Char(x) => x as i64,
151                SignedInt::Short(x) => x as i64,
152                SignedInt::Long(x) => x as i64,
153                SignedInt::LongLong(x) => x as i64,
154                SignedInt::Isize(x) => x as i64,
155            }
156        }
157    }
158
159    impl SignedInt {
160        pub fn is_sign_negative(self) -> bool {
161            match self {
162                SignedInt::Int(x) => x < 0,
163                SignedInt::Char(x) => x < 0,
164                SignedInt::Short(x) => x < 0,
165                SignedInt::Long(x) => x < 0,
166                SignedInt::LongLong(x) => x < 0,
167                SignedInt::Isize(x) => x < 0,
168            }
169        }
170    }
171
172    impl fmt::Display for SignedInt {
173        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
174            match self {
175                SignedInt::Int(x) => fmt::Display::fmt(x, f),
176                SignedInt::Char(x) => fmt::Display::fmt(x, f),
177                SignedInt::Short(x) => fmt::Display::fmt(x, f),
178                SignedInt::Long(x) => fmt::Display::fmt(x, f),
179                SignedInt::LongLong(x) => fmt::Display::fmt(x, f),
180                SignedInt::Isize(x) => fmt::Display::fmt(x, f),
181            }
182        }
183    }
184
185    #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
186    #[non_exhaustive]
187    pub enum UnsignedInt {
188        Int(c_uint),
189        Char(c_uchar),
190        Short(c_ushort),
191        Long(c_ulong),
192        LongLong(c_ulonglong),
193        Isize(usize),
194    }
195
196    impl From<UnsignedInt> for u64 {
197        fn from(num: UnsignedInt) -> Self {
198            // Some casts are only needed on some platforms.
199            #[allow(clippy::unnecessary_cast)]
200            match num {
201                UnsignedInt::Int(x) => x as u64,
202                UnsignedInt::Char(x) => x as u64,
203                UnsignedInt::Short(x) => x as u64,
204                UnsignedInt::Long(x) => x as u64,
205                UnsignedInt::LongLong(x) => x as u64,
206                UnsignedInt::Isize(x) => x as u64,
207            }
208        }
209    }
210
211    impl fmt::Display for UnsignedInt {
212        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
213            match self {
214                UnsignedInt::Int(x) => fmt::Display::fmt(x, f),
215                UnsignedInt::Char(x) => fmt::Display::fmt(x, f),
216                UnsignedInt::Short(x) => fmt::Display::fmt(x, f),
217                UnsignedInt::Long(x) => fmt::Display::fmt(x, f),
218                UnsignedInt::LongLong(x) => fmt::Display::fmt(x, f),
219                UnsignedInt::Isize(x) => fmt::Display::fmt(x, f),
220            }
221        }
222    }
223
224    impl fmt::LowerHex for UnsignedInt {
225        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
226            match self {
227                UnsignedInt::Int(x) => fmt::LowerHex::fmt(x, f),
228                UnsignedInt::Char(x) => fmt::LowerHex::fmt(x, f),
229                UnsignedInt::Short(x) => fmt::LowerHex::fmt(x, f),
230                UnsignedInt::Long(x) => fmt::LowerHex::fmt(x, f),
231                UnsignedInt::LongLong(x) => fmt::LowerHex::fmt(x, f),
232                UnsignedInt::Isize(x) => fmt::LowerHex::fmt(x, f),
233            }
234        }
235    }
236
237    impl fmt::UpperHex for UnsignedInt {
238        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
239            match self {
240                UnsignedInt::Int(x) => fmt::UpperHex::fmt(x, f),
241                UnsignedInt::Char(x) => fmt::UpperHex::fmt(x, f),
242                UnsignedInt::Short(x) => fmt::UpperHex::fmt(x, f),
243                UnsignedInt::Long(x) => fmt::UpperHex::fmt(x, f),
244                UnsignedInt::LongLong(x) => fmt::UpperHex::fmt(x, f),
245                UnsignedInt::Isize(x) => fmt::UpperHex::fmt(x, f),
246            }
247        }
248    }
249
250    impl fmt::Octal for UnsignedInt {
251        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
252            match self {
253                UnsignedInt::Int(x) => fmt::Octal::fmt(x, f),
254                UnsignedInt::Char(x) => fmt::Octal::fmt(x, f),
255                UnsignedInt::Short(x) => fmt::Octal::fmt(x, f),
256                UnsignedInt::Long(x) => fmt::Octal::fmt(x, f),
257                UnsignedInt::LongLong(x) => fmt::Octal::fmt(x, f),
258                UnsignedInt::Isize(x) => fmt::Octal::fmt(x, f),
259            }
260        }
261    }
262
263    /// An argument as passed to [`format()`].
264    #[derive(Debug, Copy, Clone, PartialEq)]
265    pub struct Argument<'a> {
266        pub flags: Flags,
267        pub width: c_int,
268        pub precision: Option<c_int>,
269        pub specifier: Specifier<'a>,
270    }
271
272    impl<'a> From<Specifier<'a>> for Argument<'a> {
273        fn from(specifier: Specifier<'a>) -> Self {
274            Self {
275                flags: Flags::empty(),
276                width: 0,
277                precision: None,
278                specifier,
279            }
280        }
281    }
282
283    /// A [format specifier](https://en.wikipedia.org/wiki/Printf_format_string#Type_field).
284    #[derive(Debug, Copy, Clone, PartialEq)]
285    #[non_exhaustive]
286    pub enum Specifier<'a> {
287        /// `%`
288        Percent,
289        /// `d`, `i`
290        Int(SignedInt),
291        /// `u`
292        Uint(UnsignedInt),
293        /// `o`
294        Octal(UnsignedInt),
295        /// `f`, `F`, `e`, `E`, `g`, `G`, `a`, `A`
296        Double { value: f64, format: DoubleFormat },
297        /// string outside of formatting
298        Bytes(&'a [u8]),
299        /// `s`
300        ///
301        /// The same as [`Bytes`][Specifier::Bytes] but guaranteed to be
302        /// null-terminated. This can be used for optimizations, where if you
303        /// need to null terminate a string to print it, you can skip that step.
304        String(&'a CStr),
305        /// `c`
306        Char(c_char),
307        /// `x`
308        Hex(UnsignedInt),
309        /// `X`
310        UpperHex(UnsignedInt),
311        /// `p`
312        Pointer(*const ()),
313        /// `n`
314        ///
315        /// # Safety
316        ///
317        /// This can be a serious security vulnerability if the format specifier
318        /// of `printf` is allowed to be user-specified. This shouldn't ever
319        /// happen, but poorly-written software may do so.
320        WriteBytesWritten(c_int, *const c_int),
321    }
322}
323
324#[cfg(test)]
325mod tests {
326    use super::{format, output};
327    use crate::va::{vararg_entry, VaListTag};
328    use alloc::string::String;
329    use libc::{c_char, c_int};
330
331    /// The implementation half of the test entry: run the REAL formatting
332    /// path (tag walk, parser, formatter) into a byte buffer, NUL
333    /// terminate it, and hand back `format`'s return value.
334    unsafe extern "C" fn fmt_into_impl(
335        out: *mut u8,
336        cap: usize,
337        fmt: *const c_char,
338        tag: *mut VaListTag,
339    ) -> c_int {
340        // SAFETY: the entry hands us a live tag, and every test call site
341        // passes arguments matching its format string and a buffer with
342        // room for the formatted bytes plus a NUL.
343        unsafe {
344            let mut s = String::new();
345            let n = format(fmt, &mut *tag, output::fmt_write(&mut s));
346            if n < 0 {
347                return n;
348            }
349            let bytes = s.as_bytes();
350            assert!(bytes.len() < cap, "test buffer too small");
351            core::ptr::copy_nonoverlapping(bytes.as_ptr(), out, bytes.len());
352            *out.add(bytes.len()) = 0;
353            n
354        }
355    }
356
357    vararg_entry! {
358        #[no_mangle]
359        unsafe extern "C" fn __taproot_printf_test_fmt(
360            out: *mut u8,
361            cap: usize,
362            fmt: *const c_char,
363            ...
364        ) -> c_int => fmt_into_impl
365    }
366
367    /// The C-side view of the entry, as in va.rs's suite: calling through
368    /// this makes each test a real-ABI round trip, with rustc performing
369    /// the caller half of the variadic protocol.
370    mod decl {
371        use libc::{c_char, c_int};
372
373        unsafe extern "C" {
374            pub fn __taproot_printf_test_fmt(
375                out: *mut u8,
376                cap: usize,
377                fmt: *const c_char,
378                ...
379            ) -> c_int;
380        }
381    }
382
383    fn formatted(buf: &[u8], n: c_int) -> &[u8] {
384        assert!(n >= 0, "formatting failed: {n}");
385        let n = n as usize;
386        assert_eq!(buf[n], 0, "missing NUL terminator");
387        &buf[..n]
388    }
389
390    #[test]
391    fn formats_d_s_f_exactly() {
392        let mut buf = [0xAA_u8; 64];
393        let n = unsafe {
394            decl::__taproot_printf_test_fmt(
395                buf.as_mut_ptr(),
396                buf.len(),
397                c"%d %s %.2f".as_ptr(),
398                42 as c_int,
399                c"carrot".as_ptr(),
400                3.14159_f64,
401            )
402        };
403        assert_eq!(formatted(&buf, n), b"42 carrot 3.14");
404        assert_eq!(n, 14);
405    }
406
407    #[test]
408    fn width_precision_and_alignment() {
409        // A zero-padded width on a precision'd double, a left-aligned
410        // width on an int, and a right-aligned width on a string.
411        let mut buf = [0xAA_u8; 64];
412        let n = unsafe {
413            decl::__taproot_printf_test_fmt(
414                buf.as_mut_ptr(),
415                buf.len(),
416                c"%08.3f|%-6d|%5s".as_ptr(),
417                2.5_f64,
418                42 as c_int,
419                c"ab".as_ptr(),
420            )
421        };
422        assert_eq!(formatted(&buf, n), b"0002.500|42    |   ab");
423    }
424
425    #[test]
426    fn star_width_comes_off_the_walk() {
427        // `%*d` fetches its width as an argument before the value.
428        let mut buf = [0xAA_u8; 32];
429        let n = unsafe {
430            decl::__taproot_printf_test_fmt(
431                buf.as_mut_ptr(),
432                buf.len(),
433                c"%*d".as_ptr(),
434                5 as c_int,
435                42 as c_int,
436            )
437        };
438        assert_eq!(formatted(&buf, n), b"   42");
439    }
440
441    #[test]
442    fn args_cross_the_overflow_area() {
443        // Three named INTEGER arguments plus seven variadic ints exhaust
444        // the six GP registers mid-walk, and the double rides xmm0: the
445        // formatter's fetches must agree with the walker across both the
446        // register and stack areas.
447        let mut buf = [0xAA_u8; 64];
448        let n = unsafe {
449            decl::__taproot_printf_test_fmt(
450                buf.as_mut_ptr(),
451                buf.len(),
452                c"%d %d %d %d %d %d %d %.1f".as_ptr(),
453                1 as c_int,
454                2 as c_int,
455                3 as c_int,
456                4 as c_int,
457                5 as c_int,
458                6 as c_int,
459                7 as c_int,
460                8.5_f64,
461            )
462        };
463        assert_eq!(formatted(&buf, n), b"1 2 3 4 5 6 7 8.5");
464    }
465
466    #[test]
467    fn percent_n_is_rejected() {
468        // The vendored formatter parses `%n` but refuses to write through
469        // it, reporting an error instead; the guard c-scape's `__*_chk`
470        // comments rely on.
471        let mut sink: c_int = 0;
472        let mut buf = [0xAA_u8; 32];
473        let n = unsafe {
474            decl::__taproot_printf_test_fmt(
475                buf.as_mut_ptr(),
476                buf.len(),
477                c"a%nb".as_ptr(),
478                &mut sink as *mut c_int,
479            )
480        };
481        assert_eq!(n, -1);
482        assert_eq!(sink, 0);
483    }
484}