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;