saf_core/
summary_registry.rs1use std::collections::BTreeMap;
14
15use crate::ids::FunctionId;
16use crate::spec::SpecRegistry;
17use crate::summary::FunctionSummary;
18
19#[derive(Debug, Clone, Default)]
24pub struct SummaryRegistry {
25 user_specs: BTreeMap<FunctionId, FunctionSummary>,
27 computed: BTreeMap<FunctionId, FunctionSummary>,
29 defaults: BTreeMap<FunctionId, FunctionSummary>,
31}
32
33impl SummaryRegistry {
34 #[must_use]
36 pub fn new() -> Self {
37 Self::default()
38 }
39
40 #[must_use]
45 pub fn get(&self, id: &FunctionId) -> Option<&FunctionSummary> {
46 self.user_specs
47 .get(id)
48 .or_else(|| self.computed.get(id))
49 .or_else(|| self.defaults.get(id))
50 }
51
52 pub fn insert_computed(&mut self, summary: FunctionSummary) {
56 self.computed.insert(summary.function_id, summary);
57 }
58
59 pub fn insert_user(&mut self, summary: FunctionSummary) {
63 self.user_specs.insert(summary.function_id, summary);
64 }
65
66 pub fn insert_default(&mut self, summary: FunctionSummary) {
70 self.defaults.insert(summary.function_id, summary);
71 }
72
73 pub fn load_specs(&mut self, registry: &SpecRegistry) {
80 for spec in registry.iter() {
81 let function_id = FunctionId::derive(spec.name.as_bytes());
82 let summary = FunctionSummary::from_spec(spec, function_id);
83 self.defaults.insert(function_id, summary);
84 }
85 }
86
87 pub fn load_user_specs(&mut self, registry: &SpecRegistry) {
92 for spec in registry.iter() {
93 let function_id = FunctionId::derive(spec.name.as_bytes());
94 let summary = FunctionSummary::from_spec(spec, function_id);
95 self.user_specs.insert(function_id, summary);
96 }
97 }
98
99 #[must_use]
104 pub fn total_count(&self) -> usize {
105 self.user_specs.len() + self.computed.len() + self.defaults.len()
106 }
107
108 #[must_use]
110 pub fn unique_count(&self) -> usize {
111 let mut ids: std::collections::BTreeSet<FunctionId> = std::collections::BTreeSet::new();
112 ids.extend(self.user_specs.keys());
113 ids.extend(self.computed.keys());
114 ids.extend(self.defaults.keys());
115 ids.len()
116 }
117
118 #[must_use]
120 pub fn is_empty(&self) -> bool {
121 self.user_specs.is_empty() && self.computed.is_empty() && self.defaults.is_empty()
122 }
123
124 pub fn iter(&self) -> impl Iterator<Item = (&FunctionId, &FunctionSummary)> {
127 let mut ids: std::collections::BTreeSet<&FunctionId> = std::collections::BTreeSet::new();
129 ids.extend(self.user_specs.keys());
130 ids.extend(self.computed.keys());
131 ids.extend(self.defaults.keys());
132
133 ids.into_iter()
134 .filter_map(move |id| self.get(id).map(|s| (id, s)))
135 }
136
137 #[must_use]
139 pub fn computed_count(&self) -> usize {
140 self.computed.len()
141 }
142
143 #[must_use]
145 pub fn user_count(&self) -> usize {
146 self.user_specs.len()
147 }
148
149 #[must_use]
151 pub fn defaults_count(&self) -> usize {
152 self.defaults.len()
153 }
154}
155
156#[cfg(test)]
157mod tests {
158 use super::*;
159 use crate::spec::{FunctionSpec, Nullness, Pointer, ReturnSpec, Role};
160 use crate::summary::{SummaryNullness, SummaryPrecision, SummaryRole, SummarySource};
161
162 fn make_fid(name: &str) -> FunctionId {
163 FunctionId::derive(name.as_bytes())
164 }
165
166 fn make_summary(name: &str, source: SummarySource) -> FunctionSummary {
167 let mut s = FunctionSummary::default_for(make_fid(name));
168 s.source = source;
169 s
170 }
171
172 #[test]
173 fn empty_registry() {
174 let reg = SummaryRegistry::new();
175 assert!(reg.is_empty());
176 assert_eq!(reg.total_count(), 0);
177 assert_eq!(reg.unique_count(), 0);
178 assert!(reg.get(&make_fid("malloc")).is_none());
179 }
180
181 #[test]
182 fn insert_and_get_computed() {
183 let mut reg = SummaryRegistry::new();
184 let summary = make_summary("test_fn", SummarySource::Analysis);
185 let fid = summary.function_id;
186
187 reg.insert_computed(summary);
188
189 let found = reg.get(&fid).expect("should find computed summary");
190 assert_eq!(found.function_id, fid);
191 assert_eq!(found.source, SummarySource::Analysis);
192 assert_eq!(reg.computed_count(), 1);
193 }
194
195 #[test]
196 fn priority_user_over_computed() {
197 let mut reg = SummaryRegistry::new();
198 let fid = make_fid("func");
199
200 let mut computed = FunctionSummary::default_for(fid);
201 computed.source = SummarySource::Analysis;
202 computed.pure = false;
203 reg.insert_computed(computed);
204
205 let mut user = FunctionSummary::default_for(fid);
206 user.source = SummarySource::Spec;
207 user.pure = true;
208 reg.insert_user(user);
209
210 let found = reg.get(&fid).expect("should find summary");
211 assert_eq!(found.source, SummarySource::Spec);
212 assert!(found.pure);
213 }
214
215 #[test]
216 fn priority_computed_over_default() {
217 let mut reg = SummaryRegistry::new();
218 let fid = make_fid("func");
219
220 let mut default_s = FunctionSummary::default_for(fid);
221 default_s.source = SummarySource::Default;
222 default_s.version = 1;
223 reg.insert_default(default_s);
224
225 let mut computed = FunctionSummary::default_for(fid);
226 computed.source = SummarySource::Analysis;
227 computed.version = 5;
228 reg.insert_computed(computed);
229
230 let found = reg.get(&fid).expect("should find summary");
231 assert_eq!(found.source, SummarySource::Analysis);
232 assert_eq!(found.version, 5);
233 }
234
235 #[test]
236 fn priority_user_over_all() {
237 let mut reg = SummaryRegistry::new();
238 let fid = make_fid("func");
239
240 reg.insert_default(make_summary("func", SummarySource::Default));
241 reg.insert_computed(make_summary("func", SummarySource::Analysis));
242
243 let mut user = FunctionSummary::default_for(fid);
244 user.source = SummarySource::Spec;
245 user.noreturn = true;
246 reg.insert_user(user);
247
248 let found = reg.get(&fid).expect("should find summary");
249 assert_eq!(found.source, SummarySource::Spec);
250 assert!(found.noreturn);
251 }
252
253 #[test]
254 fn unique_count_deduplicates() {
255 let mut reg = SummaryRegistry::new();
256
257 reg.insert_default(make_summary("func", SummarySource::Default));
258 reg.insert_computed(make_summary("func", SummarySource::Analysis));
259 reg.insert_user(make_summary("func", SummarySource::Spec));
260
261 assert_eq!(reg.total_count(), 3);
263 assert_eq!(reg.unique_count(), 1);
264 }
265
266 #[test]
267 fn load_specs_populates_defaults() {
268 let mut spec_reg = SpecRegistry::new();
269 let mut malloc_spec = FunctionSpec::new("malloc");
270 malloc_spec.role = Some(Role::Allocator);
271 malloc_spec.returns = Some(ReturnSpec {
272 pointer: Some(Pointer::FreshHeap),
273 nullness: Some(Nullness::MaybeNull),
274 ..ReturnSpec::default()
275 });
276 spec_reg.add(malloc_spec).expect("add spec");
277
278 let mut free_spec = FunctionSpec::new("free");
279 free_spec.role = Some(Role::Deallocator);
280 spec_reg.add(free_spec).expect("add spec");
281
282 let mut reg = SummaryRegistry::new();
283 reg.load_specs(&spec_reg);
284
285 assert_eq!(reg.defaults_count(), 2);
286
287 let malloc_id = make_fid("malloc");
288 let found = reg.get(&malloc_id).expect("should find malloc");
289 assert_eq!(found.role, Some(SummaryRole::Allocator));
290 assert_eq!(found.source, SummarySource::Spec);
291 assert_eq!(found.return_nullness, Some(SummaryNullness::MaybeNull));
292 assert_eq!(found.precision, SummaryPrecision::Sound);
293
294 let free_id = make_fid("free");
295 let found = reg.get(&free_id).expect("should find free");
296 assert_eq!(found.role, Some(SummaryRole::Deallocator));
297 }
298
299 #[test]
300 fn computed_overrides_loaded_specs() {
301 let mut spec_reg = SpecRegistry::new();
302 let mut spec = FunctionSpec::new("my_func");
303 spec.pure = Some(true);
304 spec_reg.add(spec).expect("add spec");
305
306 let mut reg = SummaryRegistry::new();
307 reg.load_specs(&spec_reg);
308
309 let fid = make_fid("my_func");
311 let mut computed = FunctionSummary::default_for(fid);
312 computed.source = SummarySource::Analysis;
313 computed.pure = false;
314 computed.version = 3;
315 reg.insert_computed(computed);
316
317 let found = reg.get(&fid).expect("should find summary");
318 assert_eq!(found.source, SummarySource::Analysis);
319 assert!(!found.pure);
320 assert_eq!(found.version, 3);
321 }
322
323 #[test]
324 fn iter_yields_highest_priority() {
325 let mut reg = SummaryRegistry::new();
326
327 reg.insert_default(make_summary("a", SummarySource::Default));
329 reg.insert_computed(make_summary("b", SummarySource::Analysis));
330
331 let mut computed_a = FunctionSummary::default_for(make_fid("a"));
333 computed_a.source = SummarySource::Analysis;
334 computed_a.version = 10;
335 reg.insert_computed(computed_a);
336
337 let results: Vec<_> = reg.iter().collect();
338 assert_eq!(results.len(), 2);
339
340 let a_id = make_fid("a");
342 let a_summary = results.iter().find(|(id, _)| **id == a_id).expect("find a");
343 assert_eq!(a_summary.1.source, SummarySource::Analysis);
344 assert_eq!(a_summary.1.version, 10);
345 }
346
347 #[test]
348 fn load_user_specs_goes_to_user_tier() {
349 let mut spec_reg = SpecRegistry::new();
350 let mut spec = FunctionSpec::new("custom_alloc");
351 spec.role = Some(Role::Allocator);
352 spec_reg.add(spec).expect("add spec");
353
354 let mut reg = SummaryRegistry::new();
355 reg.load_user_specs(&spec_reg);
356
357 assert_eq!(reg.user_count(), 1);
358 assert_eq!(reg.defaults_count(), 0);
359
360 let fid = make_fid("custom_alloc");
361 let found = reg.get(&fid).expect("should find");
362 assert_eq!(found.role, Some(SummaryRole::Allocator));
363 }
364
365 #[test]
366 fn replace_computed_summary() {
367 let mut reg = SummaryRegistry::new();
368 let fid = make_fid("evolving_func");
369
370 let mut v1 = FunctionSummary::default_for(fid);
371 v1.source = SummarySource::Analysis;
372 v1.version = 1;
373 v1.pure = false;
374 reg.insert_computed(v1);
375
376 let mut v2 = FunctionSummary::default_for(fid);
377 v2.source = SummarySource::Analysis;
378 v2.version = 2;
379 v2.pure = true;
380 reg.insert_computed(v2);
381
382 let found = reg.get(&fid).expect("should find");
383 assert_eq!(found.version, 2);
384 assert!(found.pure);
385 assert_eq!(reg.computed_count(), 1);
386 }
387}