Skip to main content

libafl_fandango_pyo3/fandango/
mod.rs

1use std::ffi::NulError;
2
3use pyo3::PyErr;
4use pyo3::prelude::*;
5
6pub(crate) mod inprocess;
7pub(crate) mod subprocess;
8
9pub use inprocess::FandangoInprocessModule;
10pub use subprocess::{FandangoSubprocessInitIpc, FandangoSubprocessModule};
11
12#[deprecated(
13    since = "0.4.0",
14    note = "Explicitly use FandangoInprocessModule or FandangoSubprocessModule instead"
15)]
16pub type FandangoPythonModule = FandangoInprocessModule;
17#[deprecated(since = "0.4.0", note = "Renamed to FandangoModuleInitError")]
18pub type FandangoPythonModuleInitError = FandangoModuleInitError;
19
20pub trait FandangoClient {
21    fn next_input(&mut self) -> Result<Vec<u8>, String>;
22    fn parse_input(&mut self, input: &[u8]) -> Result<u32, String>;
23}
24
25#[derive(Debug)]
26pub enum FandangoModuleInitError {
27    ModuleNotFoundError(PyErr, String),
28    PyErr(PyErr, String),
29    FilePathError(String),
30    ReadFileError(String),
31    EncodingError(NulError),
32    /// Only [`FandangoSubprocessModule`](subprocess::FandangoSubprocessModule); see [`FandangoSubprocessInitIpc`].
33    SubprocessIpc(FandangoSubprocessInitIpc),
34}
35
36impl FandangoModuleInitError {
37    /// Full human-readable report for logging or IPC.
38    ///
39    /// For Python failures this combines the exception's usual string form with the formatted
40    /// traceback when one is available (see [`FandangoInprocessModule`](inprocess::FandangoInprocessModule)’s use of `traceback.format_exception`).
41    pub fn format_report(&self) -> String {
42        match self {
43            Self::ModuleNotFoundError(err, tb) => {
44                Self::format_py_err_pair(err, tb, "Module not found")
45            }
46            Self::PyErr(err, tb) => Self::format_py_err_pair(err, tb, "Python error"),
47            Self::FilePathError(s) => s.clone(),
48            Self::ReadFileError(s) => s.clone(),
49            Self::EncodingError(e) => format!("Invalid string data (embedded NUL): {e}"),
50            Self::SubprocessIpc(e) => e.to_string(),
51        }
52    }
53
54    fn format_py_err_pair(err: &PyErr, tb: &str, label: &str) -> String {
55        Python::with_gil(|_| {
56            let head = err.to_string();
57            let tb = tb.trim();
58            let tb_useful = !tb.is_empty() && tb != "No traceback available";
59            if tb_useful {
60                format!("{label}: {head}\n\n{tb}")
61            } else {
62                format!("{label}: {head}")
63            }
64        })
65    }
66}
67
68impl std::fmt::Display for FandangoModuleInitError {
69    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
70        write!(f, "{}", self.format_report())
71    }
72}
73
74impl std::error::Error for FandangoModuleInitError {
75    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
76        match self {
77            Self::EncodingError(e) => Some(e),
78            Self::PyErr(e, _) | Self::ModuleNotFoundError(e, _) => e.source(),
79            Self::FilePathError(_) | Self::ReadFileError(_) => None,
80            Self::SubprocessIpc(e) => std::error::Error::source(e),
81        }
82    }
83}