Skip to main content

nichlink_debug_method/
mir.rs

1//! Small rustc/MIR bridge for NichLink's debug model.
2//! NichLink 调试模型使用的轻量 rustc/MIR 桥接层。
3//!
4//! Static parsing and evidence merging live in the kernel `mir` module;
5//! this file keeps the runtime-coupled merge entry point and the petgraph
6//! topology adapter wiring.
7//! 静态解析与证据归并在 kernel 的 `mir` 模块;本文件保留与运行期耦合的
8//! 归并入口和 petgraph 拓扑适配接线。
9
10use std::fmt::Write as _;
11
12pub use nichlink_run_method::registry_core::mir::{
13    CallEvidence, CallRelation, MirCall, MirGraph, MirLocal, MirParseError, merge_call_relations,
14};
15use nichlink_run_method::{CallEdge, CallTrace};
16
17/// Static MIR candidates plus calls observed in one live run.
18/// 静态 MIR 候选边与一次运行中真实观察到的调用边。
19#[derive(Clone, Debug, Default, PartialEq, Eq)]
20pub struct UnifiedCallGraph {
21    /// Calls the static MIR parse proposed, none of them confirmed as executed.
22    /// 静态 MIR 解析提出的调用,均未确认为已执行。
23    pub static_calls: Vec<MirCall>,
24    /// Calls actually observed during the live run, i.e. confirmed evidence.
25    /// 实时运行中真正观察到的调用,即已确认的证据。
26    pub runtime_calls: Vec<CallEdge>,
27}
28
29impl UnifiedCallGraph {
30    /// Pair one static MIR graph with one live trace into a single evidence
31    /// set; neither input is mutated.
32    /// 将一个静态 MIR 图与一次实时 trace 配对为一份证据集合;两侧输入都不被修改。
33    pub fn new(static_graph: &MirGraph, trace: &CallTrace) -> Self {
34        Self {
35            static_calls: static_graph.calls.clone(),
36            runtime_calls: trace.call_edges(),
37        }
38    }
39
40    /// Delegate topology queries to the petgraph-backed adapter.
41    /// 将拓扑查询委托给基于 petgraph 的适配层。
42    pub fn topology(&self) -> crate::adapters::CallGraph {
43        crate::adapters::CallGraph::from_relations(&self.relations())
44    }
45
46    /// Merge all known edges into one evidence-aware relation list.
47    /// 将所有已知边合并为一份带证据等级的关系列表。
48    ///
49    /// Live edges win over MIR candidates with the same logical symbols. A
50    /// static candidate that was not observed remains visible as `Mir`, so a
51    /// missing branch is not silently mistaken for a successful call.
52    /// 逻辑符号相同的边以 Live 证据为准。未被观察到的静态候选仍保留为
53    /// `Mir`,不会把未执行分支误报成已经成功调用。
54    pub fn relations(&self) -> Vec<CallRelation> {
55        merge_call_relations(&self.static_calls, &self.runtime_calls)
56    }
57
58    /// Render the merged relations as human-readable lines, one per relation,
59    /// each carrying its evidence marker, source location, and frame ids.
60    /// 将合并后的关系渲染为人类可读的行,每行一条,带证据标记、源码位置与帧 id。
61    pub fn render(&self) -> String {
62        let mut output = String::new();
63        let relations = self.relations();
64        output.push_str("CALL RELATIONS\n");
65        if relations.is_empty() {
66            output.push_str("  (none)\n");
67        } else {
68            for relation in relations {
69                let location = relation
70                    .source
71                    .map_or_else(String::new, |source| format!(" @ {source}"));
72                let mir_line = relation
73                    .mir_line
74                    .map_or_else(String::new, |line| format!(" @ MIR {line}"));
75                let frames = match (relation.caller_frame, relation.callee_frame) {
76                    (Some(caller), Some(callee)) => format!(" frames={caller}->{callee}"),
77                    _ => String::new(),
78                };
79                writeln!(
80                    output,
81                    "  {} {} -> {} [{}]{}{}{}",
82                    relation.evidence.marker(),
83                    relation.caller,
84                    relation.callee,
85                    relation.evidence.label(),
86                    location,
87                    mir_line,
88                    frames,
89                )
90                .unwrap();
91            }
92        }
93        output
94    }
95}
96
97#[cfg(test)]
98mod tests {
99    use super::{CallEvidence, MirGraph, UnifiedCallGraph};
100    use nichlink_run_method::{CallTrace, NodeId, SourceLocation};
101
102    #[test]
103    fn keeps_static_and_live_edges_distinct() {
104        let graph = MirGraph::from_jsonl(
105            "{\"kind\":\"call\",\"caller\":\"a\",\"callee\":\"b\",\"mir_line\":1}\n",
106        )
107        .unwrap();
108        let mut trace = CallTrace::full();
109        trace.with_at(
110            NodeId::from_path("a.rs", "A"),
111            "a",
112            SourceLocation {
113                file: "a.rs",
114                line: 1,
115                column: 1,
116                function: "a",
117            },
118            |trace| {
119                trace.with_at(
120                    NodeId::from_path("b.rs", "B"),
121                    "b",
122                    SourceLocation {
123                        file: "b.rs",
124                        line: 2,
125                        column: 1,
126                        function: "b",
127                    },
128                    |_| {},
129                );
130            },
131        );
132        let unified = UnifiedCallGraph::new(&graph, &trace);
133        assert_eq!(unified.static_calls.len(), 1);
134        assert_eq!(unified.runtime_calls.len(), 1);
135        assert!(unified.render().contains("CALL RELATIONS"));
136        assert_eq!(unified.relations()[0].evidence, CallEvidence::Live);
137        assert_eq!(unified.relations().len(), 1);
138        assert_eq!(unified.topology().edge_count(), 1);
139    }
140
141    #[test]
142    fn keeps_unobserved_mir_candidate_as_a_distinct_relation() {
143        let graph = MirGraph::from_jsonl(
144            "{\"kind\":\"call\",\"caller\":\"crate::a\",\"callee\":\"crate::c\",\"mir_line\":7}\n",
145        )
146        .unwrap();
147        let trace = CallTrace::full();
148        let unified = UnifiedCallGraph::new(&graph, &trace);
149        let relations = unified.relations();
150        assert_eq!(relations.len(), 1);
151        assert_eq!(relations[0].evidence, CallEvidence::Mir);
152        assert_eq!(relations[0].mir_line, Some(7));
153        assert!(!relations[0].evidence.confirmed());
154    }
155}