Skip to main content

const_panic/
concat_panic_.rs

1use crate::{
2    fmt::FmtKind,
3    panic_val::{PanicClass, PanicVal, StrFmt},
4    utils::{bytes_up_to, min_usize, string_cap, WasTruncated},
5};
6
7/// Panics by concatenating the argument slice.
8///
9/// This is the function that the [`concat_panic`](macro@concat_panic) macro calls to panic.
10///
11/// # Example
12///
13/// Here's how to panic with formatting without using any macros:
14///
15/// ```compile_fail
16/// use const_panic::{FmtArg, PanicVal, concat_panic};
17///
18/// const _: () = concat_panic(&[&[
19///     PanicVal::write_str("\nthe error was "),
20///     PanicVal::from_u8(100, FmtArg::DISPLAY),
21///     PanicVal::write_str(" and "),
22///     PanicVal::from_str("\nHello\tworld", FmtArg::DEBUG),
23/// ]]);
24///
25///
26/// ```
27/// That fails to compile with this error message:
28/// ```text
29/// error[E0080]: evaluation of constant value failed
30///   --> src/concat_panic_.rs:13:15
31///    |
32/// 6  |   const _: () = concat_panic(&[&[
33///    |  _______________^
34/// 7  | |     PanicVal::write_str("\nthe error was "),
35/// 8  | |     PanicVal::from_u8(100, FmtArg::DISPLAY),
36/// 9  | |     PanicVal::write_str(" and "),
37/// 10 | |     PanicVal::from_str("\nHello\tworld", FmtArg::DEBUG),
38/// 11 | | ]]);
39///    | |___^ the evaluated program panicked at '
40/// the error was 100 and "\nHello\tworld"', src/concat_panic_.rs:6:15
41/// ```
42///
43#[cold]
44#[inline(never)]
45#[track_caller]
46pub const fn concat_panic(args: &[&[PanicVal<'_>]]) -> ! {
47    // The panic message capacity starts small and gets larger each time,
48    // so that platforms with smaller stacks can call this at runtime.
49    //
50    // Also, given that most(?) panic messages are smaller than 1024 bytes long,
51    // it's not going to be any less efficient in the common case.
52
53    macro_rules! shorter_panic {
54        ($cap:expr) => {{
55            const CAP: usize = $cap;
56
57            if MAX_PANIC_MSG_LEN > CAP {
58                // using `min_usize` just in case monomorphizing `panic_inner` with
59                // too large an array type on 16 bit causes a compilation error,
60                if let Err(_) = panic_inner::<(), { min_usize(MAX_PANIC_MSG_LEN, $cap) }>(args) {}
61            }
62        }};
63    }
64
65    shorter_panic! {1024}
66
67    shorter_panic! {1024 * 6}
68
69    match panic_inner::<_, MAX_PANIC_MSG_LEN>(args) {
70        Ok(x) => x,
71        Err(_) => panic!(
72            "\
73            unreachable:\n\
74            the `write_panicval_to_buffer` macro must not return Err when \
75            $capacity == $max_capacity\
76        "
77        ),
78    }
79}
80
81macro_rules! len_on_16_bit {
82    () => {
83        512
84    };
85}
86macro_rules! len_on_other_bits {
87    () => {
88        32768
89    };
90}
91
92/// The maximum length of panic messages (in bytes),
93/// after which the message is truncated.
94///
95/// # Value
96///
97/// The value of this constant is determined by these things, from higher to lower priority:
98/// - the `CONST_PANIC_MAX_LENGTH` environment variable,
99///   in bytes and in base 10 (decimal).
100///   If the value of this env var is either an empty string or `_`,
101///   then it has the same effect as not defining it.
102/// - the amount of bits of the target platform:
103#[doc = concat!("    - on 16 bit: ", len_on_16_bit!(), " bytes")]
104#[doc = concat!("    - on any other bit count: ", len_on_other_bits!(), " bytes")]
105///
106pub const MAX_PANIC_MSG_LEN: usize = if let Some(cap) = {
107    match option_env!("CONST_PANIC_MAX_LENGTH") {
108        Some(var) if matches!(var.as_bytes(), b"" | b"_") => None,
109        var => var,
110    }
111} {
112    match crate::utils::parse_usize(cap) {
113        Some(x) => x,
114        None => panic!("`CONST_PANIC_MAX_LENGTH` environment variable is not a valid integer"),
115    }
116} else if cfg!(target_pointer_width = "16") {
117    len_on_16_bit!()
118} else {
119    len_on_other_bits!()
120};
121
122// writes a single PanicVal to an array
123macro_rules! write_panicval {
124    (
125        $outer_label:lifetime,
126        $mout:ident, $lout:ident, $tct:expr,
127        (
128            $len:expr,
129            $capacity:expr,
130            $max_capacity:expr,
131            $not_enough_space:expr,
132            $write_buffer:ident,
133            $write_buffer_checked:ident,
134        )
135    ) => {
136        let rem_space = $capacity - $len;
137        let (strfmt, class, was_truncated) = $tct;
138        let StrFmt {
139            leftpad: mut lpad,
140            rightpad: mut rpad,
141            fmt_kind,
142        } = strfmt;
143
144        let ranged = match class {
145            PanicClass::PreFmt(str) => str,
146            PanicClass::Int(int) => {
147                if int.len() <= string_cap::MEDIUM {
148                    $mout = int.fmt::<{ string_cap::MEDIUM }>();
149                    $mout.ranged()
150                } else {
151                    $lout = int.fmt::<{ string_cap::LARGE }>();
152                    $lout.ranged()
153                }
154            }
155            #[cfg(feature = "non_basic")]
156            PanicClass::Slice(_) => unreachable!(),
157        };
158
159        let trunc_end = ranged.start + was_truncated.get_length(ranged.len());
160
161        while lpad != 0 {
162            $write_buffer! {b' '}
163            lpad -= 1;
164        }
165
166        if let FmtKind::Display = fmt_kind {
167            let mut i = ranged.start;
168            while i < trunc_end {
169                $write_buffer! {ranged.bytes[i]}
170                i += 1;
171            }
172        } else if rem_space != 0 {
173            $write_buffer! {b'"'}
174            let mut i = 0;
175            while i < trunc_end {
176                use crate::debug_str_fmt::{hex_as_ascii, ForEscaping};
177
178                let c = ranged.bytes[i];
179                let mut written_c = c;
180                if ForEscaping::is_escaped(c) {
181                    $write_buffer! {b'\\'}
182                    if ForEscaping::is_backslash_escaped(c) {
183                        written_c = ForEscaping::get_backslash_escape(c);
184                    } else {
185                        $write_buffer! {b'x'}
186                        $write_buffer! {hex_as_ascii(c >> 4)}
187                        written_c = hex_as_ascii(c & 0b1111);
188                    };
189                }
190                $write_buffer! {written_c}
191
192                i += 1;
193            }
194            if let WasTruncated::No = was_truncated {
195                $write_buffer_checked! {b'"'}
196            }
197        }
198
199        while rpad != 0 {
200            $write_buffer! {b' '}
201            rpad -= 1;
202        }
203
204        if let WasTruncated::Yes(_) = was_truncated {
205            if $capacity < $max_capacity {
206                return $not_enough_space;
207            } else {
208                break $outer_label;
209            }
210        }
211    };
212}
213
214macro_rules! write_to_buffer_inner {
215    (
216        $args:ident
217        (
218            $len:expr,
219            $capacity:expr,
220            $($_rem:tt)*
221        )
222        $wptb_args:tt
223    ) => {
224        let mut args = $args;
225
226        let mut mout;
227        let mut lout;
228
229        'outer: while let [mut outer, ref nargs @ ..] = args {
230            while let [arg, nouter @ ..] = outer {
231                let tct = arg.to_class_truncated($capacity - $len);
232                match tct.1 {
233                    #[cfg(feature = "non_basic")]
234                    #[cfg_attr(feature = "docsrs", doc(cfg(feature = "non_basic")))]
235                    PanicClass::Slice(slice) => {
236                        let mut iter = slice.iter();
237
238                        'iter: loop {
239                            let (two_args, niter) = iter.next();
240
241                            let mut two_args: &[_] = &two_args;
242                            while let [arg, ntwo_args @ ..] = two_args {
243                                let tct = arg.to_class_truncated($capacity - $len);
244                                write_panicval! {'outer, mout, lout, tct, $wptb_args}
245                                two_args = ntwo_args;
246                            }
247
248                            match niter {
249                                Some(x) => iter = x,
250                                None => break 'iter,
251                            }
252                        }
253                    }
254                    _ => {
255                        write_panicval! {'outer, mout, lout, tct, $wptb_args}
256                    }
257                }
258
259                outer = nouter;
260            }
261            args = nargs;
262        }
263    };
264}
265
266macro_rules! write_to_buffer {
267    ($args:ident $wptb_args:tt) => {
268        write_to_buffer_inner! {
269            $args
270            $wptb_args
271            $wptb_args
272        }
273    };
274}
275
276macro_rules! make_buffer_writer_macros {
277    ($buffer:ident, $len:ident) => {
278        macro_rules! write_buffer {
279            ($value:expr) => {
280                __write_array! {$buffer, $len, $value}
281            };
282        }
283        macro_rules! write_buffer_checked {
284            ($value:expr) => {
285                __write_array_checked! {$buffer, $len, $value}
286            };
287        }
288    };
289}
290
291#[cold]
292#[inline(never)]
293#[track_caller]
294const fn panic_inner<T, const LEN: usize>(args: &[&[PanicVal<'_>]]) -> Result<T, NotEnoughSpace> {
295    let mut buffer = [0u8; LEN];
296    let mut len = 0usize;
297
298    make_buffer_writer_macros! {buffer, len}
299
300    write_to_buffer! {
301        args
302        (
303            len, LEN, MAX_PANIC_MSG_LEN, Err(NotEnoughSpace),
304            write_buffer, write_buffer_checked,
305        )
306    }
307
308    unsafe {
309        let buffer = bytes_up_to(&buffer, len);
310        let str = core::str::from_utf8_unchecked(buffer);
311        panic!("{}", str)
312    }
313}
314
315#[doc(hidden)]
316#[derive(Debug)]
317pub struct NotEnoughSpace;
318
319#[cfg(feature = "test")]
320use crate::test_utils::TestString;
321
322#[doc(hidden)]
323#[cfg(feature = "test")]
324pub fn format_panic_message<const LEN: usize>(
325    args: &[&[PanicVal<'_>]],
326    capacity: usize,
327    max_capacity: usize,
328) -> Result<TestString<LEN>, NotEnoughSpace> {
329    let mut buffer = [0u8; LEN];
330    let mut len = 0usize;
331    {
332        // intentionally shadowed
333        let buffer = &mut buffer[..capacity];
334
335        make_buffer_writer_macros! {buffer, len}
336
337        write_to_buffer! {
338            args
339            (
340                len, capacity, max_capacity, Err(NotEnoughSpace),
341                write_buffer, write_buffer_checked,
342            )
343        }
344    }
345
346    Ok(TestString { buffer, len })
347}
348
349#[cfg(feature = "non_basic")]
350#[cfg_attr(feature = "docsrs", doc(cfg(feature = "non_basic")))]
351#[doc(hidden)]
352pub(crate) const fn make_panic_string<const LEN: usize>(
353    args: &[&[PanicVal<'_>]],
354) -> Result<crate::ArrayString<LEN>, NotEnoughSpace> {
355    let mut buffer = [0u8; LEN];
356    let mut len = 0usize;
357
358    make_buffer_writer_macros! {buffer, len}
359
360    write_to_buffer! {
361        args
362        (len, LEN, LEN + 1, Err(NotEnoughSpace), write_buffer, write_buffer_checked,)
363    }
364
365    assert!(len as u32 as usize == len, "the panic message is too large");
366
367    Ok(crate::ArrayString {
368        buffer,
369        len: len as u32,
370    })
371}
372
373#[cfg(feature = "non_basic")]
374#[cfg_attr(feature = "docsrs", doc(cfg(feature = "non_basic")))]
375#[doc(hidden)]
376#[track_caller]
377pub const fn make_panic_string_unwrapped<const LEN: usize>(
378    args: &[&[PanicVal<'_>]],
379) -> crate::ArrayString<LEN> {
380    match make_panic_string(args) {
381        Ok(x) => x,
382        Err(_) => panic!("arguments are too large to fit in LEN"),
383    }
384}
385
386#[cfg(feature = "non_basic")]
387#[cfg_attr(feature = "docsrs", doc(cfg(feature = "non_basic")))]
388#[doc(hidden)]
389pub const fn compute_length(args: &[&[PanicVal<'_>]]) -> usize {
390    let mut len = 0usize;
391
392    macro_rules! add_to_len {
393        ($value:expr) => {{
394            let _: u8 = $value;
395            len += 1;
396        }};
397    }
398
399    write_to_buffer! {
400        args
401        (
402            len, usize::MAX - 1, usize::MAX, usize::MAX,
403            add_to_len, add_to_len,
404        )
405    }
406
407    len
408}