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}