How do I use zig reduce?

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

1 Like

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:

  1. Attempts to compile the file
  2. Checks the exit code

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

4 Likes

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;
        }
    }
}
1 Like