Zig 0.17.0-dev (Split by item)

This is an example of documentation generated by ZigDoc, an alternative to Zig's built-in Auto Doc feature. See also examples in other modes/formats. The project being documented here (as the example) is the Zig library itself.

Executable

fuzzer.Executable
const Executable = struct

File

lib/fuzzer.zig:58

Code

const Executable = struct {
    /// Tracks the hit count for each pc as updated by the test's instrumentation.
    pc_counters: []u8,

    cache_f: Io.Dir,
    /// Shared copy of all pcs that have been hit stored in a memory-mapped file that can viewed
    /// while the fuzzer is running.
    shared_seen_pcs: []align(std.heap.page_size_min) volatile u8,
    /// Hash of pcs used to uniquely identify the shared coverage file
    pc_digest: u64,

    fn getCoverageMap(
        cache_dir: Io.Dir,
        pcs: []const usize,
        pc_digest: u64,
    ) []align(std.heap.page_size_min) volatile u8 {
        const file_name = std.fmt.hex(pc_digest);

        var v = cache_dir.createDirPathOpen(io, "v", .{}) catch |e|
            panic("failed to create directory 'v': {t}", .{e});
        defer v.close(io);

        // Since acquiring locks in createFile is not gauraunteed to be atomic, it is not possible
        // to ensure if we create the file we obtain an exclusive lock to populate it since another
        // process may acquire a shared lock between the file being created and the lock request.
        //
        // Instead, the length will be used to determine if the file needs populated, and no
        // process will acquire a shared lock before the coverage file is known to have been
        // exclusively locked (i.e. is already locked). This means another process than the
        // one which created the file could populate it, which is fine.
        const coverage_file = v.createFile(io, &file_name, .{
            .read = true,
            .truncate = false,
        }) catch |e| panic("failed to open coverage file '{s}': {t}", .{ &file_name, e });

        const maybe_populate = coverage_file.tryLock(io, .exclusive) catch |e| panic(
            "failed to acquire exclusive lock coverage file '{s}': {t}",
            .{ &file_name, e },
        );
        if (!maybe_populate) {
            coverage_file.lock(io, .shared) catch |e|
                panic("failed to acquire share lock coverage file '{s}': {t}", .{ &file_name, e });
        }

        comptime assert(abi.SeenPcsHeader.trailing[0] == .pc_bits_usize);
        comptime assert(abi.SeenPcsHeader.trailing[1] == .pc_addr);
        const pc_bitset_usizes = bitsetUsizes(pcs.len);
        const coverage_file_len = @sizeOf(abi.SeenPcsHeader) +
            pc_bitset_usizes * @sizeOf(usize) +
            pcs.len * @sizeOf(usize);

        var populate: bool = false;
        const size = coverage_file.length(io) catch |e|
            panic("failed to stat coverage file '{s}': {t}", .{ &file_name, e });
        if (size == 0 and maybe_populate) {
            coverage_file.setLength(io, coverage_file_len) catch |e|
                panic("failed to resize new coverage file '{s}': {t}", .{ &file_name, e });
            populate = true;
        } else if (size != coverage_file_len) {
            panic(
                "incompatible existing coverage file '{s}' (differing lengths: {} != {})",
                .{ &file_name, size, coverage_file_len },
            );
        } else if (maybe_populate) {
            coverage_file.lock(io, .shared) catch |e|
                panic("failed to demote lock for coverage file '{s}': {t}", .{ &file_name, e });
        }

        var io_map = coverage_file.createMemoryMap(io, .{ .len = coverage_file_len }) catch |e|
            panic("failed to memmap coverage file '{s}': {t}", .{ &file_name, e });
        const map = io_map.memory;

        const header: *abi.SeenPcsHeader = @ptrCast(map[0..@sizeOf(abi.SeenPcsHeader)]);
        const trailing = map[@sizeOf(abi.SeenPcsHeader)..];
        const trailing_bitset_end = pc_bitset_usizes * @sizeOf(usize);
        const trailing_bitset: []usize = @ptrCast(@alignCast(trailing[0..trailing_bitset_end]));
        const trailing_addresses: []usize = @ptrCast(@alignCast(trailing[trailing_bitset_end..]));

        if (populate) {
            header.* = .{
                .n_runs = 0,
                .unique_runs = 0,
                .pcs_len = pcs.len,
            };
            @memset(trailing_bitset, 0);
            for (trailing_addresses, pcs) |*cov_pc, slided_pc| {
                cov_pc.* = fuzzer_unslide_address(slided_pc);
            }
            io_map.write(io) catch |e|
                panic("failed to write memory map of '{s}': {t}", .{ &file_name, e });

            coverage_file.lock(io, .shared) catch |e| panic(
                "failed to demote lock for coverage file '{s}': {t}",
                .{ &file_name, e },
            );
        } else { // Check expected contents
            if (header.pcs_len != pcs.len) panic(
                "incompatible existing coverage file '{s}' (differing pcs length: {} != {})",
                .{ &file_name, header.pcs_len, pcs.len },
            );
            for (0.., header.pcAddrs(), pcs) |i, cov_pc, slided_pc| {
                const pc = fuzzer_unslide_address(slided_pc);
                if (cov_pc != pc) panic(
                    "incompatible existing coverage file '{s}' (differing pc at index {d}: {x} != {x})",
                    .{ &file_name, i, cov_pc, pc },
                );
            }
        }
        return map;
    }

    pub fn init(cache_dir_path: []const u8) Executable {
        var self: Executable = undefined;

        const cache_dir = Io.Dir.cwd().createDirPathOpen(io, cache_dir_path, .{}) catch |e|
            panic("failed to open directory '{s}': {t}", .{ cache_dir_path, e });
        cache_dir.createDirPath(io, "tmp") catch |e|
            panic("failed to create directory 'tmp': {t}", .{e});
        log_f = cache_dir.createFile(io, "tmp/libfuzzer.log", .{ .truncate = false }) catch |e|
            panic("failed to create file 'tmp/libfuzzer.log': {t}", .{e});
        self.cache_f = cache_dir.createDirPathOpen(io, "f", .{}) catch |e|
            panic("failed to open directory 'f': {t}", .{e});

        // Linkers are expected to automatically add symbols prefixed with these for the start and
        // end of sections whose names are valid C identifiers.
        const ofmt = builtin.object_format;
        const section_start_prefix, const section_end_prefix = switch (ofmt) {
            .elf => .{ "__start_", "__stop_" },
            .macho => .{ "\x01section$start$__DATA$", "\x01section$end$__DATA$" },
            else => @compileError("unsupported fuzzing object format '" ++ @tagName(ofmt) ++ "'"),
        };

        self.pc_counters = blk: {
            const pc_counters_start_name = section_start_prefix ++ "__sancov_cntrs";
            const pc_counters_start = @extern([*]u8, .{
                .name = pc_counters_start_name,
                .linkage = .weak,
            }) orelse panic("missing {s} symbol", .{pc_counters_start_name});

            const pc_counters_end_name = section_end_prefix ++ "__sancov_cntrs";
            const pc_counters_end = @extern([*]u8, .{
                .name = pc_counters_end_name,
                .linkage = .weak,
            }) orelse panic("missing {s} symbol", .{pc_counters_end_name});

            break :blk pc_counters_start[0 .. pc_counters_end - pc_counters_start];
        };

        const pcs = blk: {
            const pcs_start_name = section_start_prefix ++ "__sancov_pcs1";
            const pcs_start = @extern([*]usize, .{
                .name = pcs_start_name,
                .linkage = .weak,
            }) orelse panic("missing {s} symbol", .{pcs_start_name});

            const pcs_end_name = section_end_prefix ++ "__sancov_pcs1";
            const pcs_end = @extern([*]usize, .{
                .name = pcs_end_name,
                .linkage = .weak,
            }) orelse panic("missing {s} symbol", .{pcs_end_name});

            break :blk pcs_start[0 .. pcs_end - pcs_start];
        };

        if (self.pc_counters.len != pcs.len) panic(
            "pc counters length and pcs length do not match ({} != {})",
            .{ self.pc_counters.len, pcs.len },
        );

        self.pc_digest = digest: {
            // Relocations have been applied to `pcs` so it contains runtime addresses (with slide
            // applied). We need to translate these to the virtual addresses as on disk.
            var h: std.hash.Wyhash = .init(0);
            for (pcs) |pc| {
                const pc_vaddr = fuzzer_unslide_address(pc);
                h.update(@ptrCast(&pc_vaddr));
            }
            break :digest h.final();
        };
        self.shared_seen_pcs = getCoverageMap(cache_dir, pcs, self.pc_digest);

        return self;
    }

    /// Asserts `buf[0..2]` is "in"
    fn inputFileName(buf: *[10]u8, i: u32) []u8 {
        assert(buf[0..2].* == "in".*);
        const hex = std.fmt.bufPrint(buf[2..], "{x}", .{i}) catch unreachable;
        return buf[0 .. 2 + hex.len];
    }

    pub fn pcBitsetIterator(self: Executable) PcBitsetIterator {
        return .{ .pc_counters = self.pc_counters };
    }

    /// Iterates over pc_counters returning a bitset for if each of them have been hit
    pub const PcBitsetIterator = struct {
        index: usize = 0,
        pc_counters: []u8,

        pub fn next(i: *PcBitsetIterator) usize {
            const rest = i.pc_counters[i.index..];
            if (rest.len >= @bitSizeOf(usize)) {
                defer i.index += @bitSizeOf(usize);
                const V = @Vector(@bitSizeOf(usize), u8);
                return @as(usize, @bitCast(@as(V, @splat(0)) != rest[0..@bitSizeOf(usize)].*));
            } else if (rest.len != 0) {
                defer i.index += rest.len;
                var res: usize = 0;
                for (0.., rest) |bit_index, byte| {
                    res |= @shlExact(@as(usize, @intFromBool(byte != 0)), @intCast(bit_index));
                }
                return res;
            } else unreachable;
        }
    };

    pub fn seenPcsHeader(e: Executable) *align(std.heap.page_size_min) volatile abi.SeenPcsHeader {
        return mem.bytesAsValue(
            abi.SeenPcsHeader,
            e.shared_seen_pcs[0..@sizeOf(abi.SeenPcsHeader)],
        );
    }
}