summaryrefslogtreecommitdiff
path: root/fuzz/atom/vercmp/fuzz.rs
blob: 98483cb9716fd081ccd41d72b5fbe45d67289a66 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
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}"),
    }
}