master
1b: *std.Build,
2step: *Step,
3test_filters: []const []const u8,
4targets: []const std.Build.ResolvedTarget,
5convert_exe: *std.Build.Step.Compile,
6
7const Config = struct {
8 name: []const u8,
9 source: []const u8,
10 /// Whether this test case expects to have unwind tables / frame pointers.
11 unwind: enum {
12 /// This case assumes that some unwind strategy, safe or unsafe, is available.
13 any,
14 /// This case assumes that no unwinding strategy is available.
15 none,
16 /// This case assumes that a safe unwind strategy, like DWARF unwinding, is available.
17 safe,
18 /// This case assumes that at most, unsafe FP unwinding is available.
19 no_safe,
20 },
21 /// If `true`, the expected exit code is that of the default panic handler, rather than 0.
22 expect_panic: bool,
23 /// When debug info is not stripped, stdout is expected to **contain** (not equal!) this string.
24 expect: []const u8,
25 /// When debug info *is* stripped, stdout is expected to **contain** (not equal!) this string.
26 expect_strip: []const u8,
27};
28
29pub fn addCase(self: *StackTrace, config: Config) void {
30 for (self.targets) |*target| {
31 addCaseTarget(
32 self,
33 config,
34 target,
35 if (target.query.isNative()) null else t: {
36 break :t target.query.zigTriple(self.b.graph.arena) catch @panic("OOM");
37 },
38 );
39 }
40}
41fn addCaseTarget(
42 self: *StackTrace,
43 config: Config,
44 target: *const std.Build.ResolvedTarget,
45 triple: ?[]const u8,
46) void {
47 const both_backends = b: {
48 if (comptime builtin.cpu.arch.endian() == .big) break :b false; // https://github.com/ziglang/zig/issues/25961
49 break :b switch (target.result.cpu.arch) {
50 .x86_64 => switch (target.result.ofmt) {
51 .elf => !target.result.os.tag.isBSD() and target.result.os.tag != .illumos,
52 else => false,
53 },
54 else => false,
55 };
56 };
57 const both_pie = switch (target.result.os.tag) {
58 .fuchsia, .openbsd => false,
59 else => true,
60 };
61 const both_libc = switch (target.result.os.tag) {
62 .freebsd, .netbsd => false,
63 else => !target.result.requiresLibC(),
64 };
65
66 // On aarch64-macos, FP unwinding is blessed by Apple to always be reliable, and std.debug knows this.
67 const fp_unwind_is_safe = target.result.cpu.arch == .aarch64 and target.result.os.tag.isDarwin();
68 const supports_unwind_tables = switch (target.result.os.tag) {
69 // x86-windows just has no way to do stack unwinding other then using frame pointers.
70 .windows => target.result.cpu.arch != .x86,
71 // We do not yet implement support for the AArch32 exception table section `.ARM.exidx`.
72 else => !target.result.cpu.arch.isArm(),
73 };
74
75 const use_llvm_vals: []const bool = if (both_backends) &.{ true, false } else &.{true};
76 const pie_vals: []const ?bool = if (both_pie) &.{ true, false } else &.{null};
77 const link_libc_vals: []const ?bool = if (both_libc) &.{ true, false } else &.{null};
78 const strip_debug_vals: []const bool = &.{ true, false };
79
80 const UnwindInfo = packed struct(u2) {
81 tables: bool,
82 fp: bool,
83 const none: @This() = .{ .tables = false, .fp = false };
84 const both: @This() = .{ .tables = true, .fp = true };
85 const only_tables: @This() = .{ .tables = true, .fp = false };
86 const only_fp: @This() = .{ .tables = false, .fp = true };
87 };
88 const unwind_info_vals: []const UnwindInfo = switch (config.unwind) {
89 .none => &.{.none},
90 .any => &.{ .only_tables, .only_fp, .both },
91 .safe => if (fp_unwind_is_safe) &.{ .only_tables, .only_fp, .both } else &.{ .only_tables, .both },
92 .no_safe => if (fp_unwind_is_safe) &.{.none} else &.{ .none, .only_fp },
93 };
94
95 for (use_llvm_vals) |use_llvm| {
96 for (pie_vals) |pie| {
97 for (link_libc_vals) |link_libc| {
98 for (strip_debug_vals) |strip_debug| {
99 for (unwind_info_vals) |unwind_info| {
100 if (unwind_info.tables and !supports_unwind_tables) continue;
101 self.addCaseInstance(
102 target,
103 triple,
104 config.name,
105 config.source,
106 use_llvm,
107 pie,
108 link_libc,
109 strip_debug,
110 !unwind_info.tables and supports_unwind_tables,
111 !unwind_info.fp,
112 config.expect_panic,
113 if (strip_debug) config.expect_strip else config.expect,
114 );
115 }
116 }
117 }
118 }
119 }
120}
121
122fn addCaseInstance(
123 self: *StackTrace,
124 target: *const std.Build.ResolvedTarget,
125 triple: ?[]const u8,
126 name: []const u8,
127 source: []const u8,
128 use_llvm: bool,
129 pie: ?bool,
130 link_libc: ?bool,
131 strip_debug: bool,
132 strip_unwind: bool,
133 omit_frame_pointer: bool,
134 expect_panic: bool,
135 expect_stderr: []const u8,
136) void {
137 const b = self.b;
138
139 if (strip_debug) {
140 // To enable this coverage, one of two things needs to happen:
141 // * The compiler needs to gain the ability to strip only debug info (not symbols)
142 // * `std.Build.Step.ObjCopy` needs to be un-regressed
143 return;
144 }
145
146 if (strip_unwind) {
147 // To enable this coverage, `std.Build.Step.ObjCopy` needs to be un-regressed and gain the
148 // ability to remove individual sections. `-fno-unwind-tables` is insufficient because it
149 // does not prevent `.debug_frame` from being emitted. If we could, we would remove the
150 // following sections:
151 // * `.eh_frame`, `.eh_frame_hdr`, `.debug_frame` (Linux)
152 // * `__TEXT,__eh_frame`, `__TEXT,__unwind_info` (macOS)
153 return;
154 }
155
156 const annotated_case_name = b.fmt("check {s} ({s}{s}{s}{s}{s}{s}{s}{s})", .{
157 name,
158 triple orelse "",
159 if (triple != null) " " else "",
160 if (use_llvm) "llvm" else "selfhosted",
161 if (pie == true) " pie" else "",
162 if (link_libc == true) " libc" else "",
163 if (strip_debug) " strip" else "",
164 if (strip_unwind) " no_unwind" else "",
165 if (omit_frame_pointer) " no_fp" else "",
166 });
167 if (self.test_filters.len > 0) {
168 for (self.test_filters) |test_filter| {
169 if (mem.indexOf(u8, annotated_case_name, test_filter)) |_| break;
170 } else return;
171 }
172
173 const write_files = b.addWriteFiles();
174 const source_zig = write_files.add("source.zig", source);
175 const exe = b.addExecutable(.{
176 .name = "test",
177 .root_module = b.createModule(.{
178 .root_source_file = source_zig,
179 .optimize = .Debug,
180 .target = target.*,
181 .omit_frame_pointer = omit_frame_pointer,
182 .link_libc = link_libc,
183 .unwind_tables = if (strip_unwind) .none else null,
184 // make panics single-threaded so that they don't include a thread ID
185 .single_threaded = expect_panic,
186 }),
187 .use_llvm = use_llvm,
188 });
189 exe.pie = pie;
190 exe.bundle_ubsan_rt = false;
191
192 const run = b.addRunArtifact(exe);
193 run.removeEnvironmentVariable("CLICOLOR_FORCE");
194 run.setEnvironmentVariable("NO_COLOR", "1");
195 run.addCheck(.{ .expect_term = term: {
196 if (!expect_panic) break :term .{ .Exited = 0 };
197 if (target.result.os.tag == .windows) break :term .{ .Exited = 3 };
198 break :term .{ .Signal = 6 };
199 } });
200 run.expectStdOutEqual("");
201
202 const check_run = b.addRunArtifact(self.convert_exe);
203 check_run.setName(annotated_case_name);
204 check_run.addFileArg(run.captureStdErr(.{}));
205 check_run.expectExitCode(0);
206 check_run.addCheck(.{ .expect_stdout_match = expect_stderr });
207
208 self.step.dependOn(&check_run.step);
209}
210
211const StackTrace = @This();
212const std = @import("std");
213const builtin = @import("builtin");
214const Step = std.Build.Step;
215const OptimizeMode = std.builtin.OptimizeMode;
216const mem = std.mem;