pedant-core 0.20.1

Analysis engine for pedant: IR extraction, style checks, and capability detection
Documentation
//! Per-function analysis summaries.
//!
//! `FunctionSummaryData` stores range indices into the file-level
//! `data_flows` array plus lock acquisitions derived once from `FnContext`.
//! `FunctionAnalysisSummary` is the borrowed public view that resolves
//! ranges against the shared flow slice.

use super::super::facts::DataFlowFact;
use super::common::{FnContext, LockAcquisition};

/// Half-open range into the file-level `data_flows` slice.
///
/// Stored as `u32` pair — a single file will never produce 4 billion
/// flow facts. Eliminates per-function `Box<[DataFlowFact]>` ownership;
/// all facts live once in the file-level `Arc<[DataFlowFact]>`.
#[derive(Clone, Copy)]
pub(super) struct FlowRange {
    start: u32,
    end: u32,
}

impl FlowRange {
    /// Record a range from the current aggregate length before and after
    /// appending a batch of facts.
    pub(super) fn new(start: usize, end: usize) -> Self {
        Self {
            start: start as u32,
            end: end as u32,
        }
    }

    /// Slice the shared flow array to this function's domain partition.
    pub(super) fn slice<'a>(&self, flows: &'a [DataFlowFact]) -> &'a [DataFlowFact] {
        &flows[self.start as usize..self.end as usize]
    }
}

/// Owned per-function cached semantic state.
///
/// Stores domain-partitioned flow ranges (indices into the file-level
/// `data_flows`) and lock acquisitions. Everything is immutable after
/// construction and borrowed through `FunctionAnalysisSummary`.
pub(super) struct FunctionSummaryData {
    pub(super) lock_acquisitions: Box<[LockAcquisition]>,
    pub(super) taint: FlowRange,
    pub(super) quality: FlowRange,
    pub(super) performance: FlowRange,
    pub(super) concurrency: FlowRange,
}

/// Where each detector's flows landed within the file-level aggregate.
pub(super) struct DetectorRanges {
    taint: FlowRange,
    quality: FlowRange,
    performance: FlowRange,
    concurrency: FlowRange,
}

impl DetectorRanges {
    /// Pair the ranges with the function's lock acquisitions to form its summary.
    pub(super) fn into_summary(
        self,
        lock_acquisitions: Box<[LockAcquisition]>,
    ) -> FunctionSummaryData {
        FunctionSummaryData {
            lock_acquisitions,
            taint: self.taint,
            quality: self.quality,
            performance: self.performance,
            concurrency: self.concurrency,
        }
    }
}

/// Run every detector over one function, appending their flows to the file
/// aggregate in a fixed order so each recorded range stays contiguous.
pub(super) fn run_detectors(
    ctx: &FnContext<'_, '_>,
    all_flows: &mut Vec<DataFlowFact>,
) -> DetectorRanges {
    let taint = super::taint::detect(ctx);
    let quality = super::quality::detect(ctx);
    let performance = super::perf::detect(ctx);
    let concurrency = super::concurrency::detect(ctx);

    DetectorRanges {
        taint: append_flows(all_flows, taint),
        quality: append_flows(all_flows, quality),
        performance: append_flows(all_flows, performance),
        concurrency: append_flows(all_flows, concurrency),
    }
}

fn append_flows(all: &mut Vec<DataFlowFact>, facts: Box<[DataFlowFact]>) -> FlowRange {
    let start = all.len();
    all.extend(facts.into_vec());
    FlowRange::new(start, all.len())
}

/// Borrowed view into one function's precomputed analysis state.
///
/// Resolves `FlowRange` indices against the file-level `data_flows`
/// slice. Never owns data.
pub struct FunctionAnalysisSummary<'a> {
    data: &'a FunctionSummaryData,
    flows: &'a [DataFlowFact],
}

impl<'a> FunctionAnalysisSummary<'a> {
    /// Create a summary view from stored function data and the shared flow slice.
    pub(super) fn new(data: &'a FunctionSummaryData, flows: &'a [DataFlowFact]) -> Self {
        Self { data, flows }
    }

    /// Quality findings: dead stores, discarded results, partial error
    /// handling, swallowed `.ok()`, immutable growable bindings.
    pub fn quality_issues(&self) -> &[DataFlowFact] {
        self.data.quality.slice(self.flows)
    }

    /// Performance findings: repeated calls, unnecessary clones,
    /// allocations in loops, redundant collects.
    pub fn performance_issues(&self) -> &[DataFlowFact] {
        self.data.performance.slice(self.flows)
    }

    /// Concurrency findings: lock guards across `.await`, inconsistent
    /// lock ordering, unobserved spawn calls.
    pub fn concurrency_issues(&self) -> &[DataFlowFact] {
        self.data.concurrency.slice(self.flows)
    }

    /// Taint flow findings: capability source → sink propagation.
    pub fn taint_flows(&self) -> &[DataFlowFact] {
        self.data.taint.slice(self.flows)
    }
}