From e3c2cc1443490973b5038e7ce4e347ad1df9b678 Mon Sep 17 00:00:00 2001 From: Benjamin San Souci Date: Tue, 8 Mar 2022 10:43:13 -0800 Subject: [PATCH] std.json: correctly handle sentinel terminated slices --- lib/std/json.zig | 57 +++++++++++++++++++++++++++++++++++++++------ lib/std/testing.zig | 42 +++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 7 deletions(-) diff --git a/lib/std/json.zig b/lib/std/json.zig index e495d0d0b9..c18f38754a 100644 --- a/lib/std/json.zig +++ b/lib/std/json.zig @@ -1870,20 +1870,34 @@ fn parseInternal( const v = try parseInternal(ptrInfo.child, tok, tokens, options); arraylist.appendAssumeCapacity(v); } + + if (ptrInfo.sentinel) |some| { + const sentinel_value = @ptrCast(*const ptrInfo.child, some).*; + try arraylist.append(sentinel_value); + const output = arraylist.toOwnedSlice(); + return output[0 .. output.len - 1 :sentinel_value]; + } + return arraylist.toOwnedSlice(); }, .String => |stringToken| { if (ptrInfo.child != u8) return error.UnexpectedToken; const source_slice = stringToken.slice(tokens.slice, tokens.i - 1); + const len = stringToken.decodedLength(); + const output = try allocator.alloc(u8, len + @boolToInt(ptrInfo.sentinel != null)); + errdefer allocator.free(output); switch (stringToken.escapes) { - .None => return allocator.dupe(u8, source_slice), - .Some => { - const output = try allocator.alloc(u8, stringToken.decodedLength()); - errdefer allocator.free(output); - try unescapeValidString(output, source_slice); - return output; - }, + .None => mem.copy(u8, output, source_slice), + .Some => try unescapeValidString(output, source_slice), } + + if (ptrInfo.sentinel) |some| { + const char = @ptrCast(*const u8, some).*; + output[len] = char; + return output[0..len :char]; + } + + return output; }, else => return error.UnexpectedToken, } @@ -2216,6 +2230,35 @@ test "parse into struct with misc fields" { try testing.expectEqual(T.Union{ .float = 100000 }, r.a_union); } +test "parse into struct with strings and arrays with sentinels" { + @setEvalBranchQuota(10000); + const options = ParseOptions{ .allocator = testing.allocator }; + const T = struct { + language: [:0]const u8, + language_without_sentinel: []const u8, + data: [:99]const i32, + simple_data: []const i32, + }; + const r = try parse(T, &TokenStream.init( + \\{ + \\ "language": "zig", + \\ "language_without_sentinel": "zig again!", + \\ "data": [1, 2, 3], + \\ "simple_data": [4, 5, 6] + \\} + ), options); + defer parseFree(T, r, options); + + try testing.expectEqualSentinel(u8, 0, "zig", r.language); + + const data = [_:99]i32{ 1, 2, 3 }; + try testing.expectEqualSentinel(i32, 99, data[0..data.len], r.data); + + // Make sure that arrays who aren't supposed to have a sentinel still parse without one. + try testing.expectEqual(@as(?i32, null), std.meta.sentinel(@TypeOf(r.simple_data))); + try testing.expectEqual(@as(?u8, null), std.meta.sentinel(@TypeOf(r.language_without_sentinel))); +} + test "parse into struct with duplicate field" { // allow allocator to detect double frees by keeping bucket in use const ballast = try testing.allocator.alloc(u64, 1); diff --git a/lib/std/testing.zig b/lib/std/testing.zig index 8d08792801..acd76ab1df 100644 --- a/lib/std/testing.zig +++ b/lib/std/testing.zig @@ -299,6 +299,48 @@ pub fn expectEqualSlices(comptime T: type, expected: []const T, actual: []const } } +/// This function is intended to be used only in tests. Checks that two slices or two arrays are equal, +/// including that their sentinel (if any) are the same. Will error if given another type. +pub fn expectEqualSentinel(comptime T: type, comptime sentinel: T, expected: [:sentinel]const T, actual: [:sentinel]const T) !void { + try expectEqualSlices(T, expected, actual); + + const expected_value_sentinel = blk: { + switch (@typeInfo(@TypeOf(expected))) { + .Pointer => { + break :blk expected[expected.len]; + }, + .Array => |array_info| { + const indexable_outside_of_bounds = @as([]const array_info.child, &expected); + break :blk indexable_outside_of_bounds[indexable_outside_of_bounds.len]; + }, + else => {}, + } + }; + + const actual_value_sentinel = blk: { + switch (@typeInfo(@TypeOf(actual))) { + .Pointer => { + break :blk actual[actual.len]; + }, + .Array => |array_info| { + const indexable_outside_of_bounds = @as([]const array_info.child, &actual); + break :blk indexable_outside_of_bounds[indexable_outside_of_bounds.len]; + }, + else => {}, + } + }; + + if (!std.meta.eql(sentinel, expected_value_sentinel)) { + std.debug.print("expectEqualSentinel: 'expected' sentinel in memory is different from its type sentinel. type sentinel {}, in memory sentinel {}\n", .{ sentinel, expected_value_sentinel }); + return error.TestExpectedEqual; + } + + if (!std.meta.eql(sentinel, actual_value_sentinel)) { + std.debug.print("expectEqualSentinel: 'actual' sentinel in memory is different from its type sentinel. type sentinel {}, in memory sentinel {}\n", .{ sentinel, actual_value_sentinel }); + return error.TestExpectedEqual; + } +} + /// This function is intended to be used only in tests. When `ok` is false, the test fails. /// A message is printed to stderr and then abort is called. pub fn expect(ok: bool) !void {