From a4caca11ada97433422b3f06cd5985cc2ac3ff15 Mon Sep 17 00:00:00 2001 From: Jay Hemnani <193022578+jayhemnani9910@users.noreply.github.com> Date: Tue, 27 Oct 2026 22:51:22 +0530 Subject: [PATCH] grep: reject an interval whose lower bound is above its upper bound Oniguruma reads a{2,1} as a possessive a{1,2}, so grep -E 'fo{2,1}' matched lines where GNU grep fails with "Invalid content of \{\}" and exit 2. With PLUS_POSSESSIVE_INTERVAL Oniguruma rejects it, but that flag also makes {n,m}+ possessive, so it is only used for one extra compile that checks for this error. --- src/matcher.rs | 21 ++++++++++++++++++++- tests/test_grep.rs | 10 ++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/matcher.rs b/src/matcher.rs index dac786b..fb9c7d0 100644 --- a/src/matcher.rs +++ b/src/matcher.rs @@ -7,7 +7,8 @@ use crate::{Config, RegexMode}; use memchr::memmem; use onig::{RegexOptions, Region, SearchOptions, Syntax, SyntaxBehavior, SyntaxOperator}; use onig_sys::{ - ONIGERR_EMPTY_RANGE_IN_CHAR_CLASS, OnigEncCtype_ONIGENC_CTYPE_WORD, OnigEncodingUTF8, + ONIGERR_EMPTY_RANGE_IN_CHAR_CLASS, ONIGERR_UPPER_SMALLER_THAN_LOWER_IN_REPEAT_RANGE, + OnigEncCtype_ONIGENC_CTYPE_WORD, OnigEncodingUTF8, }; use std::ptr::{null, null_mut}; use std::sync::Mutex; @@ -298,6 +299,24 @@ impl CompiledPattern { pattern }; + // Oniguruma reads `{2,1}` as a possessive `{1,2}`, GNU grep rejects it. + // PLUS_POSSESSIVE_INTERVAL makes Oniguruma reject it too, but it also + // makes `{n,m}+` possessive, so it's only used for this check. + if matches!(config.regex_mode, RegexMode::Basic | RegexMode::Extended) + && pattern.contains('{') + { + let mut strict = syntax; + strict.enable_operators(SyntaxOperator::SYNTAX_OPERATOR_PLUS_POSSESSIVE_INTERVAL); + if let Err(err) = OnigRegex::compile(pattern, &strict, RegexOptions::REGEX_OPTION_NONE) + && err.code == ONIGERR_UPPER_SMALLER_THAN_LOWER_IN_REPEAT_RANGE + { + return Err(USimpleError::new( + 2, + "Invalid content of \\{\\}".to_string(), + )); + } + } + if config.regex_mode == RegexMode::Perl { // GNU grep supports `(?P...)`. // Unfortunately, the onig crate defines the OP2 flag without the diff --git a/tests/test_grep.rs b/tests/test_grep.rs index 83a472f..0cfe4a2 100644 --- a/tests/test_grep.rs +++ b/tests/test_grep.rs @@ -223,6 +223,16 @@ fn misspelled_character_class_is_error() { } } +#[test] +fn reversed_interval_is_error() { + for args in [&["-E", "fo{2,1}"][..], &["fo\\{2,1\\}"][..]] { + let (_s, mut c) = ucmd(); + c.args(args) + .fails_with_code(2) + .stderr_contains("Invalid content of \\{\\}"); + } +} + #[test] fn max_count_rewinds_seekable_stdin() { // -m stops mid-input after reading ahead. When standard input can seek,