master
  1//! Reads a Zig coverage file and prints human-readable information to stdout,
  2//! including file:line:column information for each PC.
  3
  4const std = @import("std");
  5const fatal = std.process.fatal;
  6const Path = std.Build.Cache.Path;
  7const assert = std.debug.assert;
  8const SeenPcsHeader = std.Build.abi.fuzz.SeenPcsHeader;
  9
 10pub fn main() !void {
 11    var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
 12    defer _ = debug_allocator.deinit();
 13    const gpa = debug_allocator.allocator();
 14
 15    var arena_instance: std.heap.ArenaAllocator = .init(gpa);
 16    defer arena_instance.deinit();
 17    const arena = arena_instance.allocator();
 18
 19    var threaded: std.Io.Threaded = .init(gpa);
 20    defer threaded.deinit();
 21    const io = threaded.io();
 22
 23    const args = try std.process.argsAlloc(arena);
 24
 25    const target_query_str = switch (args.len) {
 26        3 => "native",
 27        4 => args[3],
 28        else => return fatal(
 29            \\usage: {0s} path/to/exe path/to/coverage [target]
 30            \\  if omitted, 'target' defaults to 'native'
 31            \\  example: {0s} zig-out/test .zig-cache/v/xxxxxxxx x86_64-linux
 32        , .{if (args.len == 0) "dump-cov" else args[0]}),
 33    };
 34
 35    const target = std.zig.resolveTargetQueryOrFatal(io, try .parse(.{
 36        .arch_os_abi = target_query_str,
 37    }));
 38
 39    const exe_file_name = args[1];
 40    const cov_file_name = args[2];
 41
 42    const exe_path: Path = .{
 43        .root_dir = .cwd(),
 44        .sub_path = exe_file_name,
 45    };
 46    const cov_path: Path = .{
 47        .root_dir = .cwd(),
 48        .sub_path = cov_file_name,
 49    };
 50
 51    var coverage: std.debug.Coverage = .init;
 52    defer coverage.deinit(gpa);
 53
 54    var debug_info = std.debug.Info.load(gpa, exe_path, &coverage, target.ofmt, target.cpu.arch) catch |err| {
 55        fatal("failed to load debug info for {f}: {s}", .{ exe_path, @errorName(err) });
 56    };
 57    defer debug_info.deinit(gpa);
 58
 59    const cov_bytes = cov_path.root_dir.handle.readFileAllocOptions(
 60        cov_path.sub_path,
 61        arena,
 62        .limited(1 << 30),
 63        .of(SeenPcsHeader),
 64        null,
 65    ) catch |err| {
 66        fatal("failed to load coverage file {f}: {s}", .{ cov_path, @errorName(err) });
 67    };
 68
 69    var stdout_buffer: [4000]u8 = undefined;
 70    var stdout_writer = std.fs.File.stdout().writerStreaming(&stdout_buffer);
 71    const stdout = &stdout_writer.interface;
 72
 73    const header: *SeenPcsHeader = @ptrCast(cov_bytes);
 74    try stdout.print("{any}\n", .{header.*});
 75    const pcs = header.pcAddrs();
 76
 77    var indexed_pcs: std.AutoArrayHashMapUnmanaged(usize, void) = .empty;
 78    try indexed_pcs.entries.resize(arena, pcs.len);
 79    @memcpy(indexed_pcs.entries.items(.key), pcs);
 80    try indexed_pcs.reIndex(arena);
 81
 82    const sorted_pcs = try arena.dupe(usize, pcs);
 83    std.mem.sortUnstable(usize, sorted_pcs, {}, std.sort.asc(usize));
 84
 85    const source_locations = try arena.alloc(std.debug.Coverage.SourceLocation, sorted_pcs.len);
 86    try debug_info.resolveAddresses(gpa, sorted_pcs, source_locations);
 87
 88    const seen_pcs = header.seenBits();
 89
 90    for (sorted_pcs, source_locations) |pc, sl| {
 91        if (sl.file == .invalid) {
 92            try stdout.print(" {x}: invalid\n", .{pc});
 93            continue;
 94        }
 95        const file = debug_info.coverage.fileAt(sl.file);
 96        const dir_name = debug_info.coverage.directories.keys()[file.directory_index];
 97        const dir_name_slice = debug_info.coverage.stringAt(dir_name);
 98        const seen_i = indexed_pcs.getIndex(pc).?;
 99        const hit: u1 = @truncate(seen_pcs[seen_i / @bitSizeOf(usize)] >> @intCast(seen_i % @bitSizeOf(usize)));
100        try stdout.print("{c}{x}: {s}/{s}:{d}:{d}\n", .{
101            "-+"[hit], pc, dir_name_slice, debug_info.coverage.stringAt(file.basename), sl.line, sl.column,
102        });
103    }
104
105    try stdout.flush();
106}