#![allow(clippy::collapsible_if, clippy::unnecessary_map_or)]
use crate::cfg::Cfg;
use crate::labels::{Cap, bare_method_name};
use crate::ssa::ir::*;
use crate::ssa::pointsto::{ContainerOp, classify_container_op};
use crate::symbol::Lang;
use crate::taint::domain::TaintOrigin;
use serde::{Deserialize, Serialize};
use smallvec::SmallVec;
use std::collections::HashMap;
static MAX_POINTSTO_OVERRIDE: std::sync::atomic::AtomicUsize =
std::sync::atomic::AtomicUsize::new(0);
pub(crate) static POINTSTO_TRUNCATION_COUNT: std::sync::atomic::AtomicUsize =
std::sync::atomic::AtomicUsize::new(0);
#[doc(hidden)]
pub fn set_max_pointsto_override(cap: usize) {
MAX_POINTSTO_OVERRIDE.store(cap, std::sync::atomic::Ordering::Relaxed);
}
pub fn effective_max_pointsto() -> usize {
let o = MAX_POINTSTO_OVERRIDE.load(std::sync::atomic::Ordering::Relaxed);
if o != 0 {
return o;
}
crate::utils::analysis_options::current().max_pointsto as usize
}
pub fn points_to_truncation_count() -> usize {
POINTSTO_TRUNCATION_COUNT.load(std::sync::atomic::Ordering::Relaxed)
}
pub fn reset_points_to_observability() {
POINTSTO_TRUNCATION_COUNT.store(0, std::sync::atomic::Ordering::Relaxed);
}
fn record_pointsto_truncation(dropped: usize) {
if dropped == 0 {
return;
}
POINTSTO_TRUNCATION_COUNT.fetch_add(dropped, std::sync::atomic::Ordering::Relaxed);
crate::taint::ssa_transfer::record_engine_note(
crate::engine_notes::EngineNote::PointsToTruncated {
dropped: dropped as u32,
},
);
}
pub const MAX_TRACKED_INDICES: usize = 8;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum HeapSlot {
Elements,
Index(u64),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct HeapObjectId(pub SsaValue);
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct PointsToSet {
ids: SmallVec<[HeapObjectId; 4]>,
}
impl PointsToSet {
pub fn empty() -> Self {
Self {
ids: SmallVec::new(),
}
}
pub fn singleton(id: HeapObjectId) -> Self {
let mut ids = SmallVec::new();
ids.push(id);
Self { ids }
}
pub fn union(&self, other: &Self) -> Self {
let cap = effective_max_pointsto();
let mut result: SmallVec<[HeapObjectId; 4]> = SmallVec::new();
let mut dropped = 0usize;
let (mut i, mut j) = (0, 0);
while i < self.ids.len() && j < other.ids.len() {
match self.ids[i].cmp(&other.ids[j]) {
std::cmp::Ordering::Less => {
if result.len() < cap {
result.push(self.ids[i]);
} else {
dropped += 1;
}
i += 1;
}
std::cmp::Ordering::Greater => {
if result.len() < cap {
result.push(other.ids[j]);
} else {
dropped += 1;
}
j += 1;
}
std::cmp::Ordering::Equal => {
if result.len() < cap {
result.push(self.ids[i]);
} else {
dropped += 1;
}
i += 1;
j += 1;
}
}
}
while i < self.ids.len() {
if result.len() < cap {
result.push(self.ids[i]);
} else {
dropped += 1;
}
i += 1;
}
while j < other.ids.len() {
if result.len() < cap {
result.push(other.ids[j]);
} else {
dropped += 1;
}
j += 1;
}
record_pointsto_truncation(dropped);
Self { ids: result }
}
pub fn insert(&mut self, id: HeapObjectId) {
match self.ids.binary_search(&id) {
Ok(_) => {} Err(pos) => {
if self.ids.len() < effective_max_pointsto() {
self.ids.insert(pos, id);
} else {
record_pointsto_truncation(1);
}
}
}
}
pub fn contains(&self, id: HeapObjectId) -> bool {
self.ids.binary_search(&id).is_ok()
}
pub fn is_empty(&self) -> bool {
self.ids.is_empty()
}
pub fn len(&self) -> usize {
self.ids.len()
}
pub fn iter(&self) -> impl Iterator<Item = &HeapObjectId> {
self.ids.iter()
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct HeapTaint {
pub caps: Cap,
pub origins: SmallVec<[TaintOrigin; 2]>,
}
impl HeapTaint {
fn merge(&mut self, caps: Cap, origins: &[TaintOrigin]) {
self.caps |= caps;
for orig in origins {
crate::taint::ssa_transfer::push_origin_bounded(&mut self.origins, *orig);
}
}
fn union(&self, other: &HeapTaint) -> HeapTaint {
let mut result = self.clone();
result.merge(other.caps, &other.origins);
result
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct HeapState {
entries: SmallVec<[((HeapObjectId, HeapSlot), HeapTaint); 4]>,
}
impl HeapState {
pub fn empty() -> Self {
Self {
entries: SmallVec::new(),
}
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn store(&mut self, id: HeapObjectId, slot: HeapSlot, caps: Cap, origins: &[TaintOrigin]) {
if caps.is_empty() {
return;
}
if let HeapSlot::Index(_) = slot {
let key = (id, slot);
let already_present = self.entries.binary_search_by_key(&key, |(k, _)| *k).is_ok();
if !already_present {
let index_count = self.count_indices_for(id);
if index_count >= MAX_TRACKED_INDICES {
self.collapse_indices_to_elements(id);
self.store_raw(id, HeapSlot::Elements, caps, origins);
return;
}
}
}
self.store_raw(id, slot, caps, origins);
}
fn store_raw(&mut self, id: HeapObjectId, slot: HeapSlot, caps: Cap, origins: &[TaintOrigin]) {
let key = (id, slot);
match self.entries.binary_search_by_key(&key, |(k, _)| *k) {
Ok(idx) => {
self.entries[idx].1.merge(caps, origins);
}
Err(idx) => {
let mut o: SmallVec<[TaintOrigin; 2]> = SmallVec::new();
for orig in origins {
crate::taint::ssa_transfer::push_origin_bounded(&mut o, *orig);
}
self.entries
.insert(idx, (key, HeapTaint { caps, origins: o }));
}
}
}
pub fn store_set(
&mut self,
pts: &PointsToSet,
slot: HeapSlot,
caps: Cap,
origins: &[TaintOrigin],
) {
for &id in pts.iter() {
self.store(id, slot, caps, origins);
}
}
pub fn load(&self, id: HeapObjectId, slot: HeapSlot) -> Option<HeapTaint> {
match slot {
HeapSlot::Index(n) => {
let idx_taint = self.load_raw(id, HeapSlot::Index(n));
let elem_taint = self.load_raw(id, HeapSlot::Elements);
match (idx_taint, elem_taint) {
(Some(a), Some(b)) => Some(a.union(b)),
(Some(a), None) => Some(a.clone()),
(None, Some(b)) => Some(b.clone()),
(None, None) => None,
}
}
HeapSlot::Elements => {
let mut result: Option<HeapTaint> = None;
for ((eid, _slot), taint) in &self.entries {
if *eid == id {
result = Some(match result {
Some(r) => r.union(taint),
None => taint.clone(),
});
}
}
result
}
}
}
fn load_raw(&self, id: HeapObjectId, slot: HeapSlot) -> Option<&HeapTaint> {
let key = (id, slot);
self.entries
.binary_search_by_key(&key, |(k, _)| *k)
.ok()
.map(|idx| &self.entries[idx].1)
}
pub fn load_set(&self, pts: &PointsToSet, slot: HeapSlot) -> Option<HeapTaint> {
let mut result: Option<HeapTaint> = None;
for &id in pts.iter() {
if let Some(ht) = self.load(id, slot) {
result = Some(match result {
Some(r) => r.union(&ht),
None => ht,
});
}
}
result
}
pub fn join(&self, other: &Self) -> Self {
let mut result = SmallVec::new();
let (mut i, mut j) = (0, 0);
while i < self.entries.len() && j < other.entries.len() {
let (ka, ta) = &self.entries[i];
let (kb, tb) = &other.entries[j];
match ka.cmp(kb) {
std::cmp::Ordering::Less => {
result.push((*ka, ta.clone()));
i += 1;
}
std::cmp::Ordering::Greater => {
result.push((*kb, tb.clone()));
j += 1;
}
std::cmp::Ordering::Equal => {
result.push((*ka, ta.union(tb)));
i += 1;
j += 1;
}
}
}
while i < self.entries.len() {
result.push(self.entries[i].clone());
i += 1;
}
while j < other.entries.len() {
result.push(other.entries[j].clone());
j += 1;
}
Self { entries: result }
}
pub fn leq(&self, other: &Self) -> bool {
let mut j = 0;
for (ka, ta) in &self.entries {
loop {
if j >= other.entries.len() {
return false;
}
let (kb, _) = &other.entries[j];
match ka.cmp(kb) {
std::cmp::Ordering::Equal => break,
std::cmp::Ordering::Greater => j += 1,
std::cmp::Ordering::Less => return false,
}
}
let (_, tb) = &other.entries[j];
if (ta.caps & !tb.caps) != Cap::empty() {
return false;
}
j += 1;
}
true
}
fn count_indices_for(&self, id: HeapObjectId) -> usize {
self.entries
.iter()
.filter(|((eid, slot), _)| *eid == id && matches!(slot, HeapSlot::Index(_)))
.count()
}
fn collapse_indices_to_elements(&mut self, id: HeapObjectId) {
let mut merged_caps = Cap::empty();
let mut merged_origins: SmallVec<[TaintOrigin; 2]> = SmallVec::new();
self.entries.retain(|((eid, slot), taint)| {
if *eid == id && matches!(slot, HeapSlot::Index(_)) {
merged_caps |= taint.caps;
for orig in &taint.origins {
crate::taint::ssa_transfer::push_origin_bounded(&mut merged_origins, *orig);
}
false } else {
true }
});
if !merged_caps.is_empty() {
self.store_raw(id, HeapSlot::Elements, merged_caps, &merged_origins);
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct PointsToResult {
pts: HashMap<SsaValue, PointsToSet>,
}
impl PointsToResult {
pub fn empty() -> Self {
Self {
pts: HashMap::new(),
}
}
pub fn get(&self, v: SsaValue) -> Option<&PointsToSet> {
self.pts.get(&v)
}
pub fn is_empty(&self) -> bool {
self.pts.is_empty()
}
}
pub fn is_container_literal_public(text: &str) -> bool {
is_container_literal(text)
}
fn is_container_literal(text: &str) -> bool {
let t = text.trim();
if t.starts_with('[') && t.ends_with(']') {
return true;
}
if t.starts_with('{') && t.ends_with('}') {
return true;
}
if t.starts_with("new ") {
return true;
}
if t == "dict()" || t == "list()" || t == "set()" {
return true;
}
false
}
pub fn is_container_constructor(callee: &str, lang: Lang) -> bool {
let after_dot = bare_method_name(callee);
let suffix = after_dot.rsplit("::").next().unwrap_or(after_dot);
let suffix_lower = suffix.to_ascii_lowercase();
match lang {
Lang::JavaScript | Lang::TypeScript => {
matches!(suffix, "Array" | "Map" | "Set" | "WeakMap" | "WeakSet")
}
Lang::Python => matches!(
suffix,
"list"
| "dict"
| "set"
| "frozenset"
| "defaultdict"
| "OrderedDict"
| "deque"
| "Counter"
),
Lang::Java => matches!(
suffix,
"ArrayList"
| "LinkedList"
| "HashMap"
| "TreeMap"
| "HashSet"
| "TreeSet"
| "Vector"
| "Stack"
| "ArrayDeque"
| "PriorityQueue"
| "ConcurrentHashMap"
| "LinkedHashMap"
| "LinkedHashSet"
| "CopyOnWriteArrayList"
),
Lang::Go => callee == "make",
Lang::Ruby => {
matches!(suffix, "new") && {
let prefix = callee.rsplit('.').nth(1).unwrap_or("");
matches!(prefix, "Array" | "Hash" | "Set")
}
}
Lang::Php => matches!(suffix, "array"),
Lang::C | Lang::Cpp => matches!(
suffix_lower.as_str(),
"vector"
| "map"
| "set"
| "unordered_map"
| "unordered_set"
| "list"
| "deque"
| "queue"
| "stack"
| "multimap"
| "multiset"
| "priority_queue"
),
Lang::Rust => {
suffix == "new" && callee.contains("::") && {
let type_part = callee.rsplit("::").nth(1).unwrap_or("");
matches!(
type_part,
"Vec"
| "HashMap"
| "HashSet"
| "BTreeMap"
| "BTreeSet"
| "VecDeque"
| "LinkedList"
| "BinaryHeap"
)
}
}
}
}
pub fn analyze_points_to(body: &SsaBody, _cfg: &Cfg, lang: Option<Lang>) -> PointsToResult {
let mut pts: HashMap<SsaValue, PointsToSet> = HashMap::new();
for block in &body.blocks {
for inst in block.phis.iter().chain(block.body.iter()) {
match &inst.op {
SsaOp::Const(Some(text)) if is_container_literal(text) => {
pts.insert(inst.value, PointsToSet::singleton(HeapObjectId(inst.value)));
}
SsaOp::Call { callee, .. } => {
if let Some(l) = lang {
if is_container_constructor(callee, l) {
pts.insert(
inst.value,
PointsToSet::singleton(HeapObjectId(inst.value)),
);
}
}
}
_ => {}
}
}
}
if pts.is_empty() {
return PointsToResult::empty();
}
let max_rounds = 10;
for _ in 0..max_rounds {
let mut changed = false;
for block in &body.blocks {
for inst in &block.phis {
if let SsaOp::Phi(operands) = &inst.op {
let mut merged = PointsToSet::empty();
for (_, v) in operands {
if let Some(p) = pts.get(v) {
merged = merged.union(p);
}
}
if !merged.is_empty() {
let old = pts.get(&inst.value);
if old.map_or(true, |o| o != &merged) {
let existing = pts.entry(inst.value).or_insert_with(PointsToSet::empty);
let new = existing.union(&merged);
if &new != existing {
*existing = new;
changed = true;
}
}
}
}
}
for inst in &block.body {
match &inst.op {
SsaOp::Assign(uses) => {
let mut merged = PointsToSet::empty();
for &u in uses {
if let Some(p) = pts.get(&u) {
merged = merged.union(p);
}
}
if !merged.is_empty() {
let old = pts.get(&inst.value);
if old.map_or(true, |o| o != &merged) {
pts.insert(inst.value, merged);
changed = true;
}
}
}
SsaOp::Call {
callee,
args,
receiver,
..
} => {
if let Some(l) = lang {
if let Some(ContainerOp::Store { .. }) =
classify_container_op(callee, l)
{
let recv_pts =
receiver.and_then(|rv| pts.get(&rv).cloned()).or_else(|| {
if l == Lang::Go {
args.first()
.and_then(|a| a.first())
.and_then(|&v| pts.get(&v).cloned())
} else {
let dot_pos = callee.rfind('.')?;
let recv_name = &callee[..dot_pos];
for arg_group in args {
for &v in arg_group {
if let Some(def) =
body.value_defs.get(v.0 as usize)
{
if def.var_name.as_deref()
== Some(recv_name)
{
return pts.get(&v).cloned();
}
}
}
}
None
}
});
if l == Lang::Go && receiver.is_none() {
if let Some(rp) = recv_pts {
let old = pts.get(&inst.value);
if old.map_or(true, |o| o != &rp) {
pts.insert(inst.value, rp);
changed = true;
}
}
}
}
}
}
_ => {}
}
}
}
if !changed {
break;
}
}
PointsToResult { pts }
}
#[cfg(test)]
mod tests {
use super::*;
use crate::labels::SourceKind;
use petgraph::graph::NodeIndex;
use std::sync::Mutex;
static TEST_GUARD: Mutex<()> = Mutex::new(());
fn origin(idx: u32) -> TaintOrigin {
TaintOrigin {
node: NodeIndex::new(idx as usize),
source_kind: SourceKind::UserInput,
source_span: None,
}
}
#[test]
fn pts_singleton() {
let s = PointsToSet::singleton(HeapObjectId(SsaValue(0)));
assert_eq!(s.len(), 1);
assert!(s.contains(HeapObjectId(SsaValue(0))));
assert!(!s.contains(HeapObjectId(SsaValue(1))));
}
#[test]
fn pts_union() {
let a = PointsToSet::singleton(HeapObjectId(SsaValue(1)));
let b = PointsToSet::singleton(HeapObjectId(SsaValue(3)));
let c = a.union(&b);
assert_eq!(c.len(), 2);
assert!(c.contains(HeapObjectId(SsaValue(1))));
assert!(c.contains(HeapObjectId(SsaValue(3))));
}
#[test]
fn pts_union_dedup() {
let a = PointsToSet::singleton(HeapObjectId(SsaValue(1)));
let b = PointsToSet::singleton(HeapObjectId(SsaValue(1)));
let c = a.union(&b);
assert_eq!(c.len(), 1);
}
#[test]
fn pts_union_overflow() {
let _g = TEST_GUARD.lock().unwrap_or_else(|e| e.into_inner());
set_max_pointsto_override(8);
reset_points_to_observability();
let cap = effective_max_pointsto();
let mut big = PointsToSet::empty();
for i in 0..cap as u32 {
big.insert(HeapObjectId(SsaValue(i)));
}
assert_eq!(big.len(), cap);
let extra = PointsToSet::singleton(HeapObjectId(SsaValue(100)));
let result = big.union(&extra);
assert_eq!(result.len(), cap);
assert_eq!(points_to_truncation_count(), 1);
set_max_pointsto_override(0);
reset_points_to_observability();
}
#[test]
fn pts_insert_overflow_counts_drops() {
let _g = TEST_GUARD.lock().unwrap_or_else(|e| e.into_inner());
set_max_pointsto_override(4);
reset_points_to_observability();
let mut s = PointsToSet::empty();
for i in 0..4u32 {
s.insert(HeapObjectId(SsaValue(i)));
}
assert_eq!(s.len(), 4);
assert_eq!(points_to_truncation_count(), 0);
for i in 4..7u32 {
s.insert(HeapObjectId(SsaValue(i)));
}
assert_eq!(s.len(), 4);
assert_eq!(points_to_truncation_count(), 3);
s.insert(HeapObjectId(SsaValue(0)));
assert_eq!(points_to_truncation_count(), 3);
set_max_pointsto_override(0);
reset_points_to_observability();
}
#[test]
fn pts_union_overflow_counts_exact_drops() {
let _g = TEST_GUARD.lock().unwrap_or_else(|e| e.into_inner());
set_max_pointsto_override(4);
reset_points_to_observability();
let mut a = PointsToSet::empty();
for i in 0..4u32 {
a.insert(HeapObjectId(SsaValue(i)));
}
let mut b = PointsToSet::empty();
for i in 4..7u32 {
b.insert(HeapObjectId(SsaValue(i)));
}
assert_eq!(points_to_truncation_count(), 0);
let c = a.union(&b);
assert_eq!(c.len(), 4);
assert!(c.contains(HeapObjectId(SsaValue(0))));
assert!(c.contains(HeapObjectId(SsaValue(3))));
assert!(!c.contains(HeapObjectId(SsaValue(6))));
assert_eq!(points_to_truncation_count(), 3);
set_max_pointsto_override(0);
reset_points_to_observability();
}
#[test]
fn pts_reset_observability_clears_counter() {
let _g = TEST_GUARD.lock().unwrap_or_else(|e| e.into_inner());
set_max_pointsto_override(2);
reset_points_to_observability();
let mut s = PointsToSet::empty();
s.insert(HeapObjectId(SsaValue(0)));
s.insert(HeapObjectId(SsaValue(1)));
s.insert(HeapObjectId(SsaValue(2))); assert_eq!(points_to_truncation_count(), 1);
reset_points_to_observability();
assert_eq!(points_to_truncation_count(), 0);
set_max_pointsto_override(0);
}
#[test]
fn pts_effective_cap_defaults_to_runtime() {
let _g = TEST_GUARD.lock().unwrap_or_else(|e| e.into_inner());
set_max_pointsto_override(0);
assert_eq!(
effective_max_pointsto(),
crate::utils::analysis_options::DEFAULT_MAX_POINTSTO as usize
);
set_max_pointsto_override(5);
assert_eq!(effective_max_pointsto(), 5);
set_max_pointsto_override(0);
}
#[test]
fn pts_empty() {
let e = PointsToSet::empty();
assert!(e.is_empty());
assert_eq!(e.len(), 0);
}
#[test]
fn pts_insert() {
let mut s = PointsToSet::empty();
s.insert(HeapObjectId(SsaValue(5)));
s.insert(HeapObjectId(SsaValue(2)));
s.insert(HeapObjectId(SsaValue(5))); assert_eq!(s.len(), 2);
let ids: Vec<_> = s.iter().collect();
assert_eq!(ids[0].0, SsaValue(2));
assert_eq!(ids[1].0, SsaValue(5));
}
#[test]
fn heap_store_and_load() {
let mut h = HeapState::empty();
let id = HeapObjectId(SsaValue(0));
h.store(id, HeapSlot::Elements, Cap::HTML_ESCAPE, &[origin(0)]);
let t = h.load(id, HeapSlot::Elements).unwrap();
assert_eq!(t.caps, Cap::HTML_ESCAPE);
assert_eq!(t.origins.len(), 1);
}
#[test]
fn heap_store_monotone_merge() {
let mut h = HeapState::empty();
let id = HeapObjectId(SsaValue(0));
h.store(id, HeapSlot::Elements, Cap::HTML_ESCAPE, &[origin(0)]);
h.store(id, HeapSlot::Elements, Cap::SQL_QUERY, &[origin(1)]);
let t = h.load(id, HeapSlot::Elements).unwrap();
assert_eq!(t.caps, Cap::HTML_ESCAPE | Cap::SQL_QUERY);
assert_eq!(t.origins.len(), 2);
}
#[test]
fn heap_store_empty_caps_noop() {
let mut h = HeapState::empty();
h.store(
HeapObjectId(SsaValue(0)),
HeapSlot::Elements,
Cap::empty(),
&[origin(0)],
);
assert!(h.is_empty());
}
#[test]
fn heap_load_missing() {
let h = HeapState::empty();
assert!(
h.load(HeapObjectId(SsaValue(0)), HeapSlot::Elements)
.is_none()
);
}
#[test]
fn heap_load_set_unions() {
let mut h = HeapState::empty();
h.store(
HeapObjectId(SsaValue(0)),
HeapSlot::Elements,
Cap::HTML_ESCAPE,
&[origin(0)],
);
h.store(
HeapObjectId(SsaValue(1)),
HeapSlot::Elements,
Cap::SQL_QUERY,
&[origin(1)],
);
let mut pts = PointsToSet::empty();
pts.insert(HeapObjectId(SsaValue(0)));
pts.insert(HeapObjectId(SsaValue(1)));
let t = h.load_set(&pts, HeapSlot::Elements).unwrap();
assert_eq!(t.caps, Cap::HTML_ESCAPE | Cap::SQL_QUERY);
assert_eq!(t.origins.len(), 2);
}
#[test]
fn heap_load_set_empty_pts() {
let mut h = HeapState::empty();
h.store(
HeapObjectId(SsaValue(0)),
HeapSlot::Elements,
Cap::HTML_ESCAPE,
&[origin(0)],
);
let pts = PointsToSet::empty();
assert!(h.load_set(&pts, HeapSlot::Elements).is_none());
}
#[test]
fn heap_store_set() {
let mut h = HeapState::empty();
let mut pts = PointsToSet::empty();
pts.insert(HeapObjectId(SsaValue(0)));
pts.insert(HeapObjectId(SsaValue(1)));
h.store_set(&pts, HeapSlot::Elements, Cap::HTML_ESCAPE, &[origin(0)]);
assert_eq!(
h.load(HeapObjectId(SsaValue(0)), HeapSlot::Elements)
.unwrap()
.caps,
Cap::HTML_ESCAPE
);
assert_eq!(
h.load(HeapObjectId(SsaValue(1)), HeapSlot::Elements)
.unwrap()
.caps,
Cap::HTML_ESCAPE
);
}
#[test]
fn heap_join() {
let mut a = HeapState::empty();
a.store(
HeapObjectId(SsaValue(0)),
HeapSlot::Elements,
Cap::HTML_ESCAPE,
&[origin(0)],
);
let mut b = HeapState::empty();
b.store(
HeapObjectId(SsaValue(0)),
HeapSlot::Elements,
Cap::SQL_QUERY,
&[origin(1)],
);
b.store(
HeapObjectId(SsaValue(1)),
HeapSlot::Elements,
Cap::FILE_IO,
&[origin(2)],
);
let c = a.join(&b);
let t0 = c
.load(HeapObjectId(SsaValue(0)), HeapSlot::Elements)
.unwrap();
assert_eq!(t0.caps, Cap::HTML_ESCAPE | Cap::SQL_QUERY);
let t1 = c
.load(HeapObjectId(SsaValue(1)), HeapSlot::Elements)
.unwrap();
assert_eq!(t1.caps, Cap::FILE_IO);
}
#[test]
fn heap_leq() {
let mut a = HeapState::empty();
a.store(
HeapObjectId(SsaValue(0)),
HeapSlot::Elements,
Cap::HTML_ESCAPE,
&[origin(0)],
);
let mut b = HeapState::empty();
b.store(
HeapObjectId(SsaValue(0)),
HeapSlot::Elements,
Cap::HTML_ESCAPE | Cap::SQL_QUERY,
&[origin(0)],
);
assert!(a.leq(&b)); assert!(!b.leq(&a)); }
#[test]
fn heap_leq_missing_entry() {
let mut a = HeapState::empty();
a.store(
HeapObjectId(SsaValue(5)),
HeapSlot::Elements,
Cap::HTML_ESCAPE,
&[origin(0)],
);
let b = HeapState::empty();
assert!(!a.leq(&b)); assert!(b.leq(&a)); }
#[test]
fn heap_indexed_store_load_isolation() {
let mut h = HeapState::empty();
let id = HeapObjectId(SsaValue(0));
h.store(id, HeapSlot::Index(0), Cap::HTML_ESCAPE, &[origin(0)]);
let t0 = h.load(id, HeapSlot::Index(0)).unwrap();
assert_eq!(t0.caps, Cap::HTML_ESCAPE);
assert!(h.load(id, HeapSlot::Index(1)).is_none());
}
#[test]
fn heap_indexed_load_unions_with_elements() {
let mut h = HeapState::empty();
let id = HeapObjectId(SsaValue(0));
h.store(id, HeapSlot::Elements, Cap::SQL_QUERY, &[origin(0)]);
let t = h.load(id, HeapSlot::Index(1)).unwrap();
assert_eq!(t.caps, Cap::SQL_QUERY);
}
#[test]
fn heap_elements_load_unions_all_indices() {
let mut h = HeapState::empty();
let id = HeapObjectId(SsaValue(0));
h.store(id, HeapSlot::Index(0), Cap::HTML_ESCAPE, &[origin(0)]);
h.store(id, HeapSlot::Index(2), Cap::SQL_QUERY, &[origin(1)]);
let t = h.load(id, HeapSlot::Elements).unwrap();
assert_eq!(t.caps, Cap::HTML_ESCAPE | Cap::SQL_QUERY);
}
#[test]
fn heap_indexed_and_elements_combined() {
let mut h = HeapState::empty();
let id = HeapObjectId(SsaValue(0));
h.store(id, HeapSlot::Index(0), Cap::HTML_ESCAPE, &[origin(0)]);
h.store(id, HeapSlot::Elements, Cap::FILE_IO, &[origin(1)]);
let t0 = h.load(id, HeapSlot::Index(0)).unwrap();
assert_eq!(t0.caps, Cap::HTML_ESCAPE | Cap::FILE_IO);
let t1 = h.load(id, HeapSlot::Index(1)).unwrap();
assert_eq!(t1.caps, Cap::FILE_IO); }
#[test]
fn heap_max_tracked_indices_collapse() {
let mut h = HeapState::empty();
let id = HeapObjectId(SsaValue(0));
for i in 0..MAX_TRACKED_INDICES as u64 {
h.store(
id,
HeapSlot::Index(i),
Cap::HTML_ESCAPE,
&[origin(i as u32)],
);
}
h.store(
id,
HeapSlot::Index(MAX_TRACKED_INDICES as u64),
Cap::SQL_QUERY,
&[origin(99)],
);
assert_eq!(h.count_indices_for(id), 0);
let t = h.load(id, HeapSlot::Elements).unwrap();
assert!(t.caps.contains(Cap::HTML_ESCAPE));
assert!(t.caps.contains(Cap::SQL_QUERY));
}
#[test]
fn container_literal_detection() {
assert!(is_container_literal("[]"));
assert!(is_container_literal("[1, 2, 3]"));
assert!(is_container_literal("{}"));
assert!(is_container_literal("{a: 1}"));
assert!(is_container_literal("new Map()"));
assert!(is_container_literal("new ArrayList<>()"));
assert!(is_container_literal("dict()"));
assert!(is_container_literal("list()"));
assert!(is_container_literal("set()"));
assert!(!is_container_literal("42"));
assert!(!is_container_literal("\"hello\""));
assert!(!is_container_literal("true"));
}
#[test]
fn container_constructor_js() {
assert!(is_container_constructor("Array", Lang::JavaScript));
assert!(is_container_constructor("Map", Lang::JavaScript));
assert!(is_container_constructor("Set", Lang::JavaScript));
assert!(!is_container_constructor("Object", Lang::JavaScript));
}
#[test]
fn container_constructor_python() {
assert!(is_container_constructor("list", Lang::Python));
assert!(is_container_constructor("dict", Lang::Python));
assert!(is_container_constructor("defaultdict", Lang::Python));
assert!(!is_container_constructor("str", Lang::Python));
}
#[test]
fn container_constructor_java() {
assert!(is_container_constructor("ArrayList", Lang::Java));
assert!(is_container_constructor("HashMap", Lang::Java));
assert!(is_container_constructor("ConcurrentHashMap", Lang::Java));
assert!(!is_container_constructor("String", Lang::Java));
}
#[test]
fn container_constructor_go() {
assert!(is_container_constructor("make", Lang::Go));
assert!(!is_container_constructor("new", Lang::Go));
}
#[test]
fn container_constructor_rust() {
assert!(is_container_constructor("Vec::new", Lang::Rust));
assert!(is_container_constructor("HashMap::new", Lang::Rust));
assert!(!is_container_constructor("String::new", Lang::Rust));
assert!(!is_container_constructor("new", Lang::Rust));
}
#[test]
fn container_constructor_cpp() {
assert!(is_container_constructor("vector", Lang::Cpp));
assert!(is_container_constructor("std::map", Lang::Cpp));
assert!(is_container_constructor("unordered_set", Lang::Cpp));
}
#[test]
fn pts_result_empty() {
let r = PointsToResult::empty();
assert!(r.is_empty());
assert!(r.get(SsaValue(0)).is_none());
}
}