1//! Used in conjuncation with `std.testing.fuzz` to generate values
2
3const builtin = @import("builtin");
4const std = @import("../std.zig");
5const assert = std.debug.assert;
6const fuzz_abi = std.Build.abi.fuzz;
7const 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.
12in: ?[]const u8,
13
14pub const Weight = fuzz_abi.Weight;
15
16fn intUid(hash: u32) fuzz_abi.Uid {
17 @disableInstrumentation();
18 return @bitCast(hash << 1);
19}
20
21fn bytesUid(hash: u32) fuzz_abi.Uid {
22 @disableInstrumentation();
23 return @bitCast(hash | 1);
24}
25
26fn Backing(T: type) type {
27 return @Int(.unsigned, @bitSizeOf(T));
28}
29
30fn toExcessK(T: type, x: T) Backing(T) {
31 return @bitCast(x -% std.math.minInt(T));
32}
33
34fn fromExcessK(T: type, x: Backing(T)) T {
35 return @as(T, @bitCast(x)) +% std.math.minInt(T);
36}
37
38const EnumField = struct {
39 name: [:0]const u8,
40 value: comptime_int,
41};
42
43fn 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
50pub 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
117test 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
169fn 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
177fn 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
194inline fn firstHash() u32 {
195 return @truncate(std.hash.int(@returnAddress()));
196}
197
198// `noinline` to capture a unique return address
199pub 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
205pub 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
211pub 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
217pub 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
226pub 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
235pub 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
244pub 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
255pub 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
267pub 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
273pub 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
279pub 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
288pub 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
298pub 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
308pub 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
318fn 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
330inline 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
341test 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
359fn 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
378pub 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
450pub 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
456fn 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
473pub 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
486pub 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.
501pub 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.
509pub 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.
519pub 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.
528pub 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.
558pub 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
566pub fn bytesWithHash(s: *Smith, out: []u8, hash: u32) void {
567 @disableInstrumentation();
568 return s.bytesWeightedWithHash(out, baselineWeights(u8), hash);
569}
570
571pub 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
597pub 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
605pub 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
623pub 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
674fn 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
710test 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
784test 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
802test 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
821test 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
836test 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
848test 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
853test 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
867test 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
882test 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
899test 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
914test 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}