nichlink_debug_method/
mir.rs1use 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#[derive(Clone, Debug, Default, PartialEq, Eq)]
20pub struct UnifiedCallGraph {
21 pub static_calls: Vec<MirCall>,
24 pub runtime_calls: Vec<CallEdge>,
27}
28
29impl UnifiedCallGraph {
30 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 pub fn topology(&self) -> crate::adapters::CallGraph {
43 crate::adapters::CallGraph::from_relations(&self.relations())
44 }
45
46 pub fn relations(&self) -> Vec<CallRelation> {
55 merge_call_relations(&self.static_calls, &self.runtime_calls)
56 }
57
58 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}