-
Notifications
You must be signed in to change notification settings - Fork 3.6k
perf(tui): stop rebuilding per-call work on the shell, hook and cloud paths (#6208) #6264
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,7 +1,34 @@ | ||||||||||||
| //! Command matching helpers for execpolicy rules. | ||||||||||||
|
|
||||||||||||
| use std::collections::HashMap; | ||||||||||||
| use std::sync::{Arc, Mutex, OnceLock}; | ||||||||||||
|
|
||||||||||||
| use regex::Regex; | ||||||||||||
|
|
||||||||||||
| /// `pattern` compiled once as an anchored `*`-glob, then reused. | ||||||||||||
| /// | ||||||||||||
| /// Every other regex metacharacter is escaped, so `*` is the only wildcard | ||||||||||||
| /// (matching any run of characters, newline included). `None` means the pattern | ||||||||||||
| /// does not compile; callers treat that as "no match". | ||||||||||||
|
Comment on lines
+11
to
+12
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Correct the wildcard description so it matches the regex actually compiled:
Suggested change
|
||||||||||||
| /// | ||||||||||||
| /// The patterns come from configuration and are stable between edits, but the | ||||||||||||
| /// callers run per shell execution and per hook event. Compiling here on first | ||||||||||||
| /// use keeps the fast path free of `Regex::new` without changing what matches. | ||||||||||||
| pub fn compiled_glob(pattern: &str) -> Option<Arc<Regex>> { | ||||||||||||
| static CACHE: OnceLock<Mutex<HashMap<String, Option<Arc<Regex>>>>> = OnceLock::new(); | ||||||||||||
| let cache = CACHE.get_or_init(|| Mutex::new(HashMap::new())); | ||||||||||||
| let mut cache = cache | ||||||||||||
| .lock() | ||||||||||||
| .unwrap_or_else(|poisoned| poisoned.into_inner()); | ||||||||||||
| cache | ||||||||||||
| .entry(pattern.to_string()) | ||||||||||||
| .or_insert_with(|| { | ||||||||||||
| let escaped = regex::escape(pattern).replace(r"\*", ".*"); | ||||||||||||
| Regex::new(&format!("^{escaped}$")).ok().map(Arc::new) | ||||||||||||
| }) | ||||||||||||
| .clone() | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| /// Normalize a command string by shlex parsing and re-joining tokens. | ||||||||||||
| /// | ||||||||||||
| /// Strips heredoc bodies first (#419) so a command like | ||||||||||||
|
|
@@ -110,11 +137,7 @@ pub fn pattern_matches(pattern: &str, command: &str) -> bool { | |||||||||||
| return true; | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| let escaped = regex::escape(&pattern).replace("\\*", ".*"); | ||||||||||||
| let Ok(re) = Regex::new(&format!("^{escaped}$")) else { | ||||||||||||
| return false; | ||||||||||||
| }; | ||||||||||||
| re.is_match(&command) | ||||||||||||
| compiled_glob(&pattern).is_some_and(|re| re.is_match(&command)) | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| #[cfg(test)] | ||||||||||||
|
|
@@ -195,4 +218,28 @@ mod tests { | |||||||||||
| let normalized = normalize_command("cat <<EOF > file.txt\nbody\nEOF"); | ||||||||||||
| assert!(pattern_matches("cat > file.txt", &normalized)); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| #[test] | ||||||||||||
| fn compiled_glob_is_compiled_once_per_pattern() { | ||||||||||||
| // The per-shell-execution and per-hook-event callers rely on this | ||||||||||||
| // returning the same compiled program rather than rebuilding it. | ||||||||||||
| let first = compiled_glob("mcp__*").expect("glob compiles"); | ||||||||||||
| let second = compiled_glob("mcp__*").expect("glob compiles"); | ||||||||||||
| assert!(std::sync::Arc::ptr_eq(&first, &second)); | ||||||||||||
| assert!(first.is_match("mcp__github__search")); | ||||||||||||
| assert!(!first.is_match("read_file")); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| #[test] | ||||||||||||
| fn compiled_glob_escapes_every_metacharacter_except_star() { | ||||||||||||
| // `regex::escape` is what makes `a.b` a literal while `*` stays a | ||||||||||||
| // wildcard — the same contract `pattern_matches` has always had. | ||||||||||||
| let literal = compiled_glob("a.b").expect("glob compiles"); | ||||||||||||
| assert!(literal.is_match("a.b")); | ||||||||||||
| assert!(!literal.is_match("axb")); | ||||||||||||
|
|
||||||||||||
| let wildcard = compiled_glob("a*b").expect("glob compiles"); | ||||||||||||
| assert!(wildcard.is_match("ab")); | ||||||||||||
| assert!(wildcard.is_match("a middle b")); | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[INFO]
compiled_globdoc says*matches newlines; the compiled.*does notThe new doc comment on
compiled_globclaims*matches 'any run of characters, newline included'. The regex it builds isformat!("^{escaped}$")where*became.*and nothing enables(?s), and Rust'sregexcrate does not let.match\n;^/$are text anchors by default, so a pattern such asmcp__*orrm -rf *will not span a newline. Runtime behaviour is unchanged (pattern_matchesand the old hook matcher built exactly this regex inline), so nothing is broken today — but the comment contradicts the property the PR description says must be preserved, and someone reasoning about multi-line command coverage (the deny path intoml_rules::evaluaterelies onexpanded_commandsto split lines precisely because a single anchored glob cannot cross one) would be misled by it. Smallest fix: correct the parenthetical.