use super::engine::requires_resharp;
struct Case {
pattern: &'static str,
expected: bool,
}
fn run_case(case: &Case) {
let actual = requires_resharp(case.pattern);
assert_eq!(
actual, case.expected,
"requires_resharp({:?}) = {} but expected {}",
case.pattern, actual, case.expected
);
}
#[test]
fn set_algebra_amp_triggers() {
run_case(&Case { pattern: "foo&bar", expected: true });
}
#[test]
fn set_algebra_complement_triggers() {
run_case(&Case { pattern: "~(foo)", expected: true });
}
#[test]
fn escaped_amp_does_not_trigger() {
run_case(&Case { pattern: r"foo\&bar", expected: false });
}
#[test]
fn in_class_amp_does_not_trigger() {
run_case(&Case { pattern: "[&a-z]+", expected: false });
}
#[test]
fn lookahead_triggers() {
run_case(&Case { pattern: "foo(?=bar)", expected: true });
}
#[test]
fn negative_lookahead_triggers() {
run_case(&Case { pattern: "foo(?!bar)", expected: true });
}
#[test]
fn lookbehind_triggers() {
run_case(&Case { pattern: "(?<=foo)bar", expected: true });
}
#[test]
fn negative_lookbehind_triggers() {
run_case(&Case { pattern: "(?<!foo)bar", expected: true });
}
#[test]
fn non_capturing_group_does_not_trigger() {
run_case(&Case { pattern: "(?:foo)bar", expected: false });
}
#[test]
fn named_capture_angle_does_not_trigger() {
run_case(&Case { pattern: "(?<name>foo)bar", expected: false });
}
#[test]
fn named_capture_p_does_not_trigger() {
run_case(&Case { pattern: "(?P<name>foo)bar", expected: false });
}
#[test]
fn inline_flags_do_not_trigger() {
run_case(&Case { pattern: "(?i)foo", expected: false });
}
#[test]
fn escaped_lookahead_does_not_trigger() {
run_case(&Case { pattern: r"\(?=foo\)", expected: false });
}
#[test]
fn in_class_lookalike_does_not_trigger() {
run_case(&Case { pattern: "[(?=]", expected: false });
}
#[test]
fn prose_em_dash_pattern_triggers() {
run_case(&Case { pattern: "(?<=[a-z]) -- (?=[a-z])", expected: true });
}
#[test]
fn plain_literal_does_not_trigger() {
run_case(&Case { pattern: "AKIA1234567890ABCDEF", expected: false });
}
#[test]
fn plain_regex_no_lookaround_does_not_trigger() {
run_case(&Case { pattern: r"hvb\.[\w-]{138,300}", expected: false });
}
#[test]
fn bare_underscore_wildcard_triggers() {
run_case(&Case { pattern: "pre_post", expected: true });
}
#[test]
fn escaped_underscore_does_not_trigger() {
run_case(&Case { pattern: r"pre\_post", expected: false });
}
#[test]
fn in_class_underscore_does_not_trigger() {
run_case(&Case { pattern: "[A-Z_]+", expected: false });
}
use super::engine::lookaround_in_complement;
fn assert_rejected(pattern: &str, expect_substr: &str) {
let actual = lookaround_in_complement(pattern);
match actual {
Some(msg) => assert!(
msg.contains(expect_substr),
"lookaround_in_complement({:?}) returned message that did not contain {:?}: {}",
pattern, expect_substr, msg
),
None => panic!(
"lookaround_in_complement({:?}) returned None; expected Some(_) containing {:?}",
pattern, expect_substr
),
}
}
fn assert_accepted(pattern: &str) {
let actual = lookaround_in_complement(pattern);
assert!(
actual.is_none(),
"lookaround_in_complement({:?}) = {:?}; expected None",
pattern, actual
);
}
#[test]
fn complement_with_word_boundary_rejected() {
assert_rejected(r"em&~(.*\bword\b.*)", r"\b");
}
#[test]
fn complement_with_not_word_boundary_rejected() {
assert_rejected(r"em&~(.*\B.*)", r"\B");
}
#[test]
fn complement_with_caret_rejected() {
assert_rejected(r"em&~(^foo$)", "^");
}
#[test]
fn complement_with_dollar_rejected() {
assert_rejected(r"em&~(foo$)", "$");
}
#[test]
fn complement_with_explicit_lookahead_rejected() {
assert_rejected(r"em&~((?=foo).*)", "lookahead");
}
#[test]
fn complement_with_explicit_neg_lookahead_rejected() {
assert_rejected(r"em&~((?!foo).*)", "lookahead");
}
#[test]
fn complement_with_explicit_lookbehind_rejected() {
assert_rejected(r"em&~((?<=foo).*)", "lookbehind");
}
#[test]
fn complement_with_explicit_neg_lookbehind_rejected() {
assert_rejected(r"em&~((?<!foo).*)", "lookbehind");
}
#[test]
fn second_of_two_complements_rejected() {
assert_rejected(r"em&~(.*foo.*)&~(.*\bword\b.*)", r"\b");
}
#[test]
fn nested_group_inside_complement_with_boundary_rejected() {
assert_rejected(r"em&~((?:foo|\bword\b).*)", r"\b");
}
#[test]
fn boundary_outside_any_complement_accepted() {
assert_accepted(r"\bem\b&_*&~(.*foo.*)");
}
#[test]
fn text_anchors_inside_complement_accepted() {
assert_accepted(r"em&~(\Afoo\z)");
}
#[test]
fn caret_in_class_inside_complement_accepted() {
assert_accepted(r"em&~([^abc].*)");
}
#[test]
fn dollar_in_class_inside_complement_accepted() {
assert_accepted(r"em&~([$].*)");
}
#[test]
fn escaped_backslash_b_inside_complement_accepted() {
assert_accepted(r"em&~(\\b.*)");
}
#[test]
fn plain_set_algebra_without_triggers_accepted() {
assert_accepted(r"BUILD_[0-9]{6}&~(BUILD_000000)");
}
#[test]
fn rule_without_complement_accepted_even_with_lookaround() {
assert_accepted(r"(?<=[a-z]) -- (?=[a-z])");
}
#[test]
fn plain_literal_accepted() {
assert_accepted("AKIA1234567890ABCDEF");
}
use super::engine::CompiledRegex;
use regex::bytes::Regex as PlainRegex;
#[test]
fn is_match_returns_result_ok_for_match_plain() {
let re = PlainRegex::new("foo").expect("compile plain regex");
let cr = CompiledRegex::Plain(re);
match cr.is_match(b"hello foo world") {
Ok(true) => {}
Ok(false) => panic!("expected match on plain branch"),
Err(()) => panic!("expected Ok, got Err on plain branch"),
}
}
#[test]
fn is_match_returns_result_ok_for_no_match_plain() {
let re = PlainRegex::new("foo").expect("compile plain regex");
let cr = CompiledRegex::Plain(re);
match cr.is_match(b"hello world") {
Ok(false) => {}
Ok(true) => panic!("expected no match on plain branch"),
Err(()) => panic!("expected Ok, got Err on plain branch"),
}
}
#[test]
fn is_match_returns_result_ok_for_match_resharp() {
let re = resharp::Regex::new("foo&_*").expect("compile resharp regex");
let cr = CompiledRegex::Resharp(re);
match cr.is_match(b"hello foo world") {
Ok(true) => {}
Ok(false) => panic!("expected match on resharp branch"),
Err(()) => panic!("expected Ok, got Err on resharp branch"),
}
}
use super::engine::{
intersection_with_lookbehind,
intersection_with_word_end_alternation,
};
#[test]
fn intersection_with_lookbehind_fires_on_minimal_shape() {
let cases = [
"(?:(?=a)&(?<=_))",
"(?:(?=a)&(?<!b))",
"(?:(?<=a)&(?=b))",
"(?:(?=(?=(?:(?:(?:EBEE)))))&(?<=(?:(?=(?=(?=_))))))",
"(?:(?=a)&b)",
"(?:(?=a)&(?=b))",
"(?:foo&(?!bar))",
];
for src in cases {
assert!(
intersection_with_lookbehind(src).is_some(),
"expected intersection_with_lookbehind to fire on {:?}",
src
);
}
}
#[test]
fn intersection_with_lookbehind_skips_safe_shapes() {
let cases = [
"(?<=a)foo",
"(?=a)bar",
"(?:foo&bar)",
"[a&b]",
"foo\\&bar",
"(?<name>a)",
];
for src in cases {
assert!(
intersection_with_lookbehind(src).is_none(),
"expected intersection_with_lookbehind to PASS on {:?}; got {:?}",
src,
intersection_with_lookbehind(src)
);
}
}
#[test]
fn intersection_with_word_end_alternation_fires_on_minimal_shape() {
let cases = [
"(?:\\w|$)(?:(?![1g]\\_X)& a)",
"(?:\\w|$)& a",
"(?u:(?:\\w|$)(?:(?![1g]\\_X)& a))",
"(?u:(?u:(?:\\w|$|(?=~(\\_))))(?:(?![1gtu-w]\\_X# lH :)& N))",
];
for src in cases {
assert!(
intersection_with_word_end_alternation(src).is_some(),
"expected intersection_with_word_end_alternation to fire on {:?}",
src
);
}
}
#[test]
fn intersection_with_word_end_alternation_skips_safe_shapes() {
let cases = [
"(?:\\w|$)foo",
"($&a)",
"(?:\\w)&a",
"[$]&\\w",
"[\\w]&$",
"\\\\w&\\$&foo",
];
for src in cases {
assert!(
intersection_with_word_end_alternation(src).is_none(),
"expected intersection_with_word_end_alternation to PASS on {:?}; got {:?}",
src,
intersection_with_word_end_alternation(src)
);
}
}
#[test]
fn compile_rule_src_does_not_panic_on_known_bad_shapes() {
use crate::rules::compile_rule_src;
let cases = [
"(?:(?=a)&(?<=_))",
"(?:(?=(?=(?:(?:(?:EBEE)))))&(?<=(?:(?=(?=(?=_))))))",
"(?u:(?u:(?:\\w|$|(?=~(\\_))))(?:(?![1gtu-w]\\_X# lH :)& N))",
"(?:\\w|$)(?:(?![1g]\\_X)& a)",
];
for src in cases {
let result = compile_rule_src(src);
assert!(
result.is_err(),
"expected Err from compile_rule_src on known-bad shape {:?}, got {:?}",
src,
result.as_ref().map(|_| "Ok(CompiledRegex)")
);
}
}
#[test]
fn find_all_catches_runtime_panic_via_catch_unwind() {
let re = resharp::Regex::new("(?:(?=a)&(?<=_))").expect("compile resharp regex");
let cr = CompiledRegex::Resharp(re);
let content = vec![b'a'; 128];
let result = cr.find_all(&content);
let _ = result;
}
use super::engine::stacked_quantifier;
#[test]
fn stacked_quantifier_fires_on_minimal_shapes() {
let cases = [
"a**",
"\\D*****aa",
"a*{5,11}",
"a{5,11}*",
"a{5,11}{5,11}",
"\\D{5,11}{5,11}{5,11}{5,11}{5,11}",
"\\D{5,11}{5,11}{5,11}{5,11}{5,11}\\D*****aa",
"a+{5,11}",
"a{5,11}+",
"a*+",
"a++",
"a**",
"a*??",
"(?:a){2}{3}",
"(?:a*?){2}{3}",
];
for case in cases {
assert!(
stacked_quantifier(case).is_some(),
"expected stacked_quantifier to fire on {case:?}",
);
}
}
#[test]
fn stacked_quantifier_skips_safe_shapes() {
let cases = [
"a*?",
"a+?",
"a??",
"(a*)*",
"(?:a*)*",
"(?:a)*",
"(?i)a*",
"(?<=a)b*",
"(?P<name>a)*",
"(?#comment)a*",
"\\D{5,11}",
"a{50}",
"a{1,2}",
"[{}]*",
"\\{*",
"",
"abc",
"a*|b*",
"a*b*c*",
"^a*$",
"a*\\bb*",
];
for case in cases {
assert!(
stacked_quantifier(case).is_none(),
"stacked_quantifier should NOT fire on {case:?}, got {:?}",
stacked_quantifier(case),
);
}
}
#[test]
fn compile_rule_src_rejects_fuzz_slow_unit_fast() {
use std::time::Instant;
let src = "(?iu)\\D{5,11}{5,11}{5,11}{5,11}{5,11}\\D*****aa";
let started = Instant::now();
let result = crate::rules::compile_rule_src(src);
let elapsed = started.elapsed();
let err = match result {
Ok(_) => panic!("expected stacked-quantifier rejection, got Ok"),
Err(e) => e,
};
assert!(
err.contains("stacked quantifier"),
"expected `stacked quantifier` in error, got {err:?}",
);
assert!(
elapsed.as_millis() < 100,
"compile_rule_src on slow-unit took {elapsed:?}; expected <100ms",
);
}
use super::engine::nested_grouped_quantifier;
#[test]
fn nested_grouped_quantifier_fires_on_minimal_shapes() {
let cases: &[&str] = &[
"(?:(?:(?:(?:a)*)*)*)*",
"(?:(?:(?:(?:(?:a)*)*)*)*)*",
"(?:(?:(?:(?:(?:a){5,11}){5,11}){5,11}){5,11}){5,11}",
"(?iu)(?:(?:(?:(?:(?:\\d){5,11}){5,11}){5,11}){5,11}){5,11}(?:(?:(?:(?:(?:\\d)*)*)*)*)*aa",
"(?:(?:(?:(?:a)*){2,3})+)?*",
"(((((a)*)*)*)*)*",
"(?:(?:(?:(?:a)*?)*?)*?)*?",
"(?:(?:(?:(?:(?:a){5,}){5,}){5,}){5,}){5,}",
"(?:(?:(?:(?:(?:a){3}){3}){3}){3}){3}",
];
for case in cases {
assert!(
nested_grouped_quantifier(case).is_some(),
"expected nested_grouped_quantifier to fire on {case:?}",
);
}
}
#[test]
fn nested_grouped_quantifier_skips_safe_shapes() {
let cases: &[&str] = &[
"",
"abc",
"a*",
"(?:a)*",
"(a)*",
"(?:(?:a)*)*",
"((a)*)*",
"(?:(?:(?:a)*)*)*",
"(((a)*)*)*",
"(?:a)*(?:b)*(?:c)*(?:d)*(?:e)*",
"(?:a)*b(?:c)*d(?:e)*f(?:g)*h(?:i)*",
"(?:a)*|(?:b)*|(?:c)*|(?:d)*",
"(?:a*b*c*d*)",
"(?P<a>x)(?P<b>y)(?P<c>z)(?P<d>w)",
"(?i)abc",
"(?=a)(?=b)(?=c)(?=d)",
"(?<=a)(?<=b)(?<=c)(?<=d)",
"(?:(?:(?:foo)*)*)*bar",
"(?:a)b(?:c)d(?:e)f(?:g)h(?:i)",
"(?:a)*\\b(?:b)*\\B(?:c)*^(?:d)*$",
"[)]*[)]*[)]*[)]*[)]*",
"\\)*\\)*\\)*\\)*\\)*",
];
for case in cases {
assert!(
nested_grouped_quantifier(case).is_none(),
"nested_grouped_quantifier should NOT fire on {case:?}, got {:?}",
nested_grouped_quantifier(case),
);
}
}
use super::engine::complement_intersection_quantified_group;
#[test]
fn complement_intersection_quantified_group_fires() {
let cases = [
"abc~(\\w)&(?:aaa)*",
"xyz~(\\w)&(?:aaaaaaaaaaaaa)*",
"[_]\u{00f1}e-XM1[^42v]~(\\w)&(?:aaaaaaaaaaaaa)*",
"(?:[^a]~(\\w)&(?:aaaaaaaaaaaaa)*)",
"(?:[_]\u{00f1}e-XM1[^42v]~(\\w)&(?:aaaaaaaaaaaaa)*)",
"x~(\\w)&(?:a)+",
"x~(\\w)&(?:a){5,11}",
"~(\\w)&(?:a)*",
"(?i) ###(?:\\s&\u{00fc}\u{00fc})(?:####)+#@(?u:0\u{00e7}308-11aaaaaaaa)aa",
"abc&(?:aaa)*",
"(?:\\s&a)(?:b)+",
];
for src in cases {
assert!(
complement_intersection_quantified_group(src).is_some(),
"expected complement_intersection_quantified_group to fire on {:?}",
src
);
}
}
#[test]
fn complement_intersection_quantified_group_skips_safe_shapes() {
let cases = [
"abc~(\\w)(?:aaa)*",
"abc~(\\w)&def",
"abc&def",
"abc[a&b](?:a)*",
"abc\\&(?:aaa)*",
"abc&def(?:aaa)",
"",
"abc",
"(?:a)*",
"RELEASE_TAG_[a-f0-9]{32}&~(RELEASE_TAG_0{32})&~(RELEASE_TAG_(de|ad|be|ef)(de|ad|be|ef)(de|ad|be|ef)(de|ad|be|ef)(de|ad|be|ef)(de|ad|be|ef)(de|ad|be|ef)(de|ad|be|ef)(de|ad|be|ef)(de|ad|be|ef)(de|ad|be|ef)(de|ad|be|ef)(de|ad|be|ef)(de|ad|be|ef)(de|ad|be|ef)(de|ad|be|ef))",
];
for src in cases {
assert!(
complement_intersection_quantified_group(src).is_none(),
"expected complement_intersection_quantified_group to PASS on {:?}; got {:?}",
src,
complement_intersection_quantified_group(src)
);
}
}
use super::engine::lookaround_in_alternation_with_sibling;
#[test]
fn lookaround_in_alternation_with_sibling_fires() {
let cases = [
"(a|(?![_]))(?!a)",
"(?:(?:(?:-\u{00f6}\u{00e9}x|-\u{00f6}pV|(?![_]))(?![a-e-u-vaaa])|a)|a|a)",
"(a|(?![_]))(?![a-e-u-vaaa])",
"(?:a|(?![_]))(?!a)",
"((?![_])|a)(?!a)",
"(a|(?![0]))(?!a)",
"(a|(?![.]))(?!a)",
"(a|(?<!_))(?<!a)",
"(a|(?<!_))(?!a)",
"(a|(?!_))(?<!a)",
"((?:a|(?!_))(?!a))",
"(?: 4qüVk|o\\w|\\s(?![_]))23o:aaaaaaaaaaaaaaa",
"(?:a|(?!b))c",
"(a|(?!b))",
"(a|(?<!b))",
];
for src in cases {
assert!(
lookaround_in_alternation_with_sibling(src).is_some(),
"expected lookaround_in_alternation_with_sibling to fire on {:?}",
src
);
}
}
#[test]
fn lookaround_in_alternation_with_sibling_skips_safe_shapes() {
let cases = [
"",
"abc",
"a|b",
"(?=a)",
"(?!a)",
"(?<=a)",
"(?<!a)",
"(?=a)(?=b)",
"(?<!a)(?<!b)",
"(a|b)(?!c)",
"(?!a)b(?!c)",
"(a)(b)(c)(d)",
"\\(a\\|\\)",
"[a|b](?!c)",
];
for src in cases {
assert!(
lookaround_in_alternation_with_sibling(src).is_none(),
"expected lookaround_in_alternation_with_sibling to PASS on {:?}; got {:?}",
src,
lookaround_in_alternation_with_sibling(src)
);
}
}
#[test]
fn compile_rule_src_rejects_alt_lookaround_sibling_shape() {
use std::time::Instant;
let src = "(a|(?![_]))(?!a)";
let started = Instant::now();
let result = crate::rules::compile_rule_src(src);
let elapsed = started.elapsed();
let err = match result {
Ok(_) => panic!("expected rejection, got Ok"),
Err(e) => e,
};
assert!(
err.contains("alternation") && err.contains("lookaround"),
"expected error mentioning `alternation` and `lookaround`, got {err:?}",
);
assert!(
elapsed.as_millis() < 100,
"compile_rule_src on alt+la+la took {elapsed:?}; expected <100ms",
);
}
#[test]
fn compile_rule_src_rejects_grouped_fuzz_slow_unit_fast() {
use std::time::Instant;
let src = "(?iu)(?:(?:(?:(?:(?:\\d){5,11}){5,11}){5,11}){5,11}){5,11}(?:(?:(?:(?:(?:\\d)*)*)*)*)*aa";
let started = Instant::now();
let result = crate::rules::compile_rule_src(src);
let elapsed = started.elapsed();
let err = match result {
Ok(_) => panic!("expected nested-grouped-quantifier rejection, got Ok"),
Err(e) => e,
};
assert!(
err.contains("nested grouped quantifier"),
"expected `nested grouped quantifier` in error, got {err:?}",
);
assert!(
elapsed.as_millis() < 100,
"compile_rule_src on grouped slow-unit took {elapsed:?}; expected <100ms",
);
}
use super::engine::nested_lookahead_in_quantified_group;
#[test]
fn nested_lookahead_in_quantified_group_fires() {
let cases = [
"(?:(?:(?!\\?)){1,5}){2,4}",
"(?:(?:(?!\\?)){1,5}){2,2}",
"(?:(?!\\?){1,2}){3}",
"(?:(?!\\?){1,2}){3,5}",
"(?:(?:(?:(?!\\?)){1,5}){1,3}){2,4}",
"(?:(?:(?:(?!\\?)){1,5}){2,3}){1,4}",
"(?:(?=a)(?:(?!\\?)){1,5}){2,4}",
"(?:(?:(?=\\?)){1,5}){2,4}",
"(?:(?:(?![ab])){1,5}){2,4}",
"(?-i)(?i:(?x:\\_))(?u:(?:(?:(?!(?:\\?){1,5})){1,5}){2,4})(?i:(?i:(?i:(?i:(?:a)*))))a",
"(?:(?u:(?:(?u:(?:\\?)+)|(?:(?:\\?){3,7}){1,5}|(?:\\?\\?\\?){1,5}))|(?:(?:(?:(?!\\?)){1,7})+){3,5}|\\_)\\_\\__",
];
for src in cases {
assert!(
nested_lookahead_in_quantified_group(src).is_some(),
"expected nested_lookahead_in_quantified_group to fire on {:?}; got None",
src
);
}
}
#[test]
fn nested_lookahead_in_quantified_group_skips_safe_shapes() {
let cases = [
"(?:(?:(?!\\?)){1,5}){1,5}",
"(?:(?:(?!\\?)){1,5}){1,4}",
"(?:(?!\\?)){2,4}",
"(?:a(?:(?!\\?)){1,5}){2,4}",
"(?:(?:(?!\\?)){1,5}a){2,4}",
"(?:a(?:(?!\\?)){1,5}a){2,4}",
"(?:(?:(?!\\?)){1,5}|a){2,4}",
"(?:(?!a*)){2,4}",
"(?:(?:a){1,5}){2,4}",
"",
"abc",
"(?:a)*",
"(?!\\?)",
"(?!\\?){2,3}",
];
for src in cases {
assert!(
nested_lookahead_in_quantified_group(src).is_none(),
"expected nested_lookahead_in_quantified_group to PASS on {:?}; got {:?}",
src,
nested_lookahead_in_quantified_group(src)
);
}
}
#[test]
fn compile_rule_src_rejects_bug_f_shape_fast() {
use std::time::Instant;
let src = "(?:(?:(?!\\?)){1,5}){2,4}";
let started = Instant::now();
let result = crate::rules::compile_rule_src(src);
let elapsed = started.elapsed();
let err = match result {
Ok(_) => panic!("expected nested-lookahead rejection, got Ok"),
Err(e) => e,
};
assert!(
err.contains("lookahead") && err.contains("overflow"),
"expected error mentioning `lookahead` and `overflow`, got {err:?}",
);
assert!(
elapsed.as_millis() < 100,
"compile_rule_src on bug-f shape took {elapsed:?}; expected <100ms",
);
}
use super::engine::quantified_lookahead_with_sibling_content;
#[test]
fn quantified_lookahead_with_sibling_content_fires() {
let cases = [
"(?:(?!abc)){4,12}a",
"(?:(?!abc)){4,12}aa",
"(?:(?!abc)){4,12}x",
"(?:(?!abc)){4,12}bc",
"(?:(?!abc)){4,12}\\?",
"(?:(?!abc)){4,12}(?:d)",
"(?:(?!abc)){4,12}[d]",
"a(?:(?!abc)){4,12}a",
"(?:(?!abc)){2,3}a",
"(?:(?!abc)){1,4}a",
"(?:(?!abc)){2,4}a",
"(?!abc){4,12}a",
"(?!abc){2,4}a",
"(?:(?=abc)){4,12}a",
"(?:(?!abc)){4,12}~(d)",
];
for src in cases {
assert!(
quantified_lookahead_with_sibling_content(src).is_some(),
"expected quantified_lookahead_with_sibling_content to fire on {:?}; got None",
src
);
}
}
#[test]
fn quantified_lookahead_with_sibling_content_skips_safe_shapes() {
let cases = [
"(?:(?!abc)){4,12}",
"(?!abc){4,12}",
"(?:(?!abc))",
"a(?:(?!abc)){4,12}",
"abc",
"(?:abc){4,12}a",
"",
"(?!abc)|(?:abc){4,12}a",
"(?!a*)abc",
"(?:abc){4,12}xyz",
"(?:(?!abc)){4}a",
"(?:(?!abc)){3}aa",
"(?:(?!abc)){4,12}aaa",
"(?:(?!abc)){4,12}abcd",
"(?:(?!abc)){2}xyz",
];
for src in cases {
assert!(
quantified_lookahead_with_sibling_content(src).is_none(),
"expected quantified_lookahead_with_sibling_content to PASS on {:?}; got {:?}",
src,
quantified_lookahead_with_sibling_content(src)
);
}
}
#[test]
fn compile_rule_src_rejects_bug_f_trailing_shape_fast() {
use std::time::Instant;
let src = "(?:(?!abc)){4,12}a";
let started = Instant::now();
let result = crate::rules::compile_rule_src(src);
let elapsed = started.elapsed();
let err = match result {
Ok(_) => panic!("expected quantified-lookahead-with-trailing rejection, got Ok"),
Err(e) => e,
};
assert!(
err.contains("lookahead") && err.contains("overflow"),
"expected error mentioning `lookahead` and `overflow`, got {err:?}",
);
assert!(
elapsed.as_millis() < 100,
"compile_rule_src on bug-f trailing shape took {elapsed:?}; expected <100ms",
);
}
use super::engine::nested_quantifier_after_wildcard;
#[test]
fn nested_quantifier_after_wildcard_fires() {
let cases: &[&str] = &[
"_){5,6}){5,12})+",
"(?:(?:(?:(?:_){5,6}){5,12})+",
"(?:(?:(?:(?:_){3,4}){5,12})+",
"_)*)*)*",
"_)?)?)?",
"_){5,6})+)*",
"_)*)*)*)*",
"_)*?)*?)*?",
"(?:(?:a|(?:(?:(?:(?:_){5,6}){5,12})+|(?:\\s|(?:(?:_){5,6})+)|(?:(?:(?:_){5,6}){5,6}){5,6}))){5,6}",
"(?:(?:a|(?:(?:(?:(?:_){3,4}){5,12})+|(?:\\s|_)|(?:(?:(?:_){5,6}){5,6}){5,6}))){5,6}",
];
for case in cases {
assert!(
nested_quantifier_after_wildcard(case).is_some(),
"expected nested_quantifier_after_wildcard to fire on {case:?}",
);
}
}
#[test]
fn nested_quantifier_after_wildcard_skips_safe_shapes() {
let cases: &[&str] = &[
"",
"_",
"_*",
"(?:_)*",
"(?:_){5,12}",
"_)*)*",
"(?:(?:_){5,6}){5,12}",
"[_]){5,6}){5,12})+",
"[_]){5,6}){5,12})*",
"\\_){5,6}){5,12})+",
"a){5,6}){5,12})+",
"(?:(?:(?:(?:a)*)*)*)*",
"_a){5,6}){5,12})+",
"_(){5,6}){5,12})+",
"abc",
"(?:a){5,12}",
"[abc]",
];
for case in cases {
assert!(
nested_quantifier_after_wildcard(case).is_none(),
"expected nested_quantifier_after_wildcard to PASS on {case:?}; got {:?}",
nested_quantifier_after_wildcard(case),
);
}
}
#[test]
fn compile_rule_src_rejects_wildcard_chain_slow_shape_fast() {
use std::time::Instant;
let src = "(?:(?:a|(?:(?:(?:(?:_){5,6}){5,12})+|(?:\\s|(?:(?:_){5,6})+)|(?:(?:(?:_){5,6}){5,6}){5,6}))){5,6}";
let started = Instant::now();
let result = crate::rules::compile_rule_src(src);
let elapsed = started.elapsed();
let err = match result {
Ok(_) => panic!("expected wildcard-chain rejection, got Ok"),
Err(e) => e,
};
assert!(
err.contains("wildcard") && err.contains("_"),
"expected error mentioning `wildcard` and `_`, got {err:?}",
);
assert!(
elapsed.as_millis() < 100,
"compile_rule_src on wildcard-chain shape took {elapsed:?}; expected <100ms",
);
}
use super::engine::nested_chain_in_lookaround_body;
#[test]
fn nested_chain_in_lookaround_body_fires() {
let cases: &[&str] = &[
"(?!(?:(?:(?:a){2}){2}){2})",
"(?=(?:(?:(?:a){2}){2}){2})",
"(?<!(?:(?:(?:a){2}){2}){2})",
"(?<=(?:(?:(?:a){2}){2}){2})",
"(?!(?!aaa)(?:(?:(?:a){5,14}){5,14}){4,12})",
"(?!(?:(?:(?:a)*)*)*)",
"(?!(?:(?:(?:a)?)?)?)",
"(?!(?:(?:(?:(?:a){2}){2}){2}){2})",
];
for case in cases {
assert!(
nested_chain_in_lookaround_body(case).is_some(),
"expected nested_chain_in_lookaround_body to fire on {case:?}",
);
}
}
#[test]
fn nested_chain_in_lookaround_body_skips_safe_shapes() {
let cases: &[&str] = &[
"",
"abc",
"(?:a)*",
"(?:(?:(?:a)*)*)*",
"(?:(?:(?:(?:a)*)*)*)*",
"(?!(?:(?:a)*)*)",
"(?=(?:(?:a)*)*)",
"(?!(?:abc){5,12})",
"(?!abc)",
"(?=xyz)",
"(?!a)(?=b)(?<!c)(?<=d)",
"(?!(?=a))",
"(?!abc)(?:(?:(?:a)*)*)*",
"(?:(?:(?:a)*)*)*(?!xyz)",
"(?:(?!\\?)){2,4}",
];
for case in cases {
assert!(
nested_chain_in_lookaround_body(case).is_none(),
"expected nested_chain_in_lookaround_body to PASS on {case:?}; got {:?}",
nested_chain_in_lookaround_body(case),
);
}
}
#[test]
fn compile_rule_src_rejects_lookaround_chain_slow_shape_fast() {
use std::time::Instant;
let src = "(?!(?:(?:(?:a){5,14}){5,14}){4,12})";
let started = Instant::now();
let result = crate::rules::compile_rule_src(src);
let elapsed = started.elapsed();
let err = match result {
Ok(_) => panic!("expected lookaround-chain rejection, got Ok"),
Err(e) => e,
};
assert!(
err.contains("lookaround"),
"expected error mentioning `lookaround`, got {err:?}",
);
assert!(
elapsed.as_millis() < 100,
"compile_rule_src on lookaround-chain shape took {elapsed:?}; expected <100ms",
);
}
use super::engine::nested_complement;
#[test]
fn nested_complement_fires() {
let cases: &[&str] = &[
"~(~(abc))",
"~((?:~(abc)))",
"~((?:(?i:~(abc))))",
"(?-i)(?:~(~((?:(?:\\s){5,14}){3,10}))){3,10}",
"(?-i)(?:~((?:~((?:(?:\\s){5,14}){3,10})))){3,10}",
"~(~(~(abc)))",
];
for case in cases {
assert!(
nested_complement(case).is_some(),
"expected nested_complement to fire on {case:?}",
);
}
}
#[test]
fn nested_complement_skips_safe_shapes() {
let cases: &[&str] = &[
"",
"abc",
"(?:a)*",
"~(abc)",
"~((?:(?:\\s){5,14}){3,10})",
"~(abc)&~(def)",
"(?:~(abc)|~(def))",
"RELEASE_TAG_[a-f0-9]{32}&~(RELEASE_TAG_(00){16})&~(RELEASE_TAG_(de|ad|be|ef){8})",
"[~()abc]",
"\\~(abc)~(def)",
"~(abc)x~(def)",
];
for case in cases {
assert!(
nested_complement(case).is_none(),
"expected nested_complement to PASS on {case:?}; got {:?}",
nested_complement(case),
);
}
}
#[test]
fn compile_rule_src_rejects_nested_complement_timeout_shape_fast() {
use std::time::Instant;
let src = "(?-i)(?:~(~((?:(?:\\s){5,14}){3,10}))){3,10}";
let started = Instant::now();
let result = crate::rules::compile_rule_src(src);
let elapsed = started.elapsed();
let err = match result {
Ok(_) => panic!("expected nested-complement rejection, got Ok"),
Err(e) => e,
};
assert!(
err.contains("complement"),
"expected error mentioning `complement`, got {err:?}",
);
assert!(
elapsed.as_millis() < 100,
"compile_rule_src on nested-complement shape took {elapsed:?}; expected <100ms",
);
}