use serde::{Deserialize, Serialize};
pub const MAX_PHI_ARMS: usize = 8;
pub const MAX_CONTAINER_ENTRIES: usize = 32;
pub const MAX_RESOLUTION_DEPTH: usize = 16;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum SymbolicValue {
Literal(LiteralValue),
Variable(String),
Parameter(usize),
FieldAccess(Box<SymbolicValue>, String),
Index(Box<SymbolicValue>, Box<SymbolicValue>),
BinaryOp(BinOp, Box<SymbolicValue>, Box<SymbolicValue>),
Concat(Vec<SymbolicValue>),
Call(String, Vec<SymbolicValue>),
Phi(Vec<SymbolicValue>),
Unknown,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum LiteralValue {
String(String),
Integer(i64),
Float(f64),
Boolean(bool),
Null,
List(Vec<SymbolicValue>),
Dict(Vec<(SymbolicValue, SymbolicValue)>),
Unknown,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum BinOp {
Add,
Sub,
Mul,
Div,
Mod,
And,
Or,
Eq,
NotEq,
Lt,
Gt,
LtEq,
GtEq,
}
impl SymbolicValue {
pub fn phi(arms: Vec<SymbolicValue>) -> Self {
if arms.len() > MAX_PHI_ARMS {
return SymbolicValue::Unknown;
}
if arms.len() == 1 {
return arms.into_iter().next().expect("phi: checked len == 1");
}
SymbolicValue::Phi(arms)
}
pub fn is_constant(value: &SymbolicValue) -> bool {
match value {
SymbolicValue::Literal(lit) => LiteralValue::is_constant(lit),
SymbolicValue::Variable(_) | SymbolicValue::Parameter(_) | SymbolicValue::Unknown => {
false
}
SymbolicValue::FieldAccess(base, _) => SymbolicValue::is_constant(base),
SymbolicValue::Index(base, idx) => {
SymbolicValue::is_constant(base) && SymbolicValue::is_constant(idx)
}
SymbolicValue::BinaryOp(_, lhs, rhs) => {
SymbolicValue::is_constant(lhs) && SymbolicValue::is_constant(rhs)
}
SymbolicValue::Concat(parts) => parts.iter().all(SymbolicValue::is_constant),
SymbolicValue::Call(_, args) => args.iter().all(SymbolicValue::is_constant),
SymbolicValue::Phi(arms) => arms.iter().all(SymbolicValue::is_constant),
}
}
pub fn is_tainted(value: &SymbolicValue) -> bool {
!SymbolicValue::is_constant(value)
}
}
impl LiteralValue {
pub fn list(items: Vec<SymbolicValue>) -> Self {
if items.len() > MAX_CONTAINER_ENTRIES {
return LiteralValue::Unknown;
}
LiteralValue::List(items)
}
pub fn dict(entries: Vec<(SymbolicValue, SymbolicValue)>) -> Self {
if entries.len() > MAX_CONTAINER_ENTRIES {
return LiteralValue::Unknown;
}
LiteralValue::Dict(entries)
}
fn is_constant(lit: &LiteralValue) -> bool {
match lit {
LiteralValue::String(_)
| LiteralValue::Integer(_)
| LiteralValue::Float(_)
| LiteralValue::Boolean(_)
| LiteralValue::Null => true,
LiteralValue::List(items) => items.iter().all(SymbolicValue::is_constant),
LiteralValue::Dict(entries) => entries
.iter()
.all(|(k, v)| SymbolicValue::is_constant(k) && SymbolicValue::is_constant(v)),
LiteralValue::Unknown => false,
}
}
#[allow(dead_code)]
fn is_tainted(lit: &LiteralValue) -> bool {
!LiteralValue::is_constant(lit)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_symbolic_value_literal_string() {
let val = SymbolicValue::Literal(LiteralValue::String("hello".to_string()));
assert!(SymbolicValue::is_constant(&val));
assert!(!SymbolicValue::is_tainted(&val));
}
#[test]
fn test_symbolic_value_unknown_is_tainted() {
let val = SymbolicValue::Unknown;
assert!(SymbolicValue::is_tainted(&val));
assert!(!SymbolicValue::is_constant(&val));
}
#[test]
fn test_symbolic_value_parameter_is_tainted() {
let val = SymbolicValue::Parameter(0);
assert!(SymbolicValue::is_tainted(&val));
assert!(!SymbolicValue::is_constant(&val));
}
#[test]
fn test_symbolic_value_phi_mixed() {
let val = SymbolicValue::Phi(vec![
SymbolicValue::Literal(LiteralValue::Integer(1)),
SymbolicValue::Unknown,
]);
assert!(SymbolicValue::is_tainted(&val));
assert!(!SymbolicValue::is_constant(&val));
}
#[test]
fn test_symbolic_value_nested_constant() {
let val = SymbolicValue::BinaryOp(
BinOp::Add,
Box::new(SymbolicValue::Literal(LiteralValue::Integer(1))),
Box::new(SymbolicValue::Literal(LiteralValue::Integer(2))),
);
assert!(SymbolicValue::is_constant(&val));
assert!(!SymbolicValue::is_tainted(&val));
}
#[test]
fn test_symbolic_value_concat() {
let val = SymbolicValue::Concat(vec![
SymbolicValue::Literal(LiteralValue::String("hello".to_string())),
SymbolicValue::Literal(LiteralValue::String(" world".to_string())),
]);
assert!(SymbolicValue::is_constant(&val));
assert!(!SymbolicValue::is_tainted(&val));
}
#[test]
fn test_phi_cap() {
let arms: Vec<SymbolicValue> = (0..MAX_PHI_ARMS + 1)
.map(|i| SymbolicValue::Literal(LiteralValue::Integer(i as i64)))
.collect();
let val = SymbolicValue::phi(arms);
assert_eq!(val, SymbolicValue::Unknown);
}
#[test]
fn test_list_cap() {
let items: Vec<SymbolicValue> = (0..MAX_CONTAINER_ENTRIES + 1)
.map(|i| SymbolicValue::Literal(LiteralValue::Integer(i as i64)))
.collect();
let lit = LiteralValue::list(items);
assert_eq!(lit, LiteralValue::Unknown);
}
#[test]
fn test_serde_roundtrip() {
let val = SymbolicValue::Call(
"foo.bar".to_string(),
vec![
SymbolicValue::Literal(LiteralValue::Integer(42)),
SymbolicValue::Literal(LiteralValue::String("baz".to_string())),
],
);
let json = serde_json::to_string(&val).unwrap();
let deserialized: SymbolicValue = serde_json::from_str(&json).unwrap();
assert_eq!(val, deserialized);
}
}