A Rare Bug in Audio Code by Zig’s Creator — Can You Find It?

Considering how much of Andrew’s code I’ve seen, bugs are abnormally rare. Well… that makes today special…I actually found one! It’s in an example he wrote in his pulseaudio fork (very mission-critical code, of course). So here’s your chance to prove your intellect: can you spot the bug?

P.S. no cheating and looking at the PR queue where I explain it

fn streamWriteCallback(stream: *pa.stream, requested_bytes: usize, userdata: ?*anyopaque) callconv(.c) void {
    const pulse: *Pulse = @ptrCast(@alignCast(userdata));
    _ = pulse;
    std.log.info("requested bytes: {d}", .{requested_bytes});
    var remaining_bytes = requested_bytes;
    while (remaining_bytes > 0) {
        var ptr_len: usize = remaining_bytes;
        var opt_ptr: ?[*]i32 = null;
        stream.begin_write(@ptrCast(&opt_ptr), &ptr_len) catch @panic("unhandleable error");
        const ptr = opt_ptr orelse @panic("unhandleable error");
        const write_len = @min(ptr_len, remaining_bytes);
        @memset(ptr[0..write_len], 0);
        stream.write(ptr, write_len, null, 0, .RELATIVE) catch @panic("unhandleable error");
        remaining_bytes -= write_len;
        std.log.info("wrote {d} bytes, {d} remaining", .{ write_len, remaining_bytes });
    }
}
8 Likes

Hint: Working with the W suffixed Windows APIs will make you very aware of this class of bug

4 Likes

Ah ok, ptr[0..write_len] with a i32 pointer when it should be a byte pointer - arguably also a failure of the Pulse Audio C API though (void**)

PS: …I didn’t cheat, but after finding the problem I wanted to check if ChatGPT does too, and it did. Might be the one thing where LLMs can actually be useful :wink:

I am a weak mind and just looked at the spoiler, but I want to mention that we follow pretty strict naming convention at TigerBeetle to minimize this sort of thing.

We never use len / length. What we use are count/index and size/offset.

  • size is always size in bytes. size == count * @sizeOf(T)
  • count is always a 1-based logical number of items
  • index is always a 0-based index of logical item. index goes with count and index + 1 == count
  • offset is always a 0-based byte offset. offset goes with size and offset + 1 == size
27 Likes

This instantly reminded me of:

Use explicitly sized types: Use data types with explicit sizes, like u32 or i64, instead of architecture-dependent types like usize. This keeps behavior consistent across platforms and avoids size-related errors, improving portability and reliability.

from Tiger Style I recently came across of

3 Likes

Thanks @matklad, I’ll be adopting this TigerBeetle convention:

10 Likes