nichlink_run_method/runtime/trace/
call_trace.rs1use std::collections::BTreeMap;
5
6use crate::registry_core::declaration::SourceLocation;
7use crate::runtime::trace::{CallSite, TraceMode};
8use crate::runtime::{CallEdge, EvidenceKind, LogicalCallEdge};
9
10pub use super::edges::{DataEdge, DataHop};
11pub use super::locals::{LocalId, LocalKind, LocalValue, Observation};
12
13pub const fn application_default_trace_mode() -> TraceMode {
21 if cfg!(debug_assertions) {
22 TraceMode::ErrorsOnly
23 } else {
24 TraceMode::Off
25 }
26}
27
28pub fn trace_mode_from_env() -> TraceMode {
31 std::env::var("NICH_LINK_TRACE")
32 .ok()
33 .and_then(|value| TraceMode::parse(&value))
34 .unwrap_or_else(application_default_trace_mode)
35}
36
37#[derive(Clone, Debug)]
40pub struct CallTrace {
41 pub(super) mode: TraceMode,
42 pub(super) frames: Vec<FrameRecord>,
43 pub(super) frame_index: BTreeMap<u64, usize>,
44 pub(super) current: Vec<u64>,
45 pub(super) locals: Vec<LocalValue>,
46 pub(super) local_index: BTreeMap<u64, usize>,
47 pub(super) local_name_index: BTreeMap<String, Vec<u64>>,
48 pub(super) local_function_index: BTreeMap<&'static str, Vec<u64>>,
49 pub(super) edges: Vec<DataEdge>,
50 pub(super) outgoing_index: BTreeMap<u64, Vec<usize>>,
51 pub(super) incoming_index: BTreeMap<u64, Vec<usize>>,
52 pub(super) next_local_id: u64,
53 pub(super) next_frame_id: u64,
54 pub(super) error_scope_depth: usize,
55}
56
57#[derive(Clone, Copy, Debug)]
58pub(super) struct TraceMark {
59 frames: usize,
60 locals: usize,
61 edges: usize,
62}
63
64impl Default for CallTrace {
65 fn default() -> Self {
66 Self::runtime()
67 }
68}
69
70#[derive(Clone, Debug)]
71pub(super) struct FrameRecord {
72 pub(super) call: CallSite,
73 pub(super) parent: Option<u64>,
74}
75
76#[derive(Clone, Copy, Debug)]
79pub struct FrameView<'a> {
80 pub call: &'a CallSite,
83 pub parent: Option<u64>,
86}
87
88impl CallTrace {
89 pub fn new() -> Self {
95 Self::runtime()
96 }
97
98 pub fn runtime() -> Self {
101 Self::with_mode(trace_mode_from_env())
102 }
103
104 pub fn disabled() -> Self {
107 Self::with_mode(TraceMode::Off)
108 }
109
110 pub fn errors_only() -> Self {
113 Self::with_mode(TraceMode::ErrorsOnly)
114 }
115
116 pub fn full() -> Self {
119 Self::with_mode(TraceMode::Full)
120 }
121
122 pub fn with_mode(mode: TraceMode) -> Self {
125 Self {
126 mode,
127 frames: Vec::new(),
128 frame_index: BTreeMap::new(),
129 current: Vec::new(),
130 locals: Vec::new(),
131 local_index: BTreeMap::new(),
132 local_name_index: BTreeMap::new(),
133 local_function_index: BTreeMap::new(),
134 edges: Vec::new(),
135 outgoing_index: BTreeMap::new(),
136 incoming_index: BTreeMap::new(),
137 next_local_id: 0,
138 next_frame_id: 0,
139 error_scope_depth: 0,
140 }
141 }
142
143 pub const fn mode(&self) -> TraceMode {
146 self.mode
147 }
148
149 pub fn set_mode(&mut self, mode: TraceMode) {
152 self.mode = mode;
153 self.clear();
154 }
155
156 pub const fn is_collecting(&self) -> bool {
159 !matches!(self.mode, TraceMode::Off)
160 }
161
162 pub fn clear(&mut self) {
165 self.frames.clear();
166 self.frame_index.clear();
167 self.current.clear();
168 self.locals.clear();
169 self.local_index.clear();
170 self.local_name_index.clear();
171 self.local_function_index.clear();
172 self.edges.clear();
173 self.outgoing_index.clear();
174 self.incoming_index.clear();
175 }
189
190 pub(super) fn mark(&self) -> TraceMark {
191 TraceMark {
192 frames: self.frames.len(),
193 locals: self.locals.len(),
194 edges: self.edges.len(),
195 }
196 }
197
198 pub(super) fn rollback(&mut self, mark: TraceMark) {
209 self.frames.truncate(mark.frames);
210 self.locals.truncate(mark.locals);
211 self.edges.truncate(mark.edges);
212 self.rebuild_indexes();
213 }
214
215 pub(super) fn rebuild_indexes(&mut self) {
216 self.frame_index.clear();
217 for (index, frame) in self.frames.iter().enumerate() {
218 self.frame_index.insert(frame.call.frame_id, index);
219 }
220 self.local_index.clear();
221 self.local_name_index.clear();
222 self.local_function_index.clear();
223 for (index, local) in self.locals.iter().enumerate() {
224 self.local_index.insert(local.id, index);
225 self.local_name_index
226 .entry(local.name.clone())
227 .or_default()
228 .push(local.id);
229 if let Some(frame_id) = local.frame_id
230 && let Some(function) = self.frame(frame_id).map(|frame| frame.call.function)
231 {
232 self.local_function_index
233 .entry(function)
234 .or_default()
235 .push(local.id);
236 }
237 }
238 self.outgoing_index.clear();
239 self.incoming_index.clear();
240 for (index, edge) in self.edges.iter().enumerate() {
241 self.outgoing_index
242 .entry(edge.from)
243 .or_default()
244 .push(index);
245 self.incoming_index.entry(edge.to).or_default().push(index);
246 }
247 }
248
249 pub fn with_result<T, E>(
256 &mut self,
257 operation: impl FnOnce(&mut Self) -> Result<T, E>,
258 ) -> Result<T, E> {
259 if !matches!(self.mode, TraceMode::ErrorsOnly) {
260 return operation(self);
261 }
262 let outer = self.error_scope_depth == 0;
263 let mark = outer.then(|| self.mark());
264 self.error_scope_depth += 1;
265 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| operation(self)));
266 self.error_scope_depth = self.error_scope_depth.saturating_sub(1);
272 match result {
273 Ok(Ok(value)) => {
274 if let Some(mark) = mark {
275 self.rollback(mark);
276 }
277 Ok(value)
278 }
279 Ok(Err(error)) => Err(error),
280 Err(payload) => std::panic::resume_unwind(payload),
281 }
282 }
283
284 pub(super) fn callsite_source(
285 &self,
286 file: &'static str,
287 line: u32,
288 column: u32,
289 ) -> SourceLocation {
290 SourceLocation {
291 file,
292 line,
293 column,
294 function: self
295 .current
296 .last()
297 .and_then(|id| self.frame(*id))
298 .map(|frame| frame.call.function)
299 .unwrap_or("<runtime>"),
300 }
301 }
302
303 pub fn call_edges(&self) -> Vec<CallEdge> {
306 let mut result = Vec::new();
307 let mut seen = std::collections::BTreeSet::new();
308 for frame in &self.frames {
309 let Some(parent) = frame.parent.and_then(|id| self.frame(id)) else {
310 continue;
311 };
312 let edge = CallEdge {
313 caller: parent.call.clone(),
314 callee: frame.call.clone(),
315 };
316 if seen.insert((edge.caller.frame_id, edge.callee.frame_id)) {
317 result.push(edge);
318 }
319 }
320 result
321 }
322
323 pub fn logical_call_edges(&self) -> Vec<LogicalCallEdge> {
330 let mut seen = std::collections::BTreeSet::new();
331 self.call_edges()
332 .into_iter()
333 .filter_map(|edge| {
334 let key = (
335 edge.caller.node,
336 edge.caller.function,
337 edge.callee.node,
338 edge.callee.function,
339 );
340 seen.insert(key).then_some(LogicalCallEdge {
341 caller: edge.caller,
342 callee: edge.callee,
343 evidence: EvidenceKind::Live,
344 })
345 })
346 .collect()
347 }
348}