Coverage Report

Created: 2026-03-31 07:30

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/fuzz_array.cpp
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
}