Skip to content

Commit d4aa2f4

Browse files
committed
Handle large formatting widths by padding directly
1 parent 3cb8345 commit d4aa2f4

File tree

5 files changed

+92
-67
lines changed

5 files changed

+92
-67
lines changed

src/format/mod.rs

Lines changed: 68 additions & 57 deletions
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,16 +321,24 @@ 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 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(u16::MAX.into()))?;
338+
Ok(width.min(u16::MAX.into()))
339+
}
340+
332341
/// Format nanoseconds with the specified precision.
333-
#[allow(clippy::uninlined_format_args)] // for readability and symmetry between if branches
334342
fn format_nanoseconds(
335343
&self,
336344
f: &mut SizeLimiter<'_>,
@@ -341,38 +349,37 @@ impl Piece {
341349

342350
if width <= 9 {
343351
let value = nanoseconds / 10u32.pow(9 - width as u32);
344-
write!(f, "{value:0n$}", n = width)
352+
write!(f, "{value:0width$}")
345353
} else {
346-
write!(f, "{nanoseconds:09}{:0n$}", 0, n = width - 9)
354+
write!(f, "{nanoseconds:09}")?;
355+
f.pad(b'0', width - 9)
347356
}
348357
}
349358

350359
/// Format a string value.
351360
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-
}
361+
if !self.flags.contains(Flag::LeftPadding) {
362+
self.write_padding(f, s.len())?;
363363
}
364+
365+
write!(f, "{s}")
364366
}
365367

366368
/// Write padding separately.
367369
fn write_padding(&self, f: &mut SizeLimiter<'_>, min_width: usize) -> Result<(), Error> {
368-
if let Some(width) = self.width {
369-
let n = width.saturating_sub(min_width);
370+
let Some(width) = self.width else {
371+
return Ok(());
372+
};
373+
374+
let n = width.saturating_sub(min_width);
375+
376+
let pad = match self.padding {
377+
Padding::Zeros => b'0',
378+
_ => b' ',
379+
};
380+
381+
f.pad(pad, n)?;
370382

371-
match self.padding {
372-
Padding::Zeros => write!(f, "{:0>n$}", "")?,
373-
_ => write!(f, "{: >n$}", "")?,
374-
};
375-
}
376383
Ok(())
377384
}
378385

@@ -396,14 +403,34 @@ impl Piece {
396403
UtcOffset::new(hour, minute, second)
397404
}
398405

399-
/// Compute hour padding for the `%z` specifier.
400-
fn hour_padding(&self, min_width: usize) -> usize {
401-
const MIN_PADDING: usize = "+hh".len();
406+
/// Write the hour sign.
407+
fn write_hour_sign(f: &mut SizeLimiter<'_>, hour: f64) -> Result<(), Error> {
408+
if hour.is_sign_negative() {
409+
write!(f, "-")?;
410+
} else {
411+
write!(f, "+")?;
412+
}
413+
414+
Ok(())
415+
}
402416

403-
match self.width {
404-
Some(width) => width.saturating_sub(min_width) + MIN_PADDING,
405-
None => MIN_PADDING,
417+
/// Write the hour with padding for the `%z` specifier.
418+
fn write_offset_hour(&self, f: &mut SizeLimiter<'_>, hour: f64, w: usize) -> Result<(), Error> {
419+
let mut pad = self.width.unwrap_or(0).saturating_sub(w);
420+
421+
if hour < 10.0 {
422+
pad += 1;
423+
}
424+
425+
if self.padding == Padding::Spaces {
426+
f.pad(b' ', pad)?;
427+
Self::write_hour_sign(f, hour)?;
428+
} else {
429+
Self::write_hour_sign(f, hour)?;
430+
f.pad(b'0', pad)?;
406431
}
432+
433+
write!(f, "{:.0}", hour.abs())
407434
}
408435

409436
/// Write the time zone UTC offset as `"+hh"`.
@@ -412,13 +439,7 @@ impl Piece {
412439
f: &mut SizeLimiter<'_>,
413440
utc_offset: &UtcOffset,
414441
) -> 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-
}
442+
self.write_offset_hour(f, utc_offset.hour, "+hh".len())
422443
}
423444

424445
/// Write the time zone UTC offset as `"+hhmm"`.
@@ -427,13 +448,10 @@ impl Piece {
427448
f: &mut SizeLimiter<'_>,
428449
utc_offset: &UtcOffset,
429450
) -> Result<(), Error> {
430-
let UtcOffset { hour, minute, .. } = utc_offset;
431-
let n = self.hour_padding("+hhmm".len());
451+
let UtcOffset { hour, minute, .. } = *utc_offset;
432452

433-
match self.padding {
434-
Padding::Spaces => write!(f, "{hour: >+n$.0}{minute:02}"),
435-
_ => write!(f, "{hour:+0n$.0}{minute:02}"),
436-
}
453+
self.write_offset_hour(f, hour, "+hhmm".len())?;
454+
write!(f, "{minute:02}")
437455
}
438456

439457
/// Write the time zone UTC offset as `"+hh:mm"`.
@@ -442,13 +460,10 @@ impl Piece {
442460
f: &mut SizeLimiter<'_>,
443461
utc_offset: &UtcOffset,
444462
) -> Result<(), Error> {
445-
let UtcOffset { hour, minute, .. } = utc_offset;
446-
let n = self.hour_padding("+hh:mm".len());
463+
let UtcOffset { hour, minute, .. } = *utc_offset;
447464

448-
match self.padding {
449-
Padding::Spaces => write!(f, "{hour: >+n$.0}:{minute:02}"),
450-
_ => write!(f, "{hour:+0n$.0}:{minute:02}"),
451-
}
465+
self.write_offset_hour(f, hour, "+hh:mm".len())?;
466+
write!(f, ":{minute:02}")
452467
}
453468

454469
/// Write the time zone UTC offset as `"+hh:mm:ss"`.
@@ -461,14 +476,10 @@ impl Piece {
461476
hour,
462477
minute,
463478
second,
464-
} = utc_offset;
465-
466-
let n = self.hour_padding("+hh:mm:ss".len());
479+
} = *utc_offset;
467480

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-
}
481+
self.write_offset_hour(f, hour, "+hh:mm:ss".len())?;
482+
write!(f, ":{minute:02}:{second:02}")
472483
}
473484

474485
/// Format time using the formatting directive.

src/format/utils.rs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,11 +81,31 @@ impl<'a> SizeLimiter<'a> {
8181
count: 0,
8282
}
8383
}
84+
85+
/// Pad with the specified character.
86+
pub(crate) fn pad(&mut self, c: u8, n: usize) -> Result<(), Error> {
87+
if self.count.saturating_add(n) > self.size_limit {
88+
return Err(Error::FormattedStringTooLarge);
89+
}
90+
91+
let buffer = [c; 1024];
92+
let mut remaining = n;
93+
94+
while remaining > 0 {
95+
let size = remaining.min(1024);
96+
self.inner.write_all(&buffer[..size])?;
97+
remaining -= size;
98+
}
99+
100+
self.count += n;
101+
102+
Ok(())
103+
}
84104
}
85105

86106
impl Write for SizeLimiter<'_> {
87107
fn write(&mut self, buf: &[u8]) -> Result<usize, Error> {
88-
if self.count + buf.len() > self.size_limit {
108+
if self.count.saturating_add(buf.len()) > self.size_limit {
89109
return Err(Error::FormattedStringTooLarge);
90110
}
91111

src/tests/format.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -825,7 +825,7 @@ fn test_format_large_width() {
825825
check_format(&time, "%-100000000m", "1");
826826
check_format(&time, "%2147483648m", "%2147483648m");
827827

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

src/tests/mod.rs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,6 @@ fn get_format_err(time: &MockTime<'_>, format: &str) -> Error {
1313
.unwrap_err()
1414
}
1515

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-
2216
fn check_format(time: &MockTime<'_>, format: &str, expected: &str) {
2317
const SIZE: usize = 100;
2418
let mut buf = [0u8; SIZE];

src/tests/rust_fmt_argument_max_padding.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ use {
2626

2727
use crate::Error;
2828

29-
use super::{check_all, get_format_err, get_format_err_bytes, MockTime};
29+
use super::{check_all, MockTime};
3030

3131
#[test]
3232
fn test_larger_than_int_max_formats_are_returned_verbatim() {
@@ -151,7 +151,7 @@ fn test_format_specifiers_int_max_fail() {
151151
.fmt(&mut &mut buf[..])
152152
.unwrap_err();
153153
assert!(
154-
matches!(err, Error::WriteZero),
154+
matches!(err, Error::FormattedStringTooLarge),
155155
"Expected write failure for specifier '{spec}' with width {width} but got unexpected error: {err:?}",
156156
);
157157
}

0 commit comments

Comments
 (0)