Hello, I have some code that crashes in release modes but not in debug mode. How do I find the cause for this? Is zig reduce even the correct tool to do so?
I am using zig 0.14.0
Hello, I have some code that crashes in release modes but not in debug mode. How do I find the cause for this? Is zig reduce even the correct tool to do so?
I am using zig 0.14.0
Figured out how to use it.
I had a big chunk of code which segfaulted compiler if I try to compile it.
const std = @import("std");
const crlf = "\r\n";
const ok_response = "HTTP/1.1 200 OK" ++ crlf ++ crlf;
const endpoints = struct {
pub fn @"/echo/{}"(str: []const u8) void {
std.log.info("answering /echo/{s} with {s}", .{ str, str });
}
pub fn @"/"() void {
std.log.info("answering /", .{});
}
};
pub fn bar(path: []const u8) void {
inline for (@typeInfo(endpoints).@"struct".decls) |decl| {
const splited_decl, const placeholders = comptime blk: {
var decl_iter = std.mem.tokenizeScalar(u8, decl.name, '/');
const DeclPart = union(enum) {
str: []const u8,
place_holder,
};
var decl_parts: []const DeclPart = &.{};
var placeholders: usize = 0;
while (decl_iter.next()) |part| {
if (std.mem.eql(u8, part, "{}")) {
decl_parts = decl_parts ++ .{.place_holder};
placeholders += 1;
} else {
decl_parts = decl_parts ++ .{DeclPart{ .str = part }};
}
}
break :blk .{ decl_parts, placeholders };
};
var params: [placeholders][]const u8 = undefined;
var param_index: usize = 0;
var path_iter = std.mem.tokenizeScalar(u8, path, '/');
var index: usize = 0;
matching: while (path_iter.next()) |part| : (index += 1) {
if (index >= splited_decl.len) break :matching;
switch (splited_decl[index]) {
.str => |s| {
if (std.mem.eql(u8, s, part)) continue else break :matching;
},
.place_holder => {
params[param_index] = part;
param_index += 1;
},
}
} else call_func: {
if (index != splited_decl.len) break :call_func;
var tuple_params: std.meta.ArgsTuple(@TypeOf(@field(endpoints, decl.name))) = undefined;
inline for (0..tuple_params.len) |i| {
tuple_params[i] = params[i];
}
@call(.auto, @field(endpoints, decl.name), tuple_params);
std.log.info("called func", .{});
break;
}
std.log.info("try next endpoint", .{});
} else {
std.log.info("not found endpoint", .{});
}
}
pub fn main() void {
bar("/");
bar("/echo/zig");
}
This piece of code should be written in a file, for example ./reducible.zig
.
The command zig reduce
will update this file in-place, and each time it modifies the file, it will call your script to check if the code is still interesting.
We need a script that:
Here’s one I got from llm with some modifications:
#!/bin/bash
# Run the command and capture its exit status
zig build-exe reducible.zig &> /dev/null
status=$?
# Check if the command segfaulted (exit status 139 on most systems for SIGSEGV)
if [[ $status -eq 139 ]]; then
exit 0 # Segfault occurred - exit with 0 (0 is intresting for zig reduce)
else
exit 2 # Success - exit with 2 (any code other than 0,1 means unintresting)
fi
Now we can call:
zig reduce ./checker.sh reducible.zig
and wait until it finishes.
Resulting code would be in reducible.zig
file. In my case resulting file contained a lot of unused variables but still caused segfault. I cleaned up variables manualy and got this:
const std = @import("std");
const endpoints = struct {
pub fn @"/"() void {
@trap();
}
};
pub fn bar(path: []const u8) void {
inline for (@typeInfo(endpoints).@"struct".decls) |_| {
var path_iter = std.mem.tokenizeScalar(u8, path, '/');
var index: usize = undefined;
while (path_iter.next()) |_| : (index += 1) {} else call_func: {
if (index != 0) break :call_func;
break;
}
} else {}
}
pub fn main() void {
bar("/echo/zig");
}
Still segfaults compiler but much better for bug reports. I hope it helps
Some more manual reductions and we got
pub fn main() void {
inline for (0..1) |_| {
var foo: ?bool = undefined;
var bar: bool = undefined;
while (foo) |_| {} else call_func: {
if (bar) break :call_func;
break;
}
}
}