Skip to content

Commit fc4f39c

Browse files
authored
Merge pull request #7259 from jfinkels/printf-missing-hex-value
printf: error on missing hexadecial escape value
2 parents c250584 + db280b6 commit fc4f39c

File tree

3 files changed

+44
-22
lines changed

3 files changed

+44
-22
lines changed

src/uucore/src/lib/features/format/escape.rs

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -94,43 +94,50 @@ fn parse_unicode(input: &mut &[u8], digits: u8) -> Option<char> {
9494
char::from_u32(ret)
9595
}
9696

97-
pub fn parse_escape_code(rest: &mut &[u8]) -> EscapedChar {
97+
/// Represents an invalid escape sequence.
98+
#[derive(Debug)]
99+
pub struct EscapeError {}
100+
101+
/// Parse an escape sequence, like `\n` or `\xff`, etc.
102+
pub fn parse_escape_code(rest: &mut &[u8]) -> Result<EscapedChar, EscapeError> {
98103
if let [c, new_rest @ ..] = rest {
99104
// This is for the \NNN syntax for octal sequences.
100105
// Note that '0' is intentionally omitted because that
101106
// would be the \0NNN syntax.
102107
if let b'1'..=b'7' = c {
103108
if let Some(parsed) = parse_code(rest, Base::Oct) {
104-
return EscapedChar::Byte(parsed);
109+
return Ok(EscapedChar::Byte(parsed));
105110
}
106111
}
107112

108113
*rest = new_rest;
109114
match c {
110-
b'\\' => EscapedChar::Byte(b'\\'),
111-
b'"' => EscapedChar::Byte(b'"'),
112-
b'a' => EscapedChar::Byte(b'\x07'),
113-
b'b' => EscapedChar::Byte(b'\x08'),
114-
b'c' => EscapedChar::End,
115-
b'e' => EscapedChar::Byte(b'\x1b'),
116-
b'f' => EscapedChar::Byte(b'\x0c'),
117-
b'n' => EscapedChar::Byte(b'\n'),
118-
b'r' => EscapedChar::Byte(b'\r'),
119-
b't' => EscapedChar::Byte(b'\t'),
120-
b'v' => EscapedChar::Byte(b'\x0b'),
115+
b'\\' => Ok(EscapedChar::Byte(b'\\')),
116+
b'"' => Ok(EscapedChar::Byte(b'"')),
117+
b'a' => Ok(EscapedChar::Byte(b'\x07')),
118+
b'b' => Ok(EscapedChar::Byte(b'\x08')),
119+
b'c' => Ok(EscapedChar::End),
120+
b'e' => Ok(EscapedChar::Byte(b'\x1b')),
121+
b'f' => Ok(EscapedChar::Byte(b'\x0c')),
122+
b'n' => Ok(EscapedChar::Byte(b'\n')),
123+
b'r' => Ok(EscapedChar::Byte(b'\r')),
124+
b't' => Ok(EscapedChar::Byte(b'\t')),
125+
b'v' => Ok(EscapedChar::Byte(b'\x0b')),
121126
b'x' => {
122127
if let Some(c) = parse_code(rest, Base::Hex) {
123-
EscapedChar::Byte(c)
128+
Ok(EscapedChar::Byte(c))
124129
} else {
125-
EscapedChar::Backslash(b'x')
130+
Err(EscapeError {})
126131
}
127132
}
128-
b'0' => EscapedChar::Byte(parse_code(rest, Base::Oct).unwrap_or(b'\0')),
129-
b'u' => EscapedChar::Char(parse_unicode(rest, 4).unwrap_or('\0')),
130-
b'U' => EscapedChar::Char(parse_unicode(rest, 8).unwrap_or('\0')),
131-
c => EscapedChar::Backslash(*c),
133+
b'0' => Ok(EscapedChar::Byte(
134+
parse_code(rest, Base::Oct).unwrap_or(b'\0'),
135+
)),
136+
b'u' => Ok(EscapedChar::Char(parse_unicode(rest, 4).unwrap_or('\0'))),
137+
b'U' => Ok(EscapedChar::Char(parse_unicode(rest, 8).unwrap_or('\0'))),
138+
c => Ok(EscapedChar::Backslash(*c)),
132139
}
133140
} else {
134-
EscapedChar::Byte(b'\\')
141+
Ok(EscapedChar::Byte(b'\\'))
135142
}
136143
}

src/uucore/src/lib/features/format/mod.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ pub enum FormatError {
6767
InvalidPrecision(String),
6868
/// The format specifier ends with a %, as in `%f%`.
6969
EndsWithPercent(Vec<u8>),
70+
/// The escape sequence `\x` appears without a literal hexadecimal value.
71+
MissingHex,
7072
}
7173

7274
impl Error for FormatError {}
@@ -105,6 +107,7 @@ impl Display for FormatError {
105107
Self::IoError(_) => write!(f, "io error"),
106108
Self::NoMoreArguments => write!(f, "no more arguments"),
107109
Self::InvalidArgument(_) => write!(f, "invalid argument"),
110+
Self::MissingHex => write!(f, "missing hexadecimal number in escape"),
108111
}
109112
}
110113
}
@@ -181,7 +184,10 @@ pub fn parse_spec_and_escape(
181184
}
182185
[b'\\', rest @ ..] => {
183186
current = rest;
184-
Some(Ok(FormatItem::Char(parse_escape_code(&mut current))))
187+
Some(match parse_escape_code(&mut current) {
188+
Ok(c) => Ok(FormatItem::Char(c)),
189+
Err(_) => Err(FormatError::MissingHex),
190+
})
185191
}
186192
[c, rest @ ..] => {
187193
current = rest;
@@ -224,7 +230,7 @@ pub fn parse_escape_only(fmt: &[u8]) -> impl Iterator<Item = EscapedChar> + '_ {
224230
[] => None,
225231
[b'\\', rest @ ..] => {
226232
current = rest;
227-
Some(parse_escape_code(&mut current))
233+
Some(parse_escape_code(&mut current).unwrap_or(EscapedChar::Backslash(b'x')))
228234
}
229235
[c, rest @ ..] => {
230236
current = rest;

tests/by-util/test_printf.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,15 @@ fn escaped_hex() {
4646
new_ucmd!().args(&["\\x41"]).succeeds().stdout_only("A");
4747
}
4848

49+
#[test]
50+
fn test_missing_escaped_hex_value() {
51+
new_ucmd!()
52+
.arg(r"\x")
53+
.fails()
54+
.code_is(1)
55+
.stderr_only("printf: missing hexadecimal number in escape\n");
56+
}
57+
4958
#[test]
5059
fn escaped_octal() {
5160
new_ucmd!().args(&["\\101"]).succeeds().stdout_only("A");

0 commit comments

Comments
 (0)