From 2ec5523d5a70db80bac8563e1f06d3b164f53db3 Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Tue, 15 Sep 2026 15:18:34 -0700 Subject: [PATCH] Reader.allocRemaining: only error with StreamTooLong when limit is exceeded Follow up to #36708 and #36692. The intended behavior of this function was what was previously documented: that StreamTooLong was only returned when the limit was *exceeded*. However, that was never implemented. This commit implements the intended behavior, with the added caveat that `limit + 1` bytes will be discarded if the limit is exceeded. --- lib/std/Io/Reader.zig | 37 +++++++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/lib/std/Io/Reader.zig b/lib/std/Io/Reader.zig index da1894cc83b5e01e685cfce8cd96242ec943dcc1..372a7890b734638cf9fd7d4a8f99ca5a29c13c01 100644 --- a/lib/std/Io/Reader.zig +++ b/lib/std/Io/Reader.zig @@ -287,16 +287,20 @@ pub const LimitedAllocError = Allocator.Error || ShortError || error{StreamTooLo /// Transfers all bytes from the current position to the end of the stream, up /// to `limit`, returning them as a caller-owned allocated slice. /// -/// If `limit` is reached or exceeded, `error.StreamTooLong` is returned -/// instead. In such case, the next byte that would be read will be the first -/// one to exceed `limit`, and all preceeding bytes have been discarded. +/// If `limit` is exceeded, `error.StreamTooLong` is returned instead. +/// In such case, the next byte that would be read will be one byte past the +/// first one to exceed `limit`, and all preceeding bytes have been discarded. /// /// See also: /// * `appendRemaining` pub fn allocRemaining(r: *Reader, gpa: Allocator, limit: Limit) LimitedAllocError![]u8 { var buffer: ArrayList(u8) = .empty; defer buffer.deinit(gpa); - try appendRemaining(r, gpa, &buffer, limit); + + try appendRemaining(r, gpa, &buffer, switch (limit) { + .unlimited => limit, + .nothing, _ => .limited(@backingInt(limit) + 1), + }); return buffer.toOwnedSlice(gpa); } @@ -1894,6 +1898,31 @@ test "takeStruct and peekStruct packed" { try testing.expectError(error.EndOfStream, r.takeStruct(S, .little)); } +test allocRemaining { + { + var r: Reader = .fixed("abc"); + const str = try r.allocRemaining(testing.allocator, .unlimited); + defer testing.allocator.free(str); + try testing.expectEqualStrings("abc", str); + } + { + var r: Reader = .fixed("abc"); + const str = try r.allocRemaining(testing.allocator, .limited(5)); + defer testing.allocator.free(str); + try testing.expectEqualStrings("abc", str); + } + { + var r: Reader = .fixed("abc"); + const str = try r.allocRemaining(testing.allocator, .limited(3)); + defer testing.allocator.free(str); + try testing.expectEqualStrings("abc", str); + } + { + var r: Reader = .fixed("abcd"); + try testing.expectError(error.StreamTooLong, r.allocRemaining(testing.allocator, .limited(3))); + } +} + /// Provides a `Reader` implementation by passing data from an underlying /// reader through `Hasher.update`. /// -- 2.54.0