Rust · 3688 bytes Raw Blame History
1 //! Smoke-level fuzz tests for the lexer and parser.
2 //!
3 //! These don't use libfuzzer — they run deterministic random inputs
4 //! through the lexer and parser to verify no panics. For real fuzzing,
5 //! use `cargo fuzz run fuzz_lexer` / `cargo fuzz run fuzz_parser`.
6
7 use armfortas::lexer::{self, SourceForm};
8 use armfortas::parser::Parser;
9
10 /// Feed N random-ish strings through the lexer.
11 fn fuzz_lexer_deterministic(seed: u64, count: usize) {
12 let mut state = seed;
13 for _ in 0..count {
14 // Simple xorshift64 PRNG.
15 state ^= state << 13;
16 state ^= state >> 7;
17 state ^= state << 17;
18
19 let len = (state % 256) as usize;
20 let bytes: Vec<u8> = (0..len)
21 .map(|i| {
22 let mut s = state.wrapping_add(i as u64);
23 s ^= s << 13;
24 s ^= s >> 7;
25 s ^= s << 17;
26 (s & 0x7F) as u8 // ASCII range
27 })
28 .collect();
29
30 if let Ok(src) = std::str::from_utf8(&bytes) {
31 let _ = lexer::tokenize(src, 0, SourceForm::FreeForm);
32 }
33 }
34 }
35
36 /// Feed N random-ish strings through lexer → parser.
37 fn fuzz_parser_deterministic(seed: u64, count: usize) {
38 let mut state = seed;
39 for _ in 0..count {
40 state ^= state << 13;
41 state ^= state >> 7;
42 state ^= state << 17;
43
44 let len = (state % 512) as usize;
45 let bytes: Vec<u8> = (0..len)
46 .map(|i| {
47 let mut s = state.wrapping_add(i as u64);
48 s ^= s << 13;
49 s ^= s >> 7;
50 s ^= s << 17;
51 (s & 0x7F) as u8
52 })
53 .collect();
54
55 if let Ok(src) = std::str::from_utf8(&bytes) {
56 if let Ok(tokens) = lexer::tokenize(src, 0, SourceForm::FreeForm) {
57 let mut parser = Parser::new(&tokens);
58 let _ = parser.parse_file();
59 }
60 }
61 }
62 }
63
64 /// Feed malformed but plausible Fortran through the parser.
65 /// Runs without threads — each input goes directly through the parser.
66 fn fuzz_parser_with_fortran_fragments(count: usize) {
67 let fragments = [
68 "program p\nend program\n", "module m\nend module\n",
69 "integer :: x\n", "real, allocatable :: a(:,:)\n",
70 "do i = 1, 10\nend do\n", "if (x > 0) then\nend if\n",
71 "select case (x)\ncase (1)\nend select\n",
72 "type :: t\n integer :: f\nend type\n",
73 "interface\nend interface\n",
74 "goto 100\n", "100 continue\n",
75 "use iso_c_binding, only: c_int\n",
76 "", "!\n", "! comment\n",
77 "end\n",
78 ];
79
80 let mut state: u64 = 0xDEADBEEF;
81 for _ in 0..count {
82 state ^= state << 13;
83 state ^= state >> 7;
84 state ^= state << 17;
85
86 // Pick 1-3 random fragments and concatenate.
87 let n_frags = 1 + (state % 3) as usize;
88 let mut src = String::new();
89 for _ in 0..n_frags {
90 state ^= state << 13;
91 state ^= state >> 7;
92 state ^= state << 17;
93 let idx = (state as usize) % fragments.len();
94 src.push_str(fragments[idx]);
95 }
96
97 if let Ok(tokens) = lexer::tokenize(&src, 0, SourceForm::FreeForm) {
98 if tokens.len() > 100 { continue; }
99 let mut parser = Parser::new(&tokens);
100 let _ = parser.parse_file();
101 }
102 }
103 }
104
105 #[test]
106 fn lexer_no_panic_on_random_ascii() {
107 fuzz_lexer_deterministic(0x12345678, 10_000);
108 }
109
110 #[test]
111 fn parser_no_panic_on_random_ascii() {
112 fuzz_parser_deterministic(0x87654321, 5_000);
113 }
114
115 #[test]
116 fn parser_no_panic_on_fortran_fragments() {
117 fuzz_parser_with_fortran_fragments(5_000);
118 }
119