Line | Count | Source |
1 | | /* Copyright 2025 Google LLC |
2 | | Licensed under the Apache License, Version 2.0 (the "License"); |
3 | | you may not use this file except in compliance with the License. |
4 | | You may obtain a copy of the License at |
5 | | http://www.apache.org/licenses/LICENSE-2.0 |
6 | | Unless required by applicable law or agreed to in writing, software |
7 | | distributed under the License is distributed on an "AS IS" BASIS, |
8 | | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
9 | | See the License for the specific language governing permissions and |
10 | | limitations under the License. |
11 | | */ |
12 | | |
13 | | /* |
14 | | * Fuzzer for Ruby's Array implementation (array.c) |
15 | | * |
16 | | * Purpose: Test array operations including creation, manipulation, sorting, |
17 | | * and iteration. Tests edge cases in memory management, element access, |
18 | | * and array-specific algorithms. |
19 | | * |
20 | | * Coverage: |
21 | | * - Element operations: push, pop, shift, unshift, insert, delete |
22 | | * - Access operations: [], []=, first, last, at, fetch |
23 | | * - Transformation: map, select, compact, flatten, reverse, sort |
24 | | * - Combining: concat, +, -, &, | |
25 | | * - Iteration: each, each_index, reverse_each |
26 | | * - Memory: Array growth/shrinkage, shared arrays |
27 | | */ |
28 | | |
29 | | #include <stdint.h> |
30 | | #include <stddef.h> |
31 | | #include <stdlib.h> |
32 | | #include <fuzzer/FuzzedDataProvider.h> |
33 | | #include "ruby.h" |
34 | | |
35 | | static int ruby_initialized = 0; |
36 | | |
37 | | extern "C" VALUE ruby_verbose; |
38 | | |
39 | | // Wrapper functions for rb_protect - necessary to catch exceptions |
40 | | // Array operations can raise exceptions (e.g., index errors, frozen arrays) |
41 | 135 | static VALUE call_array_aref(VALUE args) { |
42 | 135 | VALUE *ptr = (VALUE *)args; |
43 | 135 | return rb_ary_entry(ptr[0], FIX2INT(ptr[1])); // Array element access - ary[index] |
44 | 135 | } |
45 | | |
46 | 86 | static VALUE call_array_aset(VALUE args) { |
47 | 86 | VALUE *ptr = (VALUE *)args; |
48 | 86 | rb_ary_store(ptr[0], FIX2INT(ptr[1]), ptr[2]); // Array element assignment - ary[index] = value |
49 | 86 | return ptr[2]; // Return the value that was set |
50 | 86 | } |
51 | | |
52 | 123 | static VALUE call_array_concat(VALUE args) { |
53 | 123 | VALUE *ptr = (VALUE *)args; |
54 | 123 | return rb_ary_concat(ptr[0], ptr[1]); // Concatenate arrays |
55 | 123 | } |
56 | | |
57 | 28 | static VALUE call_array_plus(VALUE args) { |
58 | 28 | VALUE *ptr = (VALUE *)args; |
59 | 28 | return rb_funcall(ptr[0], rb_intern("+"), 1, ptr[1]); // Array addition |
60 | 28 | } |
61 | | |
62 | 21 | static VALUE call_array_minus(VALUE args) { |
63 | 21 | VALUE *ptr = (VALUE *)args; |
64 | 21 | return rb_funcall(ptr[0], rb_intern("-"), 1, ptr[1]); // Array subtraction |
65 | 21 | } |
66 | | |
67 | 173 | static VALUE call_array_and(VALUE args) { |
68 | 173 | VALUE *ptr = (VALUE *)args; |
69 | 173 | return rb_funcall(ptr[0], rb_intern("&"), 1, ptr[1]); // Array intersection |
70 | 173 | } |
71 | | |
72 | 123 | static VALUE call_array_or(VALUE args) { |
73 | 123 | VALUE *ptr = (VALUE *)args; |
74 | 123 | return rb_funcall(ptr[0], rb_intern("|"), 1, ptr[1]); // Array union |
75 | 123 | } |
76 | | |
77 | 19 | static VALUE call_array_flatten(VALUE args) { |
78 | 19 | VALUE *ptr = (VALUE *)args; |
79 | 19 | return rb_funcall(ptr[0], rb_intern("flatten"), 1, ptr[1]); // Flatten nested arrays |
80 | 19 | } |
81 | | |
82 | 15 | static VALUE call_array_compact(VALUE ary) { |
83 | 15 | return rb_funcall(ary, rb_intern("compact"), 0); // Remove nil elements |
84 | 15 | } |
85 | | |
86 | 94 | static VALUE call_array_uniq(VALUE ary) { |
87 | 94 | return rb_funcall(ary, rb_intern("uniq"), 0); // Remove duplicates |
88 | 94 | } |
89 | | |
90 | 15 | static VALUE call_array_rotate(VALUE args) { |
91 | 15 | VALUE *ptr = (VALUE *)args; |
92 | 15 | return rb_funcall(ptr[0], rb_intern("rotate"), 1, ptr[1]); // Rotate array |
93 | 15 | } |
94 | | |
95 | 16 | static VALUE call_array_sample(VALUE ary) { |
96 | 16 | return rb_funcall(ary, rb_intern("sample"), 0); // Get random element |
97 | 16 | } |
98 | | |
99 | 38 | static VALUE call_array_fetch(VALUE args) { |
100 | 38 | VALUE *ptr = (VALUE *)args; |
101 | 38 | return rb_funcall(ptr[0], rb_intern("fetch"), 1, ptr[1]); // Fetch with bounds checking |
102 | 38 | } |
103 | | |
104 | 1.62k | extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { |
105 | | // Initialize Ruby once on first call |
106 | | // Sets up VM, object system, and Array class |
107 | 1.62k | if (!ruby_initialized) { |
108 | 1 | ruby_init(); |
109 | 1 | ruby_initialized = 1; |
110 | | |
111 | | // Suppress Ruby warnings to avoid log noise |
112 | 1 | ruby_verbose = Qfalse; |
113 | 1 | } |
114 | | |
115 | 1.62k | if (size < 2) return 0; |
116 | | |
117 | | // Use FuzzedDataProvider for structured data consumption |
118 | 1.62k | FuzzedDataProvider fdp(data, size); |
119 | | |
120 | 1.62k | int state = 0; |
121 | 1.62k | VALUE ary = rb_ary_new(); |
122 | 1.62k | VALUE args[3]; |
123 | | |
124 | | // Populate array with strings only |
125 | | // Testing string arrays exercises array operations with object references |
126 | 1.62k | size_t num_elements = fdp.ConsumeIntegralInRange<size_t>(0, 30); |
127 | 12.6k | for (size_t i = 0; i < num_elements && fdp.remaining_bytes() > 0; i++) { |
128 | 11.0k | size_t str_len = fdp.ConsumeIntegralInRange<size_t>(0, 5000); |
129 | 11.0k | std::string str = fdp.ConsumeBytesAsString(str_len); |
130 | 11.0k | rb_ary_push(ary, rb_str_new(str.data(), str.size())); |
131 | 11.0k | } |
132 | | |
133 | | // Select and perform a single array operation based on fuzzer input |
134 | | // Each operation tests different aspects of array.c |
135 | 1.62k | uint8_t op = fdp.ConsumeIntegralInRange<uint8_t>(0, 24); |
136 | | |
137 | 1.62k | switch (op) { |
138 | 142 | case 0: // Array element access - tests indexing logic |
139 | 142 | if (RARRAY_LEN(ary) > 0) { |
140 | 135 | long idx = fdp.ConsumeIntegralInRange<long>(-10, 10); |
141 | 135 | args[0] = ary; |
142 | 135 | args[1] = LONG2FIX(idx); |
143 | 135 | rb_protect(call_array_aref, (VALUE)args, &state); |
144 | 135 | if (state) { rb_set_errinfo(Qnil); state = 0; } |
145 | 135 | } |
146 | 142 | break; |
147 | | |
148 | 86 | case 1: // Array element assignment - tests storage and bounds |
149 | 86 | { |
150 | 86 | long idx = fdp.ConsumeIntegralInRange<long>(-5, 15); |
151 | 86 | std::string val_data = fdp.ConsumeBytesAsString( |
152 | 86 | fdp.ConsumeIntegralInRange<size_t>(0, 5000) |
153 | 86 | ); |
154 | 86 | VALUE val = rb_str_new(val_data.data(), val_data.size()); |
155 | 86 | args[0] = ary; |
156 | 86 | args[1] = LONG2FIX(idx); |
157 | 86 | args[2] = val; |
158 | 86 | rb_protect(call_array_aset, (VALUE)args, &state); |
159 | 86 | if (state) { rb_set_errinfo(Qnil); state = 0; } |
160 | 86 | } |
161 | 86 | break; |
162 | | |
163 | 41 | case 2: // Array push - tests growth |
164 | 41 | { |
165 | 41 | std::string data_str = fdp.ConsumeBytesAsString( |
166 | 41 | fdp.ConsumeIntegralInRange<size_t>(0, 5000) |
167 | 41 | ); |
168 | 41 | rb_ary_push(ary, rb_str_new(data_str.data(), data_str.size())); |
169 | 41 | } |
170 | 41 | break; |
171 | | |
172 | 13 | case 3: // Array pop - tests shrinkage |
173 | 13 | if (RARRAY_LEN(ary) > 0) { |
174 | 11 | rb_ary_pop(ary); |
175 | 11 | } |
176 | 13 | break; |
177 | | |
178 | 13 | case 4: // Array shift - tests FIFO removal |
179 | 13 | if (RARRAY_LEN(ary) > 0) { |
180 | 12 | rb_ary_shift(ary); |
181 | 12 | } |
182 | 13 | break; |
183 | | |
184 | 54 | case 5: // Array unshift - tests prepending |
185 | 54 | { |
186 | 54 | std::string str = fdp.ConsumeBytesAsString( |
187 | 54 | fdp.ConsumeIntegralInRange<size_t>(0, 5000) |
188 | 54 | ); |
189 | 54 | rb_ary_unshift(ary, rb_str_new(str.data(), str.size())); |
190 | 54 | } |
191 | 54 | break; |
192 | | |
193 | 9 | case 6: // Array reverse - tests element reordering |
194 | 9 | rb_ary_reverse(ary); |
195 | 9 | break; |
196 | | |
197 | 157 | case 7: // Array sort - tests comparison and ordering |
198 | 157 | rb_protect((VALUE (*)(VALUE))rb_ary_sort, ary, &state); |
199 | 157 | if (state) { rb_set_errinfo(Qnil); state = 0; } |
200 | 157 | break; |
201 | | |
202 | 2 | case 8: // Array dup - tests shallow copy |
203 | 2 | rb_ary_dup(ary); |
204 | 2 | break; |
205 | | |
206 | 4 | case 9: // Array clear - tests bulk removal |
207 | 4 | rb_ary_clear(ary); |
208 | 4 | break; |
209 | | |
210 | 123 | case 10: // Array concat - tests array merging |
211 | 123 | { |
212 | 123 | VALUE other = rb_ary_new(); |
213 | 123 | size_t n = fdp.ConsumeIntegralInRange<size_t>(0, 5); |
214 | 390 | for (size_t i = 0; i < n && fdp.remaining_bytes() > 0; i++) { |
215 | 267 | std::string str = fdp.ConsumeBytesAsString( |
216 | 267 | fdp.ConsumeIntegralInRange<size_t>(0, 5000) |
217 | 267 | ); |
218 | 267 | rb_ary_push(other, rb_str_new(str.data(), str.size())); |
219 | 267 | } |
220 | 123 | args[0] = ary; |
221 | 123 | args[1] = other; |
222 | 123 | rb_protect(call_array_concat, (VALUE)args, &state); |
223 | 123 | if (state) { rb_set_errinfo(Qnil); state = 0; } |
224 | 123 | } |
225 | 123 | break; |
226 | | |
227 | 332 | case 11: // Array join - tests string conversion |
228 | 332 | { |
229 | 332 | std::string sep = fdp.ConsumeBytesAsString( |
230 | 332 | fdp.ConsumeIntegralInRange<size_t>(0, 5000) |
231 | 332 | ); |
232 | 332 | rb_ary_join(ary, rb_str_new(sep.data(), sep.size())); |
233 | 332 | } |
234 | 332 | break; |
235 | | |
236 | 39 | case 12: // Array slice - tests subarray extraction |
237 | 39 | if (RARRAY_LEN(ary) > 0) { |
238 | 38 | long start = fdp.ConsumeIntegralInRange<long>(0, RARRAY_LEN(ary)); |
239 | 38 | long len = fdp.ConsumeIntegralInRange<long>(0, 5000); |
240 | 38 | rb_ary_subseq(ary, start, len); |
241 | 38 | } |
242 | 39 | break; |
243 | | |
244 | 67 | case 13: // Array first - tests head access |
245 | 67 | { |
246 | 67 | long n = fdp.ConsumeIntegralInRange<long>(0, 2000); |
247 | 67 | rb_funcall(ary, rb_intern("first"), 1, LONG2FIX(n)); |
248 | 67 | } |
249 | 67 | break; |
250 | | |
251 | 0 | case 14: // Array last - tests tail access |
252 | 0 | { |
253 | 0 | long n = fdp.ConsumeIntegralInRange<long>(0, 2000); |
254 | 0 | rb_funcall(ary, rb_intern("last"), 1, LONG2FIX(n)); |
255 | 0 | } |
256 | 0 | break; |
257 | | |
258 | 28 | case 15: // Array + (addition) - tests combining |
259 | 28 | { |
260 | 28 | VALUE other = rb_ary_new(); |
261 | 28 | std::string str = fdp.ConsumeBytesAsString( |
262 | 28 | fdp.ConsumeIntegralInRange<size_t>(0, 5000) |
263 | 28 | ); |
264 | 28 | rb_ary_push(other, rb_str_new(str.data(), str.size())); |
265 | 28 | args[0] = ary; |
266 | 28 | args[1] = other; |
267 | 28 | rb_protect(call_array_plus, (VALUE)args, &state); |
268 | 28 | if (state) { rb_set_errinfo(Qnil); state = 0; } |
269 | 28 | } |
270 | 28 | break; |
271 | | |
272 | 21 | case 16: // Array - (subtraction) - tests difference |
273 | 21 | { |
274 | 21 | VALUE other = rb_ary_new(); |
275 | 21 | if (RARRAY_LEN(ary) > 0) { |
276 | 17 | rb_ary_push(other, rb_ary_entry(ary, 0)); |
277 | 17 | } |
278 | 21 | args[0] = ary; |
279 | 21 | args[1] = other; |
280 | 21 | rb_protect(call_array_minus, (VALUE)args, &state); |
281 | 21 | if (state) { rb_set_errinfo(Qnil); state = 0; } |
282 | 21 | } |
283 | 21 | break; |
284 | | |
285 | 173 | case 17: // Array & (intersection) - tests set operations |
286 | 173 | { |
287 | 173 | VALUE other = rb_ary_new(); |
288 | 173 | std::string str = fdp.ConsumeBytesAsString( |
289 | 173 | fdp.ConsumeIntegralInRange<size_t>(0, 5000) |
290 | 173 | ); |
291 | 173 | rb_ary_push(other, rb_str_new(str.data(), str.size())); |
292 | 173 | args[0] = ary; |
293 | 173 | args[1] = other; |
294 | 173 | rb_protect(call_array_and, (VALUE)args, &state); |
295 | 173 | if (state) { rb_set_errinfo(Qnil); state = 0; } |
296 | 173 | } |
297 | 173 | break; |
298 | | |
299 | 123 | case 18: // Array | (union) - tests set operations |
300 | 123 | { |
301 | 123 | VALUE other = rb_ary_new(); |
302 | 123 | std::string str = fdp.ConsumeBytesAsString( |
303 | 123 | fdp.ConsumeIntegralInRange<size_t>(0, 5000) |
304 | 123 | ); |
305 | 123 | rb_ary_push(other, rb_str_new(str.data(), str.size())); |
306 | 123 | args[0] = ary; |
307 | 123 | args[1] = other; |
308 | 123 | rb_protect(call_array_or, (VALUE)args, &state); |
309 | 123 | if (state) { rb_set_errinfo(Qnil); state = 0; } |
310 | 123 | } |
311 | 123 | break; |
312 | | |
313 | 19 | case 19: // Array flatten - tests nested array handling |
314 | 19 | { |
315 | 19 | args[0] = ary; |
316 | 19 | args[1] = INT2FIX(fdp.ConsumeIntegralInRange<int>(0, 3)); |
317 | 19 | rb_protect(call_array_flatten, (VALUE)args, &state); |
318 | 19 | if (state) { rb_set_errinfo(Qnil); state = 0; } |
319 | 19 | } |
320 | 19 | break; |
321 | | |
322 | 15 | case 20: // Array compact - tests nil removal |
323 | 15 | rb_protect(call_array_compact, ary, &state); |
324 | 15 | if (state) { rb_set_errinfo(Qnil); state = 0; } |
325 | 15 | break; |
326 | | |
327 | 94 | case 21: // Array uniq - tests duplicate removal |
328 | 94 | rb_protect(call_array_uniq, ary, &state); |
329 | 94 | if (state) { rb_set_errinfo(Qnil); state = 0; } |
330 | 94 | break; |
331 | | |
332 | 15 | case 22: // Array rotate - tests circular shift |
333 | 15 | { |
334 | 15 | args[0] = ary; |
335 | 15 | args[1] = INT2FIX(fdp.ConsumeIntegralInRange<int>(-5, 5)); |
336 | 15 | rb_protect(call_array_rotate, (VALUE)args, &state); |
337 | 15 | if (state) { rb_set_errinfo(Qnil); state = 0; } |
338 | 15 | } |
339 | 15 | break; |
340 | | |
341 | 16 | case 23: // Array sample - tests random access |
342 | 16 | rb_protect(call_array_sample, ary, &state); |
343 | 16 | if (state) { rb_set_errinfo(Qnil); state = 0; } |
344 | 16 | break; |
345 | | |
346 | 41 | case 24: // Array fetch - tests bounds-checked access |
347 | 41 | if (RARRAY_LEN(ary) > 0) { |
348 | 38 | long idx = fdp.ConsumeIntegralInRange<long>(-10, 10); |
349 | 38 | args[0] = ary; |
350 | 38 | args[1] = LONG2FIX(idx); |
351 | 38 | rb_protect(call_array_fetch, (VALUE)args, &state); |
352 | 38 | if (state) { rb_set_errinfo(Qnil); state = 0; } |
353 | 38 | } |
354 | 41 | break; |
355 | 1.62k | } |
356 | | |
357 | | // Clean up - force GC to release memory |
358 | | // Ensures array memory is properly freed across iterations |
359 | 1.62k | rb_gc_start(); |
360 | | |
361 | 1.62k | return 0; |
362 | 1.62k | } |