Here’s repro - not exactly the same (I’ve probably found an easier way to trigger the bug) but fails with zig default readVec impl and passes with my readVec2
const std = @import("std");
pub fn main() !void {
var buf: [255]u8 = undefined;
var x: X = .{
.reader = .{
.vtable = &.{
.stream = stream,
.readVec = readVec2,
},
.buffer = &buf,
.seek = 0,
.end = 0,
},
};
_ = try x.reader.peek(1);
const y = try x.reader.peek(2);
std.debug.print("{s}\n", .{y});
}
const X = struct {
reader: std.io.Reader,
};
fn stream(r: *std.io.Reader, _: *std.io.Writer, _: std.io.Limit) std.io.Reader.StreamError!usize {
// This was a data buffer we got from curl
const data = "xyz";
const newcap = r.bufferedLen() + data.len;
if (newcap > r.buffer.len) {
// The original code would pause reading the curl stream.
return 0;
}
// Otherwise we just try to fit as much as we can from the curl buffer into our r.buffer
// It's not so simple in the original code because curl does not allow changing the buffer size,
// so our buffer size needs to be twice the curl buffer size and we can't fill the buffer partially,
// so we always have at least one full curl bufsize to fill.
r.rebase(newcap) catch {};
@memcpy(r.buffer[r.end .. r.end + data.len], data);
r.end += data.len;
return 0;
}
// The patched readVec impl which works
pub fn readVec2(r: *std.io.Reader, data: [][]u8) std.io.Reader.Error!usize {
const first = data[0];
if (first.len >= r.buffer.len - r.end) {
var writer: std.io.Writer = .{
.buffer = first,
.end = 0,
.vtable = &.{ .drain = std.io.Writer.fixedDrain },
};
const limit: std.io.Limit = .limited(writer.buffer.len - writer.end);
return r.vtable.stream(r, &writer, limit) catch |err| switch (err) {
error.WriteFailed => unreachable,
else => |e| return e,
};
}
var writer: std.io.Writer = .{
.buffer = r.buffer,
.end = r.end,
.vtable = &.{ .drain = std.io.Writer.fixedDrain },
};
const limit: std.io.Limit = .limited(writer.buffer.len - writer.end);
// This is the change - the original code does r.end += r.vtable.stream(...)
const n = r.vtable.stream(r, &writer, limit) catch |err| switch (err) {
error.WriteFailed => unreachable,
else => |e| return e,
};
r.end += n;
return 0;
}