mirage-analyzer 1.5.1

Path-Aware Code Intelligence Engine for Rust
Documentation
use crate::analysis::DeadSymbolJson;

#[derive(serde::Serialize)]
pub(crate) struct PathsResponse {
    pub function: String,
    pub total_paths: usize,
    pub error_paths: usize,
    pub paths: Vec<PathSummary>,
}

#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
pub(crate) struct PathBlock {
    pub block_id: usize,
    pub terminator: String,
}

#[derive(serde::Serialize)]
pub(crate) struct SourceRange {
    pub file_path: String,
    pub start_line: usize,
    pub end_line: usize,
}

#[derive(serde::Serialize)]
pub(crate) struct PathSummary {
    pub path_id: String,
    pub kind: String,
    pub length: usize,
    pub blocks: Vec<PathBlock>,
    pub summary: Option<String>,
    pub source_range: Option<SourceRange>,
}

impl From<crate::cfg::Path> for PathSummary {
    fn from(path: crate::cfg::Path) -> Self {
        let length = path.len();
        let blocks: Vec<PathBlock> = path
            .blocks
            .into_iter()
            .map(|block_id| PathBlock {
                block_id,
                terminator: "Unknown".to_string(),
            })
            .collect();

        Self {
            path_id: path.path_id,
            kind: format!("{:?}", path.kind),
            length,
            blocks,
            summary: None,
            source_range: None,
        }
    }
}

impl PathSummary {
    pub fn from_with_cfg(path: crate::cfg::Path, cfg: &crate::cfg::Cfg) -> Self {
        use crate::cfg::summarize_path;

        let summary = Some(summarize_path(cfg, &path));

        let blocks: Vec<PathBlock> = path
            .blocks
            .iter()
            .map(|&block_id| {
                let node_idx = cfg.node_indices().find(|&n| cfg[n].id == block_id);

                let terminator = match node_idx {
                    Some(idx) => format!("{:?}", cfg[idx].terminator),
                    None => "Unknown".to_string(),
                };

                PathBlock {
                    block_id,
                    terminator,
                }
            })
            .collect();

        let source_range = Self::calculate_source_range(&path, cfg);

        let length = path.len();

        Self {
            path_id: path.path_id,
            kind: format!("{:?}", path.kind),
            length,
            summary,
            source_range,
            blocks,
        }
    }

    fn calculate_source_range(
        path: &crate::cfg::Path,
        cfg: &crate::cfg::Cfg,
    ) -> Option<SourceRange> {
        let first_loc = path
            .blocks
            .first()
            .and_then(|&bid| cfg.node_indices().find(|&n| cfg[n].id == bid))
            .and_then(|idx| cfg[idx].source_location.clone());

        let last_loc = path
            .blocks
            .last()
            .and_then(|&bid| cfg.node_indices().find(|&n| cfg[n].id == bid))
            .and_then(|idx| cfg[idx].source_location.clone());

        match (first_loc, last_loc) {
            (Some(first), Some(last)) => Some(SourceRange {
                file_path: first.file_path.to_string_lossy().to_string(),
                start_line: first.start_line,
                end_line: last.end_line,
            }),
            _ => None,
        }
    }
}

#[derive(serde::Serialize)]
pub(crate) struct DominanceResponse {
    pub function: String,
    pub kind: String,
    pub root: Option<usize>,
    pub dominance_tree: Vec<DominatorEntry>,
    pub must_pass_through: Option<MustPassThroughResult>,
}

#[derive(serde::Serialize)]
pub(crate) struct DominatorEntry {
    pub block: usize,
    pub immediate_dominator: Option<usize>,
    pub dominated: Vec<usize>,
}

#[derive(serde::Serialize)]
pub(crate) struct MustPassThroughResult {
    pub block: usize,
    pub must_pass: Vec<usize>,
}

#[derive(serde::Serialize)]
pub(crate) struct InterProceduralDominanceResponse {
    pub function: String,
    pub kind: String,
    pub dominator_count: usize,
    pub dominators: Vec<String>,
}

#[derive(serde::Serialize)]
pub(crate) struct UnreachableResponse {
    pub function: String,
    pub total_functions: usize,
    pub functions_with_unreachable: usize,
    pub unreachable_count: usize,
    pub blocks: Vec<UnreachableBlock>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub uncalled_functions: Option<Vec<DeadSymbolJson>>,
}

#[derive(serde::Serialize, Clone)]
pub(crate) struct IncomingEdge {
    pub from_block: usize,
    pub edge_type: String,
}

#[derive(serde::Serialize, Clone)]
pub(crate) struct UnreachableBlock {
    pub block_id: usize,
    pub kind: String,
    pub statements: Vec<String>,
    pub terminator: String,
    #[serde(skip_serializing_if = "Vec::is_empty")]
    pub incoming_edges: Vec<IncomingEdge>,
}

#[derive(serde::Serialize)]
pub(crate) struct VerifyResult {
    pub path_id: String,
    pub valid: bool,
    pub found_in_cache: bool,
    pub function_id: Option<i64>,
    pub reason: String,
    pub current_paths: usize,
}

#[derive(serde::Serialize)]
pub(crate) struct LoopsResponse {
    pub function: String,
    pub loop_count: usize,
    pub loops: Vec<LoopInfo>,
}

#[derive(serde::Serialize)]
pub(crate) struct LoopInfo {
    pub header: usize,
    pub back_edge_from: usize,
    pub body_size: usize,
    pub nesting_level: usize,
    pub body_blocks: Vec<usize>,
}

#[derive(serde::Serialize)]
pub(crate) struct PatternsResponse {
    pub function: String,
    pub if_else_count: usize,
    pub match_count: usize,
    pub if_else_patterns: Vec<IfElseInfo>,
    pub match_patterns: Vec<MatchInfo>,
}

#[derive(serde::Serialize)]
pub(crate) struct IfElseInfo {
    pub condition_block: usize,
    pub true_branch: usize,
    pub false_branch: usize,
    pub merge_point: Option<usize>,
    pub has_else: bool,
}

#[derive(serde::Serialize)]
pub(crate) struct MatchInfo {
    pub switch_block: usize,
    pub branch_count: usize,
    pub targets: Vec<usize>,
    pub otherwise: usize,
}

#[derive(serde::Serialize)]
pub(crate) struct FrontiersResponse {
    pub function: String,
    pub nodes_with_frontiers: usize,
    pub frontiers: Vec<NodeFrontier>,
}

#[derive(serde::Serialize)]
pub(crate) struct NodeFrontier {
    pub node: usize,
    pub frontier_set: Vec<usize>,
}

#[derive(serde::Serialize)]
pub(crate) struct IteratedFrontierResponse {
    pub function: String,
    pub iterated_frontier: Vec<usize>,
}

#[derive(serde::Serialize)]
pub(crate) struct BlockImpactResponse {
    pub function: String,
    pub block_id: usize,
    pub reachable_blocks: Vec<usize>,
    pub reachable_count: usize,
    pub max_depth: usize,
    pub has_cycles: bool,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub forward_impact: Option<Vec<CallGraphSymbol>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub backward_impact: Option<Vec<CallGraphSymbol>>,
}

#[derive(serde::Serialize)]
pub(crate) struct PathImpactResponse {
    pub path_id: String,
    pub path_length: usize,
    pub unique_blocks_affected: Vec<usize>,
    pub impact_count: usize,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub forward_impact: Option<Vec<CallGraphSymbol>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub backward_impact: Option<Vec<CallGraphSymbol>>,
}

#[derive(Clone, serde::Serialize)]
pub(crate) struct CallGraphSymbol {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub symbol_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub fqn: Option<String>,
    pub file_path: String,
    pub kind: String,
}

#[derive(serde::Serialize)]
pub(crate) struct HotspotsResponse {
    pub entry_point: String,
    pub total_functions: usize,
    pub hotspots: Vec<HotspotEntry>,
    pub mode: String,
}

#[derive(serde::Serialize, Clone)]
pub(crate) struct HotspotEntry {
    pub function: String,
    pub risk_score: f64,
    pub path_count: usize,
    pub dominance_factor: f64,
    pub complexity: usize,
    pub file_path: String,
}