@zigc/lib 0.16.0-dev.3091 → 0.16.0-dev.3128
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/c/math.zig +121 -30
- package/compiler/build_runner.zig +1 -0
- package/compiler/test_runner.zig +191 -59
- package/compiler_rt/cos.zig +141 -52
- package/compiler_rt/long_double.zig +37 -0
- package/compiler_rt/rem_pio2l.zig +173 -0
- package/compiler_rt/sin.zig +140 -55
- package/compiler_rt/sincos.zig +279 -72
- package/compiler_rt/tan.zig +118 -47
- package/compiler_rt/trig.zig +256 -6
- package/fuzzer.zig +855 -307
- package/package.json +1 -1
- package/std/Build/Fuzz.zig +6 -19
- package/std/Build/Step/Run.zig +530 -68
- package/std/Build/abi.zig +39 -7
- package/std/Build.zig +3 -0
- package/std/compress/flate/Compress.zig +3 -3
- package/std/debug/Info.zig +4 -0
- package/std/heap/ArenaAllocator.zig +145 -154
- package/std/mem/Allocator.zig +4 -5
- package/std/mem.zig +48 -0
- package/std/priority_dequeue.zig +13 -12
- package/std/priority_queue.zig +5 -4
- package/std/zig/Client.zig +8 -3
- package/std/zig/Server.zig +26 -0
- package/libc/mingw/complex/cabs.c +0 -48
- package/libc/mingw/complex/cabsf.c +0 -48
- package/libc/mingw/complex/cacos.c +0 -50
- package/libc/mingw/complex/cacosf.c +0 -50
- package/libc/mingw/complex/carg.c +0 -48
- package/libc/mingw/complex/cargf.c +0 -48
- package/libc/mingw/complex/casin.c +0 -50
- package/libc/mingw/complex/casinf.c +0 -50
- package/libc/mingw/complex/catan.c +0 -50
- package/libc/mingw/complex/catanf.c +0 -50
- package/libc/mingw/complex/ccos.c +0 -50
- package/libc/mingw/complex/ccosf.c +0 -50
- package/libc/mingw/complex/cexp.c +0 -48
- package/libc/mingw/complex/cexpf.c +0 -48
- package/libc/mingw/complex/cimag.c +0 -48
- package/libc/mingw/complex/cimagf.c +0 -48
- package/libc/mingw/complex/clog.c +0 -48
- package/libc/mingw/complex/clog10.c +0 -49
- package/libc/mingw/complex/clog10f.c +0 -49
- package/libc/mingw/complex/clogf.c +0 -48
- package/libc/mingw/complex/conj.c +0 -48
- package/libc/mingw/complex/conjf.c +0 -48
- package/libc/mingw/complex/cpow.c +0 -48
- package/libc/mingw/complex/cpowf.c +0 -48
- package/libc/mingw/complex/cproj.c +0 -48
- package/libc/mingw/complex/cprojf.c +0 -48
- package/libc/mingw/complex/creal.c +0 -48
- package/libc/mingw/complex/crealf.c +0 -48
- package/libc/mingw/complex/csin.c +0 -50
- package/libc/mingw/complex/csinf.c +0 -50
- package/libc/mingw/complex/csqrt.c +0 -48
- package/libc/mingw/complex/csqrtf.c +0 -48
- package/libc/mingw/complex/ctan.c +0 -50
- package/libc/mingw/complex/ctanf.c +0 -50
- package/libc/mingw/math/arm/s_rint.c +0 -86
- package/libc/mingw/math/arm/s_rintf.c +0 -51
- package/libc/mingw/math/arm/sincos.S +0 -30
- package/libc/mingw/math/arm-common/sincosl.c +0 -13
- package/libc/mingw/math/arm64/rint.c +0 -12
- package/libc/mingw/math/arm64/rintf.c +0 -12
- package/libc/mingw/math/arm64/sincos.S +0 -32
- package/libc/mingw/math/bsd_private_base.h +0 -148
- package/libc/mingw/math/frexpf.c +0 -13
- package/libc/mingw/math/frexpl.c +0 -71
- package/libc/mingw/math/x86/acosf.c +0 -29
- package/libc/mingw/math/x86/atanf.c +0 -23
- package/libc/mingw/math/x86/atanl.c +0 -18
- package/libc/mingw/math/x86/cos.def.h +0 -65
- package/libc/mingw/math/x86/cosl.c +0 -46
- package/libc/mingw/math/x86/cosl_internal.S +0 -55
- package/libc/mingw/math/x86/ldexp.c +0 -23
- package/libc/mingw/math/x86/scalbn.S +0 -41
- package/libc/mingw/math/x86/scalbnf.S +0 -40
- package/libc/mingw/math/x86/sin.def.h +0 -65
- package/libc/mingw/math/x86/sinl.c +0 -46
- package/libc/mingw/math/x86/sinl_internal.S +0 -58
- package/libc/mingw/math/x86/tanl.S +0 -62
- package/libc/mingw/misc/btowc.c +0 -28
- package/libc/mingw/misc/wcstof.c +0 -66
- package/libc/mingw/misc/wcstoimax.c +0 -132
- package/libc/mingw/misc/wcstoumax.c +0 -126
- package/libc/mingw/misc/wctob.c +0 -29
- package/libc/mingw/misc/winbs_uint64.c +0 -6
- package/libc/mingw/misc/winbs_ulong.c +0 -6
- package/libc/mingw/misc/winbs_ushort.c +0 -6
- package/libc/mingw/stdio/_Exit.c +0 -10
- package/libc/mingw/stdio/_findfirst64i32.c +0 -21
- package/libc/mingw/stdio/_findnext64i32.c +0 -21
- package/libc/mingw/stdio/_fstat64i32.c +0 -37
- package/libc/mingw/stdio/_stat64i32.c +0 -37
- package/libc/mingw/stdio/_wfindfirst64i32.c +0 -21
- package/libc/mingw/stdio/_wfindnext64i32.c +0 -21
- package/libc/mingw/stdio/_wstat64i32.c +0 -37
- package/libc/musl/src/legacy/valloc.c +0 -8
- package/libc/musl/src/math/__cosl.c +0 -96
- package/libc/musl/src/math/__sinl.c +0 -78
- package/libc/musl/src/math/__tanl.c +0 -143
- package/libc/musl/src/math/aarch64/lrint.c +0 -10
- package/libc/musl/src/math/aarch64/lrintf.c +0 -10
- package/libc/musl/src/math/aarch64/rintf.c +0 -7
- package/libc/musl/src/math/cosl.c +0 -39
- package/libc/musl/src/math/exp_data.c +0 -182
- package/libc/musl/src/math/exp_data.h +0 -26
- package/libc/musl/src/math/finite.c +0 -7
- package/libc/musl/src/math/finitef.c +0 -7
- package/libc/musl/src/math/frexp.c +0 -23
- package/libc/musl/src/math/frexpf.c +0 -23
- package/libc/musl/src/math/frexpl.c +0 -29
- package/libc/musl/src/math/i386/lrint.c +0 -8
- package/libc/musl/src/math/i386/lrintf.c +0 -8
- package/libc/musl/src/math/i386/rintf.c +0 -7
- package/libc/musl/src/math/lrint.c +0 -72
- package/libc/musl/src/math/lrintf.c +0 -8
- package/libc/musl/src/math/pow_data.c +0 -180
- package/libc/musl/src/math/pow_data.h +0 -22
- package/libc/musl/src/math/powerpc64/lrint.c +0 -16
- package/libc/musl/src/math/powerpc64/lrintf.c +0 -16
- package/libc/musl/src/math/rintf.c +0 -30
- package/libc/musl/src/math/s390x/rintf.c +0 -15
- package/libc/musl/src/math/sincosl.c +0 -60
- package/libc/musl/src/math/sinl.c +0 -41
- package/libc/musl/src/math/tanl.c +0 -29
- package/libc/musl/src/math/x32/lrint.s +0 -5
- package/libc/musl/src/math/x32/lrintf.s +0 -5
- package/libc/musl/src/math/x86_64/lrint.c +0 -8
- package/libc/musl/src/math/x86_64/lrintf.c +0 -8
- package/libc/wasi/libc-bottom-half/sources/reallocarray.c +0 -14
package/fuzzer.zig
CHANGED
|
@@ -13,7 +13,7 @@ pub const std_options = std.Options{
|
|
|
13
13
|
.logFn = logOverride,
|
|
14
14
|
};
|
|
15
15
|
|
|
16
|
-
const io =
|
|
16
|
+
const io = Io.Threaded.global_single_threaded.io();
|
|
17
17
|
|
|
18
18
|
fn logOverride(
|
|
19
19
|
comptime level: std.log.Level,
|
|
@@ -77,23 +77,27 @@ const Executable = struct {
|
|
|
77
77
|
panic("failed to create directory 'v': {t}", .{e});
|
|
78
78
|
defer v.close(io);
|
|
79
79
|
|
|
80
|
-
|
|
80
|
+
// Since acquiring locks in createFile is not gauraunteed to be atomic, it is not possible
|
|
81
|
+
// to ensure if we create the file we obtain an exclusive lock to populate it since another
|
|
82
|
+
// process may acquire a shared lock between the file being created and the lock request.
|
|
83
|
+
//
|
|
84
|
+
// Instead, the length will be used to determine if the file needs populated, and no
|
|
85
|
+
// process will acquire a shared lock before the coverage file is known to have been
|
|
86
|
+
// exclusively locked (i.e. is already locked). This means another process than the
|
|
87
|
+
// one which created the file could populate it, which is fine.
|
|
88
|
+
const coverage_file = v.createFile(io, &file_name, .{
|
|
81
89
|
.read = true,
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
.{ &file_name, e2 },
|
|
94
|
-
), false },
|
|
95
|
-
else => panic("failed to create coverage file '{s}': {t}", .{ &file_name, e }),
|
|
96
|
-
};
|
|
90
|
+
.truncate = false,
|
|
91
|
+
}) catch |e| panic("failed to open coverage file '{s}': {t}", .{ &file_name, e });
|
|
92
|
+
|
|
93
|
+
const maybe_populate = coverage_file.tryLock(io, .exclusive) catch |e| panic(
|
|
94
|
+
"failed to acquire exclusive lock coverage file '{s}': {t}",
|
|
95
|
+
.{ &file_name, e },
|
|
96
|
+
);
|
|
97
|
+
if (!maybe_populate) {
|
|
98
|
+
coverage_file.lock(io, .shared) catch |e|
|
|
99
|
+
panic("failed to acquire share lock coverage file '{s}': {t}", .{ &file_name, e });
|
|
100
|
+
}
|
|
97
101
|
|
|
98
102
|
comptime assert(abi.SeenPcsHeader.trailing[0] == .pc_bits_usize);
|
|
99
103
|
comptime assert(abi.SeenPcsHeader.trailing[1] == .pc_addr);
|
|
@@ -102,16 +106,21 @@ const Executable = struct {
|
|
|
102
106
|
pc_bitset_usizes * @sizeOf(usize) +
|
|
103
107
|
pcs.len * @sizeOf(usize);
|
|
104
108
|
|
|
105
|
-
|
|
109
|
+
var populate: bool = false;
|
|
110
|
+
const size = coverage_file.length(io) catch |e|
|
|
111
|
+
panic("failed to stat coverage file '{s}': {t}", .{ &file_name, e });
|
|
112
|
+
if (size == 0 and maybe_populate) {
|
|
106
113
|
coverage_file.setLength(io, coverage_file_len) catch |e|
|
|
107
114
|
panic("failed to resize new coverage file '{s}': {t}", .{ &file_name, e });
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
if (size != coverage_file_len) panic(
|
|
115
|
+
populate = true;
|
|
116
|
+
} else if (size != coverage_file_len) {
|
|
117
|
+
panic(
|
|
112
118
|
"incompatible existing coverage file '{s}' (differing lengths: {} != {})",
|
|
113
119
|
.{ &file_name, size, coverage_file_len },
|
|
114
120
|
);
|
|
121
|
+
} else if (maybe_populate) {
|
|
122
|
+
coverage_file.lock(io, .shared) catch |e|
|
|
123
|
+
panic("failed to demote lock for coverage file '{s}': {t}", .{ &file_name, e });
|
|
115
124
|
}
|
|
116
125
|
|
|
117
126
|
var io_map = coverage_file.createMemoryMap(io, .{ .len = coverage_file_len }) catch |e|
|
|
@@ -228,6 +237,13 @@ const Executable = struct {
|
|
|
228
237
|
return self;
|
|
229
238
|
}
|
|
230
239
|
|
|
240
|
+
/// Asserts `buf[0..2]` is "in"
|
|
241
|
+
fn inputFileName(buf: *[10]u8, i: u32) []u8 {
|
|
242
|
+
assert(buf[0..2].* == "in".*);
|
|
243
|
+
const hex = std.fmt.bufPrint(buf[2..], "{x}", .{i}) catch unreachable;
|
|
244
|
+
return buf[0 .. 2 + hex.len];
|
|
245
|
+
}
|
|
246
|
+
|
|
231
247
|
pub fn pcBitsetIterator(self: Executable) PcBitsetIterator {
|
|
232
248
|
return .{ .pc_counters = self.pc_counters };
|
|
233
249
|
}
|
|
@@ -263,32 +279,16 @@ const Executable = struct {
|
|
|
263
279
|
};
|
|
264
280
|
|
|
265
281
|
const Fuzzer = struct {
|
|
282
|
+
tests: []Test,
|
|
283
|
+
test_i: u32,
|
|
284
|
+
test_one: abi.TestOne,
|
|
285
|
+
|
|
266
286
|
// The default PRNG is not used here since going through `Random` can be very expensive
|
|
267
287
|
// since LLVM often fails to devirtualize and inline `fill`. Additionally, optimization
|
|
268
288
|
// is simpler since integers are not serialized then deserialized in the random stream.
|
|
269
289
|
//
|
|
270
290
|
// This acounts for a 30% performance improvement with LLVM 21.
|
|
271
291
|
xoshiro: std.Random.Xoshiro256,
|
|
272
|
-
test_one: abi.TestOne,
|
|
273
|
-
|
|
274
|
-
seen_pcs: []usize,
|
|
275
|
-
bests: struct {
|
|
276
|
-
len: u32,
|
|
277
|
-
quality_buf: []Input.Best,
|
|
278
|
-
input_buf: []Input.Best.Map,
|
|
279
|
-
},
|
|
280
|
-
seen_uids: std.ArrayHashMapUnmanaged(Uid, struct {
|
|
281
|
-
slices: union {
|
|
282
|
-
ints: std.ArrayList([]u64),
|
|
283
|
-
bytes: std.ArrayList(Input.Data.Bytes),
|
|
284
|
-
},
|
|
285
|
-
}, Uid.hashmap_ctx, false),
|
|
286
|
-
|
|
287
|
-
/// Past inputs leading to new pc or uid hits.
|
|
288
|
-
/// These are randomly mutated in round-robin fashion.
|
|
289
|
-
corpus: std.MultiArrayList(Input),
|
|
290
|
-
corpus_pos: Input.Index,
|
|
291
|
-
|
|
292
292
|
bytes_input: std.testing.Smith,
|
|
293
293
|
input_builder: Input.Builder,
|
|
294
294
|
/// Number of data calls the current run has made.
|
|
@@ -319,13 +319,140 @@ const Fuzzer = struct {
|
|
|
319
319
|
},
|
|
320
320
|
|
|
321
321
|
/// As values are provided to the Smith, they are appended to this. If the test
|
|
322
|
-
/// crashes, this can be recovered and used to obtain the crashing values.
|
|
322
|
+
/// crashes, this can be recovered and used to obtain the crashing values. It is
|
|
323
|
+
/// also used to rerun fresh inputs.
|
|
323
324
|
mmap_input: MemoryMappedInput,
|
|
324
|
-
///
|
|
325
|
-
|
|
326
|
-
///
|
|
327
|
-
///
|
|
328
|
-
|
|
325
|
+
/// The instance is responsible for updating the filesystem corpus.
|
|
326
|
+
///
|
|
327
|
+
/// Since different fuzzer instances can be out of sync due to finding inputs before recieving
|
|
328
|
+
/// others and nondeterministic tests, the filesystem is only based off the first instance.
|
|
329
|
+
main_instance: bool,
|
|
330
|
+
|
|
331
|
+
const Test = struct {
|
|
332
|
+
const NameHash = u64;
|
|
333
|
+
const dirname_len = @sizeOf(NameHash) * 2;
|
|
334
|
+
|
|
335
|
+
seen_pcs: []usize,
|
|
336
|
+
bests: struct {
|
|
337
|
+
len: u32,
|
|
338
|
+
quality_buf: []Input.Best,
|
|
339
|
+
input_buf: []Input.Best.Map,
|
|
340
|
+
},
|
|
341
|
+
seen_uids: std.ArrayHashMapUnmanaged(Uid, struct {
|
|
342
|
+
slices: union {
|
|
343
|
+
ints: std.ArrayList([]u64),
|
|
344
|
+
bytes: std.ArrayList(Input.Data.Bytes),
|
|
345
|
+
},
|
|
346
|
+
}, Uid.hashmap_ctx, false),
|
|
347
|
+
|
|
348
|
+
/// Past inputs leading to new pc or uid hits.
|
|
349
|
+
/// These are randomly mutated in round-robin fashion.
|
|
350
|
+
corpus: std.MultiArrayList(Input),
|
|
351
|
+
corpus_pos: Input.Index,
|
|
352
|
+
/// If this is `math.maxInt(u32)` (reserved), it means the corpus has not been loaded from
|
|
353
|
+
/// the filesystem.
|
|
354
|
+
///
|
|
355
|
+
/// If `main_instance` is set, the values in `corpus` after this are mirrored to the
|
|
356
|
+
/// filesystem.
|
|
357
|
+
start_mut_corpus: u32,
|
|
358
|
+
dirname: [dirname_len]u8,
|
|
359
|
+
/// Ensures only one fuzzer writes to the corpus.
|
|
360
|
+
///
|
|
361
|
+
/// Undefined if this is not the main instance.
|
|
362
|
+
lock_file: Io.File,
|
|
363
|
+
received: Received,
|
|
364
|
+
|
|
365
|
+
limit: ?u64,
|
|
366
|
+
/// A batch is the amount of cycles approximently for one second of runtime.
|
|
367
|
+
///
|
|
368
|
+
/// This value is set to the previous batch's runs per second or run limit.
|
|
369
|
+
batch_cycles: u32,
|
|
370
|
+
batches: u64,
|
|
371
|
+
batches_since_find: u64,
|
|
372
|
+
seen_pc_count: u32,
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
const Received = struct {
|
|
376
|
+
state: State,
|
|
377
|
+
/// Stream of inputs with each prefixed with a u32 length
|
|
378
|
+
inputs: std.ArrayList(u8),
|
|
379
|
+
|
|
380
|
+
pub const empty: Received = .{
|
|
381
|
+
.state = .{
|
|
382
|
+
.pending = false,
|
|
383
|
+
.read_lock = false,
|
|
384
|
+
.write_lock = false,
|
|
385
|
+
},
|
|
386
|
+
.inputs = .empty,
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
pub const State = packed struct(u32) {
|
|
390
|
+
pending: bool,
|
|
391
|
+
read_lock: bool,
|
|
392
|
+
/// If set in conjucation with `read_lock`, then there is a waiter on state.
|
|
393
|
+
write_lock: bool,
|
|
394
|
+
_: u29 = 0,
|
|
395
|
+
|
|
396
|
+
pub fn hasPending(s: *State) bool {
|
|
397
|
+
return @atomicLoad(State, s, .monotonic).pending;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
pub fn startReadIfPending(s: *State) bool {
|
|
401
|
+
return @cmpxchgWeak(
|
|
402
|
+
State,
|
|
403
|
+
s,
|
|
404
|
+
.{ .pending = true, .read_lock = false, .write_lock = false },
|
|
405
|
+
.{ .pending = true, .read_lock = true, .write_lock = false },
|
|
406
|
+
.acquire,
|
|
407
|
+
.monotonic,
|
|
408
|
+
) == null;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
pub fn finishRead(s: *State) void {
|
|
412
|
+
const prev = @atomicRmw(State, s, .And, .{
|
|
413
|
+
.pending = false,
|
|
414
|
+
.read_lock = false,
|
|
415
|
+
.write_lock = true,
|
|
416
|
+
}, .release);
|
|
417
|
+
assert(prev.read_lock);
|
|
418
|
+
if (prev.write_lock) {
|
|
419
|
+
abi.runner_futex_wake(@ptrCast(s), 1);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/// Returns if cancelation is requested.
|
|
424
|
+
pub fn startWrite(s: *State) bool {
|
|
425
|
+
var prev = @atomicRmw(State, s, .Or, .{
|
|
426
|
+
.pending = false,
|
|
427
|
+
.read_lock = false,
|
|
428
|
+
.write_lock = true,
|
|
429
|
+
}, .acquire);
|
|
430
|
+
assert(!prev.write_lock);
|
|
431
|
+
while (prev.read_lock) {
|
|
432
|
+
if (abi.runner_futex_wait(@ptrCast(s), @bitCast(prev))) {
|
|
433
|
+
s.* = undefined; // fuzzer is exiting
|
|
434
|
+
return true;
|
|
435
|
+
}
|
|
436
|
+
// Still need `.acquire` ordering so @atomicRmw is necessary
|
|
437
|
+
prev = @atomicRmw(State, s, .Or, .{
|
|
438
|
+
.pending = false,
|
|
439
|
+
.read_lock = false,
|
|
440
|
+
.write_lock = false,
|
|
441
|
+
}, .acquire);
|
|
442
|
+
assert(prev.write_lock);
|
|
443
|
+
}
|
|
444
|
+
return false;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
pub fn finishWrite(s: *State) void {
|
|
448
|
+
@atomicStore(State, s, .{
|
|
449
|
+
.pending = true,
|
|
450
|
+
.read_lock = false,
|
|
451
|
+
.write_lock = false,
|
|
452
|
+
}, .release);
|
|
453
|
+
}
|
|
454
|
+
};
|
|
455
|
+
};
|
|
329
456
|
|
|
330
457
|
const SeqCopy = union {
|
|
331
458
|
order_i: u32,
|
|
@@ -480,7 +607,9 @@ const Fuzzer = struct {
|
|
|
480
607
|
.total_ints = 0,
|
|
481
608
|
.total_bytes = 0,
|
|
482
609
|
.weighted_len = 0,
|
|
483
|
-
|
|
610
|
+
// The - 1 is because we check that `smithed_len` does not overflow a u32;
|
|
611
|
+
// however, `MemoryMappedInput` allows up to `1 << 32`.
|
|
612
|
+
.smithed_len = @sizeOf(abi.MmapInputHeader) - 1,
|
|
484
613
|
};
|
|
485
614
|
|
|
486
615
|
pub fn addInt(b: *Builder, uid: Uid, int: u64) void {
|
|
@@ -591,7 +720,7 @@ const Fuzzer = struct {
|
|
|
591
720
|
b.total_ints = 0;
|
|
592
721
|
b.total_bytes = 0;
|
|
593
722
|
b.weighted_len = 0;
|
|
594
|
-
b.smithed_len =
|
|
723
|
+
b.smithed_len = Builder.init.smithed_len;
|
|
595
724
|
return input;
|
|
596
725
|
}
|
|
597
726
|
|
|
@@ -604,31 +733,128 @@ const Fuzzer = struct {
|
|
|
604
733
|
}
|
|
605
734
|
}
|
|
606
735
|
b.uid_slices.clearRetainingCapacity();
|
|
736
|
+
b.bytes_table.clearRetainingCapacity();
|
|
607
737
|
b.total_ints = 0;
|
|
608
738
|
b.total_bytes = 0;
|
|
609
739
|
b.weighted_len = 0;
|
|
610
|
-
b.smithed_len =
|
|
740
|
+
b.smithed_len = Builder.init.smithed_len;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
/// Asserts the structure is reset
|
|
744
|
+
pub fn deinit(b: *Builder) void {
|
|
745
|
+
assert(b.uid_slices.entries.len == 0);
|
|
746
|
+
b.uid_slices.deinit(gpa);
|
|
747
|
+
b.bytes_table.deinit(gpa);
|
|
748
|
+
b.* = undefined;
|
|
611
749
|
}
|
|
612
750
|
};
|
|
613
751
|
};
|
|
614
752
|
|
|
615
|
-
pub fn init() Fuzzer {
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
753
|
+
pub fn init(n_tests: u32, seed: u64, instance_id: u32, limit: ?u64) Fuzzer {
|
|
754
|
+
const pcs = exec.pc_counters.len;
|
|
755
|
+
if (pcs > math.maxInt(u32)) @panic("too many pcs");
|
|
756
|
+
|
|
757
|
+
const mmap_input = map: {
|
|
758
|
+
// Find a free input file. `instance_id` should give one that is not in use;
|
|
759
|
+
// however, this may not be the case if there are multiple libfuzzers running.
|
|
760
|
+
var input_i = instance_id;
|
|
761
|
+
const input_f = while (true) {
|
|
762
|
+
var name_buf: [10]u8 = undefined;
|
|
763
|
+
name_buf[0..2].* = "in".*;
|
|
764
|
+
const hex = std.fmt.bufPrint(name_buf[2..], "{x}", .{input_i}) catch unreachable;
|
|
765
|
+
const name = name_buf[0 .. 2 + hex.len];
|
|
766
|
+
|
|
767
|
+
if (exec.cache_f.createFile(io, name, .{
|
|
768
|
+
.read = true,
|
|
769
|
+
.truncate = false,
|
|
770
|
+
.lock = .exclusive,
|
|
771
|
+
.lock_nonblocking = true,
|
|
772
|
+
})) |f| {
|
|
773
|
+
break f;
|
|
774
|
+
} else |e| switch (e) {
|
|
775
|
+
// To ensure no input file is unused to avoid the number of input files
|
|
776
|
+
// growing indefinitely across runs, they are linearly searched through.
|
|
777
|
+
//
|
|
778
|
+
// This could be avoided by creating a shared file holding the current number
|
|
779
|
+
// of input files in use; however, using multiple libfuzzers is uncommon and
|
|
780
|
+
// there should not be that many input files to search through anyways.
|
|
781
|
+
error.WouldBlock => input_i += 1,
|
|
782
|
+
else => panic("failed to create file '{s}': {t}", .{ name, e }),
|
|
783
|
+
}
|
|
784
|
+
};
|
|
785
|
+
break :map MemoryMappedInput.init(input_f, instance_id, input_i);
|
|
786
|
+
};
|
|
620
787
|
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
788
|
+
const tests = gpa.alloc(Test, n_tests) catch @panic("OOM");
|
|
789
|
+
const seen_pcs_len = bitsetUsizes(pcs);
|
|
790
|
+
var seen_pcs_bufs = gpa.alloc(usize, seen_pcs_len * n_tests) catch @panic("OOM");
|
|
791
|
+
var best_quality_bufs = gpa.alloc(Input.Best, pcs * n_tests) catch @panic("OOM");
|
|
792
|
+
var best_input_bufs = gpa.alloc(Input.Best.Map, pcs * n_tests) catch @panic("OOM");
|
|
793
|
+
@memset(seen_pcs_bufs, 0);
|
|
794
|
+
for (0.., tests) |i, *t| {
|
|
795
|
+
const name = abi.runner_test_name(@intCast(i)).toSlice();
|
|
796
|
+
// A hash is used as the dirname instead of the actual test name since the test name
|
|
797
|
+
// may be not allowed by the filesystem or have a special meaning (e.g. absolute /
|
|
798
|
+
// relative paths).
|
|
799
|
+
const dirname = std.fmt.hex(std.hash.Wyhash.hash(0, name));
|
|
800
|
+
|
|
801
|
+
const lock_file = file: {
|
|
802
|
+
if (instance_id != 0) break :file undefined;
|
|
803
|
+
|
|
804
|
+
exec.cache_f.createDir(io, &dirname, .default_dir) catch |e| switch (e) {
|
|
805
|
+
error.PathAlreadyExists => {},
|
|
806
|
+
else => panic("failed to create directory '{s}': {t}", .{ &dirname, e }),
|
|
807
|
+
};
|
|
808
|
+
|
|
809
|
+
var cname: CorpusFileName = .fromTest(dirname);
|
|
810
|
+
const lock_name = cname.syncLockName();
|
|
811
|
+
break :file exec.cache_f.createFile(io, lock_name, .{
|
|
812
|
+
.truncate = false,
|
|
813
|
+
.lock = .exclusive,
|
|
814
|
+
.lock_nonblocking = true,
|
|
815
|
+
}) catch |e| switch (e) {
|
|
816
|
+
error.WouldBlock => panic("corpus of '{s}' is in use by another fuzzer", .{name}),
|
|
817
|
+
else => panic("failed to create file '{s}': {t}", .{ lock_name, e }),
|
|
818
|
+
};
|
|
819
|
+
};
|
|
820
|
+
|
|
821
|
+
t.* = .{
|
|
822
|
+
.seen_pcs = seen_pcs_bufs[0..seen_pcs_len],
|
|
823
|
+
.bests = .{
|
|
824
|
+
.len = 0,
|
|
825
|
+
.quality_buf = best_quality_bufs[0..pcs],
|
|
826
|
+
.input_buf = best_input_bufs[0..pcs],
|
|
827
|
+
},
|
|
828
|
+
.seen_uids = .empty,
|
|
829
|
+
|
|
830
|
+
.corpus = .empty,
|
|
831
|
+
.corpus_pos = @enumFromInt(0),
|
|
832
|
+
.start_mut_corpus = math.maxInt(u32),
|
|
833
|
+
.dirname = dirname,
|
|
834
|
+
.lock_file = lock_file,
|
|
835
|
+
.received = .empty,
|
|
836
|
+
|
|
837
|
+
.limit = limit,
|
|
838
|
+
.batch_cycles = 1,
|
|
839
|
+
.batches = 0,
|
|
840
|
+
.batches_since_find = 0,
|
|
841
|
+
.seen_pc_count = 0,
|
|
842
|
+
};
|
|
843
|
+
t.corpus.append(gpa, .none) catch @panic("OOM"); // Also ensures the corpus is not empty
|
|
844
|
+
seen_pcs_bufs = seen_pcs_bufs[seen_pcs_len..];
|
|
845
|
+
best_quality_bufs = best_quality_bufs[pcs..];
|
|
846
|
+
best_input_bufs = best_input_bufs[pcs..];
|
|
847
|
+
}
|
|
848
|
+
assert(seen_pcs_bufs.len == 0);
|
|
849
|
+
assert(best_quality_bufs.len == 0);
|
|
850
|
+
assert(best_input_bufs.len == 0);
|
|
628
851
|
|
|
629
|
-
|
|
630
|
-
.
|
|
852
|
+
return .{
|
|
853
|
+
.tests = tests,
|
|
854
|
+
.test_i = undefined,
|
|
855
|
+
.test_one = undefined,
|
|
631
856
|
|
|
857
|
+
.xoshiro = .init(seed),
|
|
632
858
|
.bytes_input = undefined,
|
|
633
859
|
.input_builder = .init,
|
|
634
860
|
.req_values = undefined,
|
|
@@ -636,97 +862,144 @@ const Fuzzer = struct {
|
|
|
636
862
|
.uid_data_i = .empty,
|
|
637
863
|
.mut_data = undefined,
|
|
638
864
|
|
|
639
|
-
.mmap_input =
|
|
640
|
-
.
|
|
641
|
-
.start_corpus_dir = undefined,
|
|
865
|
+
.mmap_input = mmap_input,
|
|
866
|
+
.main_instance = instance_id == 0,
|
|
642
867
|
};
|
|
643
|
-
@memset(f.seen_pcs, 0);
|
|
644
|
-
return f;
|
|
645
868
|
}
|
|
646
869
|
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
f.bests.
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
870
|
+
pub fn deinit(f: *Fuzzer) void {
|
|
871
|
+
const pcs = exec.pc_counters.len;
|
|
872
|
+
const n_tests = f.tests.len;
|
|
873
|
+
gpa.free(f.tests[0].seen_pcs.ptr[0 .. bitsetUsizes(pcs) * n_tests]);
|
|
874
|
+
gpa.free(f.tests[0].bests.quality_buf.ptr[0 .. pcs * n_tests]);
|
|
875
|
+
gpa.free(f.tests[0].bests.input_buf.ptr[0 .. pcs * n_tests]);
|
|
876
|
+
for (f.tests) |*t| {
|
|
877
|
+
const seen_uids = t.seen_uids.entries.slice();
|
|
878
|
+
for (seen_uids.items(.key), seen_uids.items(.value)) |uid, *data| {
|
|
879
|
+
switch (uid.kind) {
|
|
880
|
+
.int => data.slices.ints.deinit(gpa),
|
|
881
|
+
.bytes => data.slices.bytes.deinit(gpa),
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
t.seen_uids.deinit(gpa);
|
|
885
|
+
const corpus = t.corpus.slice();
|
|
886
|
+
// The first input is `Input.none` and so is skipped as `deinit` is illegal.
|
|
887
|
+
for (1..corpus.len) |i| {
|
|
888
|
+
var in = corpus.get(i);
|
|
889
|
+
in.deinit();
|
|
659
890
|
}
|
|
891
|
+
if (f.main_instance) {
|
|
892
|
+
t.lock_file.close(io);
|
|
893
|
+
}
|
|
894
|
+
t.received.inputs.deinit(gpa);
|
|
660
895
|
}
|
|
661
|
-
f.
|
|
896
|
+
gpa.free(f.tests);
|
|
897
|
+
f.input_builder.deinit();
|
|
898
|
+
f.mmap_input.deinit();
|
|
899
|
+
f.* = undefined;
|
|
900
|
+
}
|
|
662
901
|
|
|
663
|
-
|
|
664
|
-
|
|
902
|
+
pub fn ensureCorpusLoaded(f: *Fuzzer) void {
|
|
903
|
+
const t = &f.tests[f.test_i];
|
|
904
|
+
if (t.start_mut_corpus != math.maxInt(u32)) return;
|
|
665
905
|
|
|
666
|
-
|
|
906
|
+
const start_mut: u32 = @intCast(t.corpus.len);
|
|
907
|
+
if (!f.main_instance) {
|
|
908
|
+
// Inputs can be culled as added since filesystem synchronacy is not required
|
|
909
|
+
t.start_mut_corpus = start_mut;
|
|
910
|
+
}
|
|
667
911
|
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
f.start_corpus_dir = undefined;
|
|
671
|
-
}
|
|
912
|
+
read_corpus: {
|
|
913
|
+
var cname: CorpusFileName = .fromTest(t.dirname);
|
|
672
914
|
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
f.corpus_dir = exec.cache_f.createDirPathOpen(io, unit_test_name, .{}) catch |e|
|
|
676
|
-
panic("failed to open directory '{s}': {t}", .{ unit_test_name, e });
|
|
677
|
-
f.mmap_input = map: {
|
|
678
|
-
const input = f.corpus_dir.createFile(io, "in", .{
|
|
679
|
-
.read = true,
|
|
915
|
+
const readlock_name = cname.readLockName();
|
|
916
|
+
const readlock_file = exec.cache_f.createFile(io, readlock_name, .{
|
|
680
917
|
.truncate = false,
|
|
681
|
-
|
|
682
|
-
// the input file is exclusively locked to ensures only one proceeds.
|
|
683
|
-
.lock = .exclusive,
|
|
684
|
-
.lock_nonblocking = true,
|
|
918
|
+
.lock = .shared,
|
|
685
919
|
}) catch |e| switch (e) {
|
|
686
|
-
|
|
687
|
-
|
|
920
|
+
// FileNotFound means the corpus directory does not exist, which means it is empty
|
|
921
|
+
error.FileNotFound => break :read_corpus,
|
|
922
|
+
else => panic("failed to open '{s}': {t}", .{ readlock_name, e }),
|
|
688
923
|
};
|
|
924
|
+
defer readlock_file.close(io);
|
|
925
|
+
|
|
926
|
+
var input_buf: std.ArrayList(u8) = .empty;
|
|
927
|
+
defer input_buf.deinit(gpa);
|
|
928
|
+
var i: u32 = 0;
|
|
929
|
+
while (true) {
|
|
930
|
+
const name = cname.inputName(i);
|
|
931
|
+
const input_file = exec.cache_f.openFile(io, name, .{}) catch |e| switch (e) {
|
|
932
|
+
error.FileNotFound => break,
|
|
933
|
+
else => panic("failed to open input file '{s}': {t}", .{ name, e }),
|
|
934
|
+
};
|
|
935
|
+
|
|
936
|
+
const len = input_file.length(io) catch |e|
|
|
937
|
+
panic("failed to get length of '{s}': {t}", .{ name, e });
|
|
938
|
+
const ulen = math.cast(usize, len) orelse @panic("OOM");
|
|
939
|
+
input_buf.resize(gpa, ulen) catch @panic("OOM");
|
|
940
|
+
|
|
941
|
+
var r = input_file.readerStreaming(io, &.{});
|
|
942
|
+
r.interface.readSliceAll(input_buf.items) catch |e| switch (e) {
|
|
943
|
+
error.ReadFailed => panic(
|
|
944
|
+
"failed to read from input file '{s}': {t}",
|
|
945
|
+
.{ name, r.err.? },
|
|
946
|
+
),
|
|
947
|
+
error.EndOfStream => panic(
|
|
948
|
+
"input file '{s}' ended before its reported length",
|
|
949
|
+
.{name},
|
|
950
|
+
),
|
|
951
|
+
};
|
|
952
|
+
f.newInputExternal(input_buf.items);
|
|
689
953
|
|
|
690
|
-
|
|
691
|
-
if (size < std.heap.page_size_max) {
|
|
692
|
-
size = std.heap.page_size_max;
|
|
693
|
-
input.setLength(io, size) catch |e| panic("failed to resize input file 'in': {t}", .{e});
|
|
954
|
+
i += 1; // Cannot overflow due to corpus 32-bit size limit
|
|
694
955
|
}
|
|
956
|
+
}
|
|
695
957
|
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
};
|
|
958
|
+
if (f.main_instance) {
|
|
959
|
+
t.start_mut_corpus = start_mut;
|
|
699
960
|
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
961
|
+
// Cull old inputs
|
|
962
|
+
const ref = t.corpus.items(.ref);
|
|
963
|
+
var i: usize = t.start_mut_corpus;
|
|
964
|
+
while (i < t.corpus.len) {
|
|
965
|
+
if (ref[i].best_i_len == 0) {
|
|
966
|
+
f.removeInput(@enumFromInt(i));
|
|
967
|
+
} else {
|
|
968
|
+
i += 1;
|
|
969
|
+
}
|
|
970
|
+
}
|
|
706
971
|
}
|
|
972
|
+
|
|
973
|
+
t.corpus_pos = @enumFromInt(0);
|
|
707
974
|
}
|
|
708
975
|
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
error.FileNotFound => break,
|
|
718
|
-
else => panic("failed to read corpus file '{s}': {t}", .{ name, e }),
|
|
719
|
-
};
|
|
720
|
-
defer gpa.free(bytes);
|
|
721
|
-
f.newInputExternal(bytes);
|
|
976
|
+
const CorpusFileName = struct {
|
|
977
|
+
buf: [Test.dirname_len + 9]u8,
|
|
978
|
+
|
|
979
|
+
pub fn fromTest(dirname: [Test.dirname_len]u8) CorpusFileName {
|
|
980
|
+
var n: CorpusFileName = undefined;
|
|
981
|
+
n.buf[0..dirname.len].* = dirname;
|
|
982
|
+
n.buf[dirname.len] = Io.Dir.path.sep;
|
|
983
|
+
return n;
|
|
722
984
|
}
|
|
723
|
-
f.corpus_pos = @enumFromInt(0);
|
|
724
|
-
}
|
|
725
985
|
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
986
|
+
pub fn readLockName(n: *CorpusFileName) []u8 {
|
|
987
|
+
const basename = "readlock";
|
|
988
|
+
n.buf[Test.dirname_len + 1 ..][0..basename.len].* = basename.*;
|
|
989
|
+
return n.buf[0 .. Test.dirname_len + 1 + basename.len];
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
pub fn syncLockName(n: *CorpusFileName) []u8 {
|
|
993
|
+
const basename = "synclock";
|
|
994
|
+
n.buf[Test.dirname_len + 1 ..][0..basename.len].* = basename.*;
|
|
995
|
+
return n.buf[0 .. Test.dirname_len + 1 + basename.len];
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
pub fn inputName(n: *CorpusFileName, i: u32) []u8 {
|
|
999
|
+
const hex = std.fmt.bufPrint(n.buf[Test.dirname_len + 1 ..][0..8], "{x}", .{i}) catch unreachable;
|
|
1000
|
+
return n.buf[0 .. Test.dirname_len + 1 + hex.len];
|
|
1001
|
+
}
|
|
1002
|
+
};
|
|
730
1003
|
|
|
731
1004
|
fn rngInt(f: *Fuzzer, T: type) T {
|
|
732
1005
|
comptime assert(@bitSizeOf(T) <= 64);
|
|
@@ -749,13 +1022,14 @@ const Fuzzer = struct {
|
|
|
749
1022
|
};
|
|
750
1023
|
|
|
751
1024
|
fn isFresh(f: *Fuzzer) bool {
|
|
1025
|
+
const t = &f.tests[f.test_i];
|
|
752
1026
|
// Store as a bool instead of returning immediately to aid optimizations
|
|
753
1027
|
// by reducing branching since a fresh input is the unlikely case.
|
|
754
1028
|
var fresh: bool = false;
|
|
755
1029
|
|
|
756
1030
|
var n_pcs: u32 = 0;
|
|
757
1031
|
var hit_pcs = exec.pcBitsetIterator();
|
|
758
|
-
for (
|
|
1032
|
+
for (t.seen_pcs) |seen| {
|
|
759
1033
|
const hits = hit_pcs.next();
|
|
760
1034
|
fresh |= hits & ~seen != 0;
|
|
761
1035
|
n_pcs += @popCount(hits);
|
|
@@ -768,7 +1042,7 @@ const Fuzzer = struct {
|
|
|
768
1042
|
.bytes = f.req_bytes,
|
|
769
1043
|
},
|
|
770
1044
|
};
|
|
771
|
-
for (
|
|
1045
|
+
for (t.bests.quality_buf[0..t.bests.len]) |best| {
|
|
772
1046
|
if (exec.pc_counters[best.pc] == 0) continue;
|
|
773
1047
|
fresh |= quality.betterLess(best.min) | quality.betterMore(best.max);
|
|
774
1048
|
}
|
|
@@ -776,12 +1050,15 @@ const Fuzzer = struct {
|
|
|
776
1050
|
return fresh;
|
|
777
1051
|
}
|
|
778
1052
|
|
|
1053
|
+
/// It is the callee's responsibility to reset the corpus pos
|
|
1054
|
+
///
|
|
779
1055
|
/// Returns if `error.SkipZigTest` was indicated
|
|
780
1056
|
fn runBytes(f: *Fuzzer, bytes: []const u8, mode: Input.Index) bool {
|
|
781
1057
|
assert(mode == .bytes_dry or mode == .bytes_fresh);
|
|
782
1058
|
|
|
783
1059
|
f.bytes_input = .{ .in = bytes };
|
|
784
|
-
f.corpus_pos = mode;
|
|
1060
|
+
f.tests[f.test_i].corpus_pos = mode;
|
|
1061
|
+
defer f.tests[f.test_i].corpus_pos = undefined;
|
|
785
1062
|
return f.run(0); // 0 since `f.uid_data` is unused
|
|
786
1063
|
}
|
|
787
1064
|
|
|
@@ -791,89 +1068,112 @@ const Fuzzer = struct {
|
|
|
791
1068
|
exec.shared_seen_pcs[@sizeOf(abi.SeenPcsHeader)..].ptr,
|
|
792
1069
|
);
|
|
793
1070
|
|
|
1071
|
+
const t = &f.tests[f.test_i];
|
|
794
1072
|
var hit_pcs = exec.pcBitsetIterator();
|
|
795
|
-
for (
|
|
1073
|
+
for (t.seen_pcs, shared_seen_pcs) |*seen, *shared_seen| {
|
|
796
1074
|
const new = hit_pcs.next() & ~seen.*;
|
|
797
1075
|
if (new != 0) {
|
|
798
1076
|
seen.* |= new;
|
|
799
1077
|
_ = @atomicRmw(usize, shared_seen, .Or, new, .monotonic);
|
|
1078
|
+
t.seen_pc_count += @popCount(new);
|
|
800
1079
|
}
|
|
801
1080
|
}
|
|
802
1081
|
}
|
|
803
1082
|
|
|
804
|
-
fn removeBest(f: *Fuzzer, i: Input.Index, best_i: u32
|
|
805
|
-
const
|
|
1083
|
+
fn removeBest(f: *Fuzzer, i: Input.Index, best_i: u32) void {
|
|
1084
|
+
const t = &f.tests[f.test_i];
|
|
1085
|
+
const ref = &t.corpus.items(.ref)[@intFromEnum(i)];
|
|
806
1086
|
const list_i = mem.indexOfScalar(u32, ref.best_i_buf[0..ref.best_i_len], best_i).?;
|
|
807
1087
|
ref.best_i_len -= 1;
|
|
808
1088
|
ref.best_i_buf[list_i] = ref.best_i_buf[ref.best_i_len];
|
|
809
1089
|
|
|
810
|
-
if (ref.best_i_len == 0 and @intFromEnum(i) >=
|
|
1090
|
+
if (ref.best_i_len == 0 and @intFromEnum(i) >= t.start_mut_corpus) {
|
|
811
1091
|
// The input is no longer valuable, so remove it.
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
.
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
}
|
|
844
|
-
|
|
1092
|
+
f.removeInput(i);
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
fn removeInput(f: *Fuzzer, i: Input.Index) void {
|
|
1097
|
+
const t = &f.tests[f.test_i];
|
|
1098
|
+
const ref = &t.corpus.items(.ref)[@intFromEnum(i)];
|
|
1099
|
+
assert(ref.best_i_len == 0 and @intFromEnum(i) >= t.start_mut_corpus);
|
|
1100
|
+
|
|
1101
|
+
var removed_input = t.corpus.get(@intFromEnum(i));
|
|
1102
|
+
for (
|
|
1103
|
+
removed_input.data.uid_slices.keys(),
|
|
1104
|
+
removed_input.data.uid_slices.values(),
|
|
1105
|
+
removed_input.seen_uid_i,
|
|
1106
|
+
) |uid, slice, seen_uid_i| {
|
|
1107
|
+
switch (uid.kind) {
|
|
1108
|
+
.int => {
|
|
1109
|
+
const seen_ints = &t.seen_uids.values()[seen_uid_i].slices.ints;
|
|
1110
|
+
const removed_ints = removed_input.data.ints[slice.base..][0..slice.len];
|
|
1111
|
+
_ = seen_ints.swapRemove(for (0.., seen_ints.items) |idx, ints| {
|
|
1112
|
+
if (removed_ints.ptr == ints.ptr) {
|
|
1113
|
+
assert(removed_ints.len == ints.len);
|
|
1114
|
+
break idx;
|
|
1115
|
+
}
|
|
1116
|
+
} else unreachable);
|
|
1117
|
+
},
|
|
1118
|
+
.bytes => {
|
|
1119
|
+
const seen_bytes = &t.seen_uids.values()[seen_uid_i].slices.bytes;
|
|
1120
|
+
const removed_bytes: Input.Data.Bytes = .{
|
|
1121
|
+
.entries = removed_input.data.bytes.entries[slice.base..][0..slice.len],
|
|
1122
|
+
.table = removed_input.data.bytes.table,
|
|
1123
|
+
};
|
|
1124
|
+
_ = seen_bytes.swapRemove(for (0.., seen_bytes.items) |idx, bytes| {
|
|
1125
|
+
if (removed_bytes.entries.ptr == bytes.entries.ptr) {
|
|
1126
|
+
assert(removed_bytes.entries.len == bytes.entries.len);
|
|
1127
|
+
assert(removed_bytes.table.ptr == bytes.table.ptr);
|
|
1128
|
+
assert(removed_bytes.table.len == bytes.table.len);
|
|
1129
|
+
break idx;
|
|
1130
|
+
}
|
|
1131
|
+
} else unreachable);
|
|
1132
|
+
},
|
|
845
1133
|
}
|
|
846
|
-
|
|
847
|
-
|
|
1134
|
+
}
|
|
1135
|
+
removed_input.deinit();
|
|
1136
|
+
t.corpus.swapRemove(@intFromEnum(i));
|
|
848
1137
|
|
|
849
|
-
|
|
850
|
-
|
|
1138
|
+
if (@intFromEnum(i) != t.corpus.len) {
|
|
1139
|
+
// The last item was moved so its refs need updated.
|
|
1140
|
+
// `ref` can be reused since it was a swap remove.
|
|
1141
|
+
for (ref.best_i_buf[0..ref.best_i_len]) |update_pc_i| {
|
|
1142
|
+
const best = &t.bests.input_buf[update_pc_i];
|
|
1143
|
+
assert(@intFromEnum(best.min) == t.corpus.len or
|
|
1144
|
+
@intFromEnum(best.max) == t.corpus.len);
|
|
851
1145
|
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
"failed to remove corpus file '{s}': {t}",
|
|
855
|
-
.{ removed_name, e },
|
|
856
|
-
);
|
|
857
|
-
return; // No item moved so no refs to update
|
|
1146
|
+
if (@intFromEnum(best.min) == t.corpus.len) best.min = i;
|
|
1147
|
+
if (@intFromEnum(best.max) == t.corpus.len) best.max = i;
|
|
858
1148
|
}
|
|
1149
|
+
}
|
|
859
1150
|
|
|
860
|
-
|
|
861
|
-
|
|
1151
|
+
if (!f.main_instance) return;
|
|
1152
|
+
|
|
1153
|
+
var removed_cname: CorpusFileName = .fromTest(t.dirname);
|
|
1154
|
+
// Temporarily use removed_name to construct the path to the lock
|
|
1155
|
+
const readlock_name = removed_cname.readLockName();
|
|
1156
|
+
const readlock_file = exec.cache_f.createFile(io, readlock_name, .{
|
|
1157
|
+
.truncate = false,
|
|
1158
|
+
.lock = .exclusive,
|
|
1159
|
+
}) catch |e| panic("failed to open '{s}': {t}", .{ readlock_name, e });
|
|
1160
|
+
defer readlock_file.close(io);
|
|
1161
|
+
|
|
1162
|
+
const removed_name = removed_cname.inputName(@intFromEnum(i) - t.start_mut_corpus);
|
|
1163
|
+
if (@intFromEnum(i) == t.corpus.len) {
|
|
1164
|
+
exec.cache_f.deleteFile(io, removed_name) catch |e| panic(
|
|
1165
|
+
"failed to remove corpus file '{s}': {t}",
|
|
1166
|
+
.{ removed_name, e },
|
|
1167
|
+
);
|
|
1168
|
+
} else {
|
|
1169
|
+
var swapped_cname: CorpusFileName = .fromTest(t.dirname);
|
|
1170
|
+
const swapped_i: u32 = @intCast(t.corpus.len);
|
|
1171
|
+
const swapped_name = swapped_cname.inputName(swapped_i - t.start_mut_corpus);
|
|
862
1172
|
|
|
863
|
-
|
|
1173
|
+
exec.cache_f.rename(swapped_name, exec.cache_f, removed_name, io) catch |e| panic(
|
|
864
1174
|
"failed to rename corpus file '{s}' to '{s}': {t}",
|
|
865
1175
|
.{ swapped_name, removed_name, e },
|
|
866
1176
|
);
|
|
867
|
-
|
|
868
|
-
// Update refrences. `ref` can be reused since it was a swap remove
|
|
869
|
-
for (ref.best_i_buf[0..ref.best_i_len]) |update_pc_i| {
|
|
870
|
-
const best = &f.bests.input_buf[update_pc_i];
|
|
871
|
-
assert(@intFromEnum(best.min) == f.corpus.len or
|
|
872
|
-
@intFromEnum(best.max) == f.corpus.len);
|
|
873
|
-
|
|
874
|
-
if (@intFromEnum(best.min) == f.corpus.len) best.min = i;
|
|
875
|
-
if (@intFromEnum(best.max) == f.corpus.len) best.max = i;
|
|
876
|
-
}
|
|
877
1177
|
}
|
|
878
1178
|
}
|
|
879
1179
|
|
|
@@ -881,51 +1181,30 @@ const Fuzzer = struct {
|
|
|
881
1181
|
// All inputs including the corpus are required to go through the memory
|
|
882
1182
|
// mapped input in case they cause a crash so they can be identified.
|
|
883
1183
|
f.mmap_input.appendSlice(bytes);
|
|
884
|
-
f.newInput(
|
|
1184
|
+
f.newInput();
|
|
885
1185
|
f.mmap_input.clearRetainingCapacity();
|
|
886
1186
|
}
|
|
887
1187
|
|
|
888
|
-
fn newInput(f: *Fuzzer
|
|
1188
|
+
fn newInput(f: *Fuzzer) void {
|
|
1189
|
+
const t = &f.tests[f.test_i];
|
|
1190
|
+
const new_is_mut = t.start_mut_corpus != math.maxInt(u32);
|
|
1191
|
+
assert(new_is_mut == (t.corpus.len >= t.start_mut_corpus));
|
|
889
1192
|
const bytes = f.mmap_input.inputSlice();
|
|
890
1193
|
// `error.SkipZigTest` here can be from one of these causes:
|
|
891
|
-
// *
|
|
892
|
-
// * An input provided by the test
|
|
1194
|
+
// * A previous corpus input after the test has changed
|
|
1195
|
+
// * An input provided by the test
|
|
893
1196
|
// * The test is non-deterministic
|
|
894
1197
|
if (f.runBytes(bytes, .bytes_fresh) and
|
|
895
|
-
|
|
896
|
-
//
|
|
1198
|
+
new_is_mut // The corpus must be mutable at this point for the input to be
|
|
1199
|
+
// omitted (i.e. test corpus inputs and filesystem inputs cannot be dropped)
|
|
897
1200
|
) {
|
|
898
1201
|
f.input_builder.reset();
|
|
899
|
-
|
|
1202
|
+
t.corpus_pos = @enumFromInt(0);
|
|
900
1203
|
return;
|
|
901
1204
|
}
|
|
1205
|
+
|
|
902
1206
|
f.req_values = f.input_builder.total_ints + f.input_builder.total_bytes;
|
|
903
1207
|
f.req_bytes = @intCast(f.input_builder.bytes_table.items.len);
|
|
904
|
-
var input = f.input_builder.build();
|
|
905
|
-
|
|
906
|
-
f.uid_data_i.ensureTotalCapacity(gpa, input.data.uid_slices.entries.len) catch @panic("OOM");
|
|
907
|
-
for (
|
|
908
|
-
input.seen_uid_i,
|
|
909
|
-
input.data.uid_slices.keys(),
|
|
910
|
-
input.data.uid_slices.values(),
|
|
911
|
-
) |*i, uid, slice| {
|
|
912
|
-
const gop = f.seen_uids.getOrPutValue(gpa, uid, switch (uid.kind) {
|
|
913
|
-
.int => .{ .slices = .{ .ints = .empty } },
|
|
914
|
-
.bytes => .{ .slices = .{ .bytes = .empty } },
|
|
915
|
-
}) catch @panic("OOM");
|
|
916
|
-
switch (uid.kind) {
|
|
917
|
-
.int => f.seen_uids.values()[gop.index].slices.ints.append(
|
|
918
|
-
gpa,
|
|
919
|
-
input.data.ints[slice.base..][0..slice.len],
|
|
920
|
-
) catch @panic("OOM"),
|
|
921
|
-
.bytes => f.seen_uids.values()[gop.index].slices.bytes.append(gpa, .{
|
|
922
|
-
.entries = input.data.bytes.entries[slice.base..][0..slice.len],
|
|
923
|
-
.table = input.data.bytes.table,
|
|
924
|
-
}) catch @panic("OOM"),
|
|
925
|
-
}
|
|
926
|
-
i.* = @intCast(gop.index);
|
|
927
|
-
}
|
|
928
|
-
|
|
929
1208
|
const quality: Input.Best.Quality = .{
|
|
930
1209
|
.n_pcs = n_pcs: {
|
|
931
1210
|
@setRuntimeSafety(builtin.mode == .Debug); // Necessary for vectorization
|
|
@@ -942,7 +1221,7 @@ const Fuzzer = struct {
|
|
|
942
1221
|
};
|
|
943
1222
|
|
|
944
1223
|
var best_i_list: std.ArrayList(u32) = .empty;
|
|
945
|
-
for (0..,
|
|
1224
|
+
for (0.., t.bests.quality_buf[0..t.bests.len]) |best_i, best| {
|
|
946
1225
|
if (exec.pc_counters[best.pc] == 0) continue;
|
|
947
1226
|
|
|
948
1227
|
const better_min = quality.betterLess(best.min);
|
|
@@ -953,30 +1232,30 @@ const Fuzzer = struct {
|
|
|
953
1232
|
}
|
|
954
1233
|
best_i_list.append(gpa, @intCast(best_i)) catch @panic("OOM");
|
|
955
1234
|
|
|
956
|
-
const map = &
|
|
1235
|
+
const map = &t.bests.input_buf[best_i];
|
|
957
1236
|
if (map.min != map.max) {
|
|
958
1237
|
if (better_min) {
|
|
959
|
-
f.removeBest(map.min, @intCast(best_i)
|
|
1238
|
+
f.removeBest(map.min, @intCast(best_i));
|
|
960
1239
|
}
|
|
961
1240
|
if (better_max) {
|
|
962
|
-
f.removeBest(map.max, @intCast(best_i)
|
|
1241
|
+
f.removeBest(map.max, @intCast(best_i));
|
|
963
1242
|
}
|
|
964
1243
|
} else {
|
|
965
1244
|
if (better_min and better_max) {
|
|
966
|
-
f.removeBest(map.min, @intCast(best_i)
|
|
1245
|
+
f.removeBest(map.min, @intCast(best_i));
|
|
967
1246
|
}
|
|
968
1247
|
}
|
|
969
1248
|
}
|
|
970
1249
|
|
|
971
1250
|
// Must come after the above since some inputs may be removed
|
|
972
|
-
const input_i: Input.Index = @enumFromInt(
|
|
1251
|
+
const input_i: Input.Index = @enumFromInt(t.corpus.len);
|
|
973
1252
|
if (input_i == Input.Index.reserved_start) {
|
|
974
1253
|
@panic("corpus size limit exceeded");
|
|
975
1254
|
}
|
|
976
1255
|
|
|
977
1256
|
for (best_i_list.items) |i| {
|
|
978
|
-
const best_qual = &
|
|
979
|
-
const best_map = &
|
|
1257
|
+
const best_qual = &t.bests.quality_buf[i];
|
|
1258
|
+
const best_map = &t.bests.input_buf[i];
|
|
980
1259
|
|
|
981
1260
|
if (quality.betterLess(best_qual.min)) {
|
|
982
1261
|
best_qual.min = quality;
|
|
@@ -994,42 +1273,74 @@ const Fuzzer = struct {
|
|
|
994
1273
|
continue;
|
|
995
1274
|
}
|
|
996
1275
|
|
|
997
|
-
if ((
|
|
1276
|
+
if ((t.seen_pcs[i / @bitSizeOf(usize)] >> @intCast(i % @bitSizeOf(usize))) & 1 == 0) {
|
|
998
1277
|
@branchHint(.unlikely);
|
|
999
|
-
best_i_list.append(gpa,
|
|
1000
|
-
|
|
1278
|
+
best_i_list.append(gpa, t.bests.len) catch @panic("OOM");
|
|
1279
|
+
t.bests.quality_buf[t.bests.len] = .{
|
|
1001
1280
|
.pc = @intCast(i),
|
|
1002
1281
|
.min = quality,
|
|
1003
1282
|
.max = quality,
|
|
1004
1283
|
};
|
|
1005
|
-
|
|
1006
|
-
|
|
1284
|
+
t.bests.input_buf[t.bests.len] = .{ .min = input_i, .max = input_i };
|
|
1285
|
+
t.bests.len += 1;
|
|
1007
1286
|
}
|
|
1008
1287
|
}
|
|
1009
1288
|
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1289
|
+
// Having no best qualities could be from one of these causes:
|
|
1290
|
+
// * A previous corpus input after the test has changed
|
|
1291
|
+
// * An input provided by the test
|
|
1292
|
+
// * The test is non-deterministic
|
|
1293
|
+
if (best_i_list.items.len == 0 and new_is_mut) {
|
|
1294
|
+
assert(best_i_list.capacity == 0);
|
|
1295
|
+
f.input_builder.reset();
|
|
1296
|
+
t.corpus_pos = @enumFromInt(0);
|
|
1015
1297
|
return;
|
|
1016
1298
|
}
|
|
1017
1299
|
|
|
1300
|
+
var input = f.input_builder.build();
|
|
1301
|
+
f.uid_data_i.ensureTotalCapacity(gpa, input.data.uid_slices.entries.len) catch @panic("OOM");
|
|
1302
|
+
for (
|
|
1303
|
+
input.seen_uid_i,
|
|
1304
|
+
input.data.uid_slices.keys(),
|
|
1305
|
+
input.data.uid_slices.values(),
|
|
1306
|
+
) |*i, uid, slice| {
|
|
1307
|
+
const gop = t.seen_uids.getOrPutValue(gpa, uid, switch (uid.kind) {
|
|
1308
|
+
.int => .{ .slices = .{ .ints = .empty } },
|
|
1309
|
+
.bytes => .{ .slices = .{ .bytes = .empty } },
|
|
1310
|
+
}) catch @panic("OOM");
|
|
1311
|
+
switch (uid.kind) {
|
|
1312
|
+
.int => t.seen_uids.values()[gop.index].slices.ints.append(
|
|
1313
|
+
gpa,
|
|
1314
|
+
input.data.ints[slice.base..][0..slice.len],
|
|
1315
|
+
) catch @panic("OOM"),
|
|
1316
|
+
.bytes => t.seen_uids.values()[gop.index].slices.bytes.append(gpa, .{
|
|
1317
|
+
.entries = input.data.bytes.entries[slice.base..][0..slice.len],
|
|
1318
|
+
.table = input.data.bytes.table,
|
|
1319
|
+
}) catch @panic("OOM"),
|
|
1320
|
+
}
|
|
1321
|
+
i.* = @intCast(gop.index);
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1018
1324
|
input.ref.best_i_buf = best_i_list.toOwnedSlice(gpa) catch @panic("OOM");
|
|
1019
1325
|
input.ref.best_i_len = @intCast(input.ref.best_i_buf.len);
|
|
1020
|
-
|
|
1021
|
-
|
|
1326
|
+
t.corpus.append(gpa, input) catch @panic("OOM");
|
|
1327
|
+
t.corpus_pos = input_i;
|
|
1022
1328
|
|
|
1023
1329
|
// Must come after the above since `seen_pcs` is used
|
|
1024
1330
|
f.updateSeenPcs();
|
|
1025
1331
|
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1332
|
+
t.batches_since_find = 0;
|
|
1333
|
+
if (f.main_instance and new_is_mut) {
|
|
1334
|
+
// Only the main instance increments the number of unique runs since it is likely
|
|
1335
|
+
// multiple instances find the same new input at the same time.
|
|
1336
|
+
_ = @atomicRmw(usize, &exec.seenPcsHeader().unique_runs, .Add, 1, .monotonic);
|
|
1337
|
+
// Write new input to the cache
|
|
1338
|
+
var cname: CorpusFileName = .fromTest(t.dirname);
|
|
1339
|
+
const name = cname.inputName(@intFromEnum(input_i) - t.start_mut_corpus);
|
|
1340
|
+
exec.cache_f.writeFile(io, .{ .sub_path = name, .data = bytes, .flags = .{
|
|
1341
|
+
.exclusive = true,
|
|
1342
|
+
} }) catch |e| panic("failed to write corpus file '{s}': {t}", .{ name, e });
|
|
1343
|
+
}
|
|
1033
1344
|
}
|
|
1034
1345
|
|
|
1035
1346
|
/// Returns if `error.SkipZigTest` was indicated
|
|
@@ -1061,8 +1372,10 @@ const Fuzzer = struct {
|
|
|
1061
1372
|
|
|
1062
1373
|
pub fn cycle(f: *Fuzzer) void {
|
|
1063
1374
|
assert(f.mmap_input.len == 0);
|
|
1064
|
-
|
|
1065
|
-
const
|
|
1375
|
+
|
|
1376
|
+
const t = &f.tests[f.test_i];
|
|
1377
|
+
const corpus = t.corpus.slice();
|
|
1378
|
+
const corpus_i = @intFromEnum(t.corpus_pos);
|
|
1066
1379
|
|
|
1067
1380
|
var small_entronopy: SmallEntronopy = .{ .bits = f.rngInt(u64) };
|
|
1068
1381
|
var n_mutate = mutCount(small_entronopy.take(u16));
|
|
@@ -1118,13 +1431,181 @@ const Fuzzer = struct {
|
|
|
1118
1431
|
if (!skip and f.isFresh()) {
|
|
1119
1432
|
@branchHint(.unlikely);
|
|
1120
1433
|
|
|
1121
|
-
|
|
1122
|
-
f.newInput(
|
|
1434
|
+
abi.runner_broadcast_input(f.test_i, .fromSlice(f.mmap_input.inputSlice()));
|
|
1435
|
+
f.newInput();
|
|
1436
|
+
} else {
|
|
1437
|
+
assert(@intFromEnum(t.corpus_pos) < t.corpus.len);
|
|
1438
|
+
t.corpus_pos = @enumFromInt((@intFromEnum(t.corpus_pos) + 1) % t.corpus.len);
|
|
1123
1439
|
}
|
|
1124
1440
|
f.mmap_input.clearRetainingCapacity();
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
fn takeReceived(f: *Fuzzer) void {
|
|
1444
|
+
const t = &f.tests[f.test_i];
|
|
1445
|
+
if (t.received.state.startReadIfPending()) {
|
|
1446
|
+
defer t.received.state.finishRead();
|
|
1447
|
+
const inputs = &t.received.inputs;
|
|
1448
|
+
var rem = inputs.items;
|
|
1449
|
+
|
|
1450
|
+
while (true) {
|
|
1451
|
+
const len: u32 = @bitCast(rem[0..4].*);
|
|
1452
|
+
rem = rem[4..];
|
|
1453
|
+
const bytes = rem[0..len];
|
|
1454
|
+
rem = rem[len..];
|
|
1455
|
+
|
|
1456
|
+
f.mmap_input.appendSlice(bytes);
|
|
1457
|
+
f.newInput();
|
|
1458
|
+
f.mmap_input.clearRetainingCapacity();
|
|
1459
|
+
|
|
1460
|
+
if (rem.len == 0) break;
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
inputs.clearRetainingCapacity();
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
pub fn batch(f: *Fuzzer) void {
|
|
1468
|
+
const t = &f.tests[f.test_i];
|
|
1469
|
+
assert(t.limit != 0);
|
|
1470
|
+
t.batches += 1;
|
|
1471
|
+
t.batches_since_find += 1;
|
|
1472
|
+
if (f.tests.len != 1) {
|
|
1473
|
+
// Use cpu_process since some fuzz tests may spawn
|
|
1474
|
+
// other threads and give all the work to them.
|
|
1475
|
+
const start: Io.Timestamp = .now(io, .cpu_process);
|
|
1476
|
+
var completed_cycles: u32 = 0;
|
|
1477
|
+
var total_cycles: u32 = t.batch_cycles;
|
|
1478
|
+
|
|
1479
|
+
while (true) {
|
|
1480
|
+
assert(completed_cycles != total_cycles);
|
|
1481
|
+
while (completed_cycles < total_cycles) {
|
|
1482
|
+
f.takeReceived();
|
|
1483
|
+
f.cycle();
|
|
1484
|
+
completed_cycles += 1;
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
const duration = start.untilNow(io, .cpu_process);
|
|
1488
|
+
const ns = @min(@max(1, duration.nanoseconds), math.maxInt(u64));
|
|
1489
|
+
const speed = @as(u64, t.batch_cycles) * std.time.ns_per_s / ns;
|
|
1490
|
+
// @min avoids large increases in batch_cycles due to just a few cycles running
|
|
1491
|
+
// fast. For example, if batch_cycles is only 2, and both run very fast due to
|
|
1492
|
+
// unlucky rng, this avoids a large runtime on the next batch. This also avoids
|
|
1493
|
+
// timer inprecision giving large values.
|
|
1494
|
+
t.batch_cycles = @max(1, @min(speed, t.batch_cycles *| 2));
|
|
1495
|
+
|
|
1496
|
+
if (ns < std.time.ns_per_s * 7 / 8) {
|
|
1497
|
+
// Keep running the test to get closer to a second. This will almost always
|
|
1498
|
+
// be the case for the first batch as the default batch_cycles is 1.
|
|
1499
|
+
if (t.limit == total_cycles) break;
|
|
1500
|
+
|
|
1501
|
+
const rem_ns: u64 = @as(u32, std.time.ns_per_s) - ns;
|
|
1502
|
+
const extra: u32 = @intCast(rem_ns * t.batch_cycles / std.time.ns_per_s);
|
|
1503
|
+
if (extra == 0) break; // No better approximation of a second possible
|
|
1504
|
+
total_cycles += extra;
|
|
1505
|
+
if (t.limit) |limit| total_cycles = @min(total_cycles, limit);
|
|
1506
|
+
continue;
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
break;
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
assert(completed_cycles == total_cycles);
|
|
1513
|
+
if (t.limit) |prev| {
|
|
1514
|
+
t.limit = prev - total_cycles;
|
|
1515
|
+
t.batch_cycles = @min(t.batch_cycles, t.limit.?);
|
|
1516
|
+
}
|
|
1517
|
+
} else {
|
|
1518
|
+
while (true) {
|
|
1519
|
+
if (t.limit) |limit| {
|
|
1520
|
+
if (limit == 0) break;
|
|
1521
|
+
t.limit = limit - 1;
|
|
1522
|
+
}
|
|
1523
|
+
f.takeReceived();
|
|
1524
|
+
f.cycle();
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
pub fn select(f: *Fuzzer) ?u32 {
|
|
1530
|
+
assert(f.tests.len > 1); // More efficiently handled by the callee
|
|
1531
|
+
|
|
1532
|
+
// The algorithm for selecting tests is such that:
|
|
1533
|
+
// - 1/4 are from the number of pcs as they give an indication of test complexity.
|
|
1534
|
+
// - 3/4 are from the recency of the last find as it gives an indication of the
|
|
1535
|
+
// effectiveness of fuzzing for the test.
|
|
1536
|
+
// - Tests finding fresh inputs are run 8x other tests.
|
|
1537
|
+
// - Since new tests are considered to have just found a fresh input, this means they
|
|
1538
|
+
// are also prioritized which allows their characteristics to be learnt.
|
|
1539
|
+
// When a test has a new input pending, it is treated as if it had just found a fresh
|
|
1540
|
+
// input instead of immediately being run. This avoids a test which is finding many new
|
|
1541
|
+
// inputs from being exclusively run.
|
|
1542
|
+
const new_batches = 16;
|
|
1543
|
+
|
|
1544
|
+
var n_with_new: u32 = 0;
|
|
1545
|
+
var n_seen_pcs: u64 = 0;
|
|
1546
|
+
var n_latest_find: u64 = 0;
|
|
1547
|
+
|
|
1548
|
+
for (f.tests) |*t| {
|
|
1549
|
+
const has_pending = t.received.state.hasPending();
|
|
1550
|
+
if (has_pending) {
|
|
1551
|
+
assert(t.limit == null); // If multiprocess limited fuzzing was to be added, then
|
|
1552
|
+
// `t.received.inputs.clearRetainingCapacity()` would need to be added after
|
|
1553
|
+
// `t.received.state.startReadIfPending()` when the limit has been reached.
|
|
1554
|
+
}
|
|
1555
|
+
if (t.limit == 0) continue;
|
|
1556
|
+
|
|
1557
|
+
const latest_find = t.batches - t.batches_since_find;
|
|
1558
|
+
n_with_new += @intFromBool(t.batches_since_find < new_batches or has_pending);
|
|
1559
|
+
n_seen_pcs += @max(t.seen_pc_count, 1);
|
|
1560
|
+
n_latest_find += @max(latest_find, 1);
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
if (n_seen_pcs == 0) {
|
|
1564
|
+
assert(n_with_new == 0);
|
|
1565
|
+
assert(n_latest_find == 0);
|
|
1566
|
+
return null; // All fuzz tests have used up their limit
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
const rng: packed struct(u64) {
|
|
1570
|
+
idx_rng: u32,
|
|
1571
|
+
from_new: u3,
|
|
1572
|
+
from_latest_find: u2,
|
|
1573
|
+
_: u27,
|
|
1574
|
+
} = @bitCast(f.rngInt(u64));
|
|
1575
|
+
|
|
1576
|
+
if (n_with_new != 0 and rng.from_new != 0) {
|
|
1577
|
+
var n = std.Random.limitRangeBiased(u32, rng.idx_rng, n_with_new);
|
|
1578
|
+
for (0.., f.tests) |i, *t| {
|
|
1579
|
+
if (t.limit == 0) continue;
|
|
1580
|
+
if (t.batches_since_find < new_batches or t.received.state.hasPending()) {
|
|
1581
|
+
if (n == 0) return @intCast(i);
|
|
1582
|
+
n -= 1;
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
unreachable;
|
|
1586
|
+
}
|
|
1125
1587
|
|
|
1126
|
-
|
|
1127
|
-
|
|
1588
|
+
if (rng.from_latest_find != 0) {
|
|
1589
|
+
const total_weight = n_latest_find;
|
|
1590
|
+
var n = f.rngLessThan(u64, total_weight);
|
|
1591
|
+
for (0.., f.tests) |i, *t| {
|
|
1592
|
+
if (t.limit == 0) continue;
|
|
1593
|
+
const latest_find = @max(t.batches - t.batches_since_find, 1);
|
|
1594
|
+
if (n < latest_find) return @intCast(i);
|
|
1595
|
+
n -= latest_find;
|
|
1596
|
+
}
|
|
1597
|
+
unreachable;
|
|
1598
|
+
} else {
|
|
1599
|
+
const total_weight = n_seen_pcs;
|
|
1600
|
+
var n = f.rngLessThan(u64, total_weight);
|
|
1601
|
+
for (0.., f.tests) |i, *t| {
|
|
1602
|
+
if (t.limit == 0) continue;
|
|
1603
|
+
const seen_pc_count = @max(t.seen_pc_count, 1);
|
|
1604
|
+
if (n < seen_pc_count) return @intCast(i);
|
|
1605
|
+
n -= seen_pc_count;
|
|
1606
|
+
}
|
|
1607
|
+
unreachable;
|
|
1608
|
+
}
|
|
1128
1609
|
}
|
|
1129
1610
|
|
|
1130
1611
|
fn weightsContain(int: u64, weights: []const abi.Weight) bool {
|
|
@@ -1184,8 +1665,9 @@ const Fuzzer = struct {
|
|
|
1184
1665
|
mutate: Untyped,
|
|
1185
1666
|
fresh: void,
|
|
1186
1667
|
} {
|
|
1187
|
-
const
|
|
1188
|
-
const
|
|
1668
|
+
const t = &f.tests[f.test_i];
|
|
1669
|
+
const corpus = t.corpus.slice();
|
|
1670
|
+
const corpus_i = @intFromEnum(t.corpus_pos);
|
|
1189
1671
|
const data = &corpus.items(.data)[corpus_i];
|
|
1190
1672
|
var small_entronopy: SmallEntronopy = .{ .bits = f.rngInt(u64) };
|
|
1191
1673
|
|
|
@@ -1276,7 +1758,7 @@ const Fuzzer = struct {
|
|
|
1276
1758
|
data_slice.len,
|
|
1277
1759
|
} else src: {
|
|
1278
1760
|
const seen_uid_i = corpus.items(.seen_uid_i)[corpus_i][uid_i];
|
|
1279
|
-
const untyped_slices =
|
|
1761
|
+
const untyped_slices = t.seen_uids.values()[seen_uid_i].slices;
|
|
1280
1762
|
switch (uid.kind) {
|
|
1281
1763
|
.int => {
|
|
1282
1764
|
const slices = untyped_slices.ints.items;
|
|
@@ -1404,7 +1886,7 @@ const Fuzzer = struct {
|
|
|
1404
1886
|
}
|
|
1405
1887
|
} else {
|
|
1406
1888
|
const seen_uid_i = corpus.items(.seen_uid_i)[corpus_i][uid_i];
|
|
1407
|
-
const untyped_slices =
|
|
1889
|
+
const untyped_slices = t.seen_uids.values()[seen_uid_i].slices;
|
|
1408
1890
|
switch (uid.kind) {
|
|
1409
1891
|
.int => {
|
|
1410
1892
|
const slices = untyped_slices.ints.items;
|
|
@@ -1432,11 +1914,12 @@ const Fuzzer = struct {
|
|
|
1432
1914
|
}
|
|
1433
1915
|
|
|
1434
1916
|
pub fn nextInt(f: *Fuzzer, uid: Uid, weights: []const abi.Weight) u64 {
|
|
1917
|
+
const t = &f.tests[f.test_i];
|
|
1435
1918
|
f.req_values += 1;
|
|
1436
|
-
if (@intFromEnum(
|
|
1919
|
+
if (@intFromEnum(t.corpus_pos) >= @intFromEnum(Input.Index.reserved_start)) {
|
|
1437
1920
|
@branchHint(.unlikely);
|
|
1438
1921
|
const int = f.bytes_input.valueWeightedWithHash(u64, weights, undefined);
|
|
1439
|
-
if (
|
|
1922
|
+
if (t.corpus_pos == .bytes_fresh) {
|
|
1440
1923
|
f.input_builder.checkSmithedLen(8);
|
|
1441
1924
|
f.input_builder.addInt(uid, int);
|
|
1442
1925
|
}
|
|
@@ -1455,11 +1938,12 @@ const Fuzzer = struct {
|
|
|
1455
1938
|
}
|
|
1456
1939
|
|
|
1457
1940
|
pub fn nextEos(f: *Fuzzer, uid: Uid, weights: []const abi.Weight) bool {
|
|
1941
|
+
const t = &f.tests[f.test_i];
|
|
1458
1942
|
f.req_values += 1;
|
|
1459
|
-
if (@intFromEnum(
|
|
1943
|
+
if (@intFromEnum(t.corpus_pos) >= @intFromEnum(Input.Index.reserved_start)) {
|
|
1460
1944
|
@branchHint(.unlikely);
|
|
1461
1945
|
const eos = f.bytes_input.eosWeightedWithHash(weights, undefined);
|
|
1462
|
-
if (
|
|
1946
|
+
if (t.corpus_pos == .bytes_fresh) {
|
|
1463
1947
|
f.input_builder.checkSmithedLen(1);
|
|
1464
1948
|
f.input_builder.addInt(uid, @intFromBool(eos));
|
|
1465
1949
|
}
|
|
@@ -1569,13 +2053,14 @@ const Fuzzer = struct {
|
|
|
1569
2053
|
}
|
|
1570
2054
|
|
|
1571
2055
|
pub fn nextBytes(f: *Fuzzer, uid: Uid, out: []u8, weights: []const abi.Weight) void {
|
|
2056
|
+
const t = &f.tests[f.test_i];
|
|
1572
2057
|
f.req_values += 1;
|
|
1573
2058
|
f.req_bytes +%= @truncate(out.len); // This function should panic since the 32-bit
|
|
1574
2059
|
// data limit is exceeded, so wrapping is fine.
|
|
1575
|
-
if (@intFromEnum(
|
|
2060
|
+
if (@intFromEnum(t.corpus_pos) >= @intFromEnum(Input.Index.reserved_start)) {
|
|
1576
2061
|
@branchHint(.unlikely);
|
|
1577
2062
|
f.bytes_input.bytesWeightedWithHash(out, weights, undefined);
|
|
1578
|
-
if (
|
|
2063
|
+
if (t.corpus_pos == .bytes_fresh) {
|
|
1579
2064
|
f.input_builder.checkSmithedLen(out.len);
|
|
1580
2065
|
f.input_builder.addBytes(uid, out);
|
|
1581
2066
|
}
|
|
@@ -1660,8 +2145,9 @@ const Fuzzer = struct {
|
|
|
1660
2145
|
len_weights: []const abi.Weight,
|
|
1661
2146
|
byte_weights: []const abi.Weight,
|
|
1662
2147
|
) u32 {
|
|
2148
|
+
const t = &f.tests[f.test_i];
|
|
1663
2149
|
f.req_values += 1;
|
|
1664
|
-
if (@intFromEnum(
|
|
2150
|
+
if (@intFromEnum(t.corpus_pos) >= @intFromEnum(Input.Index.reserved_start)) {
|
|
1665
2151
|
@branchHint(.unlikely);
|
|
1666
2152
|
const n = f.bytes_input.sliceWeightedWithHash(
|
|
1667
2153
|
buf,
|
|
@@ -1669,7 +2155,7 @@ const Fuzzer = struct {
|
|
|
1669
2155
|
byte_weights,
|
|
1670
2156
|
undefined,
|
|
1671
2157
|
);
|
|
1672
|
-
if (
|
|
2158
|
+
if (t.corpus_pos == .bytes_fresh) {
|
|
1673
2159
|
f.input_builder.checkSmithedLen(@as(usize, 4) + n);
|
|
1674
2160
|
f.input_builder.addBytes(uid, buf[0..n]);
|
|
1675
2161
|
}
|
|
@@ -1686,7 +2172,6 @@ const Fuzzer = struct {
|
|
|
1686
2172
|
|
|
1687
2173
|
export fn fuzzer_init(cache_dir_path: abi.Slice) void {
|
|
1688
2174
|
exec = .init(cache_dir_path.toSlice());
|
|
1689
|
-
fuzzer = .init();
|
|
1690
2175
|
}
|
|
1691
2176
|
|
|
1692
2177
|
export fn fuzzer_coverage() abi.Coverage {
|
|
@@ -1706,23 +2191,66 @@ export fn fuzzer_coverage() abi.Coverage {
|
|
|
1706
2191
|
};
|
|
1707
2192
|
}
|
|
1708
2193
|
|
|
1709
|
-
export fn
|
|
1710
|
-
|
|
1711
|
-
|
|
2194
|
+
export fn fuzzer_main(
|
|
2195
|
+
n_tests: u32,
|
|
2196
|
+
seed: u32,
|
|
2197
|
+
limit_kind: abi.LimitKind,
|
|
2198
|
+
amount_or_instance: u64,
|
|
2199
|
+
) void {
|
|
2200
|
+
fuzzer = .init(
|
|
2201
|
+
n_tests,
|
|
2202
|
+
seed ^ amount_or_instance, // seed is otherwise the same for all instances
|
|
2203
|
+
if (limit_kind == .forever) @as(u32, @intCast(amount_or_instance)) else 0,
|
|
2204
|
+
if (limit_kind == .forever) null else amount_or_instance,
|
|
2205
|
+
);
|
|
2206
|
+
defer fuzzer.deinit();
|
|
2207
|
+
abi.runner_start_input_poller();
|
|
2208
|
+
defer abi.runner_stop_input_poller();
|
|
2209
|
+
|
|
2210
|
+
if (n_tests == 1) {
|
|
2211
|
+
// no swapping between fuzz tests
|
|
2212
|
+
runTest(0);
|
|
2213
|
+
} else {
|
|
2214
|
+
while (fuzzer.select()) |i| {
|
|
2215
|
+
runTest(i);
|
|
2216
|
+
}
|
|
2217
|
+
}
|
|
2218
|
+
}
|
|
2219
|
+
|
|
2220
|
+
export fn fuzzer_receive_input(test_i: u32, bytes_slice: abi.Slice) bool {
|
|
2221
|
+
const recv = &fuzzer.tests[test_i].received;
|
|
2222
|
+
if (recv.state.startWrite()) return true;
|
|
2223
|
+
defer recv.state.finishWrite();
|
|
2224
|
+
|
|
2225
|
+
const bytes = bytes_slice.toSlice();
|
|
2226
|
+
const len: u32 = @intCast(bytes.len);
|
|
2227
|
+
recv.inputs.ensureUnusedCapacity(gpa, 4 + bytes.len) catch @panic("OOM");
|
|
2228
|
+
recv.inputs.appendSliceAssumeCapacity(@ptrCast(&len));
|
|
2229
|
+
recv.inputs.appendSliceAssumeCapacity(bytes);
|
|
2230
|
+
|
|
2231
|
+
return false;
|
|
2232
|
+
}
|
|
2233
|
+
|
|
2234
|
+
fn runTest(i: u32) void {
|
|
2235
|
+
fuzzer.test_i = i;
|
|
2236
|
+
fuzzer.mmap_input.setTest(i);
|
|
2237
|
+
current_test_name = abi.runner_test_name(i).toSlice();
|
|
2238
|
+
abi.runner_test_run(i);
|
|
2239
|
+
}
|
|
2240
|
+
|
|
2241
|
+
export fn fuzzer_set_test(test_one: abi.TestOne) void {
|
|
2242
|
+
fuzzer.test_one = test_one;
|
|
1712
2243
|
}
|
|
1713
2244
|
|
|
1714
2245
|
export fn fuzzer_new_input(bytes: abi.Slice) void {
|
|
1715
2246
|
if (bytes.len == 0) return; // An entry of length zero is always present
|
|
2247
|
+
if (fuzzer.tests[fuzzer.test_i].start_mut_corpus != math.maxInt(u32)) return; // Test ran previously
|
|
1716
2248
|
fuzzer.newInputExternal(bytes.toSlice());
|
|
1717
2249
|
}
|
|
1718
2250
|
|
|
1719
|
-
export fn
|
|
1720
|
-
fuzzer.
|
|
1721
|
-
|
|
1722
|
-
.forever => while (true) fuzzer.cycle(),
|
|
1723
|
-
.iterations => for (0..amount) |_| fuzzer.cycle(),
|
|
1724
|
-
}
|
|
1725
|
-
fuzzer.reset();
|
|
2251
|
+
export fn fuzzer_start_test() void {
|
|
2252
|
+
fuzzer.ensureCorpusLoaded();
|
|
2253
|
+
fuzzer.batch();
|
|
1726
2254
|
}
|
|
1727
2255
|
|
|
1728
2256
|
export fn fuzzer_int(uid: Uid, weights: abi.Weights) u64 {
|
|
@@ -1786,26 +2314,43 @@ export fn __sanitizer_cov_pcs_init(start: usize, end: usize) void {
|
|
|
1786
2314
|
/// Reusable and recoverable input.
|
|
1787
2315
|
///
|
|
1788
2316
|
/// Has a 32-bit limit on the input length. This has the nice side effect that `u32`
|
|
1789
|
-
/// can be used in most placed in `fuzzer` with the last
|
|
2317
|
+
/// can be used in most placed in `fuzzer` with the last `@sizeOf(abi.MmapInputHeader)`
|
|
2318
|
+
/// values reserved.
|
|
1790
2319
|
const MemoryMappedInput = struct {
|
|
2320
|
+
const Header = abi.MmapInputHeader;
|
|
2321
|
+
|
|
1791
2322
|
len: u32,
|
|
1792
2323
|
/// Directly accessing `memory` is unsafe, use either `inputSlice` or `writeSlice`.
|
|
1793
|
-
///
|
|
1794
|
-
/// `memory` starts with the length of the input as a little-endian 32-bit integer.
|
|
1795
2324
|
mmap: Io.File.MemoryMap,
|
|
2325
|
+
in_i: u32,
|
|
1796
2326
|
|
|
1797
2327
|
/// `file` becomes owned by the returned `MemoryMappedInput`
|
|
1798
|
-
pub fn init(file: Io.File,
|
|
1799
|
-
|
|
2328
|
+
pub fn init(file: Io.File, instance_id: u32, in_i: u32) MemoryMappedInput {
|
|
2329
|
+
var size = file.length(io) catch |e|
|
|
2330
|
+
panic("failed to get length of 'in{x}': {t}", .{ in_i, e });
|
|
2331
|
+
if (size < std.heap.page_size_max) {
|
|
2332
|
+
size = std.heap.page_size_max;
|
|
2333
|
+
file.setLength(io, size) catch |e|
|
|
2334
|
+
panic("failed to resize 'in{x}': {t}", .{ in_i, e });
|
|
2335
|
+
}
|
|
2336
|
+
const map = file.createMemoryMap(io, .{ .len = size }) catch |e|
|
|
2337
|
+
panic("failed to memmap input file 'in{x}': {t}", .{ in_i, e });
|
|
2338
|
+
@as(*volatile Header, @ptrCast(map.memory)).* = .{
|
|
2339
|
+
.pc_digest = mem.nativeToLittle(u64, exec.pc_digest),
|
|
2340
|
+
.instance_id = mem.nativeToLittle(u32, instance_id),
|
|
2341
|
+
.test_i = 0,
|
|
2342
|
+
.len = 0,
|
|
2343
|
+
};
|
|
1800
2344
|
return .{
|
|
1801
2345
|
.len = 0,
|
|
1802
|
-
.mmap =
|
|
2346
|
+
.mmap = map,
|
|
2347
|
+
.in_i = in_i,
|
|
1803
2348
|
};
|
|
1804
2349
|
}
|
|
1805
2350
|
|
|
1806
2351
|
pub fn deinit(l: *MemoryMappedInput) void {
|
|
1807
2352
|
const f = l.mmap.file;
|
|
1808
|
-
l.mmap.write(io) catch |e| panic("failed to write memory map of 'in': {t}", .{e});
|
|
2353
|
+
l.mmap.write(io) catch |e| panic("failed to write memory map of 'in{x}': {t}", .{ l.in_i, e });
|
|
1809
2354
|
l.mmap.destroy(io);
|
|
1810
2355
|
f.close(io);
|
|
1811
2356
|
l.* = undefined;
|
|
@@ -1815,40 +2360,36 @@ const MemoryMappedInput = struct {
|
|
|
1815
2360
|
///
|
|
1816
2361
|
/// Invalidates element pointers if additional memory is needed.
|
|
1817
2362
|
pub fn ensureUnusedCapacity(l: *MemoryMappedInput, additional_count: usize) void {
|
|
1818
|
-
return l.
|
|
2363
|
+
return l.ensureSize(@sizeOf(Header) + l.len + additional_count);
|
|
1819
2364
|
}
|
|
1820
2365
|
|
|
1821
|
-
|
|
1822
|
-
/// modify the array so that it can hold at least `min_capacity` items.
|
|
1823
|
-
///
|
|
1824
|
-
/// Invalidates element pointers if additional memory is needed.
|
|
1825
|
-
pub fn ensureTotalCapacity(l: *MemoryMappedInput, min_capacity: usize) void {
|
|
2366
|
+
fn ensureSize(l: *MemoryMappedInput, min_capacity: usize) void {
|
|
1826
2367
|
if (l.mmap.memory.len < min_capacity) {
|
|
1827
2368
|
@branchHint(.unlikely);
|
|
1828
2369
|
|
|
1829
|
-
const max_capacity = 1 << 32; // The size of the
|
|
2370
|
+
const max_capacity = 1 << 32; // The size of the header is not added
|
|
1830
2371
|
// in order to keep the capacity page aligned and to allow those values to
|
|
1831
2372
|
// reserved for other places.
|
|
1832
2373
|
if (min_capacity > max_capacity) @panic("too much smith data requested");
|
|
1833
2374
|
|
|
1834
2375
|
const new_capacity = @min(growCapacity(min_capacity), max_capacity);
|
|
1835
2376
|
l.mmap.file.setLength(io, new_capacity) catch |e|
|
|
1836
|
-
panic("failed to resize 'in': {t}", .{e});
|
|
2377
|
+
panic("failed to resize 'in{x}': {t}", .{ l.in_i, e });
|
|
1837
2378
|
l.mmap.setLength(io, new_capacity) catch |se| switch (se) {
|
|
1838
2379
|
error.OperationUnsupported => {
|
|
1839
2380
|
const f = l.mmap.file;
|
|
1840
2381
|
l.mmap.destroy(io);
|
|
1841
2382
|
l.mmap = f.createMemoryMap(io, .{ .len = new_capacity }) catch |e|
|
|
1842
|
-
panic("failed to memory map 'in': {t}", .{e});
|
|
2383
|
+
panic("failed to memory map 'in{x}': {t}", .{ l.in_i, e });
|
|
1843
2384
|
},
|
|
1844
|
-
else => panic("failed to resize memory map of 'in': {t}", .{se}),
|
|
2385
|
+
else => panic("failed to resize memory map of 'in{x}': {t}", .{ l.in_i, se }),
|
|
1845
2386
|
};
|
|
1846
2387
|
}
|
|
1847
2388
|
}
|
|
1848
2389
|
|
|
1849
2390
|
// Only writing has side effects, so volatile is not needed
|
|
1850
2391
|
pub fn inputSlice(l: *MemoryMappedInput) []const u8 {
|
|
1851
|
-
return l.mmap.memory[
|
|
2392
|
+
return l.mmap.memory[@sizeOf(Header)..][0..l.len];
|
|
1852
2393
|
}
|
|
1853
2394
|
|
|
1854
2395
|
// Writing has side effectsd, so volatile is necessary
|
|
@@ -1857,7 +2398,13 @@ const MemoryMappedInput = struct {
|
|
|
1857
2398
|
}
|
|
1858
2399
|
|
|
1859
2400
|
fn writeLen(l: *MemoryMappedInput) void {
|
|
1860
|
-
l.writeSlice()[0..4].* =
|
|
2401
|
+
l.writeSlice()[@offsetOf(Header, "len")..][0..4].* =
|
|
2402
|
+
@bitCast(mem.nativeToLittle(u32, l.len));
|
|
2403
|
+
}
|
|
2404
|
+
|
|
2405
|
+
pub fn setTest(l: *MemoryMappedInput, i: u32) void {
|
|
2406
|
+
l.writeSlice()[@offsetOf(Header, "test_i")..][0..4].* =
|
|
2407
|
+
@bitCast(mem.nativeToLittle(u32, i));
|
|
1861
2408
|
}
|
|
1862
2409
|
|
|
1863
2410
|
/// Invalidates all element pointers.
|
|
@@ -1871,7 +2418,7 @@ const MemoryMappedInput = struct {
|
|
|
1871
2418
|
/// Invalidates item pointers if more space is required.
|
|
1872
2419
|
pub fn appendSlice(l: *MemoryMappedInput, items: []const u8) void {
|
|
1873
2420
|
l.ensureUnusedCapacity(items.len);
|
|
1874
|
-
@memcpy(l.writeSlice()[
|
|
2421
|
+
@memcpy(l.writeSlice()[@sizeOf(Header) + l.len ..][0..items.len], items);
|
|
1875
2422
|
l.len += @as(u32, @intCast(items.len));
|
|
1876
2423
|
l.writeLen();
|
|
1877
2424
|
}
|
|
@@ -1881,7 +2428,8 @@ const MemoryMappedInput = struct {
|
|
|
1881
2428
|
/// Invalidates item pointers if more space is required.
|
|
1882
2429
|
pub fn appendLittleInt(l: *MemoryMappedInput, T: type, x: T) void {
|
|
1883
2430
|
l.ensureUnusedCapacity(@sizeOf(T));
|
|
1884
|
-
l.writeSlice()[
|
|
2431
|
+
l.writeSlice()[@sizeOf(Header) + l.len ..][0..@sizeOf(T)].* =
|
|
2432
|
+
@bitCast(mem.nativeToLittle(T, x));
|
|
1885
2433
|
l.len += @sizeOf(T);
|
|
1886
2434
|
l.writeLen();
|
|
1887
2435
|
}
|