Skip to main content

oxilean_kernel/abstract_interp/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use super::functions::AbstractDomain;
6
7/// A reduced product of interval and parity domains.
8#[allow(dead_code)]
9#[derive(Debug, Clone, Copy)]
10#[allow(missing_docs)]
11pub struct IntervalParityProduct {
12    pub interval: Interval,
13    pub parity: ParityDomain,
14}
15#[allow(dead_code)]
16impl IntervalParityProduct {
17    /// Create from components.
18    pub fn new(interval: Interval, parity: ParityDomain) -> Self {
19        IntervalParityProduct { interval, parity }
20    }
21    /// Create from a concrete value.
22    pub fn from_value(v: i64) -> Self {
23        IntervalParityProduct {
24            interval: Interval::new(v, v),
25            parity: ParityDomain::from_value(v),
26        }
27    }
28    /// Return the top element.
29    pub fn top() -> Self {
30        IntervalParityProduct {
31            interval: Interval::top(),
32            parity: ParityDomain::Top,
33        }
34    }
35    /// Return the bottom element.
36    pub fn bottom() -> Self {
37        IntervalParityProduct {
38            interval: Interval::bottom(),
39            parity: ParityDomain::Bottom,
40        }
41    }
42    /// Join two products.
43    pub fn join(&self, other: &IntervalParityProduct) -> IntervalParityProduct {
44        IntervalParityProduct {
45            interval: self.interval.join(&other.interval),
46            parity: self.parity.join(&other.parity),
47        }
48    }
49    /// Add two products.
50    pub fn add(&self, other: &IntervalParityProduct) -> IntervalParityProduct {
51        IntervalParityProduct {
52            interval: self.interval.add(&other.interval),
53            parity: self.parity.add(&other.parity),
54        }
55    }
56    /// Return whether this is bottom.
57    pub fn is_bottom(&self) -> bool {
58        self.interval.is_bottom() || self.parity.is_bottom()
59    }
60}
61/// An alarm generated when abstract analysis detects a potential issue.
62#[allow(dead_code)]
63#[derive(Debug, Clone)]
64#[allow(missing_docs)]
65pub struct AbstractAlarm {
66    pub label: String,
67    pub message: String,
68    pub severity: AlarmSeverity,
69}
70#[allow(dead_code)]
71impl AbstractAlarm {
72    /// Create an alarm.
73    pub fn new(label: &str, message: &str, severity: AlarmSeverity) -> Self {
74        AbstractAlarm {
75            label: label.to_string(),
76            message: message.to_string(),
77            severity,
78        }
79    }
80    /// Return whether this is an error-level alarm.
81    pub fn is_error(&self) -> bool {
82        self.severity == AlarmSeverity::Error
83    }
84}
85/// An analysis pass configuration.
86#[allow(dead_code)]
87#[derive(Debug, Clone)]
88#[allow(missing_docs)]
89pub struct AnalysisConfig {
90    pub max_iterations: u32,
91    pub use_widening: bool,
92    pub use_narrowing: bool,
93    pub collect_alarms: bool,
94    pub verbose: bool,
95}
96#[allow(dead_code)]
97impl AnalysisConfig {
98    /// Default configuration.
99    pub fn default_config() -> Self {
100        AnalysisConfig {
101            max_iterations: 100,
102            use_widening: true,
103            use_narrowing: true,
104            collect_alarms: true,
105            verbose: false,
106        }
107    }
108    /// Quick (fast) configuration for CI.
109    pub fn fast() -> Self {
110        AnalysisConfig {
111            max_iterations: 10,
112            use_widening: true,
113            use_narrowing: false,
114            collect_alarms: false,
115            verbose: false,
116        }
117    }
118}
119#[allow(dead_code)]
120#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
121#[allow(missing_docs)]
122pub enum AlarmSeverity {
123    Info,
124    Warning,
125    Error,
126}
127/// Abstract comparison result (may hold, definitely holds, definitely fails).
128#[allow(dead_code)]
129#[derive(Debug, Clone, Copy, PartialEq, Eq)]
130#[allow(missing_docs)]
131pub enum AbstractCmp {
132    DefinitelyTrue,
133    DefinitelyFalse,
134    Unknown,
135}
136#[allow(dead_code)]
137impl AbstractCmp {
138    /// Abstract less-than comparison of two intervals.
139    pub fn lt(a: &Interval, b: &Interval) -> Self {
140        if a.is_bottom() || b.is_bottom() {
141            return AbstractCmp::Unknown;
142        }
143        if a.hi < b.lo {
144            AbstractCmp::DefinitelyTrue
145        } else if a.lo >= b.hi {
146            AbstractCmp::DefinitelyFalse
147        } else {
148            AbstractCmp::Unknown
149        }
150    }
151    /// Abstract equality comparison.
152    pub fn eq(a: &Interval, b: &Interval) -> Self {
153        if a.is_bottom() || b.is_bottom() {
154            return AbstractCmp::Unknown;
155        }
156        let meet = a.meet(b);
157        if meet.is_bottom() {
158            AbstractCmp::DefinitelyFalse
159        } else if a.lo == a.hi && b.lo == b.hi && a.lo == b.lo {
160            AbstractCmp::DefinitelyTrue
161        } else {
162            AbstractCmp::Unknown
163        }
164    }
165    /// Return whether the comparison is definitely true.
166    pub fn is_definitely_true(&self) -> bool {
167        *self == AbstractCmp::DefinitelyTrue
168    }
169    /// Return whether the comparison is definitely false.
170    pub fn is_definitely_false(&self) -> bool {
171        *self == AbstractCmp::DefinitelyFalse
172    }
173}
174/// A collection of abstract analysis results for all variables.
175#[allow(dead_code)]
176#[allow(missing_docs)]
177pub struct AnalysisResults {
178    entries: Vec<(String, IntervalParityProduct)>,
179}
180#[allow(dead_code)]
181impl AnalysisResults {
182    /// Create empty results.
183    pub fn new() -> Self {
184        AnalysisResults {
185            entries: Vec::new(),
186        }
187    }
188    /// Record results for a variable.
189    pub fn set(&mut self, var: &str, result: IntervalParityProduct) {
190        if let Some(e) = self.entries.iter_mut().find(|(v, _)| v == var) {
191            e.1 = result;
192        } else {
193            self.entries.push((var.to_string(), result));
194        }
195    }
196    /// Look up results for a variable.
197    pub fn get(&self, var: &str) -> Option<IntervalParityProduct> {
198        self.entries.iter().find(|(v, _)| v == var).map(|(_, r)| *r)
199    }
200    /// Return all variables and their results.
201    pub fn all(&self) -> &[(String, IntervalParityProduct)] {
202        &self.entries
203    }
204    /// Return the number of analyzed variables.
205    pub fn len(&self) -> usize {
206        self.entries.len()
207    }
208    /// Return whether any results are present.
209    pub fn is_empty(&self) -> bool {
210        self.entries.is_empty()
211    }
212    /// Return variables where the interval is proven to be non-negative.
213    pub fn proven_non_negative(&self) -> Vec<&str> {
214        self.entries
215            .iter()
216            .filter(|(_, r)| r.interval.lo >= 0 && !r.interval.is_bottom())
217            .map(|(v, _)| v.as_str())
218            .collect()
219    }
220    /// Return variables proven to be even.
221    pub fn proven_even(&self) -> Vec<&str> {
222        self.entries
223            .iter()
224            .filter(|(_, r)| r.parity == ParityDomain::Even)
225            .map(|(v, _)| v.as_str())
226            .collect()
227    }
228}
229/// Abstract domain for expression depth analysis.
230#[derive(Debug, Clone, Copy, PartialEq, Eq)]
231pub struct DepthDomain {
232    /// Maximum allowed depth.
233    pub max_depth: usize,
234    /// Current depth reached.
235    pub current_depth: usize,
236}
237impl DepthDomain {
238    /// Create a new depth domain with the given maximum.
239    pub fn new(max: usize) -> Self {
240        DepthDomain {
241            max_depth: max,
242            current_depth: 0,
243        }
244    }
245    /// True if the current depth is within bounds.
246    pub fn is_bounded(&self) -> bool {
247        self.current_depth <= self.max_depth
248    }
249    /// Increase depth by one, saturating at max_depth + 1.
250    pub fn increase(&self) -> DepthDomain {
251        DepthDomain {
252            max_depth: self.max_depth,
253            current_depth: self.current_depth.saturating_add(1),
254        }
255    }
256    /// Join (take the maximum current depth) with another DepthDomain.
257    pub fn join(&self, other: &DepthDomain) -> DepthDomain {
258        DepthDomain {
259            max_depth: self.max_depth.max(other.max_depth),
260            current_depth: self.current_depth.max(other.current_depth),
261        }
262    }
263}
264/// An abstract value in the interval domain [lo, hi].
265#[allow(dead_code)]
266#[derive(Debug, Clone, Copy, PartialEq, Eq)]
267#[allow(missing_docs)]
268pub struct Interval {
269    pub lo: i64,
270    pub hi: i64,
271}
272#[allow(dead_code)]
273impl Interval {
274    /// Create an interval [lo, hi].
275    pub fn new(lo: i64, hi: i64) -> Self {
276        Interval {
277            lo: lo.min(hi),
278            hi: lo.max(hi),
279        }
280    }
281    /// Return the top interval (-∞, +∞) represented as (i64::MIN, i64::MAX).
282    pub fn top() -> Self {
283        Interval {
284            lo: i64::MIN,
285            hi: i64::MAX,
286        }
287    }
288    /// Return the bottom interval (empty).
289    pub fn bottom() -> Self {
290        Interval {
291            lo: i64::MAX,
292            hi: i64::MIN,
293        }
294    }
295    /// Return whether this is the bottom (empty) interval.
296    pub fn is_bottom(&self) -> bool {
297        self.lo > self.hi
298    }
299    /// Return whether this is the top interval.
300    pub fn is_top(&self) -> bool {
301        self.lo == i64::MIN && self.hi == i64::MAX
302    }
303    /// Return the join (widening step) of two intervals.
304    pub fn join(&self, other: &Interval) -> Interval {
305        if self.is_bottom() {
306            return *other;
307        }
308        if other.is_bottom() {
309            return *self;
310        }
311        Interval::new(self.lo.min(other.lo), self.hi.max(other.hi))
312    }
313    /// Return the meet (intersection) of two intervals.
314    pub fn meet(&self, other: &Interval) -> Interval {
315        let lo = self.lo.max(other.lo);
316        let hi = self.hi.min(other.hi);
317        if lo > hi {
318            Interval::bottom()
319        } else {
320            Interval { lo, hi }
321        }
322    }
323    /// Return whether this interval contains a given value.
324    pub fn contains(&self, v: i64) -> bool {
325        !self.is_bottom() && self.lo <= v && v <= self.hi
326    }
327    /// Return the width (hi - lo + 1), or 0 if bottom.
328    pub fn width(&self) -> u64 {
329        if self.is_bottom() {
330            0
331        } else {
332            (self.hi - self.lo + 1) as u64
333        }
334    }
335    /// Add two intervals.
336    pub fn add(&self, other: &Interval) -> Interval {
337        if self.is_bottom() || other.is_bottom() {
338            return Interval::bottom();
339        }
340        Interval::new(
341            self.lo.saturating_add(other.lo),
342            self.hi.saturating_add(other.hi),
343        )
344    }
345    /// Negate an interval.
346    pub fn negate(&self) -> Interval {
347        if self.is_bottom() {
348            return Interval::bottom();
349        }
350        Interval::new(-self.hi, -self.lo)
351    }
352    /// Subtract two intervals.
353    pub fn sub(&self, other: &Interval) -> Interval {
354        self.add(&other.negate())
355    }
356}
357/// Abstract interpreter for kernel expressions.
358pub struct AbstractInterpreter {
359    max_depth: usize,
360}
361impl AbstractInterpreter {
362    /// Create a new abstract interpreter with the given maximum depth.
363    pub fn new(max_depth: usize) -> Self {
364        AbstractInterpreter { max_depth }
365    }
366    /// Analyze the nesting depth of a parenthesised expression string.
367    pub fn analyze_depth(&self, expr_str: &str) -> DepthDomain {
368        let mut domain = DepthDomain::new(self.max_depth);
369        let mut max_seen: usize = 0;
370        let mut current: usize = 0;
371        for ch in expr_str.chars() {
372            match ch {
373                '(' | '[' | '{' => {
374                    current = current.saturating_add(1);
375                    if current > max_seen {
376                        max_seen = current;
377                    }
378                }
379                ')' | ']' | '}' => {
380                    current = current.saturating_sub(1);
381                }
382                _ => {}
383            }
384        }
385        domain.current_depth = max_seen;
386        domain
387    }
388    /// Analyze the numeric sign of a simple expression string.
389    ///
390    /// Recognises leading `-` for negative and plain digits for positive.
391    pub fn analyze_sign(&self, expr_str: &str) -> SignDomain {
392        let trimmed = expr_str.trim();
393        if trimmed.is_empty() {
394            return SignDomain::Bottom;
395        }
396        if let Ok(n) = trimmed.parse::<i64>() {
397            return match n.cmp(&0) {
398                std::cmp::Ordering::Less => SignDomain::Neg,
399                std::cmp::Ordering::Equal => SignDomain::Zero,
400                std::cmp::Ordering::Greater => SignDomain::Pos,
401            };
402        }
403        if trimmed.starts_with('-') {
404            SignDomain::Neg
405        } else if trimmed.chars().next().is_some_and(|c| c.is_ascii_digit()) {
406            SignDomain::Pos
407        } else {
408            SignDomain::Top
409        }
410    }
411    /// Analyze the approximate size of an expression string.
412    pub fn analyze_size(&self, expr_str: &str) -> SizeDomain {
413        let count = expr_str
414            .split(|c: char| c.is_whitespace() || matches!(c, '(' | ')' | '[' | ']'))
415            .filter(|s| !s.is_empty())
416            .count();
417        SizeDomain::from_count(count)
418    }
419    /// Compute a fixed point of `f` starting from `init`.
420    ///
421    /// Iterates until the value stabilises or a maximum number of steps
422    /// (1 000) is reached, at which point the last value is returned.
423    pub fn fixed_point<S: Clone + PartialEq, F: Fn(&S) -> S>(&self, init: S, f: F) -> S {
424        const MAX_ITERS: usize = 1_000;
425        let mut current = init;
426        for _ in 0..MAX_ITERS {
427            let next = f(&current);
428            if next == current {
429                return current;
430            }
431            current = next;
432        }
433        current
434    }
435}
436/// Abstract reachability analysis: which program points are reachable?
437#[allow(dead_code)]
438pub struct ReachabilityAnalysis {
439    reachable: Vec<String>,
440    unreachable: Vec<String>,
441}
442#[allow(dead_code)]
443impl ReachabilityAnalysis {
444    /// Create an empty analysis.
445    pub fn new() -> Self {
446        ReachabilityAnalysis {
447            reachable: Vec::new(),
448            unreachable: Vec::new(),
449        }
450    }
451    /// Mark a point as reachable.
452    pub fn mark_reachable(&mut self, label: &str) {
453        if !self.reachable.contains(&label.to_string()) {
454            self.reachable.push(label.to_string());
455        }
456        self.unreachable.retain(|s| s != label);
457    }
458    /// Mark a point as unreachable.
459    pub fn mark_unreachable(&mut self, label: &str) {
460        if !self.unreachable.contains(&label.to_string()) {
461            self.unreachable.push(label.to_string());
462        }
463        self.reachable.retain(|s| s != label);
464    }
465    /// Return whether a point is definitely reachable.
466    pub fn is_reachable(&self, label: &str) -> bool {
467        self.reachable.contains(&label.to_string())
468    }
469    /// Return whether a point is definitely unreachable.
470    pub fn is_unreachable(&self, label: &str) -> bool {
471        self.unreachable.contains(&label.to_string())
472    }
473    /// Return the count of reachable points.
474    pub fn reachable_count(&self) -> usize {
475        self.reachable.len()
476    }
477    /// Return the count of unreachable points.
478    pub fn unreachable_count(&self) -> usize {
479        self.unreachable.len()
480    }
481}
482/// Combined abstract state for expression analysis.
483#[derive(Debug, Clone)]
484pub struct AbstractState {
485    /// Sign information.
486    pub sign: SignDomain,
487    /// Depth information.
488    pub depth: DepthDomain,
489    /// Size information.
490    pub size: SizeDomain,
491}
492impl AbstractState {
493    /// Create an initial abstract state.
494    pub fn new() -> Self {
495        AbstractState {
496            sign: SignDomain::Bottom,
497            depth: DepthDomain::new(1024),
498            size: SizeDomain::Zero,
499        }
500    }
501    /// Join two abstract states component-wise.
502    pub fn join(&self, other: &AbstractState) -> AbstractState {
503        AbstractState {
504            sign: self.sign.join(&other.sign),
505            depth: self.depth.join(&other.depth),
506            size: SizeDomain::max(self.size, other.size),
507        }
508    }
509}
510/// A database of function summaries.
511#[allow(dead_code)]
512pub struct SummaryDatabase {
513    entries: Vec<FunctionSummary>,
514}
515#[allow(dead_code)]
516impl SummaryDatabase {
517    /// Create an empty database.
518    pub fn new() -> Self {
519        SummaryDatabase {
520            entries: Vec::new(),
521        }
522    }
523    /// Add a summary.
524    pub fn add(&mut self, summary: FunctionSummary) {
525        self.entries.push(summary);
526    }
527    /// Look up a summary by function name.
528    pub fn find(&self, name: &str) -> Option<&FunctionSummary> {
529        self.entries.iter().find(|s| s.function_name == name)
530    }
531    /// Return all functions with proven termination.
532    pub fn proven_terminating(&self) -> Vec<&str> {
533        self.entries
534            .iter()
535            .filter(|s| s.terminates())
536            .map(|s| s.function_name.as_str())
537            .collect()
538    }
539    /// Return the number of summaries.
540    pub fn len(&self) -> usize {
541        self.entries.len()
542    }
543    /// Return whether the database is empty.
544    pub fn is_empty(&self) -> bool {
545        self.entries.is_empty()
546    }
547}
548/// An abstract reachability domain for basic blocks.
549#[allow(dead_code)]
550#[derive(Debug, Clone, Copy, PartialEq, Eq)]
551#[allow(missing_docs)]
552pub enum BlockReachability {
553    Unreachable,
554    Reachable,
555    Unknown,
556}
557#[allow(dead_code)]
558impl BlockReachability {
559    /// Join two reachability values.
560    pub fn join(&self, other: &BlockReachability) -> BlockReachability {
561        use BlockReachability::*;
562        match (self, other) {
563            (Unreachable, x) | (x, Unreachable) => *x,
564            (Reachable, _) | (_, Reachable) => Reachable,
565            _ => Unknown,
566        }
567    }
568    /// Return whether this block may be reachable.
569    pub fn may_be_reachable(&self) -> bool {
570        !matches!(self, BlockReachability::Unreachable)
571    }
572}
573/// Collects alarms during abstract analysis.
574#[allow(dead_code)]
575pub struct AlarmCollector {
576    alarms: Vec<AbstractAlarm>,
577}
578#[allow(dead_code)]
579impl AlarmCollector {
580    /// Create an empty collector.
581    pub fn new() -> Self {
582        AlarmCollector { alarms: Vec::new() }
583    }
584    /// Add an alarm.
585    pub fn add(&mut self, alarm: AbstractAlarm) {
586        self.alarms.push(alarm);
587    }
588    /// Return all alarms.
589    pub fn alarms(&self) -> &[AbstractAlarm] {
590        &self.alarms
591    }
592    /// Return all error-level alarms.
593    pub fn errors(&self) -> Vec<&AbstractAlarm> {
594        self.alarms.iter().filter(|a| a.is_error()).collect()
595    }
596    /// Return whether there are any errors.
597    pub fn has_errors(&self) -> bool {
598        self.alarms.iter().any(|a| a.is_error())
599    }
600    /// Return the total alarm count.
601    pub fn len(&self) -> usize {
602        self.alarms.len()
603    }
604    /// Return whether no alarms were collected.
605    pub fn is_empty(&self) -> bool {
606        self.alarms.is_empty()
607    }
608    /// Clear all alarms.
609    pub fn clear(&mut self) {
610        self.alarms.clear();
611    }
612    /// Return the count of alarms at each severity.
613    pub fn count_by_severity(&self) -> (usize, usize, usize) {
614        let info = self
615            .alarms
616            .iter()
617            .filter(|a| a.severity == AlarmSeverity::Info)
618            .count();
619        let warn = self
620            .alarms
621            .iter()
622            .filter(|a| a.severity == AlarmSeverity::Warning)
623            .count();
624        let err = self
625            .alarms
626            .iter()
627            .filter(|a| a.severity == AlarmSeverity::Error)
628            .count();
629        (info, warn, err)
630    }
631}
632/// A powerset domain over a finite set of tags.
633#[allow(dead_code)]
634pub struct PowersetDomain {
635    elements: u64,
636    universe_size: u8,
637}
638#[allow(dead_code)]
639impl PowersetDomain {
640    /// Create an empty set (bottom).
641    pub fn bottom(universe_size: u8) -> Self {
642        PowersetDomain {
643            elements: 0,
644            universe_size,
645        }
646    }
647    /// Create the full set (top).
648    pub fn top(universe_size: u8) -> Self {
649        let mask = if universe_size >= 64 {
650            u64::MAX
651        } else {
652            (1u64 << universe_size) - 1
653        };
654        PowersetDomain {
655            elements: mask,
656            universe_size,
657        }
658    }
659    /// Add an element by index.
660    pub fn add(&mut self, idx: u8) {
661        if idx < self.universe_size {
662            self.elements |= 1 << idx;
663        }
664    }
665    /// Remove an element by index.
666    pub fn remove(&mut self, idx: u8) {
667        self.elements &= !(1 << idx);
668    }
669    /// Return whether an element is present.
670    pub fn contains(&self, idx: u8) -> bool {
671        (self.elements >> idx) & 1 != 0
672    }
673    /// Return the join (union).
674    pub fn join(&self, other: &PowersetDomain) -> PowersetDomain {
675        PowersetDomain {
676            elements: self.elements | other.elements,
677            universe_size: self.universe_size,
678        }
679    }
680    /// Return the meet (intersection).
681    pub fn meet(&self, other: &PowersetDomain) -> PowersetDomain {
682        PowersetDomain {
683            elements: self.elements & other.elements,
684            universe_size: self.universe_size,
685        }
686    }
687    /// Return the number of elements.
688    pub fn count(&self) -> u32 {
689        self.elements.count_ones()
690    }
691    /// Return whether the set is empty (bottom).
692    pub fn is_bottom(&self) -> bool {
693        self.elements == 0
694    }
695    /// Return whether the set is full (top).
696    pub fn is_top(&self) -> bool {
697        let mask = if self.universe_size >= 64 {
698            u64::MAX
699        } else {
700            (1u64 << self.universe_size) - 1
701        };
702        self.elements == mask
703    }
704}
705/// A trace of abstract values at program points.
706#[allow(dead_code)]
707pub struct AbstractTrace {
708    points: Vec<(String, Interval)>,
709}
710#[allow(dead_code)]
711impl AbstractTrace {
712    /// Create an empty trace.
713    pub fn new() -> Self {
714        AbstractTrace { points: Vec::new() }
715    }
716    /// Record an abstract value at a program point.
717    pub fn record(&mut self, label: &str, iv: Interval) {
718        self.points.push((label.to_string(), iv));
719    }
720    /// Return the interval at the given label (first match).
721    pub fn at(&self, label: &str) -> Option<Interval> {
722        self.points
723            .iter()
724            .find(|(l, _)| l == label)
725            .map(|(_, iv)| *iv)
726    }
727    /// Return all trace points.
728    pub fn all(&self) -> &[(String, Interval)] {
729        &self.points
730    }
731    /// Return the number of trace points.
732    pub fn len(&self) -> usize {
733        self.points.len()
734    }
735    /// Return whether the trace is empty.
736    pub fn is_empty(&self) -> bool {
737        self.points.is_empty()
738    }
739    /// Format the trace as a readable string.
740    pub fn format(&self) -> String {
741        self.points
742            .iter()
743            .map(|(l, iv)| format!("{}: [{}, {}]", l, iv.lo, iv.hi))
744            .collect::<Vec<_>>()
745            .join("; ")
746    }
747}
748/// Abstract cost model: estimated number of reduction steps.
749#[allow(dead_code)]
750#[derive(Debug, Clone, Copy)]
751#[allow(missing_docs)]
752pub struct CostBound {
753    pub lower: u64,
754    pub upper: Option<u64>,
755}
756#[allow(dead_code)]
757impl CostBound {
758    /// Create a tight bound.
759    pub fn exact(n: u64) -> Self {
760        CostBound {
761            lower: n,
762            upper: Some(n),
763        }
764    }
765    /// Create an open-ended bound.
766    pub fn at_least(n: u64) -> Self {
767        CostBound {
768            lower: n,
769            upper: None,
770        }
771    }
772    /// Create a range bound.
773    pub fn range(lo: u64, hi: u64) -> Self {
774        CostBound {
775            lower: lo,
776            upper: Some(hi),
777        }
778    }
779    /// Return whether the cost is bounded.
780    pub fn is_bounded(&self) -> bool {
781        self.upper.is_some()
782    }
783    /// Return the cost width (hi - lo), or None if unbounded.
784    pub fn width(&self) -> Option<u64> {
785        self.upper.map(|h| h - self.lower)
786    }
787    /// Add two cost bounds.
788    pub fn add(&self, other: &CostBound) -> CostBound {
789        CostBound {
790            lower: self.lower.saturating_add(other.lower),
791            upper: match (self.upper, other.upper) {
792                (Some(a), Some(b)) => Some(a.saturating_add(b)),
793                _ => None,
794            },
795        }
796    }
797}
798/// The congruence abstract domain: values congruent to r mod m.
799#[allow(dead_code)]
800#[derive(Debug, Clone, Copy, PartialEq, Eq)]
801#[allow(missing_docs)]
802pub struct CongruenceDomain {
803    pub modulus: u64,
804    pub residue: u64,
805}
806#[allow(dead_code)]
807impl CongruenceDomain {
808    /// Create a singleton value (mod=1, res=0 is "any").
809    pub fn singleton(v: u64) -> Self {
810        CongruenceDomain {
811            modulus: 0,
812            residue: v,
813        }
814    }
815    /// Create "all values congruent to r mod m".
816    pub fn congruent(modulus: u64, residue: u64) -> Self {
817        let r = if modulus == 0 { 0 } else { residue % modulus };
818        CongruenceDomain {
819            modulus,
820            residue: r,
821        }
822    }
823    /// Create the top element (any value).
824    pub fn top() -> Self {
825        CongruenceDomain {
826            modulus: 1,
827            residue: 0,
828        }
829    }
830    /// Create the bottom element (no values).
831    pub fn bottom() -> Self {
832        CongruenceDomain {
833            modulus: 0,
834            residue: u64::MAX,
835        }
836    }
837    /// Return whether this is top (everything).
838    pub fn is_top(&self) -> bool {
839        self.modulus == 1
840    }
841    /// Return whether this is bottom (nothing).
842    pub fn is_bottom(&self) -> bool {
843        self.modulus == 0 && self.residue == u64::MAX
844    }
845    /// Return whether a value satisfies this congruence.
846    pub fn satisfies(&self, v: u64) -> bool {
847        if self.is_bottom() {
848            return false;
849        }
850        if self.is_top() {
851            return true;
852        }
853        if self.modulus == 0 {
854            return v == self.residue;
855        }
856        v % self.modulus == self.residue
857    }
858    /// Join (GCD-based).
859    pub fn join(&self, other: &CongruenceDomain) -> CongruenceDomain {
860        if self.is_bottom() {
861            return *other;
862        }
863        if other.is_bottom() {
864            return *self;
865        }
866        if self.is_top() || other.is_top() {
867            return CongruenceDomain::top();
868        }
869        if self.modulus == other.modulus && self.residue == other.residue {
870            return *self;
871        }
872        CongruenceDomain::top()
873    }
874}
875/// The three-valued logic domain (maybe, true, false).
876#[allow(dead_code)]
877#[derive(Debug, Clone, Copy, PartialEq, Eq)]
878#[allow(missing_docs)]
879pub enum TrileanDomain {
880    Bottom,
881    False,
882    True,
883    Top,
884}
885#[allow(dead_code)]
886impl TrileanDomain {
887    /// Create from a concrete bool.
888    pub fn from_bool(b: bool) -> Self {
889        if b {
890            TrileanDomain::True
891        } else {
892            TrileanDomain::False
893        }
894    }
895    /// Logical AND of two trileans.
896    pub fn and(&self, other: &TrileanDomain) -> TrileanDomain {
897        use TrileanDomain::*;
898        match (self, other) {
899            (Bottom, _) | (_, Bottom) => Bottom,
900            (False, _) | (_, False) => False,
901            (True, True) => True,
902            _ => Top,
903        }
904    }
905    /// Logical OR of two trileans.
906    pub fn or(&self, other: &TrileanDomain) -> TrileanDomain {
907        use TrileanDomain::*;
908        match (self, other) {
909            (Bottom, _) | (_, Bottom) => Bottom,
910            (True, _) | (_, True) => True,
911            (False, False) => False,
912            _ => Top,
913        }
914    }
915    /// Logical NOT.
916    pub fn not(&self) -> TrileanDomain {
917        use TrileanDomain::*;
918        match self {
919            Bottom => Bottom,
920            False => True,
921            True => False,
922            Top => Top,
923        }
924    }
925    /// Join two trileans.
926    pub fn join(&self, other: &TrileanDomain) -> TrileanDomain {
927        use TrileanDomain::*;
928        match (self, other) {
929            (Bottom, x) | (x, Bottom) => *x,
930            (True, True) => True,
931            (False, False) => False,
932            _ => Top,
933        }
934    }
935    /// Return whether this is definitely true.
936    pub fn is_definitely_true(&self) -> bool {
937        *self == TrileanDomain::True
938    }
939    /// Return whether this might be true.
940    pub fn may_be_true(&self) -> bool {
941        matches!(self, TrileanDomain::True | TrileanDomain::Top)
942    }
943}
944#[allow(dead_code)]
945#[derive(Debug, Clone)]
946#[allow(missing_docs)]
947pub enum TransferEffect {
948    /// Assign a variable to an interval.
949    Assign { var: String, interval: Interval },
950    /// Constrain a variable to a non-bottom interval.
951    Constrain { var: String, constraint: Interval },
952    /// Invalidate (set to top) a variable.
953    Invalidate { var: String },
954}
955/// Abstract sign domain for numeric expressions.
956#[derive(Debug, Clone, Copy, PartialEq, Eq)]
957pub enum SignDomain {
958    /// No information (least element).
959    Bottom,
960    /// Strictly negative.
961    Neg,
962    /// Exactly zero.
963    Zero,
964    /// Strictly positive.
965    Pos,
966    /// Non-zero (either negative or positive).
967    Nonzero,
968    /// Non-negative (zero or positive).
969    NonNeg,
970    /// Non-positive (zero or negative).
971    NonPos,
972    /// All values (greatest element).
973    Top,
974}
975#[allow(clippy::should_implement_trait)]
976impl SignDomain {
977    /// Negate a sign value.
978    pub fn negate(&self) -> SignDomain {
979        use SignDomain::*;
980        match self {
981            Bottom => Bottom,
982            Neg => Pos,
983            Zero => Zero,
984            Pos => Neg,
985            Nonzero => Nonzero,
986            NonNeg => NonPos,
987            NonPos => NonNeg,
988            Top => Top,
989        }
990    }
991    /// Abstract addition of two sign values.
992    pub fn add(s1: SignDomain, s2: SignDomain) -> SignDomain {
993        use SignDomain::*;
994        match (s1, s2) {
995            (Bottom, _) | (_, Bottom) => Bottom,
996            (Zero, x) | (x, Zero) => x,
997            (Pos, Pos) => Pos,
998            (Neg, Neg) => Neg,
999            (Pos, NonNeg) | (NonNeg, Pos) => Pos,
1000            (Neg, NonPos) | (NonPos, Neg) => Neg,
1001            (NonNeg, NonNeg) => NonNeg,
1002            (NonPos, NonPos) => NonPos,
1003            _ => Top,
1004        }
1005    }
1006    /// Abstract multiplication of two sign values.
1007    pub fn mul(s1: SignDomain, s2: SignDomain) -> SignDomain {
1008        use SignDomain::*;
1009        match (s1, s2) {
1010            (Bottom, _) | (_, Bottom) => Bottom,
1011            (Zero, _) | (_, Zero) => Zero,
1012            (Top, _) | (_, Top) => Top,
1013            (Pos, Pos) | (Neg, Neg) => Pos,
1014            (Pos, Neg) | (Neg, Pos) => Neg,
1015            (NonNeg, NonNeg) => NonNeg,
1016            (NonPos, NonPos) => NonNeg,
1017            (NonNeg, NonPos) | (NonPos, NonNeg) => NonPos,
1018            (Pos, NonNeg) | (NonNeg, Pos) => NonNeg,
1019            (Neg, NonPos) | (NonPos, Neg) => NonNeg,
1020            (Pos, NonPos) | (NonPos, Pos) => NonPos,
1021            (Neg, NonNeg) | (NonNeg, Neg) => NonPos,
1022            _ => Top,
1023        }
1024    }
1025}
1026/// A stub abstract interpreter that runs for a fixed number of steps.
1027#[allow(dead_code)]
1028pub struct SimpleAbstractInterpreter {
1029    config: AnalysisConfig,
1030    results: AnalysisResults,
1031    alarms: AlarmCollector,
1032}
1033#[allow(dead_code)]
1034impl SimpleAbstractInterpreter {
1035    /// Create a new interpreter with the given config.
1036    pub fn new(config: AnalysisConfig) -> Self {
1037        SimpleAbstractInterpreter {
1038            config,
1039            results: AnalysisResults::new(),
1040            alarms: AlarmCollector::new(),
1041        }
1042    }
1043    /// Set an initial abstract value for a variable.
1044    pub fn init_var(&mut self, var: &str, value: IntervalParityProduct) {
1045        self.results.set(var, value);
1046    }
1047    /// Return a reference to current results.
1048    pub fn results(&self) -> &AnalysisResults {
1049        &self.results
1050    }
1051    /// Return a reference to collected alarms.
1052    pub fn alarms(&self) -> &AlarmCollector {
1053        &self.alarms
1054    }
1055    /// Return a summary (stub: always converges in 1 iteration).
1056    pub fn run_stub(&mut self) -> InterpretationSummary {
1057        InterpretationSummary::new(1, true, self.alarms.len())
1058    }
1059}
1060/// A fixpoint iteration engine for abstract interpretation.
1061#[allow(dead_code)]
1062pub struct FixpointEngine {
1063    max_iterations: u32,
1064    iterations_done: u32,
1065    widening_threshold: u32,
1066}
1067#[allow(dead_code)]
1068impl FixpointEngine {
1069    /// Create an engine with a given maximum iteration count.
1070    pub fn new(max_iterations: u32) -> Self {
1071        FixpointEngine {
1072            max_iterations,
1073            iterations_done: 0,
1074            widening_threshold: max_iterations / 2,
1075        }
1076    }
1077    /// Return whether fixpoint was reached (next == current).
1078    pub fn is_fixpoint(current: &IntervalEnv, next: &IntervalEnv) -> bool {
1079        current.is_at_least_as_wide(next) && next.is_at_least_as_wide(current)
1080    }
1081    /// Return whether we should apply widening (past the widening threshold).
1082    pub fn should_widen(&self) -> bool {
1083        self.iterations_done >= self.widening_threshold
1084    }
1085    /// Record one iteration step.
1086    pub fn step(&mut self) {
1087        self.iterations_done += 1;
1088    }
1089    /// Return whether we've exceeded the iteration limit.
1090    pub fn is_exhausted(&self) -> bool {
1091        self.iterations_done >= self.max_iterations
1092    }
1093    /// Return the iteration count so far.
1094    pub fn iterations(&self) -> u32 {
1095        self.iterations_done
1096    }
1097    /// Reset to initial state.
1098    pub fn reset(&mut self) {
1099        self.iterations_done = 0;
1100    }
1101}
1102/// The parity abstract domain: even, odd, or top/bottom.
1103#[allow(dead_code)]
1104#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1105#[allow(missing_docs)]
1106pub enum ParityDomain {
1107    Bottom,
1108    Even,
1109    Odd,
1110    Top,
1111}
1112#[allow(dead_code)]
1113impl ParityDomain {
1114    /// Create from a concrete integer.
1115    pub fn from_value(v: i64) -> Self {
1116        if v % 2 == 0 {
1117            ParityDomain::Even
1118        } else {
1119            ParityDomain::Odd
1120        }
1121    }
1122    /// Join two parity values.
1123    pub fn join(&self, other: &ParityDomain) -> ParityDomain {
1124        use ParityDomain::*;
1125        match (self, other) {
1126            (Bottom, x) | (x, Bottom) => *x,
1127            (Even, Even) => Even,
1128            (Odd, Odd) => Odd,
1129            _ => Top,
1130        }
1131    }
1132    /// Meet two parity values.
1133    pub fn meet(&self, other: &ParityDomain) -> ParityDomain {
1134        use ParityDomain::*;
1135        match (self, other) {
1136            (Top, x) | (x, Top) => *x,
1137            (Even, Even) => Even,
1138            (Odd, Odd) => Odd,
1139            _ => Bottom,
1140        }
1141    }
1142    /// Add two parity values.
1143    pub fn add(&self, other: &ParityDomain) -> ParityDomain {
1144        use ParityDomain::*;
1145        match (self, other) {
1146            (Bottom, _) | (_, Bottom) => Bottom,
1147            (Top, _) | (_, Top) => Top,
1148            (Even, x) | (x, Even) => *x,
1149            (Odd, Odd) => Even,
1150        }
1151    }
1152    /// Multiply two parity values.
1153    pub fn mul(&self, other: &ParityDomain) -> ParityDomain {
1154        use ParityDomain::*;
1155        match (self, other) {
1156            (Bottom, _) | (_, Bottom) => Bottom,
1157            (Even, _) | (_, Even) => Even,
1158            (Odd, Odd) => Odd,
1159            _ => Top,
1160        }
1161    }
1162    /// Return whether this is bottom.
1163    pub fn is_bottom(&self) -> bool {
1164        *self == ParityDomain::Bottom
1165    }
1166    /// Return whether this is top.
1167    pub fn is_top(&self) -> bool {
1168        *self == ParityDomain::Top
1169    }
1170}
1171/// A node in the call graph (function definition).
1172#[allow(dead_code)]
1173#[derive(Debug, Clone)]
1174#[allow(missing_docs)]
1175pub struct CallGraphNode {
1176    pub name: String,
1177    pub is_recursive: bool,
1178    pub callees: Vec<String>,
1179}
1180#[allow(dead_code)]
1181impl CallGraphNode {
1182    /// Create a new call graph node.
1183    pub fn new(name: impl Into<String>) -> Self {
1184        CallGraphNode {
1185            name: name.into(),
1186            is_recursive: false,
1187            callees: Vec::new(),
1188        }
1189    }
1190    /// Add a callee.
1191    pub fn add_callee(&mut self, name: &str) {
1192        if name == self.name.as_str() {
1193            self.is_recursive = true;
1194        }
1195        if !self.callees.iter().any(|s| s == name) {
1196            self.callees.push(name.to_string());
1197        }
1198    }
1199    /// Return whether this node calls `name`.
1200    pub fn calls(&self, name: &str) -> bool {
1201        self.callees.iter().any(|s| s == name)
1202    }
1203}
1204/// A product abstract domain combining two domains.
1205#[allow(dead_code)]
1206#[allow(missing_docs)]
1207#[derive(Debug, Clone, Copy)]
1208pub struct ProductDomain<A: Copy, B: Copy> {
1209    pub first: A,
1210    pub second: B,
1211}
1212#[allow(dead_code)]
1213impl<A: Copy, B: Copy> ProductDomain<A, B> {
1214    /// Create a product domain.
1215    pub fn new(first: A, second: B) -> Self {
1216        ProductDomain { first, second }
1217    }
1218}
1219/// A simple chaotic iteration fixpoint solver.
1220#[allow(dead_code)]
1221pub struct ChaoticIterator {
1222    max_steps: u32,
1223    current_step: u32,
1224    converged: bool,
1225}
1226#[allow(dead_code)]
1227impl ChaoticIterator {
1228    /// Create an iterator with a step limit.
1229    pub fn new(max_steps: u32) -> Self {
1230        ChaoticIterator {
1231            max_steps,
1232            current_step: 0,
1233            converged: false,
1234        }
1235    }
1236    /// Mark as converged.
1237    pub fn mark_converged(&mut self) {
1238        self.converged = true;
1239    }
1240    /// Advance one step. Returns false if limit exceeded.
1241    pub fn advance(&mut self) -> bool {
1242        if self.current_step >= self.max_steps {
1243            return false;
1244        }
1245        self.current_step += 1;
1246        true
1247    }
1248    /// Return whether the iterator has converged.
1249    pub fn is_converged(&self) -> bool {
1250        self.converged
1251    }
1252    /// Return whether the step limit was exceeded without convergence.
1253    pub fn is_limit_exceeded(&self) -> bool {
1254        !self.converged && self.current_step >= self.max_steps
1255    }
1256    /// Return the number of steps taken.
1257    pub fn steps(&self) -> u32 {
1258        self.current_step
1259    }
1260    /// Reset to initial state.
1261    pub fn reset(&mut self) {
1262        self.current_step = 0;
1263        self.converged = false;
1264    }
1265}
1266/// A call graph for program analysis.
1267#[allow(dead_code)]
1268pub struct CallGraph {
1269    nodes: Vec<CallGraphNode>,
1270}
1271#[allow(dead_code)]
1272impl CallGraph {
1273    /// Create an empty call graph.
1274    pub fn new() -> Self {
1275        CallGraph { nodes: Vec::new() }
1276    }
1277    /// Add a node.
1278    pub fn add_node(&mut self, node: CallGraphNode) {
1279        self.nodes.push(node);
1280    }
1281    /// Look up a node by name.
1282    pub fn find(&self, name: &str) -> Option<&CallGraphNode> {
1283        self.nodes.iter().find(|n| n.name == name)
1284    }
1285    /// Return all recursive functions.
1286    pub fn recursive_fns(&self) -> Vec<&str> {
1287        self.nodes
1288            .iter()
1289            .filter(|n| n.is_recursive)
1290            .map(|n| n.name.as_str())
1291            .collect()
1292    }
1293    /// Return callers of a given function.
1294    pub fn callers_of(&self, name: &str) -> Vec<&str> {
1295        self.nodes
1296            .iter()
1297            .filter(|n| n.calls(name))
1298            .map(|n| n.name.as_str())
1299            .collect()
1300    }
1301    /// Return the number of nodes.
1302    pub fn len(&self) -> usize {
1303        self.nodes.len()
1304    }
1305    /// Return whether the graph is empty.
1306    pub fn is_empty(&self) -> bool {
1307        self.nodes.is_empty()
1308    }
1309    /// Return all function names.
1310    pub fn function_names(&self) -> Vec<&str> {
1311        self.nodes.iter().map(|n| n.name.as_str()).collect()
1312    }
1313}
1314/// Abstract termination evidence: witness for why a function terminates.
1315#[allow(dead_code)]
1316#[derive(Debug, Clone)]
1317#[allow(missing_docs)]
1318pub enum TerminationEvidence {
1319    /// Structural recursion on argument `idx`.
1320    Structural { arg_index: u32 },
1321    /// Lexicographic tuple ordering.
1322    Lexicographic { measures: Vec<String> },
1323    /// Unknown; may not terminate.
1324    Unknown,
1325}
1326#[allow(dead_code)]
1327impl TerminationEvidence {
1328    /// Return whether termination is proven.
1329    pub fn is_proven(&self) -> bool {
1330        !matches!(self, TerminationEvidence::Unknown)
1331    }
1332    /// Format a description.
1333    pub fn describe(&self) -> String {
1334        match self {
1335            TerminationEvidence::Structural { arg_index } => {
1336                format!("structural on arg #{}", arg_index)
1337            }
1338            TerminationEvidence::Lexicographic { measures } => {
1339                format!("lexicographic on [{}]", measures.join(", "))
1340            }
1341            TerminationEvidence::Unknown => "unknown".to_string(),
1342        }
1343    }
1344}
1345/// An abstract environment mapping variable names to interval values.
1346#[allow(dead_code)]
1347pub struct IntervalEnv {
1348    pub(super) bindings: Vec<(String, Interval)>,
1349}
1350#[allow(dead_code)]
1351impl IntervalEnv {
1352    /// Create an empty environment.
1353    pub fn new() -> Self {
1354        IntervalEnv {
1355            bindings: Vec::new(),
1356        }
1357    }
1358    /// Bind a variable to an interval.
1359    pub fn set(&mut self, name: &str, iv: Interval) {
1360        if let Some(b) = self.bindings.iter_mut().find(|(n, _)| n == name) {
1361            b.1 = iv;
1362        } else {
1363            self.bindings.push((name.to_string(), iv));
1364        }
1365    }
1366    /// Look up a variable's interval (returns Top if not found).
1367    pub fn get(&self, name: &str) -> Interval {
1368        self.bindings
1369            .iter()
1370            .find(|(n, _)| n == name)
1371            .map(|(_, iv)| *iv)
1372            .unwrap_or(Interval::top())
1373    }
1374    /// Join two environments point-wise.
1375    pub fn join(&self, other: &IntervalEnv) -> IntervalEnv {
1376        let mut result = IntervalEnv::new();
1377        for (name, iv) in &self.bindings {
1378            let iv2 = other.get(name);
1379            result.set(name, iv.join(&iv2));
1380        }
1381        for (name, iv) in &other.bindings {
1382            if self.bindings.iter().all(|(n, _)| n != name) {
1383                result.set(name, *iv);
1384            }
1385        }
1386        result
1387    }
1388    /// Return whether this environment is "wider" than or equal to `other`.
1389    pub fn is_at_least_as_wide(&self, other: &IntervalEnv) -> bool {
1390        other.bindings.iter().all(|(name, iv2)| {
1391            let iv1 = self.get(name);
1392            iv1.lo <= iv2.lo && iv1.hi >= iv2.hi
1393        })
1394    }
1395    /// Return the number of variables bound.
1396    pub fn len(&self) -> usize {
1397        self.bindings.len()
1398    }
1399    /// Return whether the environment is empty.
1400    pub fn is_empty(&self) -> bool {
1401        self.bindings.is_empty()
1402    }
1403    /// Return all variable names.
1404    pub fn names(&self) -> Vec<&str> {
1405        self.bindings.iter().map(|(n, _)| n.as_str()).collect()
1406    }
1407}
1408/// Abstract domain for expression size analysis.
1409#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1410pub enum SizeDomain {
1411    /// No information.
1412    Bottom,
1413    /// Exactly zero nodes.
1414    Zero,
1415    /// A small, known number of nodes.
1416    Small(usize),
1417    /// Large but finite (unknown exact count).
1418    Large,
1419    /// Unknown or unbounded size.
1420    Top,
1421}
1422#[allow(clippy::should_implement_trait)]
1423impl SizeDomain {
1424    /// Construct from a concrete node count.
1425    pub fn from_count(n: usize) -> Self {
1426        const SMALL_THRESHOLD: usize = 100;
1427        if n == 0 {
1428            SizeDomain::Zero
1429        } else if n <= SMALL_THRESHOLD {
1430            SizeDomain::Small(n)
1431        } else {
1432            SizeDomain::Large
1433        }
1434    }
1435    /// Abstract addition of two sizes.
1436    pub fn add(a: SizeDomain, b: SizeDomain) -> SizeDomain {
1437        use SizeDomain::*;
1438        match (a, b) {
1439            (Bottom, _) | (_, Bottom) => Bottom,
1440            (Top, _) | (_, Top) => Top,
1441            (Zero, x) | (x, Zero) => x,
1442            (Small(m), Small(n)) => SizeDomain::from_count(m + n),
1443            (Large, _) | (_, Large) => Large,
1444        }
1445    }
1446    /// Abstract maximum of two sizes.
1447    pub fn max(a: SizeDomain, b: SizeDomain) -> SizeDomain {
1448        use SizeDomain::*;
1449        match (a, b) {
1450            (Bottom, x) | (x, Bottom) => x,
1451            (Top, _) | (_, Top) => Top,
1452            (Zero, x) | (x, Zero) => x,
1453            (Small(m), Small(n)) => Small(m.max(n)),
1454            (Large, _) | (_, Large) => Large,
1455        }
1456    }
1457}
1458/// A summary of an abstract interpretation run.
1459#[allow(dead_code)]
1460#[derive(Debug, Clone)]
1461#[allow(missing_docs)]
1462pub struct InterpretationSummary {
1463    pub iterations: u32,
1464    pub converged: bool,
1465    pub alarm_count: usize,
1466    pub proven_safe: bool,
1467}
1468#[allow(dead_code)]
1469impl InterpretationSummary {
1470    /// Create a new summary.
1471    pub fn new(iterations: u32, converged: bool, alarm_count: usize) -> Self {
1472        InterpretationSummary {
1473            iterations,
1474            converged,
1475            alarm_count,
1476            proven_safe: converged && alarm_count == 0,
1477        }
1478    }
1479    /// Format a brief description.
1480    pub fn describe(&self) -> String {
1481        format!(
1482            "iters={} converged={} alarms={} safe={}",
1483            self.iterations, self.converged, self.alarm_count, self.proven_safe
1484        )
1485    }
1486}
1487/// A transfer function maps an abstract state at a point to the next point.
1488#[allow(dead_code)]
1489#[allow(missing_docs)]
1490pub struct TransferFunction {
1491    pub name: String,
1492    pub effects: Vec<TransferEffect>,
1493}
1494#[allow(dead_code)]
1495impl TransferFunction {
1496    /// Create an empty transfer function.
1497    pub fn new(name: impl Into<String>) -> Self {
1498        TransferFunction {
1499            name: name.into(),
1500            effects: Vec::new(),
1501        }
1502    }
1503    /// Add an effect.
1504    pub fn add_effect(&mut self, effect: TransferEffect) {
1505        self.effects.push(effect);
1506    }
1507    /// Apply the transfer function to an environment.
1508    pub fn apply(&self, env: &IntervalEnv) -> IntervalEnv {
1509        let result_bindings = env.bindings.clone();
1510        let mut result = IntervalEnv {
1511            bindings: result_bindings,
1512        };
1513        for effect in &self.effects {
1514            match effect {
1515                TransferEffect::Assign { var, interval } => {
1516                    result.set(var, *interval);
1517                }
1518                TransferEffect::Constrain { var, constraint } => {
1519                    let current = result.get(var);
1520                    let narrowed = current.meet(constraint);
1521                    result.set(var, narrowed);
1522                }
1523                TransferEffect::Invalidate { var } => {
1524                    result.set(var, Interval::top());
1525                }
1526            }
1527        }
1528        result
1529    }
1530    /// Return the number of effects.
1531    pub fn effect_count(&self) -> usize {
1532        self.effects.len()
1533    }
1534}
1535/// A function summary: pre/post conditions as interval environments.
1536#[allow(dead_code)]
1537#[allow(missing_docs)]
1538pub struct FunctionSummary {
1539    pub function_name: String,
1540    pub precondition: IntervalEnv,
1541    pub postcondition: IntervalEnv,
1542    pub termination: TerminationEvidence,
1543    pub cost: CostBound,
1544}
1545#[allow(dead_code)]
1546impl FunctionSummary {
1547    /// Create a new summary.
1548    pub fn new(name: impl Into<String>) -> Self {
1549        FunctionSummary {
1550            function_name: name.into(),
1551            precondition: IntervalEnv::new(),
1552            postcondition: IntervalEnv::new(),
1553            termination: TerminationEvidence::Unknown,
1554            cost: CostBound::at_least(0),
1555        }
1556    }
1557    /// Return whether the function is proven to terminate.
1558    pub fn terminates(&self) -> bool {
1559        self.termination.is_proven()
1560    }
1561    /// Return a description of the summary.
1562    pub fn describe(&self) -> String {
1563        format!(
1564            "fn {}: terminates={} cost=[{}, {:?}]",
1565            self.function_name,
1566            self.terminates(),
1567            self.cost.lower,
1568            self.cost.upper
1569        )
1570    }
1571}
1572/// The nullness abstract domain: null, non-null, or top.
1573#[allow(dead_code)]
1574#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1575#[allow(missing_docs)]
1576pub enum NullnessDomain {
1577    Bottom,
1578    Null,
1579    NonNull,
1580    Top,
1581}
1582#[allow(dead_code)]
1583impl NullnessDomain {
1584    /// Create from a bool (true = non-null, false = null).
1585    pub fn from_bool(non_null: bool) -> Self {
1586        if non_null {
1587            NullnessDomain::NonNull
1588        } else {
1589            NullnessDomain::Null
1590        }
1591    }
1592    /// Join two values.
1593    pub fn join(&self, other: &NullnessDomain) -> NullnessDomain {
1594        use NullnessDomain::*;
1595        match (self, other) {
1596            (Bottom, x) | (x, Bottom) => *x,
1597            (Null, Null) => Null,
1598            (NonNull, NonNull) => NonNull,
1599            _ => Top,
1600        }
1601    }
1602    /// Return whether this might be null.
1603    pub fn may_be_null(&self) -> bool {
1604        matches!(self, NullnessDomain::Null | NullnessDomain::Top)
1605    }
1606    /// Return whether this is definitely non-null.
1607    pub fn is_definitely_non_null(&self) -> bool {
1608        *self == NullnessDomain::NonNull
1609    }
1610}