authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-09-06 10:23:41+01:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-10-08 17:00:15-07:00
logbf58b4e41990973a72ef49163abe63d41c966e23
tree43cacbdf9ba9d128dc7f73b3c667c9c3b684a621
parent889942a8b74d563e51923d9d14a45e58445aa38f

std.Io.Reader: fix delimiter bugs

Fix `takeDelimiter` and `takeDelimiterExclusive` tossing too many bytes (#25132) Also add/improve test coverage for all delimiter and sentinel methods, update usages of `takeDelimiterExclusive` to not rely on the fixed bug, tweak a handful of doc comments, and slightly simplify some logic. I have not fixed #24950 in this commit because I am a little less certain about the appropriate solution there. Resolves: #25132 Co-authored-by: Andrew Kelley <andrew@ziglang.org>

3 files changed, 48 insertions(+), 24 deletions(-)

lib/std/Io/Reader.zig+44-18
...@@ -481,7 +481,6 @@ pub fn readVecAll(r: *Reader, data: [][]u8) Error!void {...@@ -481,7 +481,6 @@ pub fn readVecAll(r: *Reader, data: [][]u8) Error!void {
481/// is returned instead.481/// is returned instead.
482///482///
483/// See also:483/// See also:
484/// * `peek`
485/// * `toss`484/// * `toss`
486pub fn peek(r: *Reader, n: usize) Error![]u8 {485pub fn peek(r: *Reader, n: usize) Error![]u8 {
487 try r.fill(n);486 try r.fill(n);
...@@ -732,7 +731,7 @@ pub const DelimiterError = error{...@@ -732,7 +731,7 @@ pub const DelimiterError = error{
732};731};
733732
734/// Returns a slice of the next bytes of buffered data from the stream until733/// Returns a slice of the next bytes of buffered data from the stream until
735/// `sentinel` is found, advancing the seek position.734/// `sentinel` is found, advancing the seek position past the sentinel.
736///735///
737/// Returned slice has a sentinel.736/// Returned slice has a sentinel.
738///737///
...@@ -765,7 +764,7 @@ pub fn peekSentinel(r: *Reader, comptime sentinel: u8) DelimiterError![:sentinel...@@ -765,7 +764,7 @@ pub fn peekSentinel(r: *Reader, comptime sentinel: u8) DelimiterError![:sentinel
765}764}
766765
767/// Returns a slice of the next bytes of buffered data from the stream until766/// Returns a slice of the next bytes of buffered data from the stream until
768/// `delimiter` is found, advancing the seek position.767/// `delimiter` is found, advancing the seek position past the delimiter.
769///768///
770/// Returned slice includes the delimiter as the last byte.769/// Returned slice includes the delimiter as the last byte.
771///770///
...@@ -818,7 +817,8 @@ pub fn peekDelimiterInclusive(r: *Reader, delimiter: u8) DelimiterError![]u8 {...@@ -818,7 +817,8 @@ pub fn peekDelimiterInclusive(r: *Reader, delimiter: u8) DelimiterError![]u8 {
818}817}
819818
820/// Returns a slice of the next bytes of buffered data from the stream until819/// Returns a slice of the next bytes of buffered data from the stream until
821/// `delimiter` is found, advancing the seek position up to the delimiter.820/// `delimiter` is found, advancing the seek position up to (but not past)
821/// the delimiter.
822///822///
823/// Returned slice excludes the delimiter. End-of-stream is treated equivalent823/// Returned slice excludes the delimiter. End-of-stream is treated equivalent
824/// to a delimiter, unless it would result in a length 0 return value, in which824/// to a delimiter, unless it would result in a length 0 return value, in which
...@@ -832,20 +832,13 @@ pub fn peekDelimiterInclusive(r: *Reader, delimiter: u8) DelimiterError![]u8 {...@@ -832,20 +832,13 @@ pub fn peekDelimiterInclusive(r: *Reader, delimiter: u8) DelimiterError![]u8 {
832/// Invalidates previously returned values from `peek`.832/// Invalidates previously returned values from `peek`.
833///833///
834/// See also:834/// See also:
835/// * `takeDelimiter`
835/// * `takeDelimiterInclusive`836/// * `takeDelimiterInclusive`
836/// * `peekDelimiterExclusive`837/// * `peekDelimiterExclusive`
837pub fn takeDelimiterExclusive(r: *Reader, delimiter: u8) DelimiterError![]u8 {838pub fn takeDelimiterExclusive(r: *Reader, delimiter: u8) DelimiterError![]u8 {
838 const result = r.peekDelimiterInclusive(delimiter) catch |err| switch (err) {839 const result = try r.peekDelimiterExclusive(delimiter);
839 error.EndOfStream => {
840 const remaining = r.buffer[r.seek..r.end];
841 if (remaining.len == 0) return error.EndOfStream;
842 r.toss(remaining.len);
843 return remaining;
844 },
845 else => |e| return e,
846 };
847 r.toss(result.len);840 r.toss(result.len);
848 return result[0 .. result.len - 1];841 return result;
849}842}
850843
851/// Returns a slice of the next bytes of buffered data from the stream until844/// Returns a slice of the next bytes of buffered data from the stream until
...@@ -866,7 +859,7 @@ pub fn takeDelimiterExclusive(r: *Reader, delimiter: u8) DelimiterError![]u8 {...@@ -866,7 +859,7 @@ pub fn takeDelimiterExclusive(r: *Reader, delimiter: u8) DelimiterError![]u8 {
866/// * `takeDelimiterInclusive`859/// * `takeDelimiterInclusive`
867/// * `takeDelimiterExclusive`860/// * `takeDelimiterExclusive`
868pub fn takeDelimiter(r: *Reader, delimiter: u8) error{ ReadFailed, StreamTooLong }!?[]u8 {861pub fn takeDelimiter(r: *Reader, delimiter: u8) error{ ReadFailed, StreamTooLong }!?[]u8 {
869 const result = r.peekDelimiterInclusive(delimiter) catch |err| switch (err) {862 const inclusive = r.peekDelimiterInclusive(delimiter) catch |err| switch (err) {
870 error.EndOfStream => {863 error.EndOfStream => {
871 const remaining = r.buffer[r.seek..r.end];864 const remaining = r.buffer[r.seek..r.end];
872 if (remaining.len == 0) return null;865 if (remaining.len == 0) return null;
...@@ -875,8 +868,8 @@ pub fn takeDelimiter(r: *Reader, delimiter: u8) error{ ReadFailed, StreamTooLong...@@ -875,8 +868,8 @@ pub fn takeDelimiter(r: *Reader, delimiter: u8) error{ ReadFailed, StreamTooLong
875 },868 },
876 else => |e| return e,869 else => |e| return e,
877 };870 };
878 r.toss(result.len + 1);871 r.toss(inclusive.len);
879 return result[0 .. result.len - 1];872 return inclusive[0 .. inclusive.len - 1];
880}873}
881874
882/// Returns a slice of the next bytes of buffered data from the stream until875/// Returns a slice of the next bytes of buffered data from the stream until
...@@ -1403,6 +1396,9 @@ test peekSentinel {...@@ -1403,6 +1396,9 @@ test peekSentinel {
1403 var r: Reader = .fixed("ab\nc");1396 var r: Reader = .fixed("ab\nc");
1404 try testing.expectEqualStrings("ab", try r.peekSentinel('\n'));1397 try testing.expectEqualStrings("ab", try r.peekSentinel('\n'));
1405 try testing.expectEqualStrings("ab", try r.peekSentinel('\n'));1398 try testing.expectEqualStrings("ab", try r.peekSentinel('\n'));
1399 r.toss(3);
1400 try testing.expectError(error.EndOfStream, r.peekSentinel('\n'));
1401 try testing.expectEqualStrings("c", try r.peek(1));
1406}1402}
14071403
1408test takeDelimiterInclusive {1404test takeDelimiterInclusive {
...@@ -1417,22 +1413,52 @@ test peekDelimiterInclusive {...@@ -1417,22 +1413,52 @@ test peekDelimiterInclusive {
1417 try testing.expectEqualStrings("ab\n", try r.peekDelimiterInclusive('\n'));1413 try testing.expectEqualStrings("ab\n", try r.peekDelimiterInclusive('\n'));
1418 r.toss(3);1414 r.toss(3);
1419 try testing.expectError(error.EndOfStream, r.peekDelimiterInclusive('\n'));1415 try testing.expectError(error.EndOfStream, r.peekDelimiterInclusive('\n'));
1416 try testing.expectEqualStrings("c", try r.peek(1));
1420}1417}
14211418
1422test takeDelimiterExclusive {1419test takeDelimiterExclusive {
1423 var r: Reader = .fixed("ab\nc");1420 var r: Reader = .fixed("ab\nc");
1421
1424 try testing.expectEqualStrings("ab", try r.takeDelimiterExclusive('\n'));1422 try testing.expectEqualStrings("ab", try r.takeDelimiterExclusive('\n'));
1423 try testing.expectEqualStrings("", try r.takeDelimiterExclusive('\n'));
1424 try testing.expectEqualStrings("", try r.takeDelimiterExclusive('\n'));
1425 try testing.expectEqualStrings("\n", try r.take(1));
1426
1425 try testing.expectEqualStrings("c", try r.takeDelimiterExclusive('\n'));1427 try testing.expectEqualStrings("c", try r.takeDelimiterExclusive('\n'));
1426 try testing.expectError(error.EndOfStream, r.takeDelimiterExclusive('\n'));1428 try testing.expectError(error.EndOfStream, r.takeDelimiterExclusive('\n'));
1427}1429}
14281430
1429test peekDelimiterExclusive {1431test peekDelimiterExclusive {
1430 var r: Reader = .fixed("ab\nc");1432 var r: Reader = .fixed("ab\nc");
1433
1431 try testing.expectEqualStrings("ab", try r.peekDelimiterExclusive('\n'));1434 try testing.expectEqualStrings("ab", try r.peekDelimiterExclusive('\n'));
1432 try testing.expectEqualStrings("ab", try r.peekDelimiterExclusive('\n'));1435 try testing.expectEqualStrings("ab", try r.peekDelimiterExclusive('\n'));
1433 r.toss(3);1436 r.toss(2);
1437 try testing.expectEqualStrings("", try r.peekDelimiterExclusive('\n'));
1438 try testing.expectEqualStrings("\n", try r.take(1));
1439
1434 try testing.expectEqualStrings("c", try r.peekDelimiterExclusive('\n'));1440 try testing.expectEqualStrings("c", try r.peekDelimiterExclusive('\n'));
1435 try testing.expectEqualStrings("c", try r.peekDelimiterExclusive('\n'));1441 try testing.expectEqualStrings("c", try r.peekDelimiterExclusive('\n'));
1442 r.toss(1);
1443 try testing.expectError(error.EndOfStream, r.peekDelimiterExclusive('\n'));
1444}
1445
1446test takeDelimiter {
1447 var r: Reader = .fixed("ab\nc\n\nd");
1448 try testing.expectEqualStrings("ab", (try r.takeDelimiter('\n')).?);
1449 try testing.expectEqualStrings("c", (try r.takeDelimiter('\n')).?);
1450 try testing.expectEqualStrings("", (try r.takeDelimiter('\n')).?);
1451 try testing.expectEqualStrings("d", (try r.takeDelimiter('\n')).?);
1452 try testing.expectEqual(null, try r.takeDelimiter('\n'));
1453 try testing.expectEqual(null, try r.takeDelimiter('\n'));
1454
1455 r = .fixed("ab\nc\n\nd\n"); // one trailing newline does not affect behavior
1456 try testing.expectEqualStrings("ab", (try r.takeDelimiter('\n')).?);
1457 try testing.expectEqualStrings("c", (try r.takeDelimiter('\n')).?);
1458 try testing.expectEqualStrings("", (try r.takeDelimiter('\n')).?);
1459 try testing.expectEqualStrings("d", (try r.takeDelimiter('\n')).?);
1460 try testing.expectEqual(null, try r.takeDelimiter('\n'));
1461 try testing.expectEqual(null, try r.takeDelimiter('\n'));
1436}1462}
14371463
1438test streamDelimiter {1464test streamDelimiter {
lib/std/net.zig+3-2
...@@ -1396,7 +1396,7 @@ fn parseHosts(...@@ -1396,7 +1396,7 @@ fn parseHosts(
1396 br: *Io.Reader,1396 br: *Io.Reader,
1397) error{ OutOfMemory, ReadFailed }!void {1397) error{ OutOfMemory, ReadFailed }!void {
1398 while (true) {1398 while (true) {
1399 const line = br.takeDelimiterExclusive('\n') catch |err| switch (err) {1399 const line = br.takeDelimiter('\n') catch |err| switch (err) {
1400 error.StreamTooLong => {1400 error.StreamTooLong => {
1401 // Skip lines that are too long.1401 // Skip lines that are too long.
1402 _ = br.discardDelimiterInclusive('\n') catch |e| switch (e) {1402 _ = br.discardDelimiterInclusive('\n') catch |e| switch (e) {
...@@ -1406,7 +1406,8 @@ fn parseHosts(...@@ -1406,7 +1406,8 @@ fn parseHosts(
1406 continue;1406 continue;
1407 },1407 },
1408 error.ReadFailed => return error.ReadFailed,1408 error.ReadFailed => return error.ReadFailed,
1409 error.EndOfStream => break,1409 } orelse {
1410 break; // end of stream
1410 };1411 };
1411 var split_it = mem.splitScalar(u8, line, '#');1412 var split_it = mem.splitScalar(u8, line, '#');
1412 const no_comment_line = split_it.first();1413 const no_comment_line = split_it.first();
lib/std/zig/system/linux.zig+1-4
...@@ -359,14 +359,11 @@ fn CpuinfoParser(comptime impl: anytype) type {...@@ -359,14 +359,11 @@ fn CpuinfoParser(comptime impl: anytype) type {
359 return struct {359 return struct {
360 fn parse(arch: Target.Cpu.Arch, reader: *std.Io.Reader) !?Target.Cpu {360 fn parse(arch: Target.Cpu.Arch, reader: *std.Io.Reader) !?Target.Cpu {
361 var obj: impl = .{};361 var obj: impl = .{};
362 while (reader.takeDelimiterExclusive('\n')) |line| {362 while (try reader.takeDelimiter('\n')) |line| {
363 const colon_pos = mem.indexOfScalar(u8, line, ':') orelse continue;363 const colon_pos = mem.indexOfScalar(u8, line, ':') orelse continue;
364 const key = mem.trimEnd(u8, line[0..colon_pos], " \t");364 const key = mem.trimEnd(u8, line[0..colon_pos], " \t");
365 const value = mem.trimStart(u8, line[colon_pos + 1 ..], " \t");365 const value = mem.trimStart(u8, line[colon_pos + 1 ..], " \t");
366 if (!try obj.line_hook(key, value)) break;366 if (!try obj.line_hook(key, value)) break;
367 } else |err| switch (err) {
368 error.EndOfStream => {},
369 else => |e| return e,
370 }367 }
371 return obj.finalize(arch);368 return obj.finalize(arch);
372 }369 }