kernel_abi_check/python_abi/
mod.rs

1use std::collections::{BTreeSet, HashMap};
2
3use eyre::Result;
4use object::{ObjectSymbol, Symbol};
5use once_cell::sync::Lazy;
6use serde::Deserialize;
7
8use crate::version::Version;
9
10static ABI_TOML: &str = include_str!("stable_abi.toml");
11
12#[derive(Deserialize)]
13struct AbiInfoSerde {
14    added: Version,
15}
16
17#[derive(Deserialize)]
18struct StableAbiSerde {
19    function: HashMap<String, AbiInfoSerde>,
20    data: HashMap<String, AbiInfoSerde>,
21}
22
23#[derive(Clone, Copy, Debug)]
24pub enum SymbolType {
25    Data,
26    Function,
27}
28
29#[derive(Clone, Debug)]
30pub struct AbiInfo {
31    #[allow(dead_code)]
32    pub symbol_type: SymbolType,
33    pub added: Version,
34}
35
36pub static PYTHON_STABLE_ABI: Lazy<HashMap<String, AbiInfo>> = Lazy::new(|| {
37    let deserialized: StableAbiSerde = toml::de::from_str(ABI_TOML).unwrap();
38    let mut symbols = HashMap::new();
39    for (name, abi) in deserialized.function {
40        symbols.insert(
41            name,
42            AbiInfo {
43                symbol_type: SymbolType::Function,
44                added: abi.added,
45            },
46        );
47    }
48    for (name, abi) in deserialized.data {
49        symbols.insert(
50            name,
51            AbiInfo {
52                symbol_type: SymbolType::Data,
53                added: abi.added,
54            },
55        );
56    }
57    symbols
58});
59
60/// Python ABI violation.
61#[derive(Debug, Clone, Eq, Ord, PartialEq, PartialOrd)]
62pub enum PythonAbiViolation {
63    /// Symbol is newer than the specified Python ABI version.
64    IncompatibleAbi3Symbol { name: String, added: Version },
65
66    /// Symbol is not part of ABI3.
67    NonAbi3Symbol { name: String },
68}
69
70/// Check for violations of the Python ABI policy.
71pub fn check_python_abi<'a>(
72    python_abi: &Version,
73    symbols: impl IntoIterator<Item = Symbol<'a, 'a>>,
74) -> Result<BTreeSet<PythonAbiViolation>> {
75    let mut violations = BTreeSet::new();
76    for symbol in symbols {
77        if !symbol.is_undefined() {
78            continue;
79        }
80
81        let symbol_name = symbol.name()?;
82
83        match PYTHON_STABLE_ABI.get(symbol_name) {
84            Some(abi_info) => {
85                if &abi_info.added > python_abi {
86                    violations.insert(PythonAbiViolation::IncompatibleAbi3Symbol {
87                        name: symbol_name.to_string(),
88                        added: abi_info.added.clone(),
89                    });
90                }
91            }
92            None => {
93                if symbol_name.starts_with("Py") || symbol_name.starts_with("_Py") {
94                    violations.insert(PythonAbiViolation::NonAbi3Symbol {
95                        name: symbol_name.to_string(),
96                    });
97                }
98            }
99        }
100    }
101
102    Ok(violations)
103}