1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
//! Anchored verifier regexes with whole-chunk-equivalent left context.
use regex::{Regex, RegexBuilder};
use std::sync::{Arc, OnceLock};
/// Lazily compiled anchored copies of a detector regex.
pub(crate) struct AnchoredRegex {
src: Arc<str>,
case_insensitive: bool,
cell: OnceLock<Arc<Regex>>,
left_context_cell: OnceLock<Arc<Regex>>,
}
impl AnchoredRegex {
pub(crate) fn new(src: &str, case_insensitive: bool) -> Self {
Self {
src: Arc::from(src),
case_insensitive,
cell: OnceLock::new(),
left_context_cell: OnceLock::new(),
}
}
/// The `\A`-anchored verifier. FAIL CLOSED in this LazyLock init: see
/// [`AnchoredRegex::compile`]. A build failure panics rather than returning
/// `None`, so consumers can never silently drop this pattern's matches.
pub(crate) fn get(&self) -> &Regex {
self.cell.get_or_init(|| self.compile(r"\A(?:", ")"))
}
/// The left-context anchored verifier (`\A(?s:.)(?:<src>)`). Same
/// fail-closed contract as [`AnchoredRegex::get`].
pub(crate) fn get_with_left_context(&self) -> &Regex {
self.left_context_cell
.get_or_init(|| self.compile(r"\A(?s:.)(?:", ")"))
}
fn compile(&self, prefix: &str, suffix: &str) -> Arc<Regex> {
let anchored = format!("{prefix}{}{suffix}", self.src);
match RegexBuilder::new(&anchored)
.case_insensitive(self.case_insensitive)
.size_limit(crate::types::REGEX_SIZE_LIMIT_BYTES)
.dfa_size_limit(crate::types::regex_dfa_limit())
.crlf(self.case_insensitive)
.build()
{
Ok(rx) => Arc::new(rx),
// Law 10 / fail-closed: the base detector regex ALREADY compiled, so
// wrapping it as `{prefix}<src>{suffix}` failing is a build-invariant
// violation of a HARDCODED transform baked into the binary, never a
// valid runtime condition. The former handling returned `None`, which
// the anchored-scan consumer swallowed into an early `return`, silently
// dropping every match for this pattern (recall loss with no fallback on
// the anchored fast path). A build bug must abort loudly, not degrade
// recall invisibly: panic in the init exactly as the CLAUDE.md Law-10
// guidance for baked-in patterns requires.
Err(error) => panic!(
"keyhog BUILD-INVARIANT VIOLATION: anchored verifier regex failed to compile \
though its base detector regex already compiled. Wrapper `{prefix}…{suffix}` over source \
`{src}` is a compile-time-constant transform, so this can only be a build bug (or a \
size/DFA-limit edge), never valid runtime input. Failing closed instead of silently \
dropping this pattern's matches (Law 10). error={error}",
src = self.src,
),
}
}
}
#[cfg(test)]
#[path = "../tests/unit/anchored_regex_cases.rs"]
mod tests;