1use std::collections::HashSet;
9use std::time::Instant;
10
11use super::ast::{self, Flags, Op};
12use super::parse_slots::node_intervals;
13use super::prefix_history::{contains_repeat, optional_states_are_equivalent, suffix_history};
14use super::repeat_alphabet::repeat_alphabet_fills;
15use super::repeat_lookbehind::walk_negative_behind;
16use super::repeat_prefix_state::{
17 PREFIX_WALK_STATE_LIMIT, PREFIX_WALK_TEXT_BUDGET, REPEAT_PREFIX_STATE_LIMIT, RepeatPrefixState,
18 capture_state, consume_atom, consume_piece, positive_assertion, state_text_size,
19};
20use super::timeout::BuilderTimeout;
21
22pub(crate) type RepeatCollector<'a> =
23 &'a mut dyn FnMut(&[Op], Flags, &[RepeatPrefixState]) -> Result<(), BuilderTimeout>;
24
25pub(crate) fn check_deadline(deadline: Option<Instant>) -> Result<(), BuilderTimeout> {
26 if let Some(deadline) = deadline
27 && Instant::now() >= deadline
28 {
29 return Err(BuilderTimeout(
30 "Pattern validation repeat-prefix construction exceeded its deadline".into(),
31 ));
32 }
33 Ok(())
34}
35
36pub(crate) fn unique_states(
37 states: Vec<RepeatPrefixState>,
38) -> Result<Vec<RepeatPrefixState>, BuilderTimeout> {
39 let mut unique: Vec<RepeatPrefixState> = Vec::with_capacity(states.len());
40 let mut seen: HashSet<RepeatPrefixState> = HashSet::with_capacity(states.len());
41 for state in states {
42 if seen.insert(state.clone()) {
43 unique.push(state);
44 }
45 }
46 if unique.len() > PREFIX_WALK_STATE_LIMIT {
47 return Err(BuilderTimeout(
48 "Pattern validation repeat-prefix state budget exceeded".into(),
49 ));
50 }
51 if unique.iter().map(state_text_size).sum::<usize>() > PREFIX_WALK_TEXT_BUDGET {
52 return Err(BuilderTimeout(
53 "Pattern validation repeat-prefix text budget exceeded".into(),
54 ));
55 }
56 Ok(unique)
57}
58
59fn assertion_witnesses(
60 body: &[Op],
61 flags: Flags,
62 deadline: Option<Instant>,
63) -> Result<Vec<RepeatPrefixState>, BuilderTimeout> {
64 let mut unused_prefixes = Vec::new();
65 let mut walker = PrefixWalk {
66 flags,
67 prefixes: &mut unused_prefixes,
68 deadline,
69 collect: false,
70 alphabet: Vec::new(),
71 repeat_collector: None,
72 canonical_optionals: false,
73 require_reachable: false,
74 };
75 let states = walker.walk(body, vec![RepeatPrefixState::default()], false)?;
76 if states.iter().any(|state| {
77 !state.pending.is_empty() || !state.forbidden.is_empty() || !state.excluded.is_empty()
78 }) {
79 return Err(BuilderTimeout(
80 "Pattern validation cannot resolve nested assertion constraints".into(),
81 ));
82 }
83 Ok(states)
84}
85
86pub(crate) struct PrefixWalk<'a> {
88 pub flags: Flags,
89 pub prefixes: &'a mut Vec<String>,
90 pub deadline: Option<Instant>,
91 pub collect: bool,
92 pub alphabet: Vec<String>,
93 pub repeat_collector: Option<RepeatCollector<'a>>,
94 pub canonical_optionals: bool,
95 pub require_reachable: bool,
96}
97
98impl PrefixWalk<'_> {
99 fn check_remaining_repeats(&self, items: &[Op]) -> Result<(), BuilderTimeout> {
100 if self.require_reachable && contains_repeat(items) {
101 return Err(BuilderTimeout(
102 "Pattern validation cannot resolve repeat-site prefixes".into(),
103 ));
104 }
105 Ok(())
106 }
107
108 fn atoms(
109 &self,
110 op: &Op,
111 states: Vec<RepeatPrefixState>,
112 ) -> Result<Vec<RepeatPrefixState>, BuilderTimeout> {
113 let intervals = node_intervals(op, self.flags);
114 let mut result = Vec::with_capacity(states.len());
115 for state in states {
116 if let Some(next_state) = consume_atom(&state, &intervals)? {
117 result.push(next_state);
118 }
119 }
120 Ok(result)
121 }
122
123 fn record_prefixes(
124 &mut self,
125 high: Option<u32>,
126 states: &[RepeatPrefixState],
127 ) -> Result<(), BuilderTimeout> {
128 if self.collect && high.is_none_or(|high| high > 1) {
129 for state in states {
130 self.prefixes
131 .push(format!("{}{}", state.text, state.pending));
132 }
133 if self.prefixes.len() > REPEAT_PREFIX_STATE_LIMIT * 4 {
134 return Err(BuilderTimeout(
135 "Pattern validation repeat-prefix candidate budget exceeded".into(),
136 ));
137 }
138 }
139 Ok(())
140 }
141
142 fn repeat(
143 &mut self,
144 low: u32,
145 high: Option<u32>,
146 body: &[Op],
147 states: Vec<RepeatPrefixState>,
148 history_observable: bool,
149 ) -> Result<Vec<RepeatPrefixState>, BuilderTimeout> {
150 self.record_prefixes(high, &states)?;
151 if high.is_none_or(|high| high > 1)
152 && let Some(collector) = self.repeat_collector.as_mut()
153 {
154 collector(body, self.flags, &states)?;
155 }
156 if low == 0 && high.is_none_or(|high| high > 0) {
157 let expanded = self.walk(body, states.clone(), history_observable)?;
158 if self.canonical_optionals
159 && optional_states_are_equivalent(body, &states, history_observable)
160 {
161 return Ok(states);
162 }
163 let mut all = states.clone();
164 all.extend(expanded.clone());
165 let combined = unique_states(all)?;
166 let remaining = match high {
167 None => u64::MAX,
168 Some(high) => u64::from(high.max(1) - 1),
169 };
170 return self.pending_repeats(body, combined, expanded, remaining, history_observable);
171 }
172 let mut current = states;
173 for _ in 0..low {
174 check_deadline(self.deadline)?;
175 let previous = current.clone();
176 current = self.walk(body, current, history_observable)?;
177 if current == previous || current.is_empty() {
178 break;
179 }
180 }
181 let remaining = match high {
182 None => u64::MAX,
183 Some(high) => u64::from(high.saturating_sub(low)),
184 };
185 let snapshot = current.clone();
186 self.pending_repeats(body, current, snapshot, remaining, history_observable)
187 }
188
189 fn pending_repeats(
190 &mut self,
191 body: &[Op],
192 result: Vec<RepeatPrefixState>,
193 frontier: Vec<RepeatPrefixState>,
194 remaining: u64,
195 history_observable: bool,
196 ) -> Result<Vec<RepeatPrefixState>, BuilderTimeout> {
197 let mut result = result;
198 let mut frontier = frontier;
199 let mut remaining = remaining;
200 while remaining > 0 {
201 let pending: Vec<RepeatPrefixState> = frontier
202 .iter()
203 .filter(|state| !state.pending.is_empty())
204 .cloned()
205 .collect();
206 if pending.is_empty() {
207 break;
208 }
209 check_deadline(self.deadline)?;
210 frontier = self.walk(body, pending.clone(), history_observable)?;
211 let mut all = result.clone();
212 all.extend(frontier.clone());
213 result = unique_states(all)?;
214 if frontier == pending {
215 break;
216 }
217 remaining -= 1;
218 }
219 Ok(result)
220 }
221
222 fn branch(
223 &mut self,
224 alternatives: &[Vec<Op>],
225 states: Vec<RepeatPrefixState>,
226 history_observable: bool,
227 ) -> Result<Vec<RepeatPrefixState>, BuilderTimeout> {
228 let mut result: Vec<RepeatPrefixState> = Vec::new();
229 for alternative in alternatives {
230 let walked = self.walk(alternative, states.clone(), history_observable)?;
231 let mut all = result;
232 all.extend(walked);
233 result = unique_states(all)?;
234 }
235 Ok(result)
236 }
237
238 fn group(
239 &mut self,
240 group: Option<u32>,
241 add: Flags,
242 del: Flags,
243 body: &[Op],
244 states: Vec<RepeatPrefixState>,
245 history_observable: bool,
246 ) -> Result<Vec<RepeatPrefixState>, BuilderTimeout> {
247 let saved_flags = self.flags;
251 self.flags.apply_delta(&add, &del);
252 let mut result: Vec<RepeatPrefixState> = Vec::new();
253 for state in states {
254 let start = state.text.len();
255 let walked = self.walk(body, vec![state], history_observable)?;
256 for next_state in walked {
257 result.push(capture_state(&next_state, group, start));
258 }
259 }
260 self.flags = saved_flags;
261 Ok(result)
262 }
263
264 fn assertion(
265 &mut self,
266 behind: bool,
267 negated: bool,
268 body: &[Op],
269 states: Vec<RepeatPrefixState>,
270 ) -> Result<Vec<RepeatPrefixState>, BuilderTimeout> {
271 let witnesses = assertion_witnesses(body, self.flags, self.deadline)?;
272 if !negated {
273 let mut result = Vec::new();
274 for state in &states {
275 result.extend(positive_assertion(behind, &witnesses, state));
276 }
277 return Ok(result);
278 }
279 if behind {
280 return walk_negative_behind(body, self.flags, &witnesses, &states, &self.alphabet);
281 }
282 let mut result = Vec::new();
283 for state in &states {
284 result.extend(super::repeat_prefix_state::negative_assertion(
285 body, self.flags, &witnesses, state,
286 ));
287 }
288 Ok(result)
289 }
290
291 fn backreference(
292 &self,
293 group: u32,
294 states: Vec<RepeatPrefixState>,
295 ) -> Result<Vec<RepeatPrefixState>, BuilderTimeout> {
296 let mut result = Vec::new();
297 for state in states {
298 let captured = state
299 .captures
300 .iter()
301 .find(|(existing, _)| *existing == group)
302 .map(|(_, value)| value.clone());
303 if let Some(captured) = captured
304 && let Some(next_state) = consume_piece(&state, &captured)?
305 {
306 result.push(next_state);
307 }
308 }
309 Ok(result)
310 }
311
312 fn node(
313 &mut self,
314 op: &Op,
315 states: Vec<RepeatPrefixState>,
316 history_observable: bool,
317 ) -> Result<Vec<RepeatPrefixState>, BuilderTimeout> {
318 match op {
319 Op::Literal(_) | Op::NotLiteral(_) | Op::In(_) | Op::Any | Op::Category(_) => {
320 self.atoms(op, states)
321 }
322 Op::Repeat {
323 kind: _,
324 low,
325 high,
326 body,
327 } => self.repeat(*low, *high, body, states, history_observable),
328 Op::GroupRef(group) => self.backreference(*group, states),
329 Op::Branch(alternatives) => self.branch(alternatives, states, history_observable),
330 Op::SubPattern {
331 group,
332 add,
333 del,
334 body,
335 } => self.group(*group, *add, *del, body, states, history_observable),
336 Op::GroupRefExists { group, yes, no } => {
337 self.conditional(*group, yes, no.as_deref(), states, history_observable)
338 }
339 Op::Assert {
340 behind,
341 negated,
342 body,
343 } => self.assertion(*behind, *negated, body, states),
344 Op::At(_) => Ok(states),
345 Op::Failure => Ok(Vec::new()),
346 }
347 }
348
349 fn conditional(
350 &mut self,
351 group: u32,
352 yes: &[Op],
353 no: Option<&[Op]>,
354 states: Vec<RepeatPrefixState>,
355 history_observable: bool,
356 ) -> Result<Vec<RepeatPrefixState>, BuilderTimeout> {
357 let mut result = Vec::new();
358 for state in states {
359 let taken = if state.captures.iter().any(|(g, _)| *g == group) {
360 yes
361 } else {
362 no.unwrap_or(&[])
363 };
364 let walked = self.walk(taken, vec![state], history_observable)?;
365 result.extend(walked);
366 }
367 Ok(result)
368 }
369
370 pub fn walk(
372 &mut self,
373 items: &[Op],
374 states: Vec<RepeatPrefixState>,
375 history_observable: bool,
376 ) -> Result<Vec<RepeatPrefixState>, BuilderTimeout> {
377 let history = suffix_history(items, history_observable);
378 let mut states = states;
379 for (index, op) in items.iter().enumerate() {
380 check_deadline(self.deadline)?;
381 states = unique_states(self.node(op, states, history[index])?)?;
382 if states.is_empty() {
383 self.check_remaining_repeats(&items[index + 1..])?;
384 break;
385 }
386 }
387 Ok(states)
388 }
389}
390
391pub fn repeat_reaching_prefixes(
393 pattern: &str,
394 flags: Flags,
395 deadline: Option<Instant>,
396 require_reachable: bool,
397) -> Result<Vec<String>, BuilderTimeout> {
398 let Ok((ops, final_flags)) = ast::parse(pattern, flags) else {
399 return Ok(Vec::new());
400 };
401 let mut prefixes: Vec<String> = Vec::new();
402 let alphabet = repeat_alphabet_fills(pattern, flags, deadline, true)?;
403 let mut walker = PrefixWalk {
404 flags: final_flags,
405 prefixes: &mut prefixes,
406 deadline,
407 collect: true,
408 alphabet,
409 repeat_collector: None,
410 canonical_optionals: true,
411 require_reachable,
412 };
413 walker.walk(&ops, vec![RepeatPrefixState::default()], false)?;
414 let mut unique: Vec<String> = Vec::new();
415 for prefix in prefixes {
416 if !prefix.is_empty() && !unique.contains(&prefix) {
417 unique.push(prefix);
418 }
419 }
420 Ok(unique)
421}
422
423#[cfg(test)]
424mod tests {
425 use super::*;
426
427 fn walk(
428 pattern: &str,
429 collect: bool,
430 require_reachable: bool,
431 ) -> Result<(Vec<String>, Vec<RepeatPrefixState>), BuilderTimeout> {
432 let (ops, flags) = crate::redos::ast::parse(pattern, Flags::default())
433 .map_err(|_| BuilderTimeout("parse error".into()))?;
434 let mut prefixes = Vec::new();
435 let mut walker = PrefixWalk {
436 flags,
437 prefixes: &mut prefixes,
438 deadline: None,
439 collect,
440 alphabet: vec!["x".to_owned(), "y".to_owned()],
441 repeat_collector: None,
442 canonical_optionals: true,
443 require_reachable,
444 };
445 let states = walker.walk(&ops, vec![RepeatPrefixState::default()], false)?;
446 Ok((prefixes, states))
447 }
448
449 #[test]
450 fn an_expired_deadline_surfaces_from_every_public_entry() {
451 let expired = std::time::Instant::now() - std::time::Duration::from_secs(1);
452 let error = walk_with_deadline("ab", expired).expect_err("deadline");
454 assert!(error.0.contains("deadline"), "{error}");
455 let error = repeat_reaching_prefixes("a{40}b", Flags::default(), Some(expired), false)
457 .expect_err("deadline");
458 assert!(error.0.contains("deadline"), "{error}");
459 let (ops, _) = crate::redos::ast::parse("(?=a{40})", Flags::default()).expect("parses");
461 let error =
462 assertion_witnesses(&ops, Flags::default(), Some(expired)).expect_err("deadline");
463 assert!(error.0.contains("deadline"), "{error}");
464 }
465
466 #[test]
467 fn a_capturing_lookahead_witness_replaces_the_group_capture() {
468 let (_prefixes, states) = walk(r"(a)(?=(b))b", false, false).expect("walk");
471 assert_eq!(states.len(), 1);
472 assert_eq!(states[0].text, "ab");
473 assert_eq!(
475 states[0].captures,
476 vec![(1, "a".to_owned()), (2, "b".to_owned())]
477 );
478 }
479
480 #[test]
481 fn positive_lookbehind_witnesses_prepend_their_text() {
482 let (_prefixes, states) = walk(r"(?<=xy)z", false, false).expect("walk");
485 assert_eq!(states.len(), 1);
486 assert_eq!(states[0].text, "xyz");
487 }
488
489 #[test]
490 fn single_bound_repeats_never_record_or_collect() {
491 let (prefixes, states) = walk("xa?", true, false).expect("walk");
493 assert!(
494 prefixes.is_empty(),
495 "high == 1 records nothing: {prefixes:?}"
496 );
497 assert_eq!(states.len(), 1);
498 }
499
500 #[test]
501 fn a_fully_excluded_class_consumes_nothing() {
502 let (_prefixes, states) = walk(r"(?!\S)\S", false, false).expect("walk");
505 assert!(states.is_empty(), "no state survives: {states:?}");
506 }
507
508 #[test]
509 fn an_uncaptured_conditional_takes_the_no_branch() {
510 let (_prefixes, states) = walk("(?(1)y|z)", false, false).expect("walk");
512 let texts: Vec<String> = states.iter().map(|s| s.text.clone()).collect();
513 assert_eq!(texts, vec!["z".to_owned()]);
514 let (_prefixes, states) = walk("(x)(?(1)y|z)", false, false).expect("walk");
516 assert_eq!(states[0].text, "xy");
517 }
518
519 #[test]
520 fn a_parse_failure_flows_through_the_harness() {
521 assert!(walk("[unterminated", false, false).is_err());
522 }
523
524 fn walk_with_deadline(
527 pattern: &str,
528 deadline: std::time::Instant,
529 ) -> Result<Vec<RepeatPrefixState>, BuilderTimeout> {
530 let (ops, flags) = crate::redos::ast::parse(pattern, Flags::default()).expect("parses");
531 let mut prefixes = Vec::new();
532 let mut walker = PrefixWalk {
533 flags,
534 prefixes: &mut prefixes,
535 deadline: Some(deadline),
536 collect: false,
537 alphabet: Vec::new(),
538 repeat_collector: None,
539 canonical_optionals: true,
540 require_reachable: false,
541 };
542 walker.walk(&ops, vec![RepeatPrefixState::default()], false)
543 }
544
545 #[test]
546 fn literals_consume_their_first_member() {
547 let (_prefixes, states) = walk("ab", false, false).expect("walk");
548 assert_eq!(states.len(), 1);
549 assert_eq!(states[0].text, "ab");
550 }
551
552 #[test]
553 fn classes_consume_the_interval_start() {
554 let (_prefixes, states) = walk(r"\d", false, false).expect("walk");
555 assert_eq!(states[0].text, "0");
556 }
557
558 #[test]
559 fn optional_atoms_branch_the_states() {
560 let (_prefixes, states) = walk("a?b", false, false).expect("walk");
563 let texts: Vec<String> = states.iter().map(|s| s.text.clone()).collect();
564 assert_eq!(texts, vec!["b".to_owned()]);
565 }
566
567 #[test]
568 fn unbounded_repeats_record_prefixes_when_collecting() {
569 let (prefixes, _states) = walk(r"xa*", true, false).expect("walk");
571 assert!(prefixes.contains(&"x".to_owned()));
572 let (prefixes, _states) = walk(r"a", false, false).expect("walk");
573 assert!(prefixes.is_empty());
574 }
575
576 #[test]
577 fn mandatory_repeats_unroll_into_the_text() {
578 let (_prefixes, states) = walk("a{2}", false, false).expect("walk");
579 assert_eq!(states[0].text, "aa");
580 }
581
582 #[test]
583 fn failure_nodes_kill_the_walk() {
584 let (_prefixes, states) = walk(r"(?!)x", false, false).expect("walk");
585 assert!(states.is_empty());
586 }
587
588 #[test]
589 fn branches_union_the_alternative_states() {
590 let (_prefixes, states) = walk("x|y", false, false).expect("walk");
591 let texts: Vec<String> = states.iter().map(|s| s.text.clone()).collect();
592 assert!(texts.contains(&"x".to_owned()));
593 assert!(texts.contains(&"y".to_owned()));
594 }
595
596 #[test]
597 fn negative_lookahead_excludes_the_member() {
598 let (_prefixes, states) = walk(r"(?!x)\S", false, false).expect("walk");
599 for state in &states {
601 assert_ne!(state.text, "x");
602 }
603 }
604
605 #[test]
606 fn positive_lookahead_pends_the_witness() {
607 let (_prefixes, states) = walk(r"(?=xy)\S", false, false).expect("walk");
608 assert_eq!(states.len(), 1);
610 assert_eq!(states[0].text, "x");
611 assert_eq!(states[0].pending, "y");
612 }
613
614 #[test]
615 fn capturing_groups_record_captures() {
616 let (_prefixes, states) = walk("(x)", false, false).expect("walk");
617 assert_eq!(states[0].text, "x");
618 assert_eq!(states[0].captures, vec![(1, "x".to_owned())]);
619 }
620
621 #[test]
622 fn backreferences_consume_the_captured_text() {
623 let (_prefixes, states) = walk(r"(xy)\1", false, false).expect("walk");
624 assert_eq!(states[0].text, "xyxy");
625 }
626
627 #[test]
628 fn a_backreference_without_a_capture_consumes_nothing() {
629 let (_prefixes, states) = walk(r"(a)|\1", false, false).expect("walk");
633 let texts: Vec<String> = states.iter().map(|s| s.text.clone()).collect();
634 assert_eq!(texts, vec!["a".to_owned()]);
635 }
636
637 #[test]
638 fn a_single_char_forbidden_word_narrows_the_available_set() {
639 let (_prefixes, states) = walk(r"(?!x$)x", false, false).expect("walk");
643 assert!(states.is_empty(), "{states:?}");
644 }
645
646 #[test]
647 fn a_multi_char_forbidden_word_stays_out_of_the_narrowing() {
648 let (_prefixes, states) = walk(r"(?!xy)z", false, false).expect("walk");
652 let texts: Vec<String> = states.iter().map(|s| s.text.clone()).collect();
653 assert_eq!(texts, vec!["z".to_owned()]);
654 }
655
656 #[test]
657 fn conditional_groups_take_the_live_branch() {
658 let (_prefixes, states) = walk("(x)(?(1)y|z)", false, false).expect("walk");
659 assert_eq!(states[0].text, "xy");
660 }
661
662 #[test]
663 fn require_reachable_rejects_unresolved_repeat_sites() {
664 let error = walk(r"(?!)b*", false, true).expect_err("unresolved");
667 assert_eq!(
668 error.0,
669 "Pattern validation cannot resolve repeat-site prefixes"
670 );
671 }
672
673 #[test]
674 fn assertion_witness_budget_is_enforced() {
675 let error = walk(r"(x)(?<!x)a", false, false).expect_err("capture lookbehind");
678 assert_eq!(
679 error.0,
680 "Pattern validation cannot resolve capture-dependent lookbehind"
681 );
682 }
683
684 #[test]
685 fn unique_states_budget_is_enforced() {
686 let many: Vec<RepeatPrefixState> = (0..=PREFIX_WALK_STATE_LIMIT)
688 .map(|index| RepeatPrefixState {
689 text: format!("s{index}"),
690 ..RepeatPrefixState::default()
691 })
692 .collect();
693 let error = unique_states(many).expect_err("state budget exceeded");
694 assert_eq!(
695 error.0,
696 "Pattern validation repeat-prefix state budget exceeded"
697 );
698 let long: Vec<RepeatPrefixState> = vec![RepeatPrefixState {
699 text: "a".repeat(PREFIX_WALK_TEXT_BUDGET + 1),
700 ..RepeatPrefixState::default()
701 }];
702 let error = unique_states(long).expect_err("text budget exceeded");
703 assert_eq!(
704 error.0,
705 "Pattern validation repeat-prefix text budget exceeded"
706 );
707 }
708
709 #[test]
710 fn repeat_reaching_prefixes_returns_deduped_prefixes() {
711 let prefixes =
713 repeat_reaching_prefixes("xa*b", Flags::default(), None, false).expect("prefixes");
714 assert!(prefixes.contains(&"x".to_owned()));
715 assert!(prefixes.iter().all(|prefix| !prefix.is_empty()));
716 let mut sorted = prefixes.clone();
717 sorted.sort();
718 sorted.dedup();
719 assert_eq!(prefixes.len(), sorted.len(), "deduped");
720 }
721
722 #[test]
723 fn repeat_reaching_prefixes_parse_failures_are_empty() {
724 let prefixes =
725 repeat_reaching_prefixes("[oops", Flags::default(), None, false).expect("empty");
726 assert!(prefixes.is_empty());
727 }
728
729 #[test]
730 fn check_deadline_maps_all_three_states() {
731 assert!(check_deadline(None).is_ok());
732 assert!(check_deadline(Some(Instant::now() + std::time::Duration::from_secs(60))).is_ok());
733 let error = check_deadline(Some(Instant::now() - std::time::Duration::from_secs(1)))
734 .expect_err("expired");
735 assert_eq!(
736 error.0,
737 "Pattern validation repeat-prefix construction exceeded its deadline"
738 );
739 }
740
741 #[test]
742 fn assertion_witnesses_reject_unresolved_nested_assertions() {
743 let (ops, _) = crate::redos::ast::parse("a(?=x)", Flags::default()).expect("parses");
744 let error = assertion_witnesses(&ops, Flags::default(), None).expect_err("pending");
745 assert_eq!(
746 error.0,
747 "Pattern validation cannot resolve nested assertion constraints"
748 );
749 }
750
751 #[test]
752 fn collected_prefixes_honor_the_candidate_budget() {
753 let (ops, flags) = crate::redos::ast::parse("x+", Flags::default()).expect("parses");
756 let mut prefixes = vec![String::new(); REPEAT_PREFIX_STATE_LIMIT * 4 + 1];
757 let mut walker = PrefixWalk {
758 flags,
759 prefixes: &mut prefixes,
760 deadline: None,
761 collect: true,
762 alphabet: Vec::new(),
763 repeat_collector: None,
764 canonical_optionals: true,
765 require_reachable: false,
766 };
767 let error = walker
768 .walk(&ops, vec![RepeatPrefixState::default()], false)
769 .expect_err("budget");
770 assert_eq!(
771 error.0,
772 "Pattern validation repeat-prefix candidate budget exceeded"
773 );
774 }
775
776 #[test]
777 fn a_fixed_repeat_of_a_state_unchanging_body_stops_early() {
778 let (_prefixes, states) = walk(r"(?:\b){2}x", false, false).expect("walk");
781 assert_eq!(states.len(), 1);
782 assert_eq!(states[0].text, "x");
783 }
784
785 #[test]
786 fn pending_assertion_states_are_replayed_after_a_repeat() {
787 let (_prefixes, states) = walk(r"(?:x(?=y))+", true, false).expect("walk");
790 assert_eq!(states.len(), 1);
791 assert_eq!(states[0].text, "x");
792 }
793
794 #[test]
795 fn a_conditional_without_else_walks_an_empty_branch() {
796 let (_prefixes, states) = walk(r"(?(1)b)x", false, false).expect("walk");
799 let texts: Vec<String> = states.iter().map(|s| s.text.clone()).collect();
800 assert_eq!(texts, vec!["x".to_owned()]);
801 }
802
803 #[test]
804 fn pending_replay_stops_at_a_fixed_point() {
805 let (_prefixes, states) = walk(r"(?:(?=x))+", true, false).expect("walk");
808 assert_eq!(states.len(), 1);
809 }
810
811 #[test]
812 fn an_unbounded_optional_repeat_replays_its_pending_states() {
813 let (_prefixes, states) = walk(r"(?=x)*y", true, false).expect("walk");
817 assert_eq!(states.len(), 1);
818 assert_eq!(states[0].text, "y");
819 }
820
821 #[test]
822 fn a_bounded_optional_repeat_keeps_a_finite_replay_budget() {
823 let (_prefixes, states) = walk(r"(?:x(?=y)){0,3}z", true, false).expect("walk");
827 assert_eq!(states.len(), 1);
828 assert_eq!(states[0].text, "z");
829 }
830}