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:
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" {