1use lru::LruCache;
6use std::num::NonZeroUsize;
7
8pub const DEFAULT_POISON_CAPACITY: usize = 64;
10
11pub struct PoisonSet {
12 cache: LruCache<String, ()>,
13}
14
15impl PoisonSet {
16 pub fn new(capacity: usize) -> Self {
17 Self {
18 cache: LruCache::new(
19 NonZeroUsize::new(capacity.max(1)).expect("capacity.max(1) is always non-zero"),
20 ),
21 }
22 }
23
24 pub fn key(tool: &str, selector: &str) -> String {
27 format!("{tool}\u{1}{selector}")
28 }
29
30 pub fn insert(&mut self, tool: &str, selector: &str) {
32 self.cache.put(Self::key(tool, selector), ());
33 }
34
35 pub fn contains(&mut self, tool: &str, selector: &str) -> bool {
37 self.cache.get(&Self::key(tool, selector)).is_some()
38 }
39
40 pub fn len(&self) -> usize {
41 self.cache.len()
42 }
43
44 pub fn is_empty(&self) -> bool {
45 self.cache.is_empty()
46 }
47}
48
49#[cfg(test)]
50mod tests {
51 use super::*;
52
53 #[test]
54 fn remembers_then_short_circuits_same_selector() {
55 let mut p = PoisonSet::new(64);
56 assert!(!p.contains("inspect_function", "boom"));
57 p.insert("inspect_function", "boom");
58 assert!(p.contains("inspect_function", "boom"));
59 assert!(!p.contains("inspect_function", "safe"));
61 assert!(!p.contains("attach_program", "boom"));
62 }
63
64 #[test]
65 fn eviction_bounds_the_set() {
66 let mut p = PoisonSet::new(2);
67 p.insert("t", "a");
68 p.insert("t", "b");
69 p.insert("t", "c"); assert_eq!(p.len(), 2);
71 assert!(!p.contains("t", "a"));
72 assert!(p.contains("t", "c"));
73 }
74
75 #[test]
76 fn key_separator_avoids_collision() {
77 assert_ne!(PoisonSet::key("a", "b"), PoisonSet::key("a b", ""));
78 }
79}