pub mod bit_domain;
pub mod interval;
pub mod path_domain;
pub mod string_domain;
pub use bit_domain::BitFact;
pub use interval::IntervalFact;
pub use path_domain::{PathFact, Tri};
pub use string_domain::StringFact;
use crate::ssa::ir::SsaValue;
use crate::state::lattice::{AbstractDomain, Lattice};
use serde::{Deserialize, Serialize};
use smallvec::SmallVec;
pub fn is_enabled() -> bool {
crate::utils::analysis_options::current().abstract_interpretation
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct AbstractValue {
pub interval: IntervalFact,
pub string: StringFact,
pub bits: BitFact,
#[serde(default, skip_serializing_if = "path_fact_is_top")]
pub path: PathFact,
}
fn path_fact_is_top(p: &PathFact) -> bool {
p.is_top()
}
impl AbstractValue {
pub fn top() -> Self {
Self {
interval: IntervalFact::top(),
string: StringFact::top(),
bits: BitFact::top(),
path: PathFact::top(),
}
}
pub fn bottom() -> Self {
Self {
interval: IntervalFact::bottom(),
string: StringFact::bottom(),
bits: BitFact::bottom(),
path: PathFact::bottom(),
}
}
pub fn with_path_fact(path: PathFact) -> Self {
Self {
interval: IntervalFact::top(),
string: StringFact::top(),
bits: BitFact::top(),
path,
}
}
pub fn is_top(&self) -> bool {
self.interval.is_top() && self.string.is_top() && self.bits.is_top() && self.path.is_top()
}
pub fn is_bottom(&self) -> bool {
self.interval.is_bottom()
&& self.string.is_bottom()
&& self.bits.is_bottom()
&& self.path.is_bottom()
}
pub fn join(&self, other: &Self) -> Self {
Self {
interval: self.interval.join(&other.interval),
string: self.string.join(&other.string),
bits: self.bits.join(&other.bits),
path: self.path.join(&other.path),
}
}
pub fn meet(&self, other: &Self) -> Self {
Self {
interval: self.interval.meet(&other.interval),
string: self.string.meet(&other.string),
bits: <BitFact as AbstractDomain>::meet(&self.bits, &other.bits),
path: <PathFact as AbstractDomain>::meet(&self.path, &other.path),
}
}
pub fn widen(&self, other: &Self) -> Self {
Self {
interval: self.interval.widen(&other.interval),
string: self.string.widen(&other.string),
bits: self.bits.widen(&other.bits),
path: self.path.widen(&other.path),
}
}
pub fn leq(&self, other: &Self) -> bool {
self.interval.leq(&other.interval)
&& self.string.leq(&other.string)
&& self.bits.leq(&other.bits)
&& self.path.leq(&other.path)
}
}
impl Lattice for AbstractValue {
fn bot() -> Self {
Self::bottom()
}
fn join(&self, other: &Self) -> Self {
self.join(other)
}
fn leq(&self, other: &Self) -> bool {
self.leq(other)
}
}
impl AbstractDomain for AbstractValue {
fn top() -> Self {
Self::top()
}
fn meet(&self, other: &Self) -> Self {
self.meet(other)
}
fn widen(&self, other: &Self) -> Self {
self.widen(other)
}
}
pub const MAX_LITERAL_PREFIX_LEN: usize = 64;
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
pub enum IntervalTransfer {
#[default]
Top,
Identity,
Affine {
add: i64,
mul: i64,
},
Clamped {
lo: i64,
hi: i64,
},
}
impl IntervalTransfer {
pub fn apply(&self, input: &IntervalFact) -> IntervalFact {
match self {
Self::Top => IntervalFact::top(),
Self::Identity => input.clone(),
Self::Affine { add, mul } => input
.mul(&IntervalFact::exact(*mul))
.add(&IntervalFact::exact(*add)),
Self::Clamped { lo, hi } if lo <= hi => IntervalFact {
lo: Some(*lo),
hi: Some(*hi),
},
Self::Clamped { .. } => IntervalFact::top(),
}
}
pub fn join(&self, other: &Self) -> Self {
use IntervalTransfer::*;
match (self, other) {
(Top, _) | (_, Top) => Top,
(a, b) if a == b => a.clone(),
(Clamped { lo: a, hi: b }, Clamped { lo: c, hi: d }) => Clamped {
lo: (*a).min(*c),
hi: (*b).max(*d),
},
_ => Top,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
pub enum StringTransfer {
#[default]
Unknown,
Identity,
LiteralPrefix(String),
}
impl StringTransfer {
pub fn literal_prefix(s: &str) -> Self {
if s.is_empty() {
return Self::Unknown;
}
if s.len() <= MAX_LITERAL_PREFIX_LEN {
Self::LiteralPrefix(s.to_string())
} else {
let mut cut = MAX_LITERAL_PREFIX_LEN;
while cut > 0 && !s.is_char_boundary(cut) {
cut -= 1;
}
if cut == 0 {
Self::Unknown
} else {
Self::LiteralPrefix(s[..cut].to_string())
}
}
}
pub fn apply(&self, input: &StringFact) -> StringFact {
match self {
Self::Unknown => StringFact::top(),
Self::Identity => input.clone(),
Self::LiteralPrefix(p) => StringFact::from_prefix(p),
}
}
pub fn join(&self, other: &Self) -> Self {
use StringTransfer::*;
match (self, other) {
(Unknown, _) | (_, Unknown) => Unknown,
(a, b) if a == b => a.clone(),
(LiteralPrefix(a), LiteralPrefix(b)) => {
let lcp: String = a
.chars()
.zip(b.chars())
.take_while(|(x, y)| x == y)
.map(|(x, _)| x)
.collect();
if lcp.is_empty() {
Unknown
} else {
Self::literal_prefix(&lcp)
}
}
_ => Unknown,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
pub struct AbstractTransfer {
#[serde(default, skip_serializing_if = "is_interval_top")]
pub interval: IntervalTransfer,
#[serde(default, skip_serializing_if = "is_string_unknown")]
pub string: StringTransfer,
}
fn is_interval_top(t: &IntervalTransfer) -> bool {
matches!(t, IntervalTransfer::Top)
}
fn is_string_unknown(t: &StringTransfer) -> bool {
matches!(t, StringTransfer::Unknown)
}
impl AbstractTransfer {
pub fn top() -> Self {
Self::default()
}
pub fn is_top(&self) -> bool {
is_interval_top(&self.interval) && is_string_unknown(&self.string)
}
pub fn apply(&self, input: &AbstractValue) -> AbstractValue {
AbstractValue {
interval: self.interval.apply(&input.interval),
string: self.string.apply(&input.string),
bits: BitFact::top(),
path: PathFact::top(),
}
}
pub fn join(&self, other: &Self) -> Self {
Self {
interval: self.interval.join(&other.interval),
string: self.string.join(&other.string),
}
}
}
const MAX_ABSTRACT_VALUES: usize = 64;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AbstractState {
values: SmallVec<[(SsaValue, AbstractValue); 8]>,
}
impl AbstractState {
pub fn empty() -> Self {
Self {
values: SmallVec::new(),
}
}
pub fn get(&self, v: SsaValue) -> AbstractValue {
self.values
.binary_search_by_key(&v, |(id, _)| *id)
.ok()
.map(|idx| self.values[idx].1.clone())
.unwrap_or_else(AbstractValue::top)
}
pub fn set(&mut self, v: SsaValue, val: AbstractValue) {
if val.is_top() {
if let Ok(idx) = self.values.binary_search_by_key(&v, |(id, _)| *id) {
self.values.remove(idx);
}
return;
}
match self.values.binary_search_by_key(&v, |(id, _)| *id) {
Ok(idx) => self.values[idx].1 = val,
Err(idx) => {
if self.values.len() < MAX_ABSTRACT_VALUES {
self.values.insert(idx, (v, val));
}
}
}
}
pub fn join(&self, other: &Self) -> Self {
let mut result = SmallVec::with_capacity(self.values.len().min(other.values.len()));
let (mut i, mut j) = (0, 0);
while i < self.values.len() && j < other.values.len() {
match self.values[i].0.cmp(&other.values[j].0) {
std::cmp::Ordering::Less => {
i += 1;
}
std::cmp::Ordering::Greater => {
j += 1;
}
std::cmp::Ordering::Equal => {
let joined = self.values[i].1.join(&other.values[j].1);
if !joined.is_top() {
result.push((self.values[i].0, joined));
}
i += 1;
j += 1;
}
}
}
Self { values: result }
}
pub fn widen(&self, other: &Self) -> Self {
let mut result = SmallVec::with_capacity(self.values.len().min(other.values.len()));
let (mut i, mut j) = (0, 0);
while i < self.values.len() && j < other.values.len() {
match self.values[i].0.cmp(&other.values[j].0) {
std::cmp::Ordering::Less => {
i += 1;
}
std::cmp::Ordering::Greater => {
j += 1;
}
std::cmp::Ordering::Equal => {
let widened = self.values[i].1.widen(&other.values[j].1);
if !widened.is_top() {
result.push((self.values[i].0, widened));
}
i += 1;
j += 1;
}
}
}
Self { values: result }
}
pub fn leq(&self, other: &Self) -> bool {
for (v, val) in &self.values {
let other_val = other.get(*v);
if !val.leq(&other_val) {
return false;
}
}
true
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn abstract_value_top_bottom() {
assert!(AbstractValue::top().is_top());
assert!(AbstractValue::bottom().is_bottom());
assert!(!AbstractValue::top().is_bottom());
assert!(!AbstractValue::bottom().is_top());
}
#[test]
fn abstract_value_join_componentwise() {
let a = AbstractValue {
interval: IntervalFact::exact(1),
string: StringFact::from_prefix("https://a.com/"),
bits: BitFact::top(),
path: PathFact::top(),
};
let b = AbstractValue {
interval: IntervalFact::exact(5),
string: StringFact::from_prefix("https://b.com/"),
bits: BitFact::top(),
path: PathFact::top(),
};
let j = a.join(&b);
assert_eq!(j.interval.lo, Some(1));
assert_eq!(j.interval.hi, Some(5));
assert_eq!(j.string.prefix.as_deref(), Some("https://"));
}
#[test]
fn abstract_value_widen_componentwise() {
let old = AbstractValue {
interval: IntervalFact {
lo: Some(0),
hi: Some(5),
},
string: StringFact::from_prefix("hello"),
bits: BitFact::top(),
path: PathFact::top(),
};
let new = AbstractValue {
interval: IntervalFact {
lo: Some(0),
hi: Some(10),
},
string: StringFact::from_prefix("hello"),
bits: BitFact::top(),
path: PathFact::top(),
};
let w = old.widen(&new);
assert_eq!(w.interval.lo, Some(0)); assert_eq!(w.interval.hi, None); assert_eq!(w.string.prefix.as_deref(), Some("hello")); }
#[test]
fn abstract_state_get_default_top() {
let state = AbstractState::empty();
assert!(state.get(SsaValue(42)).is_top());
}
#[test]
fn abstract_state_set_get() {
let mut state = AbstractState::empty();
let val = AbstractValue {
interval: IntervalFact::exact(10),
string: StringFact::top(),
bits: BitFact::top(),
path: PathFact::top(),
};
state.set(SsaValue(1), val.clone());
assert_eq!(state.get(SsaValue(1)), val);
}
#[test]
fn abstract_state_set_top_removes() {
let mut state = AbstractState::empty();
state.set(
SsaValue(1),
AbstractValue {
interval: IntervalFact::exact(5),
string: StringFact::top(),
bits: BitFact::top(),
path: PathFact::top(),
},
);
assert!(!state.get(SsaValue(1)).is_top());
state.set(SsaValue(1), AbstractValue::top());
assert!(state.get(SsaValue(1)).is_top());
assert!(state.values.is_empty());
}
#[test]
fn abstract_state_join() {
let mut a = AbstractState::empty();
a.set(
SsaValue(1),
AbstractValue {
interval: IntervalFact::exact(3),
string: StringFact::top(),
bits: BitFact::top(),
path: PathFact::top(),
},
);
a.set(
SsaValue(2),
AbstractValue {
interval: IntervalFact::exact(10),
string: StringFact::top(),
bits: BitFact::top(),
path: PathFact::top(),
},
);
let mut b = AbstractState::empty();
b.set(
SsaValue(1),
AbstractValue {
interval: IntervalFact::exact(7),
string: StringFact::top(),
bits: BitFact::top(),
path: PathFact::top(),
},
);
let j = a.join(&b);
let v1 = j.get(SsaValue(1));
assert_eq!(v1.interval.lo, Some(3));
assert_eq!(v1.interval.hi, Some(7));
assert!(j.get(SsaValue(2)).is_top());
}
#[test]
fn abstract_state_widen() {
let mut old = AbstractState::empty();
old.set(
SsaValue(1),
AbstractValue {
interval: IntervalFact {
lo: Some(0),
hi: Some(5),
},
string: StringFact::top(),
bits: BitFact::top(),
path: PathFact::top(),
},
);
let mut new = AbstractState::empty();
new.set(
SsaValue(1),
AbstractValue {
interval: IntervalFact {
lo: Some(0),
hi: Some(10),
},
string: StringFact::top(),
bits: BitFact::top(),
path: PathFact::top(),
},
);
let w = old.widen(&new);
let v1 = w.get(SsaValue(1));
assert_eq!(v1.interval.lo, Some(0)); assert_eq!(v1.interval.hi, None); }
#[test]
fn loop_carried_phi_join_and_widen() {
let init = IntervalFact::exact(0);
let inc1 = IntervalFact::exact(1);
let phi1 = init.join(&inc1);
assert_eq!(phi1.lo, Some(0));
assert_eq!(phi1.hi, Some(1));
let inc2 = IntervalFact {
lo: Some(1),
hi: Some(2),
};
let phi2 = phi1.join(&inc2);
assert_eq!(phi2.lo, Some(0));
assert_eq!(phi2.hi, Some(2));
let widened = phi1.widen(&phi2);
assert_eq!(widened.lo, Some(0));
assert_eq!(widened.hi, None);
let inc3 = IntervalFact {
lo: Some(1),
hi: None,
};
let phi3 = widened.join(&inc3);
assert_eq!(phi3.lo, Some(0));
assert_eq!(phi3.hi, None);
assert_eq!(phi3, widened); }
}