From b780dee3e845f5c8fb8ea7b7906685d5196261a4 Mon Sep 17 00:00:00 2001 From: emekoi Date: Fri, 28 Jun 2019 18:22:02 -0500 Subject: initialize sections in openSelfDebugInfoWindows --- std/debug.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'std/debug.zig') diff --git a/std/debug.zig b/std/debug.zig index 223f93d1ad..09c89befed 100644 --- a/std/debug.zig +++ b/std/debug.zig @@ -805,7 +805,7 @@ fn openSelfDebugInfoWindows(allocator: *mem.Allocator) !DebugInfo { .allocator = allocator, .coff_header = undefined, .pe_header = undefined, - .sections = undefined, + .sections = ArrayList(coff.Section).init(allocator), .guid = undefined, .age = undefined, }; -- cgit v1.2.3 From 027517a0c933f7c07497497c1e8cc0700b7f02bb Mon Sep 17 00:00:00 2001 From: emekoi Date: Sat, 29 Jun 2019 13:56:23 -0500 Subject: added init function for `Coff` --- std/coff.zig | 12 ++++++++++++ std/debug.zig | 10 +--------- 2 files changed, 13 insertions(+), 9 deletions(-) (limited to 'std/debug.zig') diff --git a/std/coff.zig b/std/coff.zig index 2c65da5e9e..9fdc368878 100644 --- a/std/coff.zig +++ b/std/coff.zig @@ -39,6 +39,18 @@ pub const Coff = struct { guid: [16]u8, age: u32, + pub fn init(allocator: *mem.Allocator, in_file: File) Coff { + return Coff{ + .in_file = in_file, + .allocator = allocator, + .coff_header = undefined, + .pe_header = undefined, + .sections = ArrayList(Section).init(allocator), + .guid = undefined, + .age = undefined, + }; + } + pub fn loadHeader(self: *Coff) !void { const pe_pointer_offset = 0x3C; diff --git a/std/debug.zig b/std/debug.zig index 09c89befed..c802f06c04 100644 --- a/std/debug.zig +++ b/std/debug.zig @@ -800,15 +800,7 @@ fn openSelfDebugInfoWindows(allocator: *mem.Allocator) !DebugInfo { defer self_file.close(); const coff_obj = try allocator.create(coff.Coff); - coff_obj.* = coff.Coff{ - .in_file = self_file, - .allocator = allocator, - .coff_header = undefined, - .pe_header = undefined, - .sections = ArrayList(coff.Section).init(allocator), - .guid = undefined, - .age = undefined, - }; + coff_obj.* = coff.Coff.init(allocator, self_file); var di = DebugInfo{ .coff = coff_obj, -- cgit v1.2.3 From 1a1598c58cf506217355c6b8eec84c0e9a3a6d2e Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 2 Jul 2019 13:27:40 -0400 Subject: stack traces on segfault by default for linux-x86_64 closes #2355 --- std/debug.zig | 67 +++++++++++++++++++ std/os.zig | 10 +++ std/os/bits/linux.zig | 15 +++-- std/os/bits/linux/x86_64.zig | 155 +++++++++++++++++++++++++++++++++++++++++++ std/os/linux.zig | 4 +- std/special/start.zig | 9 +++ 6 files changed, 252 insertions(+), 8 deletions(-) (limited to 'std/debug.zig') diff --git a/std/debug.zig b/std/debug.zig index 223f93d1ad..0a007418f5 100644 --- a/std/debug.zig +++ b/std/debug.zig @@ -99,6 +99,32 @@ pub fn dumpCurrentStackTrace(start_addr: ?usize) void { }; } +/// Tries to print the stack trace starting from the supplied base pointer to stderr, +/// unbuffered, and ignores any error returned. +/// TODO multithreaded awareness +pub fn dumpStackTraceFromBase(bp: usize, ip: usize) void { + const stderr = getStderrStream() catch return; + if (builtin.strip_debug_info) { + stderr.print("Unable to dump stack trace: debug info stripped\n") catch return; + return; + } + const debug_info = getSelfDebugInfo() catch |err| { + stderr.print("Unable to dump stack trace: Unable to open debug info: {}\n", @errorName(err)) catch return; + return; + }; + const tty_color = wantTtyColor(); + printSourceAtAddress(debug_info, stderr, ip, tty_color) catch return; + const first_return_address = @intToPtr(*const usize, bp + @sizeOf(usize)).*; + printSourceAtAddress(debug_info, stderr, first_return_address - 1, tty_color) catch return; + var it = StackIterator{ + .first_addr = null, + .fp = bp, + }; + while (it.next()) |return_address| { + printSourceAtAddress(debug_info, stderr, return_address - 1, tty_color) catch return; + } +} + /// Returns a slice with the same pointer as addresses, with a potentially smaller len. /// On Windows, when first_address is not null, we ask for at least 32 stack frames, /// and then try to find the first address. If addresses.len is more than 32, we @@ -2291,3 +2317,44 @@ fn getDebugInfoAllocator() *mem.Allocator { debug_info_allocator = &debug_info_arena_allocator.allocator; return &debug_info_arena_allocator.allocator; } + +/// Whether or not the current target can print useful debug information when a segfault occurs. +pub const have_segfault_handling_support = builtin.arch == .x86_64 and builtin.os == .linux; + +/// Attaches a global SIGSEGV handler which calls @panic("segmentation fault"); +pub fn attachSegfaultHandler() void { + if (!have_segfault_handling_support) { + @compileError("segfault handler not supported for this target"); + } + var act = os.Sigaction{ + .sigaction = handleSegfault, + .mask = os.empty_sigset, + .flags = (os.SA_SIGINFO | os.SA_RESTART | os.SA_RESETHAND), + }; + + os.sigaction(os.SIGSEGV, &act, null); +} + +extern fn handleSegfault(sig: i32, info: *const os.siginfo_t, ctx_ptr: *const c_void) noreturn { + // Reset to the default handler so that if a segfault happens in this handler it will crash + // the process. Also when this handler returns, the original instruction will be repeated + // and the resulting segfault will crash the process rather than continually dump stack traces. + var act = os.Sigaction{ + .sigaction = os.SIG_DFL, + .mask = os.empty_sigset, + .flags = 0, + }; + os.sigaction(os.SIGSEGV, &act, null); + + const ctx = @ptrCast(*const os.ucontext_t, @alignCast(@alignOf(os.ucontext_t), ctx_ptr)); + const ip = @intCast(usize, ctx.mcontext.gregs[os.REG_RIP]); + const bp = @intCast(usize, ctx.mcontext.gregs[os.REG_RBP]); + const addr = @ptrToInt(info.fields.sigfault.addr); + std.debug.warn("Segmentation fault at address 0x{x}\n", addr); + dumpStackTraceFromBase(bp, ip); + + // We cannot allow the signal handler to return because when it runs the original instruction + // again, the memory may be mapped and undefined behavior would occur rather than repeating + // the segfault. So we simply abort here. + os.abort(); +} diff --git a/std/os.zig b/std/os.zig index a0f8d1f12b..a4e8ca3ad8 100644 --- a/std/os.zig +++ b/std/os.zig @@ -2529,6 +2529,16 @@ pub fn sigaltstack(ss: ?*stack_t, old_ss: ?*stack_t) SigaltstackError!void { } } +/// Examine and change a signal action. +pub fn sigaction(sig: u6, act: *const Sigaction, oact: ?*Sigaction) void { + switch (errno(system.sigaction(sig, act, oact))) { + 0 => return, + EFAULT => unreachable, + EINVAL => unreachable, + else => unreachable, + } +} + test "" { _ = @import("os/darwin.zig"); _ = @import("os/freebsd.zig"); diff --git a/std/os/bits/linux.zig b/std/os/bits/linux.zig index 0b355cd819..a11d843f88 100644 --- a/std/os/bits/linux.zig +++ b/std/os/bits/linux.zig @@ -12,6 +12,8 @@ pub usingnamespace switch (builtin.arch) { pub const pid_t = i32; pub const fd_t = i32; +pub const uid_t = i32; +pub const clock_t = isize; pub const PATH_MAX = 4096; pub const IOV_MAX = 1024; @@ -712,22 +714,23 @@ pub const all_mask = [_]u32{ 0xffffffff, 0xffffffff }; pub const app_mask = [_]u32{ 0xfffffffc, 0x7fffffff }; pub const k_sigaction = extern struct { - handler: extern fn (i32) void, + sigaction: ?extern fn (i32, *siginfo_t, *c_void) void, flags: usize, restorer: extern fn () void, mask: [2]u32, }; /// Renamed from `sigaction` to `Sigaction` to avoid conflict with the syscall. -pub const Sigaction = struct { - handler: extern fn (i32) void, +pub const Sigaction = extern struct { + sigaction: ?extern fn (i32, *siginfo_t, *c_void) void, mask: sigset_t, flags: u32, + restorer: ?extern fn () void = null, }; -pub const SIG_ERR = @intToPtr(extern fn (i32) void, maxInt(usize)); -pub const SIG_DFL = @intToPtr(extern fn (i32) void, 0); -pub const SIG_IGN = @intToPtr(extern fn (i32) void, 1); +pub const SIG_ERR = @intToPtr(extern fn (i32, *siginfo_t, *c_void) void, maxInt(usize)); +pub const SIG_DFL = @intToPtr(?extern fn (i32, *siginfo_t, *c_void) void, 0); +pub const SIG_IGN = @intToPtr(extern fn (i32, *siginfo_t, *c_void) void, 1); pub const empty_sigset = [_]usize{0} ** sigset_t.len; pub const in_port_t = u16; diff --git a/std/os/bits/linux/x86_64.zig b/std/os/bits/linux/x86_64.zig index 12ac6b8d7a..6012d877fd 100644 --- a/std/os/bits/linux/x86_64.zig +++ b/std/os/bits/linux/x86_64.zig @@ -1,5 +1,10 @@ // x86-64-specific declarations that are intended to be imported into the POSIX namespace. const std = @import("../../../std.zig"); +const pid_t = linux.pid_t; +const uid_t = linux.uid_t; +const clock_t = linux.clock_t; +const stack_t = linux.stack_t; +const sigset_t = linux.sigset_t; const linux = std.os.linux; const sockaddr = linux.sockaddr; @@ -407,6 +412,30 @@ pub const ARCH_SET_FS = 0x1002; pub const ARCH_GET_FS = 0x1003; pub const ARCH_GET_GS = 0x1004; +pub const REG_R8 = 0; +pub const REG_R9 = 1; +pub const REG_R10 = 2; +pub const REG_R11 = 3; +pub const REG_R12 = 4; +pub const REG_R13 = 5; +pub const REG_R14 = 6; +pub const REG_R15 = 7; +pub const REG_RDI = 8; +pub const REG_RSI = 9; +pub const REG_RBP = 10; +pub const REG_RBX = 11; +pub const REG_RDX = 12; +pub const REG_RAX = 13; +pub const REG_RCX = 14; +pub const REG_RSP = 15; +pub const REG_RIP = 16; +pub const REG_EFL = 17; +pub const REG_CSGSFS = 18; +pub const REG_ERR = 19; +pub const REG_TRAPNO = 20; +pub const REG_OLDMASK = 21; +pub const REG_CR2 = 22; + pub const msghdr = extern struct { msg_name: ?*sockaddr, msg_namelen: socklen_t, @@ -468,3 +497,129 @@ pub const timezone = extern struct { }; pub const Elf_Symndx = u32; + +pub const sigval = extern union { + int: i32, + ptr: *c_void, +}; + +pub const siginfo_t = extern struct { + signo: i32, + errno: i32, + code: i32, + fields: extern union { + pad: [128 - 2 * @sizeOf(c_int) - @sizeOf(c_long)]u8, + common: extern struct { + first: extern union { + piduid: extern struct { + pid: pid_t, + uid: uid_t, + }, + timer: extern struct { + timerid: i32, + overrun: i32, + }, + }, + second: extern union { + value: sigval, + sigchld: extern struct { + status: i32, + utime: clock_t, + stime: clock_t, + }, + }, + }, + sigfault: extern struct { + addr: *c_void, + addr_lsb: i16, + first: extern union { + addr_bnd: extern struct { + lower: *c_void, + upper: *c_void, + }, + pkey: u32, + }, + }, + sigpoll: extern struct { + band: isize, + fd: i32, + }, + sigsys: extern struct { + call_addr: *c_void, + syscall: i32, + arch: u32, + }, + }, +}; + +pub const greg_t = usize; +pub const gregset_t = [23]greg_t; +pub const fpstate = extern struct { + cwd: u16, + swd: u16, + ftw: u16, + fop: u16, + rip: usize, + rdp: usize, + mxcsr: u32, + mxcr_mask: u32, + st: [8]extern struct { + significand: [4]u16, + exponent: u16, + padding: [3]u16 = undefined, + }, + xmm: [16]extern struct { + element: [4]u32, + }, + padding: [24]u32 = undefined, +}; +pub const fpregset_t = *fpstate; +pub const sigcontext = extern struct { + r8: usize, + r9: usize, + r10: usize, + r11: usize, + r12: usize, + r13: usize, + r14: usize, + r15: usize, + + rdi: usize, + rsi: usize, + rbp: usize, + rbx: usize, + rdx: usize, + rax: usize, + rcx: usize, + rsp: usize, + rip: usize, + eflags: usize, + + cs: u16, + gs: u16, + fs: u16, + pad0: u16 = undefined, + + err: usize, + trapno: usize, + oldmask: usize, + cr2: usize, + + fpstate: *fpstate, + reserved1: [8]usize = undefined, +}; + +pub const mcontext_t = extern struct { + gregs: gregset_t, + fpregs: fpregset_t, + reserved1: [8]usize = undefined, +}; + +pub const ucontext_t = extern struct { + flags: usize, + link: *ucontext_t, + stack: stack_t, + mcontext: mcontext_t, + sigmask: sigset_t, + fpregs_mem: [64]usize, +}; diff --git a/std/os/linux.zig b/std/os/linux.zig index 74e1c48444..dd65fb9d83 100644 --- a/std/os/linux.zig +++ b/std/os/linux.zig @@ -542,7 +542,7 @@ pub fn sigaction(sig: u6, noalias act: *const Sigaction, noalias oact: ?*Sigacti assert(sig != SIGKILL); assert(sig != SIGSTOP); var ksa = k_sigaction{ - .handler = act.handler, + .sigaction = act.sigaction, .flags = act.flags | SA_RESTORER, .mask = undefined, .restorer = @ptrCast(extern fn () void, restore_rt), @@ -555,7 +555,7 @@ pub fn sigaction(sig: u6, noalias act: *const Sigaction, noalias oact: ?*Sigacti return result; } if (oact) |old| { - old.handler = ksa_old.handler; + old.sigaction = ksa_old.sigaction; old.flags = @truncate(u32, ksa_old.flags); @memcpy(@ptrCast([*]u8, &old.mask), @ptrCast([*]const u8, &ksa_old.mask), @sizeOf(@typeOf(ksa_old.mask))); } diff --git a/std/special/start.zig b/std/special/start.zig index 45348feab0..f2572def05 100644 --- a/std/special/start.zig +++ b/std/special/start.zig @@ -99,6 +99,15 @@ fn posixCallMainAndExit() noreturn { inline fn callMainWithArgs(argc: usize, argv: [*][*]u8, envp: [][*]u8) u8 { std.os.argv = argv[0..argc]; std.os.environ = envp; + + const enable_segfault_handler: bool = if (@hasDecl(root, "enable_segfault_handler")) + root.enable_segfault_handler + else + std.debug.runtime_safety and std.debug.have_segfault_handling_support; + if (enable_segfault_handler) { + std.debug.attachSegfaultHandler(); + } + return callMain(); } -- cgit v1.2.3 From 53ca4118bdd3908092a53b6b4c59762a85b78dfc Mon Sep 17 00:00:00 2001 From: emekoi Date: Wed, 3 Jul 2019 10:16:52 -0500 Subject: added segfault handler support for windows --- std/debug.zig | 35 +++++++++++++++++++++++++++-------- std/os/windows/bits.zig | 22 ++++++++++++++++++++++ std/os/windows/kernel32.zig | 2 ++ std/special/start.zig | 21 ++++++++++++++------- 4 files changed, 65 insertions(+), 15 deletions(-) (limited to 'std/debug.zig') diff --git a/std/debug.zig b/std/debug.zig index 6e6c171f45..c21e2126a7 100644 --- a/std/debug.zig +++ b/std/debug.zig @@ -2311,23 +2311,31 @@ fn getDebugInfoAllocator() *mem.Allocator { } /// Whether or not the current target can print useful debug information when a segfault occurs. -pub const have_segfault_handling_support = builtin.arch == .x86_64 and builtin.os == .linux; +pub const have_segfault_handling_support = (builtin.arch == .x86_64 and builtin.os == .linux) or builtin.os == .windows; /// Attaches a global SIGSEGV handler which calls @panic("segmentation fault"); pub fn attachSegfaultHandler() void { if (!have_segfault_handling_support) { @compileError("segfault handler not supported for this target"); } - var act = os.Sigaction{ - .sigaction = handleSegfault, - .mask = os.empty_sigset, - .flags = (os.SA_SIGINFO | os.SA_RESTART | os.SA_RESETHAND), - }; + switch (builtin.os) { + .linux => { + var act = os.Sigaction{ + .sigaction = handleSegfaultLinux, + .mask = os.empty_sigset, + .flags = (os.SA_SIGINFO | os.SA_RESTART | os.SA_RESETHAND), + }; - os.sigaction(os.SIGSEGV, &act, null); + os.sigaction(os.SIGSEGV, &act, null); + }, + .windows => { + _ = windows.kernel32.AddVectoredExceptionHandler(0, handleSegfaultWindows); + }, + else => unreachable, + } } -extern fn handleSegfault(sig: i32, info: *const os.siginfo_t, ctx_ptr: *const c_void) noreturn { +extern fn handleSegfaultLinux(sig: i32, info: *const os.siginfo_t, ctx_ptr: *const c_void) noreturn { // Reset to the default handler so that if a segfault happens in this handler it will crash // the process. Also when this handler returns, the original instruction will be repeated // and the resulting segfault will crash the process rather than continually dump stack traces. @@ -2350,3 +2358,14 @@ extern fn handleSegfault(sig: i32, info: *const os.siginfo_t, ctx_ptr: *const c_ // the segfault. So we simply abort here. os.abort(); } + +stdcallcc fn handleSegfaultWindows(info: *windows.EXCEPTION_POINTERS) c_long { + const exception_address = @ptrToInt(info.ExceptionRecord.ExceptionAddress); + switch (info.ExceptionRecord.ExceptionCode) { + windows.EXCEPTION_DATATYPE_MISALIGNMENT => panicExtra(null, exception_address, "Unaligned Memory Access"), + windows.EXCEPTION_ACCESS_VIOLATION => panicExtra(null, exception_address, "Segmentation fault at address 0x{x}", info.ExceptionRecord.ExceptionInformation[1]), + windows.EXCEPTION_ILLEGAL_INSTRUCTION => panicExtra(null, exception_address, "Illegal Instruction"), + windows.EXCEPTION_STACK_OVERFLOW => panicExtra(null, exception_address, "Stack Overflow"), + else => return windows.EXCEPTION_CONTINUE_SEARCH, + } +} diff --git a/std/os/windows/bits.zig b/std/os/windows/bits.zig index c99f3ae463..fe72a710eb 100644 --- a/std/os/windows/bits.zig +++ b/std/os/windows/bits.zig @@ -539,3 +539,25 @@ pub const FORMAT_MESSAGE_FROM_STRING = 0x00000400; pub const FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000; pub const FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200; pub const FORMAT_MESSAGE_MAX_WIDTH_MASK = 0x000000FF; + +pub const EXCEPTION_DATATYPE_MISALIGNMENT = 0x80000002; +pub const EXCEPTION_ACCESS_VIOLATION = 0xc0000005; +pub const EXCEPTION_ILLEGAL_INSTRUCTION = 0xc000001d; +pub const EXCEPTION_STACK_OVERFLOW = 0xc00000fd; +pub const EXCEPTION_CONTINUE_SEARCH = 0; + +pub const EXCEPTION_RECORD = extern struct { + ExceptionCode: u32, + ExceptionFlags: u32, + ExceptionRecord: *EXCEPTION_RECORD, + ExceptionAddress: *c_void, + NumberParameters: u32, + ExceptionInformation: [15]usize, +}; + +pub const EXCEPTION_POINTERS = extern struct { + ExceptionRecord: *EXCEPTION_RECORD, + ContextRecord: *c_void, +}; + +pub const VECTORED_EXCEPTION_HANDLER = stdcallcc fn (ExceptionInfo: *EXCEPTION_POINTERS) c_long; diff --git a/std/os/windows/kernel32.zig b/std/os/windows/kernel32.zig index 27516342ac..494da90d72 100644 --- a/std/os/windows/kernel32.zig +++ b/std/os/windows/kernel32.zig @@ -1,5 +1,7 @@ usingnamespace @import("bits.zig"); +pub extern "kernel32" stdcallcc fn AddVectoredExceptionHandler(First: c_ulong, Handler: ?VECTORED_EXCEPTION_HANDLER) ?*c_void; + pub extern "kernel32" stdcallcc fn CancelIoEx(hFile: HANDLE, lpOverlapped: LPOVERLAPPED) BOOL; pub extern "kernel32" stdcallcc fn CloseHandle(hObject: HANDLE) BOOL; diff --git a/std/special/start.zig b/std/special/start.zig index f2572def05..454c6166f9 100644 --- a/std/special/start.zig +++ b/std/special/start.zig @@ -24,6 +24,16 @@ comptime { } } +fn enableSegfaultHandler() void { + const enable_segfault_handler: bool = if (@hasDecl(root, "enable_segfault_handler")) + root.enable_segfault_handler + else + std.debug.runtime_safety and std.debug.have_segfault_handling_support; + if (enable_segfault_handler) { + std.debug.attachSegfaultHandler(); + } +} + extern fn wasm_freestanding_start() void { _ = callMain(); } @@ -61,6 +71,9 @@ extern fn WinMainCRTStartup() noreturn { if (!builtin.single_threaded) { _ = @import("start_windows_tls.zig"); } + + enableSegfaultHandler(); + std.os.windows.kernel32.ExitProcess(callMain()); } @@ -100,13 +113,7 @@ inline fn callMainWithArgs(argc: usize, argv: [*][*]u8, envp: [][*]u8) u8 { std.os.argv = argv[0..argc]; std.os.environ = envp; - const enable_segfault_handler: bool = if (@hasDecl(root, "enable_segfault_handler")) - root.enable_segfault_handler - else - std.debug.runtime_safety and std.debug.have_segfault_handling_support; - if (enable_segfault_handler) { - std.debug.attachSegfaultHandler(); - } + enableSegfaultHandler(); return callMain(); } -- cgit v1.2.3 From d1cda00b36d687ba2c5df463ffad30c4f7881ee9 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 3 Jul 2019 18:41:52 -0400 Subject: workaround for no equality operator for enum literal and tagged union --- std/debug.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'std/debug.zig') diff --git a/std/debug.zig b/std/debug.zig index c21e2126a7..2cba054043 100644 --- a/std/debug.zig +++ b/std/debug.zig @@ -2311,7 +2311,7 @@ fn getDebugInfoAllocator() *mem.Allocator { } /// Whether or not the current target can print useful debug information when a segfault occurs. -pub const have_segfault_handling_support = (builtin.arch == .x86_64 and builtin.os == .linux) or builtin.os == .windows; +pub const have_segfault_handling_support = (builtin.arch == builtin.Arch.x86_64 and builtin.os == .linux) or builtin.os == .windows; /// Attaches a global SIGSEGV handler which calls @panic("segmentation fault"); pub fn attachSegfaultHandler() void { -- cgit v1.2.3 From 33eaaadd01b20d1327b67758664ce1265216e471 Mon Sep 17 00:00:00 2001 From: Nick Erdmann Date: Tue, 16 Jul 2019 01:48:47 +0200 Subject: fix documentation of assert --- std/debug.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'std/debug.zig') diff --git a/std/debug.zig b/std/debug.zig index 2cba054043..d81e62901a 100644 --- a/std/debug.zig +++ b/std/debug.zig @@ -199,7 +199,7 @@ pub fn dumpStackTrace(stack_trace: builtin.StackTrace) void { /// in its heuristics. /// Inside a test block, it is best to use the `std.testing` module rather /// than this function, because this function may not detect a test failure -/// in ReleaseFast and ReleaseSafe mode. Outside of a test block, this assert +/// in ReleaseFast and ReleaseSmall mode. Outside of a test block, this assert /// function is the correct function to use. pub fn assert(ok: bool) void { if (!ok) unreachable; // assertion failure -- cgit v1.2.3