# Need Help Finding Heisenbug

**URL:** <https://ziggit.dev/t/need-help-finding-heisenbug/9531>\
**Category:** Help\
**Tags:** language, build-system\
**Created:** [April 8, 2025, 10:27am UTC](https://ziggit.dev/t/need-help-finding-heisenbug/9531 "2025-04-08T10:27:24Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![SpinTensor](https://ziggit.dev/user_avatar/ziggit.dev/spintensor/32/2942_2.png) [@SpinTensor](https://ziggit.dev/u/SpinTensor)\
**Post date:** [April 8, 2025, 10:27am UTC](https://ziggit.dev/t/need-help-finding-heisenbug/9531/1 "2025-04-08T10:27:24Z")

</div>

Hi,  
I have a code (It solves Advent of Code 2015 day04 part 2, spoiler warning!) and it behaves weirdly in ReleaseFast mode. Debug mode is fine.

> **build.zig**
>
> ```zig
> const std = @import("std");
> 
> // Although this function looks imperative, note that its job is to
> // declaratively construct a build graph that will be executed by an external
> // runner.
> pub fn build(b: *std.Build) void {
> // Standard target options allows the person running `zig build` to choose
> // what target to build for. Here we do not override the defaults, which
> // means any target is allowed, and the default is native. Other options
> // for restricting supported target set are available.
> const target = b.standardTargetOptions(.{});
> 
> // Standard optimization options allow the person running `zig build` to select
> // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not
> // set a preferred release mode, allowing the user to decide how to optimize.
> const optimize = b.standardOptimizeOption(.{});
> 
> const exe = b.addExecutable(.{
> .name = "prog",
> .root_source_file = b.path("src/main.zig"),
> .target = target,
> .optimize = optimize,
> });
> 
> // This declares intent for the executable to be installed into the
> // standard location when the user invokes the "install" step (the default
> // step when running `zig build`).
> b.installArtifact(exe);
> 
> // This *creates* a Run step in the build graph, to be executed when another
> // step is evaluated that depends on it. The next line below will establish
> // such a dependency.
> const run_cmd = b.addRunArtifact(exe);
> 
> // By making the run step depend on the install step, it will be run from the
> // installation directory rather than directly from within the cache directory.
> // This is not necessary, however, if the application depends on other installed
> // files, this ensures they will be present and in the expected location.
> run_cmd.step.dependOn(b.getInstallStep());
> 
> // This allows the user to pass arguments to the application in the build
> // command itself, like this: `zig build run -- arg1 arg2 etc`
> if (b.args) |args| {
> run_cmd.addArgs(args);
> }
> 
> // This creates a build step. It will be visible in the `zig build --help` menu,
> // and can be selected like this: `zig build run`
> // This will evaluate the `run` step rather than the default, which is "install".
> const run_step = b.step("run", "Run the app");
> run_step.dependOn(&run_cmd.step);
> 
> const exe_unit_tests = b.addTest(.{
> .root_source_file = b.path("src/main.zig"),
> .target = target,
> .optimize = optimize,
> });
> 
> const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
> 
> // Similar to creating the run step earlier, this exposes a `test` step to
> // the `zig build --help` menu, providing a way for the user to request
> // running the unit tests.
> const test_step = b.step("test", "Run unit tests");
> test_step.dependOn(&run_exe_unit_tests.step);
> }
> 
> ```

> **main.zig**
>
> ```zig
> const std = @import("std");
> const stdout = std.io.getStdOut().writer();
> const stderr = std.io.getStdErr().writer();
> 
> pub fn startsNZerosHex(inp: []const u8, n: usize) bool {
> if (inp.len < @divFloor(n + 1, 2)) {
> return false;
> }
> const n_is_even = @mod(n, 2) == 0;
> const nev = if (n_is_even) n else n - 1;
> 
> for (inp[0..@divExact(nev, 2)]) |i| {
> if (i != 0) {
> return false;
> }
> }
> 
> if (!n_is_even) {
> return inp[@divExact(nev, 2)] < 16;
> }
> return true;
> }
> 
> pub fn main() !void {
> var gpa = std.heap.GeneralPurposeAllocator(.{}){};
> defer _ = gpa.deinit();
> const allocator = gpa.allocator();
> 
> const args = try std.process.argsAlloc(allocator);
> defer std.process.argsFree(allocator, args);
> if (args.len < 2) {
> try stderr.print("Usage: {s} <filename>\n", .{args[0]});
> return error.InvalidArguments;
> }
> const filename = args[1];
> const file = try std.fs.cwd().openFile(filename, .{});
> defer file.close();
> const file_size = try file.getEndPos();
> 
> const input = try allocator.alloc(u8, file_size);
> defer allocator.free(input);
> 
> const bytes_read = try file.readAll(input);
> if (bytes_read != file_size) {
> try stderr.print("Warning: Read {d} bytes but expected {d}\n", .{ bytes_read, file_size });
> }
> 
> // try stdout.print("File input is {s}\n", .{input}); // including this line ommits the error message.
> 
> var md5input = std.ArrayList(u8).init(allocator);
> defer md5input.deinit();
> 
> const Md5 = std.crypto.hash.Md5;
> 
> var output: [Md5.digest_length]u8 = undefined;
> 
> var num: usize = 0;
> while (!startsNZerosHex(&output, 6)) : (num += 1) {
> md5input.clearRetainingCapacity();
> _ = try md5input.writer().print("{s}{d}", .{ input[0 .. file_size - 1], num });
> Md5.hash(md5input.items, &output, .{});
> }
> num -= 1;
> 
> try stdout.print("md5(\"{s}\") = ", .{md5input.items});
> for (output) |i| {
> try stdout.print("{x:0>2}", .{i});
> }
> try stdout.print(" -> {d}\n", .{num});
> }
> 
> ```

> **test.inp**
>
> ```zig
> bgvyzdsv
> 
> ```

When I run it regularly it results in:

```bash
$ zig build run -- ./src/test.inp 
md5("bgvyzdsv1038736") = 000000b1b64bf5eb55aad89986126953 -> 1038736

```

When I run it with ReleaseFast:

```bash
$ zig build -Doptimize=ReleaseFast run -- ./src/test.inp 
Warning: Read 9 bytes but expected 9

```

and then hangs in an infinite loop.  
In line 44 I test if the read bytes and the file size match. If they do not I will print out the values. However, as you can see in the above output, the read bytes and file size match, but the output is written anyway.

Interestingly if I remove everything after line 48 the warning is not printed anymore. Also if I uncomment line 48 where I print the file contents.  
Sorry, but I could not find a way to reduce the code without this behavior disappearing.

What did I do wrong? And If I did something wrong, why does it work in debug mode but not in ReleaseFast mode?

Thanks for your help.

---

<div class="post-metadata">

**Author:** ![cryptocode](https://ziggit.dev/user_avatar/ziggit.dev/cryptocode/32/817_2.png) [@cryptocode](https://ziggit.dev/u/cryptocode)\
**Post date:** [April 8, 2025, 10:37am UTC](https://ziggit.dev/t/need-help-finding-heisenbug/9531/2 "2025-04-08T10:37:16Z")

</div>

First time you call startsNZerosHex it’s with undefined data, isn’t it? Next round it has the hash output.

---

<div class="post-metadata">

**Author:** ![SpinTensor](https://ziggit.dev/user_avatar/ziggit.dev/spintensor/32/2942_2.png) [@SpinTensor](https://ziggit.dev/u/SpinTensor)\
**Post date:** [April 8, 2025, 10:56am UTC](https://ziggit.dev/t/need-help-finding-heisenbug/9531/3 "2025-04-08T10:56:59Z")

</div>

Thank you,  
I initialized the output array

```zig
var output: [Md5.digest_length]u8 = ([_]u8{1}) ** Md5.digest_length;

```

and the error disappeared.  
But since this should be executed after parsing the and checking the file, how could this effect the `if` and, why is the expression `bytes_read != file_size` true, if the print statement shows the values to be different?

---

<div class="post-metadata">

**Author:** ![cryptocode](https://ziggit.dev/user_avatar/ziggit.dev/cryptocode/32/817_2.png) [@cryptocode](https://ziggit.dev/u/cryptocode)\
**Post date:** [April 8, 2025, 11:15am UTC](https://ziggit.dev/t/need-help-finding-heisenbug/9531/4 "2025-04-08T11:15:02Z")

</div>

In Debug mode, the `output` array was initialized to all 0xAA’s (because of = undefined). I’d go through to code and see why it happened to work with that.

---

<div class="post-metadata">

**Author:** ![LucasSantos91](https://ziggit.dev/user_avatar/ziggit.dev/lucassantos91/32/440_2.png) [@LucasSantos91](https://ziggit.dev/u/LucasSantos91)\
**Post date:** [April 8, 2025, 2:18pm UTC](https://ziggit.dev/t/need-help-finding-heisenbug/9531/5 "2025-04-08T14:18:51Z")

</div>

Reading undefined values is undefined behaviour. The compiler assumed that this code path would be unreachable:

> [@SpinTensor](#):
>
> `while (!startsNZerosHex(&output, 6))`

It therefore assumed that the true branch here:

> [@SpinTensor](#):
>
> ```zig
> if (bytes_read != file_size) {
> try stderr.print("Warning: Read {d} bytes but expected {d}\n", .{ bytes_read, file_size });
> }
> 
> ```

Would always be taken, and that print would either never return or return an error. Neither happened, and the code probably drifted into an infinite loop.
