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 Bignum/Integer implementation (bignum.c, numeric.c) |
15 | | * |
16 | | * Purpose: Test arbitrary-precision integer operations including arithmetic, |
17 | | * bitwise operations, conversions, and comparison. Tests edge cases in |
18 | | * overflow handling, sign handling, and numeric precision. |
19 | | * |
20 | | * Coverage: |
21 | | * - Arithmetic: +, -, *, /, %, ** |
22 | | * - Bitwise: &, |, ^, <<, >> |
23 | | * - Comparison: ==, <=>, <, >, <=, >= |
24 | | * - Conversion: to_s, to_f, abs, negation |
25 | | * - Edge cases: Division by zero, large exponents, overflow |
26 | | */ |
27 | | |
28 | | #include <stdint.h> |
29 | | #include <stddef.h> |
30 | | #include <stdlib.h> |
31 | | #include <fuzzer/FuzzedDataProvider.h> |
32 | | #include "ruby.h" |
33 | | |
34 | | static int ruby_initialized = 0; |
35 | | |
36 | | extern "C" VALUE ruby_verbose; |
37 | | |
38 | 7.26k | extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { |
39 | 7.26k | if (!ruby_initialized) { |
40 | 1 | ruby_init(); |
41 | 1 | ruby_initialized = 1; |
42 | | |
43 | | // Suppress Ruby warnings to avoid log noise |
44 | 1 | ruby_verbose = Qfalse; |
45 | 1 | } |
46 | | |
47 | 7.26k | if (size < 2) return 0; |
48 | | |
49 | | // Use FuzzedDataProvider for structured data consumption |
50 | 7.26k | FuzzedDataProvider fdp(data, size); |
51 | | |
52 | 7.26k | int state = 0; |
53 | 7.26k | VALUE num1 = Qnil, num2 = Qnil, result = Qnil; |
54 | | |
55 | | // Create first number from fuzzer data |
56 | 7.26k | size_t str1_len = fdp.ConsumeIntegralInRange<size_t>(0, 10000); |
57 | 7.26k | std::string str1 = fdp.ConsumeBytesAsString(str1_len); |
58 | 7.26k | num1 = rb_protect((VALUE (*)(VALUE))rb_Integer, rb_str_new(str1.data(), str1.size()), &state); |
59 | 7.26k | if (state) { |
60 | 1.68k | rb_set_errinfo(Qnil); |
61 | 1.68k | state = 0; |
62 | 1.68k | num1 = INT2FIX(fdp.ConsumeIntegral<int32_t>()); |
63 | 1.68k | } |
64 | | |
65 | | // Create second number from remaining fuzzer data |
66 | 7.26k | size_t str2_len = fdp.ConsumeIntegralInRange<size_t>(0, 10000); |
67 | 7.26k | std::string str2 = fdp.ConsumeBytesAsString(str2_len); |
68 | 7.26k | if (!str2.empty()) { |
69 | 5.20k | num2 = rb_protect((VALUE (*)(VALUE))rb_Integer, rb_str_new(str2.data(), str2.size()), &state); |
70 | 5.20k | if (state) { |
71 | 1.88k | rb_set_errinfo(Qnil); |
72 | 1.88k | state = 0; |
73 | 1.88k | num2 = INT2FIX(fdp.ConsumeIntegral<int32_t>()); |
74 | 1.88k | } |
75 | 5.20k | } else { |
76 | 2.05k | num2 = INT2FIX(1); |
77 | 2.05k | } |
78 | | |
79 | | // Select a single operation to test |
80 | 7.26k | uint8_t op = fdp.ConsumeIntegralInRange<uint8_t>(0, 19); |
81 | | |
82 | 7.26k | switch (op) { |
83 | 1.66k | case 0: // Addition |
84 | 1.66k | result = rb_funcall(num1, rb_intern("+"), 1, num2); |
85 | 1.66k | break; |
86 | 56 | case 1: // Subtraction |
87 | 56 | result = rb_funcall(num1, rb_intern("-"), 1, num2); |
88 | 56 | break; |
89 | 105 | case 2: // Multiplication |
90 | 105 | result = rb_funcall(num1, rb_intern("*"), 1, num2); |
91 | 105 | break; |
92 | 438 | case 3: // Division (skip if num2 is zero) |
93 | 438 | if (!FIXNUM_P(num2) || FIX2LONG(num2) != 0) { |
94 | 438 | result = rb_funcall(num1, rb_intern("/"), 1, num2); |
95 | 438 | } |
96 | 438 | break; |
97 | 370 | case 4: // Modulo (skip if num2 is zero) |
98 | 370 | if (!FIXNUM_P(num2) || FIX2LONG(num2) != 0) { |
99 | 370 | result = rb_funcall(num1, rb_intern("%"), 1, num2); |
100 | 370 | } |
101 | 370 | break; |
102 | 2.01k | case 5: // Power (limit exponent to avoid hang) |
103 | 2.01k | if (FIXNUM_P(num2)) { |
104 | 2.01k | long exp_val = FIX2LONG(num2); |
105 | 2.01k | if (exp_val >= 0 && exp_val < 100) { |
106 | 1.94k | result = rb_funcall(num1, rb_intern("**"), 1, num2); |
107 | 1.94k | } |
108 | 2.01k | } |
109 | 2.01k | break; |
110 | 338 | case 6: // Bitwise AND |
111 | 338 | result = rb_funcall(num1, rb_intern("&"), 1, num2); |
112 | 338 | break; |
113 | 309 | case 7: // Bitwise OR |
114 | 309 | result = rb_funcall(num1, rb_intern("|"), 1, num2); |
115 | 309 | break; |
116 | 228 | case 8: // Bitwise XOR |
117 | 228 | result = rb_funcall(num1, rb_intern("^"), 1, num2); |
118 | 228 | break; |
119 | 251 | case 9: // Left shift (limit shift amount) |
120 | 251 | if (FIXNUM_P(num2)) { |
121 | 249 | long shift = FIX2LONG(num2); |
122 | 249 | if (shift >= 0 && shift < 256) { |
123 | 174 | result = rb_funcall(num1, rb_intern("<<"), 1, num2); |
124 | 174 | } |
125 | 249 | } |
126 | 251 | break; |
127 | 272 | case 10: // Right shift (limit shift amount) |
128 | 272 | if (FIXNUM_P(num2)) { |
129 | 271 | long shift = FIX2LONG(num2); |
130 | 271 | if (shift >= 0 && shift < 256) { |
131 | 206 | result = rb_funcall(num1, rb_intern(">>"), 1, num2); |
132 | 206 | } |
133 | 271 | } |
134 | 272 | break; |
135 | 128 | case 11: // Equality |
136 | 128 | result = rb_funcall(num1, rb_intern("=="), 1, num2); |
137 | 128 | break; |
138 | 134 | case 12: // Spaceship operator (comparison) |
139 | 134 | result = rb_funcall(num1, rb_intern("<=>"), 1, num2); |
140 | 134 | break; |
141 | 89 | case 13: // Greater than |
142 | 89 | result = rb_funcall(num1, rb_intern(">"), 1, num2); |
143 | 89 | break; |
144 | 119 | case 14: // Less than |
145 | 119 | result = rb_funcall(num1, rb_intern("<"), 1, num2); |
146 | 119 | break; |
147 | 437 | case 15: // To string conversion |
148 | 437 | result = rb_funcall(num1, rb_intern("to_s"), 0); |
149 | 437 | break; |
150 | 102 | case 16: // To float conversion |
151 | 102 | result = rb_funcall(num1, rb_intern("to_f"), 0); |
152 | 102 | break; |
153 | 111 | case 17: // Absolute value |
154 | 111 | result = rb_funcall(num1, rb_intern("abs"), 0); |
155 | 111 | break; |
156 | 93 | case 18: // Negation |
157 | 93 | result = rb_funcall(num1, rb_intern("-@"), 0); |
158 | 93 | break; |
159 | 0 | case 19: // Bitwise NOT |
160 | 0 | result = rb_funcall(num1, rb_intern("~"), 0); |
161 | 0 | break; |
162 | 7.26k | } |
163 | | |
164 | | // Force GC |
165 | 7.26k | rb_gc_start(); |
166 | | |
167 | 7.26k | return 0; |
168 | 7.26k | } |