Skip to main content

pedant_core/ir/semantic/
function_summary.rs

1//! Per-function analysis summaries.
2//!
3//! `FunctionSummaryData` stores range indices into the file-level
4//! `data_flows` array plus lock acquisitions derived once from `FnContext`.
5//! `FunctionAnalysisSummary` is the borrowed public view that resolves
6//! ranges against the shared flow slice.
7
8use super::super::facts::DataFlowFact;
9use super::common::{FnContext, LockAcquisition};
10
11/// Half-open range into the file-level `data_flows` slice.
12///
13/// Stored as `u32` pair — a single file will never produce 4 billion
14/// flow facts. Eliminates per-function `Box<[DataFlowFact]>` ownership;
15/// all facts live once in the file-level `Arc<[DataFlowFact]>`.
16#[derive(Clone, Copy)]
17pub(super) struct FlowRange {
18    start: u32,
19    end: u32,
20}
21
22impl FlowRange {
23    /// Record a range from the current aggregate length before and after
24    /// appending a batch of facts.
25    pub(super) fn new(start: usize, end: usize) -> Self {
26        Self {
27            start: start as u32,
28            end: end as u32,
29        }
30    }
31
32    /// Slice the shared flow array to this function's domain partition.
33    pub(super) fn slice<'a>(&self, flows: &'a [DataFlowFact]) -> &'a [DataFlowFact] {
34        &flows[self.start as usize..self.end as usize]
35    }
36}
37
38/// Owned per-function cached semantic state.
39///
40/// Stores domain-partitioned flow ranges (indices into the file-level
41/// `data_flows`) and lock acquisitions. Everything is immutable after
42/// construction and borrowed through `FunctionAnalysisSummary`.
43pub(super) struct FunctionSummaryData {
44    pub(super) lock_acquisitions: Box<[LockAcquisition]>,
45    pub(super) taint: FlowRange,
46    pub(super) quality: FlowRange,
47    pub(super) performance: FlowRange,
48    pub(super) concurrency: FlowRange,
49}
50
51/// Where each detector's flows landed within the file-level aggregate.
52pub(super) struct DetectorRanges {
53    taint: FlowRange,
54    quality: FlowRange,
55    performance: FlowRange,
56    concurrency: FlowRange,
57}
58
59impl DetectorRanges {
60    /// Pair the ranges with the function's lock acquisitions to form its summary.
61    pub(super) fn into_summary(
62        self,
63        lock_acquisitions: Box<[LockAcquisition]>,
64    ) -> FunctionSummaryData {
65        FunctionSummaryData {
66            lock_acquisitions,
67            taint: self.taint,
68            quality: self.quality,
69            performance: self.performance,
70            concurrency: self.concurrency,
71        }
72    }
73}
74
75/// Run every detector over one function, appending their flows to the file
76/// aggregate in a fixed order so each recorded range stays contiguous.
77pub(super) fn run_detectors(
78    ctx: &FnContext<'_, '_>,
79    all_flows: &mut Vec<DataFlowFact>,
80) -> DetectorRanges {
81    let taint = super::taint::detect(ctx);
82    let quality = super::quality::detect(ctx);
83    let performance = super::perf::detect(ctx);
84    let concurrency = super::concurrency::detect(ctx);
85
86    DetectorRanges {
87        taint: append_flows(all_flows, taint),
88        quality: append_flows(all_flows, quality),
89        performance: append_flows(all_flows, performance),
90        concurrency: append_flows(all_flows, concurrency),
91    }
92}
93
94fn append_flows(all: &mut Vec<DataFlowFact>, facts: Box<[DataFlowFact]>) -> FlowRange {
95    let start = all.len();
96    all.extend(facts.into_vec());
97    FlowRange::new(start, all.len())
98}
99
100/// Borrowed view into one function's precomputed analysis state.
101///
102/// Resolves `FlowRange` indices against the file-level `data_flows`
103/// slice. Never owns data.
104pub struct FunctionAnalysisSummary<'a> {
105    data: &'a FunctionSummaryData,
106    flows: &'a [DataFlowFact],
107}
108
109impl<'a> FunctionAnalysisSummary<'a> {
110    /// Create a summary view from stored function data and the shared flow slice.
111    pub(super) fn new(data: &'a FunctionSummaryData, flows: &'a [DataFlowFact]) -> Self {
112        Self { data, flows }
113    }
114
115    /// Quality findings: dead stores, discarded results, partial error
116    /// handling, swallowed `.ok()`, immutable growable bindings.
117    pub fn quality_issues(&self) -> &[DataFlowFact] {
118        self.data.quality.slice(self.flows)
119    }
120
121    /// Performance findings: repeated calls, unnecessary clones,
122    /// allocations in loops, redundant collects.
123    pub fn performance_issues(&self) -> &[DataFlowFact] {
124        self.data.performance.slice(self.flows)
125    }
126
127    /// Concurrency findings: lock guards across `.await`, inconsistent
128    /// lock ordering, unobserved spawn calls.
129    pub fn concurrency_issues(&self) -> &[DataFlowFact] {
130        self.data.concurrency.slice(self.flows)
131    }
132
133    /// Taint flow findings: capability source → sink propagation.
134    pub fn taint_flows(&self) -> &[DataFlowFact] {
135        self.data.taint.slice(self.flows)
136    }
137}