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,