| 1 | //! Used in conjuncation with `std.testing.fuzz` to generate values |
| 2 | |
| 3 | const builtin = @import("builtin"); |
| 4 | const std = @import("../std.zig"); |
| 5 | const assert = std.debug.assert; |
| 6 | const fuzz_abi = std.Build.abi.fuzz; |
| 7 | const Smith = @This(); |
| 8 | |
| 9 | /// Null if the fuzzer is being used, in which case this struct will not be mutated. |
| 10 | /// |
| 11 | /// Intended to be initialized directly. |
| 12 | in: ?[]const u8, |
| 13 | |
| 14 | pub const Weight = fuzz_abi.Weight; |
| 15 | |
| 16 | fn intUid(hash: u32) fuzz_abi.Uid { |
| 17 | @disableInstrumentation(); |
| 18 | return @bitCast(hash << 1); |
| 19 | } |
| 20 | |
| 21 | fn bytesUid(hash: u32) fuzz_abi.Uid { |
| 22 | @disableInstrumentation(); |
| 23 | return @bitCast(hash | 1); |
| 24 | } |
| 25 | |
| 26 | fn Backing(T: type) type { |
| 27 | return @Int(.unsigned, @bitSizeOf(T)); |
| 28 | } |
| 29 | |
| 30 | fn toExcessK(T: type, x: T) Backing(T) { |
| 31 | return @bitCast(x -% std.math.minInt(T)); |
| 32 | } |
| 33 | |
| 34 | fn fromExcessK(T: type, x: Backing(T)) T { |
| 35 | return @as(T, @bitCast(x)) +% std.math.minInt(T); |
| 36 | } |
| 37 | |
| 38 | const EnumField = struct { |
| 39 | name: [:0]const u8, |
| 40 | value: comptime_int, |
| 41 | }; |
| 42 | |
| 43 | fn enumFieldLessThan(_: void, a: EnumField, b: EnumField) bool { |
| 44 | return a.value < b.value; |
| 45 | } |
| 46 | |
| 47 | /// Returns an array of weights containing each possible value of `T`. |
| 48 | // |
| 49 | // `inline` to propogate the `comptime`ness of the result |
| 50 | pub inline fn baselineWeights(T: type) []const Weight { |
| 51 | return comptime switch (@typeInfo(T)) { |
| 52 | .bool, .int, .float => i: { |
| 53 | // Reject types that don't have a fixed bitsize (esp. usize) |
| 54 | // since they are not gauraunteed to fit in a u64 across targets. |
| 55 | if (std.mem.findScalar(type, &.{ |
| 56 | isize, usize, |
| 57 | c_char, c_longdouble, |
| 58 | c_short, c_ushort, |
| 59 | c_int, c_uint, |
| 60 | c_long, c_ulong, |
| 61 | c_longlong, c_ulonglong, |
| 62 | }, T) != null) { |
| 63 | @compileError("type does not have a fixed bitsize: " ++ @typeName(T)); |
| 64 | } |
| 65 | break :i &.{.rangeAtMost(Backing(T), 0, (1 << @bitSizeOf(T)) - 1, 1)}; |
| 66 | }, |
| 67 | .@"struct" => |s| if (s.backing_integer) |B| |
| 68 | baselineWeights(B) |
| 69 | else |
| 70 | @compileError("non-packed structs cannot be weighted"), |
| 71 | .@"union" => |u| if (u.layout == .@"packed") |
| 72 | baselineWeights(Backing(T)) |
| 73 | else |
| 74 | @compileError("non-packed unions cannot be weighted"), |
| 75 | .@"enum" => |e| if (e.mode == .nonexhaustive) |
| 76 | baselineWeights(e.tag_type) |
| 77 | else if (e.field_names.len == 0) |
| 78 | // Cannot be included in below branch due to `log2_int_ceil` |
| 79 | @compileError("exhaustive zero-field enums cannot be weighted") |
| 80 | else e: { |
| 81 | @setEvalBranchQuota(@intCast(4 * e.field_names.len * |
| 82 | std.math.log2_int_ceil(usize, e.field_names.len))); |
| 83 | |
| 84 | var sorted_fields = blk: { |
| 85 | var fields: [e.field_names.len]EnumField = undefined; |
| 86 | for (e.field_names, e.field_values, &fields) |f_name, f_value, *field| { |
| 87 | field.* = .{ |
| 88 | .name = f_name, |
| 89 | .value = f_value, |
| 90 | }; |
| 91 | } |
| 92 | break :blk fields; |
| 93 | }; |
| 94 | std.mem.sortUnstable(EnumField, &sorted_fields, {}, enumFieldLessThan); |
| 95 | |
| 96 | var weights: []const Weight = &.{}; |
| 97 | var seq_first: u64 = sorted_fields[0].value; |
| 98 | for (sorted_fields[0 .. sorted_fields.len - 1], sorted_fields[1..]) |prev, field| { |
| 99 | if (field.value != prev.value + 1) { |
| 100 | weights = weights ++ .{Weight.rangeAtMost(u64, seq_first, prev.value, 1)}; |
| 101 | seq_first = field.value; |
| 102 | } |
| 103 | } |
| 104 | weights = weights ++ .{Weight.rangeAtMost( |
| 105 | u64, |
| 106 | seq_first, |
| 107 | sorted_fields[sorted_fields.len - 1].value, |
| 108 | 1, |
| 109 | )}; |
| 110 | |
| 111 | break :e weights; |
| 112 | }, |
| 113 | else => @compileError("unexpected type: " ++ @typeName(T)), |
| 114 | }; |
| 115 | } |
| 116 | |
| 117 | test baselineWeights { |
| 118 | try std.testing.expectEqualSlices( |
| 119 | Weight, |
| 120 | &.{.rangeAtMost(bool, false, true, 1)}, |
| 121 | baselineWeights(bool), |
| 122 | ); |
| 123 | try std.testing.expectEqualSlices( |
| 124 | Weight, |
| 125 | &.{.rangeAtMost(u4, 0, 15, 1)}, |
| 126 | baselineWeights(u4), |
| 127 | ); |
| 128 | try std.testing.expectEqualSlices( |
| 129 | Weight, |
| 130 | &.{.rangeAtMost(u4, 0, 15, 1)}, |
| 131 | baselineWeights(i4), |
| 132 | ); |
| 133 | try std.testing.expectEqualSlices( |
| 134 | Weight, |
| 135 | &.{.rangeAtMost(u16, 0, 0xffff, 1)}, |
| 136 | baselineWeights(f16), |
| 137 | ); |
| 138 | try std.testing.expectEqualSlices( |
| 139 | Weight, |
| 140 | &.{.rangeAtMost(u4, 0, 15, 1)}, |
| 141 | baselineWeights(packed struct(u4) { _: u4 }), |
| 142 | ); |
| 143 | try std.testing.expectEqualSlices( |
| 144 | Weight, |
| 145 | &.{.rangeAtMost(u4, 0, 15, 1)}, |
| 146 | baselineWeights(packed union { _: u4 }), |
| 147 | ); |
| 148 | try std.testing.expectEqualSlices( |
| 149 | Weight, |
| 150 | &.{.rangeAtMost(u4, 0, 15, 1)}, |
| 151 | baselineWeights(enum(u4) { _ }), |
| 152 | ); |
| 153 | try std.testing.expectEqualSlices(Weight, &.{ |
| 154 | .rangeAtMost(u4, 0, 1, 1), |
| 155 | .value(u4, 3, 1), |
| 156 | .value(u4, 5, 1), |
| 157 | .rangeAtMost(u4, 8, 10, 1), |
| 158 | }, baselineWeights(enum(u4) { |
| 159 | a = 1, |
| 160 | b = 5, |
| 161 | c = 8, |
| 162 | d = 3, |
| 163 | e = 0, |
| 164 | f = 9, |
| 165 | g = 10, |
| 166 | })); |
| 167 | } |
| 168 | |
| 169 | fn valueFromInt(T: anytype, int: Backing(T)) T { |
| 170 | @disableInstrumentation(); |
| 171 | return switch (@typeInfo(T)) { |
| 172 | .@"enum" => @fromBackingInt(@intCast(int)), |
| 173 | else => @bitCast(int), |
| 174 | }; |
| 175 | } |
| 176 | |
| 177 | fn checkWeights(weights: []const Weight, max_incl: u64) void { |
| 178 | @disableInstrumentation(); |
| 179 | const w0 = weights[0]; // Sum of weights is zero |
| 180 | assert(w0.weight != 0); |
| 181 | assert(w0.max <= max_incl); |
| 182 | |
| 183 | var incl_sum: u64 = (w0.max - w0.min) * w0.weight + (w0.weight - 1); // Sum of weights greater than 2^64 |
| 184 | for (weights[1..]) |w| { |
| 185 | assert(w.weight != 0); |
| 186 | assert(w.max <= max_incl); |
| 187 | // This addition will not overflow except with an illegal combination of weights since |
| 188 | // the exclusive sum must be at least one so a span of all values is impossible. |
| 189 | incl_sum += (w.max - w.min + 1) * w.weight; // Sum of weights greater than 2^64 |
| 190 | } |
| 191 | } |
| 192 | |
| 193 | // `inline` to propogate callee's unique return address |
| 194 | inline fn firstHash() u32 { |
| 195 | return @truncate(std.hash.int(@returnAddress())); |
| 196 | } |
| 197 | |
| 198 | // `noinline` to capture a unique return address |
| 199 | pub noinline fn value(s: *Smith, T: type) T { |
| 200 | @disableInstrumentation(); |
| 201 | return s.valueWithHash(T, firstHash()); |
| 202 | } |
| 203 | |
| 204 | // `noinline` to capture a unique return address |
| 205 | pub noinline fn valueWeighted(s: *Smith, T: type, weights: []const Weight) T { |
| 206 | @disableInstrumentation(); |
| 207 | return s.valueWeightedWithHash(T, weights, firstHash()); |
| 208 | } |
| 209 | |
| 210 | // `noinline` to capture a unique return address |
| 211 | pub noinline fn valueRangeAtMost(s: *Smith, T: type, at_least: T, at_most: T) T { |
| 212 | @disableInstrumentation(); |
| 213 | return s.valueRangeAtMostWithHash(T, at_least, at_most, firstHash()); |
| 214 | } |
| 215 | |
| 216 | // `noinline` to capture a unique return address |
| 217 | pub noinline fn valueRangeLessThan(s: *Smith, T: type, at_least: T, less_than: T) T { |
| 218 | @disableInstrumentation(); |
| 219 | return s.valueRangeLessThanWithHash(T, at_least, less_than, firstHash()); |
| 220 | } |
| 221 | |
| 222 | /// It is asserted `len` is nonzero. |
| 223 | /// It is asserted `len` fits within 64 bits. |
| 224 | // |
| 225 | // `noinline` to capture a unique return address |
| 226 | pub noinline fn index(s: *Smith, len: usize) usize { |
| 227 | @disableInstrumentation(); |
| 228 | return s.indexWithHash(len, firstHash()); |
| 229 | } |
| 230 | |
| 231 | /// It is asserted that the weight of `false` is non-zero. |
| 232 | /// It is asserted that the weight of `true` is non-zero. |
| 233 | // |
| 234 | // `noinline` to capture a unique return address |
| 235 | pub noinline fn boolWeighted(s: *Smith, false_weight: u64, true_weight: u64) bool { |
| 236 | @disableInstrumentation(); |
| 237 | return s.boolWeightedWithHash(false_weight, true_weight, firstHash()); |
| 238 | } |
| 239 | |
| 240 | /// This is similar to `value(bool)` however it is gauraunteed to eventually |
| 241 | /// return `true` and provides the fuzzer with an extra hint about the data. |
| 242 | // |
| 243 | // `noinline` to capture a unique return address |
| 244 | pub noinline fn eos(s: *Smith) bool { |
| 245 | @disableInstrumentation(); |
| 246 | return s.eosWithHash(firstHash()); |
| 247 | } |
| 248 | |
| 249 | /// This is similar to `value(bool)` however it is gauraunteed to eventually |
| 250 | /// return `true` and provides the fuzzer with an extra hint about the data. |
| 251 | /// |
| 252 | /// It is asserted that the weight of `true` is non-zero. |
| 253 | // |
| 254 | // `noinline` to capture a unique return address |
| 255 | pub noinline fn eosWeighted(s: *Smith, weights: []const Weight) bool { |
| 256 | @disableInstrumentation(); |
| 257 | return s.eosWeightedWithHash(weights, firstHash()); |
| 258 | } |
| 259 | |
| 260 | /// This is similar to `value(bool)` however it is gauraunteed to eventually |
| 261 | /// return `true` and provides the fuzzer with an extra hint about the data. |
| 262 | /// |
| 263 | /// It is asserted that the weight of `false` is non-zero. |
| 264 | /// It is asserted that the weight of `true` is non-zero. |
| 265 | // |
| 266 | // `noinline` to capture a unique return address |
| 267 | pub noinline fn eosWeightedSimple(s: *Smith, false_weight: u64, true_weight: u64) bool { |
| 268 | @disableInstrumentation(); |
| 269 | return s.eosWeightedSimpleWithHash(false_weight, true_weight, firstHash()); |
| 270 | } |
| 271 | |
| 272 | // `noinline` to capture a unique return address |
| 273 | pub noinline fn bytes(s: *Smith, out: []u8) void { |
| 274 | @disableInstrumentation(); |
| 275 | return s.bytesWithHash(out, firstHash()); |
| 276 | } |
| 277 | |
| 278 | // `noinline` to capture a unique return address |
| 279 | pub noinline fn bytesWeighted(s: *Smith, out: []u8, weights: []const Weight) void { |
| 280 | @disableInstrumentation(); |
| 281 | return s.bytesWeightedWithHash(out, weights, firstHash()); |
| 282 | } |
| 283 | |
| 284 | /// Returns the length of the filled slice |
| 285 | /// |
| 286 | /// It is asserted that `buf.len` fits within a u32 |
| 287 | // `noinline` to capture a unique return address |
| 288 | pub noinline fn slice(s: *Smith, buf: []u8) u32 { |
| 289 | @disableInstrumentation(); |
| 290 | return s.sliceWithHash(buf, firstHash()); |
| 291 | } |
| 292 | |
| 293 | /// Returns the length of the filled slice |
| 294 | /// |
| 295 | /// It is asserted that `buf.len` fits within a u32 |
| 296 | // |
| 297 | // `noinline` to capture a unique return address |
| 298 | pub noinline fn sliceWeightedBytes(s: *Smith, buf: []u8, byte_weights: []const Weight) u32 { |
| 299 | @disableInstrumentation(); |
| 300 | return s.sliceWeightedBytesWithHash(buf, byte_weights, firstHash()); |
| 301 | } |
| 302 | |
| 303 | /// Returns the length of the filled slice |
| 304 | /// |
| 305 | /// It is asserted that `buf.len` fits within a u32 |
| 306 | // |
| 307 | // `noinline` to capture a unique return address |
| 308 | pub noinline fn sliceWeighted( |
| 309 | s: *Smith, |
| 310 | buf: []u8, |
| 311 | len_weights: []const Weight, |
| 312 | byte_weights: []const Weight, |
| 313 | ) u32 { |
| 314 | @disableInstrumentation(); |
| 315 | return s.sliceWeightedWithHash(buf, len_weights, byte_weights, firstHash()); |
| 316 | } |
| 317 | |
| 318 | fn weightsContain(int: u64, weights: []const Weight) bool { |
| 319 | @disableInstrumentation(); |
| 320 | var contains: bool = false; |
| 321 | for (weights) |w| { |
| 322 | contains |= w.min <= int and int <= w.max; |
| 323 | } |
| 324 | return contains; |
| 325 | } |
| 326 | |
| 327 | /// Asserts `T` can be a member of a packed type |
| 328 | // |
| 329 | // `inline` to propogate the `comptime`ness of the result |
| 330 | inline fn allBitPatternsValid(T: type) bool { |
| 331 | return comptime switch (@typeInfo(T)) { |
| 332 | .void, .bool, .int, .float => true, |
| 333 | inline .@"struct", .@"union" => |c| c.layout == .@"packed" and for (c.field_types) |f_type| { |
| 334 | if (!allBitPatternsValid(f_type)) break false; |
| 335 | } else true, |
| 336 | .@"enum" => |e| e.mode == .nonexhaustive, |
| 337 | else => unreachable, |
| 338 | }; |
| 339 | } |
| 340 | |
| 341 | test allBitPatternsValid { |
| 342 | try std.testing.expect(allBitPatternsValid(packed struct { |
| 343 | a: void, |
| 344 | b: u8, |
| 345 | c: f16, |
| 346 | d: packed union { |
| 347 | a: u16, |
| 348 | b: i16, |
| 349 | c: f16, |
| 350 | }, |
| 351 | e: enum(u4) { _ }, |
| 352 | })); |
| 353 | try std.testing.expect(!allBitPatternsValid(packed union { |
| 354 | a: i4, |
| 355 | b: enum(u4) { a }, |
| 356 | })); |
| 357 | } |
| 358 | |
| 359 | fn UnionTagWithoutUninitializable(T: type) type { |
| 360 | const u = @typeInfo(T).@"union"; |
| 361 | const Tag = u.tag_type orelse @compileError("union must have tag"); |
| 362 | const e = @typeInfo(Tag).@"enum"; |
| 363 | var field_names: [e.field_names.len][]const u8 = undefined; |
| 364 | var field_values: [e.field_names.len]e.tag_type = undefined; |
| 365 | var n_fields = 0; |
| 366 | for (u.field_names, u.field_types) |f_name, f_type| { |
| 367 | switch (f_type) { |
| 368 | noreturn => continue, |
| 369 | else => {}, |
| 370 | } |
| 371 | field_names[n_fields] = f_name; |
| 372 | field_values[n_fields] = @backingInt(@field(Tag, f_name)); |
| 373 | n_fields += 1; |
| 374 | } |
| 375 | return @Enum(e.tag_type, .exhaustive, field_names[0..n_fields], field_values[0..n_fields]); |
| 376 | } |
| 377 | |
| 378 | pub fn valueWithHash(s: *Smith, T: type, hash: u32) T { |
| 379 | @disableInstrumentation(); |
| 380 | return switch (@typeInfo(T)) { |
| 381 | .void => {}, |
| 382 | .bool, .int, .float => full: { |
| 383 | var int: Backing(T) = 0; |
| 384 | comptime var biti = 0; |
| 385 | var rhash = hash; // 'running' hash |
| 386 | inline while (biti < @bitSizeOf(T)) { |
| 387 | const n = @min(@bitSizeOf(T) - biti, 64); |
| 388 | const P = @Int(.unsigned, n); |
| 389 | int |= @as( |
| 390 | @TypeOf(int), |
| 391 | s.valueWeightedWithHash(P, baselineWeights(P), rhash), |
| 392 | ) << biti; |
| 393 | biti += n; |
| 394 | rhash = std.hash.int(rhash); |
| 395 | } |
| 396 | break :full @bitCast(int); |
| 397 | }, |
| 398 | .@"enum" => |e| if (e.mode == .exhaustive) v: { |
| 399 | if (@bitSizeOf(e.tag_type) <= 64) { |
| 400 | break :v s.valueWeightedWithHash(T, baselineWeights(T), hash); |
| 401 | } |
| 402 | break :v std.enums.fromInt(T, s.valueWithHash(e.tag_type, hash)) orelse |
| 403 | @fromBackingInt(@intCast(e.field_values[0])); |
| 404 | } else @fromBackingInt(@intCast(s.valueWithHash(e.tag_type, hash))), |
| 405 | .optional => |o| if (s.valueWithHash(bool, hash)) |
| 406 | null |
| 407 | else |
| 408 | s.valueWithHash(o.child, std.hash.int(hash)), |
| 409 | inline .array, .vector => |a| arr: { |
| 410 | var arr: [a.len]a.child = undefined; // `T` cannot be used due to the vector case |
| 411 | if (a.child != u8) { |
| 412 | for (&arr) |*v| { |
| 413 | v.* = s.valueWithHash(a.child, hash); |
| 414 | } |
| 415 | } else { |
| 416 | s.bytesWithHash(&arr, hash); |
| 417 | } |
| 418 | break :arr arr; |
| 419 | }, |
| 420 | .@"struct" => |st| if (!allBitPatternsValid(T)) v: { |
| 421 | var v: T = undefined; |
| 422 | var rhash = hash; |
| 423 | inline for (st.field_names, st.field_types) |f_name, f_type| { |
| 424 | // rhash is incremented in the call so our rhash state is not reused (e.g. with |
| 425 | // two nested structs. note that xor cannot work for this case as the bit would |
| 426 | // be flipped back here) |
| 427 | @field(v, f_name) = s.valueWithHash(f_type, rhash +% 1); |
| 428 | rhash = std.hash.int(rhash); |
| 429 | } |
| 430 | break :v v; |
| 431 | } else @bitCast(s.valueWithHash(st.backing_integer.?, hash)), |
| 432 | .@"union" => if (!allBitPatternsValid(T)) |
| 433 | switch (s.valueWithHash( |
| 434 | UnionTagWithoutUninitializable(T), |
| 435 | // hash is incremented in the call so our hash state is not reused for below |
| 436 | std.hash.int(hash +% 1), |
| 437 | )) { |
| 438 | inline else => |t| @unionInit( |
| 439 | T, |
| 440 | @tagName(t), |
| 441 | s.valueWithHash(@FieldType(T, @tagName(t)), hash), |
| 442 | ), |
| 443 | } |
| 444 | else |
| 445 | @bitCast(s.valueWithHash(Backing(T), hash)), |
| 446 | else => @compileError("unexpected type '" ++ @typeName(T) ++ "'"), |
| 447 | }; |
| 448 | } |
| 449 | |
| 450 | pub fn valueWeightedWithHash(s: *Smith, T: type, weights: []const Weight, hash: u32) T { |
| 451 | @disableInstrumentation(); |
| 452 | checkWeights(weights, (1 << @bitSizeOf(T)) - 1); |
| 453 | return valueFromInt(T, @intCast(s.valueWeightedWithHashInner(weights, hash))); |
| 454 | } |
| 455 | |
| 456 | fn valueWeightedWithHashInner(s: *Smith, weights: []const Weight, hash: u32) u64 { |
| 457 | @disableInstrumentation(); |
| 458 | return if (s.in) |*in| int: { |
| 459 | if (in.len < 8) { |
| 460 | @branchHint(.unlikely); |
| 461 | in.* = &.{}; |
| 462 | break :int weights[0].min; |
| 463 | } |
| 464 | const int = std.mem.readInt(u64, in.*[0..8], .little); |
| 465 | in.* = in.*[8..]; |
| 466 | break :int if (weightsContain(int, weights)) int else weights[0].min; |
| 467 | } else if (builtin.fuzz) int: { |
| 468 | @branchHint(.likely); |
| 469 | break :int fuzz_abi.fuzzer_int(intUid(hash), .fromSlice(weights)); |
| 470 | } else unreachable; |
| 471 | } |
| 472 | |
| 473 | pub fn valueRangeAtMostWithHash(s: *Smith, T: type, at_least: T, at_most: T, hash: u32) T { |
| 474 | @disableInstrumentation(); |
| 475 | if (@typeInfo(T) == .int and @typeInfo(T).int.signedness == .signed) { |
| 476 | return fromExcessK(T, s.valueRangeAtMostWithHash( |
| 477 | Backing(T), |
| 478 | toExcessK(T, at_least), |
| 479 | toExcessK(T, at_most), |
| 480 | hash, |
| 481 | )); |
| 482 | } |
| 483 | return s.valueWeightedWithHash(T, &.{.rangeAtMost(T, at_least, at_most, 1)}, hash); |
| 484 | } |
| 485 | |
| 486 | pub fn valueRangeLessThanWithHash(s: *Smith, T: type, at_least: T, less_than: T, hash: u32) T { |
| 487 | @disableInstrumentation(); |
| 488 | if (@typeInfo(T) == .int and @typeInfo(T).int.signedness == .signed) { |
| 489 | return fromExcessK(T, s.valueRangeLessThanWithHash( |
| 490 | Backing(T), |
| 491 | toExcessK(T, at_least), |
| 492 | toExcessK(T, less_than), |
| 493 | hash, |
| 494 | )); |
| 495 | } |
| 496 | return s.valueWeightedWithHash(T, &.{.rangeLessThan(T, at_least, less_than, 1)}, hash); |
| 497 | } |
| 498 | |
| 499 | /// It is asserted `len` is nonzero. |
| 500 | /// It is asserted `len` fits within 64 bits. |
| 501 | pub fn indexWithHash(s: *Smith, len: usize, hash: u32) usize { |
| 502 | @disableInstrumentation(); |
| 503 | assert(len != 0); |
| 504 | return @intCast(s.valueWeightedWithHash(u64, &.{.rangeLessThan(u64, 0, @intCast(len), 1)}, hash)); |
| 505 | } |
| 506 | |
| 507 | /// It is asserted that the weight of `false` is non-zero. |
| 508 | /// It is asserted that the weight of `true` is non-zero. |
| 509 | pub fn boolWeightedWithHash(s: *Smith, false_weight: u64, true_weight: u64, hash: u32) bool { |
| 510 | @disableInstrumentation(); |
| 511 | return s.valueWeightedWithHash(bool, &.{ |
| 512 | .value(bool, false, false_weight), |
| 513 | .value(bool, true, true_weight), |
| 514 | }, hash); |
| 515 | } |
| 516 | |
| 517 | /// This is similar to `value(bool)` however it is gauraunteed to eventually |
| 518 | /// return `true` and provides the fuzzer with an extra hint about the data. |
| 519 | pub fn eosWithHash(s: *Smith, hash: u32) bool { |
| 520 | @disableInstrumentation(); |
| 521 | return s.eosWeightedWithHash(baselineWeights(bool), hash); |
| 522 | } |
| 523 | |
| 524 | /// This is similar to `value(bool)` however it is gauraunteed to eventually |
| 525 | /// return `true` and provides the fuzzer with an extra hint about the data. |
| 526 | /// |
| 527 | /// It is asserted that the weight of `true` is non-zero. |
| 528 | pub fn eosWeightedWithHash(s: *Smith, weights: []const Weight, hash: u32) bool { |
| 529 | @disableInstrumentation(); |
| 530 | checkWeights(weights, 1); |
| 531 | for (weights) |w| (if (w.max == 1) break) else unreachable; // `true` must have non-zero weight |
| 532 | |
| 533 | if (s.in) |*in| { |
| 534 | if (in.len == 0) { |
| 535 | @branchHint(.unlikely); |
| 536 | return true; |
| 537 | } |
| 538 | const eos_val = in.*[0] != 0; |
| 539 | in.* = in.*[1..]; |
| 540 | return eos_val or b: { |
| 541 | var only_true: bool = true; |
| 542 | for (weights) |w| { |
| 543 | only_true &= @as(u1, @intCast(w.min)) == 1; |
| 544 | } |
| 545 | break :b only_true; |
| 546 | }; |
| 547 | } else if (builtin.fuzz) { |
| 548 | @branchHint(.likely); |
| 549 | return fuzz_abi.fuzzer_eos(intUid(hash), .fromSlice(weights)); |
| 550 | } else unreachable; |
| 551 | } |
| 552 | |
| 553 | /// This is similar to `value(bool)` however it is gauraunteed to eventually |
| 554 | /// return `true` and provides the fuzzer with an extra hint about the data. |
| 555 | /// |
| 556 | /// It is asserted that the weight of `false` is non-zero. |
| 557 | /// It is asserted that the weight of `true` is non-zero. |
| 558 | pub fn eosWeightedSimpleWithHash(s: *Smith, false_weight: u64, true_weight: u64, hash: u32) bool { |
| 559 | @disableInstrumentation(); |
| 560 | return s.eosWeightedWithHash(&.{ |
| 561 | .value(bool, false, false_weight), |
| 562 | .value(bool, true, true_weight), |
| 563 | }, hash); |
| 564 | } |
| 565 | |
| 566 | pub fn bytesWithHash(s: *Smith, out: []u8, hash: u32) void { |
| 567 | @disableInstrumentation(); |
| 568 | return s.bytesWeightedWithHash(out, baselineWeights(u8), hash); |
| 569 | } |
| 570 | |
| 571 | pub fn bytesWeightedWithHash(s: *Smith, out: []u8, weights: []const Weight, hash: u32) void { |
| 572 | @disableInstrumentation(); |
| 573 | checkWeights(weights, 255); |
| 574 | |
| 575 | if (s.in) |*in| { |
| 576 | var present_weights: [256]bool = @splat(false); |
| 577 | for (weights) |w| { |
| 578 | @memset(present_weights[@intCast(w.min)..@intCast(w.max + 1)], true); |
| 579 | } |
| 580 | const default: u8 = @intCast(weights[0].min); |
| 581 | |
| 582 | const copy_len = @min(out.len, in.len); |
| 583 | for (in.*[0..copy_len], out[0..copy_len]) |i, *o| { |
| 584 | o.* = if (present_weights[i]) i else default; |
| 585 | } |
| 586 | in.* = in.*[copy_len..]; |
| 587 | @memset(out[copy_len..], default); |
| 588 | } else if (builtin.fuzz) { |
| 589 | @branchHint(.likely); |
| 590 | fuzz_abi.fuzzer_bytes(bytesUid(hash), .fromSlice(out), .fromSlice(weights)); |
| 591 | } else unreachable; |
| 592 | } |
| 593 | |
| 594 | /// Returns the length of the filled slice |
| 595 | /// |
| 596 | /// It is asserted that `buf.len` fits within a u32 |
| 597 | pub fn sliceWithHash(s: *Smith, buf: []u8, hash: u32) u32 { |
| 598 | @disableInstrumentation(); |
| 599 | return s.sliceWeightedBytesWithHash(buf, baselineWeights(u8), hash); |
| 600 | } |
| 601 | |
| 602 | /// Returns the length of the filled slice |
| 603 | /// |
| 604 | /// It is asserted that `buf.len` fits within a u32 |
| 605 | pub fn sliceWeightedBytesWithHash( |
| 606 | s: *Smith, |
| 607 | buf: []u8, |
| 608 | byte_weights: []const Weight, |
| 609 | hash: u32, |
| 610 | ) u32 { |
| 611 | @disableInstrumentation(); |
| 612 | return s.sliceWeightedWithHash( |
| 613 | buf, |
| 614 | &.{.rangeAtMost(u32, 0, @intCast(buf.len), 1)}, |
| 615 | byte_weights, |
| 616 | hash, |
| 617 | ); |
| 618 | } |
| 619 | |
| 620 | /// Returns the length of the filled slice |
| 621 | /// |
| 622 | /// It is asserted that `buf.len` fits within a u32 |
| 623 | pub fn sliceWeightedWithHash( |
| 624 | s: *Smith, |
| 625 | buf: []u8, |
| 626 | len_weights: []const Weight, |
| 627 | byte_weights: []const Weight, |
| 628 | hash: u32, |
| 629 | ) u32 { |
| 630 | @disableInstrumentation(); |
| 631 | checkWeights(byte_weights, 255); |
| 632 | checkWeights(len_weights, @as(u32, @intCast(buf.len))); |
| 633 | |
| 634 | if (s.in) |*in| { |
| 635 | const in_len = len: { |
| 636 | if (in.len < 4) { |
| 637 | @branchHint(.unlikely); |
| 638 | in.* = &.{}; |
| 639 | break :len 0; |
| 640 | } |
| 641 | const len = std.mem.readInt(u32, in.*[0..4], .little); |
| 642 | in.* = in.*[4..]; |
| 643 | break :len @min(len, in.len); |
| 644 | }; |
| 645 | const out_len: u32 = if (weightsContain(in_len, len_weights)) |
| 646 | in_len |
| 647 | else |
| 648 | @intCast(len_weights[0].min); |
| 649 | |
| 650 | var present_weights: [256]bool = @splat(false); |
| 651 | for (byte_weights) |w| { |
| 652 | @memset(present_weights[@intCast(w.min)..@intCast(w.max + 1)], true); |
| 653 | } |
| 654 | const default: u8 = @intCast(byte_weights[0].min); |
| 655 | |
| 656 | const copy_len = @min(out_len, in_len); |
| 657 | for (in.*[0..copy_len], buf[0..copy_len]) |i, *o| { |
| 658 | o.* = if (present_weights[i]) i else default; |
| 659 | } |
| 660 | in.* = in.*[in_len..]; |
| 661 | @memset(buf[copy_len..], default); |
| 662 | return out_len; |
| 663 | } else if (builtin.fuzz) { |
| 664 | @branchHint(.likely); |
| 665 | return fuzz_abi.fuzzer_slice( |
| 666 | bytesUid(hash), |
| 667 | .fromSlice(buf), |
| 668 | .fromSlice(len_weights), |
| 669 | .fromSlice(byte_weights), |
| 670 | ); |
| 671 | } else unreachable; |
| 672 | } |
| 673 | |
| 674 | fn constructInput(comptime values: []const union(enum) { |
| 675 | eos: bool, |
| 676 | int: u64, |
| 677 | bytes: []const u8, |
| 678 | slice: []const u8, |
| 679 | }) []const u8 { |
| 680 | const result = comptime result: { |
| 681 | var result: [ |
| 682 | len: { |
| 683 | var len = 0; |
| 684 | for (values) |v| len += switch (v) { |
| 685 | .eos => 1, |
| 686 | .int => 8, |
| 687 | .bytes => |b| b.len, |
| 688 | .slice => |s| 4 + s.len, |
| 689 | }; |
| 690 | break :len len; |
| 691 | } |
| 692 | ]u8 = undefined; |
| 693 | var w: std.Io.Writer = .fixed(&result); |
| 694 | |
| 695 | for (values) |v| switch (v) { |
| 696 | .eos => |e| w.writeByte(@intFromBool(e)) catch unreachable, |
| 697 | .int => |i| w.writeInt(u64, i, .little) catch unreachable, |
| 698 | .bytes => |b| w.writeAll(b) catch unreachable, |
| 699 | .slice => |s| { |
| 700 | w.writeInt(u32, @intCast(s.len), .little) catch unreachable; |
| 701 | w.writeAll(s) catch unreachable; |
| 702 | }, |
| 703 | }; |
| 704 | |
| 705 | break :result result; |
| 706 | }; |
| 707 | return &result; |
| 708 | } |
| 709 | |
| 710 | test value { |
| 711 | if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; |
| 712 | |
| 713 | const S = struct { |
| 714 | v: void = {}, |
| 715 | b: bool = true, |
| 716 | ih: u16 = 123, |
| 717 | iq: u64 = 55555, |
| 718 | io: u128 = (1 << 80) | (1 << 23), |
| 719 | fd: f64 = std.math.pi, |
| 720 | ft: f80 = std.math.e, |
| 721 | eh: enum(u16) { a, _ } = @fromBackingInt(@intCast(999)), |
| 722 | eo: enum(u128) { a, b, _ } = .b, |
| 723 | aw: [3]u32 = .{ 1 << 30, 1 << 20, 1 << 10 }, |
| 724 | vw: @Vector(3, u32) = .{ 1 << 10, 1 << 20, 1 << 30 }, |
| 725 | ab: [3]u8 = .{ 55, 33, 88 }, |
| 726 | vb: @Vector(3, u8) = .{ 22, 44, 99 }, |
| 727 | s: struct { q: u64 } = .{ .q = 1 }, |
| 728 | sz: struct {} = .{}, |
| 729 | sp: packed struct(u8) { a: u5, b: u3 } = .{ .a = 31, .b = 3 }, |
| 730 | si: packed struct(u8) { a: u5, b: enum(u3) { a, b } } = .{ .a = 15, .b = .b }, |
| 731 | u: union(enum(u2)) { |
| 732 | a: u64, |
| 733 | b: u64, |
| 734 | c: noreturn, |
| 735 | } = .{ .b = 777777 }, |
| 736 | up: packed union { |
| 737 | a: u16, |
| 738 | b: f16, |
| 739 | } = .{ .b = std.math.phi }, |
| 740 | |
| 741 | invalid: struct { |
| 742 | ib: u8 = 0, |
| 743 | eb: enum(u8) { a, b } = .a, |
| 744 | eo: enum(u128) { a, b } = .a, |
| 745 | u: union(enum(u1)) { a: noreturn, b: void } = .{ .b = {} }, |
| 746 | } = .{}, |
| 747 | }; |
| 748 | const s: S = .{}; |
| 749 | const ft_bits: u80 = @bitCast(s.ft); |
| 750 | const eo_bits = @backingInt(s.eo); |
| 751 | |
| 752 | var smith: Smith = .{ |
| 753 | .in = constructInput(&.{ |
| 754 | // v |
| 755 | .{ .int = @intFromBool(s.b) }, // b |
| 756 | .{ .int = s.ih }, // ih |
| 757 | .{ .int = s.iq }, // iq |
| 758 | .{ .int = @truncate(s.io) }, .{ .int = @intCast(s.io >> 64) }, // io |
| 759 | .{ .int = @bitCast(s.fd) }, // fd |
| 760 | .{ .int = @truncate(ft_bits) }, .{ .int = @intCast(ft_bits >> 64) }, // ft |
| 761 | .{ .int = @backingInt(s.eh) }, // eh |
| 762 | .{ .int = @truncate(eo_bits) }, .{ .int = @intCast(eo_bits >> 64) }, // eo |
| 763 | .{ .int = s.aw[0] }, .{ .int = s.aw[1] }, .{ .int = s.aw[2] }, // aw |
| 764 | .{ .int = s.vw[0] }, .{ .int = s.vw[1] }, .{ .int = s.vw[2] }, // vw |
| 765 | .{ .bytes = &s.ab }, // ab |
| 766 | .{ .bytes = &@as([3]u8, s.vb) }, // vb |
| 767 | .{ .int = s.s.q }, // s.q |
| 768 | //sz |
| 769 | .{ .int = @as(u8, @bitCast(s.sp)) }, // sp |
| 770 | .{ .int = s.si.a }, .{ .int = @backingInt(s.si.b) }, // si |
| 771 | .{ .int = @backingInt(s.u) }, .{ .int = s.u.b }, // u |
| 772 | .{ .int = @as(u16, @bitCast(s.up)) }, // up |
| 773 | // invalid values |
| 774 | .{ .int = 555 }, // invalid.ib |
| 775 | .{ .int = 123 }, // invalid.eb |
| 776 | .{ .int = 0 }, .{ .int = 1 }, // invalid.eo |
| 777 | .{ .int = 0 }, // invalid.u |
| 778 | }), |
| 779 | }; |
| 780 | |
| 781 | try std.testing.expectEqual(s, smith.value(S)); |
| 782 | } |
| 783 | |
| 784 | test valueWeighted { |
| 785 | var smith: Smith = .{ |
| 786 | .in = constructInput(&.{ |
| 787 | .{ .int = 200 }, |
| 788 | .{ .int = 200 }, |
| 789 | .{ .int = 300 }, |
| 790 | .{ .int = 400 }, |
| 791 | }), |
| 792 | }; |
| 793 | |
| 794 | try std.testing.expectEqual(200, smith.valueWeighted(u8, &.{.rangeAtMost(u8, 50, 200, 1)})); |
| 795 | try std.testing.expectEqual(50, smith.valueWeighted(u8, &.{.rangeLessThan(u8, 50, 200, 1)})); |
| 796 | const E = enum(u64) { a = 100, b = 200, c = 300 }; |
| 797 | try std.testing.expectEqual(E.c, smith.valueWeighted(E, baselineWeights(E))); |
| 798 | try std.testing.expectEqual(E.a, smith.valueWeighted(E, baselineWeights(E))); |
| 799 | try std.testing.expectEqual(12345, smith.valueWeighted(u64, &.{.value(u64, 12345, 1)})); |
| 800 | } |
| 801 | |
| 802 | test valueRangeAtMost { |
| 803 | var smith: Smith = .{ |
| 804 | .in = constructInput(&.{ |
| 805 | .{ .int = 100 }, |
| 806 | .{ .int = 100 }, |
| 807 | .{ .int = 200 }, |
| 808 | .{ .int = 100 }, |
| 809 | .{ .int = 200 }, |
| 810 | .{ .int = 0 }, |
| 811 | }), |
| 812 | }; |
| 813 | try std.testing.expectEqual(100, smith.valueRangeAtMost(u8, 0, 250)); |
| 814 | try std.testing.expectEqual(100, smith.valueRangeAtMost(u8, 100, 100)); |
| 815 | try std.testing.expectEqual(0, smith.valueRangeAtMost(u8, 0, 100)); |
| 816 | try std.testing.expectEqual(100 - 128, smith.valueRangeAtMost(i8, -100, 100)); |
| 817 | try std.testing.expectEqual(200 - 128, smith.valueRangeAtMost(i8, -100, 100)); |
| 818 | try std.testing.expectEqual(-100, smith.valueRangeAtMost(i8, -100, 100)); |
| 819 | } |
| 820 | |
| 821 | test valueRangeLessThan { |
| 822 | var smith: Smith = .{ |
| 823 | .in = constructInput(&.{ |
| 824 | .{ .int = 100 }, |
| 825 | .{ .int = 100 }, |
| 826 | .{ .int = 100 }, |
| 827 | .{ .int = 100 + 128 }, |
| 828 | }), |
| 829 | }; |
| 830 | try std.testing.expectEqual(100, smith.valueRangeLessThan(u8, 0, 250)); |
| 831 | try std.testing.expectEqual(0, smith.valueRangeLessThan(u8, 0, 100)); |
| 832 | try std.testing.expectEqual(100 - 128, smith.valueRangeLessThan(i8, -100, 100)); |
| 833 | try std.testing.expectEqual(-100, smith.valueRangeLessThan(i8, -100, 100)); |
| 834 | } |
| 835 | |
| 836 | test eos { |
| 837 | var smith: Smith = .{ |
| 838 | .in = constructInput(&.{ |
| 839 | .{ .eos = false }, |
| 840 | .{ .eos = true }, |
| 841 | }), |
| 842 | }; |
| 843 | try std.testing.expect(!smith.eos()); |
| 844 | try std.testing.expect(smith.eos()); |
| 845 | try std.testing.expect(smith.eos()); |
| 846 | } |
| 847 | |
| 848 | test eosWeighted { |
| 849 | var smith: Smith = .{ .in = constructInput(&.{.{ .eos = false }}) }; |
| 850 | try std.testing.expect(smith.eosWeighted(&.{.value(bool, true, std.math.maxInt(u64))})); |
| 851 | } |
| 852 | |
| 853 | test bytes { |
| 854 | var smith: Smith = .{ .in = constructInput(&.{ |
| 855 | .{ .bytes = "testing!" }, |
| 856 | .{ .bytes = "ab" }, |
| 857 | }) }; |
| 858 | var buf: [8]u8 = undefined; |
| 859 | |
| 860 | smith.bytes(&buf); |
| 861 | try std.testing.expectEqualSlices(u8, "testing!", &buf); |
| 862 | smith.bytes(buf[0..0]); |
| 863 | smith.bytes(buf[0..3]); |
| 864 | try std.testing.expectEqualSlices(u8, "ab\x00", buf[0..3]); |
| 865 | } |
| 866 | |
| 867 | test bytesWeighted { |
| 868 | var smith: Smith = .{ .in = constructInput(&.{ |
| 869 | .{ .bytes = "testing!" }, |
| 870 | .{ .bytes = "ab" }, |
| 871 | }) }; |
| 872 | const weights: []const Weight = &.{.rangeAtMost(u8, 'a', 'z', 1)}; |
| 873 | var buf: [8]u8 = undefined; |
| 874 | |
| 875 | smith.bytesWeighted(&buf, weights); |
| 876 | try std.testing.expectEqualSlices(u8, "testinga", &buf); |
| 877 | smith.bytesWeighted(buf[0..0], weights); |
| 878 | smith.bytesWeighted(buf[0..3], weights); |
| 879 | try std.testing.expectEqualSlices(u8, "aba", buf[0..3]); |
| 880 | } |
| 881 | |
| 882 | test slice { |
| 883 | var smith: Smith = .{ |
| 884 | .in = constructInput(&.{ |
| 885 | .{ .slice = "testing!" }, |
| 886 | .{ .slice = "" }, |
| 887 | .{ .slice = "ab" }, |
| 888 | .{ .bytes = std.mem.asBytes(&std.mem.nativeToLittle(u32, 4)) }, // length past end |
| 889 | }), |
| 890 | }; |
| 891 | var buf: [8]u8 = undefined; |
| 892 | |
| 893 | try std.testing.expectEqualSlices(u8, "testing!", buf[0..smith.slice(&buf)]); |
| 894 | try std.testing.expectEqualSlices(u8, "", buf[0..smith.slice(&buf)]); |
| 895 | try std.testing.expectEqualSlices(u8, "ab", buf[0..smith.slice(&buf)]); |
| 896 | try std.testing.expectEqualSlices(u8, "", buf[0..smith.slice(&buf)]); |
| 897 | } |
| 898 | |
| 899 | test sliceWeightedBytes { |
| 900 | const weights: []const Weight = &.{.rangeAtMost(u8, 'a', 'z', 1)}; |
| 901 | var smith: Smith = .{ .in = constructInput(&.{ |
| 902 | .{ .slice = "testing!" }, |
| 903 | }) }; |
| 904 | var buf: [8]u8 = undefined; |
| 905 | |
| 906 | try std.testing.expectEqualSlices( |
| 907 | u8, |
| 908 | "testinga", |
| 909 | buf[0..smith.sliceWeightedBytes(&buf, weights)], |
| 910 | ); |
| 911 | try std.testing.expectEqualSlices(u8, "", buf[0..smith.sliceWeightedBytes(&buf, weights)]); |
| 912 | } |
| 913 | |
| 914 | test sliceWeighted { |
| 915 | const len_weights: []const Weight = &.{.rangeAtMost(u8, 3, 6, 1)}; |
| 916 | const weights: []const Weight = &.{.rangeAtMost(u8, 'a', 'z', 1)}; |
| 917 | var smith: Smith = .{ .in = constructInput(&.{ |
| 918 | .{ .slice = "testing!" }, |
| 919 | .{ .slice = "ing!" }, |
| 920 | .{ .slice = "ab" }, |
| 921 | }) }; |
| 922 | var buf: [8]u8 = undefined; |
| 923 | |
| 924 | try std.testing.expectEqualSlices( |
| 925 | u8, |
| 926 | "tes", |
| 927 | buf[0..smith.sliceWeighted(&buf, len_weights, weights)], |
| 928 | ); |
| 929 | try std.testing.expectEqualSlices( |
| 930 | u8, |
| 931 | "inga", |
| 932 | buf[0..smith.sliceWeighted(&buf, len_weights, weights)], |
| 933 | ); |
| 934 | try std.testing.expectEqualSlices( |
| 935 | u8, |
| 936 | "aba", |
| 937 | buf[0..smith.sliceWeighted(&buf, len_weights, weights)], |
| 938 | ); |
| 939 | try std.testing.expectEqualSlices( |
| 940 | u8, |
| 941 | "aaa", |
| 942 | buf[0..smith.sliceWeighted(&buf, len_weights, weights)], |
| 943 | ); |
| 944 | } |