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}