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");