Skip to content

Commit 448aed8

Browse files
committed
Add tests for dynamic width specifiers exceeding u16::MAX
Rust PR rust-lang/rust#136932 (part of rust-lang/rust#99012) limited format string width and precision to 16 bits, causing panics when dynamic padding exceeds `u16::MAX`. These tests validate handling excessively large widths discovered via fuzzing in artichoke/strftime-ruby. They ensure correct, panic-free behavior consistent with CRuby's `Time#strftime`. Additionally add tests for width specifiers which exceed `INT_MAX` to ensure they return the formatting string verbatim, which were among the cases discussed in the upstream PR. See: - Upstream report: rust-lang/rust#136932 (comment) - Proposed fix: rust-lang/rust#136932 (comment)
1 parent 2f0ab4a commit 448aed8

File tree

3 files changed

+518
-50
lines changed

3 files changed

+518
-50
lines changed

src/tests/format.rs

+2-50
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,9 @@
11
#![allow(clippy::should_panic_without_expect)]
22

33
use crate::format::TimeFormatter;
4-
use crate::{Error, Time};
4+
use crate::Error;
55

6-
include!("../mock.rs.in");
7-
8-
fn get_format_err(time: &MockTime<'_>, format: &str) -> Error {
9-
TimeFormatter::new(time, format)
10-
.fmt(&mut &mut [0u8; 100][..])
11-
.unwrap_err()
12-
}
13-
14-
fn check_format(time: &MockTime<'_>, format: &str, expected: &str) {
15-
const SIZE: usize = 100;
16-
let mut buf = [0u8; SIZE];
17-
let mut cursor = &mut buf[..];
18-
19-
TimeFormatter::new(time, format).fmt(&mut cursor).unwrap();
20-
let written = SIZE - cursor.len();
21-
let data = core::str::from_utf8(&buf[..written]).unwrap();
22-
23-
assert_eq!(data, expected);
24-
}
25-
26-
fn check_all(times: &[MockTime<'_>], format: &str, all_expected: &[&str]) {
27-
assert_eq!(times.len(), all_expected.len());
28-
for (time, expected) in times.iter().zip(all_expected) {
29-
check_format(time, format, expected);
30-
}
31-
}
32-
33-
#[test]
34-
#[should_panic]
35-
#[rustfmt::skip]
36-
fn test_check_format_panics_on_error() {
37-
let time = MockTime { year: 1111, ..Default::default() };
38-
39-
check_format(&time, "'%Y'", "'1112'");
40-
}
41-
42-
#[test]
43-
#[should_panic]
44-
#[rustfmt::skip]
45-
fn test_check_all_panics_on_error() {
46-
let times = [
47-
MockTime { year: -1111, ..Default::default() },
48-
MockTime { year: -11, ..Default::default() },
49-
MockTime { year: 1, ..Default::default() },
50-
MockTime { year: 1111, ..Default::default() },
51-
];
52-
53-
check_all(&times, "'%Y'", &["'-1111'", "'-0011'", "'0001'", "'1112'"]);
54-
}
6+
use super::{check_all, check_format, get_format_err, MockTime};
557

568
#[test]
579
#[rustfmt::skip]

src/tests/mod.rs

+60
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,62 @@
11
mod error;
22
mod format;
3+
mod rust_fmt_argument_max_padding;
4+
5+
use crate::format::TimeFormatter;
6+
use crate::{Error, Time};
7+
8+
include!("../mock.rs.in");
9+
10+
fn get_format_err(time: &MockTime<'_>, format: &str) -> Error {
11+
TimeFormatter::new(time, format)
12+
.fmt(&mut &mut [0u8; 100][..])
13+
.unwrap_err()
14+
}
15+
16+
fn get_format_err_bytes(time: &MockTime<'_>, format: &[u8]) -> Error {
17+
TimeFormatter::new(time, format)
18+
.fmt(&mut &mut [0u8; 100][..])
19+
.unwrap_err()
20+
}
21+
22+
fn check_format(time: &MockTime<'_>, format: &str, expected: &str) {
23+
const SIZE: usize = 100;
24+
let mut buf = [0u8; SIZE];
25+
let mut cursor = &mut buf[..];
26+
27+
TimeFormatter::new(time, format).fmt(&mut cursor).unwrap();
28+
let written = SIZE - cursor.len();
29+
let data = core::str::from_utf8(&buf[..written]).unwrap();
30+
31+
assert_eq!(data, expected);
32+
}
33+
34+
fn check_all(times: &[MockTime<'_>], format: &str, all_expected: &[&str]) {
35+
assert_eq!(times.len(), all_expected.len());
36+
for (time, expected) in times.iter().zip(all_expected) {
37+
check_format(time, format, expected);
38+
}
39+
}
40+
41+
#[test]
42+
#[should_panic]
43+
#[rustfmt::skip]
44+
fn test_check_format_panics_on_error() {
45+
let time = MockTime { year: 1111, ..Default::default() };
46+
47+
check_format(&time, "'%Y'", "'1112'");
48+
}
49+
50+
#[test]
51+
#[should_panic]
52+
#[rustfmt::skip]
53+
fn test_check_all_panics_on_error() {
54+
let times = [
55+
MockTime { year: -1111, ..Default::default() },
56+
MockTime { year: -11, ..Default::default() },
57+
MockTime { year: 1, ..Default::default() },
58+
MockTime { year: 1111, ..Default::default() },
59+
];
60+
61+
check_all(&times, "'%Y'", &["'-1111'", "'-0011'", "'0001'", "'1112'"]);
62+
}

0 commit comments

Comments
 (0)