use anyhow::Result;
use rusqlite::params;
use std::collections::{HashMap, HashSet, VecDeque};
use std::path::Path;
use crate::generation::ChunkStore;
use crate::graph::cfg_edges_extract::{CfgEdge, CfgEdgeType, CfgWithEdges};
use crate::graph::schema::CfgBlock;
pub struct CfgOps {
chunks: ChunkStore,
}
impl CfgOps {
pub fn new(chunks: ChunkStore) -> Self {
Self { chunks }
}
pub fn index_cfg_for_function(
&self,
func_node: &tree_sitter::Node,
source: &[u8],
function_id: i64,
) -> Result<usize> {
self.index_cfg_with_4d_coordinates_from_node(func_node, source, function_id)
}
pub fn index_cfg_with_4d_coordinates_from_node(
&self,
func_node: &tree_sitter::Node,
source: &[u8],
function_id: i64,
) -> Result<usize> {
use crate::graph::cfg_edges_extract::extract_cfg_from_function_node;
let source_str = std::str::from_utf8(source)
.map_err(|e| anyhow::anyhow!("Invalid UTF-8 in function source: {}", e))?;
let mut cfg = extract_cfg_from_function_node(func_node, function_id, source_str);
if cfg.blocks.is_empty() {
return Ok(0);
}
compute_4d_coordinates(&mut cfg);
self.insert_cfg_blocks(&cfg.blocks)?;
let _ = self.insert_cfg_edges(function_id, &cfg.edges);
Ok(cfg.blocks.len())
}
pub fn index_cfg_with_4d_coordinates(
&self,
source: &str,
function_id: i64,
language: tree_sitter::Language,
) -> Result<usize> {
use crate::graph::cfg_edges_extract::extract_cfg_with_edges;
let mut cfg = extract_cfg_with_edges(source, function_id, language);
if cfg.blocks.is_empty() {
return Ok(0);
}
compute_4d_coordinates(&mut cfg);
self.insert_cfg_blocks(&cfg.blocks)?;
Ok(cfg.blocks.len())
}
pub fn insert_cfg_blocks(&self, blocks: &[CfgBlock]) -> Result<usize> {
use sha2::{Digest, Sha256};
let conn = self.chunks.connect()?;
let tx = conn.unchecked_transaction()?;
{
let mut stmt = tx.prepare(
"INSERT INTO cfg_blocks (
function_id, kind, terminator,
byte_start, byte_end,
start_line, start_col,
end_line, end_col,
cfg_hash,
statements,
coord_x, coord_y, coord_z, coord_t
) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15)",
)?;
for block in blocks {
let statements_json = block
.statements
.as_ref()
.map(|s| serde_json::to_string(s).unwrap_or_else(|_| "[]".to_string()));
let cfg_hash = if let Some(ref h) = block.cfg_hash {
h.clone()
} else {
let mut hasher = Sha256::new();
hasher.update(block.function_id.to_le_bytes());
hasher.update(&block.kind);
hasher.update(&block.terminator);
hasher.update(block.byte_start.to_le_bytes());
hasher.update(block.byte_end.to_le_bytes());
if let Some(ref s) = statements_json {
hasher.update(s.as_bytes());
}
format!("{:x}", hasher.finalize())[..16].to_string()
};
stmt.execute(params![
block.function_id,
block.kind,
block.terminator,
block.byte_start,
block.byte_end,
block.start_line,
block.start_col,
block.end_line,
block.end_col,
cfg_hash,
statements_json,
block.coord_x,
block.coord_y,
block.coord_z,
block.coord_t.as_ref(),
])?;
}
}
tx.commit()?;
if let Err(e) = crate::graph::wal::checkpoint_conn(&conn) {
eprintln!(
"Warning: WAL checkpoint failed after CFG block insert: {}",
e
);
}
Ok(blocks.len())
}
pub fn insert_cfg_edges(
&self,
function_id: i64,
edges: &[crate::graph::cfg_edges_extract::CfgEdge],
) -> Result<usize> {
if edges.is_empty() {
return Ok(0);
}
let conn = self.chunks.connect()?;
let tx = conn.unchecked_transaction()?;
{
let mut stmt = tx.prepare(
"INSERT INTO cfg_edges (function_id, source_idx, target_idx, edge_type)
VALUES (?1, ?2, ?3, ?4)",
)?;
for edge in edges {
stmt.execute(params![
function_id,
edge.source_idx as i64,
edge.target_idx as i64,
edge.edge_type.as_str(),
])?;
}
}
tx.commit()?;
Ok(edges.len())
}
pub fn get_cfg_edges_for_function(
&self,
function_id: i64,
) -> Result<Vec<crate::graph::cfg_edges_extract::CfgEdge>> {
let conn = self.chunks.connect()?;
let mut stmt = conn.prepare(
"SELECT source_idx, target_idx, edge_type
FROM cfg_edges
WHERE function_id = ?1
ORDER BY id",
)?;
let edges = stmt
.query_map(params![function_id], |row| {
let source_idx: i64 = row.get(0)?;
let target_idx: i64 = row.get(1)?;
let edge_type_str: String = row.get(2)?;
let edge_type = match edge_type_str.as_str() {
"fallthrough" => crate::graph::cfg_edges_extract::CfgEdgeType::Fallthrough,
"conditional_true" => {
crate::graph::cfg_edges_extract::CfgEdgeType::ConditionalTrue
}
"conditional_false" => {
crate::graph::cfg_edges_extract::CfgEdgeType::ConditionalFalse
}
"jump" => crate::graph::cfg_edges_extract::CfgEdgeType::Jump,
"back_edge" => crate::graph::cfg_edges_extract::CfgEdgeType::BackEdge,
"call" => crate::graph::cfg_edges_extract::CfgEdgeType::Call,
"return" => crate::graph::cfg_edges_extract::CfgEdgeType::Return,
_ => crate::graph::cfg_edges_extract::CfgEdgeType::Fallthrough,
};
Ok(crate::graph::cfg_edges_extract::CfgEdge {
source_idx: source_idx as usize,
target_idx: target_idx as usize,
edge_type,
})
})?
.collect::<Result<Vec<_>, _>>()?;
Ok(edges)
}
pub fn delete_cfg_for_function(&self, function_id: i64) -> Result<usize> {
let conn = self.chunks.connect()?;
conn.execute(
"DELETE FROM cfg_edges WHERE function_id = ?1",
params![function_id],
)?;
let affected = conn.execute(
"DELETE FROM cfg_blocks WHERE function_id = ?1",
params![function_id],
)?;
Ok(affected)
}
pub fn delete_cfg_for_functions(&self, function_ids: &[i64]) -> Result<usize> {
if function_ids.is_empty() {
return Ok(0);
}
let conn = self.chunks.connect()?;
let placeholders = function_ids
.iter()
.enumerate()
.map(|(i, _)| format!("?{}", i + 1))
.collect::<Vec<_>>()
.join(", ");
let path_elements_sql = format!(
"DELETE FROM cfg_path_elements WHERE path_id IN (SELECT path_id FROM cfg_paths WHERE function_id IN ({}))",
placeholders
);
if let Ok(mut path_elements_stmt) = conn.prepare(&path_elements_sql) {
let params = function_ids.to_vec();
let _ = path_elements_stmt.execute(rusqlite::params_from_iter(¶ms));
}
let paths_sql = format!(
"DELETE FROM cfg_paths WHERE function_id IN ({})",
placeholders
);
if let Ok(mut paths_stmt) = conn.prepare(&paths_sql) {
let params = function_ids.to_vec();
let _ = paths_stmt.execute(rusqlite::params_from_iter(¶ms));
}
let dom_sql = format!(
"DELETE FROM cfg_dominators WHERE block_id IN (SELECT id FROM cfg_blocks WHERE function_id IN ({})) OR dominator_id IN (SELECT id FROM cfg_blocks WHERE function_id IN ({}))",
placeholders, placeholders
);
if let Ok(mut dom_stmt) = conn.prepare(&dom_sql) {
let params: Vec<i64> = function_ids
.iter()
.chain(function_ids.iter())
.copied()
.collect();
let _ = dom_stmt.execute(rusqlite::params_from_iter(¶ms));
}
let post_dom_sql = format!(
"DELETE FROM cfg_post_dominators WHERE block_id IN (SELECT id FROM cfg_blocks WHERE function_id IN ({})) OR post_dominator_id IN (SELECT id FROM cfg_blocks WHERE function_id IN ({}))",
placeholders, placeholders
);
if let Ok(mut post_dom_stmt) = conn.prepare(&post_dom_sql) {
let params: Vec<i64> = function_ids
.iter()
.chain(function_ids.iter())
.copied()
.collect();
let _ = post_dom_stmt.execute(rusqlite::params_from_iter(¶ms));
}
let edge_sql = format!(
"DELETE FROM cfg_edges WHERE function_id IN ({})",
placeholders
);
let mut edge_stmt = conn.prepare(&edge_sql)?;
let params = function_ids.to_vec();
edge_stmt.execute(rusqlite::params_from_iter(¶ms))?;
let sql = format!(
"DELETE FROM cfg_blocks WHERE function_id IN ({})",
placeholders
);
let mut stmt = conn.prepare(&sql)?;
let params = function_ids.to_vec();
let affected = stmt.execute(rusqlite::params_from_iter(params))?;
Ok(affected)
}
pub fn get_cfg_for_function(&self, function_id: i64) -> Result<Vec<CfgBlock>> {
let conn = self.chunks.connect()?;
let mut stmt = conn.prepare(
"SELECT function_id, kind, terminator,
byte_start, byte_end,
start_line, start_col,
end_line, end_col,
cfg_hash, statements,
coord_x, coord_y, coord_z, coord_t
FROM cfg_blocks
WHERE function_id = ?1
ORDER BY byte_start",
)?;
let blocks = stmt
.query_map(params![function_id], |row| {
let statements_json: Option<String> = row.get(10)?;
let statements = statements_json.and_then(|s| serde_json::from_str(&s).ok());
Ok(CfgBlock {
function_id: row.get(0)?,
kind: row.get(1)?,
terminator: row.get(2)?,
byte_start: row.get(3)?,
byte_end: row.get(4)?,
start_line: row.get(5)?,
start_col: row.get(6)?,
end_line: row.get(7)?,
end_col: row.get(8)?,
cfg_hash: row.get(9)?,
statements,
coord_x: row.get(11)?,
coord_y: row.get(12)?,
coord_z: row.get(13)?,
coord_t: row.get(14)?,
})
})?
.collect::<Result<Vec<_>, _>>()?;
Ok(blocks)
}
pub fn get_cfg_for_file(&self, file_path: &str) -> Result<Vec<(i64, Vec<CfgBlock>)>> {
let conn = self.chunks.connect()?;
let mut stmt = conn.prepare(
"SELECT e.id AS function_id,
c.function_id, c.kind, c.terminator,
c.byte_start, c.byte_end,
c.start_line, c.start_col,
c.end_line, c.end_col,
c.cfg_hash, c.statements,
c.coord_x, c.coord_y, c.coord_z, c.coord_t
FROM cfg_blocks c
JOIN graph_entities e ON c.function_id = e.id
WHERE e.file_path = ?1
ORDER BY e.id, c.byte_start",
)?;
let mut result: std::collections::HashMap<i64, Vec<CfgBlock>> =
std::collections::HashMap::new();
let rows = stmt.query_map(params![file_path], |row| {
let function_id: i64 = row.get(0)?;
let statements_json: Option<String> = row.get(11)?;
let statements = statements_json.and_then(|s| serde_json::from_str(&s).ok());
let block = CfgBlock {
function_id: row.get(1)?,
kind: row.get(2)?,
terminator: row.get(3)?,
byte_start: row.get(4)?,
byte_end: row.get(5)?,
start_line: row.get(6)?,
start_col: row.get(7)?,
end_line: row.get(8)?,
end_col: row.get(9)?,
cfg_hash: row.get(10)?,
statements,
coord_x: row.get(12)?,
coord_y: row.get(13)?,
coord_z: row.get(14)?,
coord_t: row.get(15)?,
};
Ok((function_id, block))
})?;
for row in rows {
let (function_id, block) = row?;
result.entry(function_id).or_default().push(block);
}
Ok(result.into_iter().collect())
}
}
pub fn compute_dominator_depth(cfg: &CfgWithEdges) -> HashMap<usize, i64> {
let mut depth = HashMap::new();
if cfg.blocks.is_empty() {
return depth;
}
let mut predecessors: HashMap<usize, Vec<usize>> = HashMap::new();
for edge in &cfg.edges {
predecessors
.entry(edge.target_idx)
.or_default()
.push(edge.source_idx);
}
for i in 0..cfg.blocks.len() {
predecessors.entry(i).or_default();
}
let entry_idx = 0;
let mut dominators: HashMap<usize, HashSet<usize>> = HashMap::new();
for i in 0..cfg.blocks.len() {
let mut set = HashSet::new();
set.insert(i);
dominators.insert(i, set);
}
dominators.insert(entry_idx, {
let mut set = HashSet::new();
set.insert(entry_idx);
set
});
let mut changed = true;
while changed {
changed = false;
for i in 0..cfg.blocks.len() {
if i == entry_idx {
continue;
}
let preds = predecessors.get(&i).unwrap(); if preds.is_empty() {
continue;
}
let mut new_doms: HashSet<usize> = (0..cfg.blocks.len()).collect();
for pred in preds {
if let Some(pred_doms) = dominators.get(pred) {
new_doms = new_doms.intersection(pred_doms).cloned().collect();
}
}
new_doms.insert(i);
if let Some(current_doms) = dominators.get(&i) {
if new_doms != *current_doms {
dominators.insert(i, new_doms);
changed = true;
}
} else {
dominators.insert(i, new_doms);
changed = true;
}
}
}
let mut immediate_dominator: HashMap<usize, Option<usize>> = HashMap::new();
for i in 0..cfg.blocks.len() {
if i == entry_idx {
immediate_dominator.insert(i, None);
continue;
}
let doms = dominators.get(&i).cloned().unwrap_or_default();
let mut idom = None;
for d in &doms {
if *d == i {
continue;
}
let dominates_another = doms.iter().any(|&s| {
s != i
&& s != *d
&& dominators
.get(&s)
.map(|set| set.contains(d))
.unwrap_or(false)
});
if !dominates_another {
idom = Some(*d);
break;
}
}
immediate_dominator.insert(i, idom);
}
fn compute_depth(
node: usize,
imm_dom: &HashMap<usize, Option<usize>>,
cache: &mut HashMap<usize, i64>,
) -> i64 {
if let Some(&depth) = cache.get(&node) {
return depth;
}
let depth = match imm_dom.get(&node) {
None | Some(None) => 0,
Some(Some(parent)) => 1 + compute_depth(*parent, imm_dom, cache),
};
cache.insert(node, depth);
depth
}
for i in 0..cfg.blocks.len() {
let d = compute_depth(i, &immediate_dominator, &mut HashMap::new());
depth.insert(i, d);
}
depth
}
pub fn compute_loop_nesting(cfg: &CfgWithEdges) -> HashMap<usize, i64> {
let mut nesting = HashMap::new();
if cfg.blocks.is_empty() {
return nesting;
}
let mut back_edges: Vec<(usize, usize)> = Vec::new();
let mut successors: HashMap<usize, Vec<usize>> = HashMap::new();
for edge in &cfg.edges {
successors
.entry(edge.source_idx)
.or_default()
.push(edge.target_idx);
if edge.edge_type == CfgEdgeType::BackEdge {
back_edges.push((edge.source_idx, edge.target_idx));
}
}
let mut loop_headers: HashSet<usize> = HashSet::new();
for (_, header) in &back_edges {
loop_headers.insert(*header);
}
fn compute_nesting_depth(
block: usize,
loop_headers: &HashSet<usize>,
successors: &HashMap<usize, Vec<usize>>,
visited: &mut HashSet<usize>,
cache: &mut HashMap<usize, i64>,
entry_idx: usize,
) -> i64 {
if let Some(&depth) = cache.get(&block) {
return depth;
}
if visited.contains(&block) {
return 0;
}
visited.insert(block);
let base_depth = if loop_headers.contains(&block) && block != entry_idx {
1
} else {
0
};
let mut max_child_depth = 0;
if let Some(succs) = successors.get(&block) {
for &succ in succs {
let child_depth = compute_nesting_depth(
succ,
loop_headers,
successors,
visited,
cache,
entry_idx,
);
max_child_depth = max_child_depth.max(child_depth);
}
}
let depth = base_depth + max_child_depth;
cache.insert(block, depth);
depth
}
for i in 0..cfg.blocks.len() {
let depth = compute_nesting_depth(
i,
&loop_headers,
&successors,
&mut HashSet::new(),
&mut HashMap::new(),
0, );
nesting.insert(i, depth);
}
nesting
}
pub fn compute_branch_distance(cfg: &CfgWithEdges) -> HashMap<usize, i64> {
let mut distance = HashMap::new();
if cfg.blocks.is_empty() {
return distance;
}
let entry_idx = 0;
let mut queue = VecDeque::new();
let mut visited = HashSet::new();
queue.push_back((entry_idx, 0i64));
visited.insert(entry_idx);
let mut successors: HashMap<usize, Vec<(usize, bool)>> = HashMap::new();
for edge in &cfg.edges {
let is_conditional = matches!(
edge.edge_type,
CfgEdgeType::ConditionalTrue | CfgEdgeType::ConditionalFalse
);
successors
.entry(edge.source_idx)
.or_default()
.push((edge.target_idx, is_conditional));
}
while let Some((block, dist)) = queue.pop_front() {
distance.insert(block, dist);
if let Some(succs) = successors.get(&block) {
for &(succ, is_cond) in succs {
if !visited.contains(&succ) {
visited.insert(succ);
let new_dist = dist + if is_cond { 1 } else { 0 };
queue.push_back((succ, new_dist));
}
}
}
}
for i in 0..cfg.blocks.len() {
distance.entry(i).or_insert(0);
}
distance
}
pub fn get_current_git_commit() -> Option<String> {
use git2::Repository;
if let Ok(repo) = Repository::open(".") {
if let Ok(head) = repo.head() {
if let Some(commit_oid) = head.target() {
return Some(commit_oid.to_string());
}
}
}
None
}
pub fn compute_4d_coordinates(cfg: &mut CfgWithEdges) {
compute_4d_coordinates_with_commit(cfg, get_current_git_commit())
}
pub fn compute_4d_coordinates_with_commit(cfg: &mut CfgWithEdges, commit: Option<String>) {
let dom_depth = compute_dominator_depth(cfg);
let loop_nest = compute_loop_nesting(cfg);
let branch_dist = compute_branch_distance(cfg);
for (i, block) in cfg.blocks.iter_mut().enumerate() {
block.coord_x = dom_depth.get(&i).copied().unwrap_or(0);
block.coord_y = loop_nest.get(&i).copied().unwrap_or(0);
block.coord_z = branch_dist.get(&i).copied().unwrap_or(0);
block.coord_t = commit.clone();
}
}
#[cfg(test)]
mod spatial_tests {
use super::*;
fn get_test_language() -> tree_sitter::Language {
tree_sitter_rust::language()
}
#[test]
fn test_compute_dominator_depth_simple() {
let source = r#"
fn main() {
let x = 1;
if x > 0 {
println!("positive");
} else {
println!("non-positive");
}
}
"#;
let cfg =
crate::graph::cfg_edges_extract::extract_cfg_with_edges(source, 1, get_test_language());
let depths = compute_dominator_depth(&cfg);
assert_eq!(depths.get(&0), Some(&0));
assert_eq!(depths.len(), cfg.blocks.len());
}
#[test]
fn test_compute_dominator_depth_linear_chain() {
let cfg = CfgWithEdges {
function_id: 1,
blocks: vec![
CfgBlock {
function_id: 1,
kind: "ENTRY".to_string(),
terminator: "FALLTHROUGH".to_string(),
byte_start: 0,
byte_end: 10,
start_line: 1,
start_col: 0,
end_line: 1,
end_col: 10,
cfg_hash: None,
statements: None,
coord_x: 0,
coord_y: 0,
coord_z: 0,
coord_t: None,
},
CfgBlock {
function_id: 1,
kind: "BASIC".to_string(),
terminator: "FALLTHROUGH".to_string(),
byte_start: 10,
byte_end: 20,
start_line: 2,
start_col: 0,
end_line: 2,
end_col: 10,
cfg_hash: None,
statements: None,
coord_x: 0,
coord_y: 0,
coord_z: 0,
coord_t: None,
},
CfgBlock {
function_id: 1,
kind: "BASIC".to_string(),
terminator: "FALLTHROUGH".to_string(),
byte_start: 20,
byte_end: 30,
start_line: 3,
start_col: 0,
end_line: 3,
end_col: 10,
cfg_hash: None,
statements: None,
coord_x: 0,
coord_y: 0,
coord_z: 0,
coord_t: None,
},
CfgBlock {
function_id: 1,
kind: "EXIT".to_string(),
terminator: "RETURN".to_string(),
byte_start: 30,
byte_end: 40,
start_line: 4,
start_col: 0,
end_line: 4,
end_col: 10,
cfg_hash: None,
statements: None,
coord_x: 0,
coord_y: 0,
coord_z: 0,
coord_t: None,
},
],
edges: vec![
CfgEdge {
source_idx: 0,
target_idx: 1,
edge_type: CfgEdgeType::Fallthrough,
},
CfgEdge {
source_idx: 1,
target_idx: 2,
edge_type: CfgEdgeType::Fallthrough,
},
CfgEdge {
source_idx: 2,
target_idx: 3,
edge_type: CfgEdgeType::Fallthrough,
},
],
};
let depths = compute_dominator_depth(&cfg);
assert_eq!(depths.get(&0), Some(&0), "entry should have depth 0");
assert_eq!(depths.get(&1), Some(&1), "block 1 should have depth 1");
assert_eq!(depths.get(&2), Some(&2), "block 2 should have depth 2");
assert_eq!(depths.get(&3), Some(&3), "block 3 should have depth 3");
}
#[test]
fn test_compute_loop_nesting_simple() {
let source = r#"
fn main() {
let mut x = 0;
while x < 10 {
x += 1;
}
}
"#;
let cfg =
crate::graph::cfg_edges_extract::extract_cfg_with_edges(source, 1, get_test_language());
let nesting = compute_loop_nesting(&cfg);
assert_eq!(nesting.len(), cfg.blocks.len());
for depth in nesting.values() {
assert!(depth >= &0);
}
}
#[test]
fn test_compute_branch_distance_simple() {
let source = r#"
fn main() {
let x = 1;
if x > 0 {
println!("positive");
}
}
"#;
let cfg =
crate::graph::cfg_edges_extract::extract_cfg_with_edges(source, 1, get_test_language());
let distance = compute_branch_distance(&cfg);
assert_eq!(distance.get(&0), Some(&0));
assert_eq!(distance.len(), cfg.blocks.len());
}
#[test]
fn test_compute_4d_coordinates_integration() {
let source = r#"
fn example() {
let mut x = 0;
if x > 0 {
while x < 10 {
x += 1;
}
} else {
x = 5;
}
}
"#;
let mut cfg =
crate::graph::cfg_edges_extract::extract_cfg_with_edges(source, 1, get_test_language());
compute_4d_coordinates(&mut cfg);
for block in &cfg.blocks {
assert!(block.coord_x >= 0);
assert!(block.coord_y >= 0);
assert!(block.coord_z >= 0);
}
}
#[test]
fn test_git_commit_tracking() {
let commit = get_current_git_commit();
if let Some(hash) = commit {
assert_eq!(hash.len(), 40);
assert!(hash.chars().all(|c| c.is_ascii_hexdigit()));
} else {
}
}
#[test]
fn test_compute_4d_coordinates_with_explicit_commit() {
let source = r#"
fn example() {
let x = 1;
}
"#;
let mut cfg =
crate::graph::cfg_edges_extract::extract_cfg_with_edges(source, 1, get_test_language());
let test_commit = Some("abc123def456789".repeat(2)); compute_4d_coordinates_with_commit(&mut cfg, test_commit.clone());
for block in &cfg.blocks {
assert_eq!(block.coord_t, test_commit);
}
}
#[test]
fn test_cfg_edges_roundtrip() {
let chunks = ChunkStore::in_memory();
let ops = CfgOps::new(chunks);
let blocks = vec![
CfgBlock {
function_id: 42,
kind: "ENTRY".to_string(),
terminator: "FALLTHROUGH".to_string(),
byte_start: 0,
byte_end: 10,
start_line: 1,
start_col: 0,
end_line: 1,
end_col: 10,
cfg_hash: None,
statements: None,
coord_x: 0,
coord_y: 0,
coord_z: 0,
coord_t: None,
},
CfgBlock {
function_id: 42,
kind: "BASIC".to_string(),
terminator: "RETURN".to_string(),
byte_start: 10,
byte_end: 20,
start_line: 2,
start_col: 0,
end_line: 2,
end_col: 10,
cfg_hash: None,
statements: None,
coord_x: 1,
coord_y: 0,
coord_z: 1,
coord_t: None,
},
];
ops.insert_cfg_blocks(&blocks).unwrap();
let edges = vec![
CfgEdge {
source_idx: 0,
target_idx: 1,
edge_type: CfgEdgeType::Fallthrough,
},
CfgEdge {
source_idx: 1,
target_idx: 1,
edge_type: CfgEdgeType::BackEdge,
},
];
let inserted = ops.insert_cfg_edges(42, &edges).unwrap();
assert_eq!(inserted, 2);
let retrieved = ops.get_cfg_edges_for_function(42).unwrap();
assert_eq!(retrieved.len(), 2);
assert_eq!(retrieved[0].source_idx, 0);
assert_eq!(retrieved[0].target_idx, 1);
assert_eq!(retrieved[0].edge_type, CfgEdgeType::Fallthrough);
assert_eq!(retrieved[1].source_idx, 1);
assert_eq!(retrieved[1].target_idx, 1);
assert_eq!(retrieved[1].edge_type, CfgEdgeType::BackEdge);
let deleted = ops.delete_cfg_for_function(42).unwrap();
assert_eq!(deleted, 2);
let after_delete = ops.get_cfg_edges_for_function(42).unwrap();
assert!(after_delete.is_empty());
}
}