pub mod analysis;
pub mod ast;
pub mod diff;
pub mod dominance_frontiers;
pub mod dominators;
pub mod edge;
pub mod export;
pub mod git_utils;
pub mod hotpaths;
pub mod icfg;
pub mod loops;
pub mod paths;
pub mod patterns;
pub mod post_dominators;
pub mod reachability;
pub mod source;
pub mod summary;
pub use crate::storage::{
load_cfg_from_db, resolve_function_name, resolve_function_name_with_file,
};
#[cfg(feature = "sqlite")]
pub use crate::storage::load_cfg_from_db_with_conn;
pub use dominance_frontiers::compute_dominance_frontiers;
pub use dominators::DominatorTree;
pub use edge::EdgeType;
pub use export::{export_dot, export_json, CFGExport};
pub use loops::detect_natural_loops;
pub use patterns::{detect_if_else_patterns, detect_match_patterns};
pub use post_dominators::PostDominatorTree;
pub use reachability::{compute_path_impact, find_reachable_from_block, PathImpact};
pub use source::SourceLocation;
pub use summary::summarize_path;
use anyhow::Result;
use petgraph::graph::DiGraph;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
pub type HotPath = hotpaths::HotPath;
pub type HotpathsOptions = hotpaths::HotpathsOptions;
pub type Path = paths::Path;
pub type EnumerationContext = paths::EnumerationContext;
pub type EnumerationStats = paths::EnumerationStats;
pub type IncrementalPathsResult = paths::IncrementalPathsResult;
pub type LimitsHit = paths::LimitsHit;
pub type PathEnumerationResult = paths::PathEnumerationResult;
pub type PathKind = paths::PathKind;
pub type PathLimits = paths::PathLimits;
pub fn find_entry(cfg: &Cfg) -> Option<petgraph::graph::NodeIndex> {
analysis::find_entry(cfg)
}
pub fn find_exits(cfg: &Cfg) -> Vec<petgraph::graph::NodeIndex> {
analysis::find_exits(cfg)
}
pub fn compute_hot_paths(
graph: &Cfg,
paths: &[Path],
entry: petgraph::graph::NodeIndex,
loops: &[loops::NaturalLoop],
options: HotpathsOptions,
) -> Result<Vec<HotPath>> {
hotpaths::compute_hot_paths(graph, paths, entry, loops, options)
}
pub fn enumerate_paths(cfg: &Cfg, limits: &PathLimits) -> Vec<Path> {
paths::enumerate_paths(cfg, limits)
}
pub fn enumerate_paths_cached(
cfg: &Cfg,
function_id: i64,
function_hash: &str,
limits: &PathLimits,
db_conn: &mut rusqlite::Connection,
) -> anyhow::Result<Vec<Path>> {
paths::enumerate_paths_cached(cfg, function_id, function_hash, limits, db_conn)
.map_err(anyhow::Error::msg)
}
pub fn enumerate_paths_cached_with_context(
cfg: &Cfg,
function_id: i64,
function_hash: &str,
limits: &PathLimits,
ctx: &EnumerationContext,
db_conn: &mut rusqlite::Connection,
) -> anyhow::Result<Vec<Path>> {
paths::enumerate_paths_cached_with_context(
cfg,
function_id,
function_hash,
limits,
ctx,
db_conn,
)
.map_err(anyhow::Error::msg)
}
pub fn enumerate_paths_incremental(
function_name: &str,
db: &crate::storage::MirageDb,
repo_path: &std::path::Path,
since: &str,
max_length: Option<usize>,
) -> anyhow::Result<IncrementalPathsResult> {
paths::enumerate_paths_incremental(function_name, db, repo_path, since, max_length)
}
pub fn enumerate_paths_iterative(cfg: &Cfg, limits: &PathLimits) -> Vec<Path> {
paths::enumerate_paths_iterative(cfg, limits)
}
pub fn enumerate_paths_with_context(
cfg: &Cfg,
limits: &PathLimits,
ctx: &EnumerationContext,
) -> Vec<Path> {
paths::enumerate_paths_with_context(cfg, limits, ctx)
}
pub fn enumerate_paths_with_metadata(cfg: &Cfg, limits: &PathLimits) -> PathEnumerationResult {
paths::enumerate_paths_with_metadata(cfg, limits)
}
pub fn get_or_enumerate_paths(
cfg: &Cfg,
function_id: i64,
function_hash: &str,
limits: &PathLimits,
db_conn: &mut rusqlite::Connection,
) -> anyhow::Result<Vec<Path>> {
paths::get_or_enumerate_paths(cfg, function_id, function_hash, limits, db_conn)
.map_err(anyhow::Error::msg)
}
type CfgBlockRow = (
i64,
String,
Option<String>,
Option<i64>,
Option<i64>,
Option<i64>,
Option<i64>,
Option<i64>,
Option<i64>,
Option<String>,
);
pub type Cfg = DiGraph<BasicBlock, EdgeType>;
pub fn build_edges_from_terminators(
graph: &mut Cfg,
blocks: &[CfgBlockRow],
db_id_to_node: &HashMap<i64, usize>,
) -> Result<()> {
let mut blocks_with_idx = blocks.iter().enumerate().collect::<Vec<_>>();
blocks_with_idx.sort_by_key(|(_, block)| block_byte_start(block));
let mut sorted_pos_to_node: HashMap<usize, usize> = HashMap::new();
for (sorted_pos, (_original_idx, block)) in blocks_with_idx.iter().enumerate() {
if let Some(&node_idx) = db_id_to_node.get(&block_id(block)) {
sorted_pos_to_node.insert(sorted_pos, node_idx);
}
}
for (sorted_pos, (_original_idx, block)) in blocks_with_idx.iter().enumerate() {
let terminator = block_terminator(block).unwrap_or("");
let current_node = *sorted_pos_to_node.get(&sorted_pos).ok_or_else(|| {
anyhow::anyhow!("Block at position {} not found in node map", sorted_pos)
})?;
match terminator {
"fallthrough" | "goto" => {
add_cfg_edge(
graph,
&sorted_pos_to_node,
current_node,
sorted_pos + 1,
EdgeType::Fallthrough,
);
}
"conditional" => {
add_cfg_edge(
graph,
&sorted_pos_to_node,
current_node,
sorted_pos + 1,
EdgeType::TrueBranch,
);
add_cfg_edge(
graph,
&sorted_pos_to_node,
current_node,
sorted_pos + 2,
EdgeType::FalseBranch,
);
}
"return" | "panic" => {
}
"break" | "continue" => {
}
"call" => {
add_cfg_edge(
graph,
&sorted_pos_to_node,
current_node,
sorted_pos + 1,
EdgeType::Call,
);
}
_ => {
}
}
}
Ok(())
}
fn block_id(block: &CfgBlockRow) -> i64 {
block.0
}
fn block_terminator(block: &CfgBlockRow) -> Option<&str> {
block.2.as_deref()
}
fn block_byte_start(block: &CfgBlockRow) -> Option<i64> {
block.3
}
fn add_cfg_edge(
graph: &mut Cfg,
sorted_pos_to_node: &HashMap<usize, usize>,
current_node: usize,
target_sorted_pos: usize,
edge_type: EdgeType,
) {
use petgraph::graph::NodeIndex;
if let Some(&target_node) = sorted_pos_to_node.get(&target_sorted_pos) {
graph.add_edge(
NodeIndex::new(current_node),
NodeIndex::new(target_node),
edge_type,
);
}
}
pub fn build_edges_from_cfg_edges(
graph: &mut Cfg,
edges: &[(i64, i64, String)],
index_to_node: &std::collections::HashMap<usize, usize>,
) -> Result<()> {
use petgraph::graph::NodeIndex;
for (source_idx, target_idx, edge_type_str) in edges {
let source_node = *index_to_node
.get(&(*source_idx as usize))
.ok_or_else(|| anyhow::anyhow!("Source index {} not found in block map", source_idx))?;
let target_node = *index_to_node
.get(&(*target_idx as usize))
.ok_or_else(|| anyhow::anyhow!("Target index {} not found in block map", target_idx))?;
let edge_type = match edge_type_str.as_str() {
"fallthrough" => EdgeType::Fallthrough,
"conditional_true" => EdgeType::TrueBranch,
"conditional_false" => EdgeType::FalseBranch,
"back_edge" => EdgeType::LoopBack,
"call" => EdgeType::Call,
"return" => EdgeType::Return,
"jump" => EdgeType::Fallthrough,
_ => EdgeType::Fallthrough,
};
graph.add_edge(
NodeIndex::new(source_node),
NodeIndex::new(target_node),
edge_type,
);
}
Ok(())
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BasicBlock {
pub id: BlockId,
#[serde(skip_serializing_if = "Option::is_none")]
pub db_id: Option<i64>,
pub kind: BlockKind,
pub statements: Vec<String>,
pub terminator: Terminator,
pub source_location: Option<SourceLocation>,
}
pub type BlockId = usize;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum BlockKind {
Entry,
Normal,
Exit,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum Terminator {
Goto {
target: BlockId,
},
SwitchInt {
targets: Vec<BlockId>,
otherwise: BlockId,
},
Return,
Unreachable,
Call {
target: Option<BlockId>,
unwind: Option<BlockId>,
},
Abort(String),
}