authorgravatar for garrettlennoxbeck@gmail.comGarrett Beck <garrettlennoxbeck@gmail.com> 2023-07-22 17:33:50-05:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2023-07-22 22:33:50+00:00
log04c7b55de49b3f5129098e079e6cde7704c70293
tree31e54e9d524ab92d46c5f7bf6ba22938d021383f
parent32a175740c8750a7db550011795a4c22c0d0bc93
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

std.json: fix key allocation in HashMap for streaming json parsing (#16422)


2 files changed, 29 insertions(+), 6 deletions(-)

lib/std/json/hashmap.zig+1-6
...@@ -24,15 +24,10 @@ pub fn ArrayHashMap(comptime T: type) type {...@@ -24,15 +24,10 @@ pub fn ArrayHashMap(comptime T: type) type {
2424
25 if (.object_begin != try source.next()) return error.UnexpectedToken;25 if (.object_begin != try source.next()) return error.UnexpectedToken;
26 while (true) {26 while (true) {
27 const token = try source.nextAlloc(allocator, .alloc_if_needed);27 const token = try source.nextAlloc(allocator, options.allocate.?);
28 switch (token) {28 switch (token) {
29 inline .string, .allocated_string => |k| {29 inline .string, .allocated_string => |k| {
30 const gop = try map.getOrPut(allocator, k);30 const gop = try map.getOrPut(allocator, k);
31 if (token == .allocated_string) {
32 // Free the key before recursing in case we're using an allocator
33 // that optimizes freeing the last allocated object.
34 allocator.free(k);
35 }
36 if (gop.found_existing) {31 if (gop.found_existing) {
37 switch (options.duplicate_field_behavior) {32 switch (options.duplicate_field_behavior) {
38 .use_first => {33 .use_first => {
lib/std/json/hashmap_test.zig+28
...@@ -5,10 +5,13 @@ const ArrayHashMap = @import("hashmap.zig").ArrayHashMap;...@@ -5,10 +5,13 @@ const ArrayHashMap = @import("hashmap.zig").ArrayHashMap;
55
6const parseFromSlice = @import("static.zig").parseFromSlice;6const parseFromSlice = @import("static.zig").parseFromSlice;
7const parseFromSliceLeaky = @import("static.zig").parseFromSliceLeaky;7const parseFromSliceLeaky = @import("static.zig").parseFromSliceLeaky;
8const parseFromTokenSource = @import("static.zig").parseFromTokenSource;
8const parseFromValue = @import("static.zig").parseFromValue;9const parseFromValue = @import("static.zig").parseFromValue;
9const stringifyAlloc = @import("stringify.zig").stringifyAlloc;10const stringifyAlloc = @import("stringify.zig").stringifyAlloc;
10const Value = @import("dynamic.zig").Value;11const Value = @import("dynamic.zig").Value;
1112
13const jsonReader = @import("./scanner.zig").reader;
14
12const T = struct {15const T = struct {
13 i: i32,16 i: i32,
14 s: []const u8,17 s: []const u8,
...@@ -29,6 +32,31 @@ test "parse json hashmap" {...@@ -29,6 +32,31 @@ test "parse json hashmap" {
29 try testing.expectEqual(@as(i32, 1), parsed.value.map.get("xyz").?.i);32 try testing.expectEqual(@as(i32, 1), parsed.value.map.get("xyz").?.i);
30}33}
3134
35test "parse json hashmap while streaming" {
36 const doc =
37 \\{
38 \\ "abc": {"i": 0, "s": "d"},
39 \\ "xyz": {"i": 1, "s": "w"}
40 \\}
41 ;
42 var stream = std.io.fixedBufferStream(doc);
43 var json_reader = jsonReader(testing.allocator, stream.reader());
44
45 var parsed = try parseFromTokenSource(
46 ArrayHashMap(T),
47 testing.allocator,
48 &json_reader,
49 .{},
50 );
51 defer parsed.deinit();
52 // Deinit our reader to invalidate its buffer
53 json_reader.deinit();
54
55 try testing.expectEqual(@as(usize, 2), parsed.value.map.count());
56 try testing.expectEqualStrings("d", parsed.value.map.get("abc").?.s);
57 try testing.expectEqual(@as(i32, 1), parsed.value.map.get("xyz").?.i);
58}
59
32test "parse json hashmap duplicate fields" {60test "parse json hashmap duplicate fields" {
33 var arena = std.heap.ArenaAllocator.init(std.testing.allocator);61 var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
34 defer arena.deinit();62 defer arena.deinit();