zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

commit 22aa2bd1e775d5ec144711642ad5c0d7c99b4398 (tree)
parent 420c1994b37a612fc37f011713ea01ee0497739a
Author: nash1111 <nash1111@noreply.codeberg.org>
Date:   Wed,  3 Jun 2026 14:31:35 +0200

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>

Diffstat:
Mlib/std/json/static.zig | 3++-
Mlib/std/json/static_test.zig | 35++++++++++++++++++++++++++++++++++-
2 files changed, 36 insertions(+), 2 deletions(-)

diff --git a/lib/std/json/static.zig b/lib/std/json/static.zig @@ -32,6 +32,7 @@ pub const ParseOptions = struct { /// The default for `parseFromSlice` or `parseFromTokenSource` with a `*std.json.Scanner` input /// is the length of the input slice, which means `error.ValueTooLong` will never be returned. /// The default for `parseFromTokenSource` with a `*std.json.Reader` is `std.json.default_max_value_len`. + /// Ignored for values that don't need allocation or are not copied (see `allocate`). /// Ignored for `parseFromValue` and `parseFromValueLeaky`. max_value_len: ?usize = null, @@ -495,7 +496,7 @@ pub fn innerParse( if (ptrInfo.sentinel()) |s| { // Use our own array list so we can append the sentinel. var value_list = ArrayList(u8).init(allocator); - _ = try source.allocNextIntoArrayList(&value_list, .alloc_always); + _ = try source.allocNextIntoArrayListMax(&value_list, .alloc_always, options.max_value_len.?); return try value_list.toOwnedSliceSentinel(s); } if (ptrInfo.attrs.@"const") { diff --git a/lib/std/json/static_test.zig b/lib/std/json/static_test.zig @@ -772,7 +772,40 @@ test "parseFromTokenSource" { } test "max_value_len" { - try testing.expectError(error.ValueTooLong, parseFromSlice([]u8, testing.allocator, "\"0123456789\"", .{ .max_value_len = 5 })); + try testMaxValueLen([]u8, .alloc_if_needed); + try testMaxValueLen([]const u8, .alloc_always); + try testMaxValueLen([:0]u8, .alloc_if_needed); + try testMaxValueLen([:0]const u8, .alloc_if_needed); + + // If the value can be returned as a reference to the buffer, max_value_len doesn't apply. + { + const parsed = try parseFromSlice([]const u8, testing.allocator, "\"123\"", .{ .max_value_len = 1 }); + defer parsed.deinit(); + try testing.expectEqualStrings("123", parsed.value); + } + // If the value is returned as a number without needing intermediate allocations, max_value_len doesn't apply. + { + const parsed = try parseFromSlice(u32, testing.allocator, "\"001\"", .{ .max_value_len = 1 }); + defer parsed.deinit(); + try testing.expectEqual(1, parsed.value); + } +} + +fn testMaxValueLen(comptime T: type, when: Scanner.AllocWhen) !void { + const parsed = try parseFromSlice(T, testing.allocator, "\"12345\"", .{ + .max_value_len = 5, + .allocate = when, + }); + defer parsed.deinit(); + try testing.expectEqualStrings("12345", parsed.value); + + try testing.expectError( + error.ValueTooLong, + parseFromSlice(T, testing.allocator, "\"123456\"", .{ + .max_value_len = 5, + .allocate = when, + }), + ); } test "parse into vector" {