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(init: std.process.Init) !void {
28 const arena = init.arena.allocator();
29 const io = init.io;
30 const args = try init.minimal.args.toSlice(arena);
31
32 if (args.len != 2) std.process.fatal("usage: convert-stack-trace path/to/test/output", .{});
33
34 var read_buf: [1024]u8 = undefined;
35 var write_buf: [1024]u8 = undefined;
36
37 const in_file = try std.Io.Dir.cwd().openFile(io, args[1], .{});
38 defer in_file.close(io);
39
40 const out_file: std.Io.File = .stdout();
41
42 var in_fr = in_file.reader(io, &read_buf);
43 var out_fw = out_file.writer(io, &write_buf);
44
45 const w = &out_fw.interface;
46
47 while (in_fr.interface.takeDelimiterInclusive('\n')) |in_line| {
48 if (std.mem.eql(u8, in_line, "Cannot print stack trace: safe unwind unavailable for target\n") or
49 std.mem.startsWith(u8, in_line, "Unwind error at address `"))
50 {
51 // Remove these lines from the output.
52 continue;
53 }
54
55 const src_pos_end = std.mem.find(u8, in_line, ": 0x") orelse {
56 try w.writeAll(in_line);
57 continue;
58 };
59 const src_pos_start = b: {
60 const postfix = ".zig:";
61 const postfix_index = std.mem.findLast(u8, in_line[0..src_pos_end], postfix) orelse {
62 try w.writeAll(in_line);
63 continue;
64 };
65 break :b postfix_index + postfix.len;
66 };
67
68 const addr_end = std.mem.findPos(u8, in_line, src_pos_end, " in ") orelse {
69 try w.writeAll(in_line);
70 continue;
71 };
72 const symbol_end = std.mem.findPos(u8, in_line, addr_end, " (") orelse {
73 try w.writeAll(in_line);
74 continue;
75 };
76 if (!std.mem.endsWith(u8, std.mem.trimEnd(u8, in_line, "\n"), ")")) {
77 try w.writeAll(in_line);
78 continue;
79 }
80
81 // Where '_' is a placeholder for an arbitrary string, we now know the line looks like:
82 //
83 // _:_:_: 0x_ in _ (_)
84 //
85 // That seems good enough to assume it's a stack trace frame! We'll rewrite it to:
86 //
87 // _:_:_: [address] in _
88 //
89 // ...with that first '_' being replaced by its basename.
90
91 const src_path = in_line[0..src_pos_start];
92 const basename_start = if (std.mem.findLastAny(u8, src_path, "/\\")) |i| i + 1 else 0;
93 const symbol_start = addr_end + " in ".len;
94 try w.writeAll(in_line[basename_start..src_pos_end]);
95 try w.writeAll(": [address] in ");
96 try w.writeAll(in_line[symbol_start..symbol_end]);
97 try w.writeByte('\n');
98 } else |err| switch (err) {
99 error.EndOfStream => {},
100 else => |e| return e,
101 }
102
103 try w.flush();
104}
105
106const std = @import("std");