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