authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-17 18:35:38+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2025-07-17 18:35:38+02:00
loga8dc32e4ec5a1370b0b8d1d0ae3bdc5ad3add49f
treef00931ca6eb57e94e35662ad25f062094dfd6a81
parentb7d7446fbbfc35f291ffb641470233a7a9e18b9b
parent155ab56cc63d775cca77d7bedbcac6ee65558818
signaturebadge-check Signed by PGP key B5690EEEBB952194

Merge pull request #24493 from ziglang/readSliceShort

std.Io.Reader: fix readSliceShort with smaller buffer than Reader

3 files changed, 43 insertions(+), 66 deletions(-)

lib/std/Io/Reader.zig+35-61
...@@ -593,48 +593,29 @@ pub fn readSliceAll(r: *Reader, buffer: []u8) Error!void {...@@ -593,48 +593,29 @@ pub fn readSliceAll(r: *Reader, buffer: []u8) Error!void {
593/// See also:593/// See also:
594/// * `readSliceAll`594/// * `readSliceAll`
595pub fn readSliceShort(r: *Reader, buffer: []u8) ShortError!usize {595pub fn readSliceShort(r: *Reader, buffer: []u8) ShortError!usize {
596 const in_buffer = r.buffer[r.seek..r.end];596 var i: usize = 0;
597 const copy_len = @min(buffer.len, in_buffer.len);
598 @memcpy(buffer[0..copy_len], in_buffer[0..copy_len]);
599 if (buffer.len - copy_len == 0) {
600 r.seek += copy_len;
601 return buffer.len;
602 }
603 var i: usize = copy_len;
604 r.end = 0;
605 r.seek = 0;
606 while (true) {597 while (true) {
598 const buffer_contents = r.buffer[r.seek..r.end];
599 const dest = buffer[i..];
600 const copy_len = @min(dest.len, buffer_contents.len);
601 @memcpy(dest[0..copy_len], buffer_contents[0..copy_len]);
602 if (dest.len - copy_len == 0) {
603 @branchHint(.likely);
604 r.seek += copy_len;
605 return buffer.len;
606 }
607 i += copy_len;
608 r.end = 0;
609 r.seek = 0;
607 const remaining = buffer[i..];610 const remaining = buffer[i..];
608 var wrapper: Writer.VectorWrapper = .{611 const new_remaining_len = readVecInner(r, &.{}, remaining, remaining.len) catch |err| switch (err) {
609 .it = .{
610 .first = remaining,
611 .last = r.buffer,
612 },
613 .writer = .{
614 .buffer = if (remaining.len >= r.buffer.len) remaining else r.buffer,
615 .vtable = Writer.VectorWrapper.vtable,
616 },
617 };
618 const n = r.vtable.stream(r, &wrapper.writer, .unlimited) catch |err| switch (err) {
619 error.WriteFailed => {
620 if (!wrapper.used) {
621 assert(r.seek == 0);
622 r.seek = remaining.len;
623 r.end = wrapper.writer.end;
624 @memcpy(remaining, r.buffer[0..remaining.len]);
625 }
626 return buffer.len;
627 },
628 error.EndOfStream => return i,612 error.EndOfStream => return i,
629 error.ReadFailed => return error.ReadFailed,613 error.ReadFailed => return error.ReadFailed,
630 };614 };
631 if (n < remaining.len) {615 if (new_remaining_len == 0) return buffer.len;
632 i += n;616 i += remaining.len - new_remaining_len;
633 continue;
634 }
635 r.end = n - remaining.len;
636 return buffer.len;
637 }617 }
618 return buffer.len;
638}619}
639620
640/// Fill `buffer` with the next `buffer.len` bytes from the stream, advancing621/// Fill `buffer` with the next `buffer.len` bytes from the stream, advancing
...@@ -1640,6 +1621,19 @@ test readSliceShort {...@@ -1640,6 +1621,19 @@ test readSliceShort {
1640 try testing.expectEqual(0, try r.readSliceShort(&buf));1621 try testing.expectEqual(0, try r.readSliceShort(&buf));
1641}1622}
16421623
1624test "readSliceShort with smaller buffer than Reader" {
1625 var reader_buf: [15]u8 = undefined;
1626 const str = "This is a test";
1627 var one_byte_stream: testing.Reader = .init(&reader_buf, &.{
1628 .{ .buffer = str },
1629 });
1630 one_byte_stream.artificial_limit = .limited(1);
1631
1632 var buf: [14]u8 = undefined;
1633 try testing.expectEqual(14, try one_byte_stream.interface.readSliceShort(&buf));
1634 try testing.expectEqualStrings(str, &buf);
1635}
1636
1643test readVec {1637test readVec {
1644 var r: Reader = .fixed(std.ascii.letters);1638 var r: Reader = .fixed(std.ascii.letters);
1645 var flat_buffer: [52]u8 = undefined;1639 var flat_buffer: [52]u8 = undefined;
...@@ -1702,33 +1696,13 @@ fn failingDiscard(r: *Reader, limit: Limit) Error!usize {...@@ -1702,33 +1696,13 @@ fn failingDiscard(r: *Reader, limit: Limit) Error!usize {
1702}1696}
17031697
1704test "readAlloc when the backing reader provides one byte at a time" {1698test "readAlloc when the backing reader provides one byte at a time" {
1705 const OneByteReader = struct {
1706 str: []const u8,
1707 i: usize,
1708 reader: Reader,
1709
1710 fn stream(r: *Reader, w: *Writer, limit: Limit) StreamError!usize {
1711 assert(@intFromEnum(limit) >= 1);
1712 const self: *@This() = @fieldParentPtr("reader", r);
1713 if (self.str.len - self.i == 0) return error.EndOfStream;
1714 try w.writeByte(self.str[self.i]);
1715 self.i += 1;
1716 return 1;
1717 }
1718 };
1719 const str = "This is a test";1699 const str = "This is a test";
1720 var tiny_buffer: [1]u8 = undefined;1700 var tiny_buffer: [1]u8 = undefined;
1721 var one_byte_stream: OneByteReader = .{1701 var one_byte_stream: testing.Reader = .init(&tiny_buffer, &.{
1722 .str = str,1702 .{ .buffer = str },
1723 .i = 0,1703 });
1724 .reader = .{1704 one_byte_stream.artificial_limit = .limited(1);
1725 .buffer = &tiny_buffer,1705 const res = try one_byte_stream.interface.allocRemaining(std.testing.allocator, .unlimited);
1726 .vtable = &.{ .stream = OneByteReader.stream },
1727 .seek = 0,
1728 .end = 0,
1729 },
1730 };
1731 const res = try one_byte_stream.reader.allocRemaining(std.testing.allocator, .unlimited);
1732 defer std.testing.allocator.free(res);1706 defer std.testing.allocator.free(res);
1733 try std.testing.expectEqualStrings(str, res);1707 try std.testing.expectEqualStrings(str, res);
1734}1708}
lib/std/testing.zig+6-4
...@@ -1207,12 +1207,14 @@ pub inline fn fuzz(...@@ -1207,12 +1207,14 @@ pub inline fn fuzz(
1207 return @import("root").fuzz(context, testOne, options);1207 return @import("root").fuzz(context, testOne, options);
1208}1208}
12091209
1210/// A `std.io.Reader` that writes a predetermined list of buffers during `stream`.1210/// A `std.Io.Reader` that writes a predetermined list of buffers during `stream`.
1211pub const Reader = struct {1211pub const Reader = struct {
1212 calls: []const Call,1212 calls: []const Call,
1213 interface: std.io.Reader,1213 interface: std.Io.Reader,
1214 next_call_index: usize,1214 next_call_index: usize,
1215 next_offset: usize,1215 next_offset: usize,
1216 /// Further reduces how many bytes are written in each `stream` call.
1217 artificial_limit: std.Io.Limit = .unlimited,
12161218
1217 pub const Call = struct {1219 pub const Call = struct {
1218 buffer: []const u8,1220 buffer: []const u8,
...@@ -1232,11 +1234,11 @@ pub const Reader = struct {...@@ -1232,11 +1234,11 @@ pub const Reader = struct {
1232 };1234 };
1233 }1235 }
12341236
1235 fn stream(io_r: *std.io.Reader, w: *std.io.Writer, limit: std.io.Limit) std.io.Reader.StreamError!usize {1237 fn stream(io_r: *std.Io.Reader, w: *std.Io.Writer, limit: std.Io.Limit) std.Io.Reader.StreamError!usize {
1236 const r: *Reader = @alignCast(@fieldParentPtr("interface", io_r));1238 const r: *Reader = @alignCast(@fieldParentPtr("interface", io_r));
1237 if (r.calls.len - r.next_call_index == 0) return error.EndOfStream;1239 if (r.calls.len - r.next_call_index == 0) return error.EndOfStream;
1238 const call = r.calls[r.next_call_index];1240 const call = r.calls[r.next_call_index];
1239 const buffer = limit.sliceConst(call.buffer[r.next_offset..]);1241 const buffer = r.artificial_limit.sliceConst(limit.sliceConst(call.buffer[r.next_offset..]));
1240 const n = try w.write(buffer);1242 const n = try w.write(buffer);
1241 r.next_offset += n;1243 r.next_offset += n;
1242 if (call.buffer.len - r.next_offset == 0) {1244 if (call.buffer.len - r.next_offset == 0) {
lib/std/zig.zig+2-1
...@@ -536,7 +536,8 @@ pub fn readSourceFileToEndAlloc(gpa: Allocator, file_reader: *std.fs.File.Reader...@@ -536,7 +536,8 @@ pub fn readSourceFileToEndAlloc(gpa: Allocator, file_reader: *std.fs.File.Reader
536536
537 if (file_reader.getSize()) |size| {537 if (file_reader.getSize()) |size| {
538 const casted_size = std.math.cast(u32, size) orelse return error.StreamTooLong;538 const casted_size = std.math.cast(u32, size) orelse return error.StreamTooLong;
539 try buffer.ensureTotalCapacityPrecise(gpa, casted_size);539 // +1 to avoid resizing for the null byte added in toOwnedSliceSentinel below.
540 try buffer.ensureTotalCapacityPrecise(gpa, casted_size + 1);
540 } else |_| {}541 } else |_| {}
541542
542 try file_reader.interface.appendRemaining(gpa, .@"2", &buffer, .limited(max_src_size));543 try file_reader.interface.appendRemaining(gpa, .@"2", &buffer, .limited(max_src_size));