Coverage Report

Created: 2026-03-31 07:30

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/fuzz_hash.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 Hash implementation (hash.c)
15
 * 
16
 * Purpose: Test hash operations including storage, retrieval, deletion,
17
 * and complex operations like merging, inverting, and rehashing. Tests
18
 * edge cases in hash collision handling, memory management, and key equality.
19
 * 
20
 * Coverage:
21
 * - Basic operations: [], []=, delete, clear, keys, values
22
 * - Advanced operations: merge, update, invert, flatten, shift
23
 * - Edge cases: rehashing, compare_by_identity, nested hashes
24
 * - Memory: Hash growth/shrinkage, collision handling
25
 */
26
27
#include <stdint.h>
28
#include <stddef.h>
29
#include <stdlib.h>
30
#include <fuzzer/FuzzedDataProvider.h>
31
#include "ruby.h"
32
33
static int ruby_initialized = 0;
34
35
extern "C" VALUE ruby_verbose;
36
37
// Wrapper functions for rb_protect - necessary to catch exceptions
38
// Hash operations can raise exceptions (e.g., frozen hash, recursive comparison)
39
27
static VALUE call_hash_aref(VALUE args) {
40
27
    VALUE *ptr = (VALUE *)args;
41
27
    return rb_hash_aref(ptr[0], ptr[1]);  // Hash lookup - hash[key]
42
27
}
43
44
380
static VALUE call_hash_aset(VALUE args) {
45
380
    VALUE *ptr = (VALUE *)args;
46
380
    return rb_hash_aset(ptr[0], ptr[1], ptr[2]);  // Hash assignment - hash[key] = value
47
380
}
48
49
61
static VALUE call_hash_delete(VALUE args) {
50
61
    VALUE *ptr = (VALUE *)args;
51
61
    return rb_hash_delete(ptr[0], ptr[1]);  // Key deletion
52
61
}
53
54
37
static VALUE call_hash_rehash(VALUE hash) {
55
37
    return rb_funcall(hash, rb_intern("rehash"), 0);  // Rebuild hash after key mutation
56
37
}
57
58
4
static VALUE call_hash_clear(VALUE hash) {
59
4
    return rb_hash_clear(hash);  // Remove all entries
60
4
}
61
62
22
static VALUE call_hash_merge(VALUE args) {
63
22
    VALUE *ptr = (VALUE *)args;
64
22
    return rb_funcall(ptr[0], rb_intern("merge"), 1, ptr[1]);  // Non-destructive merge
65
22
}
66
67
14
static VALUE call_hash_update(VALUE args) {
68
14
    VALUE *ptr = (VALUE *)args;
69
14
    return rb_funcall(ptr[0], rb_intern("merge!"), 1, ptr[1]);  // Destructive merge
70
14
}
71
72
100
static VALUE call_hash_invert(VALUE hash) {
73
100
    return rb_funcall(hash, rb_intern("invert"), 0);  // Swap keys/values
74
100
}
75
76
18
static VALUE call_hash_to_a(VALUE hash) {
77
18
    return rb_funcall(hash, rb_intern("to_a"), 0);  // Convert to array of [k,v] pairs
78
18
}
79
80
34
static VALUE call_hash_shift(VALUE hash) {
81
34
    return rb_funcall(hash, rb_intern("shift"), 0);  // Remove and return first pair
82
34
}
83
84
25
static VALUE call_hash_compare_by_id(VALUE hash) {
85
25
    return rb_funcall(hash, rb_intern("compare_by_identity"), 0);  // Use object_id for key equality
86
25
}
87
88
25
static VALUE call_hash_flatten(VALUE args) {
89
25
    VALUE *ptr = (VALUE *)args;
90
25
    return rb_funcall(ptr[0], rb_intern("flatten"), 1, ptr[1]);  // Flatten nested arrays
91
25
}
92
93
476
static VALUE call_hash_fetch(VALUE args) {
94
476
    VALUE *ptr = (VALUE *)args;
95
476
    return rb_hash_fetch(ptr[0], ptr[1]);  // Fetch with exception if key not found
96
476
}
97
98
1.33k
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
99
    // Initialize Ruby once on first call
100
    // Sets up VM, object system, and Hash class
101
1.33k
    if (!ruby_initialized) {
102
1
        ruby_init();
103
1
        ruby_initialized = 1;
104
        
105
        // Suppress Ruby warnings to avoid log noise
106
1
        ruby_verbose = Qfalse;
107
1
    }
108
    
109
1.33k
    if (size < 2) return 0;
110
    
111
    // Use FuzzedDataProvider for structured data consumption
112
1.33k
    FuzzedDataProvider fdp(data, size);
113
    
114
1.33k
    int state = 0;
115
1.33k
    VALUE hash = rb_hash_new();
116
1.33k
    VALUE args[3];
117
    
118
    // Populate hash deterministically from fuzzer input
119
1.33k
    size_t num_entries = fdp.ConsumeIntegralInRange<size_t>(0, 20);
120
8.72k
    for (size_t i = 0; i < num_entries && fdp.remaining_bytes() > 1; i++) {
121
7.39k
        size_t key_len = fdp.ConsumeIntegralInRange<size_t>(1, 10000);
122
7.39k
        std::string key_data = fdp.ConsumeBytesAsString(key_len);
123
7.39k
        size_t val_len = fdp.ConsumeIntegralInRange<size_t>(0, 10000);
124
7.39k
        std::string val_data = fdp.ConsumeBytesAsString(val_len);
125
        
126
7.39k
        VALUE key = rb_str_new(key_data.data(), key_data.size());
127
7.39k
        VALUE val = rb_str_new(val_data.data(), val_data.size());
128
7.39k
        rb_hash_aset(hash, key, val);
129
7.39k
    }
130
    
131
    // Select a single hash operation to test
132
1.33k
    uint8_t op = fdp.ConsumeIntegralInRange<uint8_t>(0, 17);
133
    
134
    // Create key and value for the operation from remaining data
135
1.33k
    size_t key_len = fdp.ConsumeIntegralInRange<size_t>(1, 10000);
136
1.33k
    std::string key_data = fdp.ConsumeBytesAsString(key_len);
137
1.33k
    size_t val_len = fdp.ConsumeIntegralInRange<size_t>(0, 10000);
138
1.33k
    std::string val_data = fdp.ConsumeBytesAsString(val_len);
139
    
140
1.33k
    VALUE key = rb_str_new(key_data.data(), key_data.size());
141
1.33k
    VALUE val = val_data.empty() ? Qnil : rb_str_new(val_data.data(), val_data.size());
142
    
143
1.33k
    switch (op) {
144
380
            case 0: // Hash insert/update - tests storage and collision handling
145
380
                args[0] = hash;
146
380
                args[1] = key;
147
380
                args[2] = val;
148
380
                rb_protect(call_hash_aset, (VALUE)args, &state);
149
380
                if (state) { rb_set_errinfo(Qnil); state = 0; }
150
380
                break;
151
                
152
27
            case 1: // Hash lookup - tests retrieval and key equality
153
27
                args[0] = hash;
154
27
                args[1] = key;
155
27
                rb_protect(call_hash_aref, (VALUE)args, &state);
156
27
                if (state) { rb_set_errinfo(Qnil); state = 0; }
157
27
                break;
158
                
159
61
            case 2: // Hash delete - tests key removal and table shrinkage
160
61
                args[0] = hash;
161
61
                args[1] = key;
162
61
                rb_protect(call_hash_delete, (VALUE)args, &state);
163
61
                if (state) { rb_set_errinfo(Qnil); state = 0; }
164
61
                break;
165
                
166
37
            case 3: // Hash rehash - tests hash table reconstruction after key mutation
167
37
                rb_protect(call_hash_rehash, hash, &state);
168
37
                if (state) { rb_set_errinfo(Qnil); state = 0; }
169
37
                break;
170
                
171
4
            case 4: // Hash clear - tests bulk removal
172
4
                rb_protect(call_hash_clear, hash, &state);
173
4
                if (state) { rb_set_errinfo(Qnil); state = 0; }
174
4
                break;
175
                
176
8
            case 5: // Hash size - tests entry counting
177
8
                rb_hash_size(hash);
178
8
                break;
179
                
180
26
            case 6: // Hash keys - tests key array generation
181
26
                rb_funcall(hash, rb_intern("keys"), 0);
182
26
                break;
183
                
184
35
            case 7: // Hash values - tests value array generation
185
35
                rb_funcall(hash, rb_intern("values"), 0);
186
35
                break;
187
                
188
36
            case 8: // Hash has_key? - tests membership checking
189
36
                rb_funcall(hash, rb_intern("has_key?"), 1, key);
190
36
                break;
191
                
192
2
            case 9: // Hash dup - tests shallow copy operations
193
2
                rb_hash_dup(hash);
194
2
                break;
195
            
196
22
            case 10: // Hash merge - tests non-destructive combining of hashes
197
22
                {
198
22
                    VALUE other = rb_hash_new();
199
22
                    rb_hash_aset(other, key, val);
200
22
                    args[0] = hash;
201
22
                    args[1] = other;
202
22
                    rb_protect(call_hash_merge, (VALUE)args, &state);
203
22
                    if (state) { rb_set_errinfo(Qnil); state = 0; }
204
22
                }
205
22
                break;
206
            
207
14
            case 11: // Hash update/merge! - tests destructive merging
208
14
                {
209
14
                    VALUE other = rb_hash_new();
210
14
                    rb_hash_aset(other, key, val);
211
14
                    args[0] = hash;
212
14
                    args[1] = other;
213
14
                    rb_protect(call_hash_update, (VALUE)args, &state);
214
14
                    if (state) { rb_set_errinfo(Qnil); state = 0; }
215
14
                }
216
14
                break;
217
            
218
100
            case 12: // Hash invert - tests key/value swapping and collision handling
219
100
                rb_protect(call_hash_invert, hash, &state);
220
100
                if (state) { rb_set_errinfo(Qnil); state = 0; }
221
100
                break;
222
            
223
18
            case 13: // Hash to_a - tests conversion to array of pairs
224
18
                rb_protect(call_hash_to_a, hash, &state);
225
18
                if (state) { rb_set_errinfo(Qnil); state = 0; }
226
18
                break;
227
            
228
34
            case 14: // Hash shift - tests FIFO removal (first entry)
229
34
                rb_protect(call_hash_shift, hash, &state);
230
34
                if (state) { rb_set_errinfo(Qnil); state = 0; }
231
34
                break;
232
            
233
25
            case 15: // Hash compare_by_identity - tests object_id-based key comparison
234
25
                rb_protect(call_hash_compare_by_id, hash, &state);
235
25
                if (state) { rb_set_errinfo(Qnil); state = 0; }
236
25
                break;
237
            
238
25
            case 16: // Hash flatten - tests nested array flattening in values
239
25
                {
240
25
                    args[0] = hash;
241
25
                    args[1] = INT2FIX(1);  // Flatten depth
242
25
                    rb_protect(call_hash_flatten, (VALUE)args, &state);
243
25
                    if (state) { rb_set_errinfo(Qnil); state = 0; }
244
25
                }
245
25
                break;
246
            
247
476
            case 17: // Hash fetch with default - tests key lookup with fallback
248
476
                {
249
476
                    args[0] = hash;
250
476
                    args[1] = key;
251
476
                    rb_protect(call_hash_fetch, (VALUE)args, &state);
252
476
                    if (state) { rb_set_errinfo(Qnil); state = 0; }
253
476
                }
254
476
                break;
255
1.33k
        }
256
    
257
    // Clean up - force GC to release memory
258
    // Ensures hash memory is properly freed across iterations
259
1.33k
    rb_gc_start();
260
    
261
1.33k
    return 0;
262
1.33k
}