cargo_caps/
capability.rs

1use std::collections::{BTreeMap, BTreeSet};
2
3use anyhow::Context as _;
4use serde::{Deserialize, Serialize};
5
6use crate::{CrateName, Symbol, cap_rule::SymbolRules, symbol::FunctionOrPath};
7
8pub type CapabilitySet = BTreeSet<Capability>;
9
10/// A capability a crate can be granted,
11/// or is suspected of having.
12#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
13pub enum Capability {
14    /// This crate has a custom build step (build.rs)
15    ///
16    /// NOT contagious!
17    /// Depending on a crate with a build.rs file does not give you the `BuildRs` capability.
18    #[serde(rename = "build.rs")]
19    BuildRs,
20
21    /// Allocate memory (`Box::new`, `Vec::new`, โ€ฆ)
22    #[serde(rename = "alloc")]
23    Alloc,
24
25    /// Call [`panic!`]
26    #[serde(rename = "panic")]
27    Panic,
28
29    /// Read the current time and/or date
30    #[serde(rename = "time")]
31    Time,
32
33    /// Read environment variables, process info, โ€ฆ
34    #[serde(rename = "sysinfo")]
35    Sysinfo,
36
37    /// Read and write to stdin, stdout, stderr
38    #[serde(rename = "stdio")]
39    Stdio,
40
41    /// Spawn thread
42    #[serde(rename = "thread")]
43    Thread,
44
45    /// Connect over the network and/or listen for incoming network traffic
46    #[serde(rename = "net")]
47    Net,
48
49    /// Open a file on disk for reading or writing
50    #[serde(rename = "fs")]
51    FS,
52
53    /// Anything is possible, including everything else in this enum.
54    #[serde(rename = "*")]
55    Any,
56}
57
58impl std::fmt::Display for Capability {
59    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60        match self {
61            Self::BuildRs => write!(f, "build.rs"),
62            Self::Alloc => write!(f, "alloc"),
63            Self::Panic => write!(f, "panic"),
64            Self::Time => write!(f, "time"),
65            Self::Sysinfo => write!(f, "sysinfo"),
66            Self::Stdio => write!(f, "stdio"),
67            Self::Thread => write!(f, "thread"),
68            Self::Net => write!(f, "net"),
69            Self::FS => write!(f, "fs"),
70            Self::Any => write!(f, "any"),
71        }
72    }
73}
74
75impl Capability {
76    pub fn emoji(&self) -> &'static str {
77        match self {
78            Self::BuildRs => "๐Ÿ› ๏ธ ",
79            Self::Alloc => "๐Ÿ“ฆ",
80            Self::Panic => "โ—๏ธ",
81            Self::Time => "โฐ",
82            Self::Sysinfo => "๐Ÿ–ฅ๏ธ ",
83            Self::Stdio => "๐Ÿ“",
84            Self::Thread => "๐Ÿงต",
85            Self::Net => "๐ŸŒ",
86            Self::FS => "๐Ÿ“",
87            Self::Any => "โš ๏ธ ",
88        }
89    }
90}
91
92#[derive(Clone, Debug, Default)]
93pub struct DeducedCapabilities {
94    /// The known capabilities of this crate
95    pub own_caps: BTreeMap<Capability, Reasons>,
96
97    /// The crates we depend on that we know the capabilities of
98    pub known_crates: BTreeMap<CrateName, CapabilitySet>,
99
100    /// We couldn't classify these symbols
101    pub unknown_symbols: BTreeSet<Symbol>,
102
103    /// We need to resolve these crates to see what their capabilities are
104    pub unknown_crates: BTreeMap<CrateName, BTreeSet<Symbol>>,
105}
106
107/// Why do we have this capability?
108pub type Reasons = BTreeSet<Reason>;
109
110pub type Reason = Symbol;
111
112impl DeducedCapabilities {
113    pub fn from_symbols(
114        rules: &SymbolRules,
115        symbols: impl IntoIterator<Item = Symbol>,
116    ) -> anyhow::Result<Self> {
117        let mut slf = Self::default();
118        for symbol in symbols {
119            slf.add(rules, &symbol)?;
120        }
121        Ok(slf)
122    }
123
124    pub fn total_capabilities(&self) -> CapabilitySet {
125        let Self {
126            own_caps,
127            known_crates,
128            unknown_symbols,
129            unknown_crates,
130        } = self;
131
132        let mut total = BTreeSet::default();
133
134        for cap in own_caps.keys() {
135            total.insert(*cap);
136        }
137        for caps in known_crates.values() {
138            for &cap in caps {
139                total.insert(cap);
140            }
141        }
142        if !unknown_symbols.is_empty() || !unknown_crates.is_empty() {
143            total.insert(Capability::Any);
144        }
145
146        if total.contains(&Capability::Any) {
147            return std::iter::once(Capability::Any).collect();
148        }
149
150        total
151    }
152
153    /// Capability from symbol
154    fn add(&mut self, rules: &SymbolRules, symbol: &Symbol) -> anyhow::Result<()> {
155        for path in symbol.paths() {
156            match path {
157                FunctionOrPath::Function(fun_name) => {
158                    let fun_name = fun_name.trim_start_matches('_');
159
160                    // Check rules for the symbol
161                    if let Some(capabilities) = rules.match_symbol(fun_name) {
162                        for &capability in capabilities {
163                            self.own_caps
164                                .entry(capability)
165                                .or_default()
166                                .insert(symbol.clone());
167                        }
168                    } else {
169                        self.unknown_symbols.insert(symbol.clone());
170                    }
171                }
172
173                FunctionOrPath::RustPath(rust_path) => {
174                    let path_str = rust_path.to_string();
175                    // Check rules for the path
176                    if let Some(capabilities) = rules.match_symbol(&path_str) {
177                        for &capability in capabilities {
178                            self.own_caps
179                                .entry(capability)
180                                .or_default()
181                                .insert(symbol.clone());
182                        }
183                    } else {
184                        // No rule matched - assume an external crate:
185                        let segments = rust_path.segments();
186
187                        let crate_name = segments[0];
188                        let crate_name = CrateName::new(crate_name)
189                            .with_context(|| format!("mangled: {:?}", symbol.mangled))
190                            .with_context(|| format!("demangled: {:?}", symbol.demangled))?;
191                        self.unknown_crates
192                            .entry(crate_name)
193                            .or_default()
194                            .insert(symbol.clone());
195                    }
196                }
197            }
198        }
199
200        Ok(())
201    }
202}