use crate::labels::{Cap, DataLabel};
use crate::symbol::{FuncKey, Lang, normalize_namespace};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FuncSummary {
pub name: String,
pub file_path: String,
pub lang: String,
pub param_count: usize,
pub param_names: Vec<String>,
pub source_caps: u8,
pub sanitizer_caps: u8,
pub sink_caps: u8,
pub propagates_taint: bool,
pub tainted_sink_params: Vec<usize>,
pub callees: Vec<String>,
}
impl FuncSummary {
#[inline]
pub fn source_caps(&self) -> Cap {
Cap::from_bits_truncate(self.source_caps)
}
#[inline]
pub fn sanitizer_caps(&self) -> Cap {
Cap::from_bits_truncate(self.sanitizer_caps)
}
#[inline]
pub fn sink_caps(&self) -> Cap {
Cap::from_bits_truncate(self.sink_caps)
}
#[allow(dead_code)]
pub fn primary_label(&self) -> Option<DataLabel> {
let sink = self.sink_caps();
let src = self.source_caps();
let san = self.sanitizer_caps();
if !sink.is_empty() {
Some(DataLabel::Sink(sink))
} else if !src.is_empty() {
Some(DataLabel::Source(src))
} else if !san.is_empty() {
Some(DataLabel::Sanitizer(san))
} else {
None
}
}
#[allow(dead_code)]
pub fn is_interesting(&self) -> bool {
self.source_caps != 0
|| self.sanitizer_caps != 0
|| self.sink_caps != 0
|| self.propagates_taint
}
pub fn func_key(&self, scan_root: Option<&str>) -> FuncKey {
FuncKey {
lang: Lang::from_slug(&self.lang).unwrap_or(Lang::Rust),
namespace: normalize_namespace(&self.file_path, scan_root),
name: self.name.clone(),
arity: Some(self.param_count),
}
}
}
#[derive(Default)]
pub struct GlobalSummaries {
by_key: HashMap<FuncKey, FuncSummary>,
by_lang_name: HashMap<(Lang, String), Vec<FuncKey>>,
}
impl GlobalSummaries {
pub fn new() -> Self {
Self::default()
}
pub fn insert(&mut self, key: FuncKey, summary: FuncSummary) {
let lang = key.lang;
let name = key.name.clone();
self.by_key
.entry(key.clone())
.and_modify(|existing| {
existing.source_caps |= summary.source_caps;
existing.sanitizer_caps |= summary.sanitizer_caps;
existing.sink_caps |= summary.sink_caps;
existing.propagates_taint |= summary.propagates_taint;
for &idx in &summary.tainted_sink_params {
if !existing.tainted_sink_params.contains(&idx) {
existing.tainted_sink_params.push(idx);
}
}
for c in &summary.callees {
if !existing.callees.contains(c) {
existing.callees.push(c.clone());
}
}
})
.or_insert(summary);
let keys = self.by_lang_name.entry((lang, name)).or_default();
if !keys.contains(&key) {
keys.push(key);
}
}
pub fn get(&self, key: &FuncKey) -> Option<&FuncSummary> {
self.by_key.get(key)
}
pub fn lookup_same_lang(&self, lang: Lang, name: &str) -> Vec<(&FuncKey, &FuncSummary)> {
self.by_lang_name
.get(&(lang, name.to_string()))
.map(|keys| {
keys.iter()
.filter_map(|k| self.by_key.get(k).map(|v| (k, v)))
.collect()
})
.unwrap_or_default()
}
pub fn merge(&mut self, other: GlobalSummaries) {
for (key, summary) in other.by_key {
self.insert(key, summary);
}
}
#[allow(dead_code)]
pub fn is_empty(&self) -> bool {
self.by_key.is_empty()
}
#[allow(dead_code)]
pub fn iter(&self) -> impl Iterator<Item = (&FuncKey, &FuncSummary)> {
self.by_key.iter()
}
}
impl std::fmt::Debug for GlobalSummaries {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("GlobalSummaries")
.field("len", &self.by_key.len())
.finish()
}
}
pub fn merge_summaries(
per_file: impl IntoIterator<Item = FuncSummary>,
scan_root: Option<&str>,
) -> GlobalSummaries {
let mut map = GlobalSummaries::new();
for fs in per_file {
let key = fs.func_key(scan_root);
map.insert(key, fs);
}
map
}
#[cfg(test)]
mod tests;