diff --git a/lib/std/io/Reader.zig b/lib/std/io/Reader.zig index 6cfb21839f..852f6e1952 100644 --- a/lib/std/io/Reader.zig +++ b/lib/std/io/Reader.zig @@ -752,6 +752,11 @@ pub fn peekDelimiterInclusive(r: *Reader, delimiter: u8) DelimiterError![]u8 { @branchHint(.likely); return buffer[seek .. end + 1]; } + if (r.vtable.stream == &endingStream) { + // Protect the `@constCast` of `fixed`. + return error.EndOfStream; + } + r.rebase(); while (r.buffer.len - r.end != 0) { const end_cap = r.buffer[r.end..]; var writer: Writer = .fixed(end_cap); @@ -761,29 +766,9 @@ pub fn peekDelimiterInclusive(r: *Reader, delimiter: u8) DelimiterError![]u8 { }; r.end += n; if (std.mem.indexOfScalarPos(u8, end_cap[0..n], 0, delimiter)) |end| { - return r.buffer[seek .. r.end - n + end + 1]; + return r.buffer[0 .. r.end - n + end + 1]; } } - var i: usize = 0; - while (seek - i != 0) { - const begin_cap = r.buffer[i..seek]; - var writer: Writer = .fixed(begin_cap); - const n = r.vtable.stream(r, &writer, .limited(begin_cap.len)) catch |err| switch (err) { - error.WriteFailed => unreachable, - else => |e| return e, - }; - i += n; - if (std.mem.indexOfScalarPos(u8, r.buffer[0..seek], i, delimiter)) |end| { - std.mem.rotate(u8, r.buffer, seek); - r.seek = 0; - r.end += i; - return r.buffer[0 .. seek + end + 1]; - } - } else if (i != 0) { - std.mem.rotate(u8, r.buffer, seek); - r.seek = 0; - r.end += i; - } return error.StreamTooLong; } @@ -1665,6 +1650,23 @@ test "readAlloc when the backing reader provides one byte at a time" { try std.testing.expectEqualStrings(str, res); } +test "takeDelimiterInclusive when it rebases" { + const written_line = "ABCDEFGHIJKLMNOPQRSTUVWXYZ\n"; + var buffer: [128]u8 = undefined; + var tr: std.testing.Reader = .init(&buffer, &.{ + .{ .buffer = written_line }, + .{ .buffer = written_line }, + .{ .buffer = written_line }, + .{ .buffer = written_line }, + .{ .buffer = written_line }, + .{ .buffer = written_line }, + }); + const r = &tr.interface; + for (0..6) |_| { + try std.testing.expectEqualStrings(written_line, try r.takeDelimiterInclusive('\n')); + } +} + /// Provides a `Reader` implementation by passing data from an underlying /// reader through `Hasher.update`. /// diff --git a/lib/std/testing.zig b/lib/std/testing.zig index b60e780d40..8e7ffd3f23 100644 --- a/lib/std/testing.zig +++ b/lib/std/testing.zig @@ -1206,3 +1206,43 @@ pub inline fn fuzz( ) anyerror!void { return @import("root").fuzz(context, testOne, options); } + +/// A `std.io.Reader` that writes a predetermined list of buffers during `stream`. +pub const Reader = struct { + calls: []const Call, + interface: std.io.Reader, + next_call_index: usize, + next_offset: usize, + + pub const Call = struct { + buffer: []const u8, + }; + + pub fn init(buffer: []u8, calls: []const Call) Reader { + return .{ + .next_call_index = 0, + .next_offset = 0, + .interface = .{ + .vtable = &.{ .stream = stream }, + .buffer = buffer, + .seek = 0, + .end = 0, + }, + .calls = calls, + }; + } + + fn stream(io_r: *std.io.Reader, w: *std.io.Writer, limit: std.io.Limit) std.io.Reader.StreamError!usize { + const r: *Reader = @alignCast(@fieldParentPtr("interface", io_r)); + if (r.calls.len - r.next_call_index == 0) return error.EndOfStream; + const call = r.calls[r.next_call_index]; + const buffer = limit.sliceConst(call.buffer[r.next_offset..]); + const n = try w.write(buffer); + r.next_offset += n; + if (call.buffer.len - r.next_offset == 0) { + r.next_call_index += 1; + r.next_offset = 0; + } + return n; + } +};