use ferroni::oniguruma::*;
use ferroni::prelude::{Regex, RegexError, SearchOptions};
use ferroni::regcomp::onig_new;
use ferroni::regexec::{
onig_get_subexp_call_limit_in_search, onig_get_subexp_call_max_nest_level, onig_search,
onig_set_subexp_call_limit_in_search, onig_set_subexp_call_max_nest_level,
};
use ferroni::regsyntax::OnigSyntaxOniguruma;
fn search(pattern: &str, subject: &str) -> (i32, Option<(i32, i32)>) {
let reg = onig_new(
pattern.as_bytes(),
ONIG_OPTION_NONE,
&ferroni::encodings::utf8::ONIG_ENCODING_UTF8,
&OnigSyntaxOniguruma,
)
.unwrap();
let text = subject.as_bytes();
let (r, region) = onig_search(
®,
text,
text.len(),
0,
text.len(),
Some(OnigRegion::new()),
ONIG_OPTION_NONE,
);
let bounds = (r >= 0).then(|| {
let region = region.unwrap();
(region.beg[0], region.end[0])
});
(r, bounds)
}
fn nest_level_limits_recursion_depth() {
assert_eq!(onig_get_subexp_call_max_nest_level(), 20);
let a20 = "a".repeat(20);
let a21 = "a".repeat(21);
assert_eq!(search(r"\A(?<a>a\g<a>?)\z", &a20), (0, Some((0, 20))));
assert_eq!(search(r"\A(?<a>a\g<a>?)\z", &a21).0, ONIG_MISMATCH);
assert_eq!(search(r"\A(?<a>a\g<a>|a)\z", &a21).0, ONIG_MISMATCH);
assert_eq!(
search(r"(?<a>a\g<a>?)", &"a".repeat(22)),
(0, Some((0, 20)))
);
assert_eq!(
search(r"\A(?<a>a\g<b>?)(?<b>b\g<a>?)?\z", &"ab".repeat(10)),
(0, Some((0, 20)))
);
assert_eq!(
search(r"\A(?<a>a\g<b>?)(?<b>b\g<a>?)?\z", &"ab".repeat(20)).0,
ONIG_MISMATCH
);
let ab21 = format!("{}{}", "a".repeat(21), "b".repeat(21));
assert_eq!(search(r"a\g<0>?b", &ab21), (1, Some((1, 41))));
let re = Regex::new(r"\A(?<a>a\g<a>?)\z").unwrap();
assert_eq!(re.is_match_with(&a21, SearchOptions::new()), Ok(false));
onig_set_subexp_call_max_nest_level(5);
assert_eq!(search(r"\A(?<a>a\g<a>?)\z", "aaaaa"), (0, Some((0, 5))));
assert_eq!(search(r"\A(?<a>a\g<a>?)\z", "aaaaaa").0, ONIG_MISMATCH);
onig_set_subexp_call_max_nest_level(0);
assert_eq!(search(r"\A(?<a>a\g<a>?)\z", "a").0, ONIG_MISMATCH);
assert_eq!(search(r"\A(?<a>a)\z", "a"), (0, Some((0, 1))));
onig_set_subexp_call_max_nest_level(-1);
assert_eq!(search(r"\A(?<a>a\g<a>?)\z", &a21), (0, Some((0, 21))));
onig_set_subexp_call_max_nest_level(20);
}
fn limit_in_search_reports_an_error() {
assert_eq!(onig_get_subexp_call_limit_in_search(), 0);
onig_set_subexp_call_limit_in_search(10);
assert_eq!(search(r"\A(?<a>a\g<a>?)\z", "aaaaaaaaa"), (0, Some((0, 9))));
assert_eq!(
search(r"\A(?<a>a\g<a>?)\z", "aaaaaaaaaa").0,
ONIGERR_SUBEXP_CALL_LIMIT_IN_SEARCH_OVER
);
onig_set_subexp_call_limit_in_search(2);
assert_eq!(search(r"(?<a>a|b\g<a>c)", "bac"), (0, Some((0, 3))));
assert_eq!(
search(r"(?<a>a|b\g<a>c)", "bxbac").0,
ONIGERR_SUBEXP_CALL_LIMIT_IN_SEARCH_OVER
);
let re = Regex::new(r"(?<a>a|b\g<a>c)").unwrap();
assert_eq!(
re.is_match_with("bbac", SearchOptions::new()),
Err(RegexError::SubexpCallLimitOver)
);
onig_set_subexp_call_limit_in_search(0);
}
#[test]
fn subexp_call_limits_in_search() {
nest_level_limits_recursion_depth();
limit_in_search_reports_an_error();
}