Rust · 3907 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",
69 "module m\nend module\n",
70 "integer :: x\n",
71 "real, allocatable :: a(:,:)\n",
72 "do i = 1, 10\nend do\n",
73 "if (x > 0) then\nend if\n",
74 "select case (x)\ncase (1)\nend select\n",
75 "type :: t\n integer :: f\nend type\n",
76 "interface\nend interface\n",
77 "goto 100\n",
78 "100 continue\n",
79 "use iso_c_binding, only: c_int\n",
80 "",
81 "!\n",
82 "! comment\n",
83 "end\n",
84 "do concurrent (i = 1:10)\nend do\n",
85 "block\n integer :: local\nend block\n",
86 "associate (a => 1)\nend associate\n",
87 ];
88
89 let mut state: u64 = 0xDEADBEEF;
90 for _ in 0..count {
91 state ^= state << 13;
92 state ^= state >> 7;
93 state ^= state << 17;
94
95 // Pick 1-3 random fragments and concatenate.
96 let n_frags = 1 + (state % 3) as usize;
97 let mut src = String::new();
98 for _ in 0..n_frags {
99 state ^= state << 13;
100 state ^= state >> 7;
101 state ^= state << 17;
102 let idx = (state as usize) % fragments.len();
103 src.push_str(fragments[idx]);
104 }
105
106 if let Ok(tokens) = lexer::tokenize(&src, 0, SourceForm::FreeForm) {
107 if tokens.len() > 100 {
108 continue;
109 }
110 let mut parser = Parser::new(&tokens);
111 let _ = parser.parse_file();
112 }
113 }
114 }
115
116 #[test]
117 fn lexer_no_panic_on_random_ascii() {
118 fuzz_lexer_deterministic(0x12345678, 10_000);
119 }
120
121 #[test]
122 fn parser_no_panic_on_random_ascii() {
123 fuzz_parser_deterministic(0x87654321, 5_000);
124 }
125
126 #[test]
127 fn parser_no_panic_on_fortran_fragments() {
128 fuzz_parser_with_fortran_fragments(5_000);
129 }
130