kernel_abi_check/python_abi/
mod.rs

1use std::collections::{BTreeSet, HashMap};
2
3use eyre::Result;
4use object::{BinaryFormat, 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    binary_format: BinaryFormat,
74    symbols: impl IntoIterator<Item = Symbol<'a, 'a>>,
75) -> Result<BTreeSet<PythonAbiViolation>> {
76    let mut violations = BTreeSet::new();
77    for symbol in symbols {
78        if !symbol.is_undefined() {
79            continue;
80        }
81
82        let mut symbol_name = symbol.name()?;
83
84        if matches!(binary_format, BinaryFormat::MachO) {
85            // Mach-O C symbol mangling adds an underscore.
86            symbol_name = symbol_name.strip_prefix("_").unwrap_or(symbol_name);
87        }
88
89        match PYTHON_STABLE_ABI.get(symbol_name) {
90            Some(abi_info) => {
91                if &abi_info.added > python_abi {
92                    violations.insert(PythonAbiViolation::IncompatibleAbi3Symbol {
93                        name: symbol_name.to_string(),
94                        added: abi_info.added.clone(),
95                    });
96                }
97            }
98            None => {
99                if symbol_name.starts_with("Py") || symbol_name.starts_with("_Py") {
100                    violations.insert(PythonAbiViolation::NonAbi3Symbol {
101                        name: symbol_name.to_string(),
102                    });
103                }
104            }
105        }
106    }
107
108    Ok(violations)
109}