mirror of
https://github.com/uutils/grep.git
synced 2026-06-10 16:15:11 -07:00
grep: map invalid back-reference errors to GNU's wording
A back-reference to a non-existent group, e.g. (.)\2, makes oniguruma fail with 'invalid backref number/name'. GNU words this per engine: 'reference to non-existent subpattern' under -P (PCRE2) and 'Invalid back reference' for basic/extended (gnulib regex). Translate ONIGERR_INVALID_BACKREF accordingly (gnu_error_message now takes the regex mode). Fixes the GNU testsuite 'pcre-wx-backref' test. Also drop pipe_in() from the compile-error tests added in the previous commits: those patterns are rejected before stdin is read, so feeding input raced with the child exiting and intermittently panicked the test harness with a broken pipe under parallel execution.
This commit is contained in:
+18
-5
@@ -9,7 +9,7 @@ use onig::{
|
||||
SyntaxBehavior, SyntaxOperator,
|
||||
};
|
||||
use onig_sys::{
|
||||
ONIGERR_EMPTY_RANGE_IN_CHAR_CLASS, ONIGERR_RETRY_LIMIT_IN_MATCH_OVER,
|
||||
ONIGERR_EMPTY_RANGE_IN_CHAR_CLASS, ONIGERR_INVALID_BACKREF, ONIGERR_RETRY_LIMIT_IN_MATCH_OVER,
|
||||
ONIGERR_RETRY_LIMIT_IN_SEARCH_OVER, OnigEncCtype_ONIGENC_CTYPE_WORD, OnigEncodingUTF8,
|
||||
};
|
||||
use std::io;
|
||||
@@ -248,22 +248,29 @@ impl CompiledPattern {
|
||||
options |= RegexOptions::REGEX_OPTION_IGNORECASE;
|
||||
}
|
||||
|
||||
fn compile_with(pattern: &str, syntax: &Syntax, options: RegexOptions) -> UResult<Regex> {
|
||||
let mode = config.regex_mode;
|
||||
fn compile_with(
|
||||
pattern: &str,
|
||||
syntax: &Syntax,
|
||||
options: RegexOptions,
|
||||
mode: RegexMode,
|
||||
) -> UResult<Regex> {
|
||||
Regex::with_options_and_encoding(pattern, options, syntax).map_err(|err| {
|
||||
// Prefer GNU grep's wording for the errors it has a dedicated
|
||||
// message for; fall back to oniguruma's text otherwise.
|
||||
match gnu_error_message(err.code()) {
|
||||
match gnu_error_message(err.code(), mode) {
|
||||
Some(msg) => USimpleError::new(2, msg.to_string()),
|
||||
None => USimpleError::new(2, format!("invalid pattern \"{pattern}\": {err}")),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
let leftmost = compile_with(pattern, &syntax, options)?;
|
||||
let leftmost = compile_with(pattern, &syntax, options, mode)?;
|
||||
let longest_anchored = compile_with(
|
||||
pattern,
|
||||
&syntax,
|
||||
options | RegexOptions::REGEX_OPTION_FIND_LONGEST,
|
||||
mode,
|
||||
)?;
|
||||
Ok(Self {
|
||||
leftmost,
|
||||
@@ -336,10 +343,16 @@ fn match_error(err: Error) -> io::Error {
|
||||
/// `Invalid range end`) rather than oniguruma's phrasing, so translating keeps
|
||||
/// us byte-compatible. Returns `None` for errors with no GNU equivalent, where
|
||||
/// the caller falls back to oniguruma's own message.
|
||||
fn gnu_error_message(code: i32) -> Option<&'static str> {
|
||||
fn gnu_error_message(code: i32, mode: RegexMode) -> Option<&'static str> {
|
||||
match code {
|
||||
// e.g. `[b-a]`: a range whose end precedes its start.
|
||||
ONIGERR_EMPTY_RANGE_IN_CHAR_CLASS => Some("Invalid range end"),
|
||||
// e.g. `(.)\2`: a back-reference to a group that does not exist. GNU
|
||||
// (via PCRE2) and gnulib's regex word this differently.
|
||||
ONIGERR_INVALID_BACKREF if mode == RegexMode::Perl => {
|
||||
Some("reference to non-existent subpattern")
|
||||
}
|
||||
ONIGERR_INVALID_BACKREF => Some("Invalid back reference"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
+30
-3
@@ -130,10 +130,11 @@ fn ere_invalid_pattern_is_error() {
|
||||
fn confusing_bracket_class_is_error() {
|
||||
// GNU grep rejects the misspelled `[:name:]` form (meant to be
|
||||
// `[[:name:]]`) with a dedicated diagnostic and exit code 2.
|
||||
// No piped input: the pattern is rejected at compile time, before stdin is
|
||||
// read, so feeding stdin would race with the child exiting (broken pipe).
|
||||
for pattern in ["[:space:]", "[:digit:]", "[^:space:]", "x[:space:]y"] {
|
||||
let (_s, mut c) = ucmd();
|
||||
c.args(&[pattern])
|
||||
.pipe_in("x\n")
|
||||
.fails_with_code(2)
|
||||
.stderr_is("grep: character class syntax is [[:space:]], not [:space:]\n");
|
||||
}
|
||||
@@ -141,7 +142,6 @@ fn confusing_bracket_class_is_error() {
|
||||
// The same diagnostic applies in extended mode.
|
||||
let (_s, mut c) = ucmd();
|
||||
c.args(&["-E", "[:space:]"])
|
||||
.pipe_in("x\n")
|
||||
.fails_with_code(2)
|
||||
.stderr_is("grep: character class syntax is [[:space:]], not [:space:]\n");
|
||||
}
|
||||
@@ -184,10 +184,10 @@ fn lookalike_brackets_are_not_confusing() {
|
||||
fn reversed_range_uses_gnu_wording() {
|
||||
// A range like `[b-a]` is an error; GNU prints the bare POSIX diagnostic
|
||||
// "Invalid range end" (not oniguruma's phrasing) and exits 2.
|
||||
// No piped input: the pattern is rejected before stdin is read.
|
||||
for args in [&["[b-a]"][..], &["-E", "[b-a]"][..]] {
|
||||
let (_s, mut c) = ucmd();
|
||||
c.args(args)
|
||||
.pipe_in("x\n")
|
||||
.fails_with_code(2)
|
||||
.stderr_is("grep: Invalid range end\n");
|
||||
}
|
||||
@@ -205,6 +205,33 @@ fn pcre_backtracking_limit_does_not_abort() {
|
||||
.stderr_contains("backtracking limit");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_backreference_uses_gnu_wording() {
|
||||
// A back-reference to a non-existent group is worded differently by GNU
|
||||
// depending on the engine: PCRE (-P) vs gnulib regex (BRE/ERE).
|
||||
// No piped input: these patterns are rejected before stdin is read.
|
||||
let (_s, mut c) = ucmd();
|
||||
c.args(&["-P", r"(.)\2"])
|
||||
.fails_with_code(2)
|
||||
.stderr_is("grep: reference to non-existent subpattern\n");
|
||||
|
||||
for args in [&["-E", r"(.)\2"][..], &[r"\(.\)\2"][..]] {
|
||||
let (_s, mut c) = ucmd();
|
||||
c.args(args)
|
||||
.fails_with_code(2)
|
||||
.stderr_is("grep: Invalid back reference\n");
|
||||
}
|
||||
|
||||
// A valid back-reference with -Pw / -Px must still match.
|
||||
for flag in ["-Pw", "-Px"] {
|
||||
let (_s, mut c) = ucmd();
|
||||
c.args(&[flag, r"(.)\1"])
|
||||
.pipe_in("aa\n")
|
||||
.succeeds()
|
||||
.stdout_is("aa\n");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fixed_string_is_literal() {
|
||||
// Metacharacters are not interpreted.
|
||||
|
||||
Reference in New Issue
Block a user