From b9e85bf60ab4376a321f03078c06cd95ea5143ef Mon Sep 17 00:00:00 2001 From: Colin Baumgarten Date: Sat, 1 Oct 2022 00:40:59 +0200 Subject: [PATCH] Detect and reject out-of-range integers in format string literals Until now out-of-range integers in format string literals were silently ignored. They wrapped around to zero at usize::MAX, producing unexpected results. When using debug builds of rustc, such integers in format string literals even cause an 'attempt to add with overflow' panic in rustc. Fix this by producing an error diagnostic for integers in format string literals which do not fit into usize. Fixes #102528 --- compiler/rustc_parse_format/src/lib.rs | 28 ++++++++++++++++++++---- compiler/rustc_parse_format/src/tests.rs | 15 +++++++++++++ library/alloc/src/fmt.rs | 2 +- 3 files changed, 40 insertions(+), 5 deletions(-) diff --git a/compiler/rustc_parse_format/src/lib.rs b/compiler/rustc_parse_format/src/lib.rs index df22d79f82e85..1394993abade9 100644 --- a/compiler/rustc_parse_format/src/lib.rs +++ b/compiler/rustc_parse_format/src/lib.rs @@ -740,20 +740,40 @@ impl<'a> Parser<'a> { word } - /// Optionally parses an integer at the current position. This doesn't deal - /// with overflow at all, it's just accumulating digits. fn integer(&mut self) -> Option { - let mut cur = 0; + let mut cur: usize = 0; let mut found = false; + let mut overflow = false; + let start = self.current_pos(); while let Some(&(_, c)) = self.cur.peek() { if let Some(i) = c.to_digit(10) { - cur = cur * 10 + i as usize; + let (tmp, mul_overflow) = cur.overflowing_mul(10); + let (tmp, add_overflow) = tmp.overflowing_add(i as usize); + if mul_overflow || add_overflow { + overflow = true; + } + cur = tmp; found = true; self.cur.next(); } else { break; } } + + if overflow { + let end = self.current_pos(); + let overflowed_int = &self.input[start..end]; + self.err( + format!( + "integer `{}` does not fit into the type `usize` whose range is `0..={}`", + overflowed_int, + usize::MAX + ), + "integer out of range for `usize`", + self.span(start, end), + ); + } + if found { Some(cur) } else { None } } diff --git a/compiler/rustc_parse_format/src/tests.rs b/compiler/rustc_parse_format/src/tests.rs index 2f8c229c68ffe..3f9cb149b53eb 100644 --- a/compiler/rustc_parse_format/src/tests.rs +++ b/compiler/rustc_parse_format/src/tests.rs @@ -57,6 +57,21 @@ fn invalid06() { musterr("{:>>>}") } +#[test] +fn invalid_position() { + musterr("{18446744073709551616}"); +} + +#[test] +fn invalid_width() { + musterr("{:18446744073709551616}"); +} + +#[test] +fn invalid_precision() { + musterr("{:.18446744073709551616}"); +} + #[test] fn format_nothing() { same( diff --git a/library/alloc/src/fmt.rs b/library/alloc/src/fmt.rs index ed398b56612ce..799ce9d5daa88 100644 --- a/library/alloc/src/fmt.rs +++ b/library/alloc/src/fmt.rs @@ -327,7 +327,7 @@ //! - `text` must not contain any `'{'` or `'}'` characters, //! - `ws` is any character for which [`char::is_whitespace`] returns `true`, has no semantic //! meaning and is completely optional, -//! - `integer` is a decimal integer that may contain leading zeroes and +//! - `integer` is a decimal integer that may contain leading zeroes and must fit into an `usize` and //! - `identifier` is an `IDENTIFIER_OR_KEYWORD` (not an `IDENTIFIER`) as defined by the [Rust language reference](https://doc.rust-lang.org/reference/identifiers.html). //! //! # Formatting traits