| author | |
| committer | |
| log | 0e99f517f2a1c8c19d7350a221539521b365230e |
| tree | f487716661a5771d7c41f45106bf264da1ea133f |
| parent | f9f894200891c8af6ce3a3ad222cd0bf1ee15587 |
| parent | d721d9af69220c059a7be825d295a0b53081c4a0 |
| signature |
introduce a fuzz testing web interface34 files changed, 3921 insertions(+), 1078 deletions(-)
README.md+3-5| ... | ... | @@ -13,11 +13,9 @@ Documentation** corresponding to the version of Zig that you are using by |
| 13 | 13 | following the appropriate link on the |
| 14 | 14 | [download page](https://ziglang.org/download). |
| 15 | 15 | |
| 16 | Otherwise, you're looking at a release of Zig, and you can find documentation | |
| 17 | here: | |
| 18 | ||
| 19 | * doc/langref.html | |
| 20 | * doc/std/index.html | |
| 16 | Otherwise, you're looking at a release of Zig, so you can find the language | |
| 17 | reference at `doc/langref.html`, and the standard library documentation by | |
| 18 | running `zig std`, which will open a browser tab. | |
| 21 | 19 | |
| 22 | 20 | ## Installation |
| 23 | 21 |
lib/compiler/build_runner.zig+36-1| ... | ... | @@ -17,6 +17,12 @@ const runner = @This(); |
| 17 | 17 | pub const root = @import("@build"); |
| 18 | 18 | pub const dependencies = @import("@dependencies"); |
| 19 | 19 | |
| 20 | pub const std_options: std.Options = .{ | |
| 21 | .side_channels_mitigations = .none, | |
| 22 | .http_disable_tls = true, | |
| 23 | .crypto_fork_safety = false, | |
| 24 | }; | |
| 25 | ||
| 20 | 26 | pub fn main() !void { |
| 21 | 27 | // Here we use an ArenaAllocator backed by a page allocator because a build is a short-lived, |
| 22 | 28 | // one shot program. We don't need to waste time freeing memory and finding places to squish |
| ... | ... | @@ -106,6 +112,7 @@ pub fn main() !void { |
| 106 | 112 | var watch = false; |
| 107 | 113 | var fuzz = false; |
| 108 | 114 | var debounce_interval_ms: u16 = 50; |
| 115 | var listen_port: u16 = 0; | |
| 109 | 116 | |
| 110 | 117 | while (nextArg(args, &arg_idx)) |arg| { |
| 111 | 118 | if (mem.startsWith(u8, arg, "-Z")) { |
| ... | ... | @@ -203,6 +210,14 @@ pub fn main() !void { |
| 203 | 210 | next_arg, @errorName(err), |
| 204 | 211 | }); |
| 205 | 212 | }; |
| 213 | } else if (mem.eql(u8, arg, "--port")) { | |
| 214 | const next_arg = nextArg(args, &arg_idx) orelse | |
| 215 | fatalWithHint("expected u16 after '{s}'", .{arg}); | |
| 216 | listen_port = std.fmt.parseUnsigned(u16, next_arg, 10) catch |err| { | |
| 217 | fatal("unable to parse port '{s}' as unsigned 16-bit integer: {s}\n", .{ | |
| 218 | next_arg, @errorName(err), | |
| 219 | }); | |
| 220 | }; | |
| 206 | 221 | } else if (mem.eql(u8, arg, "--debug-log")) { |
| 207 | 222 | const next_arg = nextArgOrFatal(args, &arg_idx); |
| 208 | 223 | try debug_log_scopes.append(next_arg); |
| ... | ... | @@ -403,7 +418,27 @@ pub fn main() !void { |
| 403 | 418 | else => return err, |
| 404 | 419 | }; |
| 405 | 420 | if (fuzz) { |
| 406 | Fuzz.start(&run.thread_pool, run.step_stack.keys(), run.ttyconf, main_progress_node); | |
| 421 | switch (builtin.os.tag) { | |
| 422 | // Current implementation depends on two things that need to be ported to Windows: | |
| 423 | // * Memory-mapping to share data between the fuzzer and build runner. | |
| 424 | // * COFF/PE support added to `std.debug.Info` (it needs a batching API for resolving | |
| 425 | // many addresses to source locations). | |
| 426 | .windows => fatal("--fuzz not yet implemented for {s}", .{@tagName(builtin.os.tag)}), | |
| 427 | else => {}, | |
| 428 | } | |
| 429 | const listen_address = std.net.Address.parseIp("127.0.0.1", listen_port) catch unreachable; | |
| 430 | try Fuzz.start( | |
| 431 | gpa, | |
| 432 | arena, | |
| 433 | global_cache_directory, | |
| 434 | zig_lib_directory, | |
| 435 | zig_exe, | |
| 436 | &run.thread_pool, | |
| 437 | run.step_stack.keys(), | |
| 438 | run.ttyconf, | |
| 439 | listen_address, | |
| 440 | main_progress_node, | |
| 441 | ); | |
| 407 | 442 | } |
| 408 | 443 | |
| 409 | 444 | if (!watch) return cleanExit(); |
lib/compiler/std-docs.zig+4-5| ... | ... | @@ -275,10 +275,6 @@ fn buildWasmBinary( |
| 275 | 275 | ) ![]const u8 { |
| 276 | 276 | const gpa = context.gpa; |
| 277 | 277 | |
| 278 | const main_src_path = try std.fs.path.join(arena, &.{ | |
| 279 | context.zig_lib_directory, "docs", "wasm", "main.zig", | |
| 280 | }); | |
| 281 | ||
| 282 | 278 | var argv: std.ArrayListUnmanaged([]const u8) = .{}; |
| 283 | 279 | |
| 284 | 280 | try argv.appendSlice(arena, &.{ |
| ... | ... | @@ -298,7 +294,10 @@ fn buildWasmBinary( |
| 298 | 294 | "--name", |
| 299 | 295 | "autodoc", |
| 300 | 296 | "-rdynamic", |
| 301 | main_src_path, | |
| 297 | "--dep", | |
| 298 | "Walk", | |
| 299 | try std.fmt.allocPrint(arena, "-Mroot={s}/docs/wasm/main.zig", .{context.zig_lib_directory}), | |
| 300 | try std.fmt.allocPrint(arena, "-MWalk={s}/docs/wasm/Walk.zig", .{context.zig_lib_directory}), | |
| 302 | 301 | "--listen=-", |
| 303 | 302 | }); |
| 304 | 303 |
lib/compiler/test_runner.zig+33-1| ... | ... | @@ -1,8 +1,10 @@ |
| 1 | 1 | //! Default test runner for unit tests. |
| 2 | 2 | const builtin = @import("builtin"); |
| 3 | ||
| 3 | 4 | const std = @import("std"); |
| 4 | 5 | const io = std.io; |
| 5 | 6 | const testing = std.testing; |
| 7 | const assert = std.debug.assert; | |
| 6 | 8 | |
| 7 | 9 | pub const std_options = .{ |
| 8 | 10 | .logFn = log, |
| ... | ... | @@ -28,6 +30,7 @@ pub fn main() void { |
| 28 | 30 | @panic("unable to parse command line args"); |
| 29 | 31 | |
| 30 | 32 | var listen = false; |
| 33 | var opt_cache_dir: ?[]const u8 = null; | |
| 31 | 34 | |
| 32 | 35 | for (args[1..]) |arg| { |
| 33 | 36 | if (std.mem.eql(u8, arg, "--listen=-")) { |
| ... | ... | @@ -35,12 +38,18 @@ pub fn main() void { |
| 35 | 38 | } else if (std.mem.startsWith(u8, arg, "--seed=")) { |
| 36 | 39 | testing.random_seed = std.fmt.parseUnsigned(u32, arg["--seed=".len..], 0) catch |
| 37 | 40 | @panic("unable to parse --seed command line argument"); |
| 41 | } else if (std.mem.startsWith(u8, arg, "--cache-dir")) { | |
| 42 | opt_cache_dir = arg["--cache-dir=".len..]; | |
| 38 | 43 | } else { |
| 39 | 44 | @panic("unrecognized command line argument"); |
| 40 | 45 | } |
| 41 | 46 | } |
| 42 | 47 | |
| 43 | 48 | fba.reset(); |
| 49 | if (builtin.fuzz) { | |
| 50 | const cache_dir = opt_cache_dir orelse @panic("missing --cache-dir=[path] argument"); | |
| 51 | fuzzer_init(FuzzerSlice.fromSlice(cache_dir)); | |
| 52 | } | |
| 44 | 53 | |
| 45 | 54 | if (listen) { |
| 46 | 55 | return mainServer() catch @panic("internal test runner failure"); |
| ... | ... | @@ -59,6 +68,11 @@ fn mainServer() !void { |
| 59 | 68 | }); |
| 60 | 69 | defer server.deinit(); |
| 61 | 70 | |
| 71 | if (builtin.fuzz) { | |
| 72 | const coverage_id = fuzzer_coverage_id(); | |
| 73 | try server.serveU64Message(.coverage_id, coverage_id); | |
| 74 | } | |
| 75 | ||
| 62 | 76 | while (true) { |
| 63 | 77 | const hdr = try server.receiveMessage(); |
| 64 | 78 | switch (hdr.tag) { |
| ... | ... | @@ -129,7 +143,9 @@ fn mainServer() !void { |
| 129 | 143 | }); |
| 130 | 144 | }, |
| 131 | 145 | .start_fuzzing => { |
| 146 | if (!builtin.fuzz) unreachable; | |
| 132 | 147 | const index = try server.receiveBody_u32(); |
| 148 | var first = true; | |
| 133 | 149 | const test_fn = builtin.test_functions[index]; |
| 134 | 150 | while (true) { |
| 135 | 151 | testing.allocator_instance = .{}; |
| ... | ... | @@ -148,6 +164,10 @@ fn mainServer() !void { |
| 148 | 164 | }; |
| 149 | 165 | if (!is_fuzz_test) @panic("missed call to std.testing.fuzzInput"); |
| 150 | 166 | if (log_err_count != 0) @panic("error logs detected"); |
| 167 | if (first) { | |
| 168 | first = false; | |
| 169 | try server.serveU64Message(.fuzz_start_addr, entry_addr); | |
| 170 | } | |
| 151 | 171 | } |
| 152 | 172 | }, |
| 153 | 173 | |
| ... | ... | @@ -315,20 +335,32 @@ const FuzzerSlice = extern struct { |
| 315 | 335 | ptr: [*]const u8, |
| 316 | 336 | len: usize, |
| 317 | 337 | |
| 338 | /// Inline to avoid fuzzer instrumentation. | |
| 318 | 339 | inline fn toSlice(s: FuzzerSlice) []const u8 { |
| 319 | 340 | return s.ptr[0..s.len]; |
| 320 | 341 | } |
| 342 | ||
| 343 | /// Inline to avoid fuzzer instrumentation. | |
| 344 | inline fn fromSlice(s: []const u8) FuzzerSlice { | |
| 345 | return .{ .ptr = s.ptr, .len = s.len }; | |
| 346 | } | |
| 321 | 347 | }; |
| 322 | 348 | |
| 323 | 349 | var is_fuzz_test: bool = undefined; |
| 350 | var entry_addr: usize = 0; | |
| 324 | 351 | |
| 325 | 352 | extern fn fuzzer_next() FuzzerSlice; |
| 353 | extern fn fuzzer_init(cache_dir: FuzzerSlice) void; | |
| 354 | extern fn fuzzer_coverage_id() u64; | |
| 326 | 355 | |
| 327 | 356 | pub fn fuzzInput(options: testing.FuzzInputOptions) []const u8 { |
| 328 | 357 | @disableInstrumentation(); |
| 329 | 358 | if (crippled) return ""; |
| 330 | 359 | is_fuzz_test = true; |
| 331 | if (builtin.fuzz) return fuzzer_next().toSlice(); | |
| 360 | if (builtin.fuzz) { | |
| 361 | if (entry_addr == 0) entry_addr = @returnAddress(); | |
| 362 | return fuzzer_next().toSlice(); | |
| 363 | } | |
| 332 | 364 | if (options.corpus.len == 0) return ""; |
| 333 | 365 | var prng = std.Random.DefaultPrng.init(testing.random_seed); |
| 334 | 366 | const random = prng.random(); |
lib/docs/wasm/Decl.zig+9-9| ... | ... | @@ -1,3 +1,12 @@ |
| 1 | const Decl = @This(); | |
| 2 | const std = @import("std"); | |
| 3 | const Ast = std.zig.Ast; | |
| 4 | const Walk = @import("Walk.zig"); | |
| 5 | const gpa = std.heap.wasm_allocator; | |
| 6 | const assert = std.debug.assert; | |
| 7 | const log = std.log; | |
| 8 | const Oom = error{OutOfMemory}; | |
| 9 | ||
| 1 | 10 | ast_node: Ast.Node.Index, |
| 2 | 11 | file: Walk.File.Index, |
| 3 | 12 | /// The decl whose namespace this is in. |
| ... | ... | @@ -215,12 +224,3 @@ pub fn find(search_string: []const u8) Decl.Index { |
| 215 | 224 | } |
| 216 | 225 | return current_decl_index; |
| 217 | 226 | } |
| 218 | ||
| 219 | const Decl = @This(); | |
| 220 | const std = @import("std"); | |
| 221 | const Ast = std.zig.Ast; | |
| 222 | const Walk = @import("Walk.zig"); | |
| 223 | const gpa = std.heap.wasm_allocator; | |
| 224 | const assert = std.debug.assert; | |
| 225 | const log = std.log; | |
| 226 | const Oom = error{OutOfMemory}; |
lib/docs/wasm/Walk.zig+11-9| ... | ... | @@ -1,4 +1,15 @@ |
| 1 | 1 | //! Find and annotate identifiers with links to their declarations. |
| 2 | ||
| 3 | const Walk = @This(); | |
| 4 | const std = @import("std"); | |
| 5 | const Ast = std.zig.Ast; | |
| 6 | const assert = std.debug.assert; | |
| 7 | const log = std.log; | |
| 8 | const gpa = std.heap.wasm_allocator; | |
| 9 | const Oom = error{OutOfMemory}; | |
| 10 | ||
| 11 | pub const Decl = @import("Decl.zig"); | |
| 12 | ||
| 2 | 13 | pub var files: std.StringArrayHashMapUnmanaged(File) = .{}; |
| 3 | 14 | pub var decls: std.ArrayListUnmanaged(Decl) = .{}; |
| 4 | 15 | pub var modules: std.StringArrayHashMapUnmanaged(File.Index) = .{}; |
| ... | ... | @@ -1120,15 +1131,6 @@ pub fn isPrimitiveNonType(name: []const u8) bool { |
| 1120 | 1131 | // try w.root(); |
| 1121 | 1132 | //} |
| 1122 | 1133 | |
| 1123 | const Walk = @This(); | |
| 1124 | const std = @import("std"); | |
| 1125 | const Ast = std.zig.Ast; | |
| 1126 | const assert = std.debug.assert; | |
| 1127 | const Decl = @import("Decl.zig"); | |
| 1128 | const log = std.log; | |
| 1129 | const gpa = std.heap.wasm_allocator; | |
| 1130 | const Oom = error{OutOfMemory}; | |
| 1131 | ||
| 1132 | 1134 | fn shrinkToFit(m: anytype) void { |
| 1133 | 1135 | m.shrinkAndFree(gpa, m.entries.len); |
| 1134 | 1136 | } |
lib/docs/wasm/html_render.zig created+412| ... | ... | @@ -0,0 +1,412 @@ |
| 1 | const std = @import("std"); | |
| 2 | const Ast = std.zig.Ast; | |
| 3 | const assert = std.debug.assert; | |
| 4 | ||
| 5 | const Walk = @import("Walk"); | |
| 6 | const Decl = Walk.Decl; | |
| 7 | ||
| 8 | const gpa = std.heap.wasm_allocator; | |
| 9 | const Oom = error{OutOfMemory}; | |
| 10 | ||
| 11 | /// Delete this to find out where URL escaping needs to be added. | |
| 12 | pub const missing_feature_url_escape = true; | |
| 13 | ||
| 14 | pub const RenderSourceOptions = struct { | |
| 15 | skip_doc_comments: bool = false, | |
| 16 | skip_comments: bool = false, | |
| 17 | collapse_whitespace: bool = false, | |
| 18 | fn_link: Decl.Index = .none, | |
| 19 | /// Assumed to be sorted ascending. | |
| 20 | source_location_annotations: []const Annotation = &.{}, | |
| 21 | /// Concatenated with dom_id. | |
| 22 | annotation_prefix: []const u8 = "l", | |
| 23 | }; | |
| 24 | ||
| 25 | pub const Annotation = struct { | |
| 26 | file_byte_offset: u32, | |
| 27 | /// Concatenated with annotation_prefix. | |
| 28 | dom_id: u32, | |
| 29 | }; | |
| 30 | ||
| 31 | pub fn fileSourceHtml( | |
| 32 | file_index: Walk.File.Index, | |
| 33 | out: *std.ArrayListUnmanaged(u8), | |
| 34 | root_node: Ast.Node.Index, | |
| 35 | options: RenderSourceOptions, | |
| 36 | ) !void { | |
| 37 | const ast = file_index.get_ast(); | |
| 38 | const file = file_index.get(); | |
| 39 | ||
| 40 | const g = struct { | |
| 41 | var field_access_buffer: std.ArrayListUnmanaged(u8) = .{}; | |
| 42 | }; | |
| 43 | ||
| 44 | const token_tags = ast.tokens.items(.tag); | |
| 45 | const token_starts = ast.tokens.items(.start); | |
| 46 | const main_tokens = ast.nodes.items(.main_token); | |
| 47 | ||
| 48 | const start_token = ast.firstToken(root_node); | |
| 49 | const end_token = ast.lastToken(root_node) + 1; | |
| 50 | ||
| 51 | var cursor: usize = token_starts[start_token]; | |
| 52 | ||
| 53 | var indent: usize = 0; | |
| 54 | if (std.mem.lastIndexOf(u8, ast.source[0..cursor], "\n")) |newline_index| { | |
| 55 | for (ast.source[newline_index + 1 .. cursor]) |c| { | |
| 56 | if (c == ' ') { | |
| 57 | indent += 1; | |
| 58 | } else { | |
| 59 | break; | |
| 60 | } | |
| 61 | } | |
| 62 | } | |
| 63 | ||
| 64 | var next_annotate_index: usize = 0; | |
| 65 | ||
| 66 | for ( | |
| 67 | token_tags[start_token..end_token], | |
| 68 | token_starts[start_token..end_token], | |
| 69 | start_token.., | |
| 70 | ) |tag, start, token_index| { | |
| 71 | const between = ast.source[cursor..start]; | |
| 72 | if (std.mem.trim(u8, between, " \t\r\n").len > 0) { | |
| 73 | if (!options.skip_comments) { | |
| 74 | try out.appendSlice(gpa, "<span class=\"tok-comment\">"); | |
| 75 | try appendUnindented(out, between, indent); | |
| 76 | try out.appendSlice(gpa, "</span>"); | |
| 77 | } | |
| 78 | } else if (between.len > 0) { | |
| 79 | if (options.collapse_whitespace) { | |
| 80 | if (out.items.len > 0 and out.items[out.items.len - 1] != ' ') | |
| 81 | try out.append(gpa, ' '); | |
| 82 | } else { | |
| 83 | try appendUnindented(out, between, indent); | |
| 84 | } | |
| 85 | } | |
| 86 | if (tag == .eof) break; | |
| 87 | const slice = ast.tokenSlice(token_index); | |
| 88 | cursor = start + slice.len; | |
| 89 | ||
| 90 | // Insert annotations. | |
| 91 | while (true) { | |
| 92 | if (next_annotate_index >= options.source_location_annotations.len) break; | |
| 93 | const next_annotation = options.source_location_annotations[next_annotate_index]; | |
| 94 | if (cursor <= next_annotation.file_byte_offset) break; | |
| 95 | try out.writer(gpa).print("<span id=\"{s}{d}\"></span>", .{ | |
| 96 | options.annotation_prefix, next_annotation.dom_id, | |
| 97 | }); | |
| 98 | next_annotate_index += 1; | |
| 99 | } | |
| 100 | ||
| 101 | switch (tag) { | |
| 102 | .eof => unreachable, | |
| 103 | ||
| 104 | .keyword_addrspace, | |
| 105 | .keyword_align, | |
| 106 | .keyword_and, | |
| 107 | .keyword_asm, | |
| 108 | .keyword_async, | |
| 109 | .keyword_await, | |
| 110 | .keyword_break, | |
| 111 | .keyword_catch, | |
| 112 | .keyword_comptime, | |
| 113 | .keyword_const, | |
| 114 | .keyword_continue, | |
| 115 | .keyword_defer, | |
| 116 | .keyword_else, | |
| 117 | .keyword_enum, | |
| 118 | .keyword_errdefer, | |
| 119 | .keyword_error, | |
| 120 | .keyword_export, | |
| 121 | .keyword_extern, | |
| 122 | .keyword_for, | |
| 123 | .keyword_if, | |
| 124 | .keyword_inline, | |
| 125 | .keyword_noalias, | |
| 126 | .keyword_noinline, | |
| 127 | .keyword_nosuspend, | |
| 128 | .keyword_opaque, | |
| 129 | .keyword_or, | |
| 130 | .keyword_orelse, | |
| 131 | .keyword_packed, | |
| 132 | .keyword_anyframe, | |
| 133 | .keyword_pub, | |
| 134 | .keyword_resume, | |
| 135 | .keyword_return, | |
| 136 | .keyword_linksection, | |
| 137 | .keyword_callconv, | |
| 138 | .keyword_struct, | |
| 139 | .keyword_suspend, | |
| 140 | .keyword_switch, | |
| 141 | .keyword_test, | |
| 142 | .keyword_threadlocal, | |
| 143 | .keyword_try, | |
| 144 | .keyword_union, | |
| 145 | .keyword_unreachable, | |
| 146 | .keyword_usingnamespace, | |
| 147 | .keyword_var, | |
| 148 | .keyword_volatile, | |
| 149 | .keyword_allowzero, | |
| 150 | .keyword_while, | |
| 151 | .keyword_anytype, | |
| 152 | .keyword_fn, | |
| 153 | => { | |
| 154 | try out.appendSlice(gpa, "<span class=\"tok-kw\">"); | |
| 155 | try appendEscaped(out, slice); | |
| 156 | try out.appendSlice(gpa, "</span>"); | |
| 157 | }, | |
| 158 | ||
| 159 | .string_literal, | |
| 160 | .char_literal, | |
| 161 | .multiline_string_literal_line, | |
| 162 | => { | |
| 163 | try out.appendSlice(gpa, "<span class=\"tok-str\">"); | |
| 164 | try appendEscaped(out, slice); | |
| 165 | try out.appendSlice(gpa, "</span>"); | |
| 166 | }, | |
| 167 | ||
| 168 | .builtin => { | |
| 169 | try out.appendSlice(gpa, "<span class=\"tok-builtin\">"); | |
| 170 | try appendEscaped(out, slice); | |
| 171 | try out.appendSlice(gpa, "</span>"); | |
| 172 | }, | |
| 173 | ||
| 174 | .doc_comment, | |
| 175 | .container_doc_comment, | |
| 176 | => { | |
| 177 | if (!options.skip_doc_comments) { | |
| 178 | try out.appendSlice(gpa, "<span class=\"tok-comment\">"); | |
| 179 | try appendEscaped(out, slice); | |
| 180 | try out.appendSlice(gpa, "</span>"); | |
| 181 | } | |
| 182 | }, | |
| 183 | ||
| 184 | .identifier => i: { | |
| 185 | if (options.fn_link != .none) { | |
| 186 | const fn_link = options.fn_link.get(); | |
| 187 | const fn_token = main_tokens[fn_link.ast_node]; | |
| 188 | if (token_index == fn_token + 1) { | |
| 189 | try out.appendSlice(gpa, "<a class=\"tok-fn\" href=\"#"); | |
| 190 | _ = missing_feature_url_escape; | |
| 191 | try fn_link.fqn(out); | |
| 192 | try out.appendSlice(gpa, "\">"); | |
| 193 | try appendEscaped(out, slice); | |
| 194 | try out.appendSlice(gpa, "</a>"); | |
| 195 | break :i; | |
| 196 | } | |
| 197 | } | |
| 198 | ||
| 199 | if (token_index > 0 and token_tags[token_index - 1] == .keyword_fn) { | |
| 200 | try out.appendSlice(gpa, "<span class=\"tok-fn\">"); | |
| 201 | try appendEscaped(out, slice); | |
| 202 | try out.appendSlice(gpa, "</span>"); | |
| 203 | break :i; | |
| 204 | } | |
| 205 | ||
| 206 | if (Walk.isPrimitiveNonType(slice)) { | |
| 207 | try out.appendSlice(gpa, "<span class=\"tok-null\">"); | |
| 208 | try appendEscaped(out, slice); | |
| 209 | try out.appendSlice(gpa, "</span>"); | |
| 210 | break :i; | |
| 211 | } | |
| 212 | ||
| 213 | if (std.zig.primitives.isPrimitive(slice)) { | |
| 214 | try out.appendSlice(gpa, "<span class=\"tok-type\">"); | |
| 215 | try appendEscaped(out, slice); | |
| 216 | try out.appendSlice(gpa, "</span>"); | |
| 217 | break :i; | |
| 218 | } | |
| 219 | ||
| 220 | if (file.token_parents.get(token_index)) |field_access_node| { | |
| 221 | g.field_access_buffer.clearRetainingCapacity(); | |
| 222 | try walkFieldAccesses(file_index, &g.field_access_buffer, field_access_node); | |
| 223 | if (g.field_access_buffer.items.len > 0) { | |
| 224 | try out.appendSlice(gpa, "<a href=\"#"); | |
| 225 | _ = missing_feature_url_escape; | |
| 226 | try out.appendSlice(gpa, g.field_access_buffer.items); | |
| 227 | try out.appendSlice(gpa, "\">"); | |
| 228 | try appendEscaped(out, slice); | |
| 229 | try out.appendSlice(gpa, "</a>"); | |
| 230 | } else { | |
| 231 | try appendEscaped(out, slice); | |
| 232 | } | |
| 233 | break :i; | |
| 234 | } | |
| 235 | ||
| 236 | { | |
| 237 | g.field_access_buffer.clearRetainingCapacity(); | |
| 238 | try resolveIdentLink(file_index, &g.field_access_buffer, token_index); | |
| 239 | if (g.field_access_buffer.items.len > 0) { | |
| 240 | try out.appendSlice(gpa, "<a href=\"#"); | |
| 241 | _ = missing_feature_url_escape; | |
| 242 | try out.appendSlice(gpa, g.field_access_buffer.items); | |
| 243 | try out.appendSlice(gpa, "\">"); | |
| 244 | try appendEscaped(out, slice); | |
| 245 | try out.appendSlice(gpa, "</a>"); | |
| 246 | break :i; | |
| 247 | } | |
| 248 | } | |
| 249 | ||
| 250 | try appendEscaped(out, slice); | |
| 251 | }, | |
| 252 | ||
| 253 | .number_literal => { | |
| 254 | try out.appendSlice(gpa, "<span class=\"tok-number\">"); | |
| 255 | try appendEscaped(out, slice); | |
| 256 | try out.appendSlice(gpa, "</span>"); | |
| 257 | }, | |
| 258 | ||
| 259 | .bang, | |
| 260 | .pipe, | |
| 261 | .pipe_pipe, | |
| 262 | .pipe_equal, | |
| 263 | .equal, | |
| 264 | .equal_equal, | |
| 265 | .equal_angle_bracket_right, | |
| 266 | .bang_equal, | |
| 267 | .l_paren, | |
| 268 | .r_paren, | |
| 269 | .semicolon, | |
| 270 | .percent, | |
| 271 | .percent_equal, | |
| 272 | .l_brace, | |
| 273 | .r_brace, | |
| 274 | .l_bracket, | |
| 275 | .r_bracket, | |
| 276 | .period, | |
| 277 | .period_asterisk, | |
| 278 | .ellipsis2, | |
| 279 | .ellipsis3, | |
| 280 | .caret, | |
| 281 | .caret_equal, | |
| 282 | .plus, | |
| 283 | .plus_plus, | |
| 284 | .plus_equal, | |
| 285 | .plus_percent, | |
| 286 | .plus_percent_equal, | |
| 287 | .plus_pipe, | |
| 288 | .plus_pipe_equal, | |
| 289 | .minus, | |
| 290 | .minus_equal, | |
| 291 | .minus_percent, | |
| 292 | .minus_percent_equal, | |
| 293 | .minus_pipe, | |
| 294 | .minus_pipe_equal, | |
| 295 | .asterisk, | |
| 296 | .asterisk_equal, | |
| 297 | .asterisk_asterisk, | |
| 298 | .asterisk_percent, | |
| 299 | .asterisk_percent_equal, | |
| 300 | .asterisk_pipe, | |
| 301 | .asterisk_pipe_equal, | |
| 302 | .arrow, | |
| 303 | .colon, | |
| 304 | .slash, | |
| 305 | .slash_equal, | |
| 306 | .comma, | |
| 307 | .ampersand, | |
| 308 | .ampersand_equal, | |
| 309 | .question_mark, | |
| 310 | .angle_bracket_left, | |
| 311 | .angle_bracket_left_equal, | |
| 312 | .angle_bracket_angle_bracket_left, | |
| 313 | .angle_bracket_angle_bracket_left_equal, | |
| 314 | .angle_bracket_angle_bracket_left_pipe, | |
| 315 | .angle_bracket_angle_bracket_left_pipe_equal, | |
| 316 | .angle_bracket_right, | |
| 317 | .angle_bracket_right_equal, | |
| 318 | .angle_bracket_angle_bracket_right, | |
| 319 | .angle_bracket_angle_bracket_right_equal, | |
| 320 | .tilde, | |
| 321 | => try appendEscaped(out, slice), | |
| 322 | ||
| 323 | .invalid, .invalid_periodasterisks => return error.InvalidToken, | |
| 324 | } | |
| 325 | } | |
| 326 | } | |
| 327 | ||
| 328 | fn appendUnindented(out: *std.ArrayListUnmanaged(u8), s: []const u8, indent: usize) !void { | |
| 329 | var it = std.mem.splitScalar(u8, s, '\n'); | |
| 330 | var is_first_line = true; | |
| 331 | while (it.next()) |line| { | |
| 332 | if (is_first_line) { | |
| 333 | try appendEscaped(out, line); | |
| 334 | is_first_line = false; | |
| 335 | } else { | |
| 336 | try out.appendSlice(gpa, "\n"); | |
| 337 | try appendEscaped(out, unindent(line, indent)); | |
| 338 | } | |
| 339 | } | |
| 340 | } | |
| 341 | ||
| 342 | pub fn appendEscaped(out: *std.ArrayListUnmanaged(u8), s: []const u8) !void { | |
| 343 | for (s) |c| { | |
| 344 | try out.ensureUnusedCapacity(gpa, 6); | |
| 345 | switch (c) { | |
| 346 | '&' => out.appendSliceAssumeCapacity("&amp;"), | |
| 347 | '<' => out.appendSliceAssumeCapacity("&lt;"), | |
| 348 | '>' => out.appendSliceAssumeCapacity("&gt;"), | |
| 349 | '"' => out.appendSliceAssumeCapacity("&quot;"), | |
| 350 | else => out.appendAssumeCapacity(c), | |
| 351 | } | |
| 352 | } | |
| 353 | } | |
| 354 | ||
| 355 | fn walkFieldAccesses( | |
| 356 | file_index: Walk.File.Index, | |
| 357 | out: *std.ArrayListUnmanaged(u8), | |
| 358 | node: Ast.Node.Index, | |
| 359 | ) Oom!void { | |
| 360 | const ast = file_index.get_ast(); | |
| 361 | const node_tags = ast.nodes.items(.tag); | |
| 362 | assert(node_tags[node] == .field_access); | |
| 363 | const node_datas = ast.nodes.items(.data); | |
| 364 | const main_tokens = ast.nodes.items(.main_token); | |
| 365 | const object_node = node_datas[node].lhs; | |
| 366 | const dot_token = main_tokens[node]; | |
| 367 | const field_ident = dot_token + 1; | |
| 368 | switch (node_tags[object_node]) { | |
| 369 | .identifier => { | |
| 370 | const lhs_ident = main_tokens[object_node]; | |
| 371 | try resolveIdentLink(file_index, out, lhs_ident); | |
| 372 | }, | |
| 373 | .field_access => { | |
| 374 | try walkFieldAccesses(file_index, out, object_node); | |
| 375 | }, | |
| 376 | else => {}, | |
| 377 | } | |
| 378 | if (out.items.len > 0) { | |
| 379 | try out.append(gpa, '.'); | |
| 380 | try out.appendSlice(gpa, ast.tokenSlice(field_ident)); | |
| 381 | } | |
| 382 | } | |
| 383 | ||
| 384 | fn resolveIdentLink( | |
| 385 | file_index: Walk.File.Index, | |
| 386 | out: *std.ArrayListUnmanaged(u8), | |
| 387 | ident_token: Ast.TokenIndex, | |
| 388 | ) Oom!void { | |
| 389 | const decl_index = file_index.get().lookup_token(ident_token); | |
| 390 | if (decl_index == .none) return; | |
| 391 | try resolveDeclLink(decl_index, out); | |
| 392 | } | |
| 393 | ||
| 394 | fn unindent(s: []const u8, indent: usize) []const u8 { | |
| 395 | var indent_idx: usize = 0; | |
| 396 | for (s) |c| { | |
| 397 | if (c == ' ' and indent_idx < indent) { | |
| 398 | indent_idx += 1; | |
| 399 | } else { | |
| 400 | break; | |
| 401 | } | |
| 402 | } | |
| 403 | return s[indent_idx..]; | |
| 404 | } | |
| 405 | ||
| 406 | pub fn resolveDeclLink(decl_index: Decl.Index, out: *std.ArrayListUnmanaged(u8)) Oom!void { | |
| 407 | const decl = decl_index.get(); | |
| 408 | switch (decl.categorize()) { | |
| 409 | .alias => |alias_decl| try alias_decl.get().fqn(out), | |
| 410 | else => try decl.fqn(out), | |
| 411 | } | |
| 412 | } |
lib/docs/wasm/main.zig+18-392| ... | ... | @@ -1,15 +1,17 @@ |
| 1 | /// Delete this to find out where URL escaping needs to be added. | |
| 2 | const missing_feature_url_escape = true; | |
| 3 | ||
| 4 | const gpa = std.heap.wasm_allocator; | |
| 5 | ||
| 6 | 1 | const std = @import("std"); |
| 7 | 2 | const log = std.log; |
| 8 | 3 | const assert = std.debug.assert; |
| 9 | 4 | const Ast = std.zig.Ast; |
| 10 | const Walk = @import("Walk.zig"); | |
| 5 | const Walk = @import("Walk"); | |
| 11 | 6 | const markdown = @import("markdown.zig"); |
| 12 | const Decl = @import("Decl.zig"); | |
| 7 | const Decl = Walk.Decl; | |
| 8 | ||
| 9 | const fileSourceHtml = @import("html_render.zig").fileSourceHtml; | |
| 10 | const appendEscaped = @import("html_render.zig").appendEscaped; | |
| 11 | const resolveDeclLink = @import("html_render.zig").resolveDeclLink; | |
| 12 | const missing_feature_url_escape = @import("html_render.zig").missing_feature_url_escape; | |
| 13 | ||
| 14 | const gpa = std.heap.wasm_allocator; | |
| 13 | 15 | |
| 14 | 16 | const js = struct { |
| 15 | 17 | extern "js" fn log(ptr: [*]const u8, len: usize) void; |
| ... | ... | @@ -53,7 +55,7 @@ export fn unpack(tar_ptr: [*]u8, tar_len: usize) void { |
| 53 | 55 | const tar_bytes = tar_ptr[0..tar_len]; |
| 54 | 56 | //log.debug("received {d} bytes of tar file", .{tar_bytes.len}); |
| 55 | 57 | |
| 56 | unpack_inner(tar_bytes) catch |err| { | |
| 58 | unpackInner(tar_bytes) catch |err| { | |
| 57 | 59 | fatal("unable to unpack tar: {s}", .{@errorName(err)}); |
| 58 | 60 | }; |
| 59 | 61 | } |
| ... | ... | @@ -439,7 +441,7 @@ fn decl_field_html_fallible( |
| 439 | 441 | const decl = decl_index.get(); |
| 440 | 442 | const ast = decl.file.get_ast(); |
| 441 | 443 | try out.appendSlice(gpa, "<pre><code>"); |
| 442 | try file_source_html(decl.file, out, field_node, .{}); | |
| 444 | try fileSourceHtml(decl.file, out, field_node, .{}); | |
| 443 | 445 | try out.appendSlice(gpa, "</code></pre>"); |
| 444 | 446 | |
| 445 | 447 | const field = ast.fullContainerField(field_node).?; |
| ... | ... | @@ -478,7 +480,7 @@ fn decl_param_html_fallible( |
| 478 | 480 | try out.appendSlice(gpa, "<pre><code>"); |
| 479 | 481 | try appendEscaped(out, name); |
| 480 | 482 | try out.appendSlice(gpa, ": "); |
| 481 | try file_source_html(decl.file, out, param_node, .{}); | |
| 483 | try fileSourceHtml(decl.file, out, param_node, .{}); | |
| 482 | 484 | try out.appendSlice(gpa, "</code></pre>"); |
| 483 | 485 | |
| 484 | 486 | if (ast.tokens.items(.tag)[first_doc_comment] == .doc_comment) { |
| ... | ... | @@ -506,7 +508,7 @@ export fn decl_fn_proto_html(decl_index: Decl.Index, linkify_fn_name: bool) Stri |
| 506 | 508 | }; |
| 507 | 509 | |
| 508 | 510 | string_result.clearRetainingCapacity(); |
| 509 | file_source_html(decl.file, &string_result, proto_node, .{ | |
| 511 | fileSourceHtml(decl.file, &string_result, proto_node, .{ | |
| 510 | 512 | .skip_doc_comments = true, |
| 511 | 513 | .skip_comments = true, |
| 512 | 514 | .collapse_whitespace = true, |
| ... | ... | @@ -521,7 +523,7 @@ export fn decl_source_html(decl_index: Decl.Index) String { |
| 521 | 523 | const decl = decl_index.get(); |
| 522 | 524 | |
| 523 | 525 | string_result.clearRetainingCapacity(); |
| 524 | file_source_html(decl.file, &string_result, decl.ast_node, .{}) catch |err| { | |
| 526 | fileSourceHtml(decl.file, &string_result, decl.ast_node, .{}) catch |err| { | |
| 525 | 527 | fatal("unable to render source: {s}", .{@errorName(err)}); |
| 526 | 528 | }; |
| 527 | 529 | return String.init(string_result.items); |
| ... | ... | @@ -533,7 +535,7 @@ export fn decl_doctest_html(decl_index: Decl.Index) String { |
| 533 | 535 | return String.init(""); |
| 534 | 536 | |
| 535 | 537 | string_result.clearRetainingCapacity(); |
| 536 | file_source_html(decl.file, &string_result, doctest_ast_node, .{}) catch |err| { | |
| 538 | fileSourceHtml(decl.file, &string_result, doctest_ast_node, .{}) catch |err| { | |
| 537 | 539 | fatal("unable to render source: {s}", .{@errorName(err)}); |
| 538 | 540 | }; |
| 539 | 541 | return String.init(string_result.items); |
| ... | ... | @@ -691,7 +693,7 @@ fn render_docs( |
| 691 | 693 | const content = doc.string(data.text.content); |
| 692 | 694 | if (resolve_decl_path(r.context, content)) |resolved_decl_index| { |
| 693 | 695 | g.link_buffer.clearRetainingCapacity(); |
| 694 | try resolve_decl_link(resolved_decl_index, &g.link_buffer); | |
| 696 | try resolveDeclLink(resolved_decl_index, &g.link_buffer); | |
| 695 | 697 | |
| 696 | 698 | try writer.writeAll("<a href=\"#"); |
| 697 | 699 | _ = missing_feature_url_escape; |
| ... | ... | @@ -734,7 +736,7 @@ export fn decl_type_html(decl_index: Decl.Index) String { |
| 734 | 736 | if (ast.fullVarDecl(decl.ast_node)) |var_decl| { |
| 735 | 737 | if (var_decl.ast.type_node != 0) { |
| 736 | 738 | string_result.appendSlice(gpa, "<code>") catch @panic("OOM"); |
| 737 | file_source_html(decl.file, &string_result, var_decl.ast.type_node, .{ | |
| 739 | fileSourceHtml(decl.file, &string_result, var_decl.ast.type_node, .{ | |
| 738 | 740 | .skip_comments = true, |
| 739 | 741 | .collapse_whitespace = true, |
| 740 | 742 | }) catch |e| { |
| ... | ... | @@ -750,7 +752,7 @@ export fn decl_type_html(decl_index: Decl.Index) String { |
| 750 | 752 | |
| 751 | 753 | const Oom = error{OutOfMemory}; |
| 752 | 754 | |
| 753 | fn unpack_inner(tar_bytes: []u8) !void { | |
| 755 | fn unpackInner(tar_bytes: []u8) !void { | |
| 754 | 756 | var fbs = std.io.fixedBufferStream(tar_bytes); |
| 755 | 757 | var file_name_buffer: [1024]u8 = undefined; |
| 756 | 758 | var link_name_buffer: [1024]u8 = undefined; |
| ... | ... | @@ -902,382 +904,6 @@ export fn namespace_members(parent: Decl.Index, include_private: bool) Slice(Dec |
| 902 | 904 | return Slice(Decl.Index).init(g.members.items); |
| 903 | 905 | } |
| 904 | 906 | |
| 905 | const RenderSourceOptions = struct { | |
| 906 | skip_doc_comments: bool = false, | |
| 907 | skip_comments: bool = false, | |
| 908 | collapse_whitespace: bool = false, | |
| 909 | fn_link: Decl.Index = .none, | |
| 910 | }; | |
| 911 | ||
| 912 | fn file_source_html( | |
| 913 | file_index: Walk.File.Index, | |
| 914 | out: *std.ArrayListUnmanaged(u8), | |
| 915 | root_node: Ast.Node.Index, | |
| 916 | options: RenderSourceOptions, | |
| 917 | ) !void { | |
| 918 | const ast = file_index.get_ast(); | |
| 919 | const file = file_index.get(); | |
| 920 | ||
| 921 | const g = struct { | |
| 922 | var field_access_buffer: std.ArrayListUnmanaged(u8) = .{}; | |
| 923 | }; | |
| 924 | ||
| 925 | const token_tags = ast.tokens.items(.tag); | |
| 926 | const token_starts = ast.tokens.items(.start); | |
| 927 | const main_tokens = ast.nodes.items(.main_token); | |
| 928 | ||
| 929 | const start_token = ast.firstToken(root_node); | |
| 930 | const end_token = ast.lastToken(root_node) + 1; | |
| 931 | ||
| 932 | var cursor: usize = token_starts[start_token]; | |
| 933 | ||
| 934 | var indent: usize = 0; | |
| 935 | if (std.mem.lastIndexOf(u8, ast.source[0..cursor], "\n")) |newline_index| { | |
| 936 | for (ast.source[newline_index + 1 .. cursor]) |c| { | |
| 937 | if (c == ' ') { | |
| 938 | indent += 1; | |
| 939 | } else { | |
| 940 | break; | |
| 941 | } | |
| 942 | } | |
| 943 | } | |
| 944 | ||
| 945 | for ( | |
| 946 | token_tags[start_token..end_token], | |
| 947 | token_starts[start_token..end_token], | |
| 948 | start_token.., | |
| 949 | ) |tag, start, token_index| { | |
| 950 | const between = ast.source[cursor..start]; | |
| 951 | if (std.mem.trim(u8, between, " \t\r\n").len > 0) { | |
| 952 | if (!options.skip_comments) { | |
| 953 | try out.appendSlice(gpa, "<span class=\"tok-comment\">"); | |
| 954 | try appendUnindented(out, between, indent); | |
| 955 | try out.appendSlice(gpa, "</span>"); | |
| 956 | } | |
| 957 | } else if (between.len > 0) { | |
| 958 | if (options.collapse_whitespace) { | |
| 959 | if (out.items.len > 0 and out.items[out.items.len - 1] != ' ') | |
| 960 | try out.append(gpa, ' '); | |
| 961 | } else { | |
| 962 | try appendUnindented(out, between, indent); | |
| 963 | } | |
| 964 | } | |
| 965 | if (tag == .eof) break; | |
| 966 | const slice = ast.tokenSlice(token_index); | |
| 967 | cursor = start + slice.len; | |
| 968 | switch (tag) { | |
| 969 | .eof => unreachable, | |
| 970 | ||
| 971 | .keyword_addrspace, | |
| 972 | .keyword_align, | |
| 973 | .keyword_and, | |
| 974 | .keyword_asm, | |
| 975 | .keyword_async, | |
| 976 | .keyword_await, | |
| 977 | .keyword_break, | |
| 978 | .keyword_catch, | |
| 979 | .keyword_comptime, | |
| 980 | .keyword_const, | |
| 981 | .keyword_continue, | |
| 982 | .keyword_defer, | |
| 983 | .keyword_else, | |
| 984 | .keyword_enum, | |
| 985 | .keyword_errdefer, | |
| 986 | .keyword_error, | |
| 987 | .keyword_export, | |
| 988 | .keyword_extern, | |
| 989 | .keyword_for, | |
| 990 | .keyword_if, | |
| 991 | .keyword_inline, | |
| 992 | .keyword_noalias, | |
| 993 | .keyword_noinline, | |
| 994 | .keyword_nosuspend, | |
| 995 | .keyword_opaque, | |
| 996 | .keyword_or, | |
| 997 | .keyword_orelse, | |
| 998 | .keyword_packed, | |
| 999 | .keyword_anyframe, | |
| 1000 | .keyword_pub, | |
| 1001 | .keyword_resume, | |
| 1002 | .keyword_return, | |
| 1003 | .keyword_linksection, | |
| 1004 | .keyword_callconv, | |
| 1005 | .keyword_struct, | |
| 1006 | .keyword_suspend, | |
| 1007 | .keyword_switch, | |
| 1008 | .keyword_test, | |
| 1009 | .keyword_threadlocal, | |
| 1010 | .keyword_try, | |
| 1011 | .keyword_union, | |
| 1012 | .keyword_unreachable, | |
| 1013 | .keyword_usingnamespace, | |
| 1014 | .keyword_var, | |
| 1015 | .keyword_volatile, | |
| 1016 | .keyword_allowzero, | |
| 1017 | .keyword_while, | |
| 1018 | .keyword_anytype, | |
| 1019 | .keyword_fn, | |
| 1020 | => { | |
| 1021 | try out.appendSlice(gpa, "<span class=\"tok-kw\">"); | |
| 1022 | try appendEscaped(out, slice); | |
| 1023 | try out.appendSlice(gpa, "</span>"); | |
| 1024 | }, | |
| 1025 | ||
| 1026 | .string_literal, | |
| 1027 | .char_literal, | |
| 1028 | .multiline_string_literal_line, | |
| 1029 | => { | |
| 1030 | try out.appendSlice(gpa, "<span class=\"tok-str\">"); | |
| 1031 | try appendEscaped(out, slice); | |
| 1032 | try out.appendSlice(gpa, "</span>"); | |
| 1033 | }, | |
| 1034 | ||
| 1035 | .builtin => { | |
| 1036 | try out.appendSlice(gpa, "<span class=\"tok-builtin\">"); | |
| 1037 | try appendEscaped(out, slice); | |
| 1038 | try out.appendSlice(gpa, "</span>"); | |
| 1039 | }, | |
| 1040 | ||
| 1041 | .doc_comment, | |
| 1042 | .container_doc_comment, | |
| 1043 | => { | |
| 1044 | if (!options.skip_doc_comments) { | |
| 1045 | try out.appendSlice(gpa, "<span class=\"tok-comment\">"); | |
| 1046 | try appendEscaped(out, slice); | |
| 1047 | try out.appendSlice(gpa, "</span>"); | |
| 1048 | } | |
| 1049 | }, | |
| 1050 | ||
| 1051 | .identifier => i: { | |
| 1052 | if (options.fn_link != .none) { | |
| 1053 | const fn_link = options.fn_link.get(); | |
| 1054 | const fn_token = main_tokens[fn_link.ast_node]; | |
| 1055 | if (token_index == fn_token + 1) { | |
| 1056 | try out.appendSlice(gpa, "<a class=\"tok-fn\" href=\"#"); | |
| 1057 | _ = missing_feature_url_escape; | |
| 1058 | try fn_link.fqn(out); | |
| 1059 | try out.appendSlice(gpa, "\">"); | |
| 1060 | try appendEscaped(out, slice); | |
| 1061 | try out.appendSlice(gpa, "</a>"); | |
| 1062 | break :i; | |
| 1063 | } | |
| 1064 | } | |
| 1065 | ||
| 1066 | if (token_index > 0 and token_tags[token_index - 1] == .keyword_fn) { | |
| 1067 | try out.appendSlice(gpa, "<span class=\"tok-fn\">"); | |
| 1068 | try appendEscaped(out, slice); | |
| 1069 | try out.appendSlice(gpa, "</span>"); | |
| 1070 | break :i; | |
| 1071 | } | |
| 1072 | ||
| 1073 | if (Walk.isPrimitiveNonType(slice)) { | |
| 1074 | try out.appendSlice(gpa, "<span class=\"tok-null\">"); | |
| 1075 | try appendEscaped(out, slice); | |
| 1076 | try out.appendSlice(gpa, "</span>"); | |
| 1077 | break :i; | |
| 1078 | } | |
| 1079 | ||
| 1080 | if (std.zig.primitives.isPrimitive(slice)) { | |
| 1081 | try out.appendSlice(gpa, "<span class=\"tok-type\">"); | |
| 1082 | try appendEscaped(out, slice); | |
| 1083 | try out.appendSlice(gpa, "</span>"); | |
| 1084 | break :i; | |
| 1085 | } | |
| 1086 | ||
| 1087 | if (file.token_parents.get(token_index)) |field_access_node| { | |
| 1088 | g.field_access_buffer.clearRetainingCapacity(); | |
| 1089 | try walk_field_accesses(file_index, &g.field_access_buffer, field_access_node); | |
| 1090 | if (g.field_access_buffer.items.len > 0) { | |
| 1091 | try out.appendSlice(gpa, "<a href=\"#"); | |
| 1092 | _ = missing_feature_url_escape; | |
| 1093 | try out.appendSlice(gpa, g.field_access_buffer.items); | |
| 1094 | try out.appendSlice(gpa, "\">"); | |
| 1095 | try appendEscaped(out, slice); | |
| 1096 | try out.appendSlice(gpa, "</a>"); | |
| 1097 | } else { | |
| 1098 | try appendEscaped(out, slice); | |
| 1099 | } | |
| 1100 | break :i; | |
| 1101 | } | |
| 1102 | ||
| 1103 | { | |
| 1104 | g.field_access_buffer.clearRetainingCapacity(); | |
| 1105 | try resolve_ident_link(file_index, &g.field_access_buffer, token_index); | |
| 1106 | if (g.field_access_buffer.items.len > 0) { | |
| 1107 | try out.appendSlice(gpa, "<a href=\"#"); | |
| 1108 | _ = missing_feature_url_escape; | |
| 1109 | try out.appendSlice(gpa, g.field_access_buffer.items); | |
| 1110 | try out.appendSlice(gpa, "\">"); | |
| 1111 | try appendEscaped(out, slice); | |
| 1112 | try out.appendSlice(gpa, "</a>"); | |
| 1113 | break :i; | |
| 1114 | } | |
| 1115 | } | |
| 1116 | ||
| 1117 | try appendEscaped(out, slice); | |
| 1118 | }, | |
| 1119 | ||
| 1120 | .number_literal => { | |
| 1121 | try out.appendSlice(gpa, "<span class=\"tok-number\">"); | |
| 1122 | try appendEscaped(out, slice); | |
| 1123 | try out.appendSlice(gpa, "</span>"); | |
| 1124 | }, | |
| 1125 | ||
| 1126 | .bang, | |
| 1127 | .pipe, | |
| 1128 | .pipe_pipe, | |
| 1129 | .pipe_equal, | |
| 1130 | .equal, | |
| 1131 | .equal_equal, | |
| 1132 | .equal_angle_bracket_right, | |
| 1133 | .bang_equal, | |
| 1134 | .l_paren, | |
| 1135 | .r_paren, | |
| 1136 | .semicolon, | |
| 1137 | .percent, | |
| 1138 | .percent_equal, | |
| 1139 | .l_brace, | |
| 1140 | .r_brace, | |
| 1141 | .l_bracket, | |
| 1142 | .r_bracket, | |
| 1143 | .period, | |
| 1144 | .period_asterisk, | |
| 1145 | .ellipsis2, | |
| 1146 | .ellipsis3, | |
| 1147 | .caret, | |
| 1148 | .caret_equal, | |
| 1149 | .plus, | |
| 1150 | .plus_plus, | |
| 1151 | .plus_equal, | |
| 1152 | .plus_percent, | |
| 1153 | .plus_percent_equal, | |
| 1154 | .plus_pipe, | |
| 1155 | .plus_pipe_equal, | |
| 1156 | .minus, | |
| 1157 | .minus_equal, | |
| 1158 | .minus_percent, | |
| 1159 | .minus_percent_equal, | |
| 1160 | .minus_pipe, | |
| 1161 | .minus_pipe_equal, | |
| 1162 | .asterisk, | |
| 1163 | .asterisk_equal, | |
| 1164 | .asterisk_asterisk, | |
| 1165 | .asterisk_percent, | |
| 1166 | .asterisk_percent_equal, | |
| 1167 | .asterisk_pipe, | |
| 1168 | .asterisk_pipe_equal, | |
| 1169 | .arrow, | |
| 1170 | .colon, | |
| 1171 | .slash, | |
| 1172 | .slash_equal, | |
| 1173 | .comma, | |
| 1174 | .ampersand, | |
| 1175 | .ampersand_equal, | |
| 1176 | .question_mark, | |
| 1177 | .angle_bracket_left, | |
| 1178 | .angle_bracket_left_equal, | |
| 1179 | .angle_bracket_angle_bracket_left, | |
| 1180 | .angle_bracket_angle_bracket_left_equal, | |
| 1181 | .angle_bracket_angle_bracket_left_pipe, | |
| 1182 | .angle_bracket_angle_bracket_left_pipe_equal, | |
| 1183 | .angle_bracket_right, | |
| 1184 | .angle_bracket_right_equal, | |
| 1185 | .angle_bracket_angle_bracket_right, | |
| 1186 | .angle_bracket_angle_bracket_right_equal, | |
| 1187 | .tilde, | |
| 1188 | => try appendEscaped(out, slice), | |
| 1189 | ||
| 1190 | .invalid, .invalid_periodasterisks => return error.InvalidToken, | |
| 1191 | } | |
| 1192 | } | |
| 1193 | } | |
| 1194 | ||
| 1195 | fn unindent(s: []const u8, indent: usize) []const u8 { | |
| 1196 | var indent_idx: usize = 0; | |
| 1197 | for (s) |c| { | |
| 1198 | if (c == ' ' and indent_idx < indent) { | |
| 1199 | indent_idx += 1; | |
| 1200 | } else { | |
| 1201 | break; | |
| 1202 | } | |
| 1203 | } | |
| 1204 | return s[indent_idx..]; | |
| 1205 | } | |
| 1206 | ||
| 1207 | fn appendUnindented(out: *std.ArrayListUnmanaged(u8), s: []const u8, indent: usize) !void { | |
| 1208 | var it = std.mem.splitScalar(u8, s, '\n'); | |
| 1209 | var is_first_line = true; | |
| 1210 | while (it.next()) |line| { | |
| 1211 | if (is_first_line) { | |
| 1212 | try appendEscaped(out, line); | |
| 1213 | is_first_line = false; | |
| 1214 | } else { | |
| 1215 | try out.appendSlice(gpa, "\n"); | |
| 1216 | try appendEscaped(out, unindent(line, indent)); | |
| 1217 | } | |
| 1218 | } | |
| 1219 | } | |
| 1220 | ||
| 1221 | fn resolve_ident_link( | |
| 1222 | file_index: Walk.File.Index, | |
| 1223 | out: *std.ArrayListUnmanaged(u8), | |
| 1224 | ident_token: Ast.TokenIndex, | |
| 1225 | ) Oom!void { | |
| 1226 | const decl_index = file_index.get().lookup_token(ident_token); | |
| 1227 | if (decl_index == .none) return; | |
| 1228 | try resolve_decl_link(decl_index, out); | |
| 1229 | } | |
| 1230 | ||
| 1231 | fn resolve_decl_link(decl_index: Decl.Index, out: *std.ArrayListUnmanaged(u8)) Oom!void { | |
| 1232 | const decl = decl_index.get(); | |
| 1233 | switch (decl.categorize()) { | |
| 1234 | .alias => |alias_decl| try alias_decl.get().fqn(out), | |
| 1235 | else => try decl.fqn(out), | |
| 1236 | } | |
| 1237 | } | |
| 1238 | ||
| 1239 | fn walk_field_accesses( | |
| 1240 | file_index: Walk.File.Index, | |
| 1241 | out: *std.ArrayListUnmanaged(u8), | |
| 1242 | node: Ast.Node.Index, | |
| 1243 | ) Oom!void { | |
| 1244 | const ast = file_index.get_ast(); | |
| 1245 | const node_tags = ast.nodes.items(.tag); | |
| 1246 | assert(node_tags[node] == .field_access); | |
| 1247 | const node_datas = ast.nodes.items(.data); | |
| 1248 | const main_tokens = ast.nodes.items(.main_token); | |
| 1249 | const object_node = node_datas[node].lhs; | |
| 1250 | const dot_token = main_tokens[node]; | |
| 1251 | const field_ident = dot_token + 1; | |
| 1252 | switch (node_tags[object_node]) { | |
| 1253 | .identifier => { | |
| 1254 | const lhs_ident = main_tokens[object_node]; | |
| 1255 | try resolve_ident_link(file_index, out, lhs_ident); | |
| 1256 | }, | |
| 1257 | .field_access => { | |
| 1258 | try walk_field_accesses(file_index, out, object_node); | |
| 1259 | }, | |
| 1260 | else => {}, | |
| 1261 | } | |
| 1262 | if (out.items.len > 0) { | |
| 1263 | try out.append(gpa, '.'); | |
| 1264 | try out.appendSlice(gpa, ast.tokenSlice(field_ident)); | |
| 1265 | } | |
| 1266 | } | |
| 1267 | ||
| 1268 | fn appendEscaped(out: *std.ArrayListUnmanaged(u8), s: []const u8) !void { | |
| 1269 | for (s) |c| { | |
| 1270 | try out.ensureUnusedCapacity(gpa, 6); | |
| 1271 | switch (c) { | |
| 1272 | '&' => out.appendSliceAssumeCapacity("&amp;"), | |
| 1273 | '<' => out.appendSliceAssumeCapacity("&lt;"), | |
| 1274 | '>' => out.appendSliceAssumeCapacity("&gt;"), | |
| 1275 | '"' => out.appendSliceAssumeCapacity("&quot;"), | |
| 1276 | else => out.appendAssumeCapacity(c), | |
| 1277 | } | |
| 1278 | } | |
| 1279 | } | |
| 1280 | ||
| 1281 | 907 | fn count_scalar(haystack: []const u8, needle: u8) usize { |
| 1282 | 908 | var total: usize = 0; |
| 1283 | 909 | for (haystack) |elem| { |
lib/fuzzer.zig+218-27| ... | ... | @@ -2,6 +2,8 @@ const builtin = @import("builtin"); |
| 2 | 2 | const std = @import("std"); |
| 3 | 3 | const Allocator = std.mem.Allocator; |
| 4 | 4 | const assert = std.debug.assert; |
| 5 | const fatal = std.process.fatal; | |
| 6 | const SeenPcsHeader = std.Build.Fuzz.abi.SeenPcsHeader; | |
| 5 | 7 | |
| 6 | 8 | pub const std_options = .{ |
| 7 | 9 | .logFn = logOverride, |
| ... | ... | @@ -15,9 +17,9 @@ fn logOverride( |
| 15 | 17 | comptime format: []const u8, |
| 16 | 18 | args: anytype, |
| 17 | 19 | ) void { |
| 18 | if (builtin.mode != .Debug) return; | |
| 19 | 20 | const f = if (log_file) |f| f else f: { |
| 20 | const f = std.fs.cwd().createFile("libfuzzer.log", .{}) catch @panic("failed to open fuzzer log file"); | |
| 21 | const f = fuzzer.cache_dir.createFile("tmp/libfuzzer.log", .{}) catch | |
| 22 | @panic("failed to open fuzzer log file"); | |
| 21 | 23 | log_file = f; |
| 22 | 24 | break :f f; |
| 23 | 25 | }; |
| ... | ... | @@ -26,18 +28,19 @@ fn logOverride( |
| 26 | 28 | f.writer().print(prefix1 ++ prefix2 ++ format ++ "\n", args) catch @panic("failed to write to fuzzer log"); |
| 27 | 29 | } |
| 28 | 30 | |
| 29 | export threadlocal var __sancov_lowest_stack: usize = 0; | |
| 31 | export threadlocal var __sancov_lowest_stack: usize = std.math.maxInt(usize); | |
| 30 | 32 | |
| 31 | export fn __sanitizer_cov_8bit_counters_init(start: [*]u8, stop: [*]u8) void { | |
| 32 | std.log.debug("__sanitizer_cov_8bit_counters_init start={*}, stop={*}", .{ start, stop }); | |
| 33 | var module_count_8bc: usize = 0; | |
| 34 | var module_count_pcs: usize = 0; | |
| 35 | ||
| 36 | export fn __sanitizer_cov_8bit_counters_init(start: [*]u8, end: [*]u8) void { | |
| 37 | assert(@atomicRmw(usize, &module_count_8bc, .Add, 1, .monotonic) == 0); | |
| 38 | fuzzer.pc_counters = start[0 .. end - start]; | |
| 33 | 39 | } |
| 34 | 40 | |
| 35 | export fn __sanitizer_cov_pcs_init(pc_start: [*]const usize, pc_end: [*]const usize) void { | |
| 36 | std.log.debug("__sanitizer_cov_pcs_init pc_start={*}, pc_end={*}", .{ pc_start, pc_end }); | |
| 37 | fuzzer.pc_range = .{ | |
| 38 | .start = @intFromPtr(pc_start), | |
| 39 | .end = @intFromPtr(pc_start), | |
| 40 | }; | |
| 41 | export fn __sanitizer_cov_pcs_init(start: [*]const Fuzzer.FlaggedPc, end: [*]const Fuzzer.FlaggedPc) void { | |
| 42 | assert(@atomicRmw(usize, &module_count_pcs, .Add, 1, .monotonic) == 0); | |
| 43 | fuzzer.flagged_pcs = start[0 .. end - start]; | |
| 41 | 44 | } |
| 42 | 45 | |
| 43 | 46 | export fn __sanitizer_cov_trace_const_cmp1(arg1: u8, arg2: u8) void { |
| ... | ... | @@ -102,11 +105,21 @@ const Fuzzer = struct { |
| 102 | 105 | gpa: Allocator, |
| 103 | 106 | rng: std.Random.DefaultPrng, |
| 104 | 107 | input: std.ArrayListUnmanaged(u8), |
| 105 | pc_range: PcRange, | |
| 106 | count: usize, | |
| 108 | flagged_pcs: []const FlaggedPc, | |
| 109 | pc_counters: []u8, | |
| 110 | n_runs: usize, | |
| 107 | 111 | recent_cases: RunMap, |
| 108 | deduplicated_runs: usize, | |
| 112 | /// Data collected from code coverage instrumentation from one execution of | |
| 113 | /// the test function. | |
| 109 | 114 | coverage: Coverage, |
| 115 | /// Tracks which PCs have been seen across all runs that do not crash the fuzzer process. | |
| 116 | /// Stored in a memory-mapped file so that it can be shared with other | |
| 117 | /// processes and viewed while the fuzzer is running. | |
| 118 | seen_pcs: MemoryMappedList, | |
| 119 | cache_dir: std.fs.Dir, | |
| 120 | /// Identifies the file name that will be used to store coverage | |
| 121 | /// information, available to other processes. | |
| 122 | coverage_id: u64, | |
| 110 | 123 | |
| 111 | 124 | const RunMap = std.ArrayHashMapUnmanaged(Run, void, Run.HashContext, false); |
| 112 | 125 | |
| ... | ... | @@ -161,9 +174,12 @@ const Fuzzer = struct { |
| 161 | 174 | } |
| 162 | 175 | }; |
| 163 | 176 | |
| 164 | const PcRange = struct { | |
| 165 | start: usize, | |
| 166 | end: usize, | |
| 177 | const FlaggedPc = extern struct { | |
| 178 | addr: usize, | |
| 179 | flags: packed struct(usize) { | |
| 180 | entry: bool, | |
| 181 | _: @Type(.{ .Int = .{ .signedness = .unsigned, .bits = @bitSizeOf(usize) - 1 } }), | |
| 182 | }, | |
| 167 | 183 | }; |
| 168 | 184 | |
| 169 | 185 | const Analysis = struct { |
| ... | ... | @@ -171,6 +187,72 @@ const Fuzzer = struct { |
| 171 | 187 | id: Run.Id, |
| 172 | 188 | }; |
| 173 | 189 | |
| 190 | fn init(f: *Fuzzer, cache_dir: std.fs.Dir) !void { | |
| 191 | const flagged_pcs = f.flagged_pcs; | |
| 192 | ||
| 193 | f.cache_dir = cache_dir; | |
| 194 | ||
| 195 | // Choose a file name for the coverage based on a hash of the PCs that will be stored within. | |
| 196 | const pc_digest = d: { | |
| 197 | var hasher = std.hash.Wyhash.init(0); | |
| 198 | for (flagged_pcs) |flagged_pc| { | |
| 199 | hasher.update(std.mem.asBytes(&flagged_pc.addr)); | |
| 200 | } | |
| 201 | break :d f.coverage.run_id_hasher.final(); | |
| 202 | }; | |
| 203 | f.coverage_id = pc_digest; | |
| 204 | const hex_digest = std.fmt.hex(pc_digest); | |
| 205 | const coverage_file_path = "v/" ++ hex_digest; | |
| 206 | ||
| 207 | // Layout of this file: | |
| 208 | // - Header | |
| 209 | // - list of PC addresses (usize elements) | |
| 210 | // - list of hit flag, 1 bit per address (stored in u8 elements) | |
| 211 | const coverage_file = createFileBail(cache_dir, coverage_file_path, .{ | |
| 212 | .read = true, | |
| 213 | .truncate = false, | |
| 214 | }); | |
| 215 | defer coverage_file.close(); | |
| 216 | const n_bitset_elems = (flagged_pcs.len + 7) / 8; | |
| 217 | const bytes_len = @sizeOf(SeenPcsHeader) + flagged_pcs.len * @sizeOf(usize) + n_bitset_elems; | |
| 218 | const existing_len = coverage_file.getEndPos() catch |err| { | |
| 219 | fatal("unable to check len of coverage file: {s}", .{@errorName(err)}); | |
| 220 | }; | |
| 221 | if (existing_len == 0) { | |
| 222 | coverage_file.setEndPos(bytes_len) catch |err| { | |
| 223 | fatal("unable to set len of coverage file: {s}", .{@errorName(err)}); | |
| 224 | }; | |
| 225 | } else if (existing_len != bytes_len) { | |
| 226 | fatal("incompatible existing coverage file (differing lengths)", .{}); | |
| 227 | } | |
| 228 | f.seen_pcs = MemoryMappedList.init(coverage_file, existing_len, bytes_len) catch |err| { | |
| 229 | fatal("unable to init coverage memory map: {s}", .{@errorName(err)}); | |
| 230 | }; | |
| 231 | if (existing_len != 0) { | |
| 232 | const existing_pcs_bytes = f.seen_pcs.items[@sizeOf(SeenPcsHeader)..][0 .. flagged_pcs.len * @sizeOf(usize)]; | |
| 233 | const existing_pcs = std.mem.bytesAsSlice(usize, existing_pcs_bytes); | |
| 234 | for (existing_pcs, flagged_pcs, 0..) |old, new, i| { | |
| 235 | if (old != new.addr) { | |
| 236 | fatal("incompatible existing coverage file (differing PC at index {d}: {x} != {x})", .{ | |
| 237 | i, old, new.addr, | |
| 238 | }); | |
| 239 | } | |
| 240 | } | |
| 241 | } else { | |
| 242 | const header: SeenPcsHeader = .{ | |
| 243 | .n_runs = 0, | |
| 244 | .unique_runs = 0, | |
| 245 | .pcs_len = flagged_pcs.len, | |
| 246 | .lowest_stack = std.math.maxInt(usize), | |
| 247 | }; | |
| 248 | f.seen_pcs.appendSliceAssumeCapacity(std.mem.asBytes(&header)); | |
| 249 | for (flagged_pcs) |flagged_pc| { | |
| 250 | f.seen_pcs.appendSliceAssumeCapacity(std.mem.asBytes(&flagged_pc.addr)); | |
| 251 | } | |
| 252 | f.seen_pcs.appendNTimesAssumeCapacity(0, n_bitset_elems); | |
| 253 | } | |
| 254 | } | |
| 255 | ||
| 174 | 256 | fn analyzeLastRun(f: *Fuzzer) Analysis { |
| 175 | 257 | return .{ |
| 176 | 258 | .id = f.coverage.run_id_hasher.final(), |
| ... | ... | @@ -194,7 +276,7 @@ const Fuzzer = struct { |
| 194 | 276 | .score = 0, |
| 195 | 277 | }, {}); |
| 196 | 278 | } else { |
| 197 | if (f.count % 1000 == 0) f.dumpStats(); | |
| 279 | if (f.n_runs % 10000 == 0) f.dumpStats(); | |
| 198 | 280 | |
| 199 | 281 | const analysis = f.analyzeLastRun(); |
| 200 | 282 | const gop = f.recent_cases.getOrPutAssumeCapacity(.{ |
| ... | ... | @@ -204,7 +286,6 @@ const Fuzzer = struct { |
| 204 | 286 | }); |
| 205 | 287 | if (gop.found_existing) { |
| 206 | 288 | //std.log.info("duplicate analysis: score={d} id={d}", .{ analysis.score, analysis.id }); |
| 207 | f.deduplicated_runs += 1; | |
| 208 | 289 | if (f.input.items.len < gop.key_ptr.input.len or gop.key_ptr.score == 0) { |
| 209 | 290 | gpa.free(gop.key_ptr.input); |
| 210 | 291 | gop.key_ptr.input = try gpa.dupe(u8, f.input.items); |
| ... | ... | @@ -217,6 +298,28 @@ const Fuzzer = struct { |
| 217 | 298 | .input = try gpa.dupe(u8, f.input.items), |
| 218 | 299 | .score = analysis.score, |
| 219 | 300 | }; |
| 301 | ||
| 302 | // Track code coverage from all runs. | |
| 303 | { | |
| 304 | const seen_pcs = f.seen_pcs.items[@sizeOf(SeenPcsHeader) + f.flagged_pcs.len * @sizeOf(usize) ..]; | |
| 305 | for (seen_pcs, 0..) |*elem, i| { | |
| 306 | const byte_i = i * 8; | |
| 307 | const mask: u8 = | |
| 308 | (@as(u8, @intFromBool(f.pc_counters.ptr[byte_i + 0] != 0)) << 0) | | |
| 309 | (@as(u8, @intFromBool(f.pc_counters.ptr[byte_i + 1] != 0)) << 1) | | |
| 310 | (@as(u8, @intFromBool(f.pc_counters.ptr[byte_i + 2] != 0)) << 2) | | |
| 311 | (@as(u8, @intFromBool(f.pc_counters.ptr[byte_i + 3] != 0)) << 3) | | |
| 312 | (@as(u8, @intFromBool(f.pc_counters.ptr[byte_i + 4] != 0)) << 4) | | |
| 313 | (@as(u8, @intFromBool(f.pc_counters.ptr[byte_i + 5] != 0)) << 5) | | |
| 314 | (@as(u8, @intFromBool(f.pc_counters.ptr[byte_i + 6] != 0)) << 6) | | |
| 315 | (@as(u8, @intFromBool(f.pc_counters.ptr[byte_i + 7] != 0)) << 7); | |
| 316 | ||
| 317 | _ = @atomicRmw(u8, elem, .Or, mask, .monotonic); | |
| 318 | } | |
| 319 | } | |
| 320 | ||
| 321 | const header: *volatile SeenPcsHeader = @ptrCast(f.seen_pcs.items[0..@sizeOf(SeenPcsHeader)]); | |
| 322 | _ = @atomicRmw(usize, &header.unique_runs, .Add, 1, .monotonic); | |
| 220 | 323 | } |
| 221 | 324 | |
| 222 | 325 | if (f.recent_cases.entries.len >= 100) { |
| ... | ... | @@ -244,8 +347,12 @@ const Fuzzer = struct { |
| 244 | 347 | f.input.appendSliceAssumeCapacity(run.input); |
| 245 | 348 | try f.mutate(); |
| 246 | 349 | |
| 350 | f.n_runs += 1; | |
| 351 | const header: *volatile SeenPcsHeader = @ptrCast(f.seen_pcs.items[0..@sizeOf(SeenPcsHeader)]); | |
| 352 | _ = @atomicRmw(usize, &header.n_runs, .Add, 1, .monotonic); | |
| 353 | _ = @atomicRmw(usize, &header.lowest_stack, .Min, __sancov_lowest_stack, .monotonic); | |
| 354 | @memset(f.pc_counters, 0); | |
| 247 | 355 | f.coverage.reset(); |
| 248 | f.count += 1; | |
| 249 | 356 | return f.input.items; |
| 250 | 357 | } |
| 251 | 358 | |
| ... | ... | @@ -256,10 +363,6 @@ const Fuzzer = struct { |
| 256 | 363 | } |
| 257 | 364 | |
| 258 | 365 | fn dumpStats(f: *Fuzzer) void { |
| 259 | std.log.info("stats: runs={d} deduplicated={d}", .{ | |
| 260 | f.count, | |
| 261 | f.deduplicated_runs, | |
| 262 | }); | |
| 263 | 366 | for (f.recent_cases.keys()[0..@min(f.recent_cases.entries.len, 5)], 0..) |run, i| { |
| 264 | 367 | std.log.info("best[{d}] id={x} score={d} input: '{}'", .{ |
| 265 | 368 | i, run.id, run.score, std.zig.fmtEscapes(run.input), |
| ... | ... | @@ -291,6 +394,21 @@ const Fuzzer = struct { |
| 291 | 394 | } |
| 292 | 395 | }; |
| 293 | 396 | |
| 397 | fn createFileBail(dir: std.fs.Dir, sub_path: []const u8, flags: std.fs.File.CreateFlags) std.fs.File { | |
| 398 | return dir.createFile(sub_path, flags) catch |err| switch (err) { | |
| 399 | error.FileNotFound => { | |
| 400 | const dir_name = std.fs.path.dirname(sub_path).?; | |
| 401 | dir.makePath(dir_name) catch |e| { | |
| 402 | fatal("unable to make path '{s}': {s}", .{ dir_name, @errorName(e) }); | |
| 403 | }; | |
| 404 | return dir.createFile(sub_path, flags) catch |e| { | |
| 405 | fatal("unable to create file '{s}': {s}", .{ sub_path, @errorName(e) }); | |
| 406 | }; | |
| 407 | }, | |
| 408 | else => fatal("unable to create file '{s}': {s}", .{ sub_path, @errorName(err) }), | |
| 409 | }; | |
| 410 | } | |
| 411 | ||
| 294 | 412 | fn oom(err: anytype) noreturn { |
| 295 | 413 | switch (err) { |
| 296 | 414 | error.OutOfMemory => @panic("out of memory"), |
| ... | ... | @@ -303,15 +421,88 @@ var fuzzer: Fuzzer = .{ |
| 303 | 421 | .gpa = general_purpose_allocator.allocator(), |
| 304 | 422 | .rng = std.Random.DefaultPrng.init(0), |
| 305 | 423 | .input = .{}, |
| 306 | .pc_range = .{ .start = 0, .end = 0 }, | |
| 307 | .count = 0, | |
| 308 | .deduplicated_runs = 0, | |
| 424 | .flagged_pcs = undefined, | |
| 425 | .pc_counters = undefined, | |
| 426 | .n_runs = 0, | |
| 309 | 427 | .recent_cases = .{}, |
| 310 | 428 | .coverage = undefined, |
| 429 | .cache_dir = undefined, | |
| 430 | .seen_pcs = undefined, | |
| 431 | .coverage_id = undefined, | |
| 311 | 432 | }; |
| 312 | 433 | |
| 434 | /// Invalid until `fuzzer_init` is called. | |
| 435 | export fn fuzzer_coverage_id() u64 { | |
| 436 | return fuzzer.coverage_id; | |
| 437 | } | |
| 438 | ||
| 313 | 439 | export fn fuzzer_next() Fuzzer.Slice { |
| 314 | 440 | return Fuzzer.Slice.fromZig(fuzzer.next() catch |err| switch (err) { |
| 315 | 441 | error.OutOfMemory => @panic("out of memory"), |
| 316 | 442 | }); |
| 317 | 443 | } |
| 444 | ||
| 445 | export fn fuzzer_init(cache_dir_struct: Fuzzer.Slice) void { | |
| 446 | if (module_count_8bc == 0) fatal("__sanitizer_cov_8bit_counters_init was never called", .{}); | |
| 447 | if (module_count_pcs == 0) fatal("__sanitizer_cov_pcs_init was never called", .{}); | |
| 448 | ||
| 449 | const cache_dir_path = cache_dir_struct.toZig(); | |
| 450 | const cache_dir = if (cache_dir_path.len == 0) | |
| 451 | std.fs.cwd() | |
| 452 | else | |
| 453 | std.fs.cwd().makeOpenPath(cache_dir_path, .{ .iterate = true }) catch |err| { | |
| 454 | fatal("unable to open fuzz directory '{s}': {s}", .{ cache_dir_path, @errorName(err) }); | |
| 455 | }; | |
| 456 | ||
| 457 | fuzzer.init(cache_dir) catch |err| fatal("unable to init fuzzer: {s}", .{@errorName(err)}); | |
| 458 | } | |
| 459 | ||
| 460 | /// Like `std.ArrayListUnmanaged(u8)` but backed by memory mapping. | |
| 461 | pub const MemoryMappedList = struct { | |
| 462 | /// Contents of the list. | |
| 463 | /// | |
| 464 | /// Pointers to elements in this slice are invalidated by various functions | |
| 465 | /// of this ArrayList in accordance with the respective documentation. In | |
| 466 | /// all cases, "invalidated" means that the memory has been passed to this | |
| 467 | /// allocator's resize or free function. | |
| 468 | items: []align(std.mem.page_size) volatile u8, | |
| 469 | /// How many bytes this list can hold without allocating additional memory. | |
| 470 | capacity: usize, | |
| 471 | ||
| 472 | pub fn init(file: std.fs.File, length: usize, capacity: usize) !MemoryMappedList { | |
| 473 | const ptr = try std.posix.mmap( | |
| 474 | null, | |
| 475 | capacity, | |
| 476 | std.posix.PROT.READ | std.posix.PROT.WRITE, | |
| 477 | .{ .TYPE = .SHARED }, | |
| 478 | file.handle, | |
| 479 | 0, | |
| 480 | ); | |
| 481 | return .{ | |
| 482 | .items = ptr[0..length], | |
| 483 | .capacity = capacity, | |
| 484 | }; | |
| 485 | } | |
| 486 | ||
| 487 | /// Append the slice of items to the list. | |
| 488 | /// Asserts that the list can hold the additional items. | |
| 489 | pub fn appendSliceAssumeCapacity(l: *MemoryMappedList, items: []const u8) void { | |
| 490 | const old_len = l.items.len; | |
| 491 | const new_len = old_len + items.len; | |
| 492 | assert(new_len <= l.capacity); | |
| 493 | l.items.len = new_len; | |
| 494 | @memcpy(l.items[old_len..][0..items.len], items); | |
| 495 | } | |
| 496 | ||
| 497 | /// Append a value to the list `n` times. | |
| 498 | /// Never invalidates element pointers. | |
| 499 | /// The function is inline so that a comptime-known `value` parameter will | |
| 500 | /// have better memset codegen in case it has a repeated byte pattern. | |
| 501 | /// Asserts that the list can hold the additional items. | |
| 502 | pub inline fn appendNTimesAssumeCapacity(l: *MemoryMappedList, value: u8, n: usize) void { | |
| 503 | const new_len = l.items.len + n; | |
| 504 | assert(new_len <= l.capacity); | |
| 505 | @memset(l.items.ptr[l.items.len..new_len], value); | |
| 506 | l.items.len = new_len; | |
| 507 | } | |
| 508 | }; |
lib/fuzzer/index.html created+161| ... | ... | @@ -0,0 +1,161 @@ |
| 1 | <!doctype html> | |
| 2 | <html> | |
| 3 | <head> | |
| 4 | <meta charset="utf-8"> | |
| 5 | <title>Zig Build System Interface</title> | |
| 6 | <style type="text/css"> | |
| 7 | body { | |
| 8 | font-family: system-ui, -apple-system, Roboto, "Segoe UI", sans-serif; | |
| 9 | color: #000000; | |
| 10 | } | |
| 11 | .hidden { | |
| 12 | display: none; | |
| 13 | } | |
| 14 | table { | |
| 15 | width: 100%; | |
| 16 | } | |
| 17 | a { | |
| 18 | color: #2A6286; | |
| 19 | } | |
| 20 | pre{ | |
| 21 | font-family:"Source Code Pro",monospace; | |
| 22 | font-size:1em; | |
| 23 | background-color:#F5F5F5; | |
| 24 | padding: 1em; | |
| 25 | margin: 0; | |
| 26 | overflow-x: auto; | |
| 27 | } | |
| 28 | :not(pre) > code { | |
| 29 | white-space: break-spaces; | |
| 30 | } | |
| 31 | code { | |
| 32 | font-family:"Source Code Pro",monospace; | |
| 33 | font-size: 0.9em; | |
| 34 | } | |
| 35 | code a { | |
| 36 | color: #000000; | |
| 37 | } | |
| 38 | kbd { | |
| 39 | color: #000; | |
| 40 | background-color: #fafbfc; | |
| 41 | border-color: #d1d5da; | |
| 42 | border-bottom-color: #c6cbd1; | |
| 43 | box-shadow-color: #c6cbd1; | |
| 44 | display: inline-block; | |
| 45 | padding: 0.3em 0.2em; | |
| 46 | font: 1.2em monospace; | |
| 47 | line-height: 0.8em; | |
| 48 | vertical-align: middle; | |
| 49 | border: solid 1px; | |
| 50 | border-radius: 3px; | |
| 51 | box-shadow: inset 0 -1px 0; | |
| 52 | cursor: default; | |
| 53 | } | |
| 54 | ||
| 55 | .l { | |
| 56 | display: inline-block; | |
| 57 | background: red; | |
| 58 | width: 1em; | |
| 59 | height: 1em; | |
| 60 | border-radius: 1em; | |
| 61 | } | |
| 62 | .c { | |
| 63 | background-color: green; | |
| 64 | } | |
| 65 | ||
| 66 | .tok-kw { | |
| 67 | color: #333; | |
| 68 | font-weight: bold; | |
| 69 | } | |
| 70 | .tok-str { | |
| 71 | color: #d14; | |
| 72 | } | |
| 73 | .tok-builtin { | |
| 74 | color: #0086b3; | |
| 75 | } | |
| 76 | .tok-comment { | |
| 77 | color: #777; | |
| 78 | font-style: italic; | |
| 79 | } | |
| 80 | .tok-fn { | |
| 81 | color: #900; | |
| 82 | font-weight: bold; | |
| 83 | } | |
| 84 | .tok-null { | |
| 85 | color: #008080; | |
| 86 | } | |
| 87 | .tok-number { | |
| 88 | color: #008080; | |
| 89 | } | |
| 90 | .tok-type { | |
| 91 | color: #458; | |
| 92 | font-weight: bold; | |
| 93 | } | |
| 94 | ||
| 95 | @media (prefers-color-scheme: dark) { | |
| 96 | body { | |
| 97 | background-color: #111; | |
| 98 | color: #bbb; | |
| 99 | } | |
| 100 | pre { | |
| 101 | background-color: #222; | |
| 102 | color: #ccc; | |
| 103 | } | |
| 104 | a { | |
| 105 | color: #88f; | |
| 106 | } | |
| 107 | code a { | |
| 108 | color: #ccc; | |
| 109 | } | |
| 110 | .l { | |
| 111 | background-color: red; | |
| 112 | } | |
| 113 | .c { | |
| 114 | background-color: green; | |
| 115 | } | |
| 116 | .tok-kw { | |
| 117 | color: #eee; | |
| 118 | } | |
| 119 | .tok-str { | |
| 120 | color: #2e5; | |
| 121 | } | |
| 122 | .tok-builtin { | |
| 123 | color: #ff894c; | |
| 124 | } | |
| 125 | .tok-comment { | |
| 126 | color: #aa7; | |
| 127 | } | |
| 128 | .tok-fn { | |
| 129 | color: #B1A0F8; | |
| 130 | } | |
| 131 | .tok-null { | |
| 132 | color: #ff8080; | |
| 133 | } | |
| 134 | .tok-number { | |
| 135 | color: #ff8080; | |
| 136 | } | |
| 137 | .tok-type { | |
| 138 | color: #68f; | |
| 139 | } | |
| 140 | } | |
| 141 | </style> | |
| 142 | </head> | |
| 143 | <body> | |
| 144 | <p id="status">Loading JavaScript...</p> | |
| 145 | <div id="sectStats" class="hidden"> | |
| 146 | <ul> | |
| 147 | <li>Total Runs: <span id="statTotalRuns"></span></li> | |
| 148 | <li>Unique Runs: <span id="statUniqueRuns"></span></li> | |
| 149 | <li>Coverage: <span id="statCoverage"></span></li> | |
| 150 | <li>Lowest Stack: <span id="statLowestStack"></span></li> | |
| 151 | <li>Entry Points: <ul id="entryPointsList"></ul></li> | |
| 152 | </ul> | |
| 153 | </div> | |
| 154 | <div id="sectSource" class="hidden"> | |
| 155 | <h2>Source Code</h2> | |
| 156 | <pre><code id="sourceText"></code></pre> | |
| 157 | </div> | |
| 158 | <script src="main.js"></script> | |
| 159 | </body> | |
| 160 | </html> | |
| 161 |
lib/fuzzer/main.js created+249| ... | ... | @@ -0,0 +1,249 @@ |
| 1 | (function() { | |
| 2 | const domStatus = document.getElementById("status"); | |
| 3 | const domSectSource = document.getElementById("sectSource"); | |
| 4 | const domSectStats = document.getElementById("sectStats"); | |
| 5 | const domSourceText = document.getElementById("sourceText"); | |
| 6 | const domStatTotalRuns = document.getElementById("statTotalRuns"); | |
| 7 | const domStatUniqueRuns = document.getElementById("statUniqueRuns"); | |
| 8 | const domStatCoverage = document.getElementById("statCoverage"); | |
| 9 | const domStatLowestStack = document.getElementById("statLowestStack"); | |
| 10 | const domEntryPointsList = document.getElementById("entryPointsList"); | |
| 11 | ||
| 12 | let wasm_promise = fetch("main.wasm"); | |
| 13 | let sources_promise = fetch("sources.tar").then(function(response) { | |
| 14 | if (!response.ok) throw new Error("unable to download sources"); | |
| 15 | return response.arrayBuffer(); | |
| 16 | }); | |
| 17 | var wasm_exports = null; | |
| 18 | var curNavSearch = null; | |
| 19 | var curNavLocation = null; | |
| 20 | ||
| 21 | const text_decoder = new TextDecoder(); | |
| 22 | const text_encoder = new TextEncoder(); | |
| 23 | ||
| 24 | domStatus.textContent = "Loading WebAssembly..."; | |
| 25 | WebAssembly.instantiateStreaming(wasm_promise, { | |
| 26 | js: { | |
| 27 | log: function(ptr, len) { | |
| 28 | const msg = decodeString(ptr, len); | |
| 29 | console.log(msg); | |
| 30 | }, | |
| 31 | panic: function (ptr, len) { | |
| 32 | const msg = decodeString(ptr, len); | |
| 33 | throw new Error("panic: " + msg); | |
| 34 | }, | |
| 35 | emitSourceIndexChange: onSourceIndexChange, | |
| 36 | emitCoverageUpdate: onCoverageUpdate, | |
| 37 | emitEntryPointsUpdate: renderStats, | |
| 38 | }, | |
| 39 | }).then(function(obj) { | |
| 40 | wasm_exports = obj.instance.exports; | |
| 41 | window.wasm = obj; // for debugging | |
| 42 | domStatus.textContent = "Loading sources tarball..."; | |
| 43 | ||
| 44 | sources_promise.then(function(buffer) { | |
| 45 | domStatus.textContent = "Parsing sources..."; | |
| 46 | const js_array = new Uint8Array(buffer); | |
| 47 | const ptr = wasm_exports.alloc(js_array.length); | |
| 48 | const wasm_array = new Uint8Array(wasm_exports.memory.buffer, ptr, js_array.length); | |
| 49 | wasm_array.set(js_array); | |
| 50 | wasm_exports.unpack(ptr, js_array.length); | |
| 51 | ||
| 52 | window.addEventListener('popstate', onPopState, false); | |
| 53 | onHashChange(null); | |
| 54 | ||
| 55 | domStatus.textContent = "Waiting for server to send source location metadata..."; | |
| 56 | connectWebSocket(); | |
| 57 | }); | |
| 58 | }); | |
| 59 | ||
| 60 | function onPopState(ev) { | |
| 61 | onHashChange(ev.state); | |
| 62 | } | |
| 63 | ||
| 64 | function onHashChange(state) { | |
| 65 | history.replaceState({}, ""); | |
| 66 | navigate(location.hash); | |
| 67 | if (state == null) window.scrollTo({top: 0}); | |
| 68 | } | |
| 69 | ||
| 70 | function navigate(location_hash) { | |
| 71 | domSectSource.classList.add("hidden"); | |
| 72 | ||
| 73 | curNavLocation = null; | |
| 74 | curNavSearch = null; | |
| 75 | ||
| 76 | if (location_hash.length > 1 && location_hash[0] === '#') { | |
| 77 | const query = location_hash.substring(1); | |
| 78 | const qpos = query.indexOf("?"); | |
| 79 | let nonSearchPart; | |
| 80 | if (qpos === -1) { | |
| 81 | nonSearchPart = query; | |
| 82 | } else { | |
| 83 | nonSearchPart = query.substring(0, qpos); | |
| 84 | curNavSearch = decodeURIComponent(query.substring(qpos + 1)); | |
| 85 | } | |
| 86 | ||
| 87 | if (nonSearchPart[0] == "l") { | |
| 88 | curNavLocation = +nonSearchPart.substring(1); | |
| 89 | renderSource(curNavLocation); | |
| 90 | } | |
| 91 | } | |
| 92 | ||
| 93 | render(); | |
| 94 | } | |
| 95 | ||
| 96 | function connectWebSocket() { | |
| 97 | const host = document.location.host; | |
| 98 | const pathname = document.location.pathname; | |
| 99 | const isHttps = document.location.protocol === 'https:'; | |
| 100 | const match = host.match(/^(.+):(\d+)$/); | |
| 101 | const defaultPort = isHttps ? 443 : 80; | |
| 102 | const port = match ? parseInt(match[2], 10) : defaultPort; | |
| 103 | const hostName = match ? match[1] : host; | |
| 104 | const wsProto = isHttps ? "wss:" : "ws:"; | |
| 105 | const wsUrl = wsProto + '//' + hostName + ':' + port + pathname; | |
| 106 | ws = new WebSocket(wsUrl); | |
| 107 | ws.binaryType = "arraybuffer"; | |
| 108 | ws.addEventListener('message', onWebSocketMessage, false); | |
| 109 | ws.addEventListener('error', timeoutThenCreateNew, false); | |
| 110 | ws.addEventListener('close', timeoutThenCreateNew, false); | |
| 111 | ws.addEventListener('open', onWebSocketOpen, false); | |
| 112 | } | |
| 113 | ||
| 114 | function onWebSocketOpen() { | |
| 115 | //console.log("web socket opened"); | |
| 116 | } | |
| 117 | ||
| 118 | function onWebSocketMessage(ev) { | |
| 119 | wasmOnMessage(ev.data); | |
| 120 | } | |
| 121 | ||
| 122 | function timeoutThenCreateNew() { | |
| 123 | ws.removeEventListener('message', onWebSocketMessage, false); | |
| 124 | ws.removeEventListener('error', timeoutThenCreateNew, false); | |
| 125 | ws.removeEventListener('close', timeoutThenCreateNew, false); | |
| 126 | ws.removeEventListener('open', onWebSocketOpen, false); | |
| 127 | ws = null; | |
| 128 | setTimeout(connectWebSocket, 1000); | |
| 129 | } | |
| 130 | ||
| 131 | function wasmOnMessage(data) { | |
| 132 | const jsArray = new Uint8Array(data); | |
| 133 | const ptr = wasm_exports.message_begin(jsArray.length); | |
| 134 | const wasmArray = new Uint8Array(wasm_exports.memory.buffer, ptr, jsArray.length); | |
| 135 | wasmArray.set(jsArray); | |
| 136 | wasm_exports.message_end(); | |
| 137 | } | |
| 138 | ||
| 139 | function onSourceIndexChange() { | |
| 140 | render(); | |
| 141 | if (curNavLocation != null) renderSource(curNavLocation); | |
| 142 | } | |
| 143 | ||
| 144 | function onCoverageUpdate() { | |
| 145 | renderStats(); | |
| 146 | renderCoverage(); | |
| 147 | } | |
| 148 | ||
| 149 | function render() { | |
| 150 | domStatus.classList.add("hidden"); | |
| 151 | } | |
| 152 | ||
| 153 | function renderStats() { | |
| 154 | const totalRuns = wasm_exports.totalRuns(); | |
| 155 | const uniqueRuns = wasm_exports.uniqueRuns(); | |
| 156 | const totalSourceLocations = wasm_exports.totalSourceLocations(); | |
| 157 | const coveredSourceLocations = wasm_exports.coveredSourceLocations(); | |
| 158 | domStatTotalRuns.innerText = totalRuns; | |
| 159 | domStatUniqueRuns.innerText = uniqueRuns + " (" + percent(uniqueRuns, totalRuns) + "%)"; | |
| 160 | domStatCoverage.innerText = coveredSourceLocations + " / " + totalSourceLocations + " (" + percent(coveredSourceLocations, totalSourceLocations) + "%)"; | |
| 161 | domStatLowestStack.innerText = unwrapString(wasm_exports.lowestStack()); | |
| 162 | ||
| 163 | const entryPoints = unwrapInt32Array(wasm_exports.entryPoints()); | |
| 164 | resizeDomList(domEntryPointsList, entryPoints.length, "<li></li>"); | |
| 165 | for (let i = 0; i < entryPoints.length; i += 1) { | |
| 166 | const liDom = domEntryPointsList.children[i]; | |
| 167 | liDom.innerHTML = unwrapString(wasm_exports.sourceLocationLinkHtml(entryPoints[i])); | |
| 168 | } | |
| 169 | ||
| 170 | ||
| 171 | domSectStats.classList.remove("hidden"); | |
| 172 | } | |
| 173 | ||
| 174 | function renderCoverage() { | |
| 175 | if (curNavLocation == null) return; | |
| 176 | const sourceLocationIndex = curNavLocation; | |
| 177 | ||
| 178 | for (let i = 0; i < domSourceText.children.length; i += 1) { | |
| 179 | const childDom = domSourceText.children[i]; | |
| 180 | if (childDom.id != null && childDom.id[0] == "l") { | |
| 181 | childDom.classList.add("l"); | |
| 182 | childDom.classList.remove("c"); | |
| 183 | } | |
| 184 | } | |
| 185 | const coveredList = unwrapInt32Array(wasm_exports.sourceLocationFileCoveredList(sourceLocationIndex)); | |
| 186 | for (let i = 0; i < coveredList.length; i += 1) { | |
| 187 | document.getElementById("l" + coveredList[i]).classList.add("c"); | |
| 188 | } | |
| 189 | } | |
| 190 | ||
| 191 | function resizeDomList(listDom, desiredLen, templateHtml) { | |
| 192 | for (let i = listDom.childElementCount; i < desiredLen; i += 1) { | |
| 193 | listDom.insertAdjacentHTML('beforeend', templateHtml); | |
| 194 | } | |
| 195 | while (desiredLen < listDom.childElementCount) { | |
| 196 | listDom.removeChild(listDom.lastChild); | |
| 197 | } | |
| 198 | } | |
| 199 | ||
| 200 | function percent(a, b) { | |
| 201 | return ((Number(a) / Number(b)) * 100).toFixed(1); | |
| 202 | } | |
| 203 | ||
| 204 | function renderSource(sourceLocationIndex) { | |
| 205 | const pathName = unwrapString(wasm_exports.sourceLocationPath(sourceLocationIndex)); | |
| 206 | if (pathName.length === 0) return; | |
| 207 | ||
| 208 | const h2 = domSectSource.children[0]; | |
| 209 | h2.innerText = pathName; | |
| 210 | domSourceText.innerHTML = unwrapString(wasm_exports.sourceLocationFileHtml(sourceLocationIndex)); | |
| 211 | ||
| 212 | domSectSource.classList.remove("hidden"); | |
| 213 | ||
| 214 | // Empirically, Firefox needs this requestAnimationFrame in order for the scrollIntoView to work. | |
| 215 | requestAnimationFrame(function() { | |
| 216 | const slDom = document.getElementById("l" + sourceLocationIndex); | |
| 217 | if (slDom != null) slDom.scrollIntoView({ | |
| 218 | behavior: "smooth", | |
| 219 | block: "center", | |
| 220 | }); | |
| 221 | }); | |
| 222 | } | |
| 223 | ||
| 224 | function decodeString(ptr, len) { | |
| 225 | if (len === 0) return ""; | |
| 226 | return text_decoder.decode(new Uint8Array(wasm_exports.memory.buffer, ptr, len)); | |
| 227 | } | |
| 228 | ||
| 229 | function unwrapInt32Array(bigint) { | |
| 230 | const ptr = Number(bigint & 0xffffffffn); | |
| 231 | const len = Number(bigint >> 32n); | |
| 232 | if (len === 0) return new Uint32Array(); | |
| 233 | return new Uint32Array(wasm_exports.memory.buffer, ptr, len); | |
| 234 | } | |
| 235 | ||
| 236 | function setInputString(s) { | |
| 237 | const jsArray = text_encoder.encode(s); | |
| 238 | const len = jsArray.length; | |
| 239 | const ptr = wasm_exports.set_input_string(len); | |
| 240 | const wasmArray = new Uint8Array(wasm_exports.memory.buffer, ptr, len); | |
| 241 | wasmArray.set(jsArray); | |
| 242 | } | |
| 243 | ||
| 244 | function unwrapString(bigint) { | |
| 245 | const ptr = Number(bigint & 0xffffffffn); | |
| 246 | const len = Number(bigint >> 32n); | |
| 247 | return decodeString(ptr, len); | |
| 248 | } | |
| 249 | })(); |
lib/fuzzer/wasm/main.zig created+424| ... | ... | @@ -0,0 +1,424 @@ |
| 1 | const std = @import("std"); | |
| 2 | const assert = std.debug.assert; | |
| 3 | const abi = std.Build.Fuzz.abi; | |
| 4 | const gpa = std.heap.wasm_allocator; | |
| 5 | const log = std.log; | |
| 6 | const Coverage = std.debug.Coverage; | |
| 7 | const Allocator = std.mem.Allocator; | |
| 8 | ||
| 9 | const Walk = @import("Walk"); | |
| 10 | const Decl = Walk.Decl; | |
| 11 | const html_render = @import("html_render"); | |
| 12 | ||
| 13 | const js = struct { | |
| 14 | extern "js" fn log(ptr: [*]const u8, len: usize) void; | |
| 15 | extern "js" fn panic(ptr: [*]const u8, len: usize) noreturn; | |
| 16 | extern "js" fn emitSourceIndexChange() void; | |
| 17 | extern "js" fn emitCoverageUpdate() void; | |
| 18 | extern "js" fn emitEntryPointsUpdate() void; | |
| 19 | }; | |
| 20 | ||
| 21 | pub const std_options: std.Options = .{ | |
| 22 | .logFn = logFn, | |
| 23 | }; | |
| 24 | ||
| 25 | pub fn panic(msg: []const u8, st: ?*std.builtin.StackTrace, addr: ?usize) noreturn { | |
| 26 | _ = st; | |
| 27 | _ = addr; | |
| 28 | log.err("panic: {s}", .{msg}); | |
| 29 | @trap(); | |
| 30 | } | |
| 31 | ||
| 32 | fn logFn( | |
| 33 | comptime message_level: log.Level, | |
| 34 | comptime scope: @TypeOf(.enum_literal), | |
| 35 | comptime format: []const u8, | |
| 36 | args: anytype, | |
| 37 | ) void { | |
| 38 | const level_txt = comptime message_level.asText(); | |
| 39 | const prefix2 = if (scope == .default) ": " else "(" ++ @tagName(scope) ++ "): "; | |
| 40 | var buf: [500]u8 = undefined; | |
| 41 | const line = std.fmt.bufPrint(&buf, level_txt ++ prefix2 ++ format, args) catch l: { | |
| 42 | buf[buf.len - 3 ..][0..3].* = "...".*; | |
| 43 | break :l &buf; | |
| 44 | }; | |
| 45 | js.log(line.ptr, line.len); | |
| 46 | } | |
| 47 | ||
| 48 | export fn alloc(n: usize) [*]u8 { | |
| 49 | const slice = gpa.alloc(u8, n) catch @panic("OOM"); | |
| 50 | return slice.ptr; | |
| 51 | } | |
| 52 | ||
| 53 | var message_buffer: std.ArrayListAlignedUnmanaged(u8, @alignOf(u64)) = .{}; | |
| 54 | ||
| 55 | /// Resizes the message buffer to be the correct length; returns the pointer to | |
| 56 | /// the query string. | |
| 57 | export fn message_begin(len: usize) [*]u8 { | |
| 58 | message_buffer.resize(gpa, len) catch @panic("OOM"); | |
| 59 | return message_buffer.items.ptr; | |
| 60 | } | |
| 61 | ||
| 62 | export fn message_end() void { | |
| 63 | const msg_bytes = message_buffer.items; | |
| 64 | ||
| 65 | const tag: abi.ToClientTag = @enumFromInt(msg_bytes[0]); | |
| 66 | switch (tag) { | |
| 67 | .source_index => return sourceIndexMessage(msg_bytes) catch @panic("OOM"), | |
| 68 | .coverage_update => return coverageUpdateMessage(msg_bytes) catch @panic("OOM"), | |
| 69 | .entry_points => return entryPointsMessage(msg_bytes) catch @panic("OOM"), | |
| 70 | _ => unreachable, | |
| 71 | } | |
| 72 | } | |
| 73 | ||
| 74 | export fn unpack(tar_ptr: [*]u8, tar_len: usize) void { | |
| 75 | const tar_bytes = tar_ptr[0..tar_len]; | |
| 76 | log.debug("received {d} bytes of tar file", .{tar_bytes.len}); | |
| 77 | ||
| 78 | unpackInner(tar_bytes) catch |err| { | |
| 79 | fatal("unable to unpack tar: {s}", .{@errorName(err)}); | |
| 80 | }; | |
| 81 | } | |
| 82 | ||
| 83 | /// Set by `set_input_string`. | |
| 84 | var input_string: std.ArrayListUnmanaged(u8) = .{}; | |
| 85 | var string_result: std.ArrayListUnmanaged(u8) = .{}; | |
| 86 | ||
| 87 | export fn set_input_string(len: usize) [*]u8 { | |
| 88 | input_string.resize(gpa, len) catch @panic("OOM"); | |
| 89 | return input_string.items.ptr; | |
| 90 | } | |
| 91 | ||
| 92 | /// Looks up the root struct decl corresponding to a file by path. | |
| 93 | /// Uses `input_string`. | |
| 94 | export fn find_file_root() Decl.Index { | |
| 95 | const file: Walk.File.Index = @enumFromInt(Walk.files.getIndex(input_string.items) orelse return .none); | |
| 96 | return file.findRootDecl(); | |
| 97 | } | |
| 98 | ||
| 99 | export fn decl_source_html(decl_index: Decl.Index) String { | |
| 100 | const decl = decl_index.get(); | |
| 101 | ||
| 102 | string_result.clearRetainingCapacity(); | |
| 103 | html_render.fileSourceHtml(decl.file, &string_result, decl.ast_node, .{}) catch |err| { | |
| 104 | fatal("unable to render source: {s}", .{@errorName(err)}); | |
| 105 | }; | |
| 106 | return String.init(string_result.items); | |
| 107 | } | |
| 108 | ||
| 109 | export fn lowestStack() String { | |
| 110 | const header: *abi.CoverageUpdateHeader = @ptrCast(recent_coverage_update.items[0..@sizeOf(abi.CoverageUpdateHeader)]); | |
| 111 | string_result.clearRetainingCapacity(); | |
| 112 | string_result.writer(gpa).print("0x{d}", .{header.lowest_stack}) catch @panic("OOM"); | |
| 113 | return String.init(string_result.items); | |
| 114 | } | |
| 115 | ||
| 116 | export fn totalSourceLocations() usize { | |
| 117 | return coverage_source_locations.items.len; | |
| 118 | } | |
| 119 | ||
| 120 | export fn coveredSourceLocations() usize { | |
| 121 | const covered_bits = recent_coverage_update.items[@sizeOf(abi.CoverageUpdateHeader)..]; | |
| 122 | var count: usize = 0; | |
| 123 | for (covered_bits) |byte| count += @popCount(byte); | |
| 124 | return count; | |
| 125 | } | |
| 126 | ||
| 127 | export fn totalRuns() u64 { | |
| 128 | const header: *abi.CoverageUpdateHeader = @ptrCast(recent_coverage_update.items[0..@sizeOf(abi.CoverageUpdateHeader)]); | |
| 129 | return header.n_runs; | |
| 130 | } | |
| 131 | ||
| 132 | export fn uniqueRuns() u64 { | |
| 133 | const header: *abi.CoverageUpdateHeader = @ptrCast(recent_coverage_update.items[0..@sizeOf(abi.CoverageUpdateHeader)]); | |
| 134 | return header.unique_runs; | |
| 135 | } | |
| 136 | ||
| 137 | const String = Slice(u8); | |
| 138 | ||
| 139 | fn Slice(T: type) type { | |
| 140 | return packed struct(u64) { | |
| 141 | ptr: u32, | |
| 142 | len: u32, | |
| 143 | ||
| 144 | fn init(s: []const T) @This() { | |
| 145 | return .{ | |
| 146 | .ptr = @intFromPtr(s.ptr), | |
| 147 | .len = s.len, | |
| 148 | }; | |
| 149 | } | |
| 150 | }; | |
| 151 | } | |
| 152 | ||
| 153 | fn unpackInner(tar_bytes: []u8) !void { | |
| 154 | var fbs = std.io.fixedBufferStream(tar_bytes); | |
| 155 | var file_name_buffer: [1024]u8 = undefined; | |
| 156 | var link_name_buffer: [1024]u8 = undefined; | |
| 157 | var it = std.tar.iterator(fbs.reader(), .{ | |
| 158 | .file_name_buffer = &file_name_buffer, | |
| 159 | .link_name_buffer = &link_name_buffer, | |
| 160 | }); | |
| 161 | while (try it.next()) |tar_file| { | |
| 162 | switch (tar_file.kind) { | |
| 163 | .file => { | |
| 164 | if (tar_file.size == 0 and tar_file.name.len == 0) break; | |
| 165 | if (std.mem.endsWith(u8, tar_file.name, ".zig")) { | |
| 166 | log.debug("found file: '{s}'", .{tar_file.name}); | |
| 167 | const file_name = try gpa.dupe(u8, tar_file.name); | |
| 168 | if (std.mem.indexOfScalar(u8, file_name, '/')) |pkg_name_end| { | |
| 169 | const pkg_name = file_name[0..pkg_name_end]; | |
| 170 | const gop = try Walk.modules.getOrPut(gpa, pkg_name); | |
| 171 | const file: Walk.File.Index = @enumFromInt(Walk.files.entries.len); | |
| 172 | if (!gop.found_existing or | |
| 173 | std.mem.eql(u8, file_name[pkg_name_end..], "/root.zig") or | |
| 174 | std.mem.eql(u8, file_name[pkg_name_end + 1 .. file_name.len - ".zig".len], pkg_name)) | |
| 175 | { | |
| 176 | gop.value_ptr.* = file; | |
| 177 | } | |
| 178 | const file_bytes = tar_bytes[fbs.pos..][0..@intCast(tar_file.size)]; | |
| 179 | assert(file == try Walk.add_file(file_name, file_bytes)); | |
| 180 | } | |
| 181 | } else { | |
| 182 | log.warn("skipping: '{s}' - the tar creation should have done that", .{tar_file.name}); | |
| 183 | } | |
| 184 | }, | |
| 185 | else => continue, | |
| 186 | } | |
| 187 | } | |
| 188 | } | |
| 189 | ||
| 190 | fn fatal(comptime format: []const u8, args: anytype) noreturn { | |
| 191 | var buf: [500]u8 = undefined; | |
| 192 | const line = std.fmt.bufPrint(&buf, format, args) catch l: { | |
| 193 | buf[buf.len - 3 ..][0..3].* = "...".*; | |
| 194 | break :l &buf; | |
| 195 | }; | |
| 196 | js.panic(line.ptr, line.len); | |
| 197 | } | |
| 198 | ||
| 199 | fn sourceIndexMessage(msg_bytes: []u8) error{OutOfMemory}!void { | |
| 200 | const Header = abi.SourceIndexHeader; | |
| 201 | const header: Header = @bitCast(msg_bytes[0..@sizeOf(Header)].*); | |
| 202 | ||
| 203 | const directories_start = @sizeOf(Header); | |
| 204 | const directories_end = directories_start + header.directories_len * @sizeOf(Coverage.String); | |
| 205 | const files_start = directories_end; | |
| 206 | const files_end = files_start + header.files_len * @sizeOf(Coverage.File); | |
| 207 | const source_locations_start = files_end; | |
| 208 | const source_locations_end = source_locations_start + header.source_locations_len * @sizeOf(Coverage.SourceLocation); | |
| 209 | const string_bytes = msg_bytes[source_locations_end..][0..header.string_bytes_len]; | |
| 210 | ||
| 211 | const directories: []const Coverage.String = @alignCast(std.mem.bytesAsSlice(Coverage.String, msg_bytes[directories_start..directories_end])); | |
| 212 | const files: []const Coverage.File = @alignCast(std.mem.bytesAsSlice(Coverage.File, msg_bytes[files_start..files_end])); | |
| 213 | const source_locations: []const Coverage.SourceLocation = @alignCast(std.mem.bytesAsSlice(Coverage.SourceLocation, msg_bytes[source_locations_start..source_locations_end])); | |
| 214 | ||
| 215 | try updateCoverage(directories, files, source_locations, string_bytes); | |
| 216 | js.emitSourceIndexChange(); | |
| 217 | } | |
| 218 | ||
| 219 | fn coverageUpdateMessage(msg_bytes: []u8) error{OutOfMemory}!void { | |
| 220 | recent_coverage_update.clearRetainingCapacity(); | |
| 221 | recent_coverage_update.appendSlice(gpa, msg_bytes) catch @panic("OOM"); | |
| 222 | js.emitCoverageUpdate(); | |
| 223 | } | |
| 224 | ||
| 225 | var entry_points: std.ArrayListUnmanaged(u32) = .{}; | |
| 226 | ||
| 227 | fn entryPointsMessage(msg_bytes: []u8) error{OutOfMemory}!void { | |
| 228 | const header: abi.EntryPointHeader = @bitCast(msg_bytes[0..@sizeOf(abi.EntryPointHeader)].*); | |
| 229 | entry_points.resize(gpa, header.flags.locs_len) catch @panic("OOM"); | |
| 230 | @memcpy(entry_points.items, std.mem.bytesAsSlice(u32, msg_bytes[@sizeOf(abi.EntryPointHeader)..])); | |
| 231 | js.emitEntryPointsUpdate(); | |
| 232 | } | |
| 233 | ||
| 234 | export fn entryPoints() Slice(u32) { | |
| 235 | return Slice(u32).init(entry_points.items); | |
| 236 | } | |
| 237 | ||
| 238 | /// Index into `coverage_source_locations`. | |
| 239 | const SourceLocationIndex = enum(u32) { | |
| 240 | _, | |
| 241 | ||
| 242 | fn haveCoverage(sli: SourceLocationIndex) bool { | |
| 243 | return @intFromEnum(sli) < coverage_source_locations.items.len; | |
| 244 | } | |
| 245 | ||
| 246 | fn ptr(sli: SourceLocationIndex) *Coverage.SourceLocation { | |
| 247 | return &coverage_source_locations.items[@intFromEnum(sli)]; | |
| 248 | } | |
| 249 | ||
| 250 | fn sourceLocationLinkHtml( | |
| 251 | sli: SourceLocationIndex, | |
| 252 | out: *std.ArrayListUnmanaged(u8), | |
| 253 | ) Allocator.Error!void { | |
| 254 | const sl = sli.ptr(); | |
| 255 | try out.writer(gpa).print("<a href=\"#l{d}\">", .{@intFromEnum(sli)}); | |
| 256 | try sli.appendPath(out); | |
| 257 | try out.writer(gpa).print(":{d}:{d}</a>", .{ sl.line, sl.column }); | |
| 258 | } | |
| 259 | ||
| 260 | fn appendPath(sli: SourceLocationIndex, out: *std.ArrayListUnmanaged(u8)) Allocator.Error!void { | |
| 261 | const sl = sli.ptr(); | |
| 262 | const file = coverage.fileAt(sl.file); | |
| 263 | const file_name = coverage.stringAt(file.basename); | |
| 264 | const dir_name = coverage.stringAt(coverage.directories.keys()[file.directory_index]); | |
| 265 | try html_render.appendEscaped(out, dir_name); | |
| 266 | try out.appendSlice(gpa, "/"); | |
| 267 | try html_render.appendEscaped(out, file_name); | |
| 268 | } | |
| 269 | ||
| 270 | fn toWalkFile(sli: SourceLocationIndex) ?Walk.File.Index { | |
| 271 | var buf: std.ArrayListUnmanaged(u8) = .{}; | |
| 272 | defer buf.deinit(gpa); | |
| 273 | sli.appendPath(&buf) catch @panic("OOM"); | |
| 274 | return @enumFromInt(Walk.files.getIndex(buf.items) orelse return null); | |
| 275 | } | |
| 276 | ||
| 277 | fn fileHtml( | |
| 278 | sli: SourceLocationIndex, | |
| 279 | out: *std.ArrayListUnmanaged(u8), | |
| 280 | ) error{ OutOfMemory, SourceUnavailable }!void { | |
| 281 | const walk_file_index = sli.toWalkFile() orelse return error.SourceUnavailable; | |
| 282 | const root_node = walk_file_index.findRootDecl().get().ast_node; | |
| 283 | var annotations: std.ArrayListUnmanaged(html_render.Annotation) = .{}; | |
| 284 | defer annotations.deinit(gpa); | |
| 285 | try computeSourceAnnotations(sli.ptr().file, walk_file_index, &annotations, coverage_source_locations.items); | |
| 286 | html_render.fileSourceHtml(walk_file_index, out, root_node, .{ | |
| 287 | .source_location_annotations = annotations.items, | |
| 288 | }) catch |err| { | |
| 289 | fatal("unable to render source: {s}", .{@errorName(err)}); | |
| 290 | }; | |
| 291 | } | |
| 292 | }; | |
| 293 | ||
| 294 | fn computeSourceAnnotations( | |
| 295 | cov_file_index: Coverage.File.Index, | |
| 296 | walk_file_index: Walk.File.Index, | |
| 297 | annotations: *std.ArrayListUnmanaged(html_render.Annotation), | |
| 298 | source_locations: []const Coverage.SourceLocation, | |
| 299 | ) !void { | |
| 300 | // Collect all the source locations from only this file into this array | |
| 301 | // first, then sort by line, col, so that we can collect annotations with | |
| 302 | // O(N) time complexity. | |
| 303 | var locs: std.ArrayListUnmanaged(SourceLocationIndex) = .{}; | |
| 304 | defer locs.deinit(gpa); | |
| 305 | ||
| 306 | for (source_locations, 0..) |sl, sli_usize| { | |
| 307 | if (sl.file != cov_file_index) continue; | |
| 308 | const sli: SourceLocationIndex = @enumFromInt(sli_usize); | |
| 309 | try locs.append(gpa, sli); | |
| 310 | } | |
| 311 | ||
| 312 | std.mem.sortUnstable(SourceLocationIndex, locs.items, {}, struct { | |
| 313 | pub fn lessThan(context: void, lhs: SourceLocationIndex, rhs: SourceLocationIndex) bool { | |
| 314 | _ = context; | |
| 315 | const lhs_ptr = lhs.ptr(); | |
| 316 | const rhs_ptr = rhs.ptr(); | |
| 317 | if (lhs_ptr.line < rhs_ptr.line) return true; | |
| 318 | if (lhs_ptr.line > rhs_ptr.line) return false; | |
| 319 | return lhs_ptr.column < rhs_ptr.column; | |
| 320 | } | |
| 321 | }.lessThan); | |
| 322 | ||
| 323 | const source = walk_file_index.get_ast().source; | |
| 324 | var line: usize = 1; | |
| 325 | var column: usize = 1; | |
| 326 | var next_loc_index: usize = 0; | |
| 327 | for (source, 0..) |byte, offset| { | |
| 328 | if (byte == '\n') { | |
| 329 | line += 1; | |
| 330 | column = 1; | |
| 331 | } else { | |
| 332 | column += 1; | |
| 333 | } | |
| 334 | while (true) { | |
| 335 | if (next_loc_index >= locs.items.len) return; | |
| 336 | const next_sli = locs.items[next_loc_index]; | |
| 337 | const next_sl = next_sli.ptr(); | |
| 338 | if (next_sl.line > line or (next_sl.line == line and next_sl.column > column)) break; | |
| 339 | try annotations.append(gpa, .{ | |
| 340 | .file_byte_offset = offset, | |
| 341 | .dom_id = @intFromEnum(next_sli), | |
| 342 | }); | |
| 343 | next_loc_index += 1; | |
| 344 | } | |
| 345 | } | |
| 346 | } | |
| 347 | ||
| 348 | var coverage = Coverage.init; | |
| 349 | /// Index of type `SourceLocationIndex`. | |
| 350 | var coverage_source_locations: std.ArrayListUnmanaged(Coverage.SourceLocation) = .{}; | |
| 351 | /// Contains the most recent coverage update message, unmodified. | |
| 352 | var recent_coverage_update: std.ArrayListUnmanaged(u8) = .{}; | |
| 353 | ||
| 354 | fn updateCoverage( | |
| 355 | directories: []const Coverage.String, | |
| 356 | files: []const Coverage.File, | |
| 357 | source_locations: []const Coverage.SourceLocation, | |
| 358 | string_bytes: []const u8, | |
| 359 | ) !void { | |
| 360 | coverage.directories.clearRetainingCapacity(); | |
| 361 | coverage.files.clearRetainingCapacity(); | |
| 362 | coverage.string_bytes.clearRetainingCapacity(); | |
| 363 | coverage_source_locations.clearRetainingCapacity(); | |
| 364 | ||
| 365 | try coverage_source_locations.appendSlice(gpa, source_locations); | |
| 366 | try coverage.string_bytes.appendSlice(gpa, string_bytes); | |
| 367 | ||
| 368 | try coverage.files.entries.resize(gpa, files.len); | |
| 369 | @memcpy(coverage.files.entries.items(.key), files); | |
| 370 | try coverage.files.reIndexContext(gpa, .{ .string_bytes = coverage.string_bytes.items }); | |
| 371 | ||
| 372 | try coverage.directories.entries.resize(gpa, directories.len); | |
| 373 | @memcpy(coverage.directories.entries.items(.key), directories); | |
| 374 | try coverage.directories.reIndexContext(gpa, .{ .string_bytes = coverage.string_bytes.items }); | |
| 375 | } | |
| 376 | ||
| 377 | export fn sourceLocationLinkHtml(index: SourceLocationIndex) String { | |
| 378 | string_result.clearRetainingCapacity(); | |
| 379 | index.sourceLocationLinkHtml(&string_result) catch @panic("OOM"); | |
| 380 | return String.init(string_result.items); | |
| 381 | } | |
| 382 | ||
| 383 | /// Returns empty string if coverage metadata is not available for this source location. | |
| 384 | export fn sourceLocationPath(sli: SourceLocationIndex) String { | |
| 385 | string_result.clearRetainingCapacity(); | |
| 386 | if (sli.haveCoverage()) sli.appendPath(&string_result) catch @panic("OOM"); | |
| 387 | return String.init(string_result.items); | |
| 388 | } | |
| 389 | ||
| 390 | export fn sourceLocationFileHtml(sli: SourceLocationIndex) String { | |
| 391 | string_result.clearRetainingCapacity(); | |
| 392 | sli.fileHtml(&string_result) catch |err| switch (err) { | |
| 393 | error.OutOfMemory => @panic("OOM"), | |
| 394 | error.SourceUnavailable => {}, | |
| 395 | }; | |
| 396 | return String.init(string_result.items); | |
| 397 | } | |
| 398 | ||
| 399 | export fn sourceLocationFileCoveredList(sli_file: SourceLocationIndex) Slice(SourceLocationIndex) { | |
| 400 | const global = struct { | |
| 401 | var result: std.ArrayListUnmanaged(SourceLocationIndex) = .{}; | |
| 402 | fn add(i: u32, want_file: Coverage.File.Index) void { | |
| 403 | const src_loc_index: SourceLocationIndex = @enumFromInt(i); | |
| 404 | if (src_loc_index.ptr().file == want_file) result.appendAssumeCapacity(src_loc_index); | |
| 405 | } | |
| 406 | }; | |
| 407 | const want_file = sli_file.ptr().file; | |
| 408 | global.result.clearRetainingCapacity(); | |
| 409 | const covered_bits = recent_coverage_update.items[@sizeOf(abi.CoverageUpdateHeader)..]; | |
| 410 | var sli: u32 = 0; | |
| 411 | for (covered_bits) |byte| { | |
| 412 | global.result.ensureUnusedCapacity(gpa, 8) catch @panic("OOM"); | |
| 413 | if ((byte & 0b0000_0001) != 0) global.add(sli + 0, want_file); | |
| 414 | if ((byte & 0b0000_0010) != 0) global.add(sli + 1, want_file); | |
| 415 | if ((byte & 0b0000_0100) != 0) global.add(sli + 2, want_file); | |
| 416 | if ((byte & 0b0000_1000) != 0) global.add(sli + 3, want_file); | |
| 417 | if ((byte & 0b0001_0000) != 0) global.add(sli + 4, want_file); | |
| 418 | if ((byte & 0b0010_0000) != 0) global.add(sli + 5, want_file); | |
| 419 | if ((byte & 0b0100_0000) != 0) global.add(sli + 6, want_file); | |
| 420 | if ((byte & 0b1000_0000) != 0) global.add(sli + 7, want_file); | |
| 421 | sli += 8; | |
| 422 | } | |
| 423 | return Slice(SourceLocationIndex).init(global.result.items); | |
| 424 | } |
lib/std/Build.zig+8-4| ... | ... | @@ -2300,22 +2300,26 @@ pub const LazyPath = union(enum) { |
| 2300 | 2300 | } |
| 2301 | 2301 | |
| 2302 | 2302 | pub fn path(lazy_path: LazyPath, b: *Build, sub_path: []const u8) LazyPath { |
| 2303 | return lazy_path.join(b.allocator, sub_path) catch @panic("OOM"); | |
| 2304 | } | |
| 2305 | ||
| 2306 | pub fn join(lazy_path: LazyPath, arena: Allocator, sub_path: []const u8) Allocator.Error!LazyPath { | |
| 2303 | 2307 | return switch (lazy_path) { |
| 2304 | 2308 | .src_path => |src| .{ .src_path = .{ |
| 2305 | 2309 | .owner = src.owner, |
| 2306 | .sub_path = b.pathResolve(&.{ src.sub_path, sub_path }), | |
| 2310 | .sub_path = try fs.path.resolve(arena, &.{ src.sub_path, sub_path }), | |
| 2307 | 2311 | } }, |
| 2308 | 2312 | .generated => |gen| .{ .generated = .{ |
| 2309 | 2313 | .file = gen.file, |
| 2310 | 2314 | .up = gen.up, |
| 2311 | .sub_path = b.pathResolve(&.{ gen.sub_path, sub_path }), | |
| 2315 | .sub_path = try fs.path.resolve(arena, &.{ gen.sub_path, sub_path }), | |
| 2312 | 2316 | } }, |
| 2313 | 2317 | .cwd_relative => |cwd_relative| .{ |
| 2314 | .cwd_relative = b.pathResolve(&.{ cwd_relative, sub_path }), | |
| 2318 | .cwd_relative = try fs.path.resolve(arena, &.{ cwd_relative, sub_path }), | |
| 2315 | 2319 | }, |
| 2316 | 2320 | .dependency => |dep| .{ .dependency = .{ |
| 2317 | 2321 | .dependency = dep.dependency, |
| 2318 | .sub_path = b.pathResolve(&.{ dep.sub_path, sub_path }), | |
| 2322 | .sub_path = try fs.path.resolve(arena, &.{ dep.sub_path, sub_path }), | |
| 2319 | 2323 | } }, |
| 2320 | 2324 | }; |
| 2321 | 2325 | } |
lib/std/Build/Cache/Path.zig+4-4| ... | ... | @@ -32,16 +32,16 @@ pub fn resolvePosix(p: Path, arena: Allocator, sub_path: []const u8) Allocator.E |
| 32 | 32 | }; |
| 33 | 33 | } |
| 34 | 34 | |
| 35 | pub fn joinString(p: Path, allocator: Allocator, sub_path: []const u8) Allocator.Error![]u8 { | |
| 35 | pub fn joinString(p: Path, gpa: Allocator, sub_path: []const u8) Allocator.Error![]u8 { | |
| 36 | 36 | const parts: []const []const u8 = |
| 37 | 37 | if (p.sub_path.len == 0) &.{sub_path} else &.{ p.sub_path, sub_path }; |
| 38 | return p.root_dir.join(allocator, parts); | |
| 38 | return p.root_dir.join(gpa, parts); | |
| 39 | 39 | } |
| 40 | 40 | |
| 41 | pub fn joinStringZ(p: Path, allocator: Allocator, sub_path: []const u8) Allocator.Error![:0]u8 { | |
| 41 | pub fn joinStringZ(p: Path, gpa: Allocator, sub_path: []const u8) Allocator.Error![:0]u8 { | |
| 42 | 42 | const parts: []const []const u8 = |
| 43 | 43 | if (p.sub_path.len == 0) &.{sub_path} else &.{ p.sub_path, sub_path }; |
| 44 | return p.root_dir.joinZ(allocator, parts); | |
| 44 | return p.root_dir.joinZ(gpa, parts); | |
| 45 | 45 | } |
| 46 | 46 | |
| 47 | 47 | pub fn openFile( |
lib/std/Build/Fuzz.zig+72-24| ... | ... | @@ -1,57 +1,102 @@ |
| 1 | const builtin = @import("builtin"); | |
| 1 | 2 | const std = @import("../std.zig"); |
| 2 | const Fuzz = @This(); | |
| 3 | const Build = std.Build; | |
| 3 | 4 | const Step = std.Build.Step; |
| 4 | 5 | const assert = std.debug.assert; |
| 5 | 6 | const fatal = std.process.fatal; |
| 7 | const Allocator = std.mem.Allocator; | |
| 8 | const log = std.log; | |
| 9 | ||
| 10 | const Fuzz = @This(); | |
| 6 | 11 | const build_runner = @import("root"); |
| 7 | 12 | |
| 13 | pub const WebServer = @import("Fuzz/WebServer.zig"); | |
| 14 | pub const abi = @import("Fuzz/abi.zig"); | |
| 15 | ||
| 8 | 16 | pub fn start( |
| 17 | gpa: Allocator, | |
| 18 | arena: Allocator, | |
| 19 | global_cache_directory: Build.Cache.Directory, | |
| 20 | zig_lib_directory: Build.Cache.Directory, | |
| 21 | zig_exe_path: []const u8, | |
| 9 | 22 | thread_pool: *std.Thread.Pool, |
| 10 | 23 | all_steps: []const *Step, |
| 11 | 24 | ttyconf: std.io.tty.Config, |
| 25 | listen_address: std.net.Address, | |
| 12 | 26 | prog_node: std.Progress.Node, |
| 13 | ) void { | |
| 14 | const count = block: { | |
| 27 | ) Allocator.Error!void { | |
| 28 | const fuzz_run_steps = block: { | |
| 15 | 29 | const rebuild_node = prog_node.start("Rebuilding Unit Tests", 0); |
| 16 | 30 | defer rebuild_node.end(); |
| 17 | var count: usize = 0; | |
| 18 | 31 | var wait_group: std.Thread.WaitGroup = .{}; |
| 19 | 32 | defer wait_group.wait(); |
| 33 | var fuzz_run_steps: std.ArrayListUnmanaged(*Step.Run) = .{}; | |
| 34 | defer fuzz_run_steps.deinit(gpa); | |
| 20 | 35 | for (all_steps) |step| { |
| 21 | 36 | const run = step.cast(Step.Run) orelse continue; |
| 22 | 37 | if (run.fuzz_tests.items.len > 0 and run.producer != null) { |
| 23 | 38 | thread_pool.spawnWg(&wait_group, rebuildTestsWorkerRun, .{ run, ttyconf, rebuild_node }); |
| 24 | count += 1; | |
| 39 | try fuzz_run_steps.append(gpa, run); | |
| 25 | 40 | } |
| 26 | 41 | } |
| 27 | if (count == 0) fatal("no fuzz tests found", .{}); | |
| 28 | rebuild_node.setEstimatedTotalItems(count); | |
| 29 | break :block count; | |
| 42 | if (fuzz_run_steps.items.len == 0) fatal("no fuzz tests found", .{}); | |
| 43 | rebuild_node.setEstimatedTotalItems(fuzz_run_steps.items.len); | |
| 44 | break :block try arena.dupe(*Step.Run, fuzz_run_steps.items); | |
| 30 | 45 | }; |
| 31 | 46 | |
| 32 | 47 | // Detect failure. |
| 33 | for (all_steps) |step| { | |
| 34 | const run = step.cast(Step.Run) orelse continue; | |
| 35 | if (run.fuzz_tests.items.len > 0 and run.rebuilt_executable == null) | |
| 48 | for (fuzz_run_steps) |run| { | |
| 49 | assert(run.fuzz_tests.items.len > 0); | |
| 50 | if (run.rebuilt_executable == null) | |
| 36 | 51 | fatal("one or more unit tests failed to be rebuilt in fuzz mode", .{}); |
| 37 | 52 | } |
| 38 | 53 | |
| 54 | var web_server: WebServer = .{ | |
| 55 | .gpa = gpa, | |
| 56 | .global_cache_directory = global_cache_directory, | |
| 57 | .zig_lib_directory = zig_lib_directory, | |
| 58 | .zig_exe_path = zig_exe_path, | |
| 59 | .listen_address = listen_address, | |
| 60 | .fuzz_run_steps = fuzz_run_steps, | |
| 61 | ||
| 62 | .msg_queue = .{}, | |
| 63 | .mutex = .{}, | |
| 64 | .condition = .{}, | |
| 65 | ||
| 66 | .coverage_files = .{}, | |
| 67 | .coverage_mutex = .{}, | |
| 68 | .coverage_condition = .{}, | |
| 69 | }; | |
| 70 | ||
| 71 | // For accepting HTTP connections. | |
| 72 | const web_server_thread = std.Thread.spawn(.{}, WebServer.run, .{&web_server}) catch |err| { | |
| 73 | fatal("unable to spawn web server thread: {s}", .{@errorName(err)}); | |
| 74 | }; | |
| 75 | defer web_server_thread.join(); | |
| 76 | ||
| 77 | // For polling messages and sending updates to subscribers. | |
| 78 | const coverage_thread = std.Thread.spawn(.{}, WebServer.coverageRun, .{&web_server}) catch |err| { | |
| 79 | fatal("unable to spawn coverage thread: {s}", .{@errorName(err)}); | |
| 80 | }; | |
| 81 | defer coverage_thread.join(); | |
| 82 | ||
| 39 | 83 | { |
| 40 | const fuzz_node = prog_node.start("Fuzzing", count); | |
| 84 | const fuzz_node = prog_node.start("Fuzzing", fuzz_run_steps.len); | |
| 41 | 85 | defer fuzz_node.end(); |
| 42 | 86 | var wait_group: std.Thread.WaitGroup = .{}; |
| 43 | 87 | defer wait_group.wait(); |
| 44 | 88 | |
| 45 | for (all_steps) |step| { | |
| 46 | const run = step.cast(Step.Run) orelse continue; | |
| 89 | for (fuzz_run_steps) |run| { | |
| 47 | 90 | for (run.fuzz_tests.items) |unit_test_index| { |
| 48 | 91 | assert(run.rebuilt_executable != null); |
| 49 | thread_pool.spawnWg(&wait_group, fuzzWorkerRun, .{ run, unit_test_index, ttyconf, fuzz_node }); | |
| 92 | thread_pool.spawnWg(&wait_group, fuzzWorkerRun, .{ | |
| 93 | run, &web_server, unit_test_index, ttyconf, fuzz_node, | |
| 94 | }); | |
| 50 | 95 | } |
| 51 | 96 | } |
| 52 | 97 | } |
| 53 | 98 | |
| 54 | fatal("all fuzz workers crashed", .{}); | |
| 99 | log.err("all fuzz workers crashed", .{}); | |
| 55 | 100 | } |
| 56 | 101 | |
| 57 | 102 | fn rebuildTestsWorkerRun(run: *Step.Run, ttyconf: std.io.tty.Config, parent_prog_node: std.Progress.Node) void { |
| ... | ... | @@ -74,20 +119,21 @@ fn rebuildTestsWorkerRun(run: *Step.Run, ttyconf: std.io.tty.Config, parent_prog |
| 74 | 119 | build_runner.printErrorMessages(gpa, &compile.step, ttyconf, stderr, false) catch {}; |
| 75 | 120 | } |
| 76 | 121 | |
| 77 | if (result) |rebuilt_bin_path| { | |
| 78 | run.rebuilt_executable = rebuilt_bin_path; | |
| 79 | } else |err| switch (err) { | |
| 80 | error.MakeFailed => {}, | |
| 122 | const rebuilt_bin_path = result catch |err| switch (err) { | |
| 123 | error.MakeFailed => return, | |
| 81 | 124 | else => { |
| 82 | std.debug.print("step '{s}': failed to rebuild in fuzz mode: {s}\n", .{ | |
| 125 | log.err("step '{s}': failed to rebuild in fuzz mode: {s}", .{ | |
| 83 | 126 | compile.step.name, @errorName(err), |
| 84 | 127 | }); |
| 128 | return; | |
| 85 | 129 | }, |
| 86 | } | |
| 130 | }; | |
| 131 | run.rebuilt_executable = rebuilt_bin_path; | |
| 87 | 132 | } |
| 88 | 133 | |
| 89 | 134 | fn fuzzWorkerRun( |
| 90 | 135 | run: *Step.Run, |
| 136 | web_server: *WebServer, | |
| 91 | 137 | unit_test_index: u32, |
| 92 | 138 | ttyconf: std.io.tty.Config, |
| 93 | 139 | parent_prog_node: std.Progress.Node, |
| ... | ... | @@ -98,17 +144,19 @@ fn fuzzWorkerRun( |
| 98 | 144 | const prog_node = parent_prog_node.start(test_name, 0); |
| 99 | 145 | defer prog_node.end(); |
| 100 | 146 | |
| 101 | run.rerunInFuzzMode(unit_test_index, prog_node) catch |err| switch (err) { | |
| 147 | run.rerunInFuzzMode(web_server, unit_test_index, prog_node) catch |err| switch (err) { | |
| 102 | 148 | error.MakeFailed => { |
| 103 | 149 | const stderr = std.io.getStdErr(); |
| 104 | 150 | std.debug.lockStdErr(); |
| 105 | 151 | defer std.debug.unlockStdErr(); |
| 106 | 152 | build_runner.printErrorMessages(gpa, &run.step, ttyconf, stderr, false) catch {}; |
| 153 | return; | |
| 107 | 154 | }, |
| 108 | 155 | else => { |
| 109 | std.debug.print("step '{s}': failed to rebuild '{s}' in fuzz mode: {s}\n", .{ | |
| 156 | log.err("step '{s}': failed to rerun '{s}' in fuzz mode: {s}", .{ | |
| 110 | 157 | run.step.name, test_name, @errorName(err), |
| 111 | 158 | }); |
| 159 | return; | |
| 112 | 160 | }, |
| 113 | 161 | }; |
| 114 | 162 | } |
lib/std/Build/Fuzz/WebServer.zig created+679| ... | ... | @@ -0,0 +1,679 @@ |
| 1 | const builtin = @import("builtin"); | |
| 2 | ||
| 3 | const std = @import("../../std.zig"); | |
| 4 | const Allocator = std.mem.Allocator; | |
| 5 | const Build = std.Build; | |
| 6 | const Step = std.Build.Step; | |
| 7 | const Coverage = std.debug.Coverage; | |
| 8 | const abi = std.Build.Fuzz.abi; | |
| 9 | const log = std.log; | |
| 10 | ||
| 11 | const WebServer = @This(); | |
| 12 | ||
| 13 | gpa: Allocator, | |
| 14 | global_cache_directory: Build.Cache.Directory, | |
| 15 | zig_lib_directory: Build.Cache.Directory, | |
| 16 | zig_exe_path: []const u8, | |
| 17 | listen_address: std.net.Address, | |
| 18 | fuzz_run_steps: []const *Step.Run, | |
| 19 | ||
| 20 | /// Messages from fuzz workers. Protected by mutex. | |
| 21 | msg_queue: std.ArrayListUnmanaged(Msg), | |
| 22 | /// Protects `msg_queue` only. | |
| 23 | mutex: std.Thread.Mutex, | |
| 24 | /// Signaled when there is a message in `msg_queue`. | |
| 25 | condition: std.Thread.Condition, | |
| 26 | ||
| 27 | coverage_files: std.AutoArrayHashMapUnmanaged(u64, CoverageMap), | |
| 28 | /// Protects `coverage_files` only. | |
| 29 | coverage_mutex: std.Thread.Mutex, | |
| 30 | /// Signaled when `coverage_files` changes. | |
| 31 | coverage_condition: std.Thread.Condition, | |
| 32 | ||
| 33 | const CoverageMap = struct { | |
| 34 | mapped_memory: []align(std.mem.page_size) const u8, | |
| 35 | coverage: Coverage, | |
| 36 | source_locations: []Coverage.SourceLocation, | |
| 37 | /// Elements are indexes into `source_locations` pointing to the unit tests that are being fuzz tested. | |
| 38 | entry_points: std.ArrayListUnmanaged(u32), | |
| 39 | ||
| 40 | fn deinit(cm: *CoverageMap, gpa: Allocator) void { | |
| 41 | std.posix.munmap(cm.mapped_memory); | |
| 42 | cm.coverage.deinit(gpa); | |
| 43 | cm.* = undefined; | |
| 44 | } | |
| 45 | }; | |
| 46 | ||
| 47 | const Msg = union(enum) { | |
| 48 | coverage: struct { | |
| 49 | id: u64, | |
| 50 | run: *Step.Run, | |
| 51 | }, | |
| 52 | entry_point: struct { | |
| 53 | coverage_id: u64, | |
| 54 | addr: u64, | |
| 55 | }, | |
| 56 | }; | |
| 57 | ||
| 58 | pub fn run(ws: *WebServer) void { | |
| 59 | var http_server = ws.listen_address.listen(.{ | |
| 60 | .reuse_address = true, | |
| 61 | }) catch |err| { | |
| 62 | log.err("failed to listen to port {d}: {s}", .{ ws.listen_address.in.getPort(), @errorName(err) }); | |
| 63 | return; | |
| 64 | }; | |
| 65 | const port = http_server.listen_address.in.getPort(); | |
| 66 | log.info("web interface listening at http://127.0.0.1:{d}/", .{port}); | |
| 67 | ||
| 68 | while (true) { | |
| 69 | const connection = http_server.accept() catch |err| { | |
| 70 | log.err("failed to accept connection: {s}", .{@errorName(err)}); | |
| 71 | return; | |
| 72 | }; | |
| 73 | _ = std.Thread.spawn(.{}, accept, .{ ws, connection }) catch |err| { | |
| 74 | log.err("unable to spawn connection thread: {s}", .{@errorName(err)}); | |
| 75 | connection.stream.close(); | |
| 76 | continue; | |
| 77 | }; | |
| 78 | } | |
| 79 | } | |
| 80 | ||
| 81 | fn accept(ws: *WebServer, connection: std.net.Server.Connection) void { | |
| 82 | defer connection.stream.close(); | |
| 83 | ||
| 84 | var read_buffer: [0x4000]u8 = undefined; | |
| 85 | var server = std.http.Server.init(connection, &read_buffer); | |
| 86 | var web_socket: std.http.WebSocket = undefined; | |
| 87 | var send_buffer: [0x4000]u8 = undefined; | |
| 88 | var ws_recv_buffer: [0x4000]u8 align(4) = undefined; | |
| 89 | while (server.state == .ready) { | |
| 90 | var request = server.receiveHead() catch |err| switch (err) { | |
| 91 | error.HttpConnectionClosing => return, | |
| 92 | else => { | |
| 93 | log.err("closing http connection: {s}", .{@errorName(err)}); | |
| 94 | return; | |
| 95 | }, | |
| 96 | }; | |
| 97 | if (web_socket.init(&request, &send_buffer, &ws_recv_buffer) catch |err| { | |
| 98 | log.err("initializing web socket: {s}", .{@errorName(err)}); | |
| 99 | return; | |
| 100 | }) { | |
| 101 | serveWebSocket(ws, &web_socket) catch |err| { | |
| 102 | log.err("unable to serve web socket connection: {s}", .{@errorName(err)}); | |
| 103 | return; | |
| 104 | }; | |
| 105 | } else { | |
| 106 | serveRequest(ws, &request) catch |err| switch (err) { | |
| 107 | error.AlreadyReported => return, | |
| 108 | else => |e| { | |
| 109 | log.err("unable to serve {s}: {s}", .{ request.head.target, @errorName(e) }); | |
| 110 | return; | |
| 111 | }, | |
| 112 | }; | |
| 113 | } | |
| 114 | } | |
| 115 | } | |
| 116 | ||
| 117 | fn serveRequest(ws: *WebServer, request: *std.http.Server.Request) !void { | |
| 118 | if (std.mem.eql(u8, request.head.target, "/") or | |
| 119 | std.mem.eql(u8, request.head.target, "/debug") or | |
| 120 | std.mem.eql(u8, request.head.target, "/debug/")) | |
| 121 | { | |
| 122 | try serveFile(ws, request, "fuzzer/index.html", "text/html"); | |
| 123 | } else if (std.mem.eql(u8, request.head.target, "/main.js") or | |
| 124 | std.mem.eql(u8, request.head.target, "/debug/main.js")) | |
| 125 | { | |
| 126 | try serveFile(ws, request, "fuzzer/main.js", "application/javascript"); | |
| 127 | } else if (std.mem.eql(u8, request.head.target, "/main.wasm")) { | |
| 128 | try serveWasm(ws, request, .ReleaseFast); | |
| 129 | } else if (std.mem.eql(u8, request.head.target, "/debug/main.wasm")) { | |
| 130 | try serveWasm(ws, request, .Debug); | |
| 131 | } else if (std.mem.eql(u8, request.head.target, "/sources.tar") or | |
| 132 | std.mem.eql(u8, request.head.target, "/debug/sources.tar")) | |
| 133 | { | |
| 134 | try serveSourcesTar(ws, request); | |
| 135 | } else { | |
| 136 | try request.respond("not found", .{ | |
| 137 | .status = .not_found, | |
| 138 | .extra_headers = &.{ | |
| 139 | .{ .name = "content-type", .value = "text/plain" }, | |
| 140 | }, | |
| 141 | }); | |
| 142 | } | |
| 143 | } | |
| 144 | ||
| 145 | fn serveFile( | |
| 146 | ws: *WebServer, | |
| 147 | request: *std.http.Server.Request, | |
| 148 | name: []const u8, | |
| 149 | content_type: []const u8, | |
| 150 | ) !void { | |
| 151 | const gpa = ws.gpa; | |
| 152 | // The desired API is actually sendfile, which will require enhancing std.http.Server. | |
| 153 | // We load the file with every request so that the user can make changes to the file | |
| 154 | // and refresh the HTML page without restarting this server. | |
| 155 | const file_contents = ws.zig_lib_directory.handle.readFileAlloc(gpa, name, 10 * 1024 * 1024) catch |err| { | |
| 156 | log.err("failed to read '{}{s}': {s}", .{ ws.zig_lib_directory, name, @errorName(err) }); | |
| 157 | return error.AlreadyReported; | |
| 158 | }; | |
| 159 | defer gpa.free(file_contents); | |
| 160 | try request.respond(file_contents, .{ | |
| 161 | .extra_headers = &.{ | |
| 162 | .{ .name = "content-type", .value = content_type }, | |
| 163 | cache_control_header, | |
| 164 | }, | |
| 165 | }); | |
| 166 | } | |
| 167 | ||
| 168 | fn serveWasm( | |
| 169 | ws: *WebServer, | |
| 170 | request: *std.http.Server.Request, | |
| 171 | optimize_mode: std.builtin.OptimizeMode, | |
| 172 | ) !void { | |
| 173 | const gpa = ws.gpa; | |
| 174 | ||
| 175 | var arena_instance = std.heap.ArenaAllocator.init(gpa); | |
| 176 | defer arena_instance.deinit(); | |
| 177 | const arena = arena_instance.allocator(); | |
| 178 | ||
| 179 | // Do the compilation every request, so that the user can edit the files | |
| 180 | // and see the changes without restarting the server. | |
| 181 | const wasm_binary_path = try buildWasmBinary(ws, arena, optimize_mode); | |
| 182 | // std.http.Server does not have a sendfile API yet. | |
| 183 | const file_contents = try std.fs.cwd().readFileAlloc(gpa, wasm_binary_path, 10 * 1024 * 1024); | |
| 184 | defer gpa.free(file_contents); | |
| 185 | try request.respond(file_contents, .{ | |
| 186 | .extra_headers = &.{ | |
| 187 | .{ .name = "content-type", .value = "application/wasm" }, | |
| 188 | cache_control_header, | |
| 189 | }, | |
| 190 | }); | |
| 191 | } | |
| 192 | ||
| 193 | fn buildWasmBinary( | |
| 194 | ws: *WebServer, | |
| 195 | arena: Allocator, | |
| 196 | optimize_mode: std.builtin.OptimizeMode, | |
| 197 | ) ![]const u8 { | |
| 198 | const gpa = ws.gpa; | |
| 199 | ||
| 200 | const main_src_path: Build.Cache.Path = .{ | |
| 201 | .root_dir = ws.zig_lib_directory, | |
| 202 | .sub_path = "fuzzer/wasm/main.zig", | |
| 203 | }; | |
| 204 | const walk_src_path: Build.Cache.Path = .{ | |
| 205 | .root_dir = ws.zig_lib_directory, | |
| 206 | .sub_path = "docs/wasm/Walk.zig", | |
| 207 | }; | |
| 208 | const html_render_src_path: Build.Cache.Path = .{ | |
| 209 | .root_dir = ws.zig_lib_directory, | |
| 210 | .sub_path = "docs/wasm/html_render.zig", | |
| 211 | }; | |
| 212 | ||
| 213 | var argv: std.ArrayListUnmanaged([]const u8) = .{}; | |
| 214 | ||
| 215 | try argv.appendSlice(arena, &.{ | |
| 216 | ws.zig_exe_path, "build-exe", // | |
| 217 | "-fno-entry", // | |
| 218 | "-O", @tagName(optimize_mode), // | |
| 219 | "-target", "wasm32-freestanding", // | |
| 220 | "-mcpu", "baseline+atomics+bulk_memory+multivalue+mutable_globals+nontrapping_fptoint+reference_types+sign_ext", // | |
| 221 | "--cache-dir", ws.global_cache_directory.path orelse ".", // | |
| 222 | "--global-cache-dir", ws.global_cache_directory.path orelse ".", // | |
| 223 | "--name", "fuzzer", // | |
| 224 | "-rdynamic", // | |
| 225 | "-fsingle-threaded", // | |
| 226 | "--dep", "Walk", // | |
| 227 | "--dep", "html_render", // | |
| 228 | try std.fmt.allocPrint(arena, "-Mroot={}", .{main_src_path}), // | |
| 229 | try std.fmt.allocPrint(arena, "-MWalk={}", .{walk_src_path}), // | |
| 230 | "--dep", "Walk", // | |
| 231 | try std.fmt.allocPrint(arena, "-Mhtml_render={}", .{html_render_src_path}), // | |
| 232 | "--listen=-", | |
| 233 | }); | |
| 234 | ||
| 235 | var child = std.process.Child.init(argv.items, gpa); | |
| 236 | child.stdin_behavior = .Pipe; | |
| 237 | child.stdout_behavior = .Pipe; | |
| 238 | child.stderr_behavior = .Pipe; | |
| 239 | try child.spawn(); | |
| 240 | ||
| 241 | var poller = std.io.poll(gpa, enum { stdout, stderr }, .{ | |
| 242 | .stdout = child.stdout.?, | |
| 243 | .stderr = child.stderr.?, | |
| 244 | }); | |
| 245 | defer poller.deinit(); | |
| 246 | ||
| 247 | try sendMessage(child.stdin.?, .update); | |
| 248 | try sendMessage(child.stdin.?, .exit); | |
| 249 | ||
| 250 | const Header = std.zig.Server.Message.Header; | |
| 251 | var result: ?[]const u8 = null; | |
| 252 | var result_error_bundle = std.zig.ErrorBundle.empty; | |
| 253 | ||
| 254 | const stdout = poller.fifo(.stdout); | |
| 255 | ||
| 256 | poll: while (true) { | |
| 257 | while (stdout.readableLength() < @sizeOf(Header)) { | |
| 258 | if (!(try poller.poll())) break :poll; | |
| 259 | } | |
| 260 | const header = stdout.reader().readStruct(Header) catch unreachable; | |
| 261 | while (stdout.readableLength() < header.bytes_len) { | |
| 262 | if (!(try poller.poll())) break :poll; | |
| 263 | } | |
| 264 | const body = stdout.readableSliceOfLen(header.bytes_len); | |
| 265 | ||
| 266 | switch (header.tag) { | |
| 267 | .zig_version => { | |
| 268 | if (!std.mem.eql(u8, builtin.zig_version_string, body)) { | |
| 269 | return error.ZigProtocolVersionMismatch; | |
| 270 | } | |
| 271 | }, | |
| 272 | .error_bundle => { | |
| 273 | const EbHdr = std.zig.Server.Message.ErrorBundle; | |
| 274 | const eb_hdr = @as(*align(1) const EbHdr, @ptrCast(body)); | |
| 275 | const extra_bytes = | |
| 276 | body[@sizeOf(EbHdr)..][0 .. @sizeOf(u32) * eb_hdr.extra_len]; | |
| 277 | const string_bytes = | |
| 278 | body[@sizeOf(EbHdr) + extra_bytes.len ..][0..eb_hdr.string_bytes_len]; | |
| 279 | // TODO: use @ptrCast when the compiler supports it | |
| 280 | const unaligned_extra = std.mem.bytesAsSlice(u32, extra_bytes); | |
| 281 | const extra_array = try arena.alloc(u32, unaligned_extra.len); | |
| 282 | @memcpy(extra_array, unaligned_extra); | |
| 283 | result_error_bundle = .{ | |
| 284 | .string_bytes = try arena.dupe(u8, string_bytes), | |
| 285 | .extra = extra_array, | |
| 286 | }; | |
| 287 | }, | |
| 288 | .emit_bin_path => { | |
| 289 | const EbpHdr = std.zig.Server.Message.EmitBinPath; | |
| 290 | const ebp_hdr = @as(*align(1) const EbpHdr, @ptrCast(body)); | |
| 291 | if (!ebp_hdr.flags.cache_hit) { | |
| 292 | log.info("source changes detected; rebuilt wasm component", .{}); | |
| 293 | } | |
| 294 | result = try arena.dupe(u8, body[@sizeOf(EbpHdr)..]); | |
| 295 | }, | |
| 296 | else => {}, // ignore other messages | |
| 297 | } | |
| 298 | ||
| 299 | stdout.discard(body.len); | |
| 300 | } | |
| 301 | ||
| 302 | const stderr = poller.fifo(.stderr); | |
| 303 | if (stderr.readableLength() > 0) { | |
| 304 | const owned_stderr = try stderr.toOwnedSlice(); | |
| 305 | defer gpa.free(owned_stderr); | |
| 306 | std.debug.print("{s}", .{owned_stderr}); | |
| 307 | } | |
| 308 | ||
| 309 | // Send EOF to stdin. | |
| 310 | child.stdin.?.close(); | |
| 311 | child.stdin = null; | |
| 312 | ||
| 313 | switch (try child.wait()) { | |
| 314 | .Exited => |code| { | |
| 315 | if (code != 0) { | |
| 316 | log.err( | |
| 317 | "the following command exited with error code {d}:\n{s}", | |
| 318 | .{ code, try Build.Step.allocPrintCmd(arena, null, argv.items) }, | |
| 319 | ); | |
| 320 | return error.WasmCompilationFailed; | |
| 321 | } | |
| 322 | }, | |
| 323 | .Signal, .Stopped, .Unknown => { | |
| 324 | log.err( | |
| 325 | "the following command terminated unexpectedly:\n{s}", | |
| 326 | .{try Build.Step.allocPrintCmd(arena, null, argv.items)}, | |
| 327 | ); | |
| 328 | return error.WasmCompilationFailed; | |
| 329 | }, | |
| 330 | } | |
| 331 | ||
| 332 | if (result_error_bundle.errorMessageCount() > 0) { | |
| 333 | const color = std.zig.Color.auto; | |
| 334 | result_error_bundle.renderToStdErr(color.renderOptions()); | |
| 335 | log.err("the following command failed with {d} compilation errors:\n{s}", .{ | |
| 336 | result_error_bundle.errorMessageCount(), | |
| 337 | try Build.Step.allocPrintCmd(arena, null, argv.items), | |
| 338 | }); | |
| 339 | return error.WasmCompilationFailed; | |
| 340 | } | |
| 341 | ||
| 342 | return result orelse { | |
| 343 | log.err("child process failed to report result\n{s}", .{ | |
| 344 | try Build.Step.allocPrintCmd(arena, null, argv.items), | |
| 345 | }); | |
| 346 | return error.WasmCompilationFailed; | |
| 347 | }; | |
| 348 | } | |
| 349 | ||
| 350 | fn sendMessage(file: std.fs.File, tag: std.zig.Client.Message.Tag) !void { | |
| 351 | const header: std.zig.Client.Message.Header = .{ | |
| 352 | .tag = tag, | |
| 353 | .bytes_len = 0, | |
| 354 | }; | |
| 355 | try file.writeAll(std.mem.asBytes(&header)); | |
| 356 | } | |
| 357 | ||
| 358 | fn serveWebSocket(ws: *WebServer, web_socket: *std.http.WebSocket) !void { | |
| 359 | ws.coverage_mutex.lock(); | |
| 360 | defer ws.coverage_mutex.unlock(); | |
| 361 | ||
| 362 | // On first connection, the client needs all the coverage information | |
| 363 | // so that subsequent updates can contain only the updated bits. | |
| 364 | var prev_unique_runs: usize = 0; | |
| 365 | var prev_entry_points: usize = 0; | |
| 366 | try sendCoverageContext(ws, web_socket, &prev_unique_runs, &prev_entry_points); | |
| 367 | while (true) { | |
| 368 | ws.coverage_condition.timedWait(&ws.coverage_mutex, std.time.ns_per_ms * 500) catch {}; | |
| 369 | try sendCoverageContext(ws, web_socket, &prev_unique_runs, &prev_entry_points); | |
| 370 | } | |
| 371 | } | |
| 372 | ||
| 373 | fn sendCoverageContext( | |
| 374 | ws: *WebServer, | |
| 375 | web_socket: *std.http.WebSocket, | |
| 376 | prev_unique_runs: *usize, | |
| 377 | prev_entry_points: *usize, | |
| 378 | ) !void { | |
| 379 | const coverage_maps = ws.coverage_files.values(); | |
| 380 | if (coverage_maps.len == 0) return; | |
| 381 | // TODO: make each events URL correspond to one coverage map | |
| 382 | const coverage_map = &coverage_maps[0]; | |
| 383 | const cov_header: *const abi.SeenPcsHeader = @ptrCast(coverage_map.mapped_memory[0..@sizeOf(abi.SeenPcsHeader)]); | |
| 384 | const seen_pcs = coverage_map.mapped_memory[@sizeOf(abi.SeenPcsHeader) + coverage_map.source_locations.len * @sizeOf(usize) ..]; | |
| 385 | const n_runs = @atomicLoad(usize, &cov_header.n_runs, .monotonic); | |
| 386 | const unique_runs = @atomicLoad(usize, &cov_header.unique_runs, .monotonic); | |
| 387 | const lowest_stack = @atomicLoad(usize, &cov_header.lowest_stack, .monotonic); | |
| 388 | if (prev_unique_runs.* != unique_runs) { | |
| 389 | // There has been an update. | |
| 390 | if (prev_unique_runs.* == 0) { | |
| 391 | // We need to send initial context. | |
| 392 | const header: abi.SourceIndexHeader = .{ | |
| 393 | .flags = .{}, | |
| 394 | .directories_len = @intCast(coverage_map.coverage.directories.entries.len), | |
| 395 | .files_len = @intCast(coverage_map.coverage.files.entries.len), | |
| 396 | .source_locations_len = @intCast(coverage_map.source_locations.len), | |
| 397 | .string_bytes_len = @intCast(coverage_map.coverage.string_bytes.items.len), | |
| 398 | }; | |
| 399 | const iovecs: [5]std.posix.iovec_const = .{ | |
| 400 | makeIov(std.mem.asBytes(&header)), | |
| 401 | makeIov(std.mem.sliceAsBytes(coverage_map.coverage.directories.keys())), | |
| 402 | makeIov(std.mem.sliceAsBytes(coverage_map.coverage.files.keys())), | |
| 403 | makeIov(std.mem.sliceAsBytes(coverage_map.source_locations)), | |
| 404 | makeIov(coverage_map.coverage.string_bytes.items), | |
| 405 | }; | |
| 406 | try web_socket.writeMessagev(&iovecs, .binary); | |
| 407 | } | |
| 408 | ||
| 409 | const header: abi.CoverageUpdateHeader = .{ | |
| 410 | .n_runs = n_runs, | |
| 411 | .unique_runs = unique_runs, | |
| 412 | .lowest_stack = lowest_stack, | |
| 413 | }; | |
| 414 | const iovecs: [2]std.posix.iovec_const = .{ | |
| 415 | makeIov(std.mem.asBytes(&header)), | |
| 416 | makeIov(seen_pcs), | |
| 417 | }; | |
| 418 | try web_socket.writeMessagev(&iovecs, .binary); | |
| 419 | ||
| 420 | prev_unique_runs.* = unique_runs; | |
| 421 | } | |
| 422 | ||
| 423 | if (prev_entry_points.* != coverage_map.entry_points.items.len) { | |
| 424 | const header: abi.EntryPointHeader = .{ | |
| 425 | .flags = .{ | |
| 426 | .locs_len = @intCast(coverage_map.entry_points.items.len), | |
| 427 | }, | |
| 428 | }; | |
| 429 | const iovecs: [2]std.posix.iovec_const = .{ | |
| 430 | makeIov(std.mem.asBytes(&header)), | |
| 431 | makeIov(std.mem.sliceAsBytes(coverage_map.entry_points.items)), | |
| 432 | }; | |
| 433 | try web_socket.writeMessagev(&iovecs, .binary); | |
| 434 | ||
| 435 | prev_entry_points.* = coverage_map.entry_points.items.len; | |
| 436 | } | |
| 437 | } | |
| 438 | ||
| 439 | fn serveSourcesTar(ws: *WebServer, request: *std.http.Server.Request) !void { | |
| 440 | const gpa = ws.gpa; | |
| 441 | ||
| 442 | var arena_instance = std.heap.ArenaAllocator.init(gpa); | |
| 443 | defer arena_instance.deinit(); | |
| 444 | const arena = arena_instance.allocator(); | |
| 445 | ||
| 446 | var send_buffer: [0x4000]u8 = undefined; | |
| 447 | var response = request.respondStreaming(.{ | |
| 448 | .send_buffer = &send_buffer, | |
| 449 | .respond_options = .{ | |
| 450 | .extra_headers = &.{ | |
| 451 | .{ .name = "content-type", .value = "application/x-tar" }, | |
| 452 | cache_control_header, | |
| 453 | }, | |
| 454 | }, | |
| 455 | }); | |
| 456 | const w = response.writer(); | |
| 457 | ||
| 458 | const DedupeTable = std.ArrayHashMapUnmanaged(Build.Cache.Path, void, Build.Cache.Path.TableAdapter, false); | |
| 459 | var dedupe_table: DedupeTable = .{}; | |
| 460 | defer dedupe_table.deinit(gpa); | |
| 461 | ||
| 462 | for (ws.fuzz_run_steps) |run_step| { | |
| 463 | const compile_step_inputs = run_step.producer.?.step.inputs.table; | |
| 464 | for (compile_step_inputs.keys(), compile_step_inputs.values()) |dir_path, *file_list| { | |
| 465 | try dedupe_table.ensureUnusedCapacity(gpa, file_list.items.len); | |
| 466 | for (file_list.items) |sub_path| { | |
| 467 | // Special file "." means the entire directory. | |
| 468 | if (std.mem.eql(u8, sub_path, ".")) continue; | |
| 469 | const joined_path = try dir_path.join(arena, sub_path); | |
| 470 | _ = dedupe_table.getOrPutAssumeCapacity(joined_path); | |
| 471 | } | |
| 472 | } | |
| 473 | } | |
| 474 | ||
| 475 | const deduped_paths = dedupe_table.keys(); | |
| 476 | const SortContext = struct { | |
| 477 | pub fn lessThan(this: @This(), lhs: Build.Cache.Path, rhs: Build.Cache.Path) bool { | |
| 478 | _ = this; | |
| 479 | return switch (std.mem.order(u8, lhs.root_dir.path orelse ".", rhs.root_dir.path orelse ".")) { | |
| 480 | .lt => true, | |
| 481 | .gt => false, | |
| 482 | .eq => std.mem.lessThan(u8, lhs.sub_path, rhs.sub_path), | |
| 483 | }; | |
| 484 | } | |
| 485 | }; | |
| 486 | std.mem.sortUnstable(Build.Cache.Path, deduped_paths, SortContext{}, SortContext.lessThan); | |
| 487 | ||
| 488 | var cwd_cache: ?[]const u8 = null; | |
| 489 | ||
| 490 | for (deduped_paths) |joined_path| { | |
| 491 | var file = joined_path.root_dir.handle.openFile(joined_path.sub_path, .{}) catch |err| { | |
| 492 | log.err("failed to open {}: {s}", .{ joined_path, @errorName(err) }); | |
| 493 | continue; | |
| 494 | }; | |
| 495 | defer file.close(); | |
| 496 | ||
| 497 | const stat = file.stat() catch |err| { | |
| 498 | log.err("failed to stat {}: {s}", .{ joined_path, @errorName(err) }); | |
| 499 | continue; | |
| 500 | }; | |
| 501 | if (stat.kind != .file) | |
| 502 | continue; | |
| 503 | ||
| 504 | const padding = p: { | |
| 505 | const remainder = stat.size % 512; | |
| 506 | break :p if (remainder > 0) 512 - remainder else 0; | |
| 507 | }; | |
| 508 | ||
| 509 | var file_header = std.tar.output.Header.init(); | |
| 510 | file_header.typeflag = .regular; | |
| 511 | try file_header.setPath( | |
| 512 | joined_path.root_dir.path orelse try memoizedCwd(arena, &cwd_cache), | |
| 513 | joined_path.sub_path, | |
| 514 | ); | |
| 515 | try file_header.setSize(stat.size); | |
| 516 | try file_header.updateChecksum(); | |
| 517 | try w.writeAll(std.mem.asBytes(&file_header)); | |
| 518 | try w.writeFile(file); | |
| 519 | try w.writeByteNTimes(0, padding); | |
| 520 | } | |
| 521 | ||
| 522 | // intentionally omitting the pointless trailer | |
| 523 | //try w.writeByteNTimes(0, 512 * 2); | |
| 524 | try response.end(); | |
| 525 | } | |
| 526 | ||
| 527 | fn memoizedCwd(arena: Allocator, opt_ptr: *?[]const u8) ![]const u8 { | |
| 528 | if (opt_ptr.*) |cached| return cached; | |
| 529 | const result = try std.process.getCwdAlloc(arena); | |
| 530 | opt_ptr.* = result; | |
| 531 | return result; | |
| 532 | } | |
| 533 | ||
| 534 | const cache_control_header: std.http.Header = .{ | |
| 535 | .name = "cache-control", | |
| 536 | .value = "max-age=0, must-revalidate", | |
| 537 | }; | |
| 538 | ||
| 539 | pub fn coverageRun(ws: *WebServer) void { | |
| 540 | ws.mutex.lock(); | |
| 541 | defer ws.mutex.unlock(); | |
| 542 | ||
| 543 | while (true) { | |
| 544 | ws.condition.wait(&ws.mutex); | |
| 545 | for (ws.msg_queue.items) |msg| switch (msg) { | |
| 546 | .coverage => |coverage| prepareTables(ws, coverage.run, coverage.id) catch |err| switch (err) { | |
| 547 | error.AlreadyReported => continue, | |
| 548 | else => |e| log.err("failed to prepare code coverage tables: {s}", .{@errorName(e)}), | |
| 549 | }, | |
| 550 | .entry_point => |entry_point| addEntryPoint(ws, entry_point.coverage_id, entry_point.addr) catch |err| switch (err) { | |
| 551 | error.AlreadyReported => continue, | |
| 552 | else => |e| log.err("failed to prepare code coverage tables: {s}", .{@errorName(e)}), | |
| 553 | }, | |
| 554 | }; | |
| 555 | ws.msg_queue.clearRetainingCapacity(); | |
| 556 | } | |
| 557 | } | |
| 558 | ||
| 559 | fn prepareTables( | |
| 560 | ws: *WebServer, | |
| 561 | run_step: *Step.Run, | |
| 562 | coverage_id: u64, | |
| 563 | ) error{ OutOfMemory, AlreadyReported }!void { | |
| 564 | const gpa = ws.gpa; | |
| 565 | ||
| 566 | ws.coverage_mutex.lock(); | |
| 567 | defer ws.coverage_mutex.unlock(); | |
| 568 | ||
| 569 | const gop = try ws.coverage_files.getOrPut(gpa, coverage_id); | |
| 570 | if (gop.found_existing) { | |
| 571 | // We are fuzzing the same executable with multiple threads. | |
| 572 | // Perhaps the same unit test; perhaps a different one. In any | |
| 573 | // case, since the coverage file is the same, we only have to | |
| 574 | // notice changes to that one file in order to learn coverage for | |
| 575 | // this particular executable. | |
| 576 | return; | |
| 577 | } | |
| 578 | errdefer _ = ws.coverage_files.pop(); | |
| 579 | ||
| 580 | gop.value_ptr.* = .{ | |
| 581 | .coverage = std.debug.Coverage.init, | |
| 582 | .mapped_memory = undefined, // populated below | |
| 583 | .source_locations = undefined, // populated below | |
| 584 | .entry_points = .{}, | |
| 585 | }; | |
| 586 | errdefer gop.value_ptr.coverage.deinit(gpa); | |
| 587 | ||
| 588 | const rebuilt_exe_path: Build.Cache.Path = .{ | |
| 589 | .root_dir = Build.Cache.Directory.cwd(), | |
| 590 | .sub_path = run_step.rebuilt_executable.?, | |
| 591 | }; | |
| 592 | var debug_info = std.debug.Info.load(gpa, rebuilt_exe_path, &gop.value_ptr.coverage) catch |err| { | |
| 593 | log.err("step '{s}': failed to load debug information for '{}': {s}", .{ | |
| 594 | run_step.step.name, rebuilt_exe_path, @errorName(err), | |
| 595 | }); | |
| 596 | return error.AlreadyReported; | |
| 597 | }; | |
| 598 | defer debug_info.deinit(gpa); | |
| 599 | ||
| 600 | const coverage_file_path: Build.Cache.Path = .{ | |
| 601 | .root_dir = run_step.step.owner.cache_root, | |
| 602 | .sub_path = "v/" ++ std.fmt.hex(coverage_id), | |
| 603 | }; | |
| 604 | var coverage_file = coverage_file_path.root_dir.handle.openFile(coverage_file_path.sub_path, .{}) catch |err| { | |
| 605 | log.err("step '{s}': failed to load coverage file '{}': {s}", .{ | |
| 606 | run_step.step.name, coverage_file_path, @errorName(err), | |
| 607 | }); | |
| 608 | return error.AlreadyReported; | |
| 609 | }; | |
| 610 | defer coverage_file.close(); | |
| 611 | ||
| 612 | const file_size = coverage_file.getEndPos() catch |err| { | |
| 613 | log.err("unable to check len of coverage file '{}': {s}", .{ coverage_file_path, @errorName(err) }); | |
| 614 | return error.AlreadyReported; | |
| 615 | }; | |
| 616 | ||
| 617 | const mapped_memory = std.posix.mmap( | |
| 618 | null, | |
| 619 | file_size, | |
| 620 | std.posix.PROT.READ, | |
| 621 | .{ .TYPE = .SHARED }, | |
| 622 | coverage_file.handle, | |
| 623 | 0, | |
| 624 | ) catch |err| { | |
| 625 | log.err("failed to map coverage file '{}': {s}", .{ coverage_file_path, @errorName(err) }); | |
| 626 | return error.AlreadyReported; | |
| 627 | }; | |
| 628 | gop.value_ptr.mapped_memory = mapped_memory; | |
| 629 | ||
| 630 | const header: *const abi.SeenPcsHeader = @ptrCast(mapped_memory[0..@sizeOf(abi.SeenPcsHeader)]); | |
| 631 | const pcs_bytes = mapped_memory[@sizeOf(abi.SeenPcsHeader)..][0 .. header.pcs_len * @sizeOf(usize)]; | |
| 632 | const pcs = std.mem.bytesAsSlice(usize, pcs_bytes); | |
| 633 | const source_locations = try gpa.alloc(Coverage.SourceLocation, pcs.len); | |
| 634 | errdefer gpa.free(source_locations); | |
| 635 | debug_info.resolveAddresses(gpa, pcs, source_locations) catch |err| { | |
| 636 | log.err("failed to resolve addresses to source locations: {s}", .{@errorName(err)}); | |
| 637 | return error.AlreadyReported; | |
| 638 | }; | |
| 639 | gop.value_ptr.source_locations = source_locations; | |
| 640 | ||
| 641 | ws.coverage_condition.broadcast(); | |
| 642 | } | |
| 643 | ||
| 644 | fn addEntryPoint(ws: *WebServer, coverage_id: u64, addr: u64) error{ AlreadyReported, OutOfMemory }!void { | |
| 645 | ws.coverage_mutex.lock(); | |
| 646 | defer ws.coverage_mutex.unlock(); | |
| 647 | ||
| 648 | const coverage_map = ws.coverage_files.getPtr(coverage_id).?; | |
| 649 | const ptr = coverage_map.mapped_memory; | |
| 650 | const pcs_bytes = ptr[@sizeOf(abi.SeenPcsHeader)..][0 .. coverage_map.source_locations.len * @sizeOf(usize)]; | |
| 651 | const pcs: []const usize = @alignCast(std.mem.bytesAsSlice(usize, pcs_bytes)); | |
| 652 | const index = std.sort.upperBound(usize, pcs, addr, struct { | |
| 653 | fn order(context: usize, item: usize) std.math.Order { | |
| 654 | return std.math.order(item, context); | |
| 655 | } | |
| 656 | }.order); | |
| 657 | if (index >= pcs.len) { | |
| 658 | log.err("unable to find unit test entry address 0x{x} in source locations (range: 0x{x} to 0x{x})", .{ | |
| 659 | addr, pcs[0], pcs[pcs.len - 1], | |
| 660 | }); | |
| 661 | return error.AlreadyReported; | |
| 662 | } | |
| 663 | if (false) { | |
| 664 | const sl = coverage_map.source_locations[index]; | |
| 665 | const file_name = coverage_map.coverage.stringAt(coverage_map.coverage.fileAt(sl.file).basename); | |
| 666 | log.debug("server found entry point {s}:{d}:{d}", .{ | |
| 667 | file_name, sl.line, sl.column, | |
| 668 | }); | |
| 669 | } | |
| 670 | const gpa = ws.gpa; | |
| 671 | try coverage_map.entry_points.append(gpa, @intCast(index)); | |
| 672 | } | |
| 673 | ||
| 674 | fn makeIov(s: []const u8) std.posix.iovec_const { | |
| 675 | return .{ | |
| 676 | .base = s.ptr, | |
| 677 | .len = s.len, | |
| 678 | }; | |
| 679 | } |
lib/std/Build/Fuzz/abi.zig created+69| ... | ... | @@ -0,0 +1,69 @@ |
| 1 | //! This file is shared among Zig code running in wildly different contexts: | |
| 2 | //! libfuzzer, compiled alongside unit tests, the build runner, running on the | |
| 3 | //! host computer, and the fuzzing web interface webassembly code running in | |
| 4 | //! the browser. All of these components interface to some degree via an ABI. | |
| 5 | ||
| 6 | /// libfuzzer uses this and its usize is the one that counts. To match the ABI, | |
| 7 | /// make the ints be the size of the target used with libfuzzer. | |
| 8 | /// | |
| 9 | /// Trailing: | |
| 10 | /// * pc_addr: usize for each pcs_len | |
| 11 | /// * 1 bit per pc_addr, usize elements | |
| 12 | pub const SeenPcsHeader = extern struct { | |
| 13 | n_runs: usize, | |
| 14 | unique_runs: usize, | |
| 15 | pcs_len: usize, | |
| 16 | lowest_stack: usize, | |
| 17 | }; | |
| 18 | ||
| 19 | pub const ToClientTag = enum(u8) { | |
| 20 | source_index, | |
| 21 | coverage_update, | |
| 22 | entry_points, | |
| 23 | _, | |
| 24 | }; | |
| 25 | ||
| 26 | /// Sent to the fuzzer web client on first connection to the websocket URL. | |
| 27 | /// | |
| 28 | /// Trailing: | |
| 29 | /// * std.debug.Coverage.String for each directories_len | |
| 30 | /// * std.debug.Coverage.File for each files_len | |
| 31 | /// * std.debug.Coverage.SourceLocation for each source_locations_len | |
| 32 | /// * u8 for each string_bytes_len | |
| 33 | pub const SourceIndexHeader = extern struct { | |
| 34 | flags: Flags, | |
| 35 | directories_len: u32, | |
| 36 | files_len: u32, | |
| 37 | source_locations_len: u32, | |
| 38 | string_bytes_len: u32, | |
| 39 | ||
| 40 | pub const Flags = packed struct(u32) { | |
| 41 | tag: ToClientTag = .source_index, | |
| 42 | _: u24 = 0, | |
| 43 | }; | |
| 44 | }; | |
| 45 | ||
| 46 | /// Sent to the fuzzer web client whenever the set of covered source locations | |
| 47 | /// changes. | |
| 48 | /// | |
| 49 | /// Trailing: | |
| 50 | /// * one bit per source_locations_len, contained in u8 elements | |
| 51 | pub const CoverageUpdateHeader = extern struct { | |
| 52 | tag: ToClientTag = .coverage_update, | |
| 53 | n_runs: u64 align(1), | |
| 54 | unique_runs: u64 align(1), | |
| 55 | lowest_stack: u64 align(1), | |
| 56 | }; | |
| 57 | ||
| 58 | /// Sent to the fuzzer web client when the set of entry points is updated. | |
| 59 | /// | |
| 60 | /// Trailing: | |
| 61 | /// * one u32 index of source_locations per locs_len | |
| 62 | pub const EntryPointHeader = extern struct { | |
| 63 | flags: Flags, | |
| 64 | ||
| 65 | pub const Flags = packed struct(u32) { | |
| 66 | tag: ToClientTag = .entry_points, | |
| 67 | locs_len: u24, | |
| 68 | }; | |
| 69 | }; |
lib/std/Build/Step.zig+2-1| ... | ... | @@ -559,7 +559,8 @@ fn zigProcessUpdate(s: *Step, zp: *ZigProcess, watch: bool) !?[]const u8 { |
| 559 | 559 | }, |
| 560 | 560 | .zig_lib => zl: { |
| 561 | 561 | if (s.cast(Step.Compile)) |compile| { |
| 562 | if (compile.zig_lib_dir) |lp| { | |
| 562 | if (compile.zig_lib_dir) |zig_lib_dir| { | |
| 563 | const lp = try zig_lib_dir.join(arena, sub_path); | |
| 563 | 564 | try addWatchInput(s, lp); |
| 564 | 565 | break :zl; |
| 565 | 566 | } |
lib/std/Build/Step/Run.zig+57-14| ... | ... | @@ -205,6 +205,7 @@ pub fn enableTestRunnerMode(run: *Run) void { |
| 205 | 205 | run.stdio = .zig_test; |
| 206 | 206 | run.addArgs(&.{ |
| 207 | 207 | std.fmt.allocPrint(arena, "--seed=0x{x}", .{b.graph.random_seed}) catch @panic("OOM"), |
| 208 | std.fmt.allocPrint(arena, "--cache-dir={s}", .{b.cache_root.path orelse ""}) catch @panic("OOM"), | |
| 208 | 209 | "--listen=-", |
| 209 | 210 | }); |
| 210 | 211 | } |
| ... | ... | @@ -845,7 +846,12 @@ fn make(step: *Step, options: Step.MakeOptions) !void { |
| 845 | 846 | ); |
| 846 | 847 | } |
| 847 | 848 | |
| 848 | pub fn rerunInFuzzMode(run: *Run, unit_test_index: u32, prog_node: std.Progress.Node) !void { | |
| 849 | pub fn rerunInFuzzMode( | |
| 850 | run: *Run, | |
| 851 | web_server: *std.Build.Fuzz.WebServer, | |
| 852 | unit_test_index: u32, | |
| 853 | prog_node: std.Progress.Node, | |
| 854 | ) !void { | |
| 849 | 855 | const step = &run.step; |
| 850 | 856 | const b = step.owner; |
| 851 | 857 | const arena = b.allocator; |
| ... | ... | @@ -877,7 +883,10 @@ pub fn rerunInFuzzMode(run: *Run, unit_test_index: u32, prog_node: std.Progress. |
| 877 | 883 | const has_side_effects = false; |
| 878 | 884 | const rand_int = std.crypto.random.int(u64); |
| 879 | 885 | const tmp_dir_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(rand_int); |
| 880 | try runCommand(run, argv_list.items, has_side_effects, tmp_dir_path, prog_node, unit_test_index); | |
| 886 | try runCommand(run, argv_list.items, has_side_effects, tmp_dir_path, prog_node, .{ | |
| 887 | .unit_test_index = unit_test_index, | |
| 888 | .web_server = web_server, | |
| 889 | }); | |
| 881 | 890 | } |
| 882 | 891 | |
| 883 | 892 | fn populateGeneratedPaths( |
| ... | ... | @@ -952,13 +961,18 @@ fn termMatches(expected: ?std.process.Child.Term, actual: std.process.Child.Term |
| 952 | 961 | }; |
| 953 | 962 | } |
| 954 | 963 | |
| 964 | const FuzzContext = struct { | |
| 965 | web_server: *std.Build.Fuzz.WebServer, | |
| 966 | unit_test_index: u32, | |
| 967 | }; | |
| 968 | ||
| 955 | 969 | fn runCommand( |
| 956 | 970 | run: *Run, |
| 957 | 971 | argv: []const []const u8, |
| 958 | 972 | has_side_effects: bool, |
| 959 | 973 | output_dir_path: []const u8, |
| 960 | 974 | prog_node: std.Progress.Node, |
| 961 | fuzz_unit_test_index: ?u32, | |
| 975 | fuzz_context: ?FuzzContext, | |
| 962 | 976 | ) !void { |
| 963 | 977 | const step = &run.step; |
| 964 | 978 | const b = step.owner; |
| ... | ... | @@ -977,7 +991,7 @@ fn runCommand( |
| 977 | 991 | var interp_argv = std.ArrayList([]const u8).init(b.allocator); |
| 978 | 992 | defer interp_argv.deinit(); |
| 979 | 993 | |
| 980 | const result = spawnChildAndCollect(run, argv, has_side_effects, prog_node, fuzz_unit_test_index) catch |err| term: { | |
| 994 | const result = spawnChildAndCollect(run, argv, has_side_effects, prog_node, fuzz_context) catch |err| term: { | |
| 981 | 995 | // InvalidExe: cpu arch mismatch |
| 982 | 996 | // FileNotFound: can happen with a wrong dynamic linker path |
| 983 | 997 | if (err == error.InvalidExe or err == error.FileNotFound) interpret: { |
| ... | ... | @@ -1113,7 +1127,7 @@ fn runCommand( |
| 1113 | 1127 | |
| 1114 | 1128 | try Step.handleVerbose2(step.owner, cwd, run.env_map, interp_argv.items); |
| 1115 | 1129 | |
| 1116 | break :term spawnChildAndCollect(run, interp_argv.items, has_side_effects, prog_node, fuzz_unit_test_index) catch |e| { | |
| 1130 | break :term spawnChildAndCollect(run, interp_argv.items, has_side_effects, prog_node, fuzz_context) catch |e| { | |
| 1117 | 1131 | if (!run.failing_to_execute_foreign_is_an_error) return error.MakeSkipped; |
| 1118 | 1132 | |
| 1119 | 1133 | return step.fail("unable to spawn interpreter {s}: {s}", .{ |
| ... | ... | @@ -1133,7 +1147,7 @@ fn runCommand( |
| 1133 | 1147 | |
| 1134 | 1148 | const final_argv = if (interp_argv.items.len == 0) argv else interp_argv.items; |
| 1135 | 1149 | |
| 1136 | if (fuzz_unit_test_index != null) { | |
| 1150 | if (fuzz_context != null) { | |
| 1137 | 1151 | try step.handleChildProcessTerm(result.term, cwd, final_argv); |
| 1138 | 1152 | return; |
| 1139 | 1153 | } |
| ... | ... | @@ -1298,12 +1312,12 @@ fn spawnChildAndCollect( |
| 1298 | 1312 | argv: []const []const u8, |
| 1299 | 1313 | has_side_effects: bool, |
| 1300 | 1314 | prog_node: std.Progress.Node, |
| 1301 | fuzz_unit_test_index: ?u32, | |
| 1315 | fuzz_context: ?FuzzContext, | |
| 1302 | 1316 | ) !ChildProcResult { |
| 1303 | 1317 | const b = run.step.owner; |
| 1304 | 1318 | const arena = b.allocator; |
| 1305 | 1319 | |
| 1306 | if (fuzz_unit_test_index != null) { | |
| 1320 | if (fuzz_context != null) { | |
| 1307 | 1321 | assert(!has_side_effects); |
| 1308 | 1322 | assert(run.stdio == .zig_test); |
| 1309 | 1323 | } |
| ... | ... | @@ -1357,7 +1371,7 @@ fn spawnChildAndCollect( |
| 1357 | 1371 | var timer = try std.time.Timer.start(); |
| 1358 | 1372 | |
| 1359 | 1373 | const result = if (run.stdio == .zig_test) |
| 1360 | evalZigTest(run, &child, prog_node, fuzz_unit_test_index) | |
| 1374 | evalZigTest(run, &child, prog_node, fuzz_context) | |
| 1361 | 1375 | else |
| 1362 | 1376 | evalGeneric(run, &child); |
| 1363 | 1377 | |
| ... | ... | @@ -1383,7 +1397,7 @@ fn evalZigTest( |
| 1383 | 1397 | run: *Run, |
| 1384 | 1398 | child: *std.process.Child, |
| 1385 | 1399 | prog_node: std.Progress.Node, |
| 1386 | fuzz_unit_test_index: ?u32, | |
| 1400 | fuzz_context: ?FuzzContext, | |
| 1387 | 1401 | ) !StdIoResult { |
| 1388 | 1402 | const gpa = run.step.owner.allocator; |
| 1389 | 1403 | const arena = run.step.owner.allocator; |
| ... | ... | @@ -1394,8 +1408,8 @@ fn evalZigTest( |
| 1394 | 1408 | }); |
| 1395 | 1409 | defer poller.deinit(); |
| 1396 | 1410 | |
| 1397 | if (fuzz_unit_test_index) |index| { | |
| 1398 | try sendRunTestMessage(child.stdin.?, .start_fuzzing, index); | |
| 1411 | if (fuzz_context) |fuzz| { | |
| 1412 | try sendRunTestMessage(child.stdin.?, .start_fuzzing, fuzz.unit_test_index); | |
| 1399 | 1413 | } else { |
| 1400 | 1414 | run.fuzz_tests.clearRetainingCapacity(); |
| 1401 | 1415 | try sendMessage(child.stdin.?, .query_test_metadata); |
| ... | ... | @@ -1413,6 +1427,7 @@ fn evalZigTest( |
| 1413 | 1427 | var log_err_count: u32 = 0; |
| 1414 | 1428 | |
| 1415 | 1429 | var metadata: ?TestMetadata = null; |
| 1430 | var coverage_id: ?u64 = null; | |
| 1416 | 1431 | |
| 1417 | 1432 | var sub_prog_node: ?std.Progress.Node = null; |
| 1418 | 1433 | defer if (sub_prog_node) |n| n.end(); |
| ... | ... | @@ -1437,7 +1452,7 @@ fn evalZigTest( |
| 1437 | 1452 | } |
| 1438 | 1453 | }, |
| 1439 | 1454 | .test_metadata => { |
| 1440 | assert(fuzz_unit_test_index == null); | |
| 1455 | assert(fuzz_context == null); | |
| 1441 | 1456 | const TmHdr = std.zig.Server.Message.TestMetadata; |
| 1442 | 1457 | const tm_hdr = @as(*align(1) const TmHdr, @ptrCast(body)); |
| 1443 | 1458 | test_count = tm_hdr.tests_len; |
| ... | ... | @@ -1466,7 +1481,7 @@ fn evalZigTest( |
| 1466 | 1481 | try requestNextTest(child.stdin.?, &metadata.?, &sub_prog_node); |
| 1467 | 1482 | }, |
| 1468 | 1483 | .test_results => { |
| 1469 | assert(fuzz_unit_test_index == null); | |
| 1484 | assert(fuzz_context == null); | |
| 1470 | 1485 | const md = metadata.?; |
| 1471 | 1486 | |
| 1472 | 1487 | const TrHdr = std.zig.Server.Message.TestResults; |
| ... | ... | @@ -1500,6 +1515,34 @@ fn evalZigTest( |
| 1500 | 1515 | |
| 1501 | 1516 | try requestNextTest(child.stdin.?, &metadata.?, &sub_prog_node); |
| 1502 | 1517 | }, |
| 1518 | .coverage_id => { | |
| 1519 | const web_server = fuzz_context.?.web_server; | |
| 1520 | const msg_ptr: *align(1) const u64 = @ptrCast(body); | |
| 1521 | coverage_id = msg_ptr.*; | |
| 1522 | { | |
| 1523 | web_server.mutex.lock(); | |
| 1524 | defer web_server.mutex.unlock(); | |
| 1525 | try web_server.msg_queue.append(web_server.gpa, .{ .coverage = .{ | |
| 1526 | .id = coverage_id.?, | |
| 1527 | .run = run, | |
| 1528 | } }); | |
| 1529 | web_server.condition.signal(); | |
| 1530 | } | |
| 1531 | }, | |
| 1532 | .fuzz_start_addr => { | |
| 1533 | const web_server = fuzz_context.?.web_server; | |
| 1534 | const msg_ptr: *align(1) const u64 = @ptrCast(body); | |
| 1535 | const addr = msg_ptr.*; | |
| 1536 | { | |
| 1537 | web_server.mutex.lock(); | |
| 1538 | defer web_server.mutex.unlock(); | |
| 1539 | try web_server.msg_queue.append(web_server.gpa, .{ .entry_point = .{ | |
| 1540 | .addr = addr, | |
| 1541 | .coverage_id = coverage_id.?, | |
| 1542 | } }); | |
| 1543 | web_server.condition.signal(); | |
| 1544 | } | |
| 1545 | }, | |
| 1503 | 1546 | else => {}, // ignore other messages |
| 1504 | 1547 | } |
| 1505 | 1548 |
lib/std/debug.zig+30-107| ... | ... | @@ -14,9 +14,12 @@ const native_os = builtin.os.tag; |
| 14 | 14 | const native_endian = native_arch.endian(); |
| 15 | 15 | |
| 16 | 16 | pub const MemoryAccessor = @import("debug/MemoryAccessor.zig"); |
| 17 | pub const FixedBufferReader = @import("debug/FixedBufferReader.zig"); | |
| 17 | 18 | pub const Dwarf = @import("debug/Dwarf.zig"); |
| 18 | 19 | pub const Pdb = @import("debug/Pdb.zig"); |
| 19 | 20 | pub const SelfInfo = @import("debug/SelfInfo.zig"); |
| 21 | pub const Info = @import("debug/Info.zig"); | |
| 22 | pub const Coverage = @import("debug/Coverage.zig"); | |
| 20 | 23 | |
| 21 | 24 | /// Unresolved source locations can be represented with a single `usize` that |
| 22 | 25 | /// corresponds to a virtual memory address of the program counter. Combined |
| ... | ... | @@ -26,6 +29,18 @@ pub const SourceLocation = struct { |
| 26 | 29 | line: u64, |
| 27 | 30 | column: u64, |
| 28 | 31 | file_name: []const u8, |
| 32 | ||
| 33 | pub const invalid: SourceLocation = .{ | |
| 34 | .line = 0, | |
| 35 | .column = 0, | |
| 36 | .file_name = &.{}, | |
| 37 | }; | |
| 38 | }; | |
| 39 | ||
| 40 | pub const Symbol = struct { | |
| 41 | name: []const u8 = "???", | |
| 42 | compile_unit_name: []const u8 = "???", | |
| 43 | source_location: ?SourceLocation = null, | |
| 29 | 44 | }; |
| 30 | 45 | |
| 31 | 46 | /// Deprecated because it returns the optimization mode of the standard |
| ... | ... | @@ -748,7 +763,7 @@ pub fn writeCurrentStackTrace( |
| 748 | 763 | // an overflow. We do not need to signal `StackIterator` as it will correctly detect this |
| 749 | 764 | // condition on the subsequent iteration and return `null` thus terminating the loop. |
| 750 | 765 | // same behaviour for x86-windows-msvc |
| 751 | const address = if (return_address == 0) return_address else return_address - 1; | |
| 766 | const address = return_address -| 1; | |
| 752 | 767 | try printSourceAtAddress(debug_info, out_stream, address, tty_config); |
| 753 | 768 | } else printLastUnwindError(&it, debug_info, out_stream, tty_config); |
| 754 | 769 | } |
| ... | ... | @@ -871,13 +886,13 @@ pub fn printSourceAtAddress(debug_info: *SelfInfo, out_stream: anytype, address: |
| 871 | 886 | error.MissingDebugInfo, error.InvalidDebugInfo => return printUnknownSource(debug_info, out_stream, address, tty_config), |
| 872 | 887 | else => return err, |
| 873 | 888 | }; |
| 874 | defer symbol_info.deinit(debug_info.allocator); | |
| 889 | defer if (symbol_info.source_location) |sl| debug_info.allocator.free(sl.file_name); | |
| 875 | 890 | |
| 876 | 891 | return printLineInfo( |
| 877 | 892 | out_stream, |
| 878 | symbol_info.line_info, | |
| 893 | symbol_info.source_location, | |
| 879 | 894 | address, |
| 880 | symbol_info.symbol_name, | |
| 895 | symbol_info.name, | |
| 881 | 896 | symbol_info.compile_unit_name, |
| 882 | 897 | tty_config, |
| 883 | 898 | printLineFromFileAnyOs, |
| ... | ... | @@ -886,7 +901,7 @@ pub fn printSourceAtAddress(debug_info: *SelfInfo, out_stream: anytype, address: |
| 886 | 901 | |
| 887 | 902 | fn printLineInfo( |
| 888 | 903 | out_stream: anytype, |
| 889 | line_info: ?SourceLocation, | |
| 904 | source_location: ?SourceLocation, | |
| 890 | 905 | address: usize, |
| 891 | 906 | symbol_name: []const u8, |
| 892 | 907 | compile_unit_name: []const u8, |
| ... | ... | @@ -896,8 +911,8 @@ fn printLineInfo( |
| 896 | 911 | nosuspend { |
| 897 | 912 | try tty_config.setColor(out_stream, .bold); |
| 898 | 913 | |
| 899 | if (line_info) |*li| { | |
| 900 | try out_stream.print("{s}:{d}:{d}", .{ li.file_name, li.line, li.column }); | |
| 914 | if (source_location) |*sl| { | |
| 915 | try out_stream.print("{s}:{d}:{d}", .{ sl.file_name, sl.line, sl.column }); | |
| 901 | 916 | } else { |
| 902 | 917 | try out_stream.writeAll("???:?:?"); |
| 903 | 918 | } |
| ... | ... | @@ -910,11 +925,11 @@ fn printLineInfo( |
| 910 | 925 | try out_stream.writeAll("\n"); |
| 911 | 926 | |
| 912 | 927 | // Show the matching source code line if possible |
| 913 | if (line_info) |li| { | |
| 914 | if (printLineFromFile(out_stream, li)) { | |
| 915 | if (li.column > 0) { | |
| 928 | if (source_location) |sl| { | |
| 929 | if (printLineFromFile(out_stream, sl)) { | |
| 930 | if (sl.column > 0) { | |
| 916 | 931 | // The caret already takes one char |
| 917 | const space_needed = @as(usize, @intCast(li.column - 1)); | |
| 932 | const space_needed = @as(usize, @intCast(sl.column - 1)); | |
| 918 | 933 | |
| 919 | 934 | try out_stream.writeByteNTimes(' ', space_needed); |
| 920 | 935 | try tty_config.setColor(out_stream, .green); |
| ... | ... | @@ -932,10 +947,10 @@ fn printLineInfo( |
| 932 | 947 | } |
| 933 | 948 | } |
| 934 | 949 | |
| 935 | fn printLineFromFileAnyOs(out_stream: anytype, line_info: SourceLocation) !void { | |
| 950 | fn printLineFromFileAnyOs(out_stream: anytype, source_location: SourceLocation) !void { | |
| 936 | 951 | // Need this to always block even in async I/O mode, because this could potentially |
| 937 | 952 | // be called from e.g. the event loop code crashing. |
| 938 | var f = try fs.cwd().openFile(line_info.file_name, .{}); | |
| 953 | var f = try fs.cwd().openFile(source_location.file_name, .{}); | |
| 939 | 954 | defer f.close(); |
| 940 | 955 | // TODO fstat and make sure that the file has the correct size |
| 941 | 956 | |
| ... | ... | @@ -944,7 +959,7 @@ fn printLineFromFileAnyOs(out_stream: anytype, line_info: SourceLocation) !void |
| 944 | 959 | const line_start = seek: { |
| 945 | 960 | var current_line_start: usize = 0; |
| 946 | 961 | var next_line: usize = 1; |
| 947 | while (next_line != line_info.line) { | |
| 962 | while (next_line != source_location.line) { | |
| 948 | 963 | const slice = buf[current_line_start..amt_read]; |
| 949 | 964 | if (mem.indexOfScalar(u8, slice, '\n')) |pos| { |
| 950 | 965 | next_line += 1; |
| ... | ... | @@ -1481,99 +1496,6 @@ pub const SafetyLock = struct { |
| 1481 | 1496 | } |
| 1482 | 1497 | }; |
| 1483 | 1498 | |
| 1484 | /// Deprecated. Don't use this, just read from your memory directly. | |
| 1485 | /// | |
| 1486 | /// This only exists because someone was too lazy to rework logic that used to | |
| 1487 | /// operate on an open file to operate on a memory buffer instead. | |
| 1488 | pub const DeprecatedFixedBufferReader = struct { | |
| 1489 | buf: []const u8, | |
| 1490 | pos: usize = 0, | |
| 1491 | endian: std.builtin.Endian, | |
| 1492 | ||
| 1493 | pub const Error = error{ EndOfBuffer, Overflow, InvalidBuffer }; | |
| 1494 | ||
| 1495 | pub fn seekTo(fbr: *DeprecatedFixedBufferReader, pos: u64) Error!void { | |
| 1496 | if (pos > fbr.buf.len) return error.EndOfBuffer; | |
| 1497 | fbr.pos = @intCast(pos); | |
| 1498 | } | |
| 1499 | ||
| 1500 | pub fn seekForward(fbr: *DeprecatedFixedBufferReader, amount: u64) Error!void { | |
| 1501 | if (fbr.buf.len - fbr.pos < amount) return error.EndOfBuffer; | |
| 1502 | fbr.pos += @intCast(amount); | |
| 1503 | } | |
| 1504 | ||
| 1505 | pub inline fn readByte(fbr: *DeprecatedFixedBufferReader) Error!u8 { | |
| 1506 | if (fbr.pos >= fbr.buf.len) return error.EndOfBuffer; | |
| 1507 | defer fbr.pos += 1; | |
| 1508 | return fbr.buf[fbr.pos]; | |
| 1509 | } | |
| 1510 | ||
| 1511 | pub fn readByteSigned(fbr: *DeprecatedFixedBufferReader) Error!i8 { | |
| 1512 | return @bitCast(try fbr.readByte()); | |
| 1513 | } | |
| 1514 | ||
| 1515 | pub fn readInt(fbr: *DeprecatedFixedBufferReader, comptime T: type) Error!T { | |
| 1516 | const size = @divExact(@typeInfo(T).Int.bits, 8); | |
| 1517 | if (fbr.buf.len - fbr.pos < size) return error.EndOfBuffer; | |
| 1518 | defer fbr.pos += size; | |
| 1519 | return std.mem.readInt(T, fbr.buf[fbr.pos..][0..size], fbr.endian); | |
| 1520 | } | |
| 1521 | ||
| 1522 | pub fn readIntChecked( | |
| 1523 | fbr: *DeprecatedFixedBufferReader, | |
| 1524 | comptime T: type, | |
| 1525 | ma: *MemoryAccessor, | |
| 1526 | ) Error!T { | |
| 1527 | if (ma.load(T, @intFromPtr(fbr.buf[fbr.pos..].ptr)) == null) | |
| 1528 | return error.InvalidBuffer; | |
| 1529 | ||
| 1530 | return fbr.readInt(T); | |
| 1531 | } | |
| 1532 | ||
| 1533 | pub fn readUleb128(fbr: *DeprecatedFixedBufferReader, comptime T: type) Error!T { | |
| 1534 | return std.leb.readUleb128(T, fbr); | |
| 1535 | } | |
| 1536 | ||
| 1537 | pub fn readIleb128(fbr: *DeprecatedFixedBufferReader, comptime T: type) Error!T { | |
| 1538 | return std.leb.readIleb128(T, fbr); | |
| 1539 | } | |
| 1540 | ||
| 1541 | pub fn readAddress(fbr: *DeprecatedFixedBufferReader, format: std.dwarf.Format) Error!u64 { | |
| 1542 | return switch (format) { | |
| 1543 | .@"32" => try fbr.readInt(u32), | |
| 1544 | .@"64" => try fbr.readInt(u64), | |
| 1545 | }; | |
| 1546 | } | |
| 1547 | ||
| 1548 | pub fn readAddressChecked( | |
| 1549 | fbr: *DeprecatedFixedBufferReader, | |
| 1550 | format: std.dwarf.Format, | |
| 1551 | ma: *MemoryAccessor, | |
| 1552 | ) Error!u64 { | |
| 1553 | return switch (format) { | |
| 1554 | .@"32" => try fbr.readIntChecked(u32, ma), | |
| 1555 | .@"64" => try fbr.readIntChecked(u64, ma), | |
| 1556 | }; | |
| 1557 | } | |
| 1558 | ||
| 1559 | pub fn readBytes(fbr: *DeprecatedFixedBufferReader, len: usize) Error![]const u8 { | |
| 1560 | if (fbr.buf.len - fbr.pos < len) return error.EndOfBuffer; | |
| 1561 | defer fbr.pos += len; | |
| 1562 | return fbr.buf[fbr.pos..][0..len]; | |
| 1563 | } | |
| 1564 | ||
| 1565 | pub fn readBytesTo(fbr: *DeprecatedFixedBufferReader, comptime sentinel: u8) Error![:sentinel]const u8 { | |
| 1566 | const end = @call(.always_inline, std.mem.indexOfScalarPos, .{ | |
| 1567 | u8, | |
| 1568 | fbr.buf, | |
| 1569 | fbr.pos, | |
| 1570 | sentinel, | |
| 1571 | }) orelse return error.EndOfBuffer; | |
| 1572 | defer fbr.pos = end + 1; | |
| 1573 | return fbr.buf[fbr.pos..end :sentinel]; | |
| 1574 | } | |
| 1575 | }; | |
| 1576 | ||
| 1577 | 1499 | /// Detect whether the program is being executed in the Valgrind virtual machine. |
| 1578 | 1500 | /// |
| 1579 | 1501 | /// When Valgrind integrations are disabled, this returns comptime-known false. |
| ... | ... | @@ -1587,6 +1509,7 @@ pub inline fn inValgrind() bool { |
| 1587 | 1509 | test { |
| 1588 | 1510 | _ = &Dwarf; |
| 1589 | 1511 | _ = &MemoryAccessor; |
| 1512 | _ = &FixedBufferReader; | |
| 1590 | 1513 | _ = &Pdb; |
| 1591 | 1514 | _ = &SelfInfo; |
| 1592 | 1515 | _ = &dumpHex; |
lib/std/debug/Coverage.zig created+244| ... | ... | @@ -0,0 +1,244 @@ |
| 1 | const std = @import("../std.zig"); | |
| 2 | const Allocator = std.mem.Allocator; | |
| 3 | const Hash = std.hash.Wyhash; | |
| 4 | const Dwarf = std.debug.Dwarf; | |
| 5 | const assert = std.debug.assert; | |
| 6 | ||
| 7 | const Coverage = @This(); | |
| 8 | ||
| 9 | /// Provides a globally-scoped integer index for directories. | |
| 10 | /// | |
| 11 | /// As opposed to, for example, a directory index that is compilation-unit | |
| 12 | /// scoped inside a single ELF module. | |
| 13 | /// | |
| 14 | /// String memory references the memory-mapped debug information. | |
| 15 | /// | |
| 16 | /// Protected by `mutex`. | |
| 17 | directories: std.ArrayHashMapUnmanaged(String, void, String.MapContext, false), | |
| 18 | /// Provides a globally-scoped integer index for files. | |
| 19 | /// | |
| 20 | /// String memory references the memory-mapped debug information. | |
| 21 | /// | |
| 22 | /// Protected by `mutex`. | |
| 23 | files: std.ArrayHashMapUnmanaged(File, void, File.MapContext, false), | |
| 24 | string_bytes: std.ArrayListUnmanaged(u8), | |
| 25 | /// Protects the other fields. | |
| 26 | mutex: std.Thread.Mutex, | |
| 27 | ||
| 28 | pub const init: Coverage = .{ | |
| 29 | .directories = .{}, | |
| 30 | .files = .{}, | |
| 31 | .mutex = .{}, | |
| 32 | .string_bytes = .{}, | |
| 33 | }; | |
| 34 | ||
| 35 | pub const String = enum(u32) { | |
| 36 | _, | |
| 37 | ||
| 38 | pub const MapContext = struct { | |
| 39 | string_bytes: []const u8, | |
| 40 | ||
| 41 | pub fn eql(self: @This(), a: String, b: String, b_index: usize) bool { | |
| 42 | _ = b_index; | |
| 43 | const a_slice = span(self.string_bytes[@intFromEnum(a)..]); | |
| 44 | const b_slice = span(self.string_bytes[@intFromEnum(b)..]); | |
| 45 | return std.mem.eql(u8, a_slice, b_slice); | |
| 46 | } | |
| 47 | ||
| 48 | pub fn hash(self: @This(), a: String) u32 { | |
| 49 | return @truncate(Hash.hash(0, span(self.string_bytes[@intFromEnum(a)..]))); | |
| 50 | } | |
| 51 | }; | |
| 52 | ||
| 53 | pub const SliceAdapter = struct { | |
| 54 | string_bytes: []const u8, | |
| 55 | ||
| 56 | pub fn eql(self: @This(), a_slice: []const u8, b: String, b_index: usize) bool { | |
| 57 | _ = b_index; | |
| 58 | const b_slice = span(self.string_bytes[@intFromEnum(b)..]); | |
| 59 | return std.mem.eql(u8, a_slice, b_slice); | |
| 60 | } | |
| 61 | pub fn hash(self: @This(), a: []const u8) u32 { | |
| 62 | _ = self; | |
| 63 | return @truncate(Hash.hash(0, a)); | |
| 64 | } | |
| 65 | }; | |
| 66 | }; | |
| 67 | ||
| 68 | pub const SourceLocation = extern struct { | |
| 69 | file: File.Index, | |
| 70 | line: u32, | |
| 71 | column: u32, | |
| 72 | ||
| 73 | pub const invalid: SourceLocation = .{ | |
| 74 | .file = .invalid, | |
| 75 | .line = 0, | |
| 76 | .column = 0, | |
| 77 | }; | |
| 78 | }; | |
| 79 | ||
| 80 | pub const File = extern struct { | |
| 81 | directory_index: u32, | |
| 82 | basename: String, | |
| 83 | ||
| 84 | pub const Index = enum(u32) { | |
| 85 | invalid = std.math.maxInt(u32), | |
| 86 | _, | |
| 87 | }; | |
| 88 | ||
| 89 | pub const MapContext = struct { | |
| 90 | string_bytes: []const u8, | |
| 91 | ||
| 92 | pub fn hash(self: MapContext, a: File) u32 { | |
| 93 | const a_basename = span(self.string_bytes[@intFromEnum(a.basename)..]); | |
| 94 | return @truncate(Hash.hash(a.directory_index, a_basename)); | |
| 95 | } | |
| 96 | ||
| 97 | pub fn eql(self: MapContext, a: File, b: File, b_index: usize) bool { | |
| 98 | _ = b_index; | |
| 99 | if (a.directory_index != b.directory_index) return false; | |
| 100 | const a_basename = span(self.string_bytes[@intFromEnum(a.basename)..]); | |
| 101 | const b_basename = span(self.string_bytes[@intFromEnum(b.basename)..]); | |
| 102 | return std.mem.eql(u8, a_basename, b_basename); | |
| 103 | } | |
| 104 | }; | |
| 105 | ||
| 106 | pub const SliceAdapter = struct { | |
| 107 | string_bytes: []const u8, | |
| 108 | ||
| 109 | pub const Entry = struct { | |
| 110 | directory_index: u32, | |
| 111 | basename: []const u8, | |
| 112 | }; | |
| 113 | ||
| 114 | pub fn hash(self: @This(), a: Entry) u32 { | |
| 115 | _ = self; | |
| 116 | return @truncate(Hash.hash(a.directory_index, a.basename)); | |
| 117 | } | |
| 118 | ||
| 119 | pub fn eql(self: @This(), a: Entry, b: File, b_index: usize) bool { | |
| 120 | _ = b_index; | |
| 121 | if (a.directory_index != b.directory_index) return false; | |
| 122 | const b_basename = span(self.string_bytes[@intFromEnum(b.basename)..]); | |
| 123 | return std.mem.eql(u8, a.basename, b_basename); | |
| 124 | } | |
| 125 | }; | |
| 126 | }; | |
| 127 | ||
| 128 | pub fn deinit(cov: *Coverage, gpa: Allocator) void { | |
| 129 | cov.directories.deinit(gpa); | |
| 130 | cov.files.deinit(gpa); | |
| 131 | cov.string_bytes.deinit(gpa); | |
| 132 | cov.* = undefined; | |
| 133 | } | |
| 134 | ||
| 135 | pub fn fileAt(cov: *Coverage, index: File.Index) *File { | |
| 136 | return &cov.files.keys()[@intFromEnum(index)]; | |
| 137 | } | |
| 138 | ||
| 139 | pub fn stringAt(cov: *Coverage, index: String) [:0]const u8 { | |
| 140 | return span(cov.string_bytes.items[@intFromEnum(index)..]); | |
| 141 | } | |
| 142 | ||
| 143 | pub const ResolveAddressesDwarfError = Dwarf.ScanError; | |
| 144 | ||
| 145 | pub fn resolveAddressesDwarf( | |
| 146 | cov: *Coverage, | |
| 147 | gpa: Allocator, | |
| 148 | sorted_pc_addrs: []const u64, | |
| 149 | /// Asserts its length equals length of `sorted_pc_addrs`. | |
| 150 | output: []SourceLocation, | |
| 151 | d: *Dwarf, | |
| 152 | ) ResolveAddressesDwarfError!void { | |
| 153 | assert(sorted_pc_addrs.len == output.len); | |
| 154 | assert(d.compile_units_sorted); | |
| 155 | ||
| 156 | var cu_i: usize = 0; | |
| 157 | var line_table_i: usize = 0; | |
| 158 | var cu: *Dwarf.CompileUnit = &d.compile_unit_list.items[0]; | |
| 159 | var range = cu.pc_range.?; | |
| 160 | // Protects directories and files tables from other threads. | |
| 161 | cov.mutex.lock(); | |
| 162 | defer cov.mutex.unlock(); | |
| 163 | next_pc: for (sorted_pc_addrs, output) |pc, *out| { | |
| 164 | while (pc >= range.end) { | |
| 165 | cu_i += 1; | |
| 166 | if (cu_i >= d.compile_unit_list.items.len) { | |
| 167 | out.* = SourceLocation.invalid; | |
| 168 | continue :next_pc; | |
| 169 | } | |
| 170 | cu = &d.compile_unit_list.items[cu_i]; | |
| 171 | line_table_i = 0; | |
| 172 | range = cu.pc_range orelse { | |
| 173 | out.* = SourceLocation.invalid; | |
| 174 | continue :next_pc; | |
| 175 | }; | |
| 176 | } | |
| 177 | if (pc < range.start) { | |
| 178 | out.* = SourceLocation.invalid; | |
| 179 | continue :next_pc; | |
| 180 | } | |
| 181 | if (line_table_i == 0) { | |
| 182 | line_table_i = 1; | |
| 183 | cov.mutex.unlock(); | |
| 184 | defer cov.mutex.lock(); | |
| 185 | d.populateSrcLocCache(gpa, cu) catch |err| switch (err) { | |
| 186 | error.MissingDebugInfo, error.InvalidDebugInfo => { | |
| 187 | out.* = SourceLocation.invalid; | |
| 188 | cu_i += 1; | |
| 189 | if (cu_i < d.compile_unit_list.items.len) { | |
| 190 | cu = &d.compile_unit_list.items[cu_i]; | |
| 191 | line_table_i = 0; | |
| 192 | if (cu.pc_range) |r| range = r; | |
| 193 | } | |
| 194 | continue :next_pc; | |
| 195 | }, | |
| 196 | else => |e| return e, | |
| 197 | }; | |
| 198 | } | |
| 199 | const slc = &cu.src_loc_cache.?; | |
| 200 | const table_addrs = slc.line_table.keys(); | |
| 201 | while (line_table_i < table_addrs.len and table_addrs[line_table_i] < pc) line_table_i += 1; | |
| 202 | ||
| 203 | const entry = slc.line_table.values()[line_table_i - 1]; | |
| 204 | const corrected_file_index = entry.file - @intFromBool(slc.version < 5); | |
| 205 | const file_entry = slc.files[corrected_file_index]; | |
| 206 | const dir_path = slc.directories[file_entry.dir_index].path; | |
| 207 | try cov.string_bytes.ensureUnusedCapacity(gpa, dir_path.len + file_entry.path.len + 2); | |
| 208 | const dir_gop = try cov.directories.getOrPutContextAdapted(gpa, dir_path, String.SliceAdapter{ | |
| 209 | .string_bytes = cov.string_bytes.items, | |
| 210 | }, String.MapContext{ | |
| 211 | .string_bytes = cov.string_bytes.items, | |
| 212 | }); | |
| 213 | if (!dir_gop.found_existing) | |
| 214 | dir_gop.key_ptr.* = addStringAssumeCapacity(cov, dir_path); | |
| 215 | const file_gop = try cov.files.getOrPutContextAdapted(gpa, File.SliceAdapter.Entry{ | |
| 216 | .directory_index = @intCast(dir_gop.index), | |
| 217 | .basename = file_entry.path, | |
| 218 | }, File.SliceAdapter{ | |
| 219 | .string_bytes = cov.string_bytes.items, | |
| 220 | }, File.MapContext{ | |
| 221 | .string_bytes = cov.string_bytes.items, | |
| 222 | }); | |
| 223 | if (!file_gop.found_existing) file_gop.key_ptr.* = .{ | |
| 224 | .directory_index = @intCast(dir_gop.index), | |
| 225 | .basename = addStringAssumeCapacity(cov, file_entry.path), | |
| 226 | }; | |
| 227 | out.* = .{ | |
| 228 | .file = @enumFromInt(file_gop.index), | |
| 229 | .line = entry.line, | |
| 230 | .column = entry.column, | |
| 231 | }; | |
| 232 | } | |
| 233 | } | |
| 234 | ||
| 235 | pub fn addStringAssumeCapacity(cov: *Coverage, s: []const u8) String { | |
| 236 | const result: String = @enumFromInt(cov.string_bytes.items.len); | |
| 237 | cov.string_bytes.appendSliceAssumeCapacity(s); | |
| 238 | cov.string_bytes.appendAssumeCapacity(0); | |
| 239 | return result; | |
| 240 | } | |
| 241 | ||
| 242 | fn span(s: []const u8) [:0]const u8 { | |
| 243 | return std.mem.sliceTo(@as([:0]const u8, @ptrCast(s)), 0); | |
| 244 | } |
lib/std/debug/Dwarf.zig+556-204| ... | ... | @@ -12,6 +12,8 @@ const native_endian = builtin.cpu.arch.endian(); |
| 12 | 12 | |
| 13 | 13 | const std = @import("../std.zig"); |
| 14 | 14 | const Allocator = std.mem.Allocator; |
| 15 | const elf = std.elf; | |
| 16 | const mem = std.mem; | |
| 15 | 17 | const DW = std.dwarf; |
| 16 | 18 | const AT = DW.AT; |
| 17 | 19 | const EH = DW.EH; |
| ... | ... | @@ -22,11 +24,10 @@ const UT = DW.UT; |
| 22 | 24 | const assert = std.debug.assert; |
| 23 | 25 | const cast = std.math.cast; |
| 24 | 26 | const maxInt = std.math.maxInt; |
| 25 | const readInt = std.mem.readInt; | |
| 26 | 27 | const MemoryAccessor = std.debug.MemoryAccessor; |
| 28 | const Path = std.Build.Cache.Path; | |
| 27 | 29 | |
| 28 | /// Did I mention this is deprecated? | |
| 29 | const DeprecatedFixedBufferReader = std.debug.DeprecatedFixedBufferReader; | |
| 30 | const FixedBufferReader = std.debug.FixedBufferReader; | |
| 30 | 31 | |
| 31 | 32 | const Dwarf = @This(); |
| 32 | 33 | |
| ... | ... | @@ -37,6 +38,7 @@ pub const call_frame = @import("Dwarf/call_frame.zig"); |
| 37 | 38 | endian: std.builtin.Endian, |
| 38 | 39 | sections: SectionArray = null_section_array, |
| 39 | 40 | is_macho: bool, |
| 41 | compile_units_sorted: bool, | |
| 40 | 42 | |
| 41 | 43 | // Filled later by the initializer |
| 42 | 44 | abbrev_table_list: std.ArrayListUnmanaged(Abbrev.Table) = .{}, |
| ... | ... | @@ -136,6 +138,34 @@ pub const CompileUnit = struct { |
| 136 | 138 | rnglists_base: usize, |
| 137 | 139 | loclists_base: usize, |
| 138 | 140 | frame_base: ?*const FormValue, |
| 141 | ||
| 142 | src_loc_cache: ?SrcLocCache, | |
| 143 | ||
| 144 | pub const SrcLocCache = struct { | |
| 145 | line_table: LineTable, | |
| 146 | directories: []const FileEntry, | |
| 147 | files: []FileEntry, | |
| 148 | version: u16, | |
| 149 | ||
| 150 | pub const LineTable = std.AutoArrayHashMapUnmanaged(u64, LineEntry); | |
| 151 | ||
| 152 | pub const LineEntry = struct { | |
| 153 | line: u32, | |
| 154 | column: u32, | |
| 155 | /// Offset by 1 depending on whether Dwarf version is >= 5. | |
| 156 | file: u32, | |
| 157 | }; | |
| 158 | ||
| 159 | pub fn findSource(slc: *const SrcLocCache, address: u64) !LineEntry { | |
| 160 | const index = std.sort.upperBound(u64, slc.line_table.keys(), address, struct { | |
| 161 | fn order(context: u64, item: u64) std.math.Order { | |
| 162 | return std.math.order(item, context); | |
| 163 | } | |
| 164 | }.order); | |
| 165 | if (index == 0) return missing(); | |
| 166 | return slc.line_table.values()[index - 1]; | |
| 167 | } | |
| 168 | }; | |
| 139 | 169 | }; |
| 140 | 170 | |
| 141 | 171 | pub const FormValue = union(enum) { |
| ... | ... | @@ -252,13 +282,13 @@ pub const Die = struct { |
| 252 | 282 | .@"32" => { |
| 253 | 283 | const byte_offset = compile_unit.str_offsets_base + 4 * index; |
| 254 | 284 | if (byte_offset + 4 > debug_str_offsets.len) return bad(); |
| 255 | const offset = readInt(u32, debug_str_offsets[byte_offset..][0..4], di.endian); | |
| 285 | const offset = mem.readInt(u32, debug_str_offsets[byte_offset..][0..4], di.endian); | |
| 256 | 286 | return getStringGeneric(opt_str, offset); |
| 257 | 287 | }, |
| 258 | 288 | .@"64" => { |
| 259 | 289 | const byte_offset = compile_unit.str_offsets_base + 8 * index; |
| 260 | 290 | if (byte_offset + 8 > debug_str_offsets.len) return bad(); |
| 261 | const offset = readInt(u64, debug_str_offsets[byte_offset..][0..8], di.endian); | |
| 291 | const offset = mem.readInt(u64, debug_str_offsets[byte_offset..][0..8], di.endian); | |
| 262 | 292 | return getStringGeneric(opt_str, offset); |
| 263 | 293 | }, |
| 264 | 294 | } |
| ... | ... | @@ -325,7 +355,7 @@ pub const ExceptionFrameHeader = struct { |
| 325 | 355 | var left: usize = 0; |
| 326 | 356 | var len: usize = self.fde_count; |
| 327 | 357 | |
| 328 | var fbr: DeprecatedFixedBufferReader = .{ .buf = self.entries, .endian = native_endian }; | |
| 358 | var fbr: FixedBufferReader = .{ .buf = self.entries, .endian = native_endian }; | |
| 329 | 359 | |
| 330 | 360 | while (len > 1) { |
| 331 | 361 | const mid = left + len / 2; |
| ... | ... | @@ -368,7 +398,7 @@ pub const ExceptionFrameHeader = struct { |
| 368 | 398 | const eh_frame = @as([*]const u8, @ptrFromInt(self.eh_frame_ptr))[0 .. eh_frame_len orelse maxInt(u32)]; |
| 369 | 399 | |
| 370 | 400 | const fde_offset = fde_ptr - self.eh_frame_ptr; |
| 371 | var eh_frame_fbr: DeprecatedFixedBufferReader = .{ | |
| 401 | var eh_frame_fbr: FixedBufferReader = .{ | |
| 372 | 402 | .buf = eh_frame, |
| 373 | 403 | .pos = fde_offset, |
| 374 | 404 | .endian = native_endian, |
| ... | ... | @@ -426,9 +456,9 @@ pub const EntryHeader = struct { |
| 426 | 456 | } |
| 427 | 457 | |
| 428 | 458 | /// Reads a header for either an FDE or a CIE, then advances the fbr to the position after the trailing structure. |
| 429 | /// `fbr` must be a DeprecatedFixedBufferReader backed by either the .eh_frame or .debug_frame sections. | |
| 459 | /// `fbr` must be a FixedBufferReader backed by either the .eh_frame or .debug_frame sections. | |
| 430 | 460 | pub fn read( |
| 431 | fbr: *DeprecatedFixedBufferReader, | |
| 461 | fbr: *FixedBufferReader, | |
| 432 | 462 | opt_ma: ?*MemoryAccessor, |
| 433 | 463 | dwarf_section: Section.Id, |
| 434 | 464 | ) !EntryHeader { |
| ... | ... | @@ -541,7 +571,7 @@ pub const CommonInformationEntry = struct { |
| 541 | 571 | ) !CommonInformationEntry { |
| 542 | 572 | if (addr_size_bytes > 8) return error.UnsupportedAddrSize; |
| 543 | 573 | |
| 544 | var fbr: DeprecatedFixedBufferReader = .{ .buf = cie_bytes, .endian = endian }; | |
| 574 | var fbr: FixedBufferReader = .{ .buf = cie_bytes, .endian = endian }; | |
| 545 | 575 | |
| 546 | 576 | const version = try fbr.readByte(); |
| 547 | 577 | switch (dwarf_section) { |
| ... | ... | @@ -675,7 +705,7 @@ pub const FrameDescriptionEntry = struct { |
| 675 | 705 | ) !FrameDescriptionEntry { |
| 676 | 706 | if (addr_size_bytes > 8) return error.InvalidAddrSize; |
| 677 | 707 | |
| 678 | var fbr: DeprecatedFixedBufferReader = .{ .buf = fde_bytes, .endian = endian }; | |
| 708 | var fbr: FixedBufferReader = .{ .buf = fde_bytes, .endian = endian }; | |
| 679 | 709 | |
| 680 | 710 | const pc_begin = try readEhPointer(&fbr, cie.fde_pointer_enc, addr_size_bytes, .{ |
| 681 | 711 | .pc_rel_base = try pcRelBase(@intFromPtr(&fde_bytes[fbr.pos]), pc_rel_offset), |
| ... | ... | @@ -721,12 +751,14 @@ const num_sections = std.enums.directEnumArrayLen(Section.Id, 0); |
| 721 | 751 | pub const SectionArray = [num_sections]?Section; |
| 722 | 752 | pub const null_section_array = [_]?Section{null} ** num_sections; |
| 723 | 753 | |
| 754 | pub const OpenError = ScanError; | |
| 755 | ||
| 724 | 756 | /// Initialize DWARF info. The caller has the responsibility to initialize most |
| 725 | 757 | /// the `Dwarf` fields before calling. `binary_mem` is the raw bytes of the |
| 726 | 758 | /// main binary file (not the secondary debug info file). |
| 727 | pub fn open(di: *Dwarf, allocator: Allocator) !void { | |
| 728 | try di.scanAllFunctions(allocator); | |
| 729 | try di.scanAllCompileUnits(allocator); | |
| 759 | pub fn open(d: *Dwarf, gpa: Allocator) OpenError!void { | |
| 760 | try d.scanAllFunctions(gpa); | |
| 761 | try d.scanAllCompileUnits(gpa); | |
| 730 | 762 | } |
| 731 | 763 | |
| 732 | 764 | const PcRange = struct { |
| ... | ... | @@ -747,21 +779,26 @@ pub fn sectionVirtualOffset(di: Dwarf, dwarf_section: Section.Id, base_address: |
| 747 | 779 | return if (di.sections[@intFromEnum(dwarf_section)]) |s| s.virtualOffset(base_address) else null; |
| 748 | 780 | } |
| 749 | 781 | |
| 750 | pub fn deinit(di: *Dwarf, allocator: Allocator) void { | |
| 782 | pub fn deinit(di: *Dwarf, gpa: Allocator) void { | |
| 751 | 783 | for (di.sections) |opt_section| { |
| 752 | if (opt_section) |s| if (s.owned) allocator.free(s.data); | |
| 784 | if (opt_section) |s| if (s.owned) gpa.free(s.data); | |
| 753 | 785 | } |
| 754 | 786 | for (di.abbrev_table_list.items) |*abbrev| { |
| 755 | abbrev.deinit(allocator); | |
| 787 | abbrev.deinit(gpa); | |
| 756 | 788 | } |
| 757 | di.abbrev_table_list.deinit(allocator); | |
| 789 | di.abbrev_table_list.deinit(gpa); | |
| 758 | 790 | for (di.compile_unit_list.items) |*cu| { |
| 759 | cu.die.deinit(allocator); | |
| 791 | if (cu.src_loc_cache) |*slc| { | |
| 792 | slc.line_table.deinit(gpa); | |
| 793 | gpa.free(slc.directories); | |
| 794 | gpa.free(slc.files); | |
| 795 | } | |
| 796 | cu.die.deinit(gpa); | |
| 760 | 797 | } |
| 761 | di.compile_unit_list.deinit(allocator); | |
| 762 | di.func_list.deinit(allocator); | |
| 763 | di.cie_map.deinit(allocator); | |
| 764 | di.fde_list.deinit(allocator); | |
| 798 | di.compile_unit_list.deinit(gpa); | |
| 799 | di.func_list.deinit(gpa); | |
| 800 | di.cie_map.deinit(gpa); | |
| 801 | di.fde_list.deinit(gpa); | |
| 765 | 802 | di.* = undefined; |
| 766 | 803 | } |
| 767 | 804 | |
| ... | ... | @@ -777,8 +814,13 @@ pub fn getSymbolName(di: *Dwarf, address: u64) ?[]const u8 { |
| 777 | 814 | return null; |
| 778 | 815 | } |
| 779 | 816 | |
| 780 | fn scanAllFunctions(di: *Dwarf, allocator: Allocator) !void { | |
| 781 | var fbr: DeprecatedFixedBufferReader = .{ .buf = di.section(.debug_info).?, .endian = di.endian }; | |
| 817 | pub const ScanError = error{ | |
| 818 | InvalidDebugInfo, | |
| 819 | MissingDebugInfo, | |
| 820 | } || Allocator.Error || std.debug.FixedBufferReader.Error; | |
| 821 | ||
| 822 | fn scanAllFunctions(di: *Dwarf, allocator: Allocator) ScanError!void { | |
| 823 | var fbr: FixedBufferReader = .{ .buf = di.section(.debug_info).?, .endian = di.endian }; | |
| 782 | 824 | var this_unit_offset: u64 = 0; |
| 783 | 825 | |
| 784 | 826 | while (this_unit_offset < fbr.buf.len) { |
| ... | ... | @@ -837,6 +879,7 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) !void { |
| 837 | 879 | .rnglists_base = 0, |
| 838 | 880 | .loclists_base = 0, |
| 839 | 881 | .frame_base = null, |
| 882 | .src_loc_cache = null, | |
| 840 | 883 | }; |
| 841 | 884 | |
| 842 | 885 | while (true) { |
| ... | ... | @@ -964,8 +1007,8 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) !void { |
| 964 | 1007 | } |
| 965 | 1008 | } |
| 966 | 1009 | |
| 967 | fn scanAllCompileUnits(di: *Dwarf, allocator: Allocator) !void { | |
| 968 | var fbr: DeprecatedFixedBufferReader = .{ .buf = di.section(.debug_info).?, .endian = di.endian }; | |
| 1010 | fn scanAllCompileUnits(di: *Dwarf, allocator: Allocator) ScanError!void { | |
| 1011 | var fbr: FixedBufferReader = .{ .buf = di.section(.debug_info).?, .endian = di.endian }; | |
| 969 | 1012 | var this_unit_offset: u64 = 0; |
| 970 | 1013 | |
| 971 | 1014 | var attrs_buf = std.ArrayList(Die.Attr).init(allocator); |
| ... | ... | @@ -1023,6 +1066,7 @@ fn scanAllCompileUnits(di: *Dwarf, allocator: Allocator) !void { |
| 1023 | 1066 | .rnglists_base = if (compile_unit_die.getAttr(AT.rnglists_base)) |fv| try fv.getUInt(usize) else 0, |
| 1024 | 1067 | .loclists_base = if (compile_unit_die.getAttr(AT.loclists_base)) |fv| try fv.getUInt(usize) else 0, |
| 1025 | 1068 | .frame_base = compile_unit_die.getAttr(AT.frame_base), |
| 1069 | .src_loc_cache = null, | |
| 1026 | 1070 | }; |
| 1027 | 1071 | |
| 1028 | 1072 | compile_unit.pc_range = x: { |
| ... | ... | @@ -1052,12 +1096,45 @@ fn scanAllCompileUnits(di: *Dwarf, allocator: Allocator) !void { |
| 1052 | 1096 | } |
| 1053 | 1097 | } |
| 1054 | 1098 | |
| 1099 | /// Populate missing PC ranges in compilation units, and then sort them by start address. | |
| 1100 | /// Does not guarantee pc_range to be non-null because there could be missing debug info. | |
| 1101 | pub fn sortCompileUnits(d: *Dwarf) ScanError!void { | |
| 1102 | assert(!d.compile_units_sorted); | |
| 1103 | ||
| 1104 | for (d.compile_unit_list.items) |*cu| { | |
| 1105 | if (cu.pc_range != null) continue; | |
| 1106 | const ranges_value = cu.die.getAttr(AT.ranges) orelse continue; | |
| 1107 | var iter = DebugRangeIterator.init(ranges_value, d, cu) catch continue; | |
| 1108 | var start: u64 = maxInt(u64); | |
| 1109 | var end: u64 = 0; | |
| 1110 | while (try iter.next()) |range| { | |
| 1111 | start = @min(start, range.start_addr); | |
| 1112 | end = @max(end, range.end_addr); | |
| 1113 | } | |
| 1114 | if (end != 0) cu.pc_range = .{ | |
| 1115 | .start = start, | |
| 1116 | .end = end, | |
| 1117 | }; | |
| 1118 | } | |
| 1119 | ||
| 1120 | std.mem.sortUnstable(CompileUnit, d.compile_unit_list.items, {}, struct { | |
| 1121 | pub fn lessThan(ctx: void, a: CompileUnit, b: CompileUnit) bool { | |
| 1122 | _ = ctx; | |
| 1123 | const a_range = a.pc_range orelse return false; | |
| 1124 | const b_range = b.pc_range orelse return true; | |
| 1125 | return a_range.start < b_range.start; | |
| 1126 | } | |
| 1127 | }.lessThan); | |
| 1128 | ||
| 1129 | d.compile_units_sorted = true; | |
| 1130 | } | |
| 1131 | ||
| 1055 | 1132 | const DebugRangeIterator = struct { |
| 1056 | 1133 | base_address: u64, |
| 1057 | 1134 | section_type: Section.Id, |
| 1058 | 1135 | di: *const Dwarf, |
| 1059 | 1136 | compile_unit: *const CompileUnit, |
| 1060 | fbr: DeprecatedFixedBufferReader, | |
| 1137 | fbr: FixedBufferReader, | |
| 1061 | 1138 | |
| 1062 | 1139 | pub fn init(ranges_value: *const FormValue, di: *const Dwarf, compile_unit: *const CompileUnit) !@This() { |
| 1063 | 1140 | const section_type = if (compile_unit.version >= 5) Section.Id.debug_rnglists else Section.Id.debug_ranges; |
| ... | ... | @@ -1070,13 +1147,13 @@ const DebugRangeIterator = struct { |
| 1070 | 1147 | .@"32" => { |
| 1071 | 1148 | const offset_loc = @as(usize, @intCast(compile_unit.rnglists_base + 4 * idx)); |
| 1072 | 1149 | if (offset_loc + 4 > debug_ranges.len) return bad(); |
| 1073 | const offset = readInt(u32, debug_ranges[offset_loc..][0..4], di.endian); | |
| 1150 | const offset = mem.readInt(u32, debug_ranges[offset_loc..][0..4], di.endian); | |
| 1074 | 1151 | break :off compile_unit.rnglists_base + offset; |
| 1075 | 1152 | }, |
| 1076 | 1153 | .@"64" => { |
| 1077 | 1154 | const offset_loc = @as(usize, @intCast(compile_unit.rnglists_base + 8 * idx)); |
| 1078 | 1155 | if (offset_loc + 8 > debug_ranges.len) return bad(); |
| 1079 | const offset = readInt(u64, debug_ranges[offset_loc..][0..8], di.endian); | |
| 1156 | const offset = mem.readInt(u64, debug_ranges[offset_loc..][0..8], di.endian); | |
| 1080 | 1157 | break :off compile_unit.rnglists_base + offset; |
| 1081 | 1158 | }, |
| 1082 | 1159 | } |
| ... | ... | @@ -1199,7 +1276,8 @@ const DebugRangeIterator = struct { |
| 1199 | 1276 | } |
| 1200 | 1277 | }; |
| 1201 | 1278 | |
| 1202 | pub fn findCompileUnit(di: *const Dwarf, target_address: u64) !*const CompileUnit { | |
| 1279 | /// TODO: change this to binary searching the sorted compile unit list | |
| 1280 | pub fn findCompileUnit(di: *const Dwarf, target_address: u64) !*CompileUnit { | |
| 1203 | 1281 | for (di.compile_unit_list.items) |*compile_unit| { |
| 1204 | 1282 | if (compile_unit.pc_range) |range| { |
| 1205 | 1283 | if (target_address >= range.start and target_address < range.end) return compile_unit; |
| ... | ... | @@ -1231,7 +1309,7 @@ fn getAbbrevTable(di: *Dwarf, allocator: Allocator, abbrev_offset: u64) !*const |
| 1231 | 1309 | } |
| 1232 | 1310 | |
| 1233 | 1311 | fn parseAbbrevTable(di: *Dwarf, allocator: Allocator, offset: u64) !Abbrev.Table { |
| 1234 | var fbr: DeprecatedFixedBufferReader = .{ | |
| 1312 | var fbr: FixedBufferReader = .{ | |
| 1235 | 1313 | .buf = di.section(.debug_abbrev).?, |
| 1236 | 1314 | .pos = cast(usize, offset) orelse return bad(), |
| 1237 | 1315 | .endian = di.endian, |
| ... | ... | @@ -1283,11 +1361,11 @@ fn parseAbbrevTable(di: *Dwarf, allocator: Allocator, offset: u64) !Abbrev.Table |
| 1283 | 1361 | } |
| 1284 | 1362 | |
| 1285 | 1363 | fn parseDie( |
| 1286 | fbr: *DeprecatedFixedBufferReader, | |
| 1364 | fbr: *FixedBufferReader, | |
| 1287 | 1365 | attrs_buf: []Die.Attr, |
| 1288 | 1366 | abbrev_table: *const Abbrev.Table, |
| 1289 | 1367 | format: Format, |
| 1290 | ) !?Die { | |
| 1368 | ) ScanError!?Die { | |
| 1291 | 1369 | const abbrev_code = try fbr.readUleb128(u64); |
| 1292 | 1370 | if (abbrev_code == 0) return null; |
| 1293 | 1371 | const table_entry = abbrev_table.get(abbrev_code) orelse return bad(); |
| ... | ... | @@ -1309,34 +1387,36 @@ fn parseDie( |
| 1309 | 1387 | }; |
| 1310 | 1388 | } |
| 1311 | 1389 | |
| 1312 | pub fn getLineNumberInfo( | |
| 1313 | di: *Dwarf, | |
| 1314 | allocator: Allocator, | |
| 1315 | compile_unit: CompileUnit, | |
| 1316 | target_address: u64, | |
| 1317 | ) !std.debug.SourceLocation { | |
| 1318 | const compile_unit_cwd = try compile_unit.die.getAttrString(di, AT.comp_dir, di.section(.debug_line_str), compile_unit); | |
| 1390 | fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) !CompileUnit.SrcLocCache { | |
| 1391 | const compile_unit_cwd = try compile_unit.die.getAttrString(d, AT.comp_dir, d.section(.debug_line_str), compile_unit.*); | |
| 1319 | 1392 | const line_info_offset = try compile_unit.die.getAttrSecOffset(AT.stmt_list); |
| 1320 | 1393 | |
| 1321 | var fbr: DeprecatedFixedBufferReader = .{ .buf = di.section(.debug_line).?, .endian = di.endian }; | |
| 1394 | var fbr: FixedBufferReader = .{ | |
| 1395 | .buf = d.section(.debug_line).?, | |
| 1396 | .endian = d.endian, | |
| 1397 | }; | |
| 1322 | 1398 | try fbr.seekTo(line_info_offset); |
| 1323 | 1399 | |
| 1324 | 1400 | const unit_header = try readUnitHeader(&fbr, null); |
| 1325 | 1401 | if (unit_header.unit_length == 0) return missing(); |
| 1402 | ||
| 1326 | 1403 | const next_offset = unit_header.header_length + unit_header.unit_length; |
| 1327 | 1404 | |
| 1328 | 1405 | const version = try fbr.readInt(u16); |
| 1329 | 1406 | if (version < 2) return bad(); |
| 1330 | 1407 | |
| 1331 | var addr_size: u8 = switch (unit_header.format) { | |
| 1332 | .@"32" => 4, | |
| 1333 | .@"64" => 8, | |
| 1408 | const addr_size: u8, const seg_size: u8 = if (version >= 5) .{ | |
| 1409 | try fbr.readByte(), | |
| 1410 | try fbr.readByte(), | |
| 1411 | } else .{ | |
| 1412 | switch (unit_header.format) { | |
| 1413 | .@"32" => 4, | |
| 1414 | .@"64" => 8, | |
| 1415 | }, | |
| 1416 | 0, | |
| 1334 | 1417 | }; |
| 1335 | var seg_size: u8 = 0; | |
| 1336 | if (version >= 5) { | |
| 1337 | addr_size = try fbr.readByte(); | |
| 1338 | seg_size = try fbr.readByte(); | |
| 1339 | } | |
| 1418 | _ = addr_size; | |
| 1419 | _ = seg_size; | |
| 1340 | 1420 | |
| 1341 | 1421 | const prologue_length = try fbr.readAddress(unit_header.format); |
| 1342 | 1422 | const prog_start_offset = fbr.pos + prologue_length; |
| ... | ... | @@ -1345,8 +1425,8 @@ pub fn getLineNumberInfo( |
| 1345 | 1425 | if (minimum_instruction_length == 0) return bad(); |
| 1346 | 1426 | |
| 1347 | 1427 | if (version >= 4) { |
| 1348 | // maximum_operations_per_instruction | |
| 1349 | _ = try fbr.readByte(); | |
| 1428 | const maximum_operations_per_instruction = try fbr.readByte(); | |
| 1429 | _ = maximum_operations_per_instruction; | |
| 1350 | 1430 | } |
| 1351 | 1431 | |
| 1352 | 1432 | const default_is_stmt = (try fbr.readByte()) != 0; |
| ... | ... | @@ -1359,18 +1439,18 @@ pub fn getLineNumberInfo( |
| 1359 | 1439 | |
| 1360 | 1440 | const standard_opcode_lengths = try fbr.readBytes(opcode_base - 1); |
| 1361 | 1441 | |
| 1362 | var include_directories = std.ArrayList(FileEntry).init(allocator); | |
| 1363 | defer include_directories.deinit(); | |
| 1364 | var file_entries = std.ArrayList(FileEntry).init(allocator); | |
| 1365 | defer file_entries.deinit(); | |
| 1442 | var directories: std.ArrayListUnmanaged(FileEntry) = .{}; | |
| 1443 | defer directories.deinit(gpa); | |
| 1444 | var file_entries: std.ArrayListUnmanaged(FileEntry) = .{}; | |
| 1445 | defer file_entries.deinit(gpa); | |
| 1366 | 1446 | |
| 1367 | 1447 | if (version < 5) { |
| 1368 | try include_directories.append(.{ .path = compile_unit_cwd }); | |
| 1448 | try directories.append(gpa, .{ .path = compile_unit_cwd }); | |
| 1369 | 1449 | |
| 1370 | 1450 | while (true) { |
| 1371 | 1451 | const dir = try fbr.readBytesTo(0); |
| 1372 | 1452 | if (dir.len == 0) break; |
| 1373 | try include_directories.append(.{ .path = dir }); | |
| 1453 | try directories.append(gpa, .{ .path = dir }); | |
| 1374 | 1454 | } |
| 1375 | 1455 | |
| 1376 | 1456 | while (true) { |
| ... | ... | @@ -1379,7 +1459,7 @@ pub fn getLineNumberInfo( |
| 1379 | 1459 | const dir_index = try fbr.readUleb128(u32); |
| 1380 | 1460 | const mtime = try fbr.readUleb128(u64); |
| 1381 | 1461 | const size = try fbr.readUleb128(u64); |
| 1382 | try file_entries.append(.{ | |
| 1462 | try file_entries.append(gpa, .{ | |
| 1383 | 1463 | .path = file_name, |
| 1384 | 1464 | .dir_index = dir_index, |
| 1385 | 1465 | .mtime = mtime, |
| ... | ... | @@ -1403,52 +1483,10 @@ pub fn getLineNumberInfo( |
| 1403 | 1483 | } |
| 1404 | 1484 | |
| 1405 | 1485 | const directories_count = try fbr.readUleb128(usize); |
| 1406 | try include_directories.ensureUnusedCapacity(directories_count); | |
| 1407 | { | |
| 1408 | var i: usize = 0; | |
| 1409 | while (i < directories_count) : (i += 1) { | |
| 1410 | var e: FileEntry = .{ .path = &.{} }; | |
| 1411 | for (dir_ent_fmt_buf[0..directory_entry_format_count]) |ent_fmt| { | |
| 1412 | const form_value = try parseFormValue( | |
| 1413 | &fbr, | |
| 1414 | ent_fmt.form_code, | |
| 1415 | unit_header.format, | |
| 1416 | null, | |
| 1417 | ); | |
| 1418 | switch (ent_fmt.content_type_code) { | |
| 1419 | DW.LNCT.path => e.path = try form_value.getString(di.*), | |
| 1420 | DW.LNCT.directory_index => e.dir_index = try form_value.getUInt(u32), | |
| 1421 | DW.LNCT.timestamp => e.mtime = try form_value.getUInt(u64), | |
| 1422 | DW.LNCT.size => e.size = try form_value.getUInt(u64), | |
| 1423 | DW.LNCT.MD5 => e.md5 = switch (form_value) { | |
| 1424 | .data16 => |data16| data16.*, | |
| 1425 | else => return bad(), | |
| 1426 | }, | |
| 1427 | else => continue, | |
| 1428 | } | |
| 1429 | } | |
| 1430 | include_directories.appendAssumeCapacity(e); | |
| 1431 | } | |
| 1432 | } | |
| 1433 | } | |
| 1434 | ||
| 1435 | var file_ent_fmt_buf: [10]FileEntFmt = undefined; | |
| 1436 | const file_name_entry_format_count = try fbr.readByte(); | |
| 1437 | if (file_name_entry_format_count > file_ent_fmt_buf.len) return bad(); | |
| 1438 | for (file_ent_fmt_buf[0..file_name_entry_format_count]) |*ent_fmt| { | |
| 1439 | ent_fmt.* = .{ | |
| 1440 | .content_type_code = try fbr.readUleb128(u8), | |
| 1441 | .form_code = try fbr.readUleb128(u16), | |
| 1442 | }; | |
| 1443 | } | |
| 1444 | 1486 | |
| 1445 | const file_names_count = try fbr.readUleb128(usize); | |
| 1446 | try file_entries.ensureUnusedCapacity(file_names_count); | |
| 1447 | { | |
| 1448 | var i: usize = 0; | |
| 1449 | while (i < file_names_count) : (i += 1) { | |
| 1450 | var e: FileEntry = .{ .path = &.{} }; | |
| 1451 | for (file_ent_fmt_buf[0..file_name_entry_format_count]) |ent_fmt| { | |
| 1487 | for (try directories.addManyAsSlice(gpa, directories_count)) |*e| { | |
| 1488 | e.* = .{ .path = &.{} }; | |
| 1489 | for (dir_ent_fmt_buf[0..directory_entry_format_count]) |ent_fmt| { | |
| 1452 | 1490 | const form_value = try parseFormValue( |
| 1453 | 1491 | &fbr, |
| 1454 | 1492 | ent_fmt.form_code, |
| ... | ... | @@ -1456,7 +1494,7 @@ pub fn getLineNumberInfo( |
| 1456 | 1494 | null, |
| 1457 | 1495 | ); |
| 1458 | 1496 | switch (ent_fmt.content_type_code) { |
| 1459 | DW.LNCT.path => e.path = try form_value.getString(di.*), | |
| 1497 | DW.LNCT.path => e.path = try form_value.getString(d.*), | |
| 1460 | 1498 | DW.LNCT.directory_index => e.dir_index = try form_value.getUInt(u32), |
| 1461 | 1499 | DW.LNCT.timestamp => e.mtime = try form_value.getUInt(u64), |
| 1462 | 1500 | DW.LNCT.size => e.size = try form_value.getUInt(u64), |
| ... | ... | @@ -1467,17 +1505,49 @@ pub fn getLineNumberInfo( |
| 1467 | 1505 | else => continue, |
| 1468 | 1506 | } |
| 1469 | 1507 | } |
| 1470 | file_entries.appendAssumeCapacity(e); | |
| 1508 | } | |
| 1509 | } | |
| 1510 | ||
| 1511 | var file_ent_fmt_buf: [10]FileEntFmt = undefined; | |
| 1512 | const file_name_entry_format_count = try fbr.readByte(); | |
| 1513 | if (file_name_entry_format_count > file_ent_fmt_buf.len) return bad(); | |
| 1514 | for (file_ent_fmt_buf[0..file_name_entry_format_count]) |*ent_fmt| { | |
| 1515 | ent_fmt.* = .{ | |
| 1516 | .content_type_code = try fbr.readUleb128(u8), | |
| 1517 | .form_code = try fbr.readUleb128(u16), | |
| 1518 | }; | |
| 1519 | } | |
| 1520 | ||
| 1521 | const file_names_count = try fbr.readUleb128(usize); | |
| 1522 | try file_entries.ensureUnusedCapacity(gpa, file_names_count); | |
| 1523 | ||
| 1524 | for (try file_entries.addManyAsSlice(gpa, file_names_count)) |*e| { | |
| 1525 | e.* = .{ .path = &.{} }; | |
| 1526 | for (file_ent_fmt_buf[0..file_name_entry_format_count]) |ent_fmt| { | |
| 1527 | const form_value = try parseFormValue( | |
| 1528 | &fbr, | |
| 1529 | ent_fmt.form_code, | |
| 1530 | unit_header.format, | |
| 1531 | null, | |
| 1532 | ); | |
| 1533 | switch (ent_fmt.content_type_code) { | |
| 1534 | DW.LNCT.path => e.path = try form_value.getString(d.*), | |
| 1535 | DW.LNCT.directory_index => e.dir_index = try form_value.getUInt(u32), | |
| 1536 | DW.LNCT.timestamp => e.mtime = try form_value.getUInt(u64), | |
| 1537 | DW.LNCT.size => e.size = try form_value.getUInt(u64), | |
| 1538 | DW.LNCT.MD5 => e.md5 = switch (form_value) { | |
| 1539 | .data16 => |data16| data16.*, | |
| 1540 | else => return bad(), | |
| 1541 | }, | |
| 1542 | else => continue, | |
| 1543 | } | |
| 1471 | 1544 | } |
| 1472 | 1545 | } |
| 1473 | 1546 | } |
| 1474 | 1547 | |
| 1475 | var prog = LineNumberProgram.init( | |
| 1476 | default_is_stmt, | |
| 1477 | include_directories.items, | |
| 1478 | target_address, | |
| 1479 | version, | |
| 1480 | ); | |
| 1548 | var prog = LineNumberProgram.init(default_is_stmt, version); | |
| 1549 | var line_table: CompileUnit.SrcLocCache.LineTable = .{}; | |
| 1550 | errdefer line_table.deinit(gpa); | |
| 1481 | 1551 | |
| 1482 | 1552 | try fbr.seekTo(prog_start_offset); |
| 1483 | 1553 | |
| ... | ... | @@ -1493,7 +1563,7 @@ pub fn getLineNumberInfo( |
| 1493 | 1563 | switch (sub_op) { |
| 1494 | 1564 | DW.LNE.end_sequence => { |
| 1495 | 1565 | prog.end_sequence = true; |
| 1496 | if (try prog.checkLineMatch(allocator, file_entries.items)) |info| return info; | |
| 1566 | try prog.addRow(gpa, &line_table); | |
| 1497 | 1567 | prog.reset(); |
| 1498 | 1568 | }, |
| 1499 | 1569 | DW.LNE.set_address => { |
| ... | ... | @@ -1505,7 +1575,7 @@ pub fn getLineNumberInfo( |
| 1505 | 1575 | const dir_index = try fbr.readUleb128(u32); |
| 1506 | 1576 | const mtime = try fbr.readUleb128(u64); |
| 1507 | 1577 | const size = try fbr.readUleb128(u64); |
| 1508 | try file_entries.append(.{ | |
| 1578 | try file_entries.append(gpa, .{ | |
| 1509 | 1579 | .path = path, |
| 1510 | 1580 | .dir_index = dir_index, |
| 1511 | 1581 | .mtime = mtime, |
| ... | ... | @@ -1521,12 +1591,12 @@ pub fn getLineNumberInfo( |
| 1521 | 1591 | const inc_line = @as(i32, line_base) + @as(i32, adjusted_opcode % line_range); |
| 1522 | 1592 | prog.line += inc_line; |
| 1523 | 1593 | prog.address += inc_addr; |
| 1524 | if (try prog.checkLineMatch(allocator, file_entries.items)) |info| return info; | |
| 1594 | try prog.addRow(gpa, &line_table); | |
| 1525 | 1595 | prog.basic_block = false; |
| 1526 | 1596 | } else { |
| 1527 | 1597 | switch (opcode) { |
| 1528 | 1598 | DW.LNS.copy => { |
| 1529 | if (try prog.checkLineMatch(allocator, file_entries.items)) |info| return info; | |
| 1599 | try prog.addRow(gpa, &line_table); | |
| 1530 | 1600 | prog.basic_block = false; |
| 1531 | 1601 | }, |
| 1532 | 1602 | DW.LNS.advance_pc => { |
| ... | ... | @@ -1568,7 +1638,39 @@ pub fn getLineNumberInfo( |
| 1568 | 1638 | } |
| 1569 | 1639 | } |
| 1570 | 1640 | |
| 1571 | return missing(); | |
| 1641 | return .{ | |
| 1642 | .line_table = line_table, | |
| 1643 | .directories = try directories.toOwnedSlice(gpa), | |
| 1644 | .files = try file_entries.toOwnedSlice(gpa), | |
| 1645 | .version = version, | |
| 1646 | }; | |
| 1647 | } | |
| 1648 | ||
| 1649 | pub fn populateSrcLocCache(d: *Dwarf, gpa: Allocator, cu: *CompileUnit) ScanError!void { | |
| 1650 | if (cu.src_loc_cache != null) return; | |
| 1651 | cu.src_loc_cache = try runLineNumberProgram(d, gpa, cu); | |
| 1652 | } | |
| 1653 | ||
| 1654 | pub fn getLineNumberInfo( | |
| 1655 | d: *Dwarf, | |
| 1656 | gpa: Allocator, | |
| 1657 | compile_unit: *CompileUnit, | |
| 1658 | target_address: u64, | |
| 1659 | ) !std.debug.SourceLocation { | |
| 1660 | try populateSrcLocCache(d, gpa, compile_unit); | |
| 1661 | const slc = &compile_unit.src_loc_cache.?; | |
| 1662 | const entry = try slc.findSource(target_address); | |
| 1663 | const file_index = entry.file - @intFromBool(slc.version < 5); | |
| 1664 | if (file_index >= slc.files.len) return bad(); | |
| 1665 | const file_entry = &slc.files[file_index]; | |
| 1666 | if (file_entry.dir_index >= slc.directories.len) return bad(); | |
| 1667 | const dir_name = slc.directories[file_entry.dir_index].path; | |
| 1668 | const file_name = try std.fs.path.join(gpa, &.{ dir_name, file_entry.path }); | |
| 1669 | return .{ | |
| 1670 | .line = entry.line, | |
| 1671 | .column = entry.column, | |
| 1672 | .file_name = file_name, | |
| 1673 | }; | |
| 1572 | 1674 | } |
| 1573 | 1675 | |
| 1574 | 1676 | fn getString(di: Dwarf, offset: u64) ![:0]const u8 { |
| ... | ... | @@ -1588,7 +1690,7 @@ fn readDebugAddr(di: Dwarf, compile_unit: CompileUnit, index: u64) !u64 { |
| 1588 | 1690 | // The header is 8 or 12 bytes depending on is_64. |
| 1589 | 1691 | if (compile_unit.addr_base < 8) return bad(); |
| 1590 | 1692 | |
| 1591 | const version = readInt(u16, debug_addr[compile_unit.addr_base - 4 ..][0..2], di.endian); | |
| 1693 | const version = mem.readInt(u16, debug_addr[compile_unit.addr_base - 4 ..][0..2], di.endian); | |
| 1592 | 1694 | if (version != 5) return bad(); |
| 1593 | 1695 | |
| 1594 | 1696 | const addr_size = debug_addr[compile_unit.addr_base - 2]; |
| ... | ... | @@ -1598,9 +1700,9 @@ fn readDebugAddr(di: Dwarf, compile_unit: CompileUnit, index: u64) !u64 { |
| 1598 | 1700 | if (byte_offset + addr_size > debug_addr.len) return bad(); |
| 1599 | 1701 | return switch (addr_size) { |
| 1600 | 1702 | 1 => debug_addr[byte_offset], |
| 1601 | 2 => readInt(u16, debug_addr[byte_offset..][0..2], di.endian), | |
| 1602 | 4 => readInt(u32, debug_addr[byte_offset..][0..4], di.endian), | |
| 1603 | 8 => readInt(u64, debug_addr[byte_offset..][0..8], di.endian), | |
| 1703 | 2 => mem.readInt(u16, debug_addr[byte_offset..][0..2], di.endian), | |
| 1704 | 4 => mem.readInt(u32, debug_addr[byte_offset..][0..4], di.endian), | |
| 1705 | 8 => mem.readInt(u64, debug_addr[byte_offset..][0..8], di.endian), | |
| 1604 | 1706 | else => bad(), |
| 1605 | 1707 | }; |
| 1606 | 1708 | } |
| ... | ... | @@ -1611,7 +1713,7 @@ fn readDebugAddr(di: Dwarf, compile_unit: CompileUnit, index: u64) !u64 { |
| 1611 | 1713 | /// of FDEs is built for binary searching during unwinding. |
| 1612 | 1714 | pub fn scanAllUnwindInfo(di: *Dwarf, allocator: Allocator, base_address: usize) !void { |
| 1613 | 1715 | if (di.section(.eh_frame_hdr)) |eh_frame_hdr| blk: { |
| 1614 | var fbr: DeprecatedFixedBufferReader = .{ .buf = eh_frame_hdr, .endian = native_endian }; | |
| 1716 | var fbr: FixedBufferReader = .{ .buf = eh_frame_hdr, .endian = native_endian }; | |
| 1615 | 1717 | |
| 1616 | 1718 | const version = try fbr.readByte(); |
| 1617 | 1719 | if (version != 1) break :blk; |
| ... | ... | @@ -1651,7 +1753,7 @@ pub fn scanAllUnwindInfo(di: *Dwarf, allocator: Allocator, base_address: usize) |
| 1651 | 1753 | const frame_sections = [2]Section.Id{ .eh_frame, .debug_frame }; |
| 1652 | 1754 | for (frame_sections) |frame_section| { |
| 1653 | 1755 | if (di.section(frame_section)) |section_data| { |
| 1654 | var fbr: DeprecatedFixedBufferReader = .{ .buf = section_data, .endian = di.endian }; | |
| 1756 | var fbr: FixedBufferReader = .{ .buf = section_data, .endian = di.endian }; | |
| 1655 | 1757 | while (fbr.pos < fbr.buf.len) { |
| 1656 | 1758 | const entry_header = try EntryHeader.read(&fbr, null, frame_section); |
| 1657 | 1759 | switch (entry_header.type) { |
| ... | ... | @@ -1695,11 +1797,11 @@ pub fn scanAllUnwindInfo(di: *Dwarf, allocator: Allocator, base_address: usize) |
| 1695 | 1797 | } |
| 1696 | 1798 | |
| 1697 | 1799 | fn parseFormValue( |
| 1698 | fbr: *DeprecatedFixedBufferReader, | |
| 1800 | fbr: *FixedBufferReader, | |
| 1699 | 1801 | form_id: u64, |
| 1700 | 1802 | format: Format, |
| 1701 | 1803 | implicit_const: ?i64, |
| 1702 | ) anyerror!FormValue { | |
| 1804 | ) ScanError!FormValue { | |
| 1703 | 1805 | return switch (form_id) { |
| 1704 | 1806 | FORM.addr => .{ .addr = try fbr.readAddress(switch (@bitSizeOf(usize)) { |
| 1705 | 1807 | 32 => .@"32", |
| ... | ... | @@ -1783,17 +1885,6 @@ const LineNumberProgram = struct { |
| 1783 | 1885 | end_sequence: bool, |
| 1784 | 1886 | |
| 1785 | 1887 | default_is_stmt: bool, |
| 1786 | target_address: u64, | |
| 1787 | include_dirs: []const FileEntry, | |
| 1788 | ||
| 1789 | prev_valid: bool, | |
| 1790 | prev_address: u64, | |
| 1791 | prev_file: usize, | |
| 1792 | prev_line: i64, | |
| 1793 | prev_column: u64, | |
| 1794 | prev_is_stmt: bool, | |
| 1795 | prev_basic_block: bool, | |
| 1796 | prev_end_sequence: bool, | |
| 1797 | 1888 | |
| 1798 | 1889 | // Reset the state machine following the DWARF specification |
| 1799 | 1890 | pub fn reset(self: *LineNumberProgram) void { |
| ... | ... | @@ -1804,24 +1895,10 @@ const LineNumberProgram = struct { |
| 1804 | 1895 | self.is_stmt = self.default_is_stmt; |
| 1805 | 1896 | self.basic_block = false; |
| 1806 | 1897 | self.end_sequence = false; |
| 1807 | // Invalidate all the remaining fields | |
| 1808 | self.prev_valid = false; | |
| 1809 | self.prev_address = 0; | |
| 1810 | self.prev_file = undefined; | |
| 1811 | self.prev_line = undefined; | |
| 1812 | self.prev_column = undefined; | |
| 1813 | self.prev_is_stmt = undefined; | |
| 1814 | self.prev_basic_block = undefined; | |
| 1815 | self.prev_end_sequence = undefined; | |
| 1816 | 1898 | } |
| 1817 | 1899 | |
| 1818 | pub fn init( | |
| 1819 | is_stmt: bool, | |
| 1820 | include_dirs: []const FileEntry, | |
| 1821 | target_address: u64, | |
| 1822 | version: u16, | |
| 1823 | ) LineNumberProgram { | |
| 1824 | return LineNumberProgram{ | |
| 1900 | pub fn init(is_stmt: bool, version: u16) LineNumberProgram { | |
| 1901 | return .{ | |
| 1825 | 1902 | .address = 0, |
| 1826 | 1903 | .file = 1, |
| 1827 | 1904 | .line = 1, |
| ... | ... | @@ -1830,60 +1907,17 @@ const LineNumberProgram = struct { |
| 1830 | 1907 | .is_stmt = is_stmt, |
| 1831 | 1908 | .basic_block = false, |
| 1832 | 1909 | .end_sequence = false, |
| 1833 | .include_dirs = include_dirs, | |
| 1834 | 1910 | .default_is_stmt = is_stmt, |
| 1835 | .target_address = target_address, | |
| 1836 | .prev_valid = false, | |
| 1837 | .prev_address = 0, | |
| 1838 | .prev_file = undefined, | |
| 1839 | .prev_line = undefined, | |
| 1840 | .prev_column = undefined, | |
| 1841 | .prev_is_stmt = undefined, | |
| 1842 | .prev_basic_block = undefined, | |
| 1843 | .prev_end_sequence = undefined, | |
| 1844 | 1911 | }; |
| 1845 | 1912 | } |
| 1846 | 1913 | |
| 1847 | pub fn checkLineMatch( | |
| 1848 | self: *LineNumberProgram, | |
| 1849 | allocator: Allocator, | |
| 1850 | file_entries: []const FileEntry, | |
| 1851 | ) !?std.debug.SourceLocation { | |
| 1852 | if (self.prev_valid and | |
| 1853 | self.target_address >= self.prev_address and | |
| 1854 | self.target_address < self.address) | |
| 1855 | { | |
| 1856 | const file_index = if (self.version >= 5) self.prev_file else i: { | |
| 1857 | if (self.prev_file == 0) return missing(); | |
| 1858 | break :i self.prev_file - 1; | |
| 1859 | }; | |
| 1860 | ||
| 1861 | if (file_index >= file_entries.len) return bad(); | |
| 1862 | const file_entry = &file_entries[file_index]; | |
| 1863 | ||
| 1864 | if (file_entry.dir_index >= self.include_dirs.len) return bad(); | |
| 1865 | const dir_name = self.include_dirs[file_entry.dir_index].path; | |
| 1866 | ||
| 1867 | const file_name = try std.fs.path.join(allocator, &[_][]const u8{ | |
| 1868 | dir_name, file_entry.path, | |
| 1869 | }); | |
| 1870 | ||
| 1871 | return std.debug.SourceLocation{ | |
| 1872 | .line = if (self.prev_line >= 0) @as(u64, @intCast(self.prev_line)) else 0, | |
| 1873 | .column = self.prev_column, | |
| 1874 | .file_name = file_name, | |
| 1875 | }; | |
| 1876 | } | |
| 1877 | ||
| 1878 | self.prev_valid = true; | |
| 1879 | self.prev_address = self.address; | |
| 1880 | self.prev_file = self.file; | |
| 1881 | self.prev_line = self.line; | |
| 1882 | self.prev_column = self.column; | |
| 1883 | self.prev_is_stmt = self.is_stmt; | |
| 1884 | self.prev_basic_block = self.basic_block; | |
| 1885 | self.prev_end_sequence = self.end_sequence; | |
| 1886 | return null; | |
| 1914 | pub fn addRow(prog: *LineNumberProgram, gpa: Allocator, table: *CompileUnit.SrcLocCache.LineTable) !void { | |
| 1915 | if (prog.line == 0) return; // garbage data | |
| 1916 | try table.put(gpa, prog.address, .{ | |
| 1917 | .line = cast(u32, prog.line) orelse maxInt(u32), | |
| 1918 | .column = cast(u32, prog.column) orelse maxInt(u32), | |
| 1919 | .file = cast(u32, prog.file) orelse return bad(), | |
| 1920 | }); | |
| 1887 | 1921 | } |
| 1888 | 1922 | }; |
| 1889 | 1923 | |
| ... | ... | @@ -1892,7 +1926,8 @@ const UnitHeader = struct { |
| 1892 | 1926 | header_length: u4, |
| 1893 | 1927 | unit_length: u64, |
| 1894 | 1928 | }; |
| 1895 | fn readUnitHeader(fbr: *DeprecatedFixedBufferReader, opt_ma: ?*MemoryAccessor) !UnitHeader { | |
| 1929 | ||
| 1930 | fn readUnitHeader(fbr: *FixedBufferReader, opt_ma: ?*MemoryAccessor) ScanError!UnitHeader { | |
| 1896 | 1931 | return switch (try if (opt_ma) |ma| fbr.readIntChecked(u32, ma) else fbr.readInt(u32)) { |
| 1897 | 1932 | 0...0xfffffff0 - 1 => |unit_length| .{ |
| 1898 | 1933 | .format = .@"32", |
| ... | ... | @@ -1957,7 +1992,7 @@ const EhPointerContext = struct { |
| 1957 | 1992 | text_rel_base: ?u64 = null, |
| 1958 | 1993 | function_rel_base: ?u64 = null, |
| 1959 | 1994 | }; |
| 1960 | fn readEhPointer(fbr: *DeprecatedFixedBufferReader, enc: u8, addr_size_bytes: u8, ctx: EhPointerContext) !?u64 { | |
| 1995 | fn readEhPointer(fbr: *FixedBufferReader, enc: u8, addr_size_bytes: u8, ctx: EhPointerContext) !?u64 { | |
| 1961 | 1996 | if (enc == EH.PE.omit) return null; |
| 1962 | 1997 | |
| 1963 | 1998 | const value: union(enum) { |
| ... | ... | @@ -2023,3 +2058,320 @@ fn pcRelBase(field_ptr: usize, pc_rel_offset: i64) !usize { |
| 2023 | 2058 | return std.math.add(usize, field_ptr, @as(usize, @intCast(pc_rel_offset))); |
| 2024 | 2059 | } |
| 2025 | 2060 | } |
| 2061 | ||
| 2062 | pub const ElfModule = struct { | |
| 2063 | base_address: usize, | |
| 2064 | dwarf: Dwarf, | |
| 2065 | mapped_memory: []align(std.mem.page_size) const u8, | |
| 2066 | external_mapped_memory: ?[]align(std.mem.page_size) const u8, | |
| 2067 | ||
| 2068 | pub fn deinit(self: *@This(), allocator: Allocator) void { | |
| 2069 | self.dwarf.deinit(allocator); | |
| 2070 | std.posix.munmap(self.mapped_memory); | |
| 2071 | if (self.external_mapped_memory) |m| std.posix.munmap(m); | |
| 2072 | } | |
| 2073 | ||
| 2074 | pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !std.debug.Symbol { | |
| 2075 | // Translate the VA into an address into this object | |
| 2076 | const relocated_address = address - self.base_address; | |
| 2077 | return self.dwarf.getSymbol(allocator, relocated_address); | |
| 2078 | } | |
| 2079 | ||
| 2080 | pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*const Dwarf { | |
| 2081 | _ = allocator; | |
| 2082 | _ = address; | |
| 2083 | return &self.dwarf; | |
| 2084 | } | |
| 2085 | ||
| 2086 | pub const LoadError = error{ | |
| 2087 | InvalidDebugInfo, | |
| 2088 | MissingDebugInfo, | |
| 2089 | InvalidElfMagic, | |
| 2090 | InvalidElfVersion, | |
| 2091 | InvalidElfEndian, | |
| 2092 | /// TODO: implement this and then remove this error code | |
| 2093 | UnimplementedDwarfForeignEndian, | |
| 2094 | /// The debug info may be valid but this implementation uses memory | |
| 2095 | /// mapping which limits things to usize. If the target debug info is | |
| 2096 | /// 64-bit and host is 32-bit, there may be debug info that is not | |
| 2097 | /// supportable using this method. | |
| 2098 | Overflow, | |
| 2099 | ||
| 2100 | PermissionDenied, | |
| 2101 | LockedMemoryLimitExceeded, | |
| 2102 | MemoryMappingNotSupported, | |
| 2103 | } || Allocator.Error || std.fs.File.OpenError || OpenError; | |
| 2104 | ||
| 2105 | /// Reads debug info from an already mapped ELF file. | |
| 2106 | /// | |
| 2107 | /// If the required sections aren't present but a reference to external debug | |
| 2108 | /// info is, then this this function will recurse to attempt to load the debug | |
| 2109 | /// sections from an external file. | |
| 2110 | pub fn load( | |
| 2111 | gpa: Allocator, | |
| 2112 | mapped_mem: []align(std.mem.page_size) const u8, | |
| 2113 | build_id: ?[]const u8, | |
| 2114 | expected_crc: ?u32, | |
| 2115 | parent_sections: *Dwarf.SectionArray, | |
| 2116 | parent_mapped_mem: ?[]align(std.mem.page_size) const u8, | |
| 2117 | elf_filename: ?[]const u8, | |
| 2118 | ) LoadError!Dwarf.ElfModule { | |
| 2119 | if (expected_crc) |crc| if (crc != std.hash.crc.Crc32.hash(mapped_mem)) return error.InvalidDebugInfo; | |
| 2120 | ||
| 2121 | const hdr: *const elf.Ehdr = @ptrCast(&mapped_mem[0]); | |
| 2122 | if (!mem.eql(u8, hdr.e_ident[0..4], elf.MAGIC)) return error.InvalidElfMagic; | |
| 2123 | if (hdr.e_ident[elf.EI_VERSION] != 1) return error.InvalidElfVersion; | |
| 2124 | ||
| 2125 | const endian: std.builtin.Endian = switch (hdr.e_ident[elf.EI_DATA]) { | |
| 2126 | elf.ELFDATA2LSB => .little, | |
| 2127 | elf.ELFDATA2MSB => .big, | |
| 2128 | else => return error.InvalidElfEndian, | |
| 2129 | }; | |
| 2130 | if (endian != native_endian) return error.UnimplementedDwarfForeignEndian; | |
| 2131 | ||
| 2132 | const shoff = hdr.e_shoff; | |
| 2133 | const str_section_off = shoff + @as(u64, hdr.e_shentsize) * @as(u64, hdr.e_shstrndx); | |
| 2134 | const str_shdr: *const elf.Shdr = @ptrCast(@alignCast(&mapped_mem[cast(usize, str_section_off) orelse return error.Overflow])); | |
| 2135 | const header_strings = mapped_mem[str_shdr.sh_offset..][0..str_shdr.sh_size]; | |
| 2136 | const shdrs = @as( | |
| 2137 | [*]const elf.Shdr, | |
| 2138 | @ptrCast(@alignCast(&mapped_mem[shoff])), | |
| 2139 | )[0..hdr.e_shnum]; | |
| 2140 | ||
| 2141 | var sections: Dwarf.SectionArray = Dwarf.null_section_array; | |
| 2142 | ||
| 2143 | // Combine section list. This takes ownership over any owned sections from the parent scope. | |
| 2144 | for (parent_sections, &sections) |*parent, *section_elem| { | |
| 2145 | if (parent.*) |*p| { | |
| 2146 | section_elem.* = p.*; | |
| 2147 | p.owned = false; | |
| 2148 | } | |
| 2149 | } | |
| 2150 | errdefer for (sections) |opt_section| if (opt_section) |s| if (s.owned) gpa.free(s.data); | |
| 2151 | ||
| 2152 | var separate_debug_filename: ?[]const u8 = null; | |
| 2153 | var separate_debug_crc: ?u32 = null; | |
| 2154 | ||
| 2155 | for (shdrs) |*shdr| { | |
| 2156 | if (shdr.sh_type == elf.SHT_NULL or shdr.sh_type == elf.SHT_NOBITS) continue; | |
| 2157 | const name = mem.sliceTo(header_strings[shdr.sh_name..], 0); | |
| 2158 | ||
| 2159 | if (mem.eql(u8, name, ".gnu_debuglink")) { | |
| 2160 | const gnu_debuglink = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size); | |
| 2161 | const debug_filename = mem.sliceTo(@as([*:0]const u8, @ptrCast(gnu_debuglink.ptr)), 0); | |
| 2162 | const crc_offset = mem.alignForward(usize, @intFromPtr(&debug_filename[debug_filename.len]) + 1, 4) - @intFromPtr(gnu_debuglink.ptr); | |
| 2163 | const crc_bytes = gnu_debuglink[crc_offset..][0..4]; | |
| 2164 | separate_debug_crc = mem.readInt(u32, crc_bytes, native_endian); | |
| 2165 | separate_debug_filename = debug_filename; | |
| 2166 | continue; | |
| 2167 | } | |
| 2168 | ||
| 2169 | var section_index: ?usize = null; | |
| 2170 | inline for (@typeInfo(Dwarf.Section.Id).Enum.fields, 0..) |sect, i| { | |
| 2171 | if (mem.eql(u8, "." ++ sect.name, name)) section_index = i; | |
| 2172 | } | |
| 2173 | if (section_index == null) continue; | |
| 2174 | if (sections[section_index.?] != null) continue; | |
| 2175 | ||
| 2176 | const section_bytes = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size); | |
| 2177 | sections[section_index.?] = if ((shdr.sh_flags & elf.SHF_COMPRESSED) > 0) blk: { | |
| 2178 | var section_stream = std.io.fixedBufferStream(section_bytes); | |
| 2179 | const section_reader = section_stream.reader(); | |
| 2180 | const chdr = section_reader.readStruct(elf.Chdr) catch continue; | |
| 2181 | if (chdr.ch_type != .ZLIB) continue; | |
| 2182 | ||
| 2183 | var zlib_stream = std.compress.zlib.decompressor(section_reader); | |
| 2184 | ||
| 2185 | const decompressed_section = try gpa.alloc(u8, chdr.ch_size); | |
| 2186 | errdefer gpa.free(decompressed_section); | |
| 2187 | ||
| 2188 | const read = zlib_stream.reader().readAll(decompressed_section) catch continue; | |
| 2189 | assert(read == decompressed_section.len); | |
| 2190 | ||
| 2191 | break :blk .{ | |
| 2192 | .data = decompressed_section, | |
| 2193 | .virtual_address = shdr.sh_addr, | |
| 2194 | .owned = true, | |
| 2195 | }; | |
| 2196 | } else .{ | |
| 2197 | .data = section_bytes, | |
| 2198 | .virtual_address = shdr.sh_addr, | |
| 2199 | .owned = false, | |
| 2200 | }; | |
| 2201 | } | |
| 2202 | ||
| 2203 | const missing_debug_info = | |
| 2204 | sections[@intFromEnum(Dwarf.Section.Id.debug_info)] == null or | |
| 2205 | sections[@intFromEnum(Dwarf.Section.Id.debug_abbrev)] == null or | |
| 2206 | sections[@intFromEnum(Dwarf.Section.Id.debug_str)] == null or | |
| 2207 | sections[@intFromEnum(Dwarf.Section.Id.debug_line)] == null; | |
| 2208 | ||
| 2209 | // Attempt to load debug info from an external file | |
| 2210 | // See: https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html | |
| 2211 | if (missing_debug_info) { | |
| 2212 | ||
| 2213 | // Only allow one level of debug info nesting | |
| 2214 | if (parent_mapped_mem) |_| { | |
| 2215 | return error.MissingDebugInfo; | |
| 2216 | } | |
| 2217 | ||
| 2218 | const global_debug_directories = [_][]const u8{ | |
| 2219 | "/usr/lib/debug", | |
| 2220 | }; | |
| 2221 | ||
| 2222 | // <global debug directory>/.build-id/<2-character id prefix>/<id remainder>.debug | |
| 2223 | if (build_id) |id| blk: { | |
| 2224 | if (id.len < 3) break :blk; | |
| 2225 | ||
| 2226 | // Either md5 (16 bytes) or sha1 (20 bytes) are used here in practice | |
| 2227 | const extension = ".debug"; | |
| 2228 | var id_prefix_buf: [2]u8 = undefined; | |
| 2229 | var filename_buf: [38 + extension.len]u8 = undefined; | |
| 2230 | ||
| 2231 | _ = std.fmt.bufPrint(&id_prefix_buf, "{s}", .{std.fmt.fmtSliceHexLower(id[0..1])}) catch unreachable; | |
| 2232 | const filename = std.fmt.bufPrint( | |
| 2233 | &filename_buf, | |
| 2234 | "{s}" ++ extension, | |
| 2235 | .{std.fmt.fmtSliceHexLower(id[1..])}, | |
| 2236 | ) catch break :blk; | |
| 2237 | ||
| 2238 | for (global_debug_directories) |global_directory| { | |
| 2239 | const path: Path = .{ | |
| 2240 | .root_dir = std.Build.Cache.Directory.cwd(), | |
| 2241 | .sub_path = try std.fs.path.join(gpa, &.{ | |
| 2242 | global_directory, ".build-id", &id_prefix_buf, filename, | |
| 2243 | }), | |
| 2244 | }; | |
| 2245 | defer gpa.free(path.sub_path); | |
| 2246 | ||
| 2247 | return loadPath(gpa, path, null, separate_debug_crc, &sections, mapped_mem) catch continue; | |
| 2248 | } | |
| 2249 | } | |
| 2250 | ||
| 2251 | // use the path from .gnu_debuglink, in the same search order as gdb | |
| 2252 | if (separate_debug_filename) |separate_filename| blk: { | |
| 2253 | if (elf_filename != null and mem.eql(u8, elf_filename.?, separate_filename)) | |
| 2254 | return error.MissingDebugInfo; | |
| 2255 | ||
| 2256 | // <cwd>/<gnu_debuglink> | |
| 2257 | if (loadPath( | |
| 2258 | gpa, | |
| 2259 | .{ | |
| 2260 | .root_dir = std.Build.Cache.Directory.cwd(), | |
| 2261 | .sub_path = separate_filename, | |
| 2262 | }, | |
| 2263 | null, | |
| 2264 | separate_debug_crc, | |
| 2265 | &sections, | |
| 2266 | mapped_mem, | |
| 2267 | )) |debug_info| { | |
| 2268 | return debug_info; | |
| 2269 | } else |_| {} | |
| 2270 | ||
| 2271 | // <cwd>/.debug/<gnu_debuglink> | |
| 2272 | { | |
| 2273 | const path: Path = .{ | |
| 2274 | .root_dir = std.Build.Cache.Directory.cwd(), | |
| 2275 | .sub_path = try std.fs.path.join(gpa, &.{ ".debug", separate_filename }), | |
| 2276 | }; | |
| 2277 | defer gpa.free(path.sub_path); | |
| 2278 | ||
| 2279 | if (loadPath(gpa, path, null, separate_debug_crc, &sections, mapped_mem)) |debug_info| return debug_info else |_| {} | |
| 2280 | } | |
| 2281 | ||
| 2282 | var cwd_buf: [std.fs.max_path_bytes]u8 = undefined; | |
| 2283 | const cwd_path = std.posix.realpath(".", &cwd_buf) catch break :blk; | |
| 2284 | ||
| 2285 | // <global debug directory>/<absolute folder of current binary>/<gnu_debuglink> | |
| 2286 | for (global_debug_directories) |global_directory| { | |
| 2287 | const path: Path = .{ | |
| 2288 | .root_dir = std.Build.Cache.Directory.cwd(), | |
| 2289 | .sub_path = try std.fs.path.join(gpa, &.{ global_directory, cwd_path, separate_filename }), | |
| 2290 | }; | |
| 2291 | defer gpa.free(path.sub_path); | |
| 2292 | if (loadPath(gpa, path, null, separate_debug_crc, &sections, mapped_mem)) |debug_info| return debug_info else |_| {} | |
| 2293 | } | |
| 2294 | } | |
| 2295 | ||
| 2296 | return error.MissingDebugInfo; | |
| 2297 | } | |
| 2298 | ||
| 2299 | var di: Dwarf = .{ | |
| 2300 | .endian = endian, | |
| 2301 | .sections = sections, | |
| 2302 | .is_macho = false, | |
| 2303 | .compile_units_sorted = false, | |
| 2304 | }; | |
| 2305 | ||
| 2306 | try Dwarf.open(&di, gpa); | |
| 2307 | ||
| 2308 | return .{ | |
| 2309 | .base_address = 0, | |
| 2310 | .dwarf = di, | |
| 2311 | .mapped_memory = parent_mapped_mem orelse mapped_mem, | |
| 2312 | .external_mapped_memory = if (parent_mapped_mem != null) mapped_mem else null, | |
| 2313 | }; | |
| 2314 | } | |
| 2315 | ||
| 2316 | pub fn loadPath( | |
| 2317 | gpa: Allocator, | |
| 2318 | elf_file_path: Path, | |
| 2319 | build_id: ?[]const u8, | |
| 2320 | expected_crc: ?u32, | |
| 2321 | parent_sections: *Dwarf.SectionArray, | |
| 2322 | parent_mapped_mem: ?[]align(std.mem.page_size) const u8, | |
| 2323 | ) LoadError!Dwarf.ElfModule { | |
| 2324 | const elf_file = elf_file_path.root_dir.handle.openFile(elf_file_path.sub_path, .{}) catch |err| switch (err) { | |
| 2325 | error.FileNotFound => return missing(), | |
| 2326 | else => return err, | |
| 2327 | }; | |
| 2328 | defer elf_file.close(); | |
| 2329 | ||
| 2330 | const end_pos = elf_file.getEndPos() catch return bad(); | |
| 2331 | const file_len = cast(usize, end_pos) orelse return error.Overflow; | |
| 2332 | ||
| 2333 | const mapped_mem = try std.posix.mmap( | |
| 2334 | null, | |
| 2335 | file_len, | |
| 2336 | std.posix.PROT.READ, | |
| 2337 | .{ .TYPE = .SHARED }, | |
| 2338 | elf_file.handle, | |
| 2339 | 0, | |
| 2340 | ); | |
| 2341 | errdefer std.posix.munmap(mapped_mem); | |
| 2342 | ||
| 2343 | return load( | |
| 2344 | gpa, | |
| 2345 | mapped_mem, | |
| 2346 | build_id, | |
| 2347 | expected_crc, | |
| 2348 | parent_sections, | |
| 2349 | parent_mapped_mem, | |
| 2350 | elf_file_path.sub_path, | |
| 2351 | ); | |
| 2352 | } | |
| 2353 | }; | |
| 2354 | ||
| 2355 | pub fn getSymbol(di: *Dwarf, allocator: Allocator, address: u64) !std.debug.Symbol { | |
| 2356 | if (di.findCompileUnit(address)) |compile_unit| { | |
| 2357 | return .{ | |
| 2358 | .name = di.getSymbolName(address) orelse "???", | |
| 2359 | .compile_unit_name = compile_unit.die.getAttrString(di, std.dwarf.AT.name, di.section(.debug_str), compile_unit.*) catch |err| switch (err) { | |
| 2360 | error.MissingDebugInfo, error.InvalidDebugInfo => "???", | |
| 2361 | }, | |
| 2362 | .source_location = di.getLineNumberInfo(allocator, compile_unit, address) catch |err| switch (err) { | |
| 2363 | error.MissingDebugInfo, error.InvalidDebugInfo => null, | |
| 2364 | else => return err, | |
| 2365 | }, | |
| 2366 | }; | |
| 2367 | } else |err| switch (err) { | |
| 2368 | error.MissingDebugInfo, error.InvalidDebugInfo => return .{}, | |
| 2369 | else => return err, | |
| 2370 | } | |
| 2371 | } | |
| 2372 | ||
| 2373 | pub fn chopSlice(ptr: []const u8, offset: u64, size: u64) error{Overflow}![]const u8 { | |
| 2374 | const start = cast(usize, offset) orelse return error.Overflow; | |
| 2375 | const end = start + (cast(usize, size) orelse return error.Overflow); | |
| 2376 | return ptr[start..end]; | |
| 2377 | } |
lib/std/debug/FixedBufferReader.zig created+93| ... | ... | @@ -0,0 +1,93 @@ |
| 1 | //! Optimized for performance in debug builds. | |
| 2 | ||
| 3 | const std = @import("../std.zig"); | |
| 4 | const MemoryAccessor = std.debug.MemoryAccessor; | |
| 5 | ||
| 6 | const FixedBufferReader = @This(); | |
| 7 | ||
| 8 | buf: []const u8, | |
| 9 | pos: usize = 0, | |
| 10 | endian: std.builtin.Endian, | |
| 11 | ||
| 12 | pub const Error = error{ EndOfBuffer, Overflow, InvalidBuffer }; | |
| 13 | ||
| 14 | pub fn seekTo(fbr: *FixedBufferReader, pos: u64) Error!void { | |
| 15 | if (pos > fbr.buf.len) return error.EndOfBuffer; | |
| 16 | fbr.pos = @intCast(pos); | |
| 17 | } | |
| 18 | ||
| 19 | pub fn seekForward(fbr: *FixedBufferReader, amount: u64) Error!void { | |
| 20 | if (fbr.buf.len - fbr.pos < amount) return error.EndOfBuffer; | |
| 21 | fbr.pos += @intCast(amount); | |
| 22 | } | |
| 23 | ||
| 24 | pub inline fn readByte(fbr: *FixedBufferReader) Error!u8 { | |
| 25 | if (fbr.pos >= fbr.buf.len) return error.EndOfBuffer; | |
| 26 | defer fbr.pos += 1; | |
| 27 | return fbr.buf[fbr.pos]; | |
| 28 | } | |
| 29 | ||
| 30 | pub fn readByteSigned(fbr: *FixedBufferReader) Error!i8 { | |
| 31 | return @bitCast(try fbr.readByte()); | |
| 32 | } | |
| 33 | ||
| 34 | pub fn readInt(fbr: *FixedBufferReader, comptime T: type) Error!T { | |
| 35 | const size = @divExact(@typeInfo(T).Int.bits, 8); | |
| 36 | if (fbr.buf.len - fbr.pos < size) return error.EndOfBuffer; | |
| 37 | defer fbr.pos += size; | |
| 38 | return std.mem.readInt(T, fbr.buf[fbr.pos..][0..size], fbr.endian); | |
| 39 | } | |
| 40 | ||
| 41 | pub fn readIntChecked( | |
| 42 | fbr: *FixedBufferReader, | |
| 43 | comptime T: type, | |
| 44 | ma: *MemoryAccessor, | |
| 45 | ) Error!T { | |
| 46 | if (ma.load(T, @intFromPtr(fbr.buf[fbr.pos..].ptr)) == null) | |
| 47 | return error.InvalidBuffer; | |
| 48 | ||
| 49 | return fbr.readInt(T); | |
| 50 | } | |
| 51 | ||
| 52 | pub fn readUleb128(fbr: *FixedBufferReader, comptime T: type) Error!T { | |
| 53 | return std.leb.readUleb128(T, fbr); | |
| 54 | } | |
| 55 | ||
| 56 | pub fn readIleb128(fbr: *FixedBufferReader, comptime T: type) Error!T { | |
| 57 | return std.leb.readIleb128(T, fbr); | |
| 58 | } | |
| 59 | ||
| 60 | pub fn readAddress(fbr: *FixedBufferReader, format: std.dwarf.Format) Error!u64 { | |
| 61 | return switch (format) { | |
| 62 | .@"32" => try fbr.readInt(u32), | |
| 63 | .@"64" => try fbr.readInt(u64), | |
| 64 | }; | |
| 65 | } | |
| 66 | ||
| 67 | pub fn readAddressChecked( | |
| 68 | fbr: *FixedBufferReader, | |
| 69 | format: std.dwarf.Format, | |
| 70 | ma: *MemoryAccessor, | |
| 71 | ) Error!u64 { | |
| 72 | return switch (format) { | |
| 73 | .@"32" => try fbr.readIntChecked(u32, ma), | |
| 74 | .@"64" => try fbr.readIntChecked(u64, ma), | |
| 75 | }; | |
| 76 | } | |
| 77 | ||
| 78 | pub fn readBytes(fbr: *FixedBufferReader, len: usize) Error![]const u8 { | |
| 79 | if (fbr.buf.len - fbr.pos < len) return error.EndOfBuffer; | |
| 80 | defer fbr.pos += len; | |
| 81 | return fbr.buf[fbr.pos..][0..len]; | |
| 82 | } | |
| 83 | ||
| 84 | pub fn readBytesTo(fbr: *FixedBufferReader, comptime sentinel: u8) Error![:sentinel]const u8 { | |
| 85 | const end = @call(.always_inline, std.mem.indexOfScalarPos, .{ | |
| 86 | u8, | |
| 87 | fbr.buf, | |
| 88 | fbr.pos, | |
| 89 | sentinel, | |
| 90 | }) orelse return error.EndOfBuffer; | |
| 91 | defer fbr.pos = end + 1; | |
| 92 | return fbr.buf[fbr.pos..end :sentinel]; | |
| 93 | } |
lib/std/debug/Info.zig created+62| ... | ... | @@ -0,0 +1,62 @@ |
| 1 | //! Cross-platform abstraction for loading debug information into an in-memory | |
| 2 | //! format that supports queries such as "what is the source location of this | |
| 3 | //! virtual memory address?" | |
| 4 | //! | |
| 5 | //! Unlike `std.debug.SelfInfo`, this API does not assume the debug information | |
| 6 | //! in question happens to match the host CPU architecture, OS, or other target | |
| 7 | //! properties. | |
| 8 | ||
| 9 | const std = @import("../std.zig"); | |
| 10 | const Allocator = std.mem.Allocator; | |
| 11 | const Path = std.Build.Cache.Path; | |
| 12 | const Dwarf = std.debug.Dwarf; | |
| 13 | const page_size = std.mem.page_size; | |
| 14 | const assert = std.debug.assert; | |
| 15 | const Coverage = std.debug.Coverage; | |
| 16 | const SourceLocation = std.debug.Coverage.SourceLocation; | |
| 17 | ||
| 18 | const Info = @This(); | |
| 19 | ||
| 20 | /// Sorted by key, ascending. | |
| 21 | address_map: std.AutoArrayHashMapUnmanaged(u64, Dwarf.ElfModule), | |
| 22 | /// Externally managed, outlives this `Info` instance. | |
| 23 | coverage: *Coverage, | |
| 24 | ||
| 25 | pub const LoadError = Dwarf.ElfModule.LoadError; | |
| 26 | ||
| 27 | pub fn load(gpa: Allocator, path: Path, coverage: *Coverage) LoadError!Info { | |
| 28 | var sections: Dwarf.SectionArray = Dwarf.null_section_array; | |
| 29 | var elf_module = try Dwarf.ElfModule.loadPath(gpa, path, null, null, &sections, null); | |
| 30 | try elf_module.dwarf.sortCompileUnits(); | |
| 31 | var info: Info = .{ | |
| 32 | .address_map = .{}, | |
| 33 | .coverage = coverage, | |
| 34 | }; | |
| 35 | try info.address_map.put(gpa, elf_module.base_address, elf_module); | |
| 36 | return info; | |
| 37 | } | |
| 38 | ||
| 39 | pub fn deinit(info: *Info, gpa: Allocator) void { | |
| 40 | for (info.address_map.values()) |*elf_module| { | |
| 41 | elf_module.dwarf.deinit(gpa); | |
| 42 | } | |
| 43 | info.address_map.deinit(gpa); | |
| 44 | info.* = undefined; | |
| 45 | } | |
| 46 | ||
| 47 | pub const ResolveAddressesError = Coverage.ResolveAddressesDwarfError; | |
| 48 | ||
| 49 | /// Given an array of virtual memory addresses, sorted ascending, outputs a | |
| 50 | /// corresponding array of source locations. | |
| 51 | pub fn resolveAddresses( | |
| 52 | info: *Info, | |
| 53 | gpa: Allocator, | |
| 54 | sorted_pc_addrs: []const u64, | |
| 55 | /// Asserts its length equals length of `sorted_pc_addrs`. | |
| 56 | output: []SourceLocation, | |
| 57 | ) ResolveAddressesError!void { | |
| 58 | assert(sorted_pc_addrs.len == output.len); | |
| 59 | if (info.address_map.entries.len != 1) @panic("TODO"); | |
| 60 | const elf_module = &info.address_map.values()[0]; | |
| 61 | return info.coverage.resolveAddressesDwarf(gpa, sorted_pc_addrs, output, &elf_module.dwarf); | |
| 62 | } |
lib/std/debug/SelfInfo.zig+35-252| ... | ... | @@ -587,7 +587,7 @@ pub const Module = switch (native_os) { |
| 587 | 587 | } |
| 588 | 588 | if (section_index == null) continue; |
| 589 | 589 | |
| 590 | const section_bytes = try chopSlice(mapped_mem, sect.offset, sect.size); | |
| 590 | const section_bytes = try Dwarf.chopSlice(mapped_mem, sect.offset, sect.size); | |
| 591 | 591 | sections[section_index.?] = .{ |
| 592 | 592 | .data = section_bytes, |
| 593 | 593 | .virtual_address = sect.addr, |
| ... | ... | @@ -602,10 +602,11 @@ pub const Module = switch (native_os) { |
| 602 | 602 | sections[@intFromEnum(Dwarf.Section.Id.debug_line)] == null; |
| 603 | 603 | if (missing_debug_info) return error.MissingDebugInfo; |
| 604 | 604 | |
| 605 | var di = Dwarf{ | |
| 605 | var di: Dwarf = .{ | |
| 606 | 606 | .endian = .little, |
| 607 | 607 | .sections = sections, |
| 608 | 608 | .is_macho = true, |
| 609 | .compile_units_sorted = false, | |
| 609 | 610 | }; |
| 610 | 611 | |
| 611 | 612 | try Dwarf.open(&di, allocator); |
| ... | ... | @@ -622,7 +623,7 @@ pub const Module = switch (native_os) { |
| 622 | 623 | return result.value_ptr; |
| 623 | 624 | } |
| 624 | 625 | |
| 625 | pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !SymbolInfo { | |
| 626 | pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !std.debug.Symbol { | |
| 626 | 627 | nosuspend { |
| 627 | 628 | const result = try self.getOFileInfoForAddress(allocator, address); |
| 628 | 629 | if (result.symbol == null) return .{}; |
| ... | ... | @@ -630,19 +631,19 @@ pub const Module = switch (native_os) { |
| 630 | 631 | // Take the symbol name from the N_FUN STAB entry, we're going to |
| 631 | 632 | // use it if we fail to find the DWARF infos |
| 632 | 633 | const stab_symbol = mem.sliceTo(self.strings[result.symbol.?.strx..], 0); |
| 633 | if (result.o_file_info == null) return .{ .symbol_name = stab_symbol }; | |
| 634 | if (result.o_file_info == null) return .{ .name = stab_symbol }; | |
| 634 | 635 | |
| 635 | 636 | // Translate again the address, this time into an address inside the |
| 636 | 637 | // .o file |
| 637 | 638 | const relocated_address_o = result.o_file_info.?.addr_table.get(stab_symbol) orelse return .{ |
| 638 | .symbol_name = "???", | |
| 639 | .name = "???", | |
| 639 | 640 | }; |
| 640 | 641 | |
| 641 | 642 | const addr_off = result.relocated_address - result.symbol.?.addr; |
| 642 | 643 | const o_file_di = &result.o_file_info.?.di; |
| 643 | 644 | if (o_file_di.findCompileUnit(relocated_address_o)) |compile_unit| { |
| 644 | return SymbolInfo{ | |
| 645 | .symbol_name = o_file_di.getSymbolName(relocated_address_o) orelse "???", | |
| 645 | return .{ | |
| 646 | .name = o_file_di.getSymbolName(relocated_address_o) orelse "???", | |
| 646 | 647 | .compile_unit_name = compile_unit.die.getAttrString( |
| 647 | 648 | o_file_di, |
| 648 | 649 | std.dwarf.AT.name, |
| ... | ... | @@ -651,9 +652,9 @@ pub const Module = switch (native_os) { |
| 651 | 652 | ) catch |err| switch (err) { |
| 652 | 653 | error.MissingDebugInfo, error.InvalidDebugInfo => "???", |
| 653 | 654 | }, |
| 654 | .line_info = o_file_di.getLineNumberInfo( | |
| 655 | .source_location = o_file_di.getLineNumberInfo( | |
| 655 | 656 | allocator, |
| 656 | compile_unit.*, | |
| 657 | compile_unit, | |
| 657 | 658 | relocated_address_o + addr_off, |
| 658 | 659 | ) catch |err| switch (err) { |
| 659 | 660 | error.MissingDebugInfo, error.InvalidDebugInfo => null, |
| ... | ... | @@ -662,7 +663,7 @@ pub const Module = switch (native_os) { |
| 662 | 663 | }; |
| 663 | 664 | } else |err| switch (err) { |
| 664 | 665 | error.MissingDebugInfo, error.InvalidDebugInfo => { |
| 665 | return SymbolInfo{ .symbol_name = stab_symbol }; | |
| 666 | return .{ .name = stab_symbol }; | |
| 666 | 667 | }, |
| 667 | 668 | else => return err, |
| 668 | 669 | } |
| ... | ... | @@ -729,7 +730,7 @@ pub const Module = switch (native_os) { |
| 729 | 730 | } |
| 730 | 731 | } |
| 731 | 732 | |
| 732 | fn getSymbolFromPdb(self: *@This(), relocated_address: usize) !?SymbolInfo { | |
| 733 | fn getSymbolFromPdb(self: *@This(), relocated_address: usize) !?std.debug.Symbol { | |
| 733 | 734 | var coff_section: *align(1) const coff.SectionHeader = undefined; |
| 734 | 735 | const mod_index = for (self.pdb.?.sect_contribs) |sect_contrib| { |
| 735 | 736 | if (sect_contrib.Section > self.coff_section_headers.len) continue; |
| ... | ... | @@ -759,14 +760,14 @@ pub const Module = switch (native_os) { |
| 759 | 760 | relocated_address - coff_section.virtual_address, |
| 760 | 761 | ); |
| 761 | 762 | |
| 762 | return SymbolInfo{ | |
| 763 | .symbol_name = symbol_name, | |
| 763 | return .{ | |
| 764 | .name = symbol_name, | |
| 764 | 765 | .compile_unit_name = obj_basename, |
| 765 | .line_info = opt_line_info, | |
| 766 | .source_location = opt_line_info, | |
| 766 | 767 | }; |
| 767 | 768 | } |
| 768 | 769 | |
| 769 | pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !SymbolInfo { | |
| 770 | pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !std.debug.Symbol { | |
| 770 | 771 | // Translate the VA into an address into this object |
| 771 | 772 | const relocated_address = address - self.base_address; |
| 772 | 773 | |
| ... | ... | @@ -776,10 +777,10 @@ pub const Module = switch (native_os) { |
| 776 | 777 | |
| 777 | 778 | if (self.dwarf) |*dwarf| { |
| 778 | 779 | const dwarf_address = relocated_address + self.coff_image_base; |
| 779 | return getSymbolFromDwarf(allocator, dwarf_address, dwarf); | |
| 780 | return dwarf.getSymbol(allocator, dwarf_address); | |
| 780 | 781 | } |
| 781 | 782 | |
| 782 | return SymbolInfo{}; | |
| 783 | return .{}; | |
| 783 | 784 | } |
| 784 | 785 | |
| 785 | 786 | pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*const Dwarf { |
| ... | ... | @@ -792,41 +793,18 @@ pub const Module = switch (native_os) { |
| 792 | 793 | }; |
| 793 | 794 | } |
| 794 | 795 | }, |
| 795 | .linux, .netbsd, .freebsd, .dragonfly, .openbsd, .haiku, .solaris, .illumos => struct { | |
| 796 | base_address: usize, | |
| 797 | dwarf: Dwarf, | |
| 798 | mapped_memory: []align(mem.page_size) const u8, | |
| 799 | external_mapped_memory: ?[]align(mem.page_size) const u8, | |
| 800 | ||
| 801 | pub fn deinit(self: *@This(), allocator: Allocator) void { | |
| 802 | self.dwarf.deinit(allocator); | |
| 803 | posix.munmap(self.mapped_memory); | |
| 804 | if (self.external_mapped_memory) |m| posix.munmap(m); | |
| 805 | } | |
| 806 | ||
| 807 | pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !SymbolInfo { | |
| 808 | // Translate the VA into an address into this object | |
| 809 | const relocated_address = address - self.base_address; | |
| 810 | return getSymbolFromDwarf(allocator, relocated_address, &self.dwarf); | |
| 811 | } | |
| 812 | ||
| 813 | pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*const Dwarf { | |
| 814 | _ = allocator; | |
| 815 | _ = address; | |
| 816 | return &self.dwarf; | |
| 817 | } | |
| 818 | }, | |
| 796 | .linux, .netbsd, .freebsd, .dragonfly, .openbsd, .haiku, .solaris, .illumos => Dwarf.ElfModule, | |
| 819 | 797 | .wasi, .emscripten => struct { |
| 820 | 798 | pub fn deinit(self: *@This(), allocator: Allocator) void { |
| 821 | 799 | _ = self; |
| 822 | 800 | _ = allocator; |
| 823 | 801 | } |
| 824 | 802 | |
| 825 | pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !SymbolInfo { | |
| 803 | pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !std.debug.Symbol { | |
| 826 | 804 | _ = self; |
| 827 | 805 | _ = allocator; |
| 828 | 806 | _ = address; |
| 829 | return SymbolInfo{}; | |
| 807 | return .{}; | |
| 830 | 808 | } |
| 831 | 809 | |
| 832 | 810 | pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*const Dwarf { |
| ... | ... | @@ -1014,10 +992,11 @@ fn readCoffDebugInfo(allocator: Allocator, coff_obj: *coff.Coff) !Module { |
| 1014 | 992 | } else null; |
| 1015 | 993 | } |
| 1016 | 994 | |
| 1017 | var dwarf = Dwarf{ | |
| 995 | var dwarf: Dwarf = .{ | |
| 1018 | 996 | .endian = native_endian, |
| 1019 | 997 | .sections = sections, |
| 1020 | 998 | .is_macho = false, |
| 999 | .compile_units_sorted = false, | |
| 1021 | 1000 | }; |
| 1022 | 1001 | |
| 1023 | 1002 | try Dwarf.open(&dwarf, allocator); |
| ... | ... | @@ -1068,7 +1047,7 @@ pub fn readElfDebugInfo( |
| 1068 | 1047 | expected_crc: ?u32, |
| 1069 | 1048 | parent_sections: *Dwarf.SectionArray, |
| 1070 | 1049 | parent_mapped_mem: ?[]align(mem.page_size) const u8, |
| 1071 | ) !Module { | |
| 1050 | ) !Dwarf.ElfModule { | |
| 1072 | 1051 | nosuspend { |
| 1073 | 1052 | const elf_file = (if (elf_filename) |filename| blk: { |
| 1074 | 1053 | break :blk fs.cwd().openFile(filename, .{}); |
| ... | ... | @@ -1078,176 +1057,15 @@ pub fn readElfDebugInfo( |
| 1078 | 1057 | }; |
| 1079 | 1058 | |
| 1080 | 1059 | const mapped_mem = try mapWholeFile(elf_file); |
| 1081 | if (expected_crc) |crc| if (crc != std.hash.crc.Crc32.hash(mapped_mem)) return error.InvalidDebugInfo; | |
| 1082 | ||
| 1083 | const hdr: *const elf.Ehdr = @ptrCast(&mapped_mem[0]); | |
| 1084 | if (!mem.eql(u8, hdr.e_ident[0..4], elf.MAGIC)) return error.InvalidElfMagic; | |
| 1085 | if (hdr.e_ident[elf.EI_VERSION] != 1) return error.InvalidElfVersion; | |
| 1086 | ||
| 1087 | const endian: std.builtin.Endian = switch (hdr.e_ident[elf.EI_DATA]) { | |
| 1088 | elf.ELFDATA2LSB => .little, | |
| 1089 | elf.ELFDATA2MSB => .big, | |
| 1090 | else => return error.InvalidElfEndian, | |
| 1091 | }; | |
| 1092 | assert(endian == native_endian); // this is our own debug info | |
| 1093 | ||
| 1094 | const shoff = hdr.e_shoff; | |
| 1095 | const str_section_off = shoff + @as(u64, hdr.e_shentsize) * @as(u64, hdr.e_shstrndx); | |
| 1096 | const str_shdr: *const elf.Shdr = @ptrCast(@alignCast(&mapped_mem[math.cast(usize, str_section_off) orelse return error.Overflow])); | |
| 1097 | const header_strings = mapped_mem[str_shdr.sh_offset..][0..str_shdr.sh_size]; | |
| 1098 | const shdrs = @as( | |
| 1099 | [*]const elf.Shdr, | |
| 1100 | @ptrCast(@alignCast(&mapped_mem[shoff])), | |
| 1101 | )[0..hdr.e_shnum]; | |
| 1102 | ||
| 1103 | var sections: Dwarf.SectionArray = Dwarf.null_section_array; | |
| 1104 | ||
| 1105 | // Combine section list. This takes ownership over any owned sections from the parent scope. | |
| 1106 | for (parent_sections, &sections) |*parent, *section| { | |
| 1107 | if (parent.*) |*p| { | |
| 1108 | section.* = p.*; | |
| 1109 | p.owned = false; | |
| 1110 | } | |
| 1111 | } | |
| 1112 | errdefer for (sections) |section| if (section) |s| if (s.owned) allocator.free(s.data); | |
| 1113 | ||
| 1114 | var separate_debug_filename: ?[]const u8 = null; | |
| 1115 | var separate_debug_crc: ?u32 = null; | |
| 1116 | ||
| 1117 | for (shdrs) |*shdr| { | |
| 1118 | if (shdr.sh_type == elf.SHT_NULL or shdr.sh_type == elf.SHT_NOBITS) continue; | |
| 1119 | const name = mem.sliceTo(header_strings[shdr.sh_name..], 0); | |
| 1120 | ||
| 1121 | if (mem.eql(u8, name, ".gnu_debuglink")) { | |
| 1122 | const gnu_debuglink = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size); | |
| 1123 | const debug_filename = mem.sliceTo(@as([*:0]const u8, @ptrCast(gnu_debuglink.ptr)), 0); | |
| 1124 | const crc_offset = mem.alignForward(usize, @intFromPtr(&debug_filename[debug_filename.len]) + 1, 4) - @intFromPtr(gnu_debuglink.ptr); | |
| 1125 | const crc_bytes = gnu_debuglink[crc_offset..][0..4]; | |
| 1126 | separate_debug_crc = mem.readInt(u32, crc_bytes, native_endian); | |
| 1127 | separate_debug_filename = debug_filename; | |
| 1128 | continue; | |
| 1129 | } | |
| 1130 | ||
| 1131 | var section_index: ?usize = null; | |
| 1132 | inline for (@typeInfo(Dwarf.Section.Id).Enum.fields, 0..) |section, i| { | |
| 1133 | if (mem.eql(u8, "." ++ section.name, name)) section_index = i; | |
| 1134 | } | |
| 1135 | if (section_index == null) continue; | |
| 1136 | if (sections[section_index.?] != null) continue; | |
| 1137 | ||
| 1138 | const section_bytes = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size); | |
| 1139 | sections[section_index.?] = if ((shdr.sh_flags & elf.SHF_COMPRESSED) > 0) blk: { | |
| 1140 | var section_stream = std.io.fixedBufferStream(section_bytes); | |
| 1141 | var section_reader = section_stream.reader(); | |
| 1142 | const chdr = section_reader.readStruct(elf.Chdr) catch continue; | |
| 1143 | if (chdr.ch_type != .ZLIB) continue; | |
| 1144 | ||
| 1145 | var zlib_stream = std.compress.zlib.decompressor(section_stream.reader()); | |
| 1146 | ||
| 1147 | const decompressed_section = try allocator.alloc(u8, chdr.ch_size); | |
| 1148 | errdefer allocator.free(decompressed_section); | |
| 1149 | ||
| 1150 | const read = zlib_stream.reader().readAll(decompressed_section) catch continue; | |
| 1151 | assert(read == decompressed_section.len); | |
| 1152 | ||
| 1153 | break :blk .{ | |
| 1154 | .data = decompressed_section, | |
| 1155 | .virtual_address = shdr.sh_addr, | |
| 1156 | .owned = true, | |
| 1157 | }; | |
| 1158 | } else .{ | |
| 1159 | .data = section_bytes, | |
| 1160 | .virtual_address = shdr.sh_addr, | |
| 1161 | .owned = false, | |
| 1162 | }; | |
| 1163 | } | |
| 1164 | ||
| 1165 | const missing_debug_info = | |
| 1166 | sections[@intFromEnum(Dwarf.Section.Id.debug_info)] == null or | |
| 1167 | sections[@intFromEnum(Dwarf.Section.Id.debug_abbrev)] == null or | |
| 1168 | sections[@intFromEnum(Dwarf.Section.Id.debug_str)] == null or | |
| 1169 | sections[@intFromEnum(Dwarf.Section.Id.debug_line)] == null; | |
| 1170 | ||
| 1171 | // Attempt to load debug info from an external file | |
| 1172 | // See: https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html | |
| 1173 | if (missing_debug_info) { | |
| 1174 | ||
| 1175 | // Only allow one level of debug info nesting | |
| 1176 | if (parent_mapped_mem) |_| { | |
| 1177 | return error.MissingDebugInfo; | |
| 1178 | } | |
| 1179 | ||
| 1180 | const global_debug_directories = [_][]const u8{ | |
| 1181 | "/usr/lib/debug", | |
| 1182 | }; | |
| 1183 | ||
| 1184 | // <global debug directory>/.build-id/<2-character id prefix>/<id remainder>.debug | |
| 1185 | if (build_id) |id| blk: { | |
| 1186 | if (id.len < 3) break :blk; | |
| 1187 | ||
| 1188 | // Either md5 (16 bytes) or sha1 (20 bytes) are used here in practice | |
| 1189 | const extension = ".debug"; | |
| 1190 | var id_prefix_buf: [2]u8 = undefined; | |
| 1191 | var filename_buf: [38 + extension.len]u8 = undefined; | |
| 1192 | ||
| 1193 | _ = std.fmt.bufPrint(&id_prefix_buf, "{s}", .{std.fmt.fmtSliceHexLower(id[0..1])}) catch unreachable; | |
| 1194 | const filename = std.fmt.bufPrint( | |
| 1195 | &filename_buf, | |
| 1196 | "{s}" ++ extension, | |
| 1197 | .{std.fmt.fmtSliceHexLower(id[1..])}, | |
| 1198 | ) catch break :blk; | |
| 1199 | ||
| 1200 | for (global_debug_directories) |global_directory| { | |
| 1201 | const path = try fs.path.join(allocator, &.{ global_directory, ".build-id", &id_prefix_buf, filename }); | |
| 1202 | defer allocator.free(path); | |
| 1203 | ||
| 1204 | return readElfDebugInfo(allocator, path, null, separate_debug_crc, &sections, mapped_mem) catch continue; | |
| 1205 | } | |
| 1206 | } | |
| 1207 | ||
| 1208 | // use the path from .gnu_debuglink, in the same search order as gdb | |
| 1209 | if (separate_debug_filename) |separate_filename| blk: { | |
| 1210 | if (elf_filename != null and mem.eql(u8, elf_filename.?, separate_filename)) return error.MissingDebugInfo; | |
| 1211 | ||
| 1212 | // <cwd>/<gnu_debuglink> | |
| 1213 | if (readElfDebugInfo(allocator, separate_filename, null, separate_debug_crc, &sections, mapped_mem)) |debug_info| return debug_info else |_| {} | |
| 1214 | ||
| 1215 | // <cwd>/.debug/<gnu_debuglink> | |
| 1216 | { | |
| 1217 | const path = try fs.path.join(allocator, &.{ ".debug", separate_filename }); | |
| 1218 | defer allocator.free(path); | |
| 1219 | ||
| 1220 | if (readElfDebugInfo(allocator, path, null, separate_debug_crc, &sections, mapped_mem)) |debug_info| return debug_info else |_| {} | |
| 1221 | } | |
| 1222 | ||
| 1223 | var cwd_buf: [fs.max_path_bytes]u8 = undefined; | |
| 1224 | const cwd_path = posix.realpath(".", &cwd_buf) catch break :blk; | |
| 1225 | ||
| 1226 | // <global debug directory>/<absolute folder of current binary>/<gnu_debuglink> | |
| 1227 | for (global_debug_directories) |global_directory| { | |
| 1228 | const path = try fs.path.join(allocator, &.{ global_directory, cwd_path, separate_filename }); | |
| 1229 | defer allocator.free(path); | |
| 1230 | if (readElfDebugInfo(allocator, path, null, separate_debug_crc, &sections, mapped_mem)) |debug_info| return debug_info else |_| {} | |
| 1231 | } | |
| 1232 | } | |
| 1233 | ||
| 1234 | return error.MissingDebugInfo; | |
| 1235 | } | |
| 1236 | ||
| 1237 | var di = Dwarf{ | |
| 1238 | .endian = endian, | |
| 1239 | .sections = sections, | |
| 1240 | .is_macho = false, | |
| 1241 | }; | |
| 1242 | ||
| 1243 | try Dwarf.open(&di, allocator); | |
| 1244 | ||
| 1245 | return .{ | |
| 1246 | .base_address = undefined, | |
| 1247 | .dwarf = di, | |
| 1248 | .mapped_memory = parent_mapped_mem orelse mapped_mem, | |
| 1249 | .external_mapped_memory = if (parent_mapped_mem != null) mapped_mem else null, | |
| 1250 | }; | |
| 1060 | return Dwarf.ElfModule.load( | |
| 1061 | allocator, | |
| 1062 | mapped_mem, | |
| 1063 | build_id, | |
| 1064 | expected_crc, | |
| 1065 | parent_sections, | |
| 1066 | parent_mapped_mem, | |
| 1067 | elf_filename, | |
| 1068 | ); | |
| 1251 | 1069 | } |
| 1252 | 1070 | } |
| 1253 | 1071 | |
| ... | ... | @@ -1289,22 +1107,6 @@ fn mapWholeFile(file: File) ![]align(mem.page_size) const u8 { |
| 1289 | 1107 | } |
| 1290 | 1108 | } |
| 1291 | 1109 | |
| 1292 | fn chopSlice(ptr: []const u8, offset: u64, size: u64) error{Overflow}![]const u8 { | |
| 1293 | const start = math.cast(usize, offset) orelse return error.Overflow; | |
| 1294 | const end = start + (math.cast(usize, size) orelse return error.Overflow); | |
| 1295 | return ptr[start..end]; | |
| 1296 | } | |
| 1297 | ||
| 1298 | pub const SymbolInfo = struct { | |
| 1299 | symbol_name: []const u8 = "???", | |
| 1300 | compile_unit_name: []const u8 = "???", | |
| 1301 | line_info: ?std.debug.SourceLocation = null, | |
| 1302 | ||
| 1303 | pub fn deinit(self: SymbolInfo, allocator: Allocator) void { | |
| 1304 | if (self.line_info) |li| allocator.free(li.file_name); | |
| 1305 | } | |
| 1306 | }; | |
| 1307 | ||
| 1308 | 1110 | fn machoSearchSymbols(symbols: []const MachoSymbol, address: usize) ?*const MachoSymbol { |
| 1309 | 1111 | var min: usize = 0; |
| 1310 | 1112 | var max: usize = symbols.len - 1; |
| ... | ... | @@ -1350,26 +1152,6 @@ test machoSearchSymbols { |
| 1350 | 1152 | try testing.expectEqual(&symbols[2], machoSearchSymbols(&symbols, 5000).?); |
| 1351 | 1153 | } |
| 1352 | 1154 | |
| 1353 | fn getSymbolFromDwarf(allocator: Allocator, address: u64, di: *Dwarf) !SymbolInfo { | |
| 1354 | if (nosuspend di.findCompileUnit(address)) |compile_unit| { | |
| 1355 | return SymbolInfo{ | |
| 1356 | .symbol_name = nosuspend di.getSymbolName(address) orelse "???", | |
| 1357 | .compile_unit_name = compile_unit.die.getAttrString(di, std.dwarf.AT.name, di.section(.debug_str), compile_unit.*) catch |err| switch (err) { | |
| 1358 | error.MissingDebugInfo, error.InvalidDebugInfo => "???", | |
| 1359 | }, | |
| 1360 | .line_info = nosuspend di.getLineNumberInfo(allocator, compile_unit.*, address) catch |err| switch (err) { | |
| 1361 | error.MissingDebugInfo, error.InvalidDebugInfo => null, | |
| 1362 | else => return err, | |
| 1363 | }, | |
| 1364 | }; | |
| 1365 | } else |err| switch (err) { | |
| 1366 | error.MissingDebugInfo, error.InvalidDebugInfo => { | |
| 1367 | return SymbolInfo{}; | |
| 1368 | }, | |
| 1369 | else => return err, | |
| 1370 | } | |
| 1371 | } | |
| 1372 | ||
| 1373 | 1155 | /// Unwind a frame using MachO compact unwind info (from __unwind_info). |
| 1374 | 1156 | /// If the compact encoding can't encode a way to unwind a frame, it will |
| 1375 | 1157 | /// defer unwinding to DWARF, in which case `.eh_frame` will be used if available. |
| ... | ... | @@ -1796,7 +1578,7 @@ pub fn unwindFrameDwarf( |
| 1796 | 1578 | const frame_section = di.section(dwarf_section) orelse return error.MissingFDE; |
| 1797 | 1579 | if (fde_offset >= frame_section.len) return error.MissingFDE; |
| 1798 | 1580 | |
| 1799 | var fbr: std.debug.DeprecatedFixedBufferReader = .{ | |
| 1581 | var fbr: std.debug.FixedBufferReader = .{ | |
| 1800 | 1582 | .buf = frame_section, |
| 1801 | 1583 | .pos = fde_offset, |
| 1802 | 1584 | .endian = di.endian, |
| ... | ... | @@ -2028,6 +1810,7 @@ fn unwindFrameMachODwarf( |
| 2028 | 1810 | var di: Dwarf = .{ |
| 2029 | 1811 | .endian = native_endian, |
| 2030 | 1812 | .is_macho = true, |
| 1813 | .compile_units_sorted = false, | |
| 2031 | 1814 | }; |
| 2032 | 1815 | defer di.deinit(context.allocator); |
| 2033 | 1816 |
lib/std/http.zig+2| ... | ... | @@ -4,6 +4,7 @@ pub const protocol = @import("http/protocol.zig"); |
| 4 | 4 | pub const HeadParser = @import("http/HeadParser.zig"); |
| 5 | 5 | pub const ChunkParser = @import("http/ChunkParser.zig"); |
| 6 | 6 | pub const HeaderIterator = @import("http/HeaderIterator.zig"); |
| 7 | pub const WebSocket = @import("http/WebSocket.zig"); | |
| 7 | 8 | |
| 8 | 9 | pub const Version = enum { |
| 9 | 10 | @"HTTP/1.0", |
| ... | ... | @@ -318,6 +319,7 @@ test { |
| 318 | 319 | _ = Status; |
| 319 | 320 | _ = HeadParser; |
| 320 | 321 | _ = ChunkParser; |
| 322 | _ = WebSocket; | |
| 321 | 323 | _ = @import("http/test.zig"); |
| 322 | 324 | } |
| 323 | 325 | } |
lib/std/http/WebSocket.zig created+243| ... | ... | @@ -0,0 +1,243 @@ |
| 1 | //! See https://tools.ietf.org/html/rfc6455 | |
| 2 | ||
| 3 | const builtin = @import("builtin"); | |
| 4 | const std = @import("std"); | |
| 5 | const WebSocket = @This(); | |
| 6 | const assert = std.debug.assert; | |
| 7 | const native_endian = builtin.cpu.arch.endian(); | |
| 8 | ||
| 9 | key: []const u8, | |
| 10 | request: *std.http.Server.Request, | |
| 11 | recv_fifo: std.fifo.LinearFifo(u8, .Slice), | |
| 12 | reader: std.io.AnyReader, | |
| 13 | response: std.http.Server.Response, | |
| 14 | /// Number of bytes that have been peeked but not discarded yet. | |
| 15 | outstanding_len: usize, | |
| 16 | ||
| 17 | pub const InitError = error{WebSocketUpgradeMissingKey} || | |
| 18 | std.http.Server.Request.ReaderError; | |
| 19 | ||
| 20 | pub fn init( | |
| 21 | ws: *WebSocket, | |
| 22 | request: *std.http.Server.Request, | |
| 23 | send_buffer: []u8, | |
| 24 | recv_buffer: []align(4) u8, | |
| 25 | ) InitError!bool { | |
| 26 | var sec_websocket_key: ?[]const u8 = null; | |
| 27 | var upgrade_websocket: bool = false; | |
| 28 | var it = request.iterateHeaders(); | |
| 29 | while (it.next()) |header| { | |
| 30 | if (std.ascii.eqlIgnoreCase(header.name, "sec-websocket-key")) { | |
| 31 | sec_websocket_key = header.value; | |
| 32 | } else if (std.ascii.eqlIgnoreCase(header.name, "upgrade")) { | |
| 33 | if (!std.mem.eql(u8, header.value, "websocket")) | |
| 34 | return false; | |
| 35 | upgrade_websocket = true; | |
| 36 | } | |
| 37 | } | |
| 38 | if (!upgrade_websocket) | |
| 39 | return false; | |
| 40 | ||
| 41 | const key = sec_websocket_key orelse return error.WebSocketUpgradeMissingKey; | |
| 42 | ||
| 43 | var sha1 = std.crypto.hash.Sha1.init(.{}); | |
| 44 | sha1.update(key); | |
| 45 | sha1.update("258EAFA5-E914-47DA-95CA-C5AB0DC85B11"); | |
| 46 | var digest: [std.crypto.hash.Sha1.digest_length]u8 = undefined; | |
| 47 | sha1.final(&digest); | |
| 48 | var base64_digest: [28]u8 = undefined; | |
| 49 | assert(std.base64.standard.Encoder.encode(&base64_digest, &digest).len == base64_digest.len); | |
| 50 | ||
| 51 | request.head.content_length = std.math.maxInt(u64); | |
| 52 | ||
| 53 | ws.* = .{ | |
| 54 | .key = key, | |
| 55 | .recv_fifo = std.fifo.LinearFifo(u8, .Slice).init(recv_buffer), | |
| 56 | .reader = try request.reader(), | |
| 57 | .response = request.respondStreaming(.{ | |
| 58 | .send_buffer = send_buffer, | |
| 59 | .respond_options = .{ | |
| 60 | .status = .switching_protocols, | |
| 61 | .extra_headers = &.{ | |
| 62 | .{ .name = "upgrade", .value = "websocket" }, | |
| 63 | .{ .name = "connection", .value = "upgrade" }, | |
| 64 | .{ .name = "sec-websocket-accept", .value = &base64_digest }, | |
| 65 | }, | |
| 66 | .transfer_encoding = .none, | |
| 67 | }, | |
| 68 | }), | |
| 69 | .request = request, | |
| 70 | .outstanding_len = 0, | |
| 71 | }; | |
| 72 | return true; | |
| 73 | } | |
| 74 | ||
| 75 | pub const Header0 = packed struct(u8) { | |
| 76 | opcode: Opcode, | |
| 77 | rsv3: u1 = 0, | |
| 78 | rsv2: u1 = 0, | |
| 79 | rsv1: u1 = 0, | |
| 80 | fin: bool, | |
| 81 | }; | |
| 82 | ||
| 83 | pub const Header1 = packed struct(u8) { | |
| 84 | payload_len: enum(u7) { | |
| 85 | len16 = 126, | |
| 86 | len64 = 127, | |
| 87 | _, | |
| 88 | }, | |
| 89 | mask: bool, | |
| 90 | }; | |
| 91 | ||
| 92 | pub const Opcode = enum(u4) { | |
| 93 | continuation = 0, | |
| 94 | text = 1, | |
| 95 | binary = 2, | |
| 96 | connection_close = 8, | |
| 97 | ping = 9, | |
| 98 | /// "A Pong frame MAY be sent unsolicited. This serves as a unidirectional | |
| 99 | /// heartbeat. A response to an unsolicited Pong frame is not expected." | |
| 100 | pong = 10, | |
| 101 | _, | |
| 102 | }; | |
| 103 | ||
| 104 | pub const ReadSmallTextMessageError = error{ | |
| 105 | ConnectionClose, | |
| 106 | UnexpectedOpCode, | |
| 107 | MessageTooBig, | |
| 108 | MissingMaskBit, | |
| 109 | } || RecvError; | |
| 110 | ||
| 111 | pub const SmallMessage = struct { | |
| 112 | /// Can be text, binary, or ping. | |
| 113 | opcode: Opcode, | |
| 114 | data: []u8, | |
| 115 | }; | |
| 116 | ||
| 117 | /// Reads the next message from the WebSocket stream, failing if the message does not fit | |
| 118 | /// into `recv_buffer`. | |
| 119 | pub fn readSmallMessage(ws: *WebSocket) ReadSmallTextMessageError!SmallMessage { | |
| 120 | while (true) { | |
| 121 | const header_bytes = (try recv(ws, 2))[0..2]; | |
| 122 | const h0: Header0 = @bitCast(header_bytes[0]); | |
| 123 | const h1: Header1 = @bitCast(header_bytes[1]); | |
| 124 | ||
| 125 | switch (h0.opcode) { | |
| 126 | .text, .binary, .pong, .ping => {}, | |
| 127 | .connection_close => return error.ConnectionClose, | |
| 128 | .continuation => return error.UnexpectedOpCode, | |
| 129 | _ => return error.UnexpectedOpCode, | |
| 130 | } | |
| 131 | ||
| 132 | if (!h0.fin) return error.MessageTooBig; | |
| 133 | if (!h1.mask) return error.MissingMaskBit; | |
| 134 | ||
| 135 | const len: usize = switch (h1.payload_len) { | |
| 136 | .len16 => try recvReadInt(ws, u16), | |
| 137 | .len64 => std.math.cast(usize, try recvReadInt(ws, u64)) orelse return error.MessageTooBig, | |
| 138 | else => @intFromEnum(h1.payload_len), | |
| 139 | }; | |
| 140 | if (len > ws.recv_fifo.buf.len) return error.MessageTooBig; | |
| 141 | ||
| 142 | const mask: u32 = @bitCast((try recv(ws, 4))[0..4].*); | |
| 143 | const payload = try recv(ws, len); | |
| 144 | ||
| 145 | // Skip pongs. | |
| 146 | if (h0.opcode == .pong) continue; | |
| 147 | ||
| 148 | // The last item may contain a partial word of unused data. | |
| 149 | const floored_len = (payload.len / 4) * 4; | |
| 150 | const u32_payload: []align(1) u32 = @alignCast(std.mem.bytesAsSlice(u32, payload[0..floored_len])); | |
| 151 | for (u32_payload) |*elem| elem.* ^= mask; | |
| 152 | const mask_bytes = std.mem.asBytes(&mask)[0 .. payload.len - floored_len]; | |
| 153 | for (payload[floored_len..], mask_bytes) |*leftover, m| leftover.* ^= m; | |
| 154 | ||
| 155 | return .{ | |
| 156 | .opcode = h0.opcode, | |
| 157 | .data = payload, | |
| 158 | }; | |
| 159 | } | |
| 160 | } | |
| 161 | ||
| 162 | const RecvError = std.http.Server.Request.ReadError || error{EndOfStream}; | |
| 163 | ||
| 164 | fn recv(ws: *WebSocket, len: usize) RecvError![]u8 { | |
| 165 | ws.recv_fifo.discard(ws.outstanding_len); | |
| 166 | assert(len <= ws.recv_fifo.buf.len); | |
| 167 | if (len > ws.recv_fifo.count) { | |
| 168 | const small_buf = ws.recv_fifo.writableSlice(0); | |
| 169 | const needed = len - ws.recv_fifo.count; | |
| 170 | const buf = if (small_buf.len >= needed) small_buf else b: { | |
| 171 | ws.recv_fifo.realign(); | |
| 172 | break :b ws.recv_fifo.writableSlice(0); | |
| 173 | }; | |
| 174 | const n = try @as(RecvError!usize, @errorCast(ws.reader.readAtLeast(buf, needed))); | |
| 175 | if (n < needed) return error.EndOfStream; | |
| 176 | ws.recv_fifo.update(n); | |
| 177 | } | |
| 178 | ws.outstanding_len = len; | |
| 179 | // TODO: improve the std lib API so this cast isn't necessary. | |
| 180 | return @constCast(ws.recv_fifo.readableSliceOfLen(len)); | |
| 181 | } | |
| 182 | ||
| 183 | fn recvReadInt(ws: *WebSocket, comptime I: type) !I { | |
| 184 | const unswapped: I = @bitCast((try recv(ws, @sizeOf(I)))[0..@sizeOf(I)].*); | |
| 185 | return switch (native_endian) { | |
| 186 | .little => @byteSwap(unswapped), | |
| 187 | .big => unswapped, | |
| 188 | }; | |
| 189 | } | |
| 190 | ||
| 191 | pub const WriteError = std.http.Server.Response.WriteError; | |
| 192 | ||
| 193 | pub fn writeMessage(ws: *WebSocket, message: []const u8, opcode: Opcode) WriteError!void { | |
| 194 | const iovecs: [1]std.posix.iovec_const = .{ | |
| 195 | .{ .base = message.ptr, .len = message.len }, | |
| 196 | }; | |
| 197 | return writeMessagev(ws, &iovecs, opcode); | |
| 198 | } | |
| 199 | ||
| 200 | pub fn writeMessagev(ws: *WebSocket, message: []const std.posix.iovec_const, opcode: Opcode) WriteError!void { | |
| 201 | const total_len = l: { | |
| 202 | var total_len: u64 = 0; | |
| 203 | for (message) |iovec| total_len += iovec.len; | |
| 204 | break :l total_len; | |
| 205 | }; | |
| 206 | ||
| 207 | var header_buf: [2 + 8]u8 = undefined; | |
| 208 | header_buf[0] = @bitCast(@as(Header0, .{ | |
| 209 | .opcode = opcode, | |
| 210 | .fin = true, | |
| 211 | })); | |
| 212 | const header = switch (total_len) { | |
| 213 | 0...125 => blk: { | |
| 214 | header_buf[1] = @bitCast(@as(Header1, .{ | |
| 215 | .payload_len = @enumFromInt(total_len), | |
| 216 | .mask = false, | |
| 217 | })); | |
| 218 | break :blk header_buf[0..2]; | |
| 219 | }, | |
| 220 | 126...0xffff => blk: { | |
| 221 | header_buf[1] = @bitCast(@as(Header1, .{ | |
| 222 | .payload_len = .len16, | |
| 223 | .mask = false, | |
| 224 | })); | |
| 225 | std.mem.writeInt(u16, header_buf[2..4], @intCast(total_len), .big); | |
| 226 | break :blk header_buf[0..4]; | |
| 227 | }, | |
| 228 | else => blk: { | |
| 229 | header_buf[1] = @bitCast(@as(Header1, .{ | |
| 230 | .payload_len = .len64, | |
| 231 | .mask = false, | |
| 232 | })); | |
| 233 | std.mem.writeInt(u64, header_buf[2..10], total_len, .big); | |
| 234 | break :blk header_buf[0..10]; | |
| 235 | }, | |
| 236 | }; | |
| 237 | ||
| 238 | const response = &ws.response; | |
| 239 | try response.writeAll(header); | |
| 240 | for (message) |iovec| | |
| 241 | try response.writeAll(iovec.base[0..iovec.len]); | |
| 242 | try response.flush(); | |
| 243 | } |
lib/std/posix.zig+5| ... | ... | @@ -47,6 +47,11 @@ else switch (native_os) { |
| 47 | 47 | .plan9 => std.os.plan9, |
| 48 | 48 | else => struct { |
| 49 | 49 | pub const ucontext_t = void; |
| 50 | pub const pid_t = void; | |
| 51 | pub const pollfd = void; | |
| 52 | pub const fd_t = void; | |
| 53 | pub const uid_t = void; | |
| 54 | pub const gid_t = void; | |
| 50 | 55 | }, |
| 51 | 56 | }; |
| 52 | 57 |
lib/std/zig/Server.zig+22-6| ... | ... | @@ -28,6 +28,14 @@ pub const Message = struct { |
| 28 | 28 | /// The remaining bytes is the file path relative to that prefix. |
| 29 | 29 | /// The prefixes are hard-coded in Compilation.create (cwd, zig lib dir, local cache dir) |
| 30 | 30 | file_system_inputs, |
| 31 | /// Body is a u64le that indicates the file path within the cache used | |
| 32 | /// to store coverage information. The integer is a hash of the PCs | |
| 33 | /// stored within that file. | |
| 34 | coverage_id, | |
| 35 | /// Body is a u64le that indicates the function pointer virtual memory | |
| 36 | /// address of the fuzz unit test. This is used to provide a starting | |
| 37 | /// point to view coverage. | |
| 38 | fuzz_start_addr, | |
| 31 | 39 | |
| 32 | 40 | _, |
| 33 | 41 | }; |
| ... | ... | @@ -180,6 +188,14 @@ pub fn serveMessage( |
| 180 | 188 | try s.out.writevAll(iovecs[0 .. bufs.len + 1]); |
| 181 | 189 | } |
| 182 | 190 | |
| 191 | pub fn serveU64Message(s: *Server, tag: OutMessage.Tag, int: u64) !void { | |
| 192 | const msg_le = bswap(int); | |
| 193 | return s.serveMessage(.{ | |
| 194 | .tag = tag, | |
| 195 | .bytes_len = @sizeOf(u64), | |
| 196 | }, &.{std.mem.asBytes(&msg_le)}); | |
| 197 | } | |
| 198 | ||
| 183 | 199 | pub fn serveEmitBinPath( |
| 184 | 200 | s: *Server, |
| 185 | 201 | fs_path: []const u8, |
| ... | ... | @@ -187,7 +203,7 @@ pub fn serveEmitBinPath( |
| 187 | 203 | ) !void { |
| 188 | 204 | try s.serveMessage(.{ |
| 189 | 205 | .tag = .emit_bin_path, |
| 190 | .bytes_len = @as(u32, @intCast(fs_path.len + @sizeOf(OutMessage.EmitBinPath))), | |
| 206 | .bytes_len = @intCast(fs_path.len + @sizeOf(OutMessage.EmitBinPath)), | |
| 191 | 207 | }, &.{ |
| 192 | 208 | std.mem.asBytes(&header), |
| 193 | 209 | fs_path, |
| ... | ... | @@ -201,7 +217,7 @@ pub fn serveTestResults( |
| 201 | 217 | const msg_le = bswap(msg); |
| 202 | 218 | try s.serveMessage(.{ |
| 203 | 219 | .tag = .test_results, |
| 204 | .bytes_len = @as(u32, @intCast(@sizeOf(OutMessage.TestResults))), | |
| 220 | .bytes_len = @intCast(@sizeOf(OutMessage.TestResults)), | |
| 205 | 221 | }, &.{ |
| 206 | 222 | std.mem.asBytes(&msg_le), |
| 207 | 223 | }); |
| ... | ... | @@ -209,14 +225,14 @@ pub fn serveTestResults( |
| 209 | 225 | |
| 210 | 226 | pub fn serveErrorBundle(s: *Server, error_bundle: std.zig.ErrorBundle) !void { |
| 211 | 227 | const eb_hdr: OutMessage.ErrorBundle = .{ |
| 212 | .extra_len = @as(u32, @intCast(error_bundle.extra.len)), | |
| 213 | .string_bytes_len = @as(u32, @intCast(error_bundle.string_bytes.len)), | |
| 228 | .extra_len = @intCast(error_bundle.extra.len), | |
| 229 | .string_bytes_len = @intCast(error_bundle.string_bytes.len), | |
| 214 | 230 | }; |
| 215 | 231 | const bytes_len = @sizeOf(OutMessage.ErrorBundle) + |
| 216 | 232 | 4 * error_bundle.extra.len + error_bundle.string_bytes.len; |
| 217 | 233 | try s.serveMessage(.{ |
| 218 | 234 | .tag = .error_bundle, |
| 219 | .bytes_len = @as(u32, @intCast(bytes_len)), | |
| 235 | .bytes_len = @intCast(bytes_len), | |
| 220 | 236 | }, &.{ |
| 221 | 237 | std.mem.asBytes(&eb_hdr), |
| 222 | 238 | // TODO: implement @ptrCast between slices changing the length |
| ... | ... | @@ -251,7 +267,7 @@ pub fn serveTestMetadata(s: *Server, test_metadata: TestMetadata) !void { |
| 251 | 267 | |
| 252 | 268 | return s.serveMessage(.{ |
| 253 | 269 | .tag = .test_metadata, |
| 254 | .bytes_len = @as(u32, @intCast(bytes_len)), | |
| 270 | .bytes_len = @intCast(bytes_len), | |
| 255 | 271 | }, &.{ |
| 256 | 272 | std.mem.asBytes(&header), |
| 257 | 273 | // TODO: implement @ptrCast between slices changing the length |
lib/std/zig/tokenizer.zig+45| ... | ... | @@ -1840,3 +1840,48 @@ fn testTokenize(source: [:0]const u8, expected_token_tags: []const Token.Tag) !v |
| 1840 | 1840 | try std.testing.expectEqual(source.len, last_token.loc.start); |
| 1841 | 1841 | try std.testing.expectEqual(source.len, last_token.loc.end); |
| 1842 | 1842 | } |
| 1843 | ||
| 1844 | test "fuzzable properties upheld" { | |
| 1845 | const source = std.testing.fuzzInput(.{}); | |
| 1846 | const source0 = try std.testing.allocator.dupeZ(u8, source); | |
| 1847 | defer std.testing.allocator.free(source0); | |
| 1848 | var tokenizer = Tokenizer.init(source0); | |
| 1849 | var tokenization_failed = false; | |
| 1850 | while (true) { | |
| 1851 | const token = tokenizer.next(); | |
| 1852 | ||
| 1853 | // Property: token end location after start location (or equal) | |
| 1854 | try std.testing.expect(token.loc.end >= token.loc.start); | |
| 1855 | ||
| 1856 | switch (token.tag) { | |
| 1857 | .invalid => { | |
| 1858 | tokenization_failed = true; | |
| 1859 | ||
| 1860 | // Property: invalid token always ends at newline or eof | |
| 1861 | try std.testing.expect(source0[token.loc.end] == '\n' or source0[token.loc.end] == 0); | |
| 1862 | }, | |
| 1863 | .eof => { | |
| 1864 | // Property: EOF token is always 0-length at end of source. | |
| 1865 | try std.testing.expectEqual(source0.len, token.loc.start); | |
| 1866 | try std.testing.expectEqual(source0.len, token.loc.end); | |
| 1867 | break; | |
| 1868 | }, | |
| 1869 | else => continue, | |
| 1870 | } | |
| 1871 | } | |
| 1872 | ||
| 1873 | if (source0.len > 0) for (source0, source0[1..][0..source0.len]) |cur, next| { | |
| 1874 | // Property: No null byte allowed except at end. | |
| 1875 | if (cur == 0) { | |
| 1876 | try std.testing.expect(tokenization_failed); | |
| 1877 | } | |
| 1878 | // Property: No ASCII control characters other than \n and \t are allowed. | |
| 1879 | if (std.ascii.isControl(cur) and cur != '\n' and cur != '\t') { | |
| 1880 | try std.testing.expect(tokenization_failed); | |
| 1881 | } | |
| 1882 | // Property: All '\r' must be followed by '\n'. | |
| 1883 | if (cur == '\r' and next != '\n') { | |
| 1884 | try std.testing.expect(tokenization_failed); | |
| 1885 | } | |
| 1886 | }; | |
| 1887 | } |
src/Compilation.zig+26-4| ... | ... | @@ -4201,10 +4201,11 @@ fn workerDocsWasm(comp: *Compilation, parent_prog_node: std.Progress.Node) void |
| 4201 | 4201 | const prog_node = parent_prog_node.start("Compile Autodocs", 0); |
| 4202 | 4202 | defer prog_node.end(); |
| 4203 | 4203 | |
| 4204 | workerDocsWasmFallible(comp, prog_node) catch |err| { | |
| 4205 | comp.lockAndSetMiscFailure(.docs_wasm, "unable to build autodocs: {s}", .{ | |
| 4204 | workerDocsWasmFallible(comp, prog_node) catch |err| switch (err) { | |
| 4205 | error.SubCompilationFailed => return, // error reported already | |
| 4206 | else => comp.lockAndSetMiscFailure(.docs_wasm, "unable to build autodocs: {s}", .{ | |
| 4206 | 4207 | @errorName(err), |
| 4207 | }); | |
| 4208 | }), | |
| 4208 | 4209 | }; |
| 4209 | 4210 | } |
| 4210 | 4211 | |
| ... | ... | @@ -4274,8 +4275,29 @@ fn workerDocsWasmFallible(comp: *Compilation, prog_node: std.Progress.Node) anye |
| 4274 | 4275 | .cc_argv = &.{}, |
| 4275 | 4276 | .parent = null, |
| 4276 | 4277 | .builtin_mod = null, |
| 4277 | .builtin_modules = null, // there is only one module in this compilation | |
| 4278 | .builtin_modules = null, | |
| 4279 | }); | |
| 4280 | const walk_mod = try Package.Module.create(arena, .{ | |
| 4281 | .global_cache_directory = comp.global_cache_directory, | |
| 4282 | .paths = .{ | |
| 4283 | .root = .{ | |
| 4284 | .root_dir = comp.zig_lib_directory, | |
| 4285 | .sub_path = "docs/wasm", | |
| 4286 | }, | |
| 4287 | .root_src_path = "Walk.zig", | |
| 4288 | }, | |
| 4289 | .fully_qualified_name = "Walk", | |
| 4290 | .inherited = .{ | |
| 4291 | .resolved_target = resolved_target, | |
| 4292 | .optimize_mode = optimize_mode, | |
| 4293 | }, | |
| 4294 | .global = config, | |
| 4295 | .cc_argv = &.{}, | |
| 4296 | .parent = root_mod, | |
| 4297 | .builtin_mod = root_mod.getBuiltinDependency(), | |
| 4298 | .builtin_modules = null, // `builtin_mod` is set | |
| 4278 | 4299 | }); |
| 4300 | try root_mod.deps.put(arena, "Walk", walk_mod); | |
| 4279 | 4301 | const bin_basename = try std.zig.binNameAlloc(arena, .{ |
| 4280 | 4302 | .root_name = root_name, |
| 4281 | 4303 | .target = resolved_target.result, |
test/standalone/coff_dwarf/build.zig+4-3| ... | ... | @@ -7,9 +7,10 @@ pub fn build(b: *std.Build) void { |
| 7 | 7 | b.default_step = test_step; |
| 8 | 8 | |
| 9 | 9 | const optimize: std.builtin.OptimizeMode = .Debug; |
| 10 | const target = b.standardTargetOptions(.{}); | |
| 11 | ||
| 12 | if (builtin.os.tag != .windows) return; | |
| 10 | const target = if (builtin.os.tag == .windows) | |
| 11 | b.standardTargetOptions(.{}) | |
| 12 | else | |
| 13 | b.resolveTargetQuery(.{ .os_tag = .windows }); | |
| 13 | 14 | |
| 14 | 15 | if (builtin.cpu.arch == .aarch64) { |
| 15 | 16 | // https://github.com/ziglang/zig/issues/18427 |
test/standalone/coff_dwarf/main.zig+6-6| ... | ... | @@ -17,11 +17,11 @@ pub fn main() !void { |
| 17 | 17 | |
| 18 | 18 | const module = try debug_info.getModuleForAddress(add_addr); |
| 19 | 19 | const symbol = try module.getSymbolAtAddress(allocator, add_addr); |
| 20 | defer symbol.deinit(allocator); | |
| 20 | defer if (symbol.source_location) |sl| allocator.free(sl.file_name); | |
| 21 | 21 | |
| 22 | try testing.expectEqualStrings("add", symbol.symbol_name); | |
| 23 | try testing.expect(symbol.line_info != null); | |
| 24 | try testing.expectEqualStrings("shared_lib.c", std.fs.path.basename(symbol.line_info.?.file_name)); | |
| 25 | try testing.expectEqual(@as(u64, 3), symbol.line_info.?.line); | |
| 26 | try testing.expectEqual(@as(u64, 0), symbol.line_info.?.column); | |
| 22 | try testing.expectEqualStrings("add", symbol.name); | |
| 23 | try testing.expect(symbol.source_location != null); | |
| 24 | try testing.expectEqualStrings("shared_lib.c", std.fs.path.basename(symbol.source_location.?.file_name)); | |
| 25 | try testing.expectEqual(@as(u64, 3), symbol.source_location.?.line); | |
| 26 | try testing.expectEqual(@as(u64, 0), symbol.source_location.?.column); | |
| 27 | 27 | } |
tools/dump-cov.zig created+79| ... | ... | @@ -0,0 +1,79 @@ |
| 1 | //! Reads a Zig coverage file and prints human-readable information to stdout, | |
| 2 | //! including file:line:column information for each PC. | |
| 3 | ||
| 4 | const std = @import("std"); | |
| 5 | const fatal = std.process.fatal; | |
| 6 | const Path = std.Build.Cache.Path; | |
| 7 | const assert = std.debug.assert; | |
| 8 | ||
| 9 | pub fn main() !void { | |
| 10 | var general_purpose_allocator: std.heap.GeneralPurposeAllocator(.{}) = .{}; | |
| 11 | defer _ = general_purpose_allocator.deinit(); | |
| 12 | const gpa = general_purpose_allocator.allocator(); | |
| 13 | ||
| 14 | var arena_instance = std.heap.ArenaAllocator.init(gpa); | |
| 15 | defer arena_instance.deinit(); | |
| 16 | const arena = arena_instance.allocator(); | |
| 17 | ||
| 18 | const args = try std.process.argsAlloc(arena); | |
| 19 | const exe_file_name = args[1]; | |
| 20 | const cov_file_name = args[2]; | |
| 21 | ||
| 22 | const exe_path: Path = .{ | |
| 23 | .root_dir = std.Build.Cache.Directory.cwd(), | |
| 24 | .sub_path = exe_file_name, | |
| 25 | }; | |
| 26 | const cov_path: Path = .{ | |
| 27 | .root_dir = std.Build.Cache.Directory.cwd(), | |
| 28 | .sub_path = cov_file_name, | |
| 29 | }; | |
| 30 | ||
| 31 | var coverage = std.debug.Coverage.init; | |
| 32 | defer coverage.deinit(gpa); | |
| 33 | ||
| 34 | var debug_info = std.debug.Info.load(gpa, exe_path, &coverage) catch |err| { | |
| 35 | fatal("failed to load debug info for {}: {s}", .{ exe_path, @errorName(err) }); | |
| 36 | }; | |
| 37 | defer debug_info.deinit(gpa); | |
| 38 | ||
| 39 | const cov_bytes = cov_path.root_dir.handle.readFileAlloc(arena, cov_path.sub_path, 1 << 30) catch |err| { | |
| 40 | fatal("failed to load coverage file {}: {s}", .{ cov_path, @errorName(err) }); | |
| 41 | }; | |
| 42 | ||
| 43 | var bw = std.io.bufferedWriter(std.io.getStdOut().writer()); | |
| 44 | const stdout = bw.writer(); | |
| 45 | ||
| 46 | const header: *align(1) SeenPcsHeader = @ptrCast(cov_bytes); | |
| 47 | try stdout.print("{any}\n", .{header.*}); | |
| 48 | //const n_bitset_elems = (header.pcs_len + 7) / 8; | |
| 49 | const pcs_bytes = cov_bytes[@sizeOf(SeenPcsHeader)..][0 .. header.pcs_len * @sizeOf(usize)]; | |
| 50 | const pcs = try arena.alloc(usize, header.pcs_len); | |
| 51 | for (0..pcs_bytes.len / @sizeOf(usize), pcs) |i, *pc| { | |
| 52 | pc.* = std.mem.readInt(usize, pcs_bytes[i * @sizeOf(usize) ..][0..@sizeOf(usize)], .little); | |
| 53 | } | |
| 54 | assert(std.sort.isSorted(usize, pcs, {}, std.sort.asc(usize))); | |
| 55 | ||
| 56 | const seen_pcs = cov_bytes[@sizeOf(SeenPcsHeader) + pcs.len * @sizeOf(usize) ..]; | |
| 57 | ||
| 58 | const source_locations = try arena.alloc(std.debug.Coverage.SourceLocation, pcs.len); | |
| 59 | try debug_info.resolveAddresses(gpa, pcs, source_locations); | |
| 60 | ||
| 61 | for (pcs, source_locations, 0..) |pc, sl, i| { | |
| 62 | const file = debug_info.coverage.fileAt(sl.file); | |
| 63 | const dir_name = debug_info.coverage.directories.keys()[file.directory_index]; | |
| 64 | const dir_name_slice = debug_info.coverage.stringAt(dir_name); | |
| 65 | const hit: u1 = @truncate(seen_pcs[i / 8] >> @intCast(i % 8)); | |
| 66 | try stdout.print("{c}{x}: {s}/{s}:{d}:{d}\n", .{ | |
| 67 | "-+"[hit], pc, dir_name_slice, debug_info.coverage.stringAt(file.basename), sl.line, sl.column, | |
| 68 | }); | |
| 69 | } | |
| 70 | ||
| 71 | try bw.flush(); | |
| 72 | } | |
| 73 | ||
| 74 | const SeenPcsHeader = extern struct { | |
| 75 | n_runs: usize, | |
| 76 | deduplicated_runs: usize, | |
| 77 | pcs_len: usize, | |
| 78 | lowest_stack: usize, | |
| 79 | }; |