authorgravatar for nash1111@noreply.codeberg.orgnash1111 <nash1111@noreply.codeberg.org> 2026-06-03 14:31:35+02:00
committergravatar for squeek502@hotmail.comRyan Liptak <squeek502@hotmail.com> 2026-06-03 14:31:35+02:00
log22aa2bd1e775d5ec144711642ad5c0d7c99b4398
tree31c1a4fc31f7bf64f225b6fbec8e9c05468018ef
parent420c1994b37a612fc37f011713ea01ee0497739a

std.json: honor max_value_len for sentinel strings (#30782)

Fixes `std.json` so `ParseOptions.max_value_len` is honored when parsing sentinel-terminated strings that require allocation/copying. Previously, the sentinel string path used `allocNextIntoArrayList`, which did not use the caller-provided `max_value_len`. This meant sentinel-terminated string parsing did not consistently follow the same allocation limit behavior as other allocating string paths. `max_value_len` is an allocation/copy limit, not a general limit on the length of returned values. Values that can be returned as references to the input buffer, or parsed without intermediate allocation, are not limited by it. Fixes #30579 Verified with: ```sh zig test lib/std/std.zig --zig-lib-dir lib --test-filter max_value_len ``` Co-authored-by: nash1111 <nash1111@users.noreply.github.com> Reviewed-on: https://codeberg.org/ziglang/zig/pulls/30782 Reviewed-by: Ryan Liptak <squeek502@noreply.codeberg.org>

2 files changed, 36 insertions(+), 2 deletions(-)

lib/std/json/static.zig+2-1
...@@ -32,6 +32,7 @@ pub const ParseOptions = struct {...@@ -32,6 +32,7 @@ pub const ParseOptions = struct {
32 /// The default for `parseFromSlice` or `parseFromTokenSource` with a `*std.json.Scanner` input32 /// The default for `parseFromSlice` or `parseFromTokenSource` with a `*std.json.Scanner` input
33 /// is the length of the input slice, which means `error.ValueTooLong` will never be returned.33 /// is the length of the input slice, which means `error.ValueTooLong` will never be returned.
34 /// The default for `parseFromTokenSource` with a `*std.json.Reader` is `std.json.default_max_value_len`.34 /// The default for `parseFromTokenSource` with a `*std.json.Reader` is `std.json.default_max_value_len`.
35 /// Ignored for values that don't need allocation or are not copied (see `allocate`).
35 /// Ignored for `parseFromValue` and `parseFromValueLeaky`.36 /// Ignored for `parseFromValue` and `parseFromValueLeaky`.
36 max_value_len: ?usize = null,37 max_value_len: ?usize = null,
3738
...@@ -495,7 +496,7 @@ pub fn innerParse(...@@ -495,7 +496,7 @@ pub fn innerParse(
495 if (ptrInfo.sentinel()) |s| {496 if (ptrInfo.sentinel()) |s| {
496 // Use our own array list so we can append the sentinel.497 // Use our own array list so we can append the sentinel.
497 var value_list = ArrayList(u8).init(allocator);498 var value_list = ArrayList(u8).init(allocator);
498 _ = try source.allocNextIntoArrayList(&value_list, .alloc_always);499 _ = try source.allocNextIntoArrayListMax(&value_list, .alloc_always, options.max_value_len.?);
499 return try value_list.toOwnedSliceSentinel(s);500 return try value_list.toOwnedSliceSentinel(s);
500 }501 }
501 if (ptrInfo.attrs.@"const") {502 if (ptrInfo.attrs.@"const") {
lib/std/json/static_test.zig+34-1
...@@ -772,7 +772,40 @@ test "parseFromTokenSource" {...@@ -772,7 +772,40 @@ test "parseFromTokenSource" {
772}772}
773773
774test "max_value_len" {774test "max_value_len" {
775 try testing.expectError(error.ValueTooLong, parseFromSlice([]u8, testing.allocator, "\"0123456789\"", .{ .max_value_len = 5 }));775 try testMaxValueLen([]u8, .alloc_if_needed);
776 try testMaxValueLen([]const u8, .alloc_always);
777 try testMaxValueLen([:0]u8, .alloc_if_needed);
778 try testMaxValueLen([:0]const u8, .alloc_if_needed);
779
780 // If the value can be returned as a reference to the buffer, max_value_len doesn't apply.
781 {
782 const parsed = try parseFromSlice([]const u8, testing.allocator, "\"123\"", .{ .max_value_len = 1 });
783 defer parsed.deinit();
784 try testing.expectEqualStrings("123", parsed.value);
785 }
786 // If the value is returned as a number without needing intermediate allocations, max_value_len doesn't apply.
787 {
788 const parsed = try parseFromSlice(u32, testing.allocator, "\"001\"", .{ .max_value_len = 1 });
789 defer parsed.deinit();
790 try testing.expectEqual(1, parsed.value);
791 }
792}
793
794fn testMaxValueLen(comptime T: type, when: Scanner.AllocWhen) !void {
795 const parsed = try parseFromSlice(T, testing.allocator, "\"12345\"", .{
796 .max_value_len = 5,
797 .allocate = when,
798 });
799 defer parsed.deinit();
800 try testing.expectEqualStrings("12345", parsed.value);
801
802 try testing.expectError(
803 error.ValueTooLong,
804 parseFromSlice(T, testing.allocator, "\"123456\"", .{
805 .max_value_len = 5,
806 .allocate = when,
807 }),
808 );
776}809}
777810
778test "parse into vector" {811test "parse into vector" {