Skip to main content

pedant_core/ir/
dataflow.rs

1//! Data-flow findings: taint edges, quality issues, and concurrency hazards.
2//!
3//! Only semantic enrichment populates these; every other path leaves the slice
4//! empty.
5
6use std::fmt;
7
8use pedant_types::Capability;
9
10use super::facts::IrSpan;
11
12/// Discriminant for data flow findings.
13#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
14pub enum DataFlowKind {
15    /// Tainted data flows from a capability source to a capability sink.
16    TaintFlow,
17    /// Value assigned then overwritten before read.
18    DeadStore,
19    /// Function returning Result called without binding the return.
20    DiscardedResult,
21    /// Result handled on some paths, dropped on others.
22    PartialErrorHandling,
23    /// Same function called with identical arguments within a single scope.
24    RepeatedCall,
25    /// `.clone()` called but the original is never used afterward.
26    UnnecessaryClone,
27    /// `Vec::new()`, `String::new()`, or `format!()` inside a loop body.
28    AllocationInLoop,
29    /// `.collect()` followed immediately by `.iter()` or `.into_iter()`.
30    RedundantCollect,
31    /// Lock guard held across an `.await` point (potential deadlock or task starvation).
32    LockAcrossAwait,
33    /// Same locks acquired in different orders across functions (potential deadlock).
34    InconsistentLockOrder,
35    /// Vec or String binding never mutated after construction.
36    ImmutableGrowable,
37    /// `.ok()` called on Result where the resulting Option is discarded.
38    SwallowedOk,
39    /// Thread or task spawned with the JoinHandle dropped or unbound.
40    UnobservedSpawn,
41}
42
43impl DataFlowKind {
44    /// Kebab-case identifier for this data flow kind.
45    pub fn code(self) -> &'static str {
46        match self {
47            Self::TaintFlow => "taint-flow",
48            Self::DeadStore => "dead-store",
49            Self::DiscardedResult => "discarded-result",
50            Self::PartialErrorHandling => "partial-error-handling",
51            Self::RepeatedCall => "repeated-call",
52            Self::UnnecessaryClone => "unnecessary-clone",
53            Self::AllocationInLoop => "allocation-in-loop",
54            Self::RedundantCollect => "redundant-collect",
55            Self::LockAcrossAwait => "lock-across-await",
56            Self::InconsistentLockOrder => "inconsistent-lock-order",
57            Self::ImmutableGrowable => "immutable-growable",
58            Self::SwallowedOk => "swallowed-ok",
59            Self::UnobservedSpawn => "unobserved-spawn",
60        }
61    }
62}
63
64impl fmt::Display for DataFlowKind {
65    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66        f.write_str(self.code())
67    }
68}
69
70/// Data flow finding: taint edge, quality issue, or concurrency hazard.
71#[derive(Debug, Clone)]
72pub struct DataFlowFact {
73    /// What kind of data flow issue this represents.
74    pub kind: DataFlowKind,
75    /// Where the tainted data originates (taint flows only).
76    pub source_capability: Option<Capability>,
77    /// Location of the source expression.
78    pub source_span: IrSpan,
79    /// Where the tainted data is consumed (taint flows only).
80    pub sink_capability: Option<Capability>,
81    /// Location of the sink expression.
82    pub sink_span: IrSpan,
83    /// Intermediate function names the data passes through.
84    pub call_chain: Box<[Box<str>]>,
85    /// Human-readable description of the finding.
86    pub message: Box<str>,
87}