use super::functions::AbstractDomain;
#[allow(dead_code)]
#[derive(Debug, Clone, Copy)]
#[allow(missing_docs)]
pub struct IntervalParityProduct {
pub interval: Interval,
pub parity: ParityDomain,
}
#[allow(dead_code)]
impl IntervalParityProduct {
pub fn new(interval: Interval, parity: ParityDomain) -> Self {
IntervalParityProduct { interval, parity }
}
pub fn from_value(v: i64) -> Self {
IntervalParityProduct {
interval: Interval::new(v, v),
parity: ParityDomain::from_value(v),
}
}
pub fn top() -> Self {
IntervalParityProduct {
interval: Interval::top(),
parity: ParityDomain::Top,
}
}
pub fn bottom() -> Self {
IntervalParityProduct {
interval: Interval::bottom(),
parity: ParityDomain::Bottom,
}
}
pub fn join(&self, other: &IntervalParityProduct) -> IntervalParityProduct {
IntervalParityProduct {
interval: self.interval.join(&other.interval),
parity: self.parity.join(&other.parity),
}
}
pub fn add(&self, other: &IntervalParityProduct) -> IntervalParityProduct {
IntervalParityProduct {
interval: self.interval.add(&other.interval),
parity: self.parity.add(&other.parity),
}
}
pub fn is_bottom(&self) -> bool {
self.interval.is_bottom() || self.parity.is_bottom()
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
#[allow(missing_docs)]
pub struct AbstractAlarm {
pub label: String,
pub message: String,
pub severity: AlarmSeverity,
}
#[allow(dead_code)]
impl AbstractAlarm {
pub fn new(label: &str, message: &str, severity: AlarmSeverity) -> Self {
AbstractAlarm {
label: label.to_string(),
message: message.to_string(),
severity,
}
}
pub fn is_error(&self) -> bool {
self.severity == AlarmSeverity::Error
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
#[allow(missing_docs)]
pub struct AnalysisConfig {
pub max_iterations: u32,
pub use_widening: bool,
pub use_narrowing: bool,
pub collect_alarms: bool,
pub verbose: bool,
}
#[allow(dead_code)]
impl AnalysisConfig {
pub fn default_config() -> Self {
AnalysisConfig {
max_iterations: 100,
use_widening: true,
use_narrowing: true,
collect_alarms: true,
verbose: false,
}
}
pub fn fast() -> Self {
AnalysisConfig {
max_iterations: 10,
use_widening: true,
use_narrowing: false,
collect_alarms: false,
verbose: false,
}
}
}
#[allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[allow(missing_docs)]
pub enum AlarmSeverity {
Info,
Warning,
Error,
}
#[allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[allow(missing_docs)]
pub enum AbstractCmp {
DefinitelyTrue,
DefinitelyFalse,
Unknown,
}
#[allow(dead_code)]
impl AbstractCmp {
pub fn lt(a: &Interval, b: &Interval) -> Self {
if a.is_bottom() || b.is_bottom() {
return AbstractCmp::Unknown;
}
if a.hi < b.lo {
AbstractCmp::DefinitelyTrue
} else if a.lo >= b.hi {
AbstractCmp::DefinitelyFalse
} else {
AbstractCmp::Unknown
}
}
pub fn eq(a: &Interval, b: &Interval) -> Self {
if a.is_bottom() || b.is_bottom() {
return AbstractCmp::Unknown;
}
let meet = a.meet(b);
if meet.is_bottom() {
AbstractCmp::DefinitelyFalse
} else if a.lo == a.hi && b.lo == b.hi && a.lo == b.lo {
AbstractCmp::DefinitelyTrue
} else {
AbstractCmp::Unknown
}
}
pub fn is_definitely_true(&self) -> bool {
*self == AbstractCmp::DefinitelyTrue
}
pub fn is_definitely_false(&self) -> bool {
*self == AbstractCmp::DefinitelyFalse
}
}
#[allow(dead_code)]
#[allow(missing_docs)]
pub struct AnalysisResults {
entries: Vec<(String, IntervalParityProduct)>,
}
#[allow(dead_code)]
impl AnalysisResults {
pub fn new() -> Self {
AnalysisResults {
entries: Vec::new(),
}
}
pub fn set(&mut self, var: &str, result: IntervalParityProduct) {
if let Some(e) = self.entries.iter_mut().find(|(v, _)| v == var) {
e.1 = result;
} else {
self.entries.push((var.to_string(), result));
}
}
pub fn get(&self, var: &str) -> Option<IntervalParityProduct> {
self.entries.iter().find(|(v, _)| v == var).map(|(_, r)| *r)
}
pub fn all(&self) -> &[(String, IntervalParityProduct)] {
&self.entries
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn proven_non_negative(&self) -> Vec<&str> {
self.entries
.iter()
.filter(|(_, r)| r.interval.lo >= 0 && !r.interval.is_bottom())
.map(|(v, _)| v.as_str())
.collect()
}
pub fn proven_even(&self) -> Vec<&str> {
self.entries
.iter()
.filter(|(_, r)| r.parity == ParityDomain::Even)
.map(|(v, _)| v.as_str())
.collect()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DepthDomain {
pub max_depth: usize,
pub current_depth: usize,
}
impl DepthDomain {
pub fn new(max: usize) -> Self {
DepthDomain {
max_depth: max,
current_depth: 0,
}
}
pub fn is_bounded(&self) -> bool {
self.current_depth <= self.max_depth
}
pub fn increase(&self) -> DepthDomain {
DepthDomain {
max_depth: self.max_depth,
current_depth: self.current_depth.saturating_add(1),
}
}
pub fn join(&self, other: &DepthDomain) -> DepthDomain {
DepthDomain {
max_depth: self.max_depth.max(other.max_depth),
current_depth: self.current_depth.max(other.current_depth),
}
}
}
#[allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[allow(missing_docs)]
pub struct Interval {
pub lo: i64,
pub hi: i64,
}
#[allow(dead_code)]
impl Interval {
pub fn new(lo: i64, hi: i64) -> Self {
Interval {
lo: lo.min(hi),
hi: lo.max(hi),
}
}
pub fn top() -> Self {
Interval {
lo: i64::MIN,
hi: i64::MAX,
}
}
pub fn bottom() -> Self {
Interval {
lo: i64::MAX,
hi: i64::MIN,
}
}
pub fn is_bottom(&self) -> bool {
self.lo > self.hi
}
pub fn is_top(&self) -> bool {
self.lo == i64::MIN && self.hi == i64::MAX
}
pub fn join(&self, other: &Interval) -> Interval {
if self.is_bottom() {
return *other;
}
if other.is_bottom() {
return *self;
}
Interval::new(self.lo.min(other.lo), self.hi.max(other.hi))
}
pub fn meet(&self, other: &Interval) -> Interval {
let lo = self.lo.max(other.lo);
let hi = self.hi.min(other.hi);
if lo > hi {
Interval::bottom()
} else {
Interval { lo, hi }
}
}
pub fn contains(&self, v: i64) -> bool {
!self.is_bottom() && self.lo <= v && v <= self.hi
}
pub fn width(&self) -> u64 {
if self.is_bottom() {
0
} else {
(self.hi - self.lo + 1) as u64
}
}
pub fn add(&self, other: &Interval) -> Interval {
if self.is_bottom() || other.is_bottom() {
return Interval::bottom();
}
Interval::new(
self.lo.saturating_add(other.lo),
self.hi.saturating_add(other.hi),
)
}
pub fn negate(&self) -> Interval {
if self.is_bottom() {
return Interval::bottom();
}
Interval::new(-self.hi, -self.lo)
}
pub fn sub(&self, other: &Interval) -> Interval {
self.add(&other.negate())
}
}
pub struct AbstractInterpreter {
max_depth: usize,
}
impl AbstractInterpreter {
pub fn new(max_depth: usize) -> Self {
AbstractInterpreter { max_depth }
}
pub fn analyze_depth(&self, expr_str: &str) -> DepthDomain {
let mut domain = DepthDomain::new(self.max_depth);
let mut max_seen: usize = 0;
let mut current: usize = 0;
for ch in expr_str.chars() {
match ch {
'(' | '[' | '{' => {
current = current.saturating_add(1);
if current > max_seen {
max_seen = current;
}
}
')' | ']' | '}' => {
current = current.saturating_sub(1);
}
_ => {}
}
}
domain.current_depth = max_seen;
domain
}
pub fn analyze_sign(&self, expr_str: &str) -> SignDomain {
let trimmed = expr_str.trim();
if trimmed.is_empty() {
return SignDomain::Bottom;
}
if let Ok(n) = trimmed.parse::<i64>() {
return match n.cmp(&0) {
std::cmp::Ordering::Less => SignDomain::Neg,
std::cmp::Ordering::Equal => SignDomain::Zero,
std::cmp::Ordering::Greater => SignDomain::Pos,
};
}
if trimmed.starts_with('-') {
SignDomain::Neg
} else if trimmed.chars().next().is_some_and(|c| c.is_ascii_digit()) {
SignDomain::Pos
} else {
SignDomain::Top
}
}
pub fn analyze_size(&self, expr_str: &str) -> SizeDomain {
let count = expr_str
.split(|c: char| c.is_whitespace() || matches!(c, '(' | ')' | '[' | ']'))
.filter(|s| !s.is_empty())
.count();
SizeDomain::from_count(count)
}
pub fn fixed_point<S: Clone + PartialEq, F: Fn(&S) -> S>(&self, init: S, f: F) -> S {
const MAX_ITERS: usize = 1_000;
let mut current = init;
for _ in 0..MAX_ITERS {
let next = f(¤t);
if next == current {
return current;
}
current = next;
}
current
}
}
#[allow(dead_code)]
pub struct ReachabilityAnalysis {
reachable: Vec<String>,
unreachable: Vec<String>,
}
#[allow(dead_code)]
impl ReachabilityAnalysis {
pub fn new() -> Self {
ReachabilityAnalysis {
reachable: Vec::new(),
unreachable: Vec::new(),
}
}
pub fn mark_reachable(&mut self, label: &str) {
if !self.reachable.contains(&label.to_string()) {
self.reachable.push(label.to_string());
}
self.unreachable.retain(|s| s != label);
}
pub fn mark_unreachable(&mut self, label: &str) {
if !self.unreachable.contains(&label.to_string()) {
self.unreachable.push(label.to_string());
}
self.reachable.retain(|s| s != label);
}
pub fn is_reachable(&self, label: &str) -> bool {
self.reachable.contains(&label.to_string())
}
pub fn is_unreachable(&self, label: &str) -> bool {
self.unreachable.contains(&label.to_string())
}
pub fn reachable_count(&self) -> usize {
self.reachable.len()
}
pub fn unreachable_count(&self) -> usize {
self.unreachable.len()
}
}
#[derive(Debug, Clone)]
pub struct AbstractState {
pub sign: SignDomain,
pub depth: DepthDomain,
pub size: SizeDomain,
}
impl AbstractState {
pub fn new() -> Self {
AbstractState {
sign: SignDomain::Bottom,
depth: DepthDomain::new(1024),
size: SizeDomain::Zero,
}
}
pub fn join(&self, other: &AbstractState) -> AbstractState {
AbstractState {
sign: self.sign.join(&other.sign),
depth: self.depth.join(&other.depth),
size: SizeDomain::max(self.size, other.size),
}
}
}
#[allow(dead_code)]
pub struct SummaryDatabase {
entries: Vec<FunctionSummary>,
}
#[allow(dead_code)]
impl SummaryDatabase {
pub fn new() -> Self {
SummaryDatabase {
entries: Vec::new(),
}
}
pub fn add(&mut self, summary: FunctionSummary) {
self.entries.push(summary);
}
pub fn find(&self, name: &str) -> Option<&FunctionSummary> {
self.entries.iter().find(|s| s.function_name == name)
}
pub fn proven_terminating(&self) -> Vec<&str> {
self.entries
.iter()
.filter(|s| s.terminates())
.map(|s| s.function_name.as_str())
.collect()
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
}
#[allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[allow(missing_docs)]
pub enum BlockReachability {
Unreachable,
Reachable,
Unknown,
}
#[allow(dead_code)]
impl BlockReachability {
pub fn join(&self, other: &BlockReachability) -> BlockReachability {
use BlockReachability::*;
match (self, other) {
(Unreachable, x) | (x, Unreachable) => *x,
(Reachable, _) | (_, Reachable) => Reachable,
_ => Unknown,
}
}
pub fn may_be_reachable(&self) -> bool {
!matches!(self, BlockReachability::Unreachable)
}
}
#[allow(dead_code)]
pub struct AlarmCollector {
alarms: Vec<AbstractAlarm>,
}
#[allow(dead_code)]
impl AlarmCollector {
pub fn new() -> Self {
AlarmCollector { alarms: Vec::new() }
}
pub fn add(&mut self, alarm: AbstractAlarm) {
self.alarms.push(alarm);
}
pub fn alarms(&self) -> &[AbstractAlarm] {
&self.alarms
}
pub fn errors(&self) -> Vec<&AbstractAlarm> {
self.alarms.iter().filter(|a| a.is_error()).collect()
}
pub fn has_errors(&self) -> bool {
self.alarms.iter().any(|a| a.is_error())
}
pub fn len(&self) -> usize {
self.alarms.len()
}
pub fn is_empty(&self) -> bool {
self.alarms.is_empty()
}
pub fn clear(&mut self) {
self.alarms.clear();
}
pub fn count_by_severity(&self) -> (usize, usize, usize) {
let info = self
.alarms
.iter()
.filter(|a| a.severity == AlarmSeverity::Info)
.count();
let warn = self
.alarms
.iter()
.filter(|a| a.severity == AlarmSeverity::Warning)
.count();
let err = self
.alarms
.iter()
.filter(|a| a.severity == AlarmSeverity::Error)
.count();
(info, warn, err)
}
}
#[allow(dead_code)]
pub struct PowersetDomain {
elements: u64,
universe_size: u8,
}
#[allow(dead_code)]
impl PowersetDomain {
pub fn bottom(universe_size: u8) -> Self {
PowersetDomain {
elements: 0,
universe_size,
}
}
pub fn top(universe_size: u8) -> Self {
let mask = if universe_size >= 64 {
u64::MAX
} else {
(1u64 << universe_size) - 1
};
PowersetDomain {
elements: mask,
universe_size,
}
}
pub fn add(&mut self, idx: u8) {
if idx < self.universe_size {
self.elements |= 1 << idx;
}
}
pub fn remove(&mut self, idx: u8) {
self.elements &= !(1 << idx);
}
pub fn contains(&self, idx: u8) -> bool {
(self.elements >> idx) & 1 != 0
}
pub fn join(&self, other: &PowersetDomain) -> PowersetDomain {
PowersetDomain {
elements: self.elements | other.elements,
universe_size: self.universe_size,
}
}
pub fn meet(&self, other: &PowersetDomain) -> PowersetDomain {
PowersetDomain {
elements: self.elements & other.elements,
universe_size: self.universe_size,
}
}
pub fn count(&self) -> u32 {
self.elements.count_ones()
}
pub fn is_bottom(&self) -> bool {
self.elements == 0
}
pub fn is_top(&self) -> bool {
let mask = if self.universe_size >= 64 {
u64::MAX
} else {
(1u64 << self.universe_size) - 1
};
self.elements == mask
}
}
#[allow(dead_code)]
pub struct AbstractTrace {
points: Vec<(String, Interval)>,
}
#[allow(dead_code)]
impl AbstractTrace {
pub fn new() -> Self {
AbstractTrace { points: Vec::new() }
}
pub fn record(&mut self, label: &str, iv: Interval) {
self.points.push((label.to_string(), iv));
}
pub fn at(&self, label: &str) -> Option<Interval> {
self.points
.iter()
.find(|(l, _)| l == label)
.map(|(_, iv)| *iv)
}
pub fn all(&self) -> &[(String, Interval)] {
&self.points
}
pub fn len(&self) -> usize {
self.points.len()
}
pub fn is_empty(&self) -> bool {
self.points.is_empty()
}
pub fn format(&self) -> String {
self.points
.iter()
.map(|(l, iv)| format!("{}: [{}, {}]", l, iv.lo, iv.hi))
.collect::<Vec<_>>()
.join("; ")
}
}
#[allow(dead_code)]
#[derive(Debug, Clone, Copy)]
#[allow(missing_docs)]
pub struct CostBound {
pub lower: u64,
pub upper: Option<u64>,
}
#[allow(dead_code)]
impl CostBound {
pub fn exact(n: u64) -> Self {
CostBound {
lower: n,
upper: Some(n),
}
}
pub fn at_least(n: u64) -> Self {
CostBound {
lower: n,
upper: None,
}
}
pub fn range(lo: u64, hi: u64) -> Self {
CostBound {
lower: lo,
upper: Some(hi),
}
}
pub fn is_bounded(&self) -> bool {
self.upper.is_some()
}
pub fn width(&self) -> Option<u64> {
self.upper.map(|h| h - self.lower)
}
pub fn add(&self, other: &CostBound) -> CostBound {
CostBound {
lower: self.lower.saturating_add(other.lower),
upper: match (self.upper, other.upper) {
(Some(a), Some(b)) => Some(a.saturating_add(b)),
_ => None,
},
}
}
}
#[allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[allow(missing_docs)]
pub struct CongruenceDomain {
pub modulus: u64,
pub residue: u64,
}
#[allow(dead_code)]
impl CongruenceDomain {
pub fn singleton(v: u64) -> Self {
CongruenceDomain {
modulus: 0,
residue: v,
}
}
pub fn congruent(modulus: u64, residue: u64) -> Self {
let r = if modulus == 0 { 0 } else { residue % modulus };
CongruenceDomain {
modulus,
residue: r,
}
}
pub fn top() -> Self {
CongruenceDomain {
modulus: 1,
residue: 0,
}
}
pub fn bottom() -> Self {
CongruenceDomain {
modulus: 0,
residue: u64::MAX,
}
}
pub fn is_top(&self) -> bool {
self.modulus == 1
}
pub fn is_bottom(&self) -> bool {
self.modulus == 0 && self.residue == u64::MAX
}
pub fn satisfies(&self, v: u64) -> bool {
if self.is_bottom() {
return false;
}
if self.is_top() {
return true;
}
if self.modulus == 0 {
return v == self.residue;
}
v % self.modulus == self.residue
}
pub fn join(&self, other: &CongruenceDomain) -> CongruenceDomain {
if self.is_bottom() {
return *other;
}
if other.is_bottom() {
return *self;
}
if self.is_top() || other.is_top() {
return CongruenceDomain::top();
}
if self.modulus == other.modulus && self.residue == other.residue {
return *self;
}
CongruenceDomain::top()
}
}
#[allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[allow(missing_docs)]
pub enum TrileanDomain {
Bottom,
False,
True,
Top,
}
#[allow(dead_code)]
impl TrileanDomain {
pub fn from_bool(b: bool) -> Self {
if b {
TrileanDomain::True
} else {
TrileanDomain::False
}
}
pub fn and(&self, other: &TrileanDomain) -> TrileanDomain {
use TrileanDomain::*;
match (self, other) {
(Bottom, _) | (_, Bottom) => Bottom,
(False, _) | (_, False) => False,
(True, True) => True,
_ => Top,
}
}
pub fn or(&self, other: &TrileanDomain) -> TrileanDomain {
use TrileanDomain::*;
match (self, other) {
(Bottom, _) | (_, Bottom) => Bottom,
(True, _) | (_, True) => True,
(False, False) => False,
_ => Top,
}
}
pub fn not(&self) -> TrileanDomain {
use TrileanDomain::*;
match self {
Bottom => Bottom,
False => True,
True => False,
Top => Top,
}
}
pub fn join(&self, other: &TrileanDomain) -> TrileanDomain {
use TrileanDomain::*;
match (self, other) {
(Bottom, x) | (x, Bottom) => *x,
(True, True) => True,
(False, False) => False,
_ => Top,
}
}
pub fn is_definitely_true(&self) -> bool {
*self == TrileanDomain::True
}
pub fn may_be_true(&self) -> bool {
matches!(self, TrileanDomain::True | TrileanDomain::Top)
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
#[allow(missing_docs)]
pub enum TransferEffect {
Assign { var: String, interval: Interval },
Constrain { var: String, constraint: Interval },
Invalidate { var: String },
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SignDomain {
Bottom,
Neg,
Zero,
Pos,
Nonzero,
NonNeg,
NonPos,
Top,
}
#[allow(clippy::should_implement_trait)]
impl SignDomain {
pub fn negate(&self) -> SignDomain {
use SignDomain::*;
match self {
Bottom => Bottom,
Neg => Pos,
Zero => Zero,
Pos => Neg,
Nonzero => Nonzero,
NonNeg => NonPos,
NonPos => NonNeg,
Top => Top,
}
}
pub fn add(s1: SignDomain, s2: SignDomain) -> SignDomain {
use SignDomain::*;
match (s1, s2) {
(Bottom, _) | (_, Bottom) => Bottom,
(Zero, x) | (x, Zero) => x,
(Pos, Pos) => Pos,
(Neg, Neg) => Neg,
(Pos, NonNeg) | (NonNeg, Pos) => Pos,
(Neg, NonPos) | (NonPos, Neg) => Neg,
(NonNeg, NonNeg) => NonNeg,
(NonPos, NonPos) => NonPos,
_ => Top,
}
}
pub fn mul(s1: SignDomain, s2: SignDomain) -> SignDomain {
use SignDomain::*;
match (s1, s2) {
(Bottom, _) | (_, Bottom) => Bottom,
(Zero, _) | (_, Zero) => Zero,
(Top, _) | (_, Top) => Top,
(Pos, Pos) | (Neg, Neg) => Pos,
(Pos, Neg) | (Neg, Pos) => Neg,
(NonNeg, NonNeg) => NonNeg,
(NonPos, NonPos) => NonNeg,
(NonNeg, NonPos) | (NonPos, NonNeg) => NonPos,
(Pos, NonNeg) | (NonNeg, Pos) => NonNeg,
(Neg, NonPos) | (NonPos, Neg) => NonNeg,
(Pos, NonPos) | (NonPos, Pos) => NonPos,
(Neg, NonNeg) | (NonNeg, Neg) => NonPos,
_ => Top,
}
}
}
#[allow(dead_code)]
pub struct SimpleAbstractInterpreter {
config: AnalysisConfig,
results: AnalysisResults,
alarms: AlarmCollector,
}
#[allow(dead_code)]
impl SimpleAbstractInterpreter {
pub fn new(config: AnalysisConfig) -> Self {
SimpleAbstractInterpreter {
config,
results: AnalysisResults::new(),
alarms: AlarmCollector::new(),
}
}
pub fn init_var(&mut self, var: &str, value: IntervalParityProduct) {
self.results.set(var, value);
}
pub fn results(&self) -> &AnalysisResults {
&self.results
}
pub fn alarms(&self) -> &AlarmCollector {
&self.alarms
}
pub fn run_stub(&mut self) -> InterpretationSummary {
InterpretationSummary::new(1, true, self.alarms.len())
}
}
#[allow(dead_code)]
pub struct FixpointEngine {
max_iterations: u32,
iterations_done: u32,
widening_threshold: u32,
}
#[allow(dead_code)]
impl FixpointEngine {
pub fn new(max_iterations: u32) -> Self {
FixpointEngine {
max_iterations,
iterations_done: 0,
widening_threshold: max_iterations / 2,
}
}
pub fn is_fixpoint(current: &IntervalEnv, next: &IntervalEnv) -> bool {
current.is_at_least_as_wide(next) && next.is_at_least_as_wide(current)
}
pub fn should_widen(&self) -> bool {
self.iterations_done >= self.widening_threshold
}
pub fn step(&mut self) {
self.iterations_done += 1;
}
pub fn is_exhausted(&self) -> bool {
self.iterations_done >= self.max_iterations
}
pub fn iterations(&self) -> u32 {
self.iterations_done
}
pub fn reset(&mut self) {
self.iterations_done = 0;
}
}
#[allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[allow(missing_docs)]
pub enum ParityDomain {
Bottom,
Even,
Odd,
Top,
}
#[allow(dead_code)]
impl ParityDomain {
pub fn from_value(v: i64) -> Self {
if v % 2 == 0 {
ParityDomain::Even
} else {
ParityDomain::Odd
}
}
pub fn join(&self, other: &ParityDomain) -> ParityDomain {
use ParityDomain::*;
match (self, other) {
(Bottom, x) | (x, Bottom) => *x,
(Even, Even) => Even,
(Odd, Odd) => Odd,
_ => Top,
}
}
pub fn meet(&self, other: &ParityDomain) -> ParityDomain {
use ParityDomain::*;
match (self, other) {
(Top, x) | (x, Top) => *x,
(Even, Even) => Even,
(Odd, Odd) => Odd,
_ => Bottom,
}
}
pub fn add(&self, other: &ParityDomain) -> ParityDomain {
use ParityDomain::*;
match (self, other) {
(Bottom, _) | (_, Bottom) => Bottom,
(Top, _) | (_, Top) => Top,
(Even, x) | (x, Even) => *x,
(Odd, Odd) => Even,
}
}
pub fn mul(&self, other: &ParityDomain) -> ParityDomain {
use ParityDomain::*;
match (self, other) {
(Bottom, _) | (_, Bottom) => Bottom,
(Even, _) | (_, Even) => Even,
(Odd, Odd) => Odd,
_ => Top,
}
}
pub fn is_bottom(&self) -> bool {
*self == ParityDomain::Bottom
}
pub fn is_top(&self) -> bool {
*self == ParityDomain::Top
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
#[allow(missing_docs)]
pub struct CallGraphNode {
pub name: String,
pub is_recursive: bool,
pub callees: Vec<String>,
}
#[allow(dead_code)]
impl CallGraphNode {
pub fn new(name: impl Into<String>) -> Self {
CallGraphNode {
name: name.into(),
is_recursive: false,
callees: Vec::new(),
}
}
pub fn add_callee(&mut self, name: &str) {
if name == self.name.as_str() {
self.is_recursive = true;
}
if !self.callees.iter().any(|s| s == name) {
self.callees.push(name.to_string());
}
}
pub fn calls(&self, name: &str) -> bool {
self.callees.iter().any(|s| s == name)
}
}
#[allow(dead_code)]
#[allow(missing_docs)]
#[derive(Debug, Clone, Copy)]
pub struct ProductDomain<A: Copy, B: Copy> {
pub first: A,
pub second: B,
}
#[allow(dead_code)]
impl<A: Copy, B: Copy> ProductDomain<A, B> {
pub fn new(first: A, second: B) -> Self {
ProductDomain { first, second }
}
}
#[allow(dead_code)]
pub struct ChaoticIterator {
max_steps: u32,
current_step: u32,
converged: bool,
}
#[allow(dead_code)]
impl ChaoticIterator {
pub fn new(max_steps: u32) -> Self {
ChaoticIterator {
max_steps,
current_step: 0,
converged: false,
}
}
pub fn mark_converged(&mut self) {
self.converged = true;
}
pub fn advance(&mut self) -> bool {
if self.current_step >= self.max_steps {
return false;
}
self.current_step += 1;
true
}
pub fn is_converged(&self) -> bool {
self.converged
}
pub fn is_limit_exceeded(&self) -> bool {
!self.converged && self.current_step >= self.max_steps
}
pub fn steps(&self) -> u32 {
self.current_step
}
pub fn reset(&mut self) {
self.current_step = 0;
self.converged = false;
}
}
#[allow(dead_code)]
pub struct CallGraph {
nodes: Vec<CallGraphNode>,
}
#[allow(dead_code)]
impl CallGraph {
pub fn new() -> Self {
CallGraph { nodes: Vec::new() }
}
pub fn add_node(&mut self, node: CallGraphNode) {
self.nodes.push(node);
}
pub fn find(&self, name: &str) -> Option<&CallGraphNode> {
self.nodes.iter().find(|n| n.name == name)
}
pub fn recursive_fns(&self) -> Vec<&str> {
self.nodes
.iter()
.filter(|n| n.is_recursive)
.map(|n| n.name.as_str())
.collect()
}
pub fn callers_of(&self, name: &str) -> Vec<&str> {
self.nodes
.iter()
.filter(|n| n.calls(name))
.map(|n| n.name.as_str())
.collect()
}
pub fn len(&self) -> usize {
self.nodes.len()
}
pub fn is_empty(&self) -> bool {
self.nodes.is_empty()
}
pub fn function_names(&self) -> Vec<&str> {
self.nodes.iter().map(|n| n.name.as_str()).collect()
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
#[allow(missing_docs)]
pub enum TerminationEvidence {
Structural { arg_index: u32 },
Lexicographic { measures: Vec<String> },
Unknown,
}
#[allow(dead_code)]
impl TerminationEvidence {
pub fn is_proven(&self) -> bool {
!matches!(self, TerminationEvidence::Unknown)
}
pub fn describe(&self) -> String {
match self {
TerminationEvidence::Structural { arg_index } => {
format!("structural on arg #{}", arg_index)
}
TerminationEvidence::Lexicographic { measures } => {
format!("lexicographic on [{}]", measures.join(", "))
}
TerminationEvidence::Unknown => "unknown".to_string(),
}
}
}
#[allow(dead_code)]
pub struct IntervalEnv {
pub(super) bindings: Vec<(String, Interval)>,
}
#[allow(dead_code)]
impl IntervalEnv {
pub fn new() -> Self {
IntervalEnv {
bindings: Vec::new(),
}
}
pub fn set(&mut self, name: &str, iv: Interval) {
if let Some(b) = self.bindings.iter_mut().find(|(n, _)| n == name) {
b.1 = iv;
} else {
self.bindings.push((name.to_string(), iv));
}
}
pub fn get(&self, name: &str) -> Interval {
self.bindings
.iter()
.find(|(n, _)| n == name)
.map(|(_, iv)| *iv)
.unwrap_or(Interval::top())
}
pub fn join(&self, other: &IntervalEnv) -> IntervalEnv {
let mut result = IntervalEnv::new();
for (name, iv) in &self.bindings {
let iv2 = other.get(name);
result.set(name, iv.join(&iv2));
}
for (name, iv) in &other.bindings {
if self.bindings.iter().all(|(n, _)| n != name) {
result.set(name, *iv);
}
}
result
}
pub fn is_at_least_as_wide(&self, other: &IntervalEnv) -> bool {
other.bindings.iter().all(|(name, iv2)| {
let iv1 = self.get(name);
iv1.lo <= iv2.lo && iv1.hi >= iv2.hi
})
}
pub fn len(&self) -> usize {
self.bindings.len()
}
pub fn is_empty(&self) -> bool {
self.bindings.is_empty()
}
pub fn names(&self) -> Vec<&str> {
self.bindings.iter().map(|(n, _)| n.as_str()).collect()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SizeDomain {
Bottom,
Zero,
Small(usize),
Large,
Top,
}
#[allow(clippy::should_implement_trait)]
impl SizeDomain {
pub fn from_count(n: usize) -> Self {
const SMALL_THRESHOLD: usize = 100;
if n == 0 {
SizeDomain::Zero
} else if n <= SMALL_THRESHOLD {
SizeDomain::Small(n)
} else {
SizeDomain::Large
}
}
pub fn add(a: SizeDomain, b: SizeDomain) -> SizeDomain {
use SizeDomain::*;
match (a, b) {
(Bottom, _) | (_, Bottom) => Bottom,
(Top, _) | (_, Top) => Top,
(Zero, x) | (x, Zero) => x,
(Small(m), Small(n)) => SizeDomain::from_count(m + n),
(Large, _) | (_, Large) => Large,
}
}
pub fn max(a: SizeDomain, b: SizeDomain) -> SizeDomain {
use SizeDomain::*;
match (a, b) {
(Bottom, x) | (x, Bottom) => x,
(Top, _) | (_, Top) => Top,
(Zero, x) | (x, Zero) => x,
(Small(m), Small(n)) => Small(m.max(n)),
(Large, _) | (_, Large) => Large,
}
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
#[allow(missing_docs)]
pub struct InterpretationSummary {
pub iterations: u32,
pub converged: bool,
pub alarm_count: usize,
pub proven_safe: bool,
}
#[allow(dead_code)]
impl InterpretationSummary {
pub fn new(iterations: u32, converged: bool, alarm_count: usize) -> Self {
InterpretationSummary {
iterations,
converged,
alarm_count,
proven_safe: converged && alarm_count == 0,
}
}
pub fn describe(&self) -> String {
format!(
"iters={} converged={} alarms={} safe={}",
self.iterations, self.converged, self.alarm_count, self.proven_safe
)
}
}
#[allow(dead_code)]
#[allow(missing_docs)]
pub struct TransferFunction {
pub name: String,
pub effects: Vec<TransferEffect>,
}
#[allow(dead_code)]
impl TransferFunction {
pub fn new(name: impl Into<String>) -> Self {
TransferFunction {
name: name.into(),
effects: Vec::new(),
}
}
pub fn add_effect(&mut self, effect: TransferEffect) {
self.effects.push(effect);
}
pub fn apply(&self, env: &IntervalEnv) -> IntervalEnv {
let result_bindings = env.bindings.clone();
let mut result = IntervalEnv {
bindings: result_bindings,
};
for effect in &self.effects {
match effect {
TransferEffect::Assign { var, interval } => {
result.set(var, *interval);
}
TransferEffect::Constrain { var, constraint } => {
let current = result.get(var);
let narrowed = current.meet(constraint);
result.set(var, narrowed);
}
TransferEffect::Invalidate { var } => {
result.set(var, Interval::top());
}
}
}
result
}
pub fn effect_count(&self) -> usize {
self.effects.len()
}
}
#[allow(dead_code)]
#[allow(missing_docs)]
pub struct FunctionSummary {
pub function_name: String,
pub precondition: IntervalEnv,
pub postcondition: IntervalEnv,
pub termination: TerminationEvidence,
pub cost: CostBound,
}
#[allow(dead_code)]
impl FunctionSummary {
pub fn new(name: impl Into<String>) -> Self {
FunctionSummary {
function_name: name.into(),
precondition: IntervalEnv::new(),
postcondition: IntervalEnv::new(),
termination: TerminationEvidence::Unknown,
cost: CostBound::at_least(0),
}
}
pub fn terminates(&self) -> bool {
self.termination.is_proven()
}
pub fn describe(&self) -> String {
format!(
"fn {}: terminates={} cost=[{}, {:?}]",
self.function_name,
self.terminates(),
self.cost.lower,
self.cost.upper
)
}
}
#[allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[allow(missing_docs)]
pub enum NullnessDomain {
Bottom,
Null,
NonNull,
Top,
}
#[allow(dead_code)]
impl NullnessDomain {
pub fn from_bool(non_null: bool) -> Self {
if non_null {
NullnessDomain::NonNull
} else {
NullnessDomain::Null
}
}
pub fn join(&self, other: &NullnessDomain) -> NullnessDomain {
use NullnessDomain::*;
match (self, other) {
(Bottom, x) | (x, Bottom) => *x,
(Null, Null) => Null,
(NonNull, NonNull) => NonNull,
_ => Top,
}
}
pub fn may_be_null(&self) -> bool {
matches!(self, NullnessDomain::Null | NullnessDomain::Top)
}
pub fn is_definitely_non_null(&self) -> bool {
*self == NullnessDomain::NonNull
}
}