1use std::collections::{HashMap, HashSet};
6
7pub struct IntervalWidening;
9impl IntervalWidening {
10 pub fn apply(a: &IntervalDomain, b: &IntervalDomain) -> IntervalDomain {
12 a.widen(b)
13 }
14}
15#[derive(Debug, Clone)]
17pub struct AbstractState {
18 pub vars: HashMap<String, IntervalDomain>,
20}
21impl AbstractState {
22 pub fn bottom() -> Self {
24 Self {
25 vars: HashMap::new(),
26 }
27 }
28 pub fn get(&self, var: &str) -> IntervalDomain {
30 self.vars
31 .get(var)
32 .cloned()
33 .unwrap_or_else(IntervalDomain::top)
34 }
35 pub fn set(&mut self, var: impl Into<String>, val: IntervalDomain) {
37 self.vars.insert(var.into(), val);
38 }
39 pub fn join(&self, other: &Self) -> Self {
41 let mut result = self.clone();
42 for (k, v) in &other.vars {
43 let joined = result.get(k).join(v);
44 result.vars.insert(k.clone(), joined);
45 }
46 result
47 }
48 pub fn widen(&self, other: &Self) -> Self {
50 let mut result = self.clone();
51 for (k, v) in &other.vars {
52 let widened = result.get(k).widen(v);
53 result.vars.insert(k.clone(), widened);
54 }
55 result
56 }
57}
58pub struct PolyhedralDomain {
60 pub n: usize,
62 pub constraints: Vec<(Vec<f64>, f64)>,
64}
65impl PolyhedralDomain {
66 pub fn top(n: usize) -> Self {
68 Self {
69 n,
70 constraints: vec![],
71 }
72 }
73 pub fn add_constraint(&mut self, coeffs: Vec<f64>, rhs: f64) {
75 if coeffs.len() == self.n {
76 self.constraints.push((coeffs, rhs));
77 }
78 }
79 pub fn contains(&self, point: &[f64]) -> bool {
81 if point.len() != self.n {
82 return false;
83 }
84 self.constraints.iter().all(|(coeffs, rhs)| {
85 let sum: f64 = coeffs.iter().zip(point.iter()).map(|(a, x)| a * x).sum();
86 sum <= *rhs
87 })
88 }
89 pub fn num_constraints(&self) -> usize {
91 self.constraints.len()
92 }
93}
94#[derive(Debug, Clone, PartialEq, Eq)]
96pub enum ParityDomain {
97 Bottom,
99 Even,
101 Odd,
103 Top,
105}
106impl ParityDomain {
107 pub fn join(&self, other: &Self) -> Self {
109 use ParityDomain::*;
110 match (self, other) {
111 (Bottom, x) | (x, Bottom) => x.clone(),
112 (Top, _) | (_, Top) => Top,
113 (a, b) if a == b => a.clone(),
114 _ => Top,
115 }
116 }
117 pub fn add(&self, other: &Self) -> Self {
119 use ParityDomain::*;
120 match (self, other) {
121 (Bottom, _) | (_, Bottom) => Bottom,
122 (Top, _) | (_, Top) => Top,
123 (Even, Even) => Even,
124 (Odd, Odd) => Even,
125 (Even, Odd) | (Odd, Even) => Odd,
126 }
127 }
128 pub fn mul(&self, other: &Self) -> Self {
130 use ParityDomain::*;
131 match (self, other) {
132 (Bottom, _) | (_, Bottom) => Bottom,
133 (Even, _) | (_, Even) => Even,
134 (Odd, Odd) => Odd,
135 _ => Top,
136 }
137 }
138}
139#[allow(dead_code)]
145pub struct ProbabilisticAbstractDomain {
146 pub k: usize,
148 pub prob_lower: Vec<f64>,
150 pub prob_upper: Vec<f64>,
152}
153#[allow(dead_code)]
154impl ProbabilisticAbstractDomain {
155 pub fn uniform(k: usize) -> Self {
157 let p = 1.0 / k as f64;
158 Self {
159 k,
160 prob_lower: vec![p; k],
161 prob_upper: vec![p; k],
162 }
163 }
164 pub fn top(k: usize) -> Self {
166 Self {
167 k,
168 prob_lower: vec![0.0; k],
169 prob_upper: vec![1.0; k],
170 }
171 }
172 pub fn dirac(k: usize, i: usize) -> Self {
174 let mut lower = vec![0.0; k];
175 let mut upper = vec![0.0; k];
176 lower[i] = 1.0;
177 upper[i] = 1.0;
178 Self {
179 k,
180 prob_lower: lower,
181 prob_upper: upper,
182 }
183 }
184 pub fn join(&self, other: &Self) -> Self {
186 assert_eq!(self.k, other.k);
187 Self {
188 k: self.k,
189 prob_lower: self
190 .prob_lower
191 .iter()
192 .zip(&other.prob_lower)
193 .map(|(a, b)| a.min(*b))
194 .collect(),
195 prob_upper: self
196 .prob_upper
197 .iter()
198 .zip(&other.prob_upper)
199 .map(|(a, b)| a.max(*b))
200 .collect(),
201 }
202 }
203 pub fn abstract_expectation(&self, f: &[f64]) -> (f64, f64) {
206 assert_eq!(f.len(), self.k);
207 let lower_e: f64 = self
208 .prob_lower
209 .iter()
210 .zip(f.iter())
211 .map(|(p, fi)| p * fi)
212 .sum();
213 let upper_e: f64 = self
214 .prob_upper
215 .iter()
216 .zip(f.iter())
217 .map(|(p, fi)| p * fi)
218 .sum();
219 (lower_e, upper_e)
220 }
221 pub fn is_sound(&self) -> bool {
224 let lo_ok = self
225 .prob_lower
226 .iter()
227 .zip(&self.prob_upper)
228 .all(|(l, u)| l <= u && *l >= 0.0 && *u <= 1.0);
229 let sum_lo: f64 = self.prob_lower.iter().sum();
230 let sum_hi: f64 = self.prob_upper.iter().sum();
231 lo_ok && sum_lo <= 1.0 + 1e-9 && sum_hi >= 1.0 - 1e-9
232 }
233}
234#[derive(Debug, Clone)]
240#[allow(dead_code)]
241pub struct DeepPolyNeuron {
242 pub lb_coeffs: Vec<f64>,
244 pub lb_bias: f64,
246 pub ub_coeffs: Vec<f64>,
248 pub ub_bias: f64,
250 pub concrete_lb: f64,
252 pub concrete_ub: f64,
254}
255#[allow(dead_code)]
256impl DeepPolyNeuron {
257 pub fn constant(val: f64, input_dim: usize) -> Self {
259 Self {
260 lb_coeffs: vec![0.0; input_dim],
261 lb_bias: val,
262 ub_coeffs: vec![0.0; input_dim],
263 ub_bias: val,
264 concrete_lb: val,
265 concrete_ub: val,
266 }
267 }
268 pub fn abstract_relu(&self) -> Self {
270 let input_dim = self.lb_coeffs.len();
271 if self.concrete_ub <= 0.0 {
272 DeepPolyNeuron::constant(0.0, input_dim)
273 } else if self.concrete_lb >= 0.0 {
274 self.clone()
275 } else {
276 let slope = self.concrete_ub / (self.concrete_ub - self.concrete_lb);
277 let ub_coeffs: Vec<f64> = self.ub_coeffs.iter().map(|c| slope * c).collect();
278 let ub_bias = slope * (self.ub_bias - self.concrete_lb);
279 DeepPolyNeuron {
280 lb_coeffs: vec![0.0; input_dim],
281 lb_bias: 0.0,
282 ub_coeffs,
283 ub_bias,
284 concrete_lb: 0.0,
285 concrete_ub: slope * (self.concrete_ub - self.concrete_lb),
286 }
287 }
288 }
289 pub fn contains_concrete(&self, val: f64) -> bool {
291 val >= self.concrete_lb && val <= self.concrete_ub
292 }
293}
294pub struct LoopBound {
296 pub bound: Option<u64>,
298}
299impl LoopBound {
300 pub fn unknown() -> Self {
302 Self { bound: None }
303 }
304 pub fn finite(n: u64) -> Self {
306 Self { bound: Some(n) }
307 }
308 pub fn from_interval(interval: &IntervalDomain) -> Self {
310 match &interval.upper {
311 Bound::Finite(n) if *n >= 0 => Self::finite(*n as u64 + 1),
312 _ => Self::unknown(),
313 }
314 }
315 pub fn terminates(&self) -> bool {
317 self.bound.is_some()
318 }
319}
320#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
322pub enum Bound {
323 NegInf,
325 Finite(i64),
327 PosInf,
329}
330impl Bound {
331 pub fn add(&self, other: &Self) -> Self {
333 match (self, other) {
334 (Bound::NegInf, Bound::PosInf) | (Bound::PosInf, Bound::NegInf) => Bound::NegInf,
335 (Bound::NegInf, _) | (_, Bound::NegInf) => Bound::NegInf,
336 (Bound::PosInf, _) | (_, Bound::PosInf) => Bound::PosInf,
337 (Bound::Finite(a), Bound::Finite(b)) => Bound::Finite(a.saturating_add(*b)),
338 }
339 }
340 pub fn neg(&self) -> Self {
342 match self {
343 Bound::NegInf => Bound::PosInf,
344 Bound::PosInf => Bound::NegInf,
345 Bound::Finite(n) => Bound::Finite(-n),
346 }
347 }
348}
349#[derive(Debug, Clone, PartialEq, Eq)]
351pub enum TaintValue {
352 Clean,
354 Tainted,
356 Bottom,
358}
359pub struct AbstractionFunction {
361 gc: GaloisConnection,
362}
363impl AbstractionFunction {
364 pub fn new() -> Self {
366 Self {
367 gc: GaloisConnection::interval_galois(),
368 }
369 }
370 pub fn apply(&self, vals: &[i64]) -> IntervalDomain {
372 self.gc.abstract_of(vals)
373 }
374}
375#[derive(Debug, Clone, PartialEq, Eq)]
377pub enum SignDomain {
378 Bottom,
380 Neg,
382 Zero,
384 Pos,
386 NonNeg,
388 NonPos,
390 Top,
392}
393impl SignDomain {
394 pub fn join(&self, other: &Self) -> Self {
396 use SignDomain::*;
397 match (self, other) {
398 (Bottom, x) | (x, Bottom) => x.clone(),
399 (Top, _) | (_, Top) => Top,
400 (a, b) if a == b => a.clone(),
401 (Zero, Pos) | (Pos, Zero) => NonNeg,
402 (Zero, Neg) | (Neg, Zero) => NonPos,
403 (NonNeg, Neg) | (Neg, NonNeg) => Top,
404 (NonPos, Pos) | (Pos, NonPos) => Top,
405 (Neg, Pos) | (Pos, Neg) => Top,
406 (NonNeg, NonPos) | (NonPos, NonNeg) => Top,
407 _ => Top,
408 }
409 }
410 pub fn widen(&self, other: &Self) -> Self {
412 self.join(other)
413 }
414 pub fn add(&self, other: &Self) -> Self {
416 use SignDomain::*;
417 match (self, other) {
418 (Bottom, _) | (_, Bottom) => Bottom,
419 (Top, _) | (_, Top) => Top,
420 (Zero, x) | (x, Zero) => x.clone(),
421 (Pos, Pos) => Pos,
422 (Neg, Neg) => Neg,
423 (NonNeg, NonNeg) => NonNeg,
424 (NonPos, NonPos) => NonPos,
425 (Pos, NonNeg) | (NonNeg, Pos) => Pos,
426 (Neg, NonPos) | (NonPos, Neg) => Neg,
427 _ => Top,
428 }
429 }
430 pub fn neg(&self) -> Self {
432 use SignDomain::*;
433 match self {
434 Bottom => Bottom,
435 Neg => Pos,
436 Zero => Zero,
437 Pos => Neg,
438 NonNeg => NonPos,
439 NonPos => NonNeg,
440 Top => Top,
441 }
442 }
443}
444pub struct OctagonDomain {
447 pub n: usize,
449 pub matrix: Vec<Vec<Option<i64>>>,
451}
452impl OctagonDomain {
453 pub fn top(n: usize) -> Self {
455 let size = 2 * n;
456 Self {
457 n,
458 matrix: vec![vec![None; size]; size],
459 }
460 }
461 pub fn bottom(n: usize) -> Self {
463 let size = 2 * n;
464 let mut matrix = vec![vec![None; size]; size];
465 for i in 0..size {
466 matrix[i][i] = Some(-1);
467 }
468 Self { n, matrix }
469 }
470 pub fn add_upper_bound(&mut self, i: usize, c: i64) {
472 if i < self.n {
473 let pos = 2 * i;
474 let neg = 2 * i + 1;
475 let cur = self.matrix[pos][neg];
476 let val = 2 * c;
477 self.matrix[pos][neg] = Some(match cur {
478 None => val,
479 Some(v) => v.min(val),
480 });
481 }
482 }
483 pub fn is_satisfiable(&self) -> bool {
485 for i in 0..2 * self.n {
486 if let Some(v) = self.matrix[i][i] {
487 if v < 0 {
488 return false;
489 }
490 }
491 }
492 true
493 }
494}
495pub struct FixpointComputation {
497 pub max_iter: usize,
499 pub widen_delay: usize,
501}
502impl FixpointComputation {
503 pub fn new() -> Self {
505 Self {
506 max_iter: 1000,
507 widen_delay: 3,
508 }
509 }
510 pub fn compute<F>(&self, f: F, init: AbstractState) -> AbstractState
512 where
513 F: Fn(&AbstractState) -> AbstractState,
514 {
515 let mut current = init;
516 let mut delay = DelayedWidening::new(self.widen_delay);
517 for _ in 0..self.max_iter {
518 let next = f(¤t);
519 let widened = AbstractState {
520 vars: {
521 let mut vars = current.vars.clone();
522 for (k, v) in &next.vars {
523 let cur_v = current.get(k);
524 let w = delay.apply(&cur_v, v);
525 vars.insert(k.clone(), w);
526 }
527 vars
528 },
529 };
530 if widened
531 .vars
532 .iter()
533 .all(|(k, v)| current.vars.get(k) == Some(v))
534 && current.vars.len() == widened.vars.len()
535 {
536 return widened;
537 }
538 current = widened;
539 }
540 current
541 }
542}
543pub struct ConcretizationFunction {
545 gc: GaloisConnection,
546}
547impl ConcretizationFunction {
548 pub fn new() -> Self {
550 Self {
551 gc: GaloisConnection::interval_galois(),
552 }
553 }
554 pub fn apply(&self, a: &IntervalDomain) -> Option<Vec<i64>> {
556 self.gc.concretize(a)
557 }
558}
559pub struct TaintAnalysis {
561 pub taint: HashMap<String, TaintValue>,
563}
564impl TaintAnalysis {
565 pub fn new() -> Self {
567 Self {
568 taint: HashMap::new(),
569 }
570 }
571 pub fn add_source(&mut self, var: impl Into<String>) {
573 self.taint.insert(var.into(), TaintValue::Tainted);
574 }
575 pub fn is_tainted(&self, var: &str) -> bool {
577 matches!(self.taint.get(var), Some(TaintValue::Tainted))
578 }
579 pub fn propagate(&mut self, result: impl Into<String>, inputs: &[&str]) {
581 let tainted = inputs.iter().any(|v| self.is_tainted(v));
582 let val = if tainted {
583 TaintValue::Tainted
584 } else {
585 TaintValue::Clean
586 };
587 self.taint.insert(result.into(), val);
588 }
589}
590#[derive(Debug, Clone, PartialEq, Eq)]
593pub struct IntervalDomain {
594 pub lower: Bound,
596 pub upper: Bound,
598 pub is_bottom: bool,
600}
601impl IntervalDomain {
602 pub fn bottom() -> Self {
604 Self {
605 lower: Bound::PosInf,
606 upper: Bound::NegInf,
607 is_bottom: true,
608 }
609 }
610 pub fn top() -> Self {
612 Self {
613 lower: Bound::NegInf,
614 upper: Bound::PosInf,
615 is_bottom: false,
616 }
617 }
618 pub fn constant(n: i64) -> Self {
620 Self {
621 lower: Bound::Finite(n),
622 upper: Bound::Finite(n),
623 is_bottom: false,
624 }
625 }
626 pub fn new(lower: Bound, upper: Bound) -> Self {
628 if lower > upper {
629 Self::bottom()
630 } else {
631 Self {
632 lower,
633 upper,
634 is_bottom: false,
635 }
636 }
637 }
638 pub fn join(&self, other: &Self) -> Self {
640 if self.is_bottom {
641 return other.clone();
642 }
643 if other.is_bottom {
644 return self.clone();
645 }
646 Self::new(
647 self.lower.clone().min(other.lower.clone()),
648 self.upper.clone().max(other.upper.clone()),
649 )
650 }
651 pub fn meet(&self, other: &Self) -> Self {
653 if self.is_bottom || other.is_bottom {
654 return Self::bottom();
655 }
656 Self::new(
657 self.lower.clone().max(other.lower.clone()),
658 self.upper.clone().min(other.upper.clone()),
659 )
660 }
661 pub fn widen(&self, other: &Self) -> Self {
663 if self.is_bottom {
664 return other.clone();
665 }
666 if other.is_bottom {
667 return self.clone();
668 }
669 let new_lower = if other.lower < self.lower {
670 Bound::NegInf
671 } else {
672 self.lower.clone()
673 };
674 let new_upper = if other.upper > self.upper {
675 Bound::PosInf
676 } else {
677 self.upper.clone()
678 };
679 Self::new(new_lower, new_upper)
680 }
681 pub fn narrow(&self, other: &Self) -> Self {
683 if self.is_bottom || other.is_bottom {
684 return Self::bottom();
685 }
686 let new_lower = if self.lower == Bound::NegInf {
687 other.lower.clone()
688 } else {
689 self.lower.clone()
690 };
691 let new_upper = if self.upper == Bound::PosInf {
692 other.upper.clone()
693 } else {
694 self.upper.clone()
695 };
696 Self::new(new_lower, new_upper)
697 }
698 pub fn add(&self, other: &Self) -> Self {
700 if self.is_bottom || other.is_bottom {
701 return Self::bottom();
702 }
703 Self::new(self.lower.add(&other.lower), self.upper.add(&other.upper))
704 }
705 pub fn contains(&self, n: i64) -> bool {
707 if self.is_bottom {
708 return false;
709 }
710 let lo_ok = match &self.lower {
711 Bound::NegInf => true,
712 Bound::Finite(l) => *l <= n,
713 Bound::PosInf => false,
714 };
715 let hi_ok = match &self.upper {
716 Bound::NegInf => false,
717 Bound::Finite(u) => n <= *u,
718 Bound::PosInf => true,
719 };
720 lo_ok && hi_ok
721 }
722}
723pub struct AbstractTransformer {
725 pub transform: Box<dyn Fn(&AbstractState) -> AbstractState>,
727}
728impl AbstractTransformer {
729 pub fn new<F: Fn(&AbstractState) -> AbstractState + 'static>(f: F) -> Self {
731 Self {
732 transform: Box::new(f),
733 }
734 }
735 pub fn apply(&self, state: &AbstractState) -> AbstractState {
737 (self.transform)(state)
738 }
739 pub fn compose(self, other: Self) -> Self {
741 Self::new(move |s| other.apply(&self.apply(s)))
742 }
743}
744#[derive(Debug, Clone, PartialEq, Eq)]
746pub enum AnalysisDirection {
747 Forward,
749 Backward,
751}
752#[derive(Debug, Clone, PartialEq, Eq)]
754pub enum NullValue {
755 MustNull,
757 NonNull,
759 MayNull,
761 Bottom,
763}
764#[allow(dead_code)]
769pub struct ZonotopeDomain {
770 pub dim: usize,
772 pub center: Vec<f64>,
774 pub generators: Vec<Vec<f64>>,
776}
777#[allow(dead_code)]
778impl ZonotopeDomain {
779 pub fn point(center: Vec<f64>) -> Self {
781 let dim = center.len();
782 Self {
783 dim,
784 center,
785 generators: vec![],
786 }
787 }
788 pub fn from_intervals(lower: &[f64], upper: &[f64]) -> Self {
791 assert_eq!(lower.len(), upper.len());
792 let dim = lower.len();
793 let center: Vec<f64> = lower
794 .iter()
795 .zip(upper.iter())
796 .map(|(l, h)| (l + h) / 2.0)
797 .collect();
798 let generators: Vec<Vec<f64>> = (0..dim)
799 .map(|i| {
800 let mut g = vec![0.0; dim];
801 g[i] = (upper[i] - lower[i]) / 2.0;
802 g
803 })
804 .collect();
805 Self {
806 dim,
807 center,
808 generators,
809 }
810 }
811 pub fn to_intervals(&self) -> (Vec<f64>, Vec<f64>) {
814 let mut lower = self.center.clone();
815 let mut upper = self.center.clone();
816 for g in &self.generators {
817 for (i, gi) in g.iter().enumerate() {
818 let abs_g = gi.abs();
819 lower[i] -= abs_g;
820 upper[i] += abs_g;
821 }
822 }
823 (lower, upper)
824 }
825 pub fn affine_map(&self, matrix: &[Vec<f64>], bias: &[f64]) -> Self {
828 let out_dim = matrix.len();
829 let new_center: Vec<f64> = (0..out_dim)
830 .map(|i| {
831 let dot: f64 = matrix[i]
832 .iter()
833 .zip(self.center.iter())
834 .map(|(a, c)| a * c)
835 .sum();
836 dot + bias[i]
837 })
838 .collect();
839 let new_generators: Vec<Vec<f64>> = self
840 .generators
841 .iter()
842 .map(|g| {
843 (0..out_dim)
844 .map(|i| matrix[i].iter().zip(g.iter()).map(|(a, gi)| a * gi).sum())
845 .collect()
846 })
847 .collect();
848 Self {
849 dim: out_dim,
850 center: new_center,
851 generators: new_generators,
852 }
853 }
854 pub fn join(&self, other: &Self) -> Self {
856 assert_eq!(self.dim, other.dim);
857 let (l1, u1) = self.to_intervals();
858 let (l2, u2) = other.to_intervals();
859 let lower: Vec<f64> = l1.iter().zip(l2.iter()).map(|(a, b)| a.min(*b)).collect();
860 let upper: Vec<f64> = u1.iter().zip(u2.iter()).map(|(a, b)| a.max(*b)).collect();
861 Self::from_intervals(&lower, &upper)
862 }
863 pub fn may_contain(&self, point: &[f64]) -> bool {
866 if point.len() != self.dim {
867 return false;
868 }
869 let (lower, upper) = self.to_intervals();
870 lower
871 .iter()
872 .zip(upper.iter())
873 .zip(point.iter())
874 .all(|((l, u), p)| *l <= *p && *p <= *u)
875 }
876}
877pub struct ArrayBoundsAnalysis {
879 pub index_bounds: HashMap<String, IntervalDomain>,
881 pub array_sizes: HashMap<String, IntervalDomain>,
883}
884impl ArrayBoundsAnalysis {
885 pub fn new() -> Self {
887 Self {
888 index_bounds: HashMap::new(),
889 array_sizes: HashMap::new(),
890 }
891 }
892 pub fn is_safe_access(&self, array: &str, idx_var: &str) -> bool {
894 let idx = self
895 .index_bounds
896 .get(idx_var)
897 .cloned()
898 .unwrap_or_else(IntervalDomain::top);
899 let sz = match self.array_sizes.get(array) {
900 Some(s) => s.clone(),
901 None => return false,
902 };
903 if idx.is_bottom {
904 return true;
905 }
906 let lo_ok = match &idx.lower {
907 Bound::Finite(l) => *l >= 0,
908 _ => false,
909 };
910 let hi_ok = match (&idx.upper, &sz.lower) {
911 (Bound::Finite(hi), Bound::Finite(sz_lo)) => *hi < *sz_lo,
912 _ => false,
913 };
914 lo_ok && hi_ok
915 }
916}
917pub struct DelayedWidening {
919 pub delay: usize,
921 pub step: usize,
923}
924impl DelayedWidening {
925 pub fn new(delay: usize) -> Self {
927 Self { delay, step: 0 }
928 }
929 pub fn apply(&mut self, a: &IntervalDomain, b: &IntervalDomain) -> IntervalDomain {
931 self.step += 1;
932 if self.step <= self.delay {
933 a.join(b)
934 } else {
935 a.widen(b)
936 }
937 }
938 pub fn reset(&mut self) {
940 self.step = 0;
941 }
942}
943pub struct GaloisConnection {
945 pub alpha: Box<dyn Fn(&[i64]) -> IntervalDomain>,
947 pub gamma: Box<dyn Fn(&IntervalDomain) -> Option<Vec<i64>>>,
949}
950impl GaloisConnection {
951 pub fn interval_galois() -> Self {
953 Self {
954 alpha: Box::new(|vals: &[i64]| {
955 if vals.is_empty() {
956 return IntervalDomain::bottom();
957 }
958 let lo = vals
959 .iter()
960 .copied()
961 .min()
962 .expect("vals is non-empty: checked by early return");
963 let hi = vals
964 .iter()
965 .copied()
966 .max()
967 .expect("vals is non-empty: checked by early return");
968 IntervalDomain::new(Bound::Finite(lo), Bound::Finite(hi))
969 }),
970 gamma: Box::new(|interval: &IntervalDomain| {
971 if interval.is_bottom {
972 return Some(vec![]);
973 }
974 match (&interval.lower, &interval.upper) {
975 (Bound::Finite(l), Bound::Finite(u)) if u - l <= 1000 => {
976 Some((*l..=*u).collect())
977 }
978 _ => None,
979 }
980 }),
981 }
982 }
983 pub fn abstract_of(&self, vals: &[i64]) -> IntervalDomain {
985 (self.alpha)(vals)
986 }
987 pub fn concretize(&self, a: &IntervalDomain) -> Option<Vec<i64>> {
989 (self.gamma)(a)
990 }
991}
992pub struct ReachabilityAnalysis {
994 pub reachable: std::collections::HashSet<usize>,
996}
997impl ReachabilityAnalysis {
998 pub fn new() -> Self {
1000 Self {
1001 reachable: std::collections::HashSet::new(),
1002 }
1003 }
1004 pub fn mark_reachable(&mut self, label: usize) {
1006 self.reachable.insert(label);
1007 }
1008 pub fn is_reachable(&self, label: usize) -> bool {
1010 self.reachable.contains(&label)
1011 }
1012}
1013#[allow(dead_code)]
1018pub struct AssumeGuaranteeContract {
1019 pub assumption: AbstractState,
1021 pub guarantee: AbstractState,
1023 pub component_name: String,
1025}
1026#[allow(dead_code)]
1027impl AssumeGuaranteeContract {
1028 pub fn new(
1030 component_name: impl Into<String>,
1031 assumption: AbstractState,
1032 guarantee: AbstractState,
1033 ) -> Self {
1034 Self {
1035 assumption,
1036 guarantee,
1037 component_name: component_name.into(),
1038 }
1039 }
1040 pub fn compose(&self, next: &Self) -> Option<Self> {
1043 let compatible = next.assumption.vars.iter().all(|(var, needed)| {
1044 if let Some(provided) = self.guarantee.vars.get(var) {
1045 match (
1046 &provided.lower,
1047 &needed.lower,
1048 &provided.upper,
1049 &needed.upper,
1050 ) {
1051 (
1052 Bound::Finite(pl),
1053 Bound::Finite(nl),
1054 Bound::Finite(pu),
1055 Bound::Finite(nu),
1056 ) => pl >= nl && pu <= nu,
1057 _ => true,
1058 }
1059 } else {
1060 true
1061 }
1062 });
1063 if compatible {
1064 Some(Self::new(
1065 format!("{};{}", self.component_name, next.component_name),
1066 self.assumption.clone(),
1067 next.guarantee.clone(),
1068 ))
1069 } else {
1070 None
1071 }
1072 }
1073 pub fn check(&self, pre: &AbstractState, post: &AbstractState) -> bool {
1075 let pre_ok = self.assumption.vars.iter().all(|(var, assumed)| {
1076 let actual = pre.get(var);
1077 match (&actual.lower, &assumed.lower, &actual.upper, &assumed.upper) {
1078 (Bound::Finite(al), Bound::Finite(asl), Bound::Finite(au), Bound::Finite(asu)) => {
1079 al >= asl && au <= asu
1080 }
1081 _ => !actual.is_bottom,
1082 }
1083 });
1084 let post_ok = self.guarantee.vars.iter().all(|(var, guaranteed)| {
1085 let actual = post.get(var);
1086 match (
1087 &actual.lower,
1088 &guaranteed.lower,
1089 &actual.upper,
1090 &guaranteed.upper,
1091 ) {
1092 (Bound::Finite(al), Bound::Finite(gl), Bound::Finite(au), Bound::Finite(gu)) => {
1093 al >= gl && au <= gu
1094 }
1095 _ => !actual.is_bottom,
1096 }
1097 });
1098 pre_ok && post_ok
1099 }
1100}
1101#[allow(dead_code)]
1103pub struct DeepPolyLayer {
1104 pub neurons: Vec<DeepPolyNeuron>,
1106}
1107#[allow(dead_code)]
1108impl DeepPolyLayer {
1109 pub fn input_layer(lower: &[f64], upper: &[f64]) -> Self {
1111 let dim = lower.len();
1112 let neurons = (0..dim)
1113 .map(|i| {
1114 let mut lb_coeffs = vec![0.0; dim];
1115 let mut ub_coeffs = vec![0.0; dim];
1116 lb_coeffs[i] = 1.0;
1117 ub_coeffs[i] = 1.0;
1118 DeepPolyNeuron {
1119 lb_coeffs,
1120 lb_bias: 0.0,
1121 ub_coeffs,
1122 ub_bias: 0.0,
1123 concrete_lb: lower[i],
1124 concrete_ub: upper[i],
1125 }
1126 })
1127 .collect();
1128 Self { neurons }
1129 }
1130 pub fn affine(&self, weights: &[Vec<f64>], bias: &[f64]) -> Self {
1132 let in_dim = self.neurons.len();
1133 let out_dim = weights.len();
1134 let neurons = (0..out_dim)
1135 .map(|j| {
1136 let mut concrete_lb = bias[j];
1137 let mut concrete_ub = bias[j];
1138 for (i, neuron) in self.neurons.iter().enumerate() {
1139 let w = weights[j][i];
1140 if w >= 0.0 {
1141 concrete_lb += w * neuron.concrete_lb;
1142 concrete_ub += w * neuron.concrete_ub;
1143 } else {
1144 concrete_lb += w * neuron.concrete_ub;
1145 concrete_ub += w * neuron.concrete_lb;
1146 }
1147 }
1148 let lb_coeffs = vec![0.0; in_dim];
1149 let ub_coeffs = vec![0.0; in_dim];
1150 DeepPolyNeuron {
1151 lb_coeffs,
1152 lb_bias: concrete_lb,
1153 ub_coeffs,
1154 ub_bias: concrete_ub,
1155 concrete_lb,
1156 concrete_ub,
1157 }
1158 })
1159 .collect();
1160 Self { neurons }
1161 }
1162 pub fn relu(&self) -> Self {
1164 Self {
1165 neurons: self.neurons.iter().map(|n| n.abstract_relu()).collect(),
1166 }
1167 }
1168 pub fn concrete_lower(&self) -> Vec<f64> {
1170 self.neurons.iter().map(|n| n.concrete_lb).collect()
1171 }
1172 pub fn concrete_upper(&self) -> Vec<f64> {
1174 self.neurons.iter().map(|n| n.concrete_ub).collect()
1175 }
1176}
1177#[allow(dead_code)]
1182pub struct TemplatePolyhedronDomain {
1183 pub dim: usize,
1185 pub template: Vec<Vec<f64>>,
1187 pub bounds: Vec<f64>,
1189 pub is_bottom: bool,
1191}
1192#[allow(dead_code)]
1193impl TemplatePolyhedronDomain {
1194 pub fn top(dim: usize, template: Vec<Vec<f64>>) -> Self {
1196 let k = template.len();
1197 Self {
1198 dim,
1199 template,
1200 bounds: vec![f64::INFINITY; k],
1201 is_bottom: false,
1202 }
1203 }
1204 pub fn bottom(dim: usize, template: Vec<Vec<f64>>) -> Self {
1206 let k = template.len();
1207 Self {
1208 dim,
1209 template,
1210 bounds: vec![f64::NEG_INFINITY; k],
1211 is_bottom: true,
1212 }
1213 }
1214 pub fn from_point(dim: usize, template: Vec<Vec<f64>>, point: &[f64]) -> Self {
1216 assert_eq!(point.len(), dim);
1217 let bounds: Vec<f64> = template
1218 .iter()
1219 .map(|row| row.iter().zip(point.iter()).map(|(ci, xi)| ci * xi).sum())
1220 .collect();
1221 Self {
1222 dim,
1223 template,
1224 bounds,
1225 is_bottom: false,
1226 }
1227 }
1228 pub fn join(&self, other: &Self) -> Self {
1230 assert_eq!(self.template.len(), other.template.len());
1231 if self.is_bottom {
1232 return other.clone();
1233 }
1234 if other.is_bottom {
1235 return self.clone();
1236 }
1237 let bounds: Vec<f64> = self
1238 .bounds
1239 .iter()
1240 .zip(other.bounds.iter())
1241 .map(|(a, b)| a.max(*b))
1242 .collect();
1243 Self {
1244 dim: self.dim,
1245 template: self.template.clone(),
1246 bounds,
1247 is_bottom: false,
1248 }
1249 }
1250 pub fn meet(&self, other: &Self) -> Self {
1252 assert_eq!(self.template.len(), other.template.len());
1253 if self.is_bottom || other.is_bottom {
1254 return Self::bottom(self.dim, self.template.clone());
1255 }
1256 let bounds: Vec<f64> = self
1257 .bounds
1258 .iter()
1259 .zip(other.bounds.iter())
1260 .map(|(a, b)| a.min(*b))
1261 .collect();
1262 Self {
1263 dim: self.dim,
1264 template: self.template.clone(),
1265 bounds,
1266 is_bottom: false,
1267 }
1268 }
1269 pub fn widen(&self, next: &Self) -> Self {
1271 assert_eq!(self.bounds.len(), next.bounds.len());
1272 if self.is_bottom {
1273 return next.clone();
1274 }
1275 let bounds: Vec<f64> = self
1276 .bounds
1277 .iter()
1278 .zip(next.bounds.iter())
1279 .map(|(a, b)| if *b <= *a { *a } else { f64::INFINITY })
1280 .collect();
1281 Self {
1282 dim: self.dim,
1283 template: self.template.clone(),
1284 bounds,
1285 is_bottom: false,
1286 }
1287 }
1288 pub fn contains(&self, point: &[f64]) -> bool {
1290 if self.is_bottom || point.len() != self.dim {
1291 return false;
1292 }
1293 self.template
1294 .iter()
1295 .zip(self.bounds.iter())
1296 .all(|(row, &d)| {
1297 let lhs: f64 = row.iter().zip(point.iter()).map(|(c, x)| c * x).sum();
1298 lhs <= d
1299 })
1300 }
1301}
1302pub struct DataflowAnalysis {
1304 pub direction: AnalysisDirection,
1306 pub values: HashMap<usize, AbstractState>,
1308}
1309impl DataflowAnalysis {
1310 pub fn new_forward() -> Self {
1312 Self {
1313 direction: AnalysisDirection::Forward,
1314 values: HashMap::new(),
1315 }
1316 }
1317 pub fn new_backward() -> Self {
1319 Self {
1320 direction: AnalysisDirection::Backward,
1321 values: HashMap::new(),
1322 }
1323 }
1324 pub fn at(&self, label: usize) -> AbstractState {
1326 self.values
1327 .get(&label)
1328 .cloned()
1329 .unwrap_or_else(AbstractState::bottom)
1330 }
1331 pub fn set_at(&mut self, label: usize, state: AbstractState) {
1333 self.values.insert(label, state);
1334 }
1335}
1336pub struct NullPointerAnalysis {
1338 pub nullness: HashMap<String, NullValue>,
1340}
1341impl NullPointerAnalysis {
1342 pub fn new() -> Self {
1344 Self {
1345 nullness: HashMap::new(),
1346 }
1347 }
1348 pub fn may_be_null(&self, var: &str) -> bool {
1350 matches!(
1351 self.nullness.get(var),
1352 Some(NullValue::MayNull) | Some(NullValue::MustNull) | None
1353 )
1354 }
1355 pub fn must_be_null(&self, var: &str) -> bool {
1357 matches!(self.nullness.get(var), Some(NullValue::MustNull))
1358 }
1359}