use super::abstract_interp::{AbstractState, AbstractValue, Nullability};
#[derive(Debug, Clone, PartialEq)]
pub enum GuardCondition {
Eq {
var: String,
value: i64,
},
Neq {
var: String,
value: i64,
},
Lt {
var: String,
value: i64,
},
Le {
var: String,
value: i64,
},
Gt {
var: String,
value: i64,
},
Ge {
var: String,
value: i64,
},
NotNull {
var: String,
},
IsNull {
var: String,
},
Truthy {
var: String,
},
Falsy {
var: String,
},
}
fn is_identifier(s: &str) -> bool {
if s.is_empty() {
return false;
}
let mut chars = s.chars();
match chars.next() {
Some(c) if c.is_alphabetic() || c == '_' => {}
_ => return false,
}
chars.all(|c| c.is_alphanumeric() || c == '_')
}
fn parse_int_literal(s: &str) -> Option<i64> {
s.parse::<i64>().ok()
}
fn flip_operator(op: &str) -> Option<&'static str> {
match op {
"==" => Some("=="),
"!=" => Some("!="),
"<" => Some(">"),
"<=" => Some(">="),
">" => Some("<"),
">=" => Some("<="),
_ => None,
}
}
pub fn parse_guard_condition(condition: &str) -> Option<GuardCondition> {
let s = condition.trim();
if s.is_empty() {
return None;
}
if let Some(rest) = s.strip_suffix(".is_some()") {
let var = rest.trim();
if is_identifier(var) {
return Some(GuardCondition::NotNull {
var: var.to_string(),
});
}
}
if let Some(rest) = s.strip_suffix(".is_none()") {
let var = rest.trim();
if is_identifier(var) {
return Some(GuardCondition::IsNull {
var: var.to_string(),
});
}
}
if let Some(var_part) = s.strip_suffix(" is not None") {
let var = var_part.trim();
if is_identifier(var) {
return Some(GuardCondition::NotNull {
var: var.to_string(),
});
}
}
if let Some(var_part) = s.strip_suffix(" is None") {
let var = var_part.trim();
if is_identifier(var) {
return Some(GuardCondition::IsNull {
var: var.to_string(),
});
}
}
if let Some(rest) = s.strip_prefix("not ") {
let var = rest.trim();
if is_identifier(var) {
return Some(GuardCondition::Falsy {
var: var.to_string(),
});
}
return None;
}
let two_char_ops = ["!==", "===", ">=", "<=", "!=", "=="];
let one_char_ops = [">", "<"];
for &op in &two_char_ops {
if let Some(result) = try_comparison_split(s, op) {
return Some(result);
}
}
for &op in &one_char_ops {
if let Some(result) = try_comparison_split(s, op) {
return Some(result);
}
}
if let Some(rest) = s.strip_prefix('!') {
let var = rest.trim();
if is_identifier(var) {
return Some(GuardCondition::Falsy {
var: var.to_string(),
});
}
return None;
}
if is_identifier(s) {
return Some(GuardCondition::Truthy {
var: s.to_string(),
});
}
None
}
const NULL_KEYWORDS: &[&str] = &["null", "nil", "nullptr", "NULL", "None"];
fn try_comparison_split(s: &str, op: &str) -> Option<GuardCondition> {
let idx = s.find(op)?;
let lhs = s[..idx].trim();
let rhs = s[idx + op.len()..].trim();
if lhs.is_empty() || rhs.is_empty() {
return None;
}
let canonical_op = match op {
"===" => "==",
"!==" => "!=",
other => other,
};
if is_identifier(lhs) && NULL_KEYWORDS.contains(&rhs) {
return match canonical_op {
"==" => Some(GuardCondition::IsNull {
var: lhs.to_string(),
}),
"!=" => Some(GuardCondition::NotNull {
var: lhs.to_string(),
}),
_ => None, };
}
if NULL_KEYWORDS.contains(&lhs) && is_identifier(rhs) {
return match canonical_op {
"==" => Some(GuardCondition::IsNull {
var: rhs.to_string(),
}),
"!=" => Some(GuardCondition::NotNull {
var: rhs.to_string(),
}),
_ => None,
};
}
if is_identifier(lhs) {
if let Some(value) = parse_int_literal(rhs) {
return make_guard(lhs, canonical_op, value);
}
}
if is_identifier(rhs) {
if let Some(value) = parse_int_literal(lhs) {
let flipped = flip_operator(canonical_op)?;
return make_guard(rhs, flipped, value);
}
}
None
}
fn make_guard(var: &str, op: &str, value: i64) -> Option<GuardCondition> {
let var = var.to_string();
match op {
"==" => Some(GuardCondition::Eq { var, value }),
"!=" => Some(GuardCondition::Neq { var, value }),
"<" => Some(GuardCondition::Lt { var, value }),
"<=" => Some(GuardCondition::Le { var, value }),
">" => Some(GuardCondition::Gt { var, value }),
">=" => Some(GuardCondition::Ge { var, value }),
_ => None,
}
}
pub fn guard_variable(guard: &GuardCondition) -> &str {
match guard {
GuardCondition::Eq { var, .. }
| GuardCondition::Neq { var, .. }
| GuardCondition::Lt { var, .. }
| GuardCondition::Le { var, .. }
| GuardCondition::Gt { var, .. }
| GuardCondition::Ge { var, .. }
| GuardCondition::NotNull { var }
| GuardCondition::IsNull { var }
| GuardCondition::Truthy { var }
| GuardCondition::Falsy { var } => var.as_str(),
}
}
pub fn narrow_value(
value: &AbstractValue,
guard: &GuardCondition,
is_true_branch: bool,
) -> AbstractValue {
if !is_true_branch {
let complement = negate_guard(guard);
return narrow_value(value, &complement, true);
}
let mut result = value.clone();
match guard {
GuardCondition::Eq { value: c, .. } => {
result.range_ = intersect_range(value.range_, Some((Some(*c), Some(*c))));
}
GuardCondition::Neq { value: c, .. } => {
match value.range_ {
Some((Some(lo), Some(hi))) if lo == *c && hi == *c => {
return AbstractValue::bottom();
}
Some((Some(lo), hi)) if lo == *c => {
if let Some(new_lo) = c.checked_add(1) {
result.range_ = Some((Some(new_lo), hi));
}
}
Some((lo, Some(hi))) if hi == *c => {
if let Some(new_hi) = c.checked_sub(1) {
result.range_ = Some((lo, Some(new_hi)));
}
}
_ => {
}
}
}
GuardCondition::Lt { value: c, .. } => {
let upper = c.checked_sub(1);
let guard_range = match upper {
Some(u) => Some((None, Some(u))),
None => {
return AbstractValue::bottom();
}
};
result.range_ = intersect_range(value.range_, guard_range);
}
GuardCondition::Le { value: c, .. } => {
let guard_range = Some((None, Some(*c)));
result.range_ = intersect_range(value.range_, guard_range);
}
GuardCondition::Gt { value: c, .. } => {
let lower = c.checked_add(1);
let guard_range = match lower {
Some(l) => Some((Some(l), None)),
None => {
return AbstractValue::bottom();
}
};
result.range_ = intersect_range(value.range_, guard_range);
}
GuardCondition::Ge { value: c, .. } => {
let guard_range = Some((Some(*c), None));
result.range_ = intersect_range(value.range_, guard_range);
}
GuardCondition::NotNull { .. } => {
result.nullable = Nullability::Never;
}
GuardCondition::IsNull { .. } => {
result.nullable = Nullability::Always;
}
GuardCondition::Truthy { .. } => {
result.nullable = Nullability::Never;
result.range_ = exclude_zero(value.range_);
}
GuardCondition::Falsy { .. } => {
result.nullable = Nullability::Maybe;
}
}
if is_empty_range(result.range_) {
return AbstractValue::bottom();
}
result
}
pub fn narrow_state(
state: &AbstractState,
guard: &GuardCondition,
is_true_branch: bool,
) -> AbstractState {
let var = guard_variable(guard);
let current = state.get(var);
let narrowed = narrow_value(¤t, guard, is_true_branch);
state.set(var, narrowed)
}
fn negate_guard(guard: &GuardCondition) -> GuardCondition {
match guard {
GuardCondition::Eq { var, value } => GuardCondition::Neq {
var: var.clone(),
value: *value,
},
GuardCondition::Neq { var, value } => GuardCondition::Eq {
var: var.clone(),
value: *value,
},
GuardCondition::Lt { var, value } => GuardCondition::Ge {
var: var.clone(),
value: *value,
},
GuardCondition::Le { var, value } => GuardCondition::Gt {
var: var.clone(),
value: *value,
},
GuardCondition::Gt { var, value } => GuardCondition::Le {
var: var.clone(),
value: *value,
},
GuardCondition::Ge { var, value } => GuardCondition::Lt {
var: var.clone(),
value: *value,
},
GuardCondition::NotNull { var } => GuardCondition::IsNull { var: var.clone() },
GuardCondition::IsNull { var } => GuardCondition::NotNull { var: var.clone() },
GuardCondition::Truthy { var } => GuardCondition::Falsy { var: var.clone() },
GuardCondition::Falsy { var } => GuardCondition::Truthy { var: var.clone() },
}
}
fn intersect_range(
a: Option<(Option<i64>, Option<i64>)>,
b: Option<(Option<i64>, Option<i64>)>,
) -> Option<(Option<i64>, Option<i64>)> {
match (a, b) {
(None, None) => None,
(None, Some(r)) | (Some(r), None) => Some(r),
(Some((a_lo, a_hi)), Some((b_lo, b_hi))) => {
let lo = match (a_lo, b_lo) {
(None, None) => None,
(Some(v), None) | (None, Some(v)) => Some(v),
(Some(a), Some(b)) => Some(a.max(b)),
};
let hi = match (a_hi, b_hi) {
(None, None) => None,
(Some(v), None) | (None, Some(v)) => Some(v),
(Some(a), Some(b)) => Some(a.min(b)),
};
Some((lo, hi))
}
}
}
fn is_empty_range(range: Option<(Option<i64>, Option<i64>)>) -> bool {
match range {
None => false,
Some((Some(lo), Some(hi))) => lo > hi,
_ => false, }
}
fn exclude_zero(range: Option<(Option<i64>, Option<i64>)>) -> Option<(Option<i64>, Option<i64>)> {
match range {
None => None, Some((lo, hi)) => {
let lo_val = lo.unwrap_or(i64::MIN);
let hi_val = hi.unwrap_or(i64::MAX);
if lo_val == 0 && hi_val == 0 {
Some((Some(1), Some(0))) } else if lo_val == 0 {
Some((Some(1), hi))
} else if hi_val == 0 {
Some((lo, Some(-1)))
} else {
Some((lo, hi))
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_eq_positive() {
assert_eq!(
parse_guard_condition("x == 5"),
Some(GuardCondition::Eq {
var: "x".into(),
value: 5
})
);
}
#[test]
fn test_neq_positive() {
assert_eq!(
parse_guard_condition("x != 0"),
Some(GuardCondition::Neq {
var: "x".into(),
value: 0
})
);
}
#[test]
fn test_lt_positive() {
assert_eq!(
parse_guard_condition("x < 10"),
Some(GuardCondition::Lt {
var: "x".into(),
value: 10
})
);
}
#[test]
fn test_le_positive() {
assert_eq!(
parse_guard_condition("x <= 100"),
Some(GuardCondition::Le {
var: "x".into(),
value: 100
})
);
}
#[test]
fn test_gt_positive() {
assert_eq!(
parse_guard_condition("x > 0"),
Some(GuardCondition::Gt {
var: "x".into(),
value: 0
})
);
}
#[test]
fn test_ge_positive() {
assert_eq!(
parse_guard_condition("x >= 1"),
Some(GuardCondition::Ge {
var: "x".into(),
value: 1
})
);
}
#[test]
fn test_eq_negative() {
assert_eq!(
parse_guard_condition("x == -5"),
Some(GuardCondition::Eq {
var: "x".into(),
value: -5
})
);
}
#[test]
fn test_neq_negative() {
assert_eq!(
parse_guard_condition("x != -1"),
Some(GuardCondition::Neq {
var: "x".into(),
value: -1
})
);
}
#[test]
fn test_lt_negative() {
assert_eq!(
parse_guard_condition("x < -3"),
Some(GuardCondition::Lt {
var: "x".into(),
value: -3
})
);
}
#[test]
fn test_le_negative() {
assert_eq!(
parse_guard_condition("x <= -10"),
Some(GuardCondition::Le {
var: "x".into(),
value: -10
})
);
}
#[test]
fn test_gt_negative() {
assert_eq!(
parse_guard_condition("x > -1"),
Some(GuardCondition::Gt {
var: "x".into(),
value: -1
})
);
}
#[test]
fn test_ge_negative() {
assert_eq!(
parse_guard_condition("x >= -100"),
Some(GuardCondition::Ge {
var: "x".into(),
value: -100
})
);
}
#[test]
fn test_reversed_eq() {
assert_eq!(
parse_guard_condition("0 == x"),
Some(GuardCondition::Eq {
var: "x".into(),
value: 0
})
);
}
#[test]
fn test_reversed_neq() {
assert_eq!(
parse_guard_condition("5 != x"),
Some(GuardCondition::Neq {
var: "x".into(),
value: 5
})
);
}
#[test]
fn test_reversed_lt() {
assert_eq!(
parse_guard_condition("5 < x"),
Some(GuardCondition::Gt {
var: "x".into(),
value: 5
})
);
}
#[test]
fn test_reversed_le() {
assert_eq!(
parse_guard_condition("5 <= x"),
Some(GuardCondition::Ge {
var: "x".into(),
value: 5
})
);
}
#[test]
fn test_reversed_gt() {
assert_eq!(
parse_guard_condition("10 > x"),
Some(GuardCondition::Lt {
var: "x".into(),
value: 10
})
);
}
#[test]
fn test_reversed_ge() {
assert_eq!(
parse_guard_condition("10 >= x"),
Some(GuardCondition::Le {
var: "x".into(),
value: 10
})
);
}
#[test]
fn test_python_is_not_none() {
assert_eq!(
parse_guard_condition("x is not None"),
Some(GuardCondition::NotNull {
var: "x".into()
})
);
}
#[test]
fn test_python_is_none() {
assert_eq!(
parse_guard_condition("x is None"),
Some(GuardCondition::IsNull {
var: "x".into()
})
);
}
#[test]
fn test_ts_strict_not_null() {
assert_eq!(
parse_guard_condition("x !== null"),
Some(GuardCondition::NotNull {
var: "x".into()
})
);
}
#[test]
fn test_ts_strict_is_null() {
assert_eq!(
parse_guard_condition("x === null"),
Some(GuardCondition::IsNull {
var: "x".into()
})
);
}
#[test]
fn test_ts_loose_not_null() {
assert_eq!(
parse_guard_condition("x != null"),
Some(GuardCondition::NotNull {
var: "x".into()
})
);
}
#[test]
fn test_ts_loose_is_null() {
assert_eq!(
parse_guard_condition("x == null"),
Some(GuardCondition::IsNull {
var: "x".into()
})
);
}
#[test]
fn test_go_not_nil() {
assert_eq!(
parse_guard_condition("x != nil"),
Some(GuardCondition::NotNull {
var: "x".into()
})
);
}
#[test]
fn test_go_is_nil() {
assert_eq!(
parse_guard_condition("x == nil"),
Some(GuardCondition::IsNull {
var: "x".into()
})
);
}
#[test]
fn test_rust_is_some() {
assert_eq!(
parse_guard_condition("x.is_some()"),
Some(GuardCondition::NotNull {
var: "x".into()
})
);
}
#[test]
fn test_rust_is_none() {
assert_eq!(
parse_guard_condition("x.is_none()"),
Some(GuardCondition::IsNull {
var: "x".into()
})
);
}
#[test]
fn test_cpp_not_nullptr() {
assert_eq!(
parse_guard_condition("x != nullptr"),
Some(GuardCondition::NotNull {
var: "x".into()
})
);
}
#[test]
fn test_c_not_null_macro() {
assert_eq!(
parse_guard_condition("x != NULL"),
Some(GuardCondition::NotNull {
var: "x".into()
})
);
}
#[test]
fn test_c_is_null_macro() {
assert_eq!(
parse_guard_condition("x == NULL"),
Some(GuardCondition::IsNull {
var: "x".into()
})
);
}
#[test]
fn test_truthy_bare_identifier() {
assert_eq!(
parse_guard_condition("x"),
Some(GuardCondition::Truthy {
var: "x".into()
})
);
}
#[test]
fn test_falsy_bang() {
assert_eq!(
parse_guard_condition("!x"),
Some(GuardCondition::Falsy {
var: "x".into()
})
);
}
#[test]
fn test_falsy_not_keyword() {
assert_eq!(
parse_guard_condition("not x"),
Some(GuardCondition::Falsy {
var: "x".into()
})
);
}
#[test]
fn test_empty_string() {
assert_eq!(parse_guard_condition(""), None);
}
#[test]
fn test_whitespace_only() {
assert_eq!(parse_guard_condition(" "), None);
}
#[test]
fn test_function_call_unparseable() {
assert_eq!(parse_guard_condition("f(x)"), None);
}
#[test]
fn test_compound_condition_unparseable() {
assert_eq!(parse_guard_condition("x > 0 && x < 10"), None);
}
#[test]
fn test_variable_with_underscores() {
assert_eq!(
parse_guard_condition("my_var > 0"),
Some(GuardCondition::Gt {
var: "my_var".into(),
value: 0
})
);
}
#[test]
fn test_variable_with_dots_unparseable() {
assert_eq!(parse_guard_condition("obj.field > 0"), None);
}
#[test]
fn test_negative_literal_gt() {
assert_eq!(
parse_guard_condition("x > -1"),
Some(GuardCondition::Gt {
var: "x".into(),
value: -1
})
);
}
#[test]
fn test_negative_literal_eq() {
assert_eq!(
parse_guard_condition("x == -5"),
Some(GuardCondition::Eq {
var: "x".into(),
value: -5
})
);
}
#[test]
fn test_whitespace_around_operator() {
assert_eq!(
parse_guard_condition(" x == 5 "),
Some(GuardCondition::Eq {
var: "x".into(),
value: 5
})
);
}
#[test]
fn test_multiword_identifier_truthy() {
assert_eq!(
parse_guard_condition("is_ready"),
Some(GuardCondition::Truthy {
var: "is_ready".into()
})
);
}
#[test]
fn test_cpp_nullptr_is_null() {
assert_eq!(
parse_guard_condition("x == nullptr"),
Some(GuardCondition::IsNull {
var: "x".into()
})
);
}
#[test]
fn test_is_identifier_valid() {
assert!(is_identifier("x"));
assert!(is_identifier("my_var"));
assert!(is_identifier("_private"));
assert!(is_identifier("camelCase"));
assert!(is_identifier("x123"));
}
#[test]
fn test_is_identifier_invalid() {
assert!(!is_identifier(""));
assert!(!is_identifier("123abc"));
assert!(!is_identifier("a.b"));
assert!(!is_identifier("a b"));
assert!(!is_identifier("a-b"));
assert!(!is_identifier("f(x)"));
}
#[test]
fn test_guard_variable_eq() {
let g = GuardCondition::Eq {
var: "x".into(),
value: 5,
};
assert_eq!(guard_variable(&g), "x");
}
#[test]
fn test_guard_variable_neq() {
let g = GuardCondition::Neq {
var: "count".into(),
value: 0,
};
assert_eq!(guard_variable(&g), "count");
}
#[test]
fn test_guard_variable_lt() {
let g = GuardCondition::Lt {
var: "idx".into(),
value: 10,
};
assert_eq!(guard_variable(&g), "idx");
}
#[test]
fn test_guard_variable_not_null() {
let g = GuardCondition::NotNull {
var: "ptr".into(),
};
assert_eq!(guard_variable(&g), "ptr");
}
#[test]
fn test_guard_variable_truthy() {
let g = GuardCondition::Truthy {
var: "flag".into(),
};
assert_eq!(guard_variable(&g), "flag");
}
#[test]
fn test_guard_variable_falsy() {
let g = GuardCondition::Falsy {
var: "done".into(),
};
assert_eq!(guard_variable(&g), "done");
}
#[test]
fn test_narrow_eq_true_unbounded() {
let val = AbstractValue::top();
let guard = GuardCondition::Eq {
var: "x".into(),
value: 5,
};
let result = narrow_value(&val, &guard, true);
assert_eq!(result.range_, Some((Some(5), Some(5))));
}
#[test]
fn test_narrow_eq_true_bounded() {
let val = AbstractValue {
range_: Some((Some(0), Some(10))),
..AbstractValue::top()
};
let guard = GuardCondition::Eq {
var: "x".into(),
value: 5,
};
let result = narrow_value(&val, &guard, true);
assert_eq!(result.range_, Some((Some(5), Some(5))));
}
#[test]
fn test_narrow_eq_true_outside_range() {
let val = AbstractValue {
range_: Some((Some(0), Some(10))),
..AbstractValue::top()
};
let guard = GuardCondition::Eq {
var: "x".into(),
value: 15,
};
let result = narrow_value(&val, &guard, true);
assert_eq!(result, AbstractValue::bottom());
}
#[test]
fn test_narrow_eq_false_bounded() {
let val = AbstractValue {
range_: Some((Some(0), Some(10))),
..AbstractValue::top()
};
let guard = GuardCondition::Eq {
var: "x".into(),
value: 5,
};
let result = narrow_value(&val, &guard, false);
assert_eq!(result.range_, Some((Some(0), Some(10))));
}
#[test]
fn test_narrow_neq_true_exact_match() {
let val = AbstractValue {
range_: Some((Some(0), Some(0))),
..AbstractValue::top()
};
let guard = GuardCondition::Neq {
var: "x".into(),
value: 0,
};
let result = narrow_value(&val, &guard, true);
assert_eq!(result, AbstractValue::bottom());
}
#[test]
fn test_narrow_neq_true_wider_range() {
let val = AbstractValue {
range_: Some((Some(0), Some(10))),
..AbstractValue::top()
};
let guard = GuardCondition::Neq {
var: "x".into(),
value: 5,
};
let result = narrow_value(&val, &guard, true);
assert_eq!(result.range_, Some((Some(0), Some(10))));
}
#[test]
fn test_narrow_neq_false() {
let val = AbstractValue {
range_: Some((Some(0), Some(10))),
..AbstractValue::top()
};
let guard = GuardCondition::Neq {
var: "x".into(),
value: 5,
};
let result = narrow_value(&val, &guard, false);
assert_eq!(result.range_, Some((Some(5), Some(5))));
}
#[test]
fn test_narrow_lt_true() {
let val = AbstractValue {
range_: Some((Some(0), Some(100))),
..AbstractValue::top()
};
let guard = GuardCondition::Lt {
var: "x".into(),
value: 10,
};
let result = narrow_value(&val, &guard, true);
assert_eq!(result.range_, Some((Some(0), Some(9))));
}
#[test]
fn test_narrow_lt_false() {
let val = AbstractValue {
range_: Some((Some(0), Some(100))),
..AbstractValue::top()
};
let guard = GuardCondition::Lt {
var: "x".into(),
value: 10,
};
let result = narrow_value(&val, &guard, false);
assert_eq!(result.range_, Some((Some(10), Some(100))));
}
#[test]
fn test_narrow_le_true() {
let val = AbstractValue {
range_: Some((Some(-5), Some(5))),
..AbstractValue::top()
};
let guard = GuardCondition::Le {
var: "x".into(),
value: 0,
};
let result = narrow_value(&val, &guard, true);
assert_eq!(result.range_, Some((Some(-5), Some(0))));
}
#[test]
fn test_narrow_le_false() {
let val = AbstractValue {
range_: Some((Some(-5), Some(5))),
..AbstractValue::top()
};
let guard = GuardCondition::Le {
var: "x".into(),
value: 0,
};
let result = narrow_value(&val, &guard, false);
assert_eq!(result.range_, Some((Some(1), Some(5))));
}
#[test]
fn test_narrow_gt_true_unbounded() {
let val = AbstractValue::top();
let guard = GuardCondition::Gt {
var: "x".into(),
value: 0,
};
let result = narrow_value(&val, &guard, true);
assert_eq!(result.range_, Some((Some(1), None)));
}
#[test]
fn test_narrow_gt_false() {
let val = AbstractValue {
range_: Some((Some(0), Some(100))),
..AbstractValue::top()
};
let guard = GuardCondition::Gt {
var: "x".into(),
value: 50,
};
let result = narrow_value(&val, &guard, false);
assert_eq!(result.range_, Some((Some(0), Some(50))));
}
#[test]
fn test_narrow_ge_true() {
let val = AbstractValue {
range_: Some((Some(-5), Some(5))),
..AbstractValue::top()
};
let guard = GuardCondition::Ge {
var: "x".into(),
value: 1,
};
let result = narrow_value(&val, &guard, true);
assert_eq!(result.range_, Some((Some(1), Some(5))));
}
#[test]
fn test_narrow_ge_false() {
let val = AbstractValue {
range_: Some((Some(-5), Some(5))),
..AbstractValue::top()
};
let guard = GuardCondition::Ge {
var: "x".into(),
value: 1,
};
let result = narrow_value(&val, &guard, false);
assert_eq!(result.range_, Some((Some(-5), Some(0))));
}
#[test]
fn test_narrow_not_null_true() {
let val = AbstractValue {
nullable: Nullability::Maybe,
..AbstractValue::top()
};
let guard = GuardCondition::NotNull {
var: "x".into(),
};
let result = narrow_value(&val, &guard, true);
assert_eq!(result.nullable, Nullability::Never);
}
#[test]
fn test_narrow_not_null_false() {
let val = AbstractValue {
nullable: Nullability::Maybe,
..AbstractValue::top()
};
let guard = GuardCondition::NotNull {
var: "x".into(),
};
let result = narrow_value(&val, &guard, false);
assert_eq!(result.nullable, Nullability::Always);
}
#[test]
fn test_narrow_is_null_true() {
let val = AbstractValue {
nullable: Nullability::Maybe,
..AbstractValue::top()
};
let guard = GuardCondition::IsNull {
var: "x".into(),
};
let result = narrow_value(&val, &guard, true);
assert_eq!(result.nullable, Nullability::Always);
}
#[test]
fn test_narrow_is_null_false() {
let val = AbstractValue {
nullable: Nullability::Maybe,
..AbstractValue::top()
};
let guard = GuardCondition::IsNull {
var: "x".into(),
};
let result = narrow_value(&val, &guard, false);
assert_eq!(result.nullable, Nullability::Never);
}
#[test]
fn test_narrow_truthy_true_excludes_zero() {
let val = AbstractValue {
range_: Some((Some(0), Some(10))),
nullable: Nullability::Maybe,
..AbstractValue::top()
};
let guard = GuardCondition::Truthy {
var: "x".into(),
};
let result = narrow_value(&val, &guard, true);
assert_eq!(result.nullable, Nullability::Never);
assert_eq!(result.range_, Some((Some(1), Some(10))));
}
#[test]
fn test_narrow_truthy_true_zero_at_upper() {
let val = AbstractValue {
range_: Some((Some(-5), Some(0))),
nullable: Nullability::Maybe,
..AbstractValue::top()
};
let guard = GuardCondition::Truthy {
var: "x".into(),
};
let result = narrow_value(&val, &guard, true);
assert_eq!(result.nullable, Nullability::Never);
assert_eq!(result.range_, Some((Some(-5), Some(-1))));
}
#[test]
fn test_narrow_truthy_true_zero_only() {
let val = AbstractValue {
range_: Some((Some(0), Some(0))),
nullable: Nullability::Maybe,
..AbstractValue::top()
};
let guard = GuardCondition::Truthy {
var: "x".into(),
};
let result = narrow_value(&val, &guard, true);
assert_eq!(result, AbstractValue::bottom());
}
#[test]
fn test_narrow_falsy_true() {
let val = AbstractValue {
range_: Some((Some(0), Some(10))),
nullable: Nullability::Never,
..AbstractValue::top()
};
let guard = GuardCondition::Falsy {
var: "x".into(),
};
let result = narrow_value(&val, &guard, true);
assert_eq!(result.nullable, Nullability::Maybe);
assert_eq!(result.range_, Some((Some(0), Some(10))));
}
#[test]
fn test_narrow_no_range_gt_creates_range() {
let val = AbstractValue::top();
let guard = GuardCondition::Gt {
var: "x".into(),
value: 0,
};
let result = narrow_value(&val, &guard, true);
assert_eq!(result.range_, Some((Some(1), None)));
}
#[test]
fn test_narrow_empty_intersection_produces_bottom() {
let val = AbstractValue {
range_: Some((Some(0), Some(5))),
..AbstractValue::top()
};
let guard = GuardCondition::Gt {
var: "x".into(),
value: 100,
};
let result = narrow_value(&val, &guard, true);
assert_eq!(result, AbstractValue::bottom());
}
#[test]
fn test_narrow_soundness_subset() {
let val = AbstractValue {
range_: Some((Some(0), Some(100))),
..AbstractValue::top()
};
let guard = GuardCondition::Ge {
var: "x".into(),
value: 10,
};
let result = narrow_value(&val, &guard, true);
let (r_lo, r_hi) = result.range_.unwrap();
let (i_lo, i_hi) = val.range_.unwrap();
assert!(r_lo.unwrap() >= i_lo.unwrap());
assert!(r_hi.unwrap() <= i_hi.unwrap());
}
#[test]
fn test_narrow_gt_i64_max_overflow() {
let val = AbstractValue {
range_: Some((Some(0), Some(100))),
..AbstractValue::top()
};
let guard = GuardCondition::Gt {
var: "x".into(),
value: i64::MAX,
};
let result = narrow_value(&val, &guard, true);
assert_eq!(result, AbstractValue::bottom());
}
#[test]
fn test_narrow_lt_i64_min_overflow() {
let val = AbstractValue {
range_: Some((Some(0), Some(100))),
..AbstractValue::top()
};
let guard = GuardCondition::Lt {
var: "x".into(),
value: i64::MIN,
};
let result = narrow_value(&val, &guard, true);
assert_eq!(result, AbstractValue::bottom());
}
#[test]
fn test_narrow_state_narrows_mentioned_var() {
let state = AbstractState::new()
.set(
"x",
AbstractValue {
range_: Some((Some(0), Some(100))),
..AbstractValue::top()
},
)
.set(
"y",
AbstractValue {
range_: Some((Some(-10), Some(10))),
..AbstractValue::top()
},
);
let guard = GuardCondition::Gt {
var: "x".into(),
value: 50,
};
let narrowed = narrow_state(&state, &guard, true);
assert_eq!(narrowed.get("x").range_, Some((Some(51), Some(100))));
assert_eq!(narrowed.get("y").range_, Some((Some(-10), Some(10))));
}
#[test]
fn test_narrow_state_missing_variable() {
let state = AbstractState::new().set(
"y",
AbstractValue {
range_: Some((Some(0), Some(10))),
..AbstractValue::top()
},
);
let guard = GuardCondition::Gt {
var: "x".into(),
value: 0,
};
let narrowed = narrow_state(&state, &guard, true);
assert_eq!(narrowed.get("y").range_, Some((Some(0), Some(10))));
assert_eq!(narrowed.get("x").range_, Some((Some(1), None)));
}
#[test]
fn test_narrow_state_null_guard() {
let state = AbstractState::new().set(
"ptr",
AbstractValue {
nullable: Nullability::Maybe,
..AbstractValue::top()
},
);
let guard = GuardCondition::NotNull {
var: "ptr".into(),
};
let narrowed = narrow_state(&state, &guard, true);
assert_eq!(narrowed.get("ptr").nullable, Nullability::Never);
}
#[test]
fn test_narrow_ge_with_half_bounded_range() {
let val = AbstractValue {
range_: Some((None, Some(50))),
..AbstractValue::top()
};
let guard = GuardCondition::Ge {
var: "x".into(),
value: 10,
};
let result = narrow_value(&val, &guard, true);
assert_eq!(result.range_, Some((Some(10), Some(50))));
}
#[test]
fn test_narrow_le_with_half_bounded_range() {
let val = AbstractValue {
range_: Some((Some(5), None)),
..AbstractValue::top()
};
let guard = GuardCondition::Le {
var: "x".into(),
value: 20,
};
let result = narrow_value(&val, &guard, true);
assert_eq!(result.range_, Some((Some(5), Some(20))));
}
#[test]
fn test_narrow_truthy_no_range_info() {
let val = AbstractValue::top();
let guard = GuardCondition::Truthy {
var: "x".into(),
};
let result = narrow_value(&val, &guard, true);
assert_eq!(result.nullable, Nullability::Never);
assert_eq!(result.range_, None);
}
#[test]
fn test_narrow_preserves_type() {
let val = AbstractValue {
type_: Some("int".to_string()),
range_: Some((Some(0), Some(100))),
nullable: Nullability::Never,
constant: None,
};
let guard = GuardCondition::Gt {
var: "x".into(),
value: 50,
};
let result = narrow_value(&val, &guard, true);
assert_eq!(result.type_, Some("int".to_string()));
assert_eq!(result.range_, Some((Some(51), Some(100))));
}
}