use crate::ssa::const_prop::ConstLattice;
use crate::ssa::ir::SsaValue;
use crate::ssa::type_facts::{TypeFactResult, TypeKind};
use serde::{Deserialize, Serialize};
use smallvec::SmallVec;
use std::collections::HashMap;
pub const MAX_PATH_ENV_ENTRIES: usize = 64;
pub const MAX_EQUALITY_EDGES: usize = 32;
pub const MAX_DISEQUALITY_EDGES: usize = 32;
pub const MAX_REFINE_PER_BLOCK: usize = 128;
pub const WIDEN_THRESHOLD: u8 = 3;
pub const MAX_RELATIONAL: usize = 16;
const MAX_NEQ: usize = 8;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum RelOp {
Lt,
Le,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum ConstValue {
Int(i64),
Str(String),
Bool(bool),
Null,
}
impl ConstValue {
pub fn from_const_lattice(cl: &ConstLattice) -> Option<Self> {
match cl {
ConstLattice::Int(i) => Some(ConstValue::Int(*i)),
ConstLattice::Str(s) => Some(ConstValue::Str(s.clone())),
ConstLattice::Bool(b) => Some(ConstValue::Bool(*b)),
ConstLattice::Null => Some(ConstValue::Null),
ConstLattice::Top | ConstLattice::Varying => None,
}
}
pub fn parse_literal(text: &str) -> Option<Self> {
let t = text.trim();
if t.is_empty() {
return None;
}
if t == "null" || t == "nil" || t == "None" || t == "undefined" || t == "NULL" {
return Some(ConstValue::Null);
}
if t == "true" || t == "True" || t == "TRUE" {
return Some(ConstValue::Bool(true));
}
if t == "false" || t == "False" || t == "FALSE" {
return Some(ConstValue::Bool(false));
}
if t.len() >= 2
&& ((t.starts_with('"') && t.ends_with('"'))
|| (t.starts_with('\'') && t.ends_with('\''))
|| (t.starts_with('`') && t.ends_with('`')))
{
return Some(ConstValue::Str(t[1..t.len() - 1].to_string()));
}
if let Ok(i) = t.parse::<i64>() {
return Some(ConstValue::Int(i));
}
None
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct TypeSet(u16);
impl TypeSet {
pub const TOP: Self = Self(0x0FFF);
pub const BOTTOM: Self = Self(0);
pub fn singleton(kind: &TypeKind) -> Self {
Self(1u16 << type_kind_index(kind))
}
pub fn contains(&self, kind: &TypeKind) -> bool {
self.0 & (1u16 << type_kind_index(kind)) != 0
}
pub fn meet(self, other: Self) -> Self {
Self(self.0 & other.0)
}
pub fn join(self, other: Self) -> Self {
Self(self.0 | other.0)
}
pub fn is_bottom(self) -> bool {
self.0 == 0
}
pub fn is_top(self) -> bool {
self == Self::TOP
}
pub fn complement(self) -> Self {
Self(!self.0 & Self::TOP.0)
}
pub fn is_singleton_of(&self, kind: &TypeKind) -> bool {
self.0 != 0 && self.0 == (1u16 << type_kind_index(kind))
}
pub fn as_singleton(&self) -> Option<TypeKind> {
if self.0 != 0 && self.0.count_ones() == 1 {
type_kind_from_index(self.0.trailing_zeros())
} else {
None
}
}
}
fn type_kind_index(kind: &TypeKind) -> u32 {
match kind {
TypeKind::String => 0,
TypeKind::Int => 1,
TypeKind::Bool => 2,
TypeKind::Object => 3,
TypeKind::Array => 4,
TypeKind::Null => 5,
TypeKind::Unknown => 6,
TypeKind::HttpResponse => 7,
TypeKind::DatabaseConnection => 8,
TypeKind::FileHandle => 9,
TypeKind::Url => 10,
TypeKind::HttpClient => 11,
TypeKind::LocalCollection => 12,
TypeKind::RequestBuilder => 13,
TypeKind::Dto(_) => 6,
}
}
fn type_kind_from_index(idx: u32) -> Option<TypeKind> {
match idx {
0 => Some(TypeKind::String),
1 => Some(TypeKind::Int),
2 => Some(TypeKind::Bool),
3 => Some(TypeKind::Object),
4 => Some(TypeKind::Array),
5 => Some(TypeKind::Null),
6 => Some(TypeKind::Unknown),
7 => Some(TypeKind::HttpResponse),
8 => Some(TypeKind::DatabaseConnection),
9 => Some(TypeKind::FileHandle),
10 => Some(TypeKind::Url),
11 => Some(TypeKind::HttpClient),
12 => Some(TypeKind::LocalCollection),
13 => Some(TypeKind::RequestBuilder),
_ => None,
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Nullability {
Unknown,
Null,
NonNull,
Bottom,
}
impl Nullability {
pub fn meet(self, other: Self) -> Self {
use Nullability::*;
match (self, other) {
(Bottom, _) | (_, Bottom) => Bottom,
(Unknown, x) | (x, Unknown) => x,
(Null, Null) => Null,
(NonNull, NonNull) => NonNull,
(Null, NonNull) | (NonNull, Null) => Bottom,
}
}
pub fn join(self, other: Self) -> Self {
use Nullability::*;
match (self, other) {
(Bottom, x) | (x, Bottom) => x,
(Unknown, _) | (_, Unknown) => Unknown,
(Null, Null) => Null,
(NonNull, NonNull) => NonNull,
(Null, NonNull) | (NonNull, Null) => Unknown,
}
}
pub fn negate(self) -> Self {
match self {
Self::Null => Self::NonNull,
Self::NonNull => Self::Null,
other => other,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum BoolState {
Unknown,
True,
False,
Bottom,
}
impl BoolState {
pub fn meet(self, other: Self) -> Self {
use BoolState::*;
match (self, other) {
(Bottom, _) | (_, Bottom) => Bottom,
(Unknown, x) | (x, Unknown) => x,
(True, True) => True,
(False, False) => False,
(True, False) | (False, True) => Bottom,
}
}
pub fn join(self, other: Self) -> Self {
use BoolState::*;
match (self, other) {
(Bottom, x) | (x, Bottom) => x,
(Unknown, _) | (_, Unknown) => Unknown,
(True, True) => True,
(False, False) => False,
(True, False) | (False, True) => Unknown,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ValueFact {
pub exact: Option<ConstValue>,
pub excluded: SmallVec<[ConstValue; 4]>,
pub lo: Option<i64>,
pub hi: Option<i64>,
pub lo_strict: bool,
pub hi_strict: bool,
pub null: Nullability,
pub bool_state: BoolState,
pub types: TypeSet,
}
impl ValueFact {
pub fn top() -> Self {
Self {
exact: None,
excluded: SmallVec::new(),
lo: None,
hi: None,
lo_strict: false,
hi_strict: false,
null: Nullability::Unknown,
bool_state: BoolState::Unknown,
types: TypeSet::TOP,
}
}
pub fn bottom() -> Self {
Self {
exact: None,
excluded: SmallVec::new(),
lo: None,
hi: None,
lo_strict: false,
hi_strict: false,
null: Nullability::Bottom,
bool_state: BoolState::Bottom,
types: TypeSet::BOTTOM,
}
}
pub fn is_bottom(&self) -> bool {
self.types.is_bottom()
|| self.null == Nullability::Bottom
|| self.bool_state == BoolState::Bottom
|| self.interval_empty()
|| self.exact_excluded_contradiction()
}
pub fn is_top(&self) -> bool {
self.exact.is_none()
&& self.excluded.is_empty()
&& self.lo.is_none()
&& self.hi.is_none()
&& self.null == Nullability::Unknown
&& self.bool_state == BoolState::Unknown
&& self.types.is_top()
}
fn interval_empty(&self) -> bool {
match (self.lo, self.hi) {
(Some(lo), Some(hi)) => {
if self.lo_strict || self.hi_strict {
lo >= hi
} else {
lo > hi
}
}
_ => false,
}
}
fn exact_excluded_contradiction(&self) -> bool {
if let Some(ref exact) = self.exact {
self.excluded.contains(exact)
} else {
false
}
}
pub fn meet(&self, other: &Self) -> Self {
let exact = match (&self.exact, &other.exact) {
(Some(a), Some(b)) => {
if a == b {
Some(a.clone())
} else {
return Self::bottom();
}
}
(Some(a), None) => Some(a.clone()),
(None, Some(b)) => Some(b.clone()),
(None, None) => None,
};
let mut excluded = self.excluded.clone();
for v in &other.excluded {
if excluded.len() >= MAX_NEQ {
break;
}
if !excluded.contains(v) {
excluded.push(v.clone());
}
}
let (lo, lo_strict) = tighten_lower(self.lo, self.lo_strict, other.lo, other.lo_strict);
let (hi, hi_strict) = tighten_upper(self.hi, self.hi_strict, other.hi, other.hi_strict);
let null = self.null.meet(other.null);
let bool_state = self.bool_state.meet(other.bool_state);
let types = self.types.meet(other.types);
let result = Self {
exact,
excluded,
lo,
hi,
lo_strict,
hi_strict,
null,
bool_state,
types,
};
if result.is_bottom() {
Self::bottom()
} else {
result
}
}
pub fn join(&self, other: &Self) -> Self {
let exact = match (&self.exact, &other.exact) {
(Some(a), Some(b)) if a == b => Some(a.clone()),
_ => None,
};
let excluded: SmallVec<[ConstValue; 4]> = self
.excluded
.iter()
.filter(|v| other.excluded.contains(v))
.cloned()
.collect();
let (lo, lo_strict) = widen_lower(self.lo, self.lo_strict, other.lo, other.lo_strict);
let (hi, hi_strict) = widen_upper(self.hi, self.hi_strict, other.hi, other.hi_strict);
let null = self.null.join(other.null);
let bool_state = self.bool_state.join(other.bool_state);
let types = self.types.join(other.types);
Self {
exact,
excluded,
lo,
hi,
lo_strict,
hi_strict,
null,
bool_state,
types,
}
}
pub fn widen(&self, other: &Self) -> Self {
let lo = if self.lo == other.lo && self.lo_strict == other.lo_strict {
self.lo
} else {
None
};
let lo_strict = if lo.is_some() { self.lo_strict } else { false };
let hi = if self.hi == other.hi && self.hi_strict == other.hi_strict {
self.hi
} else {
None
};
let hi_strict = if hi.is_some() { self.hi_strict } else { false };
let exact = if self.exact == other.exact {
self.exact.clone()
} else {
None
};
let excluded = if self.excluded.len() <= other.excluded.len()
&& self.excluded.iter().all(|v| other.excluded.contains(v))
{
other.excluded.clone()
} else {
SmallVec::new()
};
let null = self.null.join(other.null);
let bool_state = self.bool_state.join(other.bool_state);
let types = self.types.join(other.types);
Self {
exact,
excluded,
lo,
hi,
lo_strict,
hi_strict,
null,
bool_state,
types,
}
}
}
fn tighten_lower(
a: Option<i64>,
a_strict: bool,
b: Option<i64>,
b_strict: bool,
) -> (Option<i64>, bool) {
match (a, b) {
(None, None) => (None, false),
(Some(v), None) => (Some(v), a_strict),
(None, Some(v)) => (Some(v), b_strict),
(Some(va), Some(vb)) => {
if va > vb {
(Some(va), a_strict)
} else if vb > va {
(Some(vb), b_strict)
} else {
(Some(va), a_strict || b_strict)
}
}
}
}
fn tighten_upper(
a: Option<i64>,
a_strict: bool,
b: Option<i64>,
b_strict: bool,
) -> (Option<i64>, bool) {
match (a, b) {
(None, None) => (None, false),
(Some(v), None) => (Some(v), a_strict),
(None, Some(v)) => (Some(v), b_strict),
(Some(va), Some(vb)) => {
if va < vb {
(Some(va), a_strict)
} else if vb < va {
(Some(vb), b_strict)
} else {
(Some(va), a_strict || b_strict)
}
}
}
}
fn widen_lower(
a: Option<i64>,
a_strict: bool,
b: Option<i64>,
b_strict: bool,
) -> (Option<i64>, bool) {
match (a, b) {
(None, _) | (_, None) => (None, false),
(Some(va), Some(vb)) => {
if va < vb {
(Some(va), a_strict)
} else if vb < va {
(Some(vb), b_strict)
} else {
(Some(va), a_strict && b_strict)
}
}
}
}
fn widen_upper(
a: Option<i64>,
a_strict: bool,
b: Option<i64>,
b_strict: bool,
) -> (Option<i64>, bool) {
match (a, b) {
(None, _) | (_, None) => (None, false),
(Some(va), Some(vb)) => {
if va > vb {
(Some(va), a_strict)
} else if vb > va {
(Some(vb), b_strict)
} else {
(Some(va), a_strict && b_strict)
}
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct UnionFind {
parent: SmallVec<[(SsaValue, SsaValue); 8]>,
edges: usize,
}
impl UnionFind {
pub fn new() -> Self {
Self {
parent: SmallVec::new(),
edges: 0,
}
}
pub fn find(&mut self, x: SsaValue) -> SsaValue {
let parent = self.parent.iter().find(|(k, _)| *k == x).map(|(_, v)| *v);
match parent {
None => x, Some(p) if p == x => x,
Some(p) => {
let root = self.find(p);
if root != p
&& let Some(entry) = self.parent.iter_mut().find(|(k, _)| *k == x)
{
entry.1 = root;
}
root
}
}
}
pub fn find_immutable(&self, x: SsaValue) -> SsaValue {
let mut current = x;
loop {
let parent = self
.parent
.iter()
.find(|(k, _)| *k == current)
.map(|(_, v)| *v);
match parent {
None => return current,
Some(p) if p == current => return current,
Some(p) => current = p,
}
}
}
pub fn union(&mut self, a: SsaValue, b: SsaValue) -> bool {
let ra = self.find(a);
let rb = self.find(b);
if ra == rb {
return false; }
if self.edges >= MAX_EQUALITY_EDGES {
return false; }
let (root, child) = if ra.0 <= rb.0 { (ra, rb) } else { (rb, ra) };
match self.parent.iter_mut().find(|(k, _)| *k == child) {
Some(entry) => entry.1 = root,
None => self.parent.push((child, root)),
}
self.edges += 1;
true
}
pub fn same_class(&self, a: SsaValue, b: SsaValue) -> bool {
self.find_immutable(a) == self.find_immutable(b)
}
pub fn class_members(&self, v: SsaValue) -> SmallVec<[SsaValue; 4]> {
let root = self.find_immutable(v);
let mut members = SmallVec::new();
members.push(root);
for &(k, _) in &self.parent {
if self.find_immutable(k) == root && !members.contains(&k) {
members.push(k);
}
}
members
}
pub fn edge_count(&self) -> usize {
self.edges
}
}
impl Default for UnionFind {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PathEnv {
facts: SmallVec<[(SsaValue, ValueFact); 8]>,
pub(crate) uf: UnionFind,
disequalities: SmallVec<[(SsaValue, SsaValue); 4]>,
relational: SmallVec<[(SsaValue, RelOp, SsaValue); 8]>,
unsat: bool,
meet_counts: SmallVec<[(SsaValue, u8); 8]>,
refine_count: u16,
}
impl PathEnv {
pub fn empty() -> Self {
Self {
facts: SmallVec::new(),
uf: UnionFind::new(),
disequalities: SmallVec::new(),
relational: SmallVec::new(),
unsat: false,
meet_counts: SmallVec::new(),
refine_count: 0,
}
}
pub fn is_unsat(&self) -> bool {
self.unsat
}
pub fn get(&self, v: SsaValue) -> ValueFact {
let canonical = self.uf.find_immutable(v);
self.facts
.binary_search_by_key(&canonical, |(k, _)| *k)
.ok()
.map(|idx| self.facts[idx].1.clone())
.unwrap_or_else(ValueFact::top)
}
pub fn refine(&mut self, v: SsaValue, fact: &ValueFact) {
if self.unsat {
return;
}
if self.refine_count >= MAX_REFINE_PER_BLOCK as u16 {
return; }
let canonical = self.uf.find_immutable(v);
self.refine_single(canonical, fact);
let members = self.uf.class_members(canonical);
for member in members {
if member != canonical {
self.refine_single(member, fact);
}
}
}
fn refine_single(&mut self, v: SsaValue, fact: &ValueFact) {
if self.unsat {
return;
}
self.refine_count = self.refine_count.saturating_add(1);
let pos = self.facts.binary_search_by_key(&v, |(k, _)| *k);
if pos.is_err() && self.facts.len() >= MAX_PATH_ENV_ENTRIES {
return; }
let count = self.get_meet_count(v);
let existing = match pos {
Ok(idx) => &self.facts[idx].1,
Err(_) => &ValueFact::top(), };
let new_fact = if count >= WIDEN_THRESHOLD {
existing.widen(fact)
} else {
existing.meet(fact)
};
self.increment_meet_count(v);
if new_fact.is_bottom() {
self.unsat = true;
return;
}
match pos {
Ok(idx) => self.facts[idx].1 = new_fact,
Err(idx) => self.facts.insert(idx, (v, new_fact)),
}
}
fn get_meet_count(&self, v: SsaValue) -> u8 {
self.meet_counts
.binary_search_by_key(&v, |(k, _)| *k)
.ok()
.map(|idx| self.meet_counts[idx].1)
.unwrap_or(0)
}
fn increment_meet_count(&mut self, v: SsaValue) {
match self.meet_counts.binary_search_by_key(&v, |(k, _)| *k) {
Ok(idx) => self.meet_counts[idx].1 = self.meet_counts[idx].1.saturating_add(1),
Err(idx) => self.meet_counts.insert(idx, (v, 1)),
}
}
pub fn assert_equal(&mut self, a: SsaValue, b: SsaValue) {
if self.unsat || a == b {
return;
}
let ra = self.uf.find_immutable(a);
let rb = self.uf.find_immutable(b);
if ra == rb {
return; }
let pair = (ra.min(rb), ra.max(rb));
if self.disequalities.contains(&pair) {
self.unsat = true;
return;
}
for &(lhs, op, rhs) in &self.relational {
if op == RelOp::Lt && ((lhs == ra && rhs == rb) || (lhs == rb && rhs == ra)) {
self.unsat = true;
return;
}
}
let fa = self.get(ra);
let fb = self.get(rb);
let merged = fa.meet(&fb);
if merged.is_bottom() {
self.unsat = true;
return;
}
self.uf.union(a, b);
let new_rep = self.uf.find_immutable(a);
self.refine_single(new_rep, &merged);
}
pub fn assert_not_equal(&mut self, a: SsaValue, b: SsaValue) {
if self.unsat {
return;
}
if a == b {
self.unsat = true;
return;
}
let ra = self.uf.find_immutable(a);
let rb = self.uf.find_immutable(b);
if ra == rb {
self.unsat = true;
return;
}
let pair = (ra.min(rb), ra.max(rb));
if self.disequalities.contains(&pair) {
return; }
if self.disequalities.len() < MAX_DISEQUALITY_EDGES {
match self.disequalities.binary_search(&pair) {
Ok(_) => {} Err(idx) => self.disequalities.insert(idx, pair),
}
}
let fa = self.get(ra);
let fb = self.get(rb);
if let Some(ref cv) = fa.exact {
let mut neq_fact = ValueFact::top();
neq_fact.excluded.push(cv.clone());
self.refine_single(rb, &neq_fact);
}
if let Some(ref cv) = fb.exact {
let mut neq_fact = ValueFact::top();
neq_fact.excluded.push(cv.clone());
self.refine_single(ra, &neq_fact);
}
}
pub fn assert_relational(&mut self, a: SsaValue, op: RelOp, b: SsaValue) {
if self.unsat {
return;
}
let ra = self.uf.find_immutable(a);
let rb = self.uf.find_immutable(b);
if ra == rb {
if op == RelOp::Lt {
self.unsat = true;
}
return;
}
for &(lhs, existing_op, rhs) in &self.relational {
if lhs == rb && rhs == ra {
if op == RelOp::Lt || existing_op == RelOp::Lt {
self.unsat = true;
return;
}
}
}
if self.check_relational_cycle(ra, rb, op) {
self.unsat = true;
return;
}
let already_present = self
.relational
.iter()
.any(|&(l, o, r)| l == ra && o == op && r == rb);
if already_present {
} else {
if self.relational.len() < MAX_RELATIONAL {
self.relational.push((ra, op, rb));
}
}
self.refine_relational_intervals(ra, op, rb);
}
fn check_relational_cycle(
&self,
target: SsaValue,
start: SsaValue,
new_edge_op: RelOp,
) -> bool {
const MAX_DEPTH: u8 = 4;
let mut has_strict = new_edge_op == RelOp::Lt;
let mut current = start;
for _ in 0..MAX_DEPTH {
let mut found_next = false;
for &(lhs, op, rhs) in &self.relational {
if lhs == current {
if rhs == target {
if has_strict || op == RelOp::Lt {
return true;
}
return false;
}
if op == RelOp::Lt {
has_strict = true;
}
current = rhs;
found_next = true;
break;
}
}
if !found_next {
break;
}
}
false
}
fn refine_relational_intervals(&mut self, a: SsaValue, op: RelOp, b: SsaValue) {
let fact_a = self.get(a);
let fact_b = self.get(b);
if let Some(hi_b) = fact_b.hi {
let new_hi = match op {
RelOp::Lt => {
if hi_b != i64::MIN {
Some(hi_b - 1)
} else {
None }
}
RelOp::Le => {
Some(hi_b)
}
};
if let Some(h) = new_hi {
let mut refine_fact = ValueFact::top();
refine_fact.hi = Some(h);
self.refine(a, &refine_fact);
}
}
if let Some(lo_a) = fact_a.lo {
let new_lo = match op {
RelOp::Lt => {
if lo_a != i64::MAX {
Some(lo_a + 1)
} else {
None }
}
RelOp::Le => {
Some(lo_a)
}
};
if let Some(l) = new_lo {
let mut refine_fact = ValueFact::top();
refine_fact.lo = Some(l);
self.refine(b, &refine_fact);
}
}
}
pub fn join(&self, other: &Self) -> Self {
if self.unsat {
return other.clone();
}
if other.unsat {
return self.clone();
}
let mut facts = SmallVec::new();
let (mut i, mut j) = (0, 0);
while i < self.facts.len() && j < other.facts.len() {
match self.facts[i].0.cmp(&other.facts[j].0) {
std::cmp::Ordering::Less => {
i += 1;
}
std::cmp::Ordering::Greater => {
j += 1;
}
std::cmp::Ordering::Equal => {
let joined = self.facts[i].1.join(&other.facts[j].1);
if !joined.is_top() {
facts.push((self.facts[i].0, joined));
}
i += 1;
j += 1;
}
}
}
let equalities_self = &self.uf;
let equalities_other = &other.uf;
let mut uf = UnionFind::new();
for &(k, _) in &equalities_self.parent {
let rep_self = equalities_self.find_immutable(k);
if equalities_other.same_class(k, rep_self) {
uf.union(k, rep_self);
}
}
let disequalities: SmallVec<[(SsaValue, SsaValue); 4]> = self
.disequalities
.iter()
.filter(|pair| other.disequalities.contains(pair))
.cloned()
.collect();
let relational: SmallVec<[(SsaValue, RelOp, SsaValue); 8]> = self
.relational
.iter()
.filter(|rel| other.relational.contains(rel))
.cloned()
.collect();
PathEnv {
facts,
uf,
disequalities,
relational,
unsat: false,
meet_counts: SmallVec::new(), refine_count: 0,
}
}
pub fn seed_from_optimization(
&mut self,
const_values: &HashMap<SsaValue, ConstLattice>,
type_facts: &TypeFactResult,
) {
for (v, cl) in const_values {
if let Some(cv) = ConstValue::from_const_lattice(cl) {
let mut fact = ValueFact::top();
fact.exact = Some(cv.clone());
match &cv {
ConstValue::Int(i) => {
fact.lo = Some(*i);
fact.hi = Some(*i);
fact.types = TypeSet::singleton(&TypeKind::Int);
fact.null = Nullability::NonNull;
}
ConstValue::Bool(b) => {
fact.bool_state = if *b {
BoolState::True
} else {
BoolState::False
};
fact.types = TypeSet::singleton(&TypeKind::Bool);
fact.null = Nullability::NonNull;
}
ConstValue::Null => {
fact.null = Nullability::Null;
fact.types = TypeSet::singleton(&TypeKind::Null);
}
ConstValue::Str(_) => {
fact.types = TypeSet::singleton(&TypeKind::String);
fact.null = Nullability::NonNull;
}
}
self.refine_single(*v, &fact);
}
}
for (v, tf) in &type_facts.facts {
let mut fact = ValueFact::top();
fact.types = TypeSet::singleton(&tf.kind);
if !tf.nullable && tf.kind != TypeKind::Null {
fact.null = Nullability::NonNull;
}
self.refine_single(*v, &fact);
}
}
pub fn reset_refine_count(&mut self) {
self.refine_count = 0;
}
pub fn fact_count(&self) -> usize {
self.facts.len()
}
pub fn facts(&self) -> &[(SsaValue, ValueFact)] {
&self.facts
}
pub fn disequalities(&self) -> &[(SsaValue, SsaValue)] {
&self.disequalities
}
pub fn relational(&self) -> &[(SsaValue, RelOp, SsaValue)] {
&self.relational
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn is_singleton_of_matching() {
let ts = TypeSet::singleton(&TypeKind::Int);
assert!(ts.is_singleton_of(&TypeKind::Int));
}
#[test]
fn is_singleton_of_non_matching() {
let ts = TypeSet::singleton(&TypeKind::Int);
assert!(!ts.is_singleton_of(&TypeKind::String));
}
#[test]
fn is_singleton_of_multi_type() {
let ts = TypeSet::singleton(&TypeKind::Int).join(TypeSet::singleton(&TypeKind::String));
assert!(!ts.is_singleton_of(&TypeKind::Int));
assert!(!ts.is_singleton_of(&TypeKind::String));
}
#[test]
fn is_singleton_of_empty() {
assert!(!TypeSet::BOTTOM.is_singleton_of(&TypeKind::Int));
}
#[test]
fn as_singleton_returns_kind_for_singleton() {
let ts = TypeSet::singleton(&TypeKind::HttpResponse);
assert_eq!(ts.as_singleton(), Some(TypeKind::HttpResponse));
}
#[test]
fn as_singleton_none_for_multi_type() {
let ts = TypeSet::singleton(&TypeKind::Int).join(TypeSet::singleton(&TypeKind::Bool));
assert_eq!(ts.as_singleton(), None);
}
#[test]
fn as_singleton_none_for_empty() {
assert_eq!(TypeSet::BOTTOM.as_singleton(), None);
}
#[test]
fn type_kind_index_round_trip() {
let all_kinds = [
TypeKind::String,
TypeKind::Int,
TypeKind::Bool,
TypeKind::Object,
TypeKind::Array,
TypeKind::Null,
TypeKind::Unknown,
TypeKind::HttpResponse,
TypeKind::DatabaseConnection,
TypeKind::FileHandle,
TypeKind::Url,
TypeKind::HttpClient,
];
for kind in &all_kinds {
let idx = type_kind_index(kind);
let recovered = type_kind_from_index(idx);
assert_eq!(
recovered.as_ref(),
Some(kind),
"round-trip failed for {:?} at index {}",
kind,
idx
);
}
}
#[test]
fn join_both_narrow_same_type() {
let v = SsaValue(0);
let mut env1 = PathEnv::empty();
let mut fact1 = ValueFact::top();
fact1.types = TypeSet::singleton(&TypeKind::Int);
env1.refine(v, &fact1);
let mut env2 = PathEnv::empty();
let mut fact2 = ValueFact::top();
fact2.types = TypeSet::singleton(&TypeKind::Int);
env2.refine(v, &fact2);
let joined = env1.join(&env2);
let result = joined.get(v);
assert!(
result.types.is_singleton_of(&TypeKind::Int),
"expected singleton Int, got {:?}",
result.types
);
}
#[test]
fn join_different_types_produces_union() {
let v = SsaValue(0);
let mut env1 = PathEnv::empty();
let mut fact1 = ValueFact::top();
fact1.types = TypeSet::singleton(&TypeKind::Int);
env1.refine(v, &fact1);
let mut env2 = PathEnv::empty();
let mut fact2 = ValueFact::top();
fact2.types = TypeSet::singleton(&TypeKind::String);
env2.refine(v, &fact2);
let joined = env1.join(&env2);
let result = joined.get(v);
assert!(result.types.contains(&TypeKind::Int));
assert!(result.types.contains(&TypeKind::String));
assert!(result.types.as_singleton().is_none());
}
#[test]
fn join_one_side_missing_drops_entry() {
let v = SsaValue(0);
let mut env1 = PathEnv::empty();
let mut fact1 = ValueFact::top();
fact1.types = TypeSet::singleton(&TypeKind::Int);
env1.refine(v, &fact1);
let env2 = PathEnv::empty();
let joined = env1.join(&env2);
let result = joined.get(v);
assert!(
result.is_top(),
"expected Top for key absent on one side, got {:?}",
result
);
}
}