use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::OnceLock;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum DataflowKind {
Definition,
Use,
Mutation,
Return,
Param,
Yield,
ComprehensionVar,
LambdaParam,
Global,
Nonlocal,
NestedParam,
ClosureCapture,
Goroutine,
ChannelSend,
ChannelReceive,
Defer,
TypeAssertion,
Panic,
Recover,
ErrorAssign,
ErrorCheck,
NamedReturnModify,
ChannelMake,
ChannelCloseDfg,
SelectReceive,
SelectSend,
MutexLock,
MutexUnlock,
WaitGroupAdd,
WaitGroupDone,
WaitGroupWait,
OnceDo,
ContextDone,
ContextErr,
ContextValue,
PoolGet,
PoolPut,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DataflowEdge {
pub variable: String,
pub from_line: usize,
pub to_line: usize,
pub kind: DataflowKind,
}
#[derive(Debug)]
pub struct AdjacencyCache {
pub incoming: HashMap<usize, Vec<usize>>,
pub outgoing: HashMap<usize, Vec<usize>>,
}
#[derive(Debug)]
pub struct VariableAdjacencyCache {
pub var_incoming: HashMap<String, HashMap<usize, Vec<usize>>>,
pub var_outgoing: HashMap<String, HashMap<usize, Vec<usize>>>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct DFGInfo {
pub function_name: String,
pub edges: Vec<DataflowEdge>,
pub definitions: HashMap<String, Vec<usize>>,
pub uses: HashMap<String, Vec<usize>>,
#[serde(skip)]
adjacency_cache: OnceLock<AdjacencyCache>,
#[serde(skip)]
variable_adjacency_cache: OnceLock<VariableAdjacencyCache>,
}
impl Clone for DFGInfo {
fn clone(&self) -> Self {
Self {
function_name: self.function_name.clone(),
edges: self.edges.clone(),
definitions: self.definitions.clone(),
uses: self.uses.clone(),
adjacency_cache: OnceLock::new(),
variable_adjacency_cache: OnceLock::new(),
}
}
}
#[allow(dead_code)]
impl DFGInfo {
pub fn new(
function_name: String,
edges: Vec<DataflowEdge>,
definitions: HashMap<String, Vec<usize>>,
uses: HashMap<String, Vec<usize>>,
) -> Self {
Self {
function_name,
edges,
definitions,
uses,
adjacency_cache: OnceLock::new(),
variable_adjacency_cache: OnceLock::new(),
}
}
#[inline]
pub fn get_adjacency_cache(&self) -> &AdjacencyCache {
self.adjacency_cache.get_or_init(|| {
let edge_count = self.edges.len();
let mut incoming: HashMap<usize, Vec<usize>> = HashMap::with_capacity(edge_count);
let mut outgoing: HashMap<usize, Vec<usize>> = HashMap::with_capacity(edge_count);
for edge in &self.edges {
incoming
.entry(edge.to_line)
.or_default()
.push(edge.from_line);
outgoing
.entry(edge.from_line)
.or_default()
.push(edge.to_line);
}
AdjacencyCache { incoming, outgoing }
})
}
#[inline]
pub fn get_incoming(&self, line: usize) -> Option<&Vec<usize>> {
self.get_adjacency_cache().incoming.get(&line)
}
#[inline]
pub fn get_outgoing(&self, line: usize) -> Option<&Vec<usize>> {
self.get_adjacency_cache().outgoing.get(&line)
}
#[inline]
pub fn has_adjacency_cache(&self) -> bool {
self.adjacency_cache.get().is_some()
}
#[inline]
pub fn get_variable_adjacency_cache(&self) -> &VariableAdjacencyCache {
self.variable_adjacency_cache.get_or_init(|| {
let mut var_incoming: HashMap<String, HashMap<usize, Vec<usize>>> = HashMap::new();
let mut var_outgoing: HashMap<String, HashMap<usize, Vec<usize>>> = HashMap::new();
for edge in &self.edges {
var_incoming
.entry(edge.variable.clone())
.or_default()
.entry(edge.to_line)
.or_default()
.push(edge.from_line);
var_outgoing
.entry(edge.variable.clone())
.or_default()
.entry(edge.from_line)
.or_default()
.push(edge.to_line);
}
VariableAdjacencyCache {
var_incoming,
var_outgoing,
}
})
}
#[inline]
pub fn get_var_incoming(&self, variable: &str, line: usize) -> Option<&Vec<usize>> {
self.get_variable_adjacency_cache()
.var_incoming
.get(variable)?
.get(&line)
}
#[inline]
pub fn get_var_outgoing(&self, variable: &str, line: usize) -> Option<&Vec<usize>> {
self.get_variable_adjacency_cache()
.var_outgoing
.get(variable)?
.get(&line)
}
#[inline]
pub fn get_var_incoming_lines(&self, variable: &str) -> Option<&HashMap<usize, Vec<usize>>> {
self.get_variable_adjacency_cache()
.var_incoming
.get(variable)
}
#[inline]
pub fn get_var_outgoing_lines(&self, variable: &str) -> Option<&HashMap<usize, Vec<usize>>> {
self.get_variable_adjacency_cache()
.var_outgoing
.get(variable)
}
#[inline]
pub fn has_variable_adjacency_cache(&self) -> bool {
self.variable_adjacency_cache.get().is_some()
}
#[inline]
pub fn backward_slice(&self, target_line: usize) -> Vec<usize> {
crate::dfg::slice::backward_slice_all(self, target_line)
}
#[inline]
pub fn forward_slice(&self, source_line: usize) -> Vec<usize> {
crate::dfg::slice::forward_slice_all(self, source_line)
}
pub fn variables(&self) -> Vec<&str> {
let mut vars: Vec<_> = self.definitions.keys().map(|s| s.as_str()).collect();
vars.sort();
vars.dedup();
vars
}
}
#[cfg(test)]
mod tests {
use super::*;
fn create_test_dfg() -> DFGInfo {
DFGInfo::new(
"test_func".to_string(),
vec![
DataflowEdge {
variable: "x".to_string(),
from_line: 1,
to_line: 2,
kind: DataflowKind::Definition,
},
DataflowEdge {
variable: "x".to_string(),
from_line: 1,
to_line: 4,
kind: DataflowKind::Use,
},
DataflowEdge {
variable: "y".to_string(),
from_line: 2,
to_line: 3,
kind: DataflowKind::Definition,
},
DataflowEdge {
variable: "z".to_string(),
from_line: 3,
to_line: 4,
kind: DataflowKind::Definition,
},
],
[
("x".to_string(), vec![1]),
("y".to_string(), vec![2]),
("z".to_string(), vec![3]),
]
.into_iter()
.collect(),
[
("x".to_string(), vec![2, 4]),
("y".to_string(), vec![3]),
("z".to_string(), vec![4]),
]
.into_iter()
.collect(),
)
}
#[test]
fn test_variable_adjacency_cache_lazy_init() {
let dfg = create_test_dfg();
assert!(!dfg.has_variable_adjacency_cache());
let _ = dfg.get_variable_adjacency_cache();
assert!(dfg.has_variable_adjacency_cache());
}
#[test]
fn test_get_var_incoming() {
let dfg = create_test_dfg();
let incoming = dfg.get_var_incoming("x", 2);
assert!(incoming.is_some());
assert!(incoming.unwrap().contains(&1));
let incoming = dfg.get_var_incoming("x", 4);
assert!(incoming.is_some());
assert!(incoming.unwrap().contains(&1));
let incoming = dfg.get_var_incoming("y", 3);
assert!(incoming.is_some());
assert!(incoming.unwrap().contains(&2));
let incoming = dfg.get_var_incoming("x", 3);
assert!(incoming.is_none());
let incoming = dfg.get_var_incoming("nonexistent", 1);
assert!(incoming.is_none());
}
#[test]
fn test_get_var_outgoing() {
let dfg = create_test_dfg();
let outgoing = dfg.get_var_outgoing("x", 1);
assert!(outgoing.is_some());
let outgoing = outgoing.unwrap();
assert!(outgoing.contains(&2));
assert!(outgoing.contains(&4));
let outgoing = dfg.get_var_outgoing("y", 2);
assert!(outgoing.is_some());
assert!(outgoing.unwrap().contains(&3));
let outgoing = dfg.get_var_outgoing("x", 3);
assert!(outgoing.is_none());
let outgoing = dfg.get_var_outgoing("nonexistent", 1);
assert!(outgoing.is_none());
}
#[test]
fn test_get_var_incoming_lines() {
let dfg = create_test_dfg();
let x_incoming = dfg.get_var_incoming_lines("x");
assert!(x_incoming.is_some());
let x_incoming = x_incoming.unwrap();
assert!(x_incoming.contains_key(&2));
assert!(x_incoming.contains_key(&4));
assert!(!x_incoming.contains_key(&1));
assert!(dfg.get_var_incoming_lines("nonexistent").is_none());
}
#[test]
fn test_get_var_outgoing_lines() {
let dfg = create_test_dfg();
let x_outgoing = dfg.get_var_outgoing_lines("x");
assert!(x_outgoing.is_some());
let x_outgoing = x_outgoing.unwrap();
assert!(x_outgoing.contains_key(&1));
assert!(!x_outgoing.contains_key(&2));
assert!(dfg.get_var_outgoing_lines("nonexistent").is_none());
}
#[test]
fn test_variable_cache_clone_independent() {
let dfg = create_test_dfg();
let _ = dfg.get_variable_adjacency_cache();
assert!(dfg.has_variable_adjacency_cache());
let cloned = dfg.clone();
assert!(!cloned.has_variable_adjacency_cache());
let _ = cloned.get_variable_adjacency_cache();
assert!(cloned.has_variable_adjacency_cache());
}
#[test]
fn test_empty_dfg_variable_cache() {
let dfg = DFGInfo::new(
"empty".to_string(),
vec![],
HashMap::new(),
HashMap::new(),
);
let cache = dfg.get_variable_adjacency_cache();
assert!(cache.var_incoming.is_empty());
assert!(cache.var_outgoing.is_empty());
assert!(dfg.get_var_incoming("x", 1).is_none());
assert!(dfg.get_var_outgoing("x", 1).is_none());
assert!(dfg.get_var_incoming_lines("x").is_none());
assert!(dfg.get_var_outgoing_lines("x").is_none());
}
}