1use super::functions::AbstractDomain;
6
7#[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 pub fn new(interval: Interval, parity: ParityDomain) -> Self {
19 IntervalParityProduct { interval, parity }
20 }
21 pub fn from_value(v: i64) -> Self {
23 IntervalParityProduct {
24 interval: Interval::new(v, v),
25 parity: ParityDomain::from_value(v),
26 }
27 }
28 pub fn top() -> Self {
30 IntervalParityProduct {
31 interval: Interval::top(),
32 parity: ParityDomain::Top,
33 }
34 }
35 pub fn bottom() -> Self {
37 IntervalParityProduct {
38 interval: Interval::bottom(),
39 parity: ParityDomain::Bottom,
40 }
41 }
42 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 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 pub fn is_bottom(&self) -> bool {
58 self.interval.is_bottom() || self.parity.is_bottom()
59 }
60}
61#[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 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 pub fn is_error(&self) -> bool {
82 self.severity == AlarmSeverity::Error
83 }
84}
85#[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 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 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#[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 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 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 pub fn is_definitely_true(&self) -> bool {
167 *self == AbstractCmp::DefinitelyTrue
168 }
169 pub fn is_definitely_false(&self) -> bool {
171 *self == AbstractCmp::DefinitelyFalse
172 }
173}
174#[allow(dead_code)]
176#[allow(missing_docs)]
177pub struct AnalysisResults {
178 entries: Vec<(String, IntervalParityProduct)>,
179}
180#[allow(dead_code)]
181impl AnalysisResults {
182 pub fn new() -> Self {
184 AnalysisResults {
185 entries: Vec::new(),
186 }
187 }
188 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 pub fn get(&self, var: &str) -> Option<IntervalParityProduct> {
198 self.entries.iter().find(|(v, _)| v == var).map(|(_, r)| *r)
199 }
200 pub fn all(&self) -> &[(String, IntervalParityProduct)] {
202 &self.entries
203 }
204 pub fn len(&self) -> usize {
206 self.entries.len()
207 }
208 pub fn is_empty(&self) -> bool {
210 self.entries.is_empty()
211 }
212 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 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
231pub struct DepthDomain {
232 pub max_depth: usize,
234 pub current_depth: usize,
236}
237impl DepthDomain {
238 pub fn new(max: usize) -> Self {
240 DepthDomain {
241 max_depth: max,
242 current_depth: 0,
243 }
244 }
245 pub fn is_bounded(&self) -> bool {
247 self.current_depth <= self.max_depth
248 }
249 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 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#[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 pub fn new(lo: i64, hi: i64) -> Self {
276 Interval {
277 lo: lo.min(hi),
278 hi: lo.max(hi),
279 }
280 }
281 pub fn top() -> Self {
283 Interval {
284 lo: i64::MIN,
285 hi: i64::MAX,
286 }
287 }
288 pub fn bottom() -> Self {
290 Interval {
291 lo: i64::MAX,
292 hi: i64::MIN,
293 }
294 }
295 pub fn is_bottom(&self) -> bool {
297 self.lo > self.hi
298 }
299 pub fn is_top(&self) -> bool {
301 self.lo == i64::MIN && self.hi == i64::MAX
302 }
303 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 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 pub fn contains(&self, v: i64) -> bool {
325 !self.is_bottom() && self.lo <= v && v <= self.hi
326 }
327 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 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 pub fn negate(&self) -> Interval {
347 if self.is_bottom() {
348 return Interval::bottom();
349 }
350 Interval::new(-self.hi, -self.lo)
351 }
352 pub fn sub(&self, other: &Interval) -> Interval {
354 self.add(&other.negate())
355 }
356}
357pub struct AbstractInterpreter {
359 max_depth: usize,
360}
361impl AbstractInterpreter {
362 pub fn new(max_depth: usize) -> Self {
364 AbstractInterpreter { max_depth }
365 }
366 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 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 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 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(¤t);
428 if next == current {
429 return current;
430 }
431 current = next;
432 }
433 current
434 }
435}
436#[allow(dead_code)]
438pub struct ReachabilityAnalysis {
439 reachable: Vec<String>,
440 unreachable: Vec<String>,
441}
442#[allow(dead_code)]
443impl ReachabilityAnalysis {
444 pub fn new() -> Self {
446 ReachabilityAnalysis {
447 reachable: Vec::new(),
448 unreachable: Vec::new(),
449 }
450 }
451 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 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 pub fn is_reachable(&self, label: &str) -> bool {
467 self.reachable.contains(&label.to_string())
468 }
469 pub fn is_unreachable(&self, label: &str) -> bool {
471 self.unreachable.contains(&label.to_string())
472 }
473 pub fn reachable_count(&self) -> usize {
475 self.reachable.len()
476 }
477 pub fn unreachable_count(&self) -> usize {
479 self.unreachable.len()
480 }
481}
482#[derive(Debug, Clone)]
484pub struct AbstractState {
485 pub sign: SignDomain,
487 pub depth: DepthDomain,
489 pub size: SizeDomain,
491}
492impl AbstractState {
493 pub fn new() -> Self {
495 AbstractState {
496 sign: SignDomain::Bottom,
497 depth: DepthDomain::new(1024),
498 size: SizeDomain::Zero,
499 }
500 }
501 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#[allow(dead_code)]
512pub struct SummaryDatabase {
513 entries: Vec<FunctionSummary>,
514}
515#[allow(dead_code)]
516impl SummaryDatabase {
517 pub fn new() -> Self {
519 SummaryDatabase {
520 entries: Vec::new(),
521 }
522 }
523 pub fn add(&mut self, summary: FunctionSummary) {
525 self.entries.push(summary);
526 }
527 pub fn find(&self, name: &str) -> Option<&FunctionSummary> {
529 self.entries.iter().find(|s| s.function_name == name)
530 }
531 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 pub fn len(&self) -> usize {
541 self.entries.len()
542 }
543 pub fn is_empty(&self) -> bool {
545 self.entries.is_empty()
546 }
547}
548#[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 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 pub fn may_be_reachable(&self) -> bool {
570 !matches!(self, BlockReachability::Unreachable)
571 }
572}
573#[allow(dead_code)]
575pub struct AlarmCollector {
576 alarms: Vec<AbstractAlarm>,
577}
578#[allow(dead_code)]
579impl AlarmCollector {
580 pub fn new() -> Self {
582 AlarmCollector { alarms: Vec::new() }
583 }
584 pub fn add(&mut self, alarm: AbstractAlarm) {
586 self.alarms.push(alarm);
587 }
588 pub fn alarms(&self) -> &[AbstractAlarm] {
590 &self.alarms
591 }
592 pub fn errors(&self) -> Vec<&AbstractAlarm> {
594 self.alarms.iter().filter(|a| a.is_error()).collect()
595 }
596 pub fn has_errors(&self) -> bool {
598 self.alarms.iter().any(|a| a.is_error())
599 }
600 pub fn len(&self) -> usize {
602 self.alarms.len()
603 }
604 pub fn is_empty(&self) -> bool {
606 self.alarms.is_empty()
607 }
608 pub fn clear(&mut self) {
610 self.alarms.clear();
611 }
612 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#[allow(dead_code)]
634pub struct PowersetDomain {
635 elements: u64,
636 universe_size: u8,
637}
638#[allow(dead_code)]
639impl PowersetDomain {
640 pub fn bottom(universe_size: u8) -> Self {
642 PowersetDomain {
643 elements: 0,
644 universe_size,
645 }
646 }
647 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 pub fn add(&mut self, idx: u8) {
661 if idx < self.universe_size {
662 self.elements |= 1 << idx;
663 }
664 }
665 pub fn remove(&mut self, idx: u8) {
667 self.elements &= !(1 << idx);
668 }
669 pub fn contains(&self, idx: u8) -> bool {
671 (self.elements >> idx) & 1 != 0
672 }
673 pub fn join(&self, other: &PowersetDomain) -> PowersetDomain {
675 PowersetDomain {
676 elements: self.elements | other.elements,
677 universe_size: self.universe_size,
678 }
679 }
680 pub fn meet(&self, other: &PowersetDomain) -> PowersetDomain {
682 PowersetDomain {
683 elements: self.elements & other.elements,
684 universe_size: self.universe_size,
685 }
686 }
687 pub fn count(&self) -> u32 {
689 self.elements.count_ones()
690 }
691 pub fn is_bottom(&self) -> bool {
693 self.elements == 0
694 }
695 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#[allow(dead_code)]
707pub struct AbstractTrace {
708 points: Vec<(String, Interval)>,
709}
710#[allow(dead_code)]
711impl AbstractTrace {
712 pub fn new() -> Self {
714 AbstractTrace { points: Vec::new() }
715 }
716 pub fn record(&mut self, label: &str, iv: Interval) {
718 self.points.push((label.to_string(), iv));
719 }
720 pub fn at(&self, label: &str) -> Option<Interval> {
722 self.points
723 .iter()
724 .find(|(l, _)| l == label)
725 .map(|(_, iv)| *iv)
726 }
727 pub fn all(&self) -> &[(String, Interval)] {
729 &self.points
730 }
731 pub fn len(&self) -> usize {
733 self.points.len()
734 }
735 pub fn is_empty(&self) -> bool {
737 self.points.is_empty()
738 }
739 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#[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 pub fn exact(n: u64) -> Self {
760 CostBound {
761 lower: n,
762 upper: Some(n),
763 }
764 }
765 pub fn at_least(n: u64) -> Self {
767 CostBound {
768 lower: n,
769 upper: None,
770 }
771 }
772 pub fn range(lo: u64, hi: u64) -> Self {
774 CostBound {
775 lower: lo,
776 upper: Some(hi),
777 }
778 }
779 pub fn is_bounded(&self) -> bool {
781 self.upper.is_some()
782 }
783 pub fn width(&self) -> Option<u64> {
785 self.upper.map(|h| h - self.lower)
786 }
787 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#[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 pub fn singleton(v: u64) -> Self {
810 CongruenceDomain {
811 modulus: 0,
812 residue: v,
813 }
814 }
815 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 pub fn top() -> Self {
825 CongruenceDomain {
826 modulus: 1,
827 residue: 0,
828 }
829 }
830 pub fn bottom() -> Self {
832 CongruenceDomain {
833 modulus: 0,
834 residue: u64::MAX,
835 }
836 }
837 pub fn is_top(&self) -> bool {
839 self.modulus == 1
840 }
841 pub fn is_bottom(&self) -> bool {
843 self.modulus == 0 && self.residue == u64::MAX
844 }
845 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 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#[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 pub fn from_bool(b: bool) -> Self {
889 if b {
890 TrileanDomain::True
891 } else {
892 TrileanDomain::False
893 }
894 }
895 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 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 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 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 pub fn is_definitely_true(&self) -> bool {
937 *self == TrileanDomain::True
938 }
939 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 { var: String, interval: Interval },
950 Constrain { var: String, constraint: Interval },
952 Invalidate { var: String },
954}
955#[derive(Debug, Clone, Copy, PartialEq, Eq)]
957pub enum SignDomain {
958 Bottom,
960 Neg,
962 Zero,
964 Pos,
966 Nonzero,
968 NonNeg,
970 NonPos,
972 Top,
974}
975#[allow(clippy::should_implement_trait)]
976impl SignDomain {
977 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 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 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#[allow(dead_code)]
1028pub struct SimpleAbstractInterpreter {
1029 config: AnalysisConfig,
1030 results: AnalysisResults,
1031 alarms: AlarmCollector,
1032}
1033#[allow(dead_code)]
1034impl SimpleAbstractInterpreter {
1035 pub fn new(config: AnalysisConfig) -> Self {
1037 SimpleAbstractInterpreter {
1038 config,
1039 results: AnalysisResults::new(),
1040 alarms: AlarmCollector::new(),
1041 }
1042 }
1043 pub fn init_var(&mut self, var: &str, value: IntervalParityProduct) {
1045 self.results.set(var, value);
1046 }
1047 pub fn results(&self) -> &AnalysisResults {
1049 &self.results
1050 }
1051 pub fn alarms(&self) -> &AlarmCollector {
1053 &self.alarms
1054 }
1055 pub fn run_stub(&mut self) -> InterpretationSummary {
1057 InterpretationSummary::new(1, true, self.alarms.len())
1058 }
1059}
1060#[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 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 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 pub fn should_widen(&self) -> bool {
1083 self.iterations_done >= self.widening_threshold
1084 }
1085 pub fn step(&mut self) {
1087 self.iterations_done += 1;
1088 }
1089 pub fn is_exhausted(&self) -> bool {
1091 self.iterations_done >= self.max_iterations
1092 }
1093 pub fn iterations(&self) -> u32 {
1095 self.iterations_done
1096 }
1097 pub fn reset(&mut self) {
1099 self.iterations_done = 0;
1100 }
1101}
1102#[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 pub fn from_value(v: i64) -> Self {
1116 if v % 2 == 0 {
1117 ParityDomain::Even
1118 } else {
1119 ParityDomain::Odd
1120 }
1121 }
1122 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 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 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 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 pub fn is_bottom(&self) -> bool {
1164 *self == ParityDomain::Bottom
1165 }
1166 pub fn is_top(&self) -> bool {
1168 *self == ParityDomain::Top
1169 }
1170}
1171#[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 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 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 pub fn calls(&self, name: &str) -> bool {
1201 self.callees.iter().any(|s| s == name)
1202 }
1203}
1204#[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 pub fn new(first: A, second: B) -> Self {
1216 ProductDomain { first, second }
1217 }
1218}
1219#[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 pub fn new(max_steps: u32) -> Self {
1230 ChaoticIterator {
1231 max_steps,
1232 current_step: 0,
1233 converged: false,
1234 }
1235 }
1236 pub fn mark_converged(&mut self) {
1238 self.converged = true;
1239 }
1240 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 pub fn is_converged(&self) -> bool {
1250 self.converged
1251 }
1252 pub fn is_limit_exceeded(&self) -> bool {
1254 !self.converged && self.current_step >= self.max_steps
1255 }
1256 pub fn steps(&self) -> u32 {
1258 self.current_step
1259 }
1260 pub fn reset(&mut self) {
1262 self.current_step = 0;
1263 self.converged = false;
1264 }
1265}
1266#[allow(dead_code)]
1268pub struct CallGraph {
1269 nodes: Vec<CallGraphNode>,
1270}
1271#[allow(dead_code)]
1272impl CallGraph {
1273 pub fn new() -> Self {
1275 CallGraph { nodes: Vec::new() }
1276 }
1277 pub fn add_node(&mut self, node: CallGraphNode) {
1279 self.nodes.push(node);
1280 }
1281 pub fn find(&self, name: &str) -> Option<&CallGraphNode> {
1283 self.nodes.iter().find(|n| n.name == name)
1284 }
1285 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 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 pub fn len(&self) -> usize {
1303 self.nodes.len()
1304 }
1305 pub fn is_empty(&self) -> bool {
1307 self.nodes.is_empty()
1308 }
1309 pub fn function_names(&self) -> Vec<&str> {
1311 self.nodes.iter().map(|n| n.name.as_str()).collect()
1312 }
1313}
1314#[allow(dead_code)]
1316#[derive(Debug, Clone)]
1317#[allow(missing_docs)]
1318pub enum TerminationEvidence {
1319 Structural { arg_index: u32 },
1321 Lexicographic { measures: Vec<String> },
1323 Unknown,
1325}
1326#[allow(dead_code)]
1327impl TerminationEvidence {
1328 pub fn is_proven(&self) -> bool {
1330 !matches!(self, TerminationEvidence::Unknown)
1331 }
1332 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#[allow(dead_code)]
1347pub struct IntervalEnv {
1348 pub(super) bindings: Vec<(String, Interval)>,
1349}
1350#[allow(dead_code)]
1351impl IntervalEnv {
1352 pub fn new() -> Self {
1354 IntervalEnv {
1355 bindings: Vec::new(),
1356 }
1357 }
1358 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 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 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 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 pub fn len(&self) -> usize {
1397 self.bindings.len()
1398 }
1399 pub fn is_empty(&self) -> bool {
1401 self.bindings.is_empty()
1402 }
1403 pub fn names(&self) -> Vec<&str> {
1405 self.bindings.iter().map(|(n, _)| n.as_str()).collect()
1406 }
1407}
1408#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1410pub enum SizeDomain {
1411 Bottom,
1413 Zero,
1415 Small(usize),
1417 Large,
1419 Top,
1421}
1422#[allow(clippy::should_implement_trait)]
1423impl SizeDomain {
1424 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 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 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#[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 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 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#[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 pub fn new(name: impl Into<String>) -> Self {
1498 TransferFunction {
1499 name: name.into(),
1500 effects: Vec::new(),
1501 }
1502 }
1503 pub fn add_effect(&mut self, effect: TransferEffect) {
1505 self.effects.push(effect);
1506 }
1507 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 pub fn effect_count(&self) -> usize {
1532 self.effects.len()
1533 }
1534}
1535#[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 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 pub fn terminates(&self) -> bool {
1559 self.termination.is_proven()
1560 }
1561 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#[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 pub fn from_bool(non_null: bool) -> Self {
1586 if non_null {
1587 NullnessDomain::NonNull
1588 } else {
1589 NullnessDomain::Null
1590 }
1591 }
1592 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 pub fn may_be_null(&self) -> bool {
1604 matches!(self, NullnessDomain::Null | NullnessDomain::Top)
1605 }
1606 pub fn is_definitely_non_null(&self) -> bool {
1608 *self == NullnessDomain::NonNull
1609 }
1610}