| 1 | const Coverage = @This(); |
| 2 | |
| 3 | const std = @import("../std.zig"); |
| 4 | const Io = std.Io; |
| 5 | const Allocator = std.mem.Allocator; |
| 6 | const Hash = std.hash.Wyhash; |
| 7 | const Dwarf = std.debug.Dwarf; |
| 8 | const assert = std.debug.assert; |
| 9 | |
| 10 | /// Provides a globally-scoped integer index for directories. |
| 11 | /// |
| 12 | /// As opposed to, for example, a directory index that is compilation-unit |
| 13 | /// scoped inside a single ELF module. |
| 14 | /// |
| 15 | /// String memory references the memory-mapped debug information. |
| 16 | /// |
| 17 | /// Protected by `mutex`. |
| 18 | directories: std.array_hash_map.Custom(String, void, String.MapContext, false), |
| 19 | /// Provides a globally-scoped integer index for files. |
| 20 | /// |
| 21 | /// String memory references the memory-mapped debug information. |
| 22 | /// |
| 23 | /// Protected by `mutex`. |
| 24 | files: std.array_hash_map.Custom(File, void, File.MapContext, false), |
| 25 | string_bytes: std.ArrayList(u8), |
| 26 | /// Protects the other fields. |
| 27 | mutex: Io.Mutex, |
| 28 | |
| 29 | pub const init: Coverage = .{ |
| 30 | .directories = .empty, |
| 31 | .files = .empty, |
| 32 | .mutex = .init, |
| 33 | .string_bytes = .empty, |
| 34 | }; |
| 35 | |
| 36 | pub const String = enum(u32) { |
| 37 | _, |
| 38 | |
| 39 | pub const MapContext = struct { |
| 40 | string_bytes: []const u8, |
| 41 | |
| 42 | pub fn eql(self: @This(), a: String, b: String, b_index: usize) bool { |
| 43 | _ = b_index; |
| 44 | const a_slice = span(self.string_bytes[@backingInt(a)..]); |
| 45 | const b_slice = span(self.string_bytes[@backingInt(b)..]); |
| 46 | return std.mem.eql(u8, a_slice, b_slice); |
| 47 | } |
| 48 | |
| 49 | pub fn hash(self: @This(), a: String) u32 { |
| 50 | return @truncate(Hash.hash(0, span(self.string_bytes[@backingInt(a)..]))); |
| 51 | } |
| 52 | }; |
| 53 | |
| 54 | pub const SliceAdapter = struct { |
| 55 | string_bytes: []const u8, |
| 56 | |
| 57 | pub fn eql(self: @This(), a_slice: []const u8, b: String, b_index: usize) bool { |
| 58 | _ = b_index; |
| 59 | const b_slice = span(self.string_bytes[@backingInt(b)..]); |
| 60 | return std.mem.eql(u8, a_slice, b_slice); |
| 61 | } |
| 62 | pub fn hash(self: @This(), a: []const u8) u32 { |
| 63 | _ = self; |
| 64 | return @truncate(Hash.hash(0, a)); |
| 65 | } |
| 66 | }; |
| 67 | }; |
| 68 | |
| 69 | pub const SourceLocation = extern struct { |
| 70 | file: File.Index, |
| 71 | line: u32, |
| 72 | column: u32, |
| 73 | |
| 74 | pub const invalid: SourceLocation = .{ |
| 75 | .file = .invalid, |
| 76 | .line = 0, |
| 77 | .column = 0, |
| 78 | }; |
| 79 | }; |
| 80 | |
| 81 | pub const File = extern struct { |
| 82 | directory_index: u32, |
| 83 | basename: String, |
| 84 | |
| 85 | pub const Index = enum(u32) { |
| 86 | invalid = std.math.maxInt(u32), |
| 87 | _, |
| 88 | }; |
| 89 | |
| 90 | pub const MapContext = struct { |
| 91 | string_bytes: []const u8, |
| 92 | |
| 93 | pub fn hash(self: MapContext, a: File) u32 { |
| 94 | const a_basename = span(self.string_bytes[@backingInt(a.basename)..]); |
| 95 | return @truncate(Hash.hash(a.directory_index, a_basename)); |
| 96 | } |
| 97 | |
| 98 | pub fn eql(self: MapContext, a: File, b: File, b_index: usize) bool { |
| 99 | _ = b_index; |
| 100 | if (a.directory_index != b.directory_index) return false; |
| 101 | const a_basename = span(self.string_bytes[@backingInt(a.basename)..]); |
| 102 | const b_basename = span(self.string_bytes[@backingInt(b.basename)..]); |
| 103 | return std.mem.eql(u8, a_basename, b_basename); |
| 104 | } |
| 105 | }; |
| 106 | |
| 107 | pub const SliceAdapter = struct { |
| 108 | string_bytes: []const u8, |
| 109 | |
| 110 | pub const Entry = struct { |
| 111 | directory_index: u32, |
| 112 | basename: []const u8, |
| 113 | }; |
| 114 | |
| 115 | pub fn hash(self: @This(), a: Entry) u32 { |
| 116 | _ = self; |
| 117 | return @truncate(Hash.hash(a.directory_index, a.basename)); |
| 118 | } |
| 119 | |
| 120 | pub fn eql(self: @This(), a: Entry, b: File, b_index: usize) bool { |
| 121 | _ = b_index; |
| 122 | if (a.directory_index != b.directory_index) return false; |
| 123 | const b_basename = span(self.string_bytes[@backingInt(b.basename)..]); |
| 124 | return std.mem.eql(u8, a.basename, b_basename); |
| 125 | } |
| 126 | }; |
| 127 | }; |
| 128 | |
| 129 | pub fn deinit(cov: *Coverage, gpa: Allocator) void { |
| 130 | cov.directories.deinit(gpa); |
| 131 | cov.files.deinit(gpa); |
| 132 | cov.string_bytes.deinit(gpa); |
| 133 | cov.* = undefined; |
| 134 | } |
| 135 | |
| 136 | pub fn fileAt(cov: *Coverage, index: File.Index) *File { |
| 137 | return &cov.files.keys()[@backingInt(index)]; |
| 138 | } |
| 139 | |
| 140 | pub fn stringAt(cov: *Coverage, index: String) [:0]const u8 { |
| 141 | return span(cov.string_bytes.items[@backingInt(index)..]); |
| 142 | } |
| 143 | |
| 144 | pub const ResolveAddressesDwarfError = Dwarf.ScanError || Io.Cancelable; |
| 145 | |
| 146 | pub fn resolveAddressesDwarf( |
| 147 | cov: *Coverage, |
| 148 | gpa: Allocator, |
| 149 | io: Io, |
| 150 | endian: std.builtin.Endian, |
| 151 | /// Asserts the addresses are in ascending order. |
| 152 | sorted_pc_addrs: []const u64, |
| 153 | /// Asserts its length equals length of `sorted_pc_addrs`. |
| 154 | output: []SourceLocation, |
| 155 | d: *Dwarf, |
| 156 | ) ResolveAddressesDwarfError!void { |
| 157 | assert(sorted_pc_addrs.len == output.len); |
| 158 | assert(d.ranges.items.len != 0); // call `populateRanges` first. |
| 159 | |
| 160 | var range_i: usize = 0; |
| 161 | var range: *std.debug.Dwarf.Range = &d.ranges.items[0]; |
| 162 | var line_table_i: usize = undefined; |
| 163 | var prev_pc: u64 = 0; |
| 164 | var prev_cu: ?*std.debug.Dwarf.CompileUnit = null; |
| 165 | // Protects directories and files tables from other threads. |
| 166 | try cov.mutex.lock(io); |
| 167 | defer cov.mutex.unlock(io); |
| 168 | next_pc: for (sorted_pc_addrs, output) |pc, *out| { |
| 169 | assert(pc >= prev_pc); |
| 170 | prev_pc = pc; |
| 171 | |
| 172 | while (pc >= range.end) { |
| 173 | range_i += 1; |
| 174 | if (range_i >= d.ranges.items.len) { |
| 175 | out.* = SourceLocation.invalid; |
| 176 | continue :next_pc; |
| 177 | } |
| 178 | range = &d.ranges.items[range_i]; |
| 179 | } |
| 180 | if (pc < range.start) { |
| 181 | out.* = SourceLocation.invalid; |
| 182 | continue :next_pc; |
| 183 | } |
| 184 | const cu = &d.compile_unit_list.items[range.compile_unit_index]; |
| 185 | if (cu != prev_cu) { |
| 186 | prev_cu = cu; |
| 187 | if (cu.src_loc_cache == null) { |
| 188 | cov.mutex.unlock(io); |
| 189 | defer cov.mutex.lockUncancelable(io); |
| 190 | d.populateSrcLocCache(gpa, endian, cu) catch |err| switch (err) { |
| 191 | error.MissingDebugInfo, error.InvalidDebugInfo => { |
| 192 | out.* = SourceLocation.invalid; |
| 193 | continue :next_pc; |
| 194 | }, |
| 195 | else => |e| return e, |
| 196 | }; |
| 197 | } |
| 198 | const slc = &cu.src_loc_cache.?; |
| 199 | const table_addrs = slc.line_table.keys(); |
| 200 | line_table_i = std.sort.upperBound(u64, table_addrs, pc, struct { |
| 201 | fn order(context: u64, item: u64) std.math.Order { |
| 202 | return std.math.order(context, item); |
| 203 | } |
| 204 | }.order); |
| 205 | } |
| 206 | const slc = &cu.src_loc_cache.?; |
| 207 | const table_addrs = slc.line_table.keys(); |
| 208 | while (line_table_i < table_addrs.len and table_addrs[line_table_i] <= pc) line_table_i += 1; |
| 209 | |
| 210 | const entry = slc.line_table.values()[line_table_i - 1]; |
| 211 | const corrected_file_index = entry.file - @intFromBool(slc.version < 5); |
| 212 | const file_entry = slc.files[corrected_file_index]; |
| 213 | const dir_path = slc.directories[file_entry.dir_index].path; |
| 214 | try cov.string_bytes.ensureUnusedCapacity(gpa, dir_path.len + file_entry.path.len + 2); |
| 215 | const dir_gop = try cov.directories.getOrPutContextAdapted(gpa, dir_path, String.SliceAdapter{ |
| 216 | .string_bytes = cov.string_bytes.items, |
| 217 | }, String.MapContext{ |
| 218 | .string_bytes = cov.string_bytes.items, |
| 219 | }); |
| 220 | if (!dir_gop.found_existing) |
| 221 | dir_gop.key_ptr.* = addStringAssumeCapacity(cov, dir_path); |
| 222 | const file_gop = try cov.files.getOrPutContextAdapted(gpa, File.SliceAdapter.Entry{ |
| 223 | .directory_index = @intCast(dir_gop.index), |
| 224 | .basename = file_entry.path, |
| 225 | }, File.SliceAdapter{ |
| 226 | .string_bytes = cov.string_bytes.items, |
| 227 | }, File.MapContext{ |
| 228 | .string_bytes = cov.string_bytes.items, |
| 229 | }); |
| 230 | if (!file_gop.found_existing) file_gop.key_ptr.* = .{ |
| 231 | .directory_index = @intCast(dir_gop.index), |
| 232 | .basename = addStringAssumeCapacity(cov, file_entry.path), |
| 233 | }; |
| 234 | out.* = .{ |
| 235 | .file = @fromBackingInt(@intCast(file_gop.index)), |
| 236 | .line = entry.line, |
| 237 | .column = entry.column, |
| 238 | }; |
| 239 | } |
| 240 | } |
| 241 | |
| 242 | pub fn addStringAssumeCapacity(cov: *Coverage, s: []const u8) String { |
| 243 | const result: String = @fromBackingInt(@intCast(cov.string_bytes.items.len)); |
| 244 | cov.string_bytes.appendSliceAssumeCapacity(s); |
| 245 | cov.string_bytes.appendAssumeCapacity(0); |
| 246 | return result; |
| 247 | } |
| 248 | |
| 249 | fn span(s: []const u8) [:0]const u8 { |
| 250 | return std.mem.sliceTo(@as([:0]const u8, @ptrCast(s)), 0); |
| 251 | } |