Bounded regex compilation¶
Any regular expression whose pattern comes from outside the binary — a config file, a
CLI flag, TUI input, an HTTP payload, a message queue — must be compiled through
compile_bounded. Patterns that are build-time literals may use regex::Regex::new
directly.
compile_bounded¶
The length gate is checked before the pattern reaches the regex engine, so an
over-long pattern costs a len() comparison rather than a compile attempt.
The limits¶
| Constant | Value | What it caps |
|---|---|---|
MAX_PATTERN_LEN |
1024 (1 KiB) |
Pattern length in bytes, not characters. |
SIZE_LIMIT |
1 << 20 (1 MiB) |
RegexBuilder::size_limit — the compiled program. |
DFA_SIZE_LIMIT |
8 << 20 (8 MiB) |
RegexBuilder::dfa_size_limit — the lazy-DFA cache. |
All three are pub const and fixed. There is no builder, no options struct and
no way to raise or lower them for a particular call site. A tool that genuinely needs
different bounds has to build its own RegexBuilder, and owns the consequences.
A pattern of exactly MAX_PATTERN_LEN bytes passes; MAX_PATTERN_LEN + 1 is
rejected.
RegexCompileError¶
#[derive(Debug, thiserror::Error, miette::Diagnostic)]
pub enum RegexCompileError {
TooLong { len: usize },
Compile(#[source] regex::Error),
}
| Variant | Message | Diagnostic code | Cause |
|---|---|---|---|
TooLong |
regex pattern is {len} bytes; limit is 1024 bytes |
rtb_app::regex::too_long |
The pattern exceeded MAX_PATTERN_LEN. |
Compile |
regex failed to compile within memory bounds |
rtb_app::regex::compile |
Invalid syntax, or the compiled program would exceed SIZE_LIMIT / DFA_SIZE_LIMIT. |
Compile carries the underlying regex::Error as its source, so the specific syntax
complaint is available on the error chain — the top-level message does not include
it. miette attaches the help text "simplify the pattern or reduce repetition
counts" to this variant.
Note that a syntax error and a memory-bound breach are the same variant. If your
tool needs to tell a user "your pattern is invalid" apart from "your pattern is too
expensive", inspect the wrapped regex::Error — the enum discriminant will not tell
you.
Neither variant panics and neither allocates unboundedly:
compile_bounded(r"(?:a{10000}){10000}") returns Err(Compile(_)).
Why there is no match timeout¶
Rust's regex crate is a Thompson NFA with linear-time matching, so there is no
catastrophic-backtracking class to defend against. That is the difference from the Go
side of the toolkit, where regexutil also has to bound match time. Here the only
remaining denial-of-service vector is compile-time memory, and that is what these
three limits bound.