use std::collections::{HashMap, HashSet, VecDeque};
use super::cfg::{CfgNodeId, ControlFlowGraph};
use super::pdg::ProgramDependenceGraph;
use petgraph::graph::NodeIndex;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum TaintSlot {
Param(usize),
Return,
}
#[derive(Debug, Clone)]
pub struct ParamInEdge {
pub caller_func: NodeIndex,
pub caller_block: CfgNodeId,
pub callee_func: NodeIndex,
pub formal_param_index: usize,
}
#[derive(Debug, Clone)]
pub struct ParamOutEdge {
pub callee_func: NodeIndex,
pub callee_return_block: CfgNodeId,
pub caller_func: NodeIndex,
pub caller_result_block: CfgNodeId,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct SummaryEdge {
pub func: NodeIndex,
pub from_param: usize,
pub to_output: TaintSlot,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct SdgNode {
pub func: NodeIndex,
pub block: CfgNodeId,
}
#[derive(Debug, Clone)]
pub struct InterproceduralSliceCriterion {
pub func: NodeIndex,
pub block: CfgNodeId,
pub variable: Option<String>,
}
#[derive(Debug, Clone)]
pub struct InterproceduralSlice {
pub nodes: HashSet<SdgNode>,
pub criterion: InterproceduralSliceCriterion,
}
impl InterproceduralSlice {
pub fn contains(&self, func: NodeIndex, block: CfgNodeId) -> bool {
self.nodes.contains(&SdgNode { func, block })
}
pub fn len(&self) -> usize {
self.nodes.len()
}
pub fn is_empty(&self) -> bool {
self.nodes.is_empty()
}
pub fn functions_involved(&self) -> HashSet<NodeIndex> {
self.nodes.iter().map(|n| n.func).collect()
}
}
#[derive(Debug, Clone)]
pub struct CallEdgeDescriptor {
pub caller_func: NodeIndex,
pub caller_block: CfgNodeId,
pub callee_func: NodeIndex,
pub argument_count: usize,
pub result_block: CfgNodeId,
}
pub struct SystemDependenceGraph {
pub function_pdgs: HashMap<NodeIndex, ProgramDependenceGraph>,
pub function_cfgs: HashMap<NodeIndex, ControlFlowGraph>,
pub call_edges: Vec<(SdgNode, NodeIndex)>,
pub param_in_edges: Vec<ParamInEdge>,
pub param_out_edges: Vec<ParamOutEdge>,
pub summary_edges: Vec<SummaryEdge>,
}
impl SystemDependenceGraph {
pub fn build(
function_pdgs: HashMap<NodeIndex, ProgramDependenceGraph>,
function_cfgs: HashMap<NodeIndex, ControlFlowGraph>,
call_edges_raw: Vec<CallEdgeDescriptor>,
) -> Self {
let mut call_edges = Vec::new();
let mut param_in_edges = Vec::new();
let mut param_out_edges = Vec::new();
for desc in &call_edges_raw {
call_edges.push((
SdgNode {
func: desc.caller_func,
block: desc.caller_block,
},
desc.callee_func,
));
for param_idx in 0..desc.argument_count {
param_in_edges.push(ParamInEdge {
caller_func: desc.caller_func,
caller_block: desc.caller_block,
callee_func: desc.callee_func,
formal_param_index: param_idx,
});
}
if let Some(callee_cfg) = function_cfgs.get(&desc.callee_func) {
if let Some(exit_id) = callee_cfg.exit() {
param_out_edges.push(ParamOutEdge {
callee_func: desc.callee_func,
callee_return_block: exit_id,
caller_func: desc.caller_func,
caller_result_block: desc.result_block,
});
}
}
}
let mut sdg = Self {
function_pdgs,
function_cfgs,
call_edges,
param_in_edges,
param_out_edges,
summary_edges: Vec::new(),
};
sdg.compute_summary_edges();
sdg
}
fn compute_summary_edges(&mut self) {
let callee_funcs: HashSet<NodeIndex> =
self.call_edges.iter().map(|(_, callee)| *callee).collect();
let mut all_summaries: HashSet<SummaryEdge> = HashSet::new();
let mut changed = true;
while changed {
changed = false;
for &callee_func in &callee_funcs {
let pdg = match self.function_pdgs.get(&callee_func) {
Some(p) => p,
None => continue,
};
let cfg = match self.function_cfgs.get(&callee_func) {
Some(c) => c,
None => continue,
};
let exit_id = match cfg.exit() {
Some(e) => e,
None => continue,
};
let entry_id = match cfg.entry() {
Some(e) => e,
None => continue,
};
let max_param = self
.param_in_edges
.iter()
.filter(|e| e.callee_func == callee_func)
.map(|e| e.formal_param_index)
.max();
let param_count = match max_param {
Some(m) => m + 1,
None => {
continue;
}
};
let reachable_from_exit = pdg.backward_reachable(exit_id);
if reachable_from_exit.contains(&entry_id) {
for param_idx in 0..param_count {
let edge = SummaryEdge {
func: callee_func,
from_param: param_idx,
to_output: TaintSlot::Return,
};
if all_summaries.insert(edge.clone()) {
changed = true;
}
}
}
}
}
self.summary_edges = all_summaries.into_iter().collect();
}
pub fn blocks_of(&self, func: NodeIndex) -> Vec<CfgNodeId> {
if let Some(pdg) = self.function_pdgs.get(&func) {
pdg.cfg_blocks().to_vec()
} else if let Some(cfg) = self.function_cfgs.get(&func) {
cfg.blocks().map(|(&id, _)| id).collect()
} else {
Vec::new()
}
}
pub fn interprocedural_backward_slice(
&self,
criterion: &InterproceduralSliceCriterion,
) -> InterproceduralSlice {
let mut result_nodes: HashSet<SdgNode> = HashSet::new();
let mut worklist: VecDeque<SdgNode> = VecDeque::new();
let start = SdgNode {
func: criterion.func,
block: criterion.block,
};
result_nodes.insert(start);
worklist.push_back(start);
let mut callee_entries_for_phase2: Vec<SdgNode> = Vec::new();
while let Some(current) = worklist.pop_front() {
if let Some(pdg) = self.function_pdgs.get(¤t.func) {
for &(a, b) in pdg.control_dep_edges() {
if b == current.block {
let node = SdgNode {
func: current.func,
block: a,
};
if result_nodes.insert(node) {
worklist.push_back(node);
}
}
}
for (a, b, _var) in pdg.data_dep_edges() {
if *b == current.block {
let node = SdgNode {
func: current.func,
block: *a,
};
if result_nodes.insert(node) {
worklist.push_back(node);
}
}
}
}
for (caller_sdg_node, callee_func) in &self.call_edges {
if caller_sdg_node.func == current.func && caller_sdg_node.block == current.block {
for se in &self.summary_edges {
if se.func == *callee_func {
let node = SdgNode {
func: current.func,
block: current.block,
};
result_nodes.insert(node);
}
}
if let Some(callee_cfg) = self.function_cfgs.get(callee_func) {
if let Some(callee_entry) = callee_cfg.entry() {
let callee_node = SdgNode {
func: *callee_func,
block: callee_entry,
};
if result_nodes.insert(callee_node) {
callee_entries_for_phase2.push(callee_node);
}
}
if let Some(callee_exit) = callee_cfg.exit() {
let callee_exit_node = SdgNode {
func: *callee_func,
block: callee_exit,
};
if result_nodes.insert(callee_exit_node) {
callee_entries_for_phase2.push(callee_exit_node);
}
}
}
}
}
for poe in &self.param_out_edges {
if poe.caller_func == current.func && poe.caller_result_block == current.block {
let callee_ret = SdgNode {
func: poe.callee_func,
block: poe.callee_return_block,
};
if result_nodes.insert(callee_ret) {
callee_entries_for_phase2.push(callee_ret);
}
}
}
}
let mut phase2_worklist: VecDeque<SdgNode> = VecDeque::new();
for node in &callee_entries_for_phase2 {
phase2_worklist.push_back(*node);
}
let mut phase2_visited: HashSet<SdgNode> = HashSet::new();
for node in &callee_entries_for_phase2 {
phase2_visited.insert(*node);
}
while let Some(current) = phase2_worklist.pop_front() {
if let Some(pdg) = self.function_pdgs.get(¤t.func) {
for &(a, b) in pdg.control_dep_edges() {
if b == current.block {
let node = SdgNode {
func: current.func,
block: a,
};
if phase2_visited.insert(node) {
result_nodes.insert(node);
phase2_worklist.push_back(node);
}
}
}
for (a, b, _var) in pdg.data_dep_edges() {
if *b == current.block {
let node = SdgNode {
func: current.func,
block: *a,
};
if phase2_visited.insert(node) {
result_nodes.insert(node);
phase2_worklist.push_back(node);
}
}
}
}
}
InterproceduralSlice {
nodes: result_nodes,
criterion: criterion.clone(),
}
}
pub fn interprocedural_forward_slice(
&self,
criterion: &InterproceduralSliceCriterion,
) -> InterproceduralSlice {
let mut result_nodes: HashSet<SdgNode> = HashSet::new();
let mut worklist: VecDeque<SdgNode> = VecDeque::new();
let start = SdgNode {
func: criterion.func,
block: criterion.block,
};
result_nodes.insert(start);
worklist.push_back(start);
let mut caller_sites_for_phase2: Vec<SdgNode> = Vec::new();
while let Some(current) = worklist.pop_front() {
if let Some(pdg) = self.function_pdgs.get(¤t.func) {
for &(a, b) in pdg.control_dep_edges() {
if a == current.block {
let node = SdgNode {
func: current.func,
block: b,
};
if result_nodes.insert(node) {
worklist.push_back(node);
}
}
}
for (a, b, _var) in pdg.data_dep_edges() {
if *a == current.block {
let node = SdgNode {
func: current.func,
block: *b,
};
if result_nodes.insert(node) {
worklist.push_back(node);
}
}
}
}
for (caller_sdg_node, callee_func) in &self.call_edges {
if caller_sdg_node.func == current.func && caller_sdg_node.block == current.block {
if let Some(callee_cfg) = self.function_cfgs.get(callee_func) {
if let Some(callee_entry) = callee_cfg.entry() {
let callee_node = SdgNode {
func: *callee_func,
block: callee_entry,
};
if result_nodes.insert(callee_node) {
worklist.push_back(callee_node);
}
}
}
}
}
for poe in &self.param_out_edges {
if poe.callee_func == current.func && poe.callee_return_block == current.block {
let caller_result = SdgNode {
func: poe.caller_func,
block: poe.caller_result_block,
};
if result_nodes.insert(caller_result) {
caller_sites_for_phase2.push(caller_result);
}
}
}
}
let mut phase2_worklist: VecDeque<SdgNode> = VecDeque::new();
let mut phase2_visited: HashSet<SdgNode> = HashSet::new();
for node in &caller_sites_for_phase2 {
phase2_worklist.push_back(*node);
phase2_visited.insert(*node);
}
while let Some(current) = phase2_worklist.pop_front() {
if let Some(pdg) = self.function_pdgs.get(¤t.func) {
for &(a, b) in pdg.control_dep_edges() {
if a == current.block {
let node = SdgNode {
func: current.func,
block: b,
};
if phase2_visited.insert(node) {
result_nodes.insert(node);
phase2_worklist.push_back(node);
}
}
}
for (a, b, _var) in pdg.data_dep_edges() {
if *a == current.block {
let node = SdgNode {
func: current.func,
block: *b,
};
if phase2_visited.insert(node) {
result_nodes.insert(node);
phase2_worklist.push_back(node);
}
}
}
}
}
InterproceduralSlice {
nodes: result_nodes,
criterion: criterion.clone(),
}
}
pub fn callees_of(&self, func: NodeIndex) -> HashSet<NodeIndex> {
self.call_edges
.iter()
.filter(|(caller, _)| caller.func == func)
.map(|(_, callee)| *callee)
.collect()
}
pub fn callers_of(&self, func: NodeIndex) -> HashSet<NodeIndex> {
self.call_edges
.iter()
.filter(|(_, callee)| *callee == func)
.map(|(caller, _)| caller.func)
.collect()
}
pub fn total_intraprocedural_edges(&self) -> usize {
self.function_pdgs
.values()
.map(|pdg| pdg.control_dep_edges().len() + pdg.data_dep_edges().len())
.sum()
}
pub fn total_interprocedural_edges(&self) -> usize {
self.call_edges.len()
+ self.param_in_edges.len()
+ self.param_out_edges.len()
+ self.summary_edges.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::SourceSpan;
use crate::graph::cfg::{CfgEdgeKind, ControlFlowGraph};
use crate::graph::dataflow::{BlockDataFlow, DefPoint, ReachingDefinitions, UsePoint, VarRef};
use crate::graph::pdg::ProgramDependenceGraph;
use std::collections::{HashMap, HashSet};
fn make_linear_cfg(
name: &str,
entry_defs: Vec<&str>,
body_uses: Vec<&str>,
body_defs: Vec<&str>,
exit_uses: Vec<&str>,
) -> (
ControlFlowGraph,
HashMap<CfgNodeId, BlockDataFlow>,
ReachingDefinitions,
Vec<CfgNodeId>,
) {
let mut cfg = ControlFlowGraph::new(name);
let entry = cfg.create_entry();
let exit = cfg.create_exit();
let body = cfg.create_block("body");
cfg.add_statement(entry, SourceSpan::new(0, 5));
cfg.add_statement(body, SourceSpan::new(6, 15));
cfg.add_edge(entry, body, CfgEdgeKind::FallThrough);
cfg.add_edge(body, exit, CfgEdgeKind::FallThrough);
let mut byte_offset = 0usize;
let mut all_defs_global = Vec::new();
let entry_def_points: Vec<DefPoint> = entry_defs
.iter()
.enumerate()
.map(|(i, &name)| {
let dp = DefPoint {
var: VarRef::new(name),
block: entry,
stmt_index: i,
start_byte: byte_offset,
end_byte: byte_offset + 5,
};
byte_offset += 6;
dp
})
.collect();
all_defs_global.extend(entry_def_points.iter().cloned());
let body_def_points: Vec<DefPoint> = body_defs
.iter()
.enumerate()
.map(|(i, &name)| {
let dp = DefPoint {
var: VarRef::new(name),
block: body,
stmt_index: i,
start_byte: byte_offset,
end_byte: byte_offset + 5,
};
byte_offset += 6;
dp
})
.collect();
all_defs_global.extend(body_def_points.iter().cloned());
let body_use_points: Vec<UsePoint> = body_uses
.iter()
.enumerate()
.map(|(i, &name)| UsePoint {
var: VarRef::new(name),
block: body,
stmt_index: i,
start_byte: byte_offset,
end_byte: byte_offset + 3,
})
.collect();
let exit_use_points: Vec<UsePoint> = exit_uses
.iter()
.enumerate()
.map(|(i, &name)| UsePoint {
var: VarRef::new(name),
block: exit,
stmt_index: i,
start_byte: byte_offset,
end_byte: byte_offset + 3,
})
.collect();
let mut block_facts = HashMap::new();
block_facts.insert(
entry,
BlockDataFlow {
defs: entry_def_points,
uses: vec![],
gen: HashSet::new(),
kill: HashSet::new(),
},
);
block_facts.insert(
body,
BlockDataFlow {
defs: body_def_points,
uses: body_use_points,
gen: HashSet::new(),
kill: HashSet::new(),
},
);
block_facts.insert(
exit,
BlockDataFlow {
defs: vec![],
uses: exit_use_points,
gen: HashSet::new(),
kill: HashSet::new(),
},
);
let mut sorted_blocks: Vec<CfgNodeId> = block_facts.keys().copied().collect();
sorted_blocks.sort();
let mut sorted_defs = Vec::new();
for &bid in &sorted_blocks {
if let Some(facts) = block_facts.get(&bid) {
sorted_defs.extend(facts.defs.iter().cloned());
}
}
let mut var_to_indices: HashMap<String, Vec<usize>> = HashMap::new();
for (i, def) in sorted_defs.iter().enumerate() {
var_to_indices
.entry(def.var.name.clone())
.or_default()
.push(i);
}
let mut reach_in: HashMap<CfgNodeId, HashSet<usize>> = HashMap::new();
let mut reach_out: HashMap<CfgNodeId, HashSet<usize>> = HashMap::new();
reach_in.insert(entry, HashSet::new());
let entry_def_indices: HashSet<usize> = sorted_defs
.iter()
.enumerate()
.filter(|(_, d)| d.block == entry)
.map(|(i, _)| i)
.collect();
reach_out.insert(entry, entry_def_indices.clone());
let body_reach_in = entry_def_indices.clone();
let body_def_indices: HashSet<usize> = sorted_defs
.iter()
.enumerate()
.filter(|(_, d)| d.block == body)
.map(|(i, _)| i)
.collect();
let body_reach_out: HashSet<usize> =
body_reach_in.union(&body_def_indices).copied().collect();
reach_in.insert(body, body_reach_in);
reach_out.insert(body, body_reach_out.clone());
reach_in.insert(exit, body_reach_out.clone());
reach_out.insert(exit, body_reach_out);
let reaching = ReachingDefinitions {
reach_in,
reach_out,
};
(cfg, block_facts, reaching, vec![entry, exit, body])
}
fn build_pdg_from_linear(
cfg: &ControlFlowGraph,
block_facts: &HashMap<CfgNodeId, BlockDataFlow>,
reaching: &ReachingDefinitions,
) -> ProgramDependenceGraph {
ProgramDependenceGraph::build(cfg, block_facts, reaching)
}
#[test]
fn test_single_function_backward_slice_matches_intraprocedural() {
let func_a = NodeIndex::new(0);
let (cfg_a, facts_a, reaching_a, ids_a) =
make_linear_cfg("func_a", vec!["x"], vec!["x"], vec!["y"], vec!["y"]);
let pdg_a = build_pdg_from_linear(&cfg_a, &facts_a, &reaching_a);
let _entry_a = ids_a[0];
let exit_a = ids_a[1];
let _body_a = ids_a[2];
let intra_reachable = pdg_a.backward_reachable(exit_a);
let mut function_pdgs = HashMap::new();
function_pdgs.insert(func_a, pdg_a);
let mut function_cfgs = HashMap::new();
function_cfgs.insert(func_a, cfg_a);
let sdg = SystemDependenceGraph::build(function_pdgs, function_cfgs, vec![]);
let criterion = InterproceduralSliceCriterion {
func: func_a,
block: exit_a,
variable: None,
};
let inter_slice = sdg.interprocedural_backward_slice(&criterion);
for &block in &intra_reachable {
assert!(
inter_slice.contains(func_a, block),
"Intra-procedural block {:?} should be in inter-procedural slice",
block
);
}
assert_eq!(
inter_slice.functions_involved().len(),
1,
"Only one function should be involved"
);
}
#[test]
fn test_interprocedural_backward_slice_crosses_call_boundary() {
let func_a = NodeIndex::new(0);
let func_b = NodeIndex::new(1);
let (cfg_a, facts_a, reaching_a, ids_a) =
make_linear_cfg("func_a", vec!["a"], vec!["a"], vec!["r"], vec!["r"]);
let pdg_a = build_pdg_from_linear(&cfg_a, &facts_a, &reaching_a);
let entry_a = ids_a[0];
let exit_a = ids_a[1];
let body_a = ids_a[2];
let (cfg_b, facts_b, reaching_b, ids_b) =
make_linear_cfg("func_b", vec!["p"], vec!["p"], vec!["q"], vec!["q"]);
let pdg_b = build_pdg_from_linear(&cfg_b, &facts_b, &reaching_b);
let _entry_b = ids_b[0];
let _exit_b = ids_b[1];
let _body_b = ids_b[2];
let mut function_pdgs = HashMap::new();
function_pdgs.insert(func_a, pdg_a);
function_pdgs.insert(func_b, pdg_b);
let mut function_cfgs = HashMap::new();
function_cfgs.insert(func_a, cfg_a.clone());
function_cfgs.insert(func_b, cfg_b.clone());
let call_desc = CallEdgeDescriptor {
caller_func: func_a,
caller_block: body_a,
callee_func: func_b,
argument_count: 1,
result_block: body_a,
};
let sdg = SystemDependenceGraph::build(function_pdgs, function_cfgs, vec![call_desc]);
let criterion = InterproceduralSliceCriterion {
func: func_a,
block: exit_a,
variable: None,
};
let slice = sdg.interprocedural_backward_slice(&criterion);
assert!(
slice.contains(func_a, exit_a),
"A's exit should be in the slice"
);
assert!(
slice.contains(func_a, body_a),
"A's body (call site) should be in the slice"
);
assert!(
slice.contains(func_a, entry_a),
"A's entry should be in the slice"
);
assert!(
slice.functions_involved().contains(&func_b),
"Function B should be involved in the slice"
);
}
#[test]
fn test_summary_edges_bypass_callee_body() {
let func_callee = NodeIndex::new(0);
let (cfg_c, facts_c, reaching_c, _ids_c) =
make_linear_cfg("callee", vec!["p"], vec!["p"], vec!["q"], vec!["q"]);
let pdg_c = build_pdg_from_linear(&cfg_c, &facts_c, &reaching_c);
let func_caller = NodeIndex::new(1);
let (cfg_caller, facts_caller, reaching_caller, ids_caller) =
make_linear_cfg("caller", vec!["a"], vec!["a"], vec!["r"], vec!["r"]);
let pdg_caller = build_pdg_from_linear(&cfg_caller, &facts_caller, &reaching_caller);
let body_caller = ids_caller[2];
let mut function_pdgs = HashMap::new();
function_pdgs.insert(func_callee, pdg_c);
function_pdgs.insert(func_caller, pdg_caller);
let mut function_cfgs = HashMap::new();
function_cfgs.insert(func_callee, cfg_c);
function_cfgs.insert(func_caller, cfg_caller);
let call_desc = CallEdgeDescriptor {
caller_func: func_caller,
caller_block: body_caller,
callee_func: func_callee,
argument_count: 1,
result_block: body_caller,
};
let sdg = SystemDependenceGraph::build(function_pdgs, function_cfgs, vec![call_desc]);
assert!(
!sdg.summary_edges.is_empty(),
"Summary edges should be computed for the callee"
);
let callee_summaries: Vec<_> = sdg
.summary_edges
.iter()
.filter(|se| se.func == func_callee)
.collect();
assert!(
!callee_summaries.is_empty(),
"Callee should have summary edges"
);
assert!(
callee_summaries
.iter()
.any(|se| se.from_param == 0 && se.to_output == TaintSlot::Return),
"Summary edge should map param 0 to Return, got: {:?}",
callee_summaries
);
}
#[test]
fn test_forward_slice_propagates_through_call_sites() {
let func_a = NodeIndex::new(0);
let func_b = NodeIndex::new(1);
let (cfg_a, facts_a, reaching_a, ids_a) =
make_linear_cfg("func_a", vec!["a"], vec!["a"], vec!["r"], vec!["r"]);
let pdg_a = build_pdg_from_linear(&cfg_a, &facts_a, &reaching_a);
let entry_a = ids_a[0];
let body_a = ids_a[2];
let (cfg_b, facts_b, reaching_b, _ids_b) =
make_linear_cfg("func_b", vec!["p"], vec!["p"], vec!["q"], vec!["q"]);
let pdg_b = build_pdg_from_linear(&cfg_b, &facts_b, &reaching_b);
let mut function_pdgs = HashMap::new();
function_pdgs.insert(func_a, pdg_a);
function_pdgs.insert(func_b, pdg_b);
let mut function_cfgs = HashMap::new();
function_cfgs.insert(func_a, cfg_a);
function_cfgs.insert(func_b, cfg_b);
let call_desc = CallEdgeDescriptor {
caller_func: func_a,
caller_block: body_a,
callee_func: func_b,
argument_count: 1,
result_block: body_a,
};
let sdg = SystemDependenceGraph::build(function_pdgs, function_cfgs, vec![call_desc]);
let criterion = InterproceduralSliceCriterion {
func: func_a,
block: entry_a,
variable: None,
};
let slice = sdg.interprocedural_forward_slice(&criterion);
assert!(
slice.contains(func_a, entry_a),
"A's entry should be in forward slice"
);
assert!(
slice.contains(func_a, body_a),
"A's body (call site) should be in forward slice"
);
assert!(
slice.functions_involved().contains(&func_b),
"Function B should be involved in the forward slice"
);
}
#[test]
fn test_phase2_does_not_ascend_to_callers() {
let func_a = NodeIndex::new(0);
let func_b = NodeIndex::new(1);
let func_c = NodeIndex::new(2);
let (cfg_a, facts_a, reaching_a, ids_a) =
make_linear_cfg("func_a", vec!["a"], vec!["a"], vec!["r"], vec!["a"]);
let pdg_a = build_pdg_from_linear(&cfg_a, &facts_a, &reaching_a);
let body_a = ids_a[2];
let (cfg_b, facts_b, reaching_b, ids_b) =
make_linear_cfg("func_b", vec!["p"], vec!["p"], vec!["q"], vec!["q"]);
let pdg_b = build_pdg_from_linear(&cfg_b, &facts_b, &reaching_b);
let entry_b = ids_b[0];
let exit_b = ids_b[1];
let body_b = ids_b[2];
let (cfg_c, facts_c, reaching_c, ids_c) =
make_linear_cfg("func_c", vec!["c"], vec!["c"], vec!["s"], vec!["s"]);
let pdg_c = build_pdg_from_linear(&cfg_c, &facts_c, &reaching_c);
let _entry_c = ids_c[0];
let body_c = ids_c[2];
let mut function_pdgs = HashMap::new();
function_pdgs.insert(func_a, pdg_a);
function_pdgs.insert(func_b, pdg_b);
function_pdgs.insert(func_c, pdg_c);
let mut function_cfgs = HashMap::new();
function_cfgs.insert(func_a, cfg_a);
function_cfgs.insert(func_b, cfg_b);
function_cfgs.insert(func_c, cfg_c);
let call_a_b = CallEdgeDescriptor {
caller_func: func_a,
caller_block: body_a,
callee_func: func_b,
argument_count: 1,
result_block: body_a,
};
let call_c_b = CallEdgeDescriptor {
caller_func: func_c,
caller_block: body_c,
callee_func: func_b,
argument_count: 1,
result_block: body_c,
};
let sdg =
SystemDependenceGraph::build(function_pdgs, function_cfgs, vec![call_a_b, call_c_b]);
let criterion = InterproceduralSliceCriterion {
func: func_b,
block: exit_b,
variable: None,
};
let slice = sdg.interprocedural_backward_slice(&criterion);
assert!(
slice.contains(func_b, exit_b),
"B's exit should be in the slice"
);
assert!(
slice.contains(func_b, entry_b),
"B's entry should be in the slice"
);
assert!(
slice.contains(func_b, body_b),
"B's body should be in the slice"
);
assert!(
!slice.functions_involved().contains(&func_a),
"Function A (caller) should NOT be in the slice when starting from B"
);
assert!(
!slice.functions_involved().contains(&func_c),
"Function C (caller) should NOT be in the slice when starting from B"
);
}
#[test]
fn test_interprocedural_slice_utility_methods() {
let func_a = NodeIndex::new(0);
let (cfg_a, facts_a, reaching_a, ids_a) =
make_linear_cfg("func_a", vec!["x"], vec!["x"], vec!["y"], vec!["y"]);
let pdg_a = build_pdg_from_linear(&cfg_a, &facts_a, &reaching_a);
let exit_a = ids_a[1];
let mut function_pdgs = HashMap::new();
function_pdgs.insert(func_a, pdg_a);
let mut function_cfgs = HashMap::new();
function_cfgs.insert(func_a, cfg_a);
let sdg = SystemDependenceGraph::build(function_pdgs, function_cfgs, vec![]);
let criterion = InterproceduralSliceCriterion {
func: func_a,
block: exit_a,
variable: None,
};
let slice = sdg.interprocedural_backward_slice(&criterion);
assert!(!slice.is_empty(), "Slice should not be empty");
assert!(!slice.is_empty(), "Slice should contain at least one node");
assert_eq!(
slice.functions_involved().len(),
1,
"Only one function should be involved"
);
assert!(
slice.functions_involved().contains(&func_a),
"func_a should be involved"
);
}
#[test]
fn test_sdg_no_call_edges() {
let func_a = NodeIndex::new(0);
let (cfg_a, facts_a, reaching_a, _ids_a) =
make_linear_cfg("func_a", vec!["x"], vec!["x"], vec!["y"], vec!["y"]);
let pdg_a = build_pdg_from_linear(&cfg_a, &facts_a, &reaching_a);
let mut function_pdgs = HashMap::new();
function_pdgs.insert(func_a, pdg_a);
let mut function_cfgs = HashMap::new();
function_cfgs.insert(func_a, cfg_a);
let sdg = SystemDependenceGraph::build(function_pdgs, function_cfgs, vec![]);
assert!(sdg.call_edges.is_empty());
assert!(sdg.param_in_edges.is_empty());
assert!(sdg.param_out_edges.is_empty());
assert!(sdg.summary_edges.is_empty());
assert_eq!(sdg.total_interprocedural_edges(), 0);
assert!(sdg.total_intraprocedural_edges() > 0);
}
}