expr: avoid panic on regex retry-limit

Treat onig retry-limit-in-match errors as no match. GNU expr returns exit 1 for the same input.

Fixes #11612
This commit is contained in:
Eyüp Can Akman
2026-04-15 13:39:12 +03:00
committed by Daniel Hofstetter
parent a3ac3badb8
commit 2d5e77deb2
2 changed files with 33 additions and 29 deletions
+24 -29
View File
@@ -9,7 +9,7 @@ use std::{cell::Cell, collections::BTreeMap};
use num_bigint::BigInt;
use num_traits::ToPrimitive;
use onig::{Regex, RegexOptions, Syntax};
use onig::{MatchParam, Regex, RegexOptions, SearchOptions, Syntax};
use crate::{
ExprError, ExprResult,
@@ -366,6 +366,25 @@ fn build_regex(pattern_bytes: Vec<u8>) -> ExprResult<(Regex, String)> {
Ok((re, re_string))
}
/// Run a regex search, treating runtime match errors as no match.
fn regex_search<T: onig::EncodedChars>(
regex: &Regex,
chars: T,
to: usize,
region: &mut onig::Region,
) -> Option<usize> {
regex
.search_with_param(
chars,
0,
to,
SearchOptions::SEARCH_OPTION_NONE,
Some(region),
MatchParam::default(),
)
.unwrap_or(None)
}
/// Find matches in the input using the compiled regex
fn find_match(regex: Regex, re_string: String, left_bytes: Vec<u8>) -> String {
use onig::EncodedBytes;
@@ -380,13 +399,7 @@ fn find_match(regex: Regex, re_string: String, left_bytes: Vec<u8>) -> String {
// In UTF-8 locale, check if input is valid UTF-8
if let Ok(left_str) = std::str::from_utf8(&left_bytes) {
// Valid UTF-8, match as UTF-8
let pos = regex.search_with_encoding(
left_str,
0,
left_str.len(),
onig::SearchOptions::SEARCH_OPTION_NONE,
Some(&mut region),
);
let pos = regex_search(&regex, left_str, left_str.len(), &mut region);
if pos.is_some() {
if regex.captures_len() > 0 {
@@ -421,13 +434,7 @@ fn find_match(regex: Regex, re_string: String, left_bytes: Vec<u8>) -> String {
.ok();
if let Some(re_ascii) = re_ascii {
let pos = re_ascii.search_with_encoding(
left_encoded,
0,
left_bytes.len(),
onig::SearchOptions::SEARCH_OPTION_NONE,
Some(&mut region),
);
let pos = regex_search(&re_ascii, left_encoded, left_bytes.len(), &mut region);
if pos.is_some() {
if re_ascii.captures_len() > 0 {
@@ -469,13 +476,7 @@ fn find_match(regex: Regex, re_string: String, left_bytes: Vec<u8>) -> String {
UEncoding::Ascii => {
// In ASCII/C locale, work with bytes directly
let left_encoded = EncodedBytes::ascii(&left_bytes);
let pos = regex.search_with_encoding(
left_encoded,
0,
left_bytes.len(),
onig::SearchOptions::SEARCH_OPTION_NONE,
Some(&mut region),
);
let pos = regex_search(&regex, left_encoded, left_bytes.len(), &mut region);
if pos.is_some() {
if regex.captures_len() > 0 {
@@ -515,13 +516,7 @@ fn evaluate_match_expression(left_bytes: Vec<u8>, right_bytes: Vec<u8>) -> ExprR
// Try to find the actual capture bytes for ASCII locale
let mut region = onig::Region::new();
let left_encoded = onig::EncodedBytes::ascii(&left_bytes);
let pos = regex.search_with_encoding(
left_encoded,
0,
left_bytes.len(),
onig::SearchOptions::SEARCH_OPTION_NONE,
Some(&mut region),
);
let pos = regex_search(&regex, left_encoded, left_bytes.len(), &mut region);
if pos.is_some() {
if let Some((start, end)) = region.pos(1) {
+9
View File
@@ -488,6 +488,15 @@ fn test_regex_newline() {
.stdout_only("18\n");
}
#[test]
fn test_regex_catastrophic_backtracking() {
let input = "a".repeat(30) + "c";
new_ucmd!()
.args(&[input.as_str(), ":", "\\(a\\+a\\+\\)\\+b"])
.fails_with_code(1)
.stdout_only("\n");
}
#[test]
fn test_substr() {
new_ucmd!()