use crate::abstract_interp::{AbstractTransfer, AbstractValue, PathFact};
use crate::labels::Cap;
use crate::ssa::type_facts::TypeKind;
use crate::summary::SinkSite;
use crate::summary::points_to::{FieldPointsToSummary, PointsToSummary};
use serde::{Deserialize, Serialize};
use smallvec::SmallVec;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum TaintTransform {
Identity,
StripBits(Cap),
AddBits(Cap),
}
pub const MAX_RETURN_PATHS: usize = 8;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ReturnPathTransform {
pub transform: TaintTransform,
pub path_predicate_hash: u64,
pub known_true: u8,
pub known_false: u8,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub abstract_contribution: Option<AbstractValue>,
}
impl ReturnPathTransform {
pub fn dedup_key(&self) -> (u64, &TaintTransform, u8, u8) {
(
self.path_predicate_hash,
&self.transform,
self.known_true,
self.known_false,
)
}
}
pub fn merge_return_paths(
existing: &mut SmallVec<[ReturnPathTransform; 2]>,
incoming: &[ReturnPathTransform],
) {
for new_entry in incoming {
let key = new_entry.dedup_key();
if let Some(slot) = existing.iter_mut().find(|e| e.dedup_key() == key) {
slot.abstract_contribution = match (
slot.abstract_contribution.take(),
&new_entry.abstract_contribution,
) {
(Some(a), Some(b)) => Some(a.join(b)),
(Some(a), None) => Some(a),
(None, Some(b)) => Some(b.clone()),
(None, None) => None,
};
} else {
existing.push(new_entry.clone());
}
}
if existing.len() > MAX_RETURN_PATHS {
let mut joined = ReturnPathTransform {
transform: TaintTransform::Identity,
path_predicate_hash: 0,
known_true: 0,
known_false: 0,
abstract_contribution: None,
};
let mut strip_bits = Cap::all();
let mut add_bits = Cap::empty();
let mut saw_add = false;
let mut abs: Option<AbstractValue> = None;
let mut known_true = u8::MAX;
let mut known_false = u8::MAX;
for e in existing.iter() {
match &e.transform {
TaintTransform::Identity => {
strip_bits = Cap::empty();
}
TaintTransform::StripBits(bits) => strip_bits &= *bits,
TaintTransform::AddBits(bits) => {
add_bits |= *bits;
saw_add = true;
}
}
known_true &= e.known_true;
known_false &= e.known_false;
abs = match (abs, &e.abstract_contribution) {
(None, None) => None,
(Some(a), None) => Some(a),
(None, Some(b)) => Some(b.clone()),
(Some(a), Some(b)) => Some(a.join(b)),
};
}
joined.transform = if saw_add {
TaintTransform::AddBits(add_bits)
} else if strip_bits.is_empty() {
TaintTransform::Identity
} else {
TaintTransform::StripBits(strip_bits)
};
joined.known_true = known_true;
joined.known_false = known_false;
joined.abstract_contribution = abs;
existing.clear();
existing.push(joined);
}
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct SsaFuncSummary {
pub param_to_return: Vec<(usize, TaintTransform)>,
#[serde(default)]
pub param_to_sink: Vec<(usize, SmallVec<[SinkSite; 1]>)>,
pub source_caps: Cap,
#[serde(default)]
pub param_to_sink_param: Vec<(usize, usize, Cap)>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub param_to_gate_filters: Vec<(usize, Cap)>,
#[serde(default)]
pub param_container_to_return: Vec<usize>,
#[serde(default)]
pub param_to_container_store: Vec<(usize, usize)>,
#[serde(default)]
pub return_type: Option<TypeKind>,
#[serde(default)]
pub return_abstract: Option<AbstractValue>,
#[serde(default)]
pub source_to_callback: Vec<(usize, Cap)>,
#[serde(default)]
pub receiver_to_return: Option<TaintTransform>,
#[serde(default)]
pub receiver_to_sink: Cap,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub abstract_transfer: Vec<(usize, AbstractTransfer)>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub param_return_paths: Vec<(usize, SmallVec<[ReturnPathTransform; 2]>)>,
#[serde(default, skip_serializing_if = "PointsToSummary::is_empty")]
pub points_to: PointsToSummary,
#[serde(default, skip_serializing_if = "FieldPointsToSummary::is_empty")]
pub field_points_to: FieldPointsToSummary,
#[serde(default, skip_serializing_if = "SmallVec::is_empty")]
pub return_path_facts: SmallVec<[PathFactReturnEntry; 2]>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub typed_call_receivers: Vec<(u32, String)>,
#[serde(default, skip_serializing_if = "SmallVec::is_empty")]
pub validated_params_to_return: SmallVec<[usize; 2]>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PathFactReturnEntry {
pub predicate_hash: u64,
pub known_true: u8,
pub known_false: u8,
pub path_fact: PathFact,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub variant_inner_fact: Option<PathFact>,
}
impl PathFactReturnEntry {
pub fn dedup_key(&self) -> (u64, &PathFact, Option<&PathFact>) {
(
self.predicate_hash,
&self.path_fact,
self.variant_inner_fact.as_ref(),
)
}
}
pub const MAX_PATH_FACT_RETURN_ENTRIES: usize = 8;
pub fn merge_path_fact_return_paths(
existing: &mut SmallVec<[PathFactReturnEntry; 2]>,
incoming: &[PathFactReturnEntry],
) {
for new_entry in incoming {
if let Some(slot) = existing
.iter_mut()
.find(|e| e.predicate_hash == new_entry.predicate_hash)
{
slot.path_fact = slot.path_fact.join(&new_entry.path_fact);
slot.variant_inner_fact = match (
slot.variant_inner_fact.take(),
&new_entry.variant_inner_fact,
) {
(Some(a), Some(b)) => Some(a.join(b)),
(Some(a), None) => Some(a),
(None, Some(b)) => Some(b.clone()),
(None, None) => None,
};
slot.known_true &= new_entry.known_true;
slot.known_false &= new_entry.known_false;
} else {
existing.push(new_entry.clone());
}
}
if existing.len() > MAX_PATH_FACT_RETURN_ENTRIES {
let mut joined_fact = PathFact::bottom();
let mut joined_inner: Option<PathFact> = None;
let mut kt = u8::MAX;
let mut kf = u8::MAX;
for e in existing.iter() {
joined_fact = joined_fact.join(&e.path_fact);
joined_inner = match (joined_inner, &e.variant_inner_fact) {
(None, None) => None,
(Some(a), None) => Some(a),
(None, Some(b)) => Some(b.clone()),
(Some(a), Some(b)) => Some(a.join(b)),
};
kt &= e.known_true;
kf &= e.known_false;
}
existing.clear();
existing.push(PathFactReturnEntry {
predicate_hash: 0,
known_true: kt,
known_false: kf,
path_fact: joined_fact,
variant_inner_fact: joined_inner,
});
}
}
pub fn union_param_return_paths(
existing: &mut Vec<(usize, SmallVec<[ReturnPathTransform; 2]>)>,
incoming: &[(usize, SmallVec<[ReturnPathTransform; 2]>)],
) {
for (idx, paths) in incoming {
if let Some((_, slot)) = existing.iter_mut().find(|(i, _)| *i == *idx) {
merge_return_paths(slot, paths);
} else {
let mut fresh: SmallVec<[ReturnPathTransform; 2]> = SmallVec::new();
merge_return_paths(&mut fresh, paths);
existing.push((*idx, fresh));
}
}
}
impl SsaFuncSummary {
pub fn param_to_sink_caps(&self) -> Vec<(usize, Cap)> {
self.param_to_sink
.iter()
.map(|(idx, sites)| {
let caps = sites.iter().fold(Cap::empty(), |acc, s| acc | s.cap);
(*idx, caps)
})
.collect()
}
pub fn total_param_sink_caps(&self) -> Cap {
self.param_to_sink
.iter()
.flat_map(|(_, sites)| sites.iter())
.fold(Cap::empty(), |acc, s| acc | s.cap)
}
}