diff options
| author | John Turner <jturner.usa@gmail.com> | 2025-11-19 01:00:48 +0000 |
|---|---|---|
| committer | John Turner <jturner.usa@gmail.com> | 2025-11-19 01:00:48 +0000 |
| commit | 70e8ea24a8ec9e3dfe484227463fdb97ee341227 (patch) | |
| tree | 1a19f0974b683c87e6e5c791252d5a5b99669b97 /fuzz/atom/vercmp/fuzz.rs | |
| parent | e01637fd3a0a59ffea320a7df9770e73f486c91e (diff) | |
| download | gentoo-utils-70e8ea24a8ec9e3dfe484227463fdb97ee341227.tar.gz | |
impl vercmp fuzzer
Diffstat (limited to 'fuzz/atom/vercmp/fuzz.rs')
| -rw-r--r-- | fuzz/atom/vercmp/fuzz.rs | 112 |
1 files changed, 112 insertions, 0 deletions
diff --git a/fuzz/atom/vercmp/fuzz.rs b/fuzz/atom/vercmp/fuzz.rs new file mode 100644 index 0000000..98483cb --- /dev/null +++ b/fuzz/atom/vercmp/fuzz.rs @@ -0,0 +1,112 @@ +use core::slice; +use gentoo_utils::{ + Parseable, + atom::{Atom, Version}, +}; +use mon::{Parser, ParserFinishedError, input::InputIter}; +use std::{ + cmp::Ordering, + io::{BufRead, BufReader, Write}, + process::{ChildStdin, ChildStdout, Command, Stdio}, + sync::{LazyLock, Mutex}, +}; + +struct PyProcess { + stdin: Mutex<ChildStdin>, + stdout: Mutex<BufReader<ChildStdout>>, + buffer: Mutex<String>, +} + +#[allow(clippy::missing_safety_doc, clippy::needless_return)] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn LLVMFuzzerTestOneInput(input: *const u8, len: usize) -> i32 { + static PY_PROCESS: LazyLock<PyProcess> = LazyLock::new(|| { + #[allow(clippy::zombie_processes)] + let mut proc = Command::new("vercmp.py") + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::inherit()) + .spawn() + .expect("failed to spawn vercmp.py"); + + let stdin = Mutex::new(proc.stdin.take().unwrap()); + let stdout = Mutex::new(BufReader::new(proc.stdout.take().unwrap())); + + PyProcess { + stdin, + stdout, + buffer: Mutex::new(String::new()), + } + }); + + let control = Version::parser() + .parse_finished(InputIter::new("1.2.0a_alpha1_beta2-r1-8")) + .unwrap(); + + let slice = unsafe { slice::from_raw_parts(input, len) }; + + if slice.iter().any(|b| !b.is_ascii_graphic()) { + return -1; + } + + let str = match str::from_utf8(slice) { + Ok(str) => str, + Err(_) => return -1, + }; + + let version_str = match str.split_ascii_whitespace().nth(0) { + Some(lhs) => lhs, + None => return -1, + }; + + dbg!(version_str); + + let version = match Version::parser().parse_finished(InputIter::new(version_str)) { + Ok(a) => a, + Err(_) => return -1, + }; + + let gentoo_utils = control.cmp(&version); + + let portage_result = portage_vercmp(&PY_PROCESS, &control, &version); + + match portage_result { + Ok(portage) if portage == gentoo_utils => { + eprintln!("agreement on {control} cmp {version} == {portage:?}"); + } + Ok(portage) => { + panic!( + "disagreement on {control} == {version}:\nportage:{portage:?} gentoo_utils:{gentoo_utils:?}" + ) + } + Err(_) => { + panic!("parsed invalid versions: {control} | {version}") + } + } + + return 0; +} + +fn portage_vercmp(pyproc: &PyProcess, a: &Version, b: &Version) -> Result<Ordering, ()> { + let mut stdin = pyproc.stdin.lock().expect("failed to get stdin lock"); + let mut stdout = pyproc.stdout.lock().expect("failed to get stdout lock"); + let mut buffer = pyproc.buffer.lock().expect("failed to get buffer lock"); + + writeln!(&mut stdin, "{a} {b}").expect("failed to write line to python process"); + + stdin.flush().unwrap(); + + buffer.clear(); + + stdout + .read_line(&mut buffer) + .expect("failed to read line from python process"); + + match buffer.as_str().trim() { + "0" => Ok(Ordering::Equal), + "1" => Ok(Ordering::Greater), + "-1" => Ok(Ordering::Less), + "err" => Err(()), + other => panic!("unexpected result from python: {other}"), + } +} |
