mirror of
https://github.com/ziglang/zig.git
synced 2025-12-06 06:13:07 +00:00
Reader.peekDelimiterInclusive: Fix handling of stream implementations that return 0
Previously, the logic in peekDelimiterInclusive (when the delimiter was not found in the existing buffer) used the `n` returned from `r.vtable.stream` as the length of the slice to check, but it's valid for `vtable.stream` implementations to return 0 if they wrote to the buffer instead of `w`. In that scenario, the `indexOfScalarPos` would be given a 0-length slice so it would never be able to find the delimiter. This commit changes the logic to assume that `r.vtable.stream` can both: - return 0, and - modify seek/end (i.e. it's also valid for a `vtable.stream` implementation to rebase) Also introduces `std.testing.ReaderIndirect` which helps in being able to test against Reader implementations that return 0 from `stream`/`readVec` Fixes #25428
This commit is contained in:
parent
60be67d3c0
commit
328ae41468
@ -795,13 +795,14 @@ pub fn takeDelimiterInclusive(r: *Reader, delimiter: u8) DelimiterError![]u8 {
|
|||||||
pub fn peekDelimiterInclusive(r: *Reader, delimiter: u8) DelimiterError![]u8 {
|
pub fn peekDelimiterInclusive(r: *Reader, delimiter: u8) DelimiterError![]u8 {
|
||||||
const buffer = r.buffer[0..r.end];
|
const buffer = r.buffer[0..r.end];
|
||||||
const seek = r.seek;
|
const seek = r.seek;
|
||||||
if (std.mem.indexOfScalarPos(u8, buffer, seek, delimiter)) |end| {
|
if (std.mem.indexOfScalarPos(u8, buffer, seek, delimiter)) |delimiter_index| {
|
||||||
@branchHint(.likely);
|
@branchHint(.likely);
|
||||||
return buffer[seek .. end + 1];
|
return buffer[seek .. delimiter_index + 1];
|
||||||
}
|
}
|
||||||
// TODO take a parameter for max search length rather than relying on buffer capacity
|
// TODO take a parameter for max search length rather than relying on buffer capacity
|
||||||
try rebase(r, r.buffer.len);
|
try rebase(r, r.buffer.len);
|
||||||
while (r.buffer.len - r.end != 0) {
|
while (r.buffer.len - r.end != 0) {
|
||||||
|
const existing_buffered_len = r.end - r.seek;
|
||||||
const end_cap = r.buffer[r.end..];
|
const end_cap = r.buffer[r.end..];
|
||||||
var writer: Writer = .fixed(end_cap);
|
var writer: Writer = .fixed(end_cap);
|
||||||
const n = r.vtable.stream(r, &writer, .limited(end_cap.len)) catch |err| switch (err) {
|
const n = r.vtable.stream(r, &writer, .limited(end_cap.len)) catch |err| switch (err) {
|
||||||
@ -809,8 +810,8 @@ pub fn peekDelimiterInclusive(r: *Reader, delimiter: u8) DelimiterError![]u8 {
|
|||||||
else => |e| return e,
|
else => |e| return e,
|
||||||
};
|
};
|
||||||
r.end += n;
|
r.end += n;
|
||||||
if (std.mem.indexOfScalarPos(u8, end_cap[0..n], 0, delimiter)) |end| {
|
if (std.mem.indexOfScalarPos(u8, r.buffer[0..r.end], r.seek + existing_buffered_len, delimiter)) |delimiter_index| {
|
||||||
return r.buffer[0 .. r.end - n + end + 1];
|
return r.buffer[r.seek .. delimiter_index + 1];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return error.StreamTooLong;
|
return error.StreamTooLong;
|
||||||
@ -1601,6 +1602,18 @@ test "readSliceShort with smaller buffer than Reader" {
|
|||||||
try testing.expectEqualStrings(str, &buf);
|
try testing.expectEqualStrings(str, &buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "readSliceShort with indirect reader" {
|
||||||
|
var r: Reader = .fixed("HelloFren");
|
||||||
|
var ri_buf: [3]u8 = undefined;
|
||||||
|
var ri: std.testing.ReaderIndirect = .init(&r, &ri_buf);
|
||||||
|
var buf: [5]u8 = undefined;
|
||||||
|
try testing.expectEqual(5, try ri.interface.readSliceShort(&buf));
|
||||||
|
try testing.expectEqualStrings("Hello", buf[0..5]);
|
||||||
|
try testing.expectEqual(4, try ri.interface.readSliceShort(&buf));
|
||||||
|
try testing.expectEqualStrings("Fren", buf[0..4]);
|
||||||
|
try testing.expectEqual(0, try ri.interface.readSliceShort(&buf));
|
||||||
|
}
|
||||||
|
|
||||||
test readVec {
|
test readVec {
|
||||||
var r: Reader = .fixed(std.ascii.letters);
|
var r: Reader = .fixed(std.ascii.letters);
|
||||||
var flat_buffer: [52]u8 = undefined;
|
var flat_buffer: [52]u8 = undefined;
|
||||||
@ -1701,6 +1714,26 @@ test "takeDelimiterInclusive when it rebases" {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "takeDelimiterInclusive on an indirect reader when it rebases" {
|
||||||
|
const written_line = "ABCDEFGHIJKLMNOPQRSTUVWXYZ\n";
|
||||||
|
var buffer: [128]u8 = undefined;
|
||||||
|
var tr: std.testing.Reader = .init(&buffer, &.{
|
||||||
|
.{ .buffer = written_line[0..4] },
|
||||||
|
.{ .buffer = written_line[4..] },
|
||||||
|
.{ .buffer = written_line },
|
||||||
|
.{ .buffer = written_line },
|
||||||
|
.{ .buffer = written_line },
|
||||||
|
.{ .buffer = written_line },
|
||||||
|
.{ .buffer = written_line },
|
||||||
|
});
|
||||||
|
var indirect_buffer: [128]u8 = undefined;
|
||||||
|
var tri: std.testing.ReaderIndirect = .init(&tr.interface, &indirect_buffer);
|
||||||
|
const r = &tri.interface;
|
||||||
|
for (0..6) |_| {
|
||||||
|
try std.testing.expectEqualStrings(written_line, try r.takeDelimiterInclusive('\n'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
test "takeStruct and peekStruct packed" {
|
test "takeStruct and peekStruct packed" {
|
||||||
var r: Reader = .fixed(&.{ 0b11110000, 0b00110011 });
|
var r: Reader = .fixed(&.{ 0b11110000, 0b00110011 });
|
||||||
const S = packed struct(u16) { a: u2, b: u6, c: u7, d: u1 };
|
const S = packed struct(u16) { a: u2, b: u6, c: u7, d: u1 };
|
||||||
|
|||||||
@ -1242,3 +1242,63 @@ pub const Reader = struct {
|
|||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// A `std.Io.Reader` that gets its data from another `std.Io.Reader`, and always
|
||||||
|
/// writes to its own buffer (and returns 0) during `stream` and `readVec`.
|
||||||
|
pub const ReaderIndirect = struct {
|
||||||
|
in: *std.Io.Reader,
|
||||||
|
interface: std.Io.Reader,
|
||||||
|
|
||||||
|
pub fn init(in: *std.Io.Reader, buffer: []u8) ReaderIndirect {
|
||||||
|
return .{
|
||||||
|
.in = in,
|
||||||
|
.interface = .{
|
||||||
|
.vtable = &.{
|
||||||
|
.stream = stream,
|
||||||
|
.readVec = readVec,
|
||||||
|
},
|
||||||
|
.buffer = buffer,
|
||||||
|
.seek = 0,
|
||||||
|
.end = 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn readVec(r: *std.Io.Reader, _: [][]u8) std.Io.Reader.Error!usize {
|
||||||
|
try streamInner(r);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stream(r: *std.Io.Reader, _: *std.Io.Writer, _: std.Io.Limit) std.Io.Reader.StreamError!usize {
|
||||||
|
try streamInner(r);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn streamInner(r: *std.Io.Reader) std.Io.Reader.Error!void {
|
||||||
|
const r_indirect: *ReaderIndirect = @alignCast(@fieldParentPtr("interface", r));
|
||||||
|
|
||||||
|
// If there's no room remaining in the buffer at all, make room.
|
||||||
|
if (r.buffer.len == r.end) {
|
||||||
|
try r.rebase(r.buffer.len);
|
||||||
|
}
|
||||||
|
|
||||||
|
var writer: std.Io.Writer = .{
|
||||||
|
.buffer = r.buffer,
|
||||||
|
.end = r.end,
|
||||||
|
.vtable = &.{
|
||||||
|
.drain = std.Io.Writer.unreachableDrain,
|
||||||
|
.rebase = std.Io.Writer.unreachableRebase,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
defer r.end = writer.end;
|
||||||
|
|
||||||
|
r_indirect.in.streamExact(&writer, r.buffer.len - r.end) catch |err| switch (err) {
|
||||||
|
// Only forward EndOfStream if no new bytes were written to the buffer
|
||||||
|
error.EndOfStream => |e| if (r.end == writer.end) {
|
||||||
|
return e;
|
||||||
|
},
|
||||||
|
error.WriteFailed => unreachable,
|
||||||
|
else => |e| return e,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user