Skip to content

Commit 23c2037

Browse files
committed
Handle excessive widths.
Without relying on write!() to support very large widths.
1 parent decff38 commit 23c2037

File tree

4 files changed

+62
-56
lines changed

4 files changed

+62
-56
lines changed

src/format/mod.rs

+47-52
Original file line numberDiff line numberDiff line change
@@ -303,10 +303,10 @@ impl Piece {
303303
if self.flags.contains(Flag::LeftPadding) {
304304
write!(f, "{value}")
305305
} else if self.padding == Padding::Spaces {
306-
let width = self.width.unwrap_or(default_width);
306+
let width = self.pad_width(f, b' ', default_width)?;
307307
write!(f, "{value: >width$}")
308308
} else {
309-
let width = self.width.unwrap_or(default_width);
309+
let width = self.pad_width(f, b'0', default_width)?;
310310
write!(f, "{value:0width$}")
311311
}
312312
}
@@ -321,14 +321,23 @@ impl Piece {
321321
if self.flags.contains(Flag::LeftPadding) {
322322
write!(f, "{value}")
323323
} else if self.padding == Padding::Zeros {
324-
let width = self.width.unwrap_or(default_width);
324+
let width = self.pad_width(f, b'0', default_width)?;
325325
write!(f, "{value:0width$}")
326326
} else {
327-
let width = self.width.unwrap_or(default_width);
327+
let width = self.pad_width(f, b' ', default_width)?;
328328
write!(f, "{value: >width$}")
329329
}
330330
}
331331

332+
// Returns the width to use for the padding.
333+
//
334+
// Prints any excessive (>10) padding directly.
335+
fn pad_width(&self, f: &mut SizeLimiter<'_>, pad: u8, default: usize) -> Result<usize, Error> {
336+
let width = self.width.unwrap_or(default);
337+
f.pad(pad, width.saturating_sub(10))?;
338+
Ok(width.min(10))
339+
}
340+
332341
/// Format nanoseconds with the specified precision.
333342
#[allow(clippy::uninlined_format_args)] // for readability and symmetry between if branches
334343
fn format_nanoseconds(
@@ -343,35 +352,30 @@ impl Piece {
343352
let value = nanoseconds / 10u32.pow(9 - width as u32);
344353
write!(f, "{value:0n$}", n = width)
345354
} else {
346-
write!(f, "{nanoseconds:09}{:0n$}", 0, n = width - 9)
355+
write!(f, "{nanoseconds:09}")?;
356+
f.pad(b'0', width - 9)
347357
}
348358
}
349359

350360
/// Format a string value.
351361
fn format_string(&self, f: &mut SizeLimiter<'_>, s: &str) -> Result<(), Error> {
352-
match self.width {
353-
None => write!(f, "{s}"),
354-
Some(width) => {
355-
if self.flags.contains(Flag::LeftPadding) {
356-
write!(f, "{s}")
357-
} else if self.padding == Padding::Zeros {
358-
write!(f, "{s:0>width$}")
359-
} else {
360-
write!(f, "{s: >width$}")
361-
}
362-
}
362+
if !self.flags.contains(Flag::LeftPadding) {
363+
self.write_padding(f, s.len())?;
363364
}
365+
write!(f, "{s}")
364366
}
365367

366368
/// Write padding separately.
367369
fn write_padding(&self, f: &mut SizeLimiter<'_>, min_width: usize) -> Result<(), Error> {
368370
if let Some(width) = self.width {
369371
let n = width.saturating_sub(min_width);
370372

371-
match self.padding {
372-
Padding::Zeros => write!(f, "{:0>n$}", "")?,
373-
_ => write!(f, "{: >n$}", "")?,
373+
let pad = match self.padding {
374+
Padding::Zeros => b'0',
375+
_ => b' ',
374376
};
377+
378+
f.pad(pad, n)?;
375379
}
376380
Ok(())
377381
}
@@ -396,14 +400,24 @@ impl Piece {
396400
UtcOffset::new(hour, minute, second)
397401
}
398402

399-
/// Compute hour padding for the `%z` specifier.
400-
fn hour_padding(&self, min_width: usize) -> usize {
401-
const MIN_PADDING: usize = "+hh".len();
402-
403-
match self.width {
404-
Some(width) => width.saturating_sub(min_width) + MIN_PADDING,
405-
None => MIN_PADDING,
403+
/// Print the hour with padding for the `%z` specifier.
404+
fn write_offset_hour(&self, f: &mut SizeLimiter<'_>, hour: f64, w: usize) -> Result<(), Error> {
405+
let mut pad = self.width.unwrap_or(0).saturating_sub(w);
406+
if hour < 10.0 {
407+
pad += 1;
408+
}
409+
if self.padding == Padding::Spaces {
410+
f.pad(b' ', pad)?;
411+
}
412+
if hour.is_sign_negative() {
413+
write!(f, "-")?;
414+
} else {
415+
write!(f, "+")?;
406416
}
417+
if self.padding != Padding::Spaces {
418+
f.pad(b'0', pad)?;
419+
}
420+
write!(f, "{:.0}", hour.abs())
407421
}
408422

409423
/// Write the time zone UTC offset as `"+hh"`.
@@ -412,13 +426,7 @@ impl Piece {
412426
f: &mut SizeLimiter<'_>,
413427
utc_offset: &UtcOffset,
414428
) -> Result<(), Error> {
415-
let hour = utc_offset.hour;
416-
let n = self.hour_padding("+hh".len());
417-
418-
match self.padding {
419-
Padding::Spaces => write!(f, "{hour: >+n$.0}"),
420-
_ => write!(f, "{hour:+0n$.0}"),
421-
}
429+
self.write_offset_hour(f, utc_offset.hour, "+hh".len())
422430
}
423431

424432
/// Write the time zone UTC offset as `"+hhmm"`.
@@ -428,12 +436,8 @@ impl Piece {
428436
utc_offset: &UtcOffset,
429437
) -> Result<(), Error> {
430438
let UtcOffset { hour, minute, .. } = utc_offset;
431-
let n = self.hour_padding("+hhmm".len());
432-
433-
match self.padding {
434-
Padding::Spaces => write!(f, "{hour: >+n$.0}{minute:02}"),
435-
_ => write!(f, "{hour:+0n$.0}{minute:02}"),
436-
}
439+
self.write_offset_hour(f, *hour, "+hhmm".len())?;
440+
write!(f, "{minute:02}")
437441
}
438442

439443
/// Write the time zone UTC offset as `"+hh:mm"`.
@@ -443,12 +447,8 @@ impl Piece {
443447
utc_offset: &UtcOffset,
444448
) -> Result<(), Error> {
445449
let UtcOffset { hour, minute, .. } = utc_offset;
446-
let n = self.hour_padding("+hh:mm".len());
447-
448-
match self.padding {
449-
Padding::Spaces => write!(f, "{hour: >+n$.0}:{minute:02}"),
450-
_ => write!(f, "{hour:+0n$.0}:{minute:02}"),
451-
}
450+
self.write_offset_hour(f, *hour, "+hh:mm".len())?;
451+
write!(f, ":{minute:02}")
452452
}
453453

454454
/// Write the time zone UTC offset as `"+hh:mm:ss"`.
@@ -462,13 +462,8 @@ impl Piece {
462462
minute,
463463
second,
464464
} = utc_offset;
465-
466-
let n = self.hour_padding("+hh:mm:ss".len());
467-
468-
match self.padding {
469-
Padding::Spaces => write!(f, "{hour: >+n$.0}:{minute:02}:{second:02}"),
470-
_ => write!(f, "{hour:+0n$.0}:{minute:02}:{second:02}"),
471-
}
465+
self.write_offset_hour(f, *hour, "+hh:mm:ss".len())?;
466+
write!(f, ":{minute:02}:{second:02}")
472467
}
473468

474469
/// Format time using the formatting directive.

src/format/utils.rs

+11
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,17 @@ impl<'a> SizeLimiter<'a> {
8181
count: 0,
8282
}
8383
}
84+
85+
pub(crate) fn pad(&mut self, c: u8, n: usize) -> Result<(), Error> {
86+
if self.count + n > self.size_limit {
87+
return Err(Error::FormattedStringTooLarge);
88+
}
89+
for _ in 0..n {
90+
self.inner.write_all(&[c])?;
91+
}
92+
self.count += n;
93+
Ok(())
94+
}
8495
}
8596

8697
impl Write for SizeLimiter<'_> {

src/tests/format.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -826,7 +826,7 @@ fn test_format_large_width() {
826826
check_format(&time, "%2147483648m", "%2147483648m");
827827

828828
let err = get_format_err(&time, "%2147483647m");
829-
assert!(matches!(err, Error::WriteZero));
829+
assert!(matches!(err, Error::FormattedStringTooLarge));
830830
}
831831

832832
#[cfg(feature = "alloc")]

src/tests/rust_fmt_argument_max_padding.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ fn test_rust_136932_reduce_fmt_argument_width_and_precision_fuzzer_failures() {
8383

8484
for (time, format) in test_cases {
8585
let err = get_format_err(&time, format);
86-
assert!(matches!(err, Error::WriteZero));
86+
assert!(matches!(err, Error::WriteZero | Error::FormattedStringTooLarge));
8787
}
8888
}
8989

@@ -127,7 +127,7 @@ fn test_rust_136932_reduce_fmt_argument_width_and_precision_fuzzer_failures_byte
127127

128128
for (time, format) in test_cases {
129129
let err = get_format_err_bytes(&time, format);
130-
assert!(matches!(err, Error::WriteZero));
130+
assert!(matches!(err, Error::WriteZero | Error::FormattedStringTooLarge));
131131
}
132132
}
133133

@@ -237,7 +237,7 @@ fn test_format_specifiers_int_max_fail() {
237237
.fmt(&mut &mut buf[..])
238238
.unwrap_err();
239239
assert!(
240-
matches!(err, Error::WriteZero),
240+
matches!(err, Error::WriteZero | Error::FormattedStringTooLarge),
241241
"Expected write failure for specifier '{spec}' with width {width} but got unexpected error: {err:?}",
242242
);
243243
}

0 commit comments

Comments
 (0)