From 4555e6fe488572a5fc6474bc69e40c4a5aca8103 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Teemu=20P=C3=A4tsi?= Date: Sat, 24 May 2025 01:27:09 +0300 Subject: [PATCH] expr: Handle trailing backslash error --- src/uu/expr/src/expr.rs | 2 ++ src/uu/expr/src/syntax_tree.rs | 6 ++++++ tests/by-util/test_expr.rs | 20 ++++++++++++++++++++ 3 files changed, 28 insertions(+) diff --git a/src/uu/expr/src/expr.rs b/src/uu/expr/src/expr.rs index 073bf501a..fa165f9f3 100644 --- a/src/uu/expr/src/expr.rs +++ b/src/uu/expr/src/expr.rs @@ -50,6 +50,8 @@ pub enum ExprError { UnmatchedClosingBrace, #[error("Invalid content of \\{{\\}}")] InvalidBracketContent, + #[error("Trailing backslash")] + TrailingBackslash, } impl UError for ExprError { diff --git a/src/uu/expr/src/syntax_tree.rs b/src/uu/expr/src/syntax_tree.rs index 106b4bd68..11103ee4b 100644 --- a/src/uu/expr/src/syntax_tree.rs +++ b/src/uu/expr/src/syntax_tree.rs @@ -161,6 +161,7 @@ impl StringOp { match first { Some('^') => {} // Start of string anchor is already added Some('*') => re_string.push_str(r"\*"), + Some('\\') if right.len() == 1 => return Err(ExprError::TrailingBackslash), Some(char) => re_string.push(char), None => return Ok(0.into()), }; @@ -169,6 +170,8 @@ impl StringOp { let mut prev = first.unwrap_or_default(); let mut prev_is_escaped = false; while let Some(curr) = pattern_chars.next() { + let curr_is_escaped = prev == '\\' && !prev_is_escaped; + match curr { '^' => match (prev, prev_is_escaped) { // Start of a capturing group @@ -201,6 +204,9 @@ impl StringOp { re_string.push('$'); } } + '\\' if !curr_is_escaped && pattern_chars.peek().is_none() => { + return Err(ExprError::TrailingBackslash); + } _ => re_string.push(curr), } diff --git a/tests/by-util/test_expr.rs b/tests/by-util/test_expr.rs index c5fb96c3d..e301c2470 100644 --- a/tests/by-util/test_expr.rs +++ b/tests/by-util/test_expr.rs @@ -365,6 +365,26 @@ fn test_regex() { .stdout_only("0\n"); } +#[test] +fn test_regex_trailing_backslash() { + new_ucmd!() + .args(&["\\", ":", "\\\\"]) + .succeeds() + .stdout_only("1\n"); + new_ucmd!() + .args(&["\\", ":", "\\"]) + .fails() + .stderr_only("expr: Trailing backslash\n"); + new_ucmd!() + .args(&["abc\\", ":", "abc\\\\"]) + .succeeds() + .stdout_only("4\n"); + new_ucmd!() + .args(&["abc\\", ":", "abc\\"]) + .fails() + .stderr_only("expr: Trailing backslash\n"); +} + #[test] fn test_substr() { new_ucmd!()