master
  1//! Accepts a stack trace in a file (whose path is given as argv[1]), and removes all
  2//! non-reproducible information from it, including addresses, module names, and file
  3//! paths. All module names are removed, file paths become just their basename, and
  4//! addresses are replaced with a fixed string. So, lines like this:
  5//!
  6//!   /something/foo.zig:1:5: 0x12345678 in bar (main.o)
  7//!       doThing();
  8//!              ^
  9//!   ???:?:?: 0x12345678 in qux (other.o)
 10//!   ???:?:?: 0x12345678 in ??? (???)
 11//!
 12//! ...are turned into lines like this:
 13//!
 14//!   foo.zig:1:5: [address] in bar
 15//!       doThing();
 16//!              ^
 17//!   ???:?:?: [address] in qux
 18//!   ???:?:?: [address] in ???
 19//!
 20//! Additionally, lines reporting unwind errors are removed:
 21//!
 22//!   Unwind error at address `/proc/self/exe:0x1016533` (unwind info unavailable), remaining frames may be incorrect
 23//!   Cannot print stack trace: safe unwind unavilable for target
 24//!
 25//! With these transformations, the test harness can safely do string comparisons.
 26
 27pub fn main() !void {
 28    var arena_instance: std.heap.ArenaAllocator = .init(std.heap.page_allocator);
 29    defer arena_instance.deinit();
 30    const arena = arena_instance.allocator();
 31
 32    const args = try std.process.argsAlloc(arena);
 33    if (args.len != 2) std.process.fatal("usage: convert-stack-trace path/to/test/output", .{});
 34
 35    const gpa = arena;
 36
 37    var threaded: std.Io.Threaded = .init(gpa);
 38    defer threaded.deinit();
 39    const io = threaded.io();
 40
 41    var read_buf: [1024]u8 = undefined;
 42    var write_buf: [1024]u8 = undefined;
 43
 44    const in_file = try std.fs.cwd().openFile(args[1], .{});
 45    defer in_file.close();
 46
 47    const out_file: std.fs.File = .stdout();
 48
 49    var in_fr = in_file.reader(io, &read_buf);
 50    var out_fw = out_file.writer(&write_buf);
 51
 52    const w = &out_fw.interface;
 53
 54    while (in_fr.interface.takeDelimiterInclusive('\n')) |in_line| {
 55        if (std.mem.eql(u8, in_line, "Cannot print stack trace: safe unwind unavailable for target\n") or
 56            std.mem.startsWith(u8, in_line, "Unwind error at address `"))
 57        {
 58            // Remove these lines from the output.
 59            continue;
 60        }
 61
 62        const src_col_end = std.mem.indexOf(u8, in_line, ": 0x") orelse {
 63            try w.writeAll(in_line);
 64            continue;
 65        };
 66        const src_row_end = std.mem.lastIndexOfScalar(u8, in_line[0..src_col_end], ':') orelse {
 67            try w.writeAll(in_line);
 68            continue;
 69        };
 70        const src_path_end = std.mem.lastIndexOfScalar(u8, in_line[0..src_row_end], ':') orelse {
 71            try w.writeAll(in_line);
 72            continue;
 73        };
 74
 75        const addr_end = std.mem.indexOfPos(u8, in_line, src_col_end, " in ") orelse {
 76            try w.writeAll(in_line);
 77            continue;
 78        };
 79        const symbol_end = std.mem.indexOfPos(u8, in_line, addr_end, " (") orelse {
 80            try w.writeAll(in_line);
 81            continue;
 82        };
 83        if (!std.mem.endsWith(u8, std.mem.trimEnd(u8, in_line, "\n"), ")")) {
 84            try w.writeAll(in_line);
 85            continue;
 86        }
 87
 88        // Where '_' is a placeholder for an arbitrary string, we now know the line looks like:
 89        //
 90        //   _:_:_: 0x_ in _ (_)
 91        //
 92        // That seems good enough to assume it's a stack trace frame! We'll rewrite it to:
 93        //
 94        //   _:_:_: [address] in _
 95        //
 96        // ...with that first '_' being replaced by its basename.
 97
 98        const src_path = in_line[0..src_path_end];
 99        const basename_start = if (std.mem.lastIndexOfAny(u8, src_path, "/\\")) |i| i + 1 else 0;
100        const symbol_start = addr_end + " in ".len;
101        try w.writeAll(in_line[basename_start..src_col_end]);
102        try w.writeAll(": [address] in ");
103        try w.writeAll(in_line[symbol_start..symbol_end]);
104        try w.writeByte('\n');
105    } else |err| switch (err) {
106        error.EndOfStream => {},
107        else => |e| return e,
108    }
109
110    try w.flush();
111}
112
113const std = @import("std");