use super::functions::*;
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet, VecDeque};
#[derive(Debug, Clone)]
pub struct EraserState {
pub candidate_set: Option<BTreeSet<usize>>,
pub race_reported: bool,
pub last_writer: Option<usize>,
pub has_write: bool,
}
impl EraserState {
pub fn new() -> Self {
Self {
candidate_set: None,
race_reported: false,
last_writer: None,
has_write: false,
}
}
pub fn observe_access(&mut self, thread: usize, locks: &BTreeSet<usize>, is_write: bool) {
self.candidate_set = Some(match &self.candidate_set {
None => locks.clone(),
Some(prev) => prev.intersection(locks).copied().collect(),
});
if let Some(ref cs) = self.candidate_set {
if cs.is_empty() && (is_write || self.has_write) {
if self.last_writer.is_some_and(|w| w != thread) || is_write {
self.race_reported = true;
}
}
}
if is_write {
self.has_write = true;
self.last_writer = Some(thread);
}
}
pub fn has_race(&self) -> bool {
self.race_reported
}
}
pub struct FixpointSolver<V: Clone + PartialEq> {
pub num_nodes: usize,
pub values: Vec<V>,
pub edges: Vec<Vec<usize>>,
pub bottom: V,
pub join: fn(&V, &V) -> V,
pub transfer: fn(usize, &V) -> V,
}
impl<V: Clone + PartialEq> FixpointSolver<V> {
pub fn new(
num_nodes: usize,
edges: Vec<Vec<usize>>,
bottom: V,
join: fn(&V, &V) -> V,
transfer: fn(usize, &V) -> V,
) -> Self {
let values = vec![bottom.clone(); num_nodes];
Self {
num_nodes,
values,
edges,
bottom,
join,
transfer,
}
}
pub fn solve(&mut self) {
let mut worklist: VecDeque<usize> = (0..self.num_nodes).collect();
while let Some(n) = worklist.pop_front() {
let out = (self.transfer)(n, &self.values[n]);
for &succ in &self.edges[n].clone() {
let new_val = (self.join)(&self.values[succ], &out);
if new_val != self.values[succ] {
self.values[succ] = new_val;
worklist.push_back(succ);
}
}
}
}
}
#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum SecurityLevel {
Low,
High,
}
impl SecurityLevel {
pub fn join(&self, other: &SecurityLevel) -> SecurityLevel {
if *self == SecurityLevel::High || *other == SecurityLevel::High {
SecurityLevel::High
} else {
SecurityLevel::Low
}
}
pub fn can_flow_to(&self, other: &SecurityLevel) -> bool {
*self <= *other
}
}
#[allow(dead_code)]
#[derive(Debug, Clone, Default)]
pub struct IFCTracker {
pub labels: std::collections::HashMap<String, SecurityLevel>,
pub violations: Vec<(String, SecurityLevel)>,
}
impl IFCTracker {
pub fn new() -> Self {
Self::default()
}
pub fn assign(&mut self, var: impl Into<String>, level: SecurityLevel) {
self.labels.insert(var.into(), level);
}
pub fn label_of(&self, var: &str) -> SecurityLevel {
self.labels.get(var).cloned().unwrap_or(SecurityLevel::Low)
}
pub fn propagate(&mut self, dst: impl Into<String>, srcs: &[&str]) {
let joined = srcs
.iter()
.fold(SecurityLevel::Low, |acc, &s| acc.join(&self.label_of(s)));
let dst = dst.into();
self.labels.insert(dst, joined);
}
pub fn check_flow(&mut self, var: &str, required: &SecurityLevel) {
let lbl = self.label_of(var);
if !lbl.can_flow_to(required) {
self.violations.push((var.to_string(), required.clone()));
}
}
pub fn has_violation(&self) -> bool {
!self.violations.is_empty()
}
}
#[allow(dead_code)]
#[derive(Debug, Clone, Default)]
pub struct ConstPropState {
pub values: std::collections::HashMap<String, Option<i64>>,
}
impl ConstPropState {
pub fn new() -> Self {
Self::default()
}
pub fn set_const(&mut self, var: impl Into<String>, val: i64) {
self.values.insert(var.into(), Some(val));
}
pub fn set_top(&mut self, var: impl Into<String>) {
self.values.insert(var.into(), None);
}
pub fn get(&self, var: &str) -> Option<i64> {
self.values.get(var).copied().flatten()
}
pub fn join(&self, other: &ConstPropState) -> ConstPropState {
let mut result = ConstPropState::new();
for (var, &val) in &self.values {
let merged = match (val, other.values.get(var).copied().flatten()) {
(Some(a), Some(b)) if a == b => Some(a),
_ => None,
};
result.values.insert(var.clone(), merged);
}
for var in other.values.keys() {
if !result.values.contains_key(var) {
result.values.insert(var.clone(), None);
}
}
result
}
pub fn fold_add(&self, lhs: &str, rhs: &str) -> Option<i64> {
self.get(lhs)?.checked_add(self.get(rhs)?)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Interval {
Bottom,
Range(i64, i64),
}
impl Interval {
pub fn bot() -> Self {
Interval::Bottom
}
pub fn top() -> Self {
Interval::Range(i64::MIN, i64::MAX)
}
pub fn single(v: i64) -> Self {
Interval::Range(v, v)
}
pub fn leq(&self, other: &Self) -> bool {
match (self, other) {
(Interval::Bottom, _) => true,
(_, Interval::Bottom) => false,
(Interval::Range(a, b), Interval::Range(c, d)) => *a >= *c && *b <= *d,
}
}
pub fn join(&self, other: &Self) -> Self {
match (self, other) {
(Interval::Bottom, x) | (x, Interval::Bottom) => x.clone(),
(Interval::Range(a, b), Interval::Range(c, d)) => {
Interval::Range((*a).min(*c), (*b).max(*d))
}
}
}
pub fn meet(&self, other: &Self) -> Self {
match (self, other) {
(Interval::Bottom, _) | (_, Interval::Bottom) => Interval::Bottom,
(Interval::Range(a, b), Interval::Range(c, d)) => {
let lo = (*a).max(*c);
let hi = (*b).min(*d);
if lo > hi {
Interval::Bottom
} else {
Interval::Range(lo, hi)
}
}
}
}
pub fn widen(&self, other: &Self) -> Self {
match (self, other) {
(Interval::Bottom, x) => x.clone(),
(x, Interval::Bottom) => x.clone(),
(Interval::Range(a, b), Interval::Range(c, d)) => {
let lo = if *c < *a { i64::MIN } else { *a };
let hi = if *d > *b { i64::MAX } else { *b };
Interval::Range(lo, hi)
}
}
}
pub fn narrow(&self, other: &Self) -> Self {
match (self, other) {
(Interval::Bottom, _) => Interval::Bottom,
(x, Interval::Bottom) => x.clone(),
(Interval::Range(a, b), Interval::Range(c, d)) => {
let lo = if *a == i64::MIN { *c } else { *a };
let hi = if *b == i64::MAX { *d } else { *b };
if lo > hi {
Interval::Bottom
} else {
Interval::Range(lo, hi)
}
}
}
}
pub fn add(&self, other: &Self) -> Self {
match (self, other) {
(Interval::Bottom, _) | (_, Interval::Bottom) => Interval::Bottom,
(Interval::Range(a, b), Interval::Range(c, d)) => {
Interval::Range(a.saturating_add(*c), b.saturating_add(*d))
}
}
}
pub fn contains(&self, v: i64) -> bool {
match self {
Interval::Bottom => false,
Interval::Range(lo, hi) => *lo <= v && v <= *hi,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct TaintState {
pub tainted: HashSet<String>,
pub sanitized: HashSet<String>,
}
impl TaintState {
pub fn new() -> Self {
Self::default()
}
pub fn add_source(&mut self, var: impl Into<String>) {
let v = var.into();
self.sanitized.remove(&v);
self.tainted.insert(v);
}
pub fn sanitize(&mut self, var: &str) {
self.tainted.remove(var);
self.sanitized.insert(var.to_string());
}
pub fn propagate(&mut self, dst: impl Into<String>, srcs: &[&str]) {
let dst = dst.into();
let tainted = srcs.iter().any(|&s| self.tainted.contains(s));
if tainted {
self.sanitized.remove(&dst);
self.tainted.insert(dst);
} else {
self.tainted.remove(&dst);
}
}
pub fn violates(&self, var: &str) -> bool {
self.tainted.contains(var) && !self.sanitized.contains(var)
}
pub fn join(&self, other: &TaintState) -> TaintState {
TaintState {
tainted: self.tainted.union(&other.tainted).cloned().collect(),
sanitized: self
.sanitized
.intersection(&other.sanitized)
.cloned()
.collect(),
}
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct TypestateAutomaton {
pub num_states: usize,
pub initial: usize,
pub accepting: BTreeSet<usize>,
pub transitions: Vec<std::collections::BTreeMap<String, usize>>,
pub error_states: BTreeSet<usize>,
}
impl TypestateAutomaton {
pub fn new(num_states: usize, initial: usize) -> Self {
Self {
num_states,
initial,
accepting: BTreeSet::new(),
transitions: vec![std::collections::BTreeMap::new(); num_states],
error_states: BTreeSet::new(),
}
}
pub fn add_transition(&mut self, from: usize, op: &str, to: usize) {
self.transitions[from].insert(op.to_string(), to);
}
pub fn set_accepting(&mut self, state: usize) {
self.accepting.insert(state);
}
pub fn simulate(&self, ops: &[&str]) -> Option<usize> {
let mut state = self.initial;
for &op in ops {
match self.transitions[state].get(op) {
Some(&next) => state = next,
None => return None,
}
}
Some(state)
}
pub fn accepts(&self, ops: &[&str]) -> bool {
self.simulate(ops)
.map_or(false, |s| self.accepting.contains(&s))
}
pub fn violates(&self, ops: &[&str]) -> bool {
self.simulate(ops).is_none()
}
}
#[derive(Debug, Clone, Default)]
pub struct AndersenPTA {
pub num_vars: usize,
pub pts: Vec<BTreeSet<usize>>,
pub copy_edges: Vec<BTreeSet<usize>>,
pub store: Vec<Vec<(usize, usize)>>,
pub load: Vec<Vec<(usize, usize)>>,
}
impl AndersenPTA {
pub fn new(num_vars: usize) -> Self {
Self {
num_vars,
pts: vec![BTreeSet::new(); num_vars],
copy_edges: vec![BTreeSet::new(); num_vars],
store: vec![vec![]; num_vars],
load: vec![vec![]; num_vars],
}
}
pub fn add_alloc(&mut self, a: usize, site: usize) {
self.pts[a].insert(site);
}
pub fn add_copy(&mut self, src: usize, dst: usize) {
self.copy_edges[src].insert(dst);
}
pub fn solve(&mut self) {
let mut worklist: VecDeque<usize> = (0..self.num_vars).collect();
while let Some(v) = worklist.pop_front() {
let pts_v: Vec<usize> = self.pts[v].iter().copied().collect();
for &dst in &self.copy_edges[v].clone() {
let added: Vec<usize> = pts_v
.iter()
.filter(|&&s| self.pts[dst].insert(s))
.copied()
.collect();
if !added.is_empty() {
worklist.push_back(dst);
}
}
}
}
pub fn may_alias(&self, a: usize, b: usize) -> bool {
!self.pts[a].is_disjoint(&self.pts[b])
}
}
#[allow(dead_code)]
#[derive(Debug, Clone, Default)]
pub struct PDGraph {
pub num_stmts: usize,
pub data_edges: Vec<BTreeSet<usize>>,
pub ctrl_edges: Vec<BTreeSet<usize>>,
}
impl PDGraph {
pub fn new(num_stmts: usize) -> Self {
Self {
num_stmts,
data_edges: vec![BTreeSet::new(); num_stmts],
ctrl_edges: vec![BTreeSet::new(); num_stmts],
}
}
pub fn add_data_edge(&mut self, src: usize, dst: usize) {
self.data_edges[src].insert(dst);
}
pub fn add_ctrl_edge(&mut self, src: usize, dst: usize) {
self.ctrl_edges[src].insert(dst);
}
pub fn backward_slice(&self, criterion: usize) -> BTreeSet<usize> {
let mut slice = BTreeSet::new();
let mut worklist: VecDeque<usize> = VecDeque::new();
worklist.push_back(criterion);
while let Some(s) = worklist.pop_front() {
if slice.insert(s) {
for src in 0..self.num_stmts {
if self.data_edges[src].contains(&s) || self.ctrl_edges[src].contains(&s) {
if !slice.contains(&src) {
worklist.push_back(src);
}
}
}
}
}
slice
}
pub fn forward_slice(&self, criterion: usize) -> BTreeSet<usize> {
let mut slice = BTreeSet::new();
let mut worklist: VecDeque<usize> = VecDeque::new();
worklist.push_back(criterion);
while let Some(s) = worklist.pop_front() {
if slice.insert(s) {
for &dst in &self.data_edges[s] {
if !slice.contains(&dst) {
worklist.push_back(dst);
}
}
for &dst in &self.ctrl_edges[s] {
if !slice.contains(&dst) {
worklist.push_back(dst);
}
}
}
}
slice
}
}
#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Nullability {
Null,
NonNull,
MaybeNull,
}
#[allow(dead_code)]
#[derive(Debug, Clone, Default)]
pub struct NullTracker {
pub status: std::collections::HashMap<String, Nullability>,
pub alarms: Vec<String>,
}
impl NullTracker {
pub fn new() -> Self {
Self::default()
}
pub fn declare_non_null(&mut self, var: impl Into<String>) {
self.status.insert(var.into(), Nullability::NonNull);
}
pub fn declare_maybe_null(&mut self, var: impl Into<String>) {
self.status.insert(var.into(), Nullability::MaybeNull);
}
pub fn declare_null(&mut self, var: impl Into<String>) {
self.status.insert(var.into(), Nullability::Null);
}
pub fn get(&self, var: &str) -> &Nullability {
self.status.get(var).unwrap_or(&Nullability::MaybeNull)
}
pub fn dereference(&mut self, var: &str) {
match self.get(var) {
Nullability::Null | Nullability::MaybeNull => self.alarms.push(var.to_string()),
Nullability::NonNull => {}
}
}
pub fn join(&self, other: &NullTracker) -> NullTracker {
let mut result = NullTracker::new();
for (var, lhs) in &self.status {
let rhs = other.get(var);
let merged = match (lhs, rhs) {
(Nullability::NonNull, Nullability::NonNull) => Nullability::NonNull,
(Nullability::Null, Nullability::Null) => Nullability::Null,
_ => Nullability::MaybeNull,
};
result.status.insert(var.clone(), merged);
}
for var in other.status.keys() {
if !result.status.contains_key(var) {
result.status.insert(var.clone(), Nullability::MaybeNull);
}
}
result
}
pub fn has_alarm(&self) -> bool {
!self.alarms.is_empty()
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum Sign {
Bottom,
Neg,
Zero,
Pos,
Top,
}
impl Sign {
pub fn of(v: i64) -> Self {
if v < 0 {
Sign::Neg
} else if v == 0 {
Sign::Zero
} else {
Sign::Pos
}
}
pub fn join(&self, other: &Self) -> Self {
if self == other {
return self.clone();
}
match (self, other) {
(Sign::Bottom, x) | (x, Sign::Bottom) => x.clone(),
_ => Sign::Top,
}
}
pub fn leq(&self, other: &Self) -> bool {
self == &Sign::Bottom || other == &Sign::Top || self == other
}
pub fn add(&self, other: &Self) -> Self {
match (self, other) {
(Sign::Bottom, _) | (_, Sign::Bottom) => Sign::Bottom,
(Sign::Zero, x) | (x, Sign::Zero) => x.clone(),
(Sign::Pos, Sign::Pos) => Sign::Pos,
(Sign::Neg, Sign::Neg) => Sign::Neg,
_ => Sign::Top,
}
}
pub fn neg(&self) -> Self {
match self {
Sign::Pos => Sign::Neg,
Sign::Neg => Sign::Pos,
x => x.clone(),
}
}
}