authorgravatar for benjamin.sansouci@gmail.comBenjamin San Souci <benjamin.sansouci@gmail.com> 2022-03-08 10:43:13-08:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2022-03-08 20:43:13+02:00
loge3c2cc1443490973b5038e7ce4e347ad1df9b678
treeaf6f49b5c69003c00956138ccf68c12d89ebc058
parent4b9fd57aa86a480f2afd6ba117fcc7ef6eace572
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

std.json: correctly handle sentinel terminated slices


2 files changed, 92 insertions(+), 7 deletions(-)

lib/std/json.zig+50-7
......@@ -1870,20 +1870,34 @@ fn parseInternal(
18701870 const v = try parseInternal(ptrInfo.child, tok, tokens, options);
18711871 arraylist.appendAssumeCapacity(v);
18721872 }
1873
1874 if (ptrInfo.sentinel) |some| {
1875 const sentinel_value = @ptrCast(*const ptrInfo.child, some).*;
1876 try arraylist.append(sentinel_value);
1877 const output = arraylist.toOwnedSlice();
1878 return output[0 .. output.len - 1 :sentinel_value];
1879 }
1880
18731881 return arraylist.toOwnedSlice();
18741882 },
18751883 .String => |stringToken| {
18761884 if (ptrInfo.child != u8) return error.UnexpectedToken;
18771885 const source_slice = stringToken.slice(tokens.slice, tokens.i - 1);
1886 const len = stringToken.decodedLength();
1887 const output = try allocator.alloc(u8, len + @boolToInt(ptrInfo.sentinel != null));
1888 errdefer allocator.free(output);
18781889 switch (stringToken.escapes) {
1879 .None => return allocator.dupe(u8, source_slice),
1880 .Some => {
1881 const output = try allocator.alloc(u8, stringToken.decodedLength());
1882 errdefer allocator.free(output);
1883 try unescapeValidString(output, source_slice);
1884 return output;
1885 },
1890 .None => mem.copy(u8, output, source_slice),
1891 .Some => try unescapeValidString(output, source_slice),
18861892 }
1893
1894 if (ptrInfo.sentinel) |some| {
1895 const char = @ptrCast(*const u8, some).*;
1896 output[len] = char;
1897 return output[0..len :char];
1898 }
1899
1900 return output;
18871901 },
18881902 else => return error.UnexpectedToken,
18891903 }
......@@ -2216,6 +2230,35 @@ test "parse into struct with misc fields" {
22162230 try testing.expectEqual(T.Union{ .float = 100000 }, r.a_union);
22172231}
22182232
2233test "parse into struct with strings and arrays with sentinels" {
2234 @setEvalBranchQuota(10000);
2235 const options = ParseOptions{ .allocator = testing.allocator };
2236 const T = struct {
2237 language: [:0]const u8,
2238 language_without_sentinel: []const u8,
2239 data: [:99]const i32,
2240 simple_data: []const i32,
2241 };
2242 const r = try parse(T, &TokenStream.init(
2243 \\{
2244 \\ "language": "zig",
2245 \\ "language_without_sentinel": "zig again!",
2246 \\ "data": [1, 2, 3],
2247 \\ "simple_data": [4, 5, 6]
2248 \\}
2249 ), options);
2250 defer parseFree(T, r, options);
2251
2252 try testing.expectEqualSentinel(u8, 0, "zig", r.language);
2253
2254 const data = [_:99]i32{ 1, 2, 3 };
2255 try testing.expectEqualSentinel(i32, 99, data[0..data.len], r.data);
2256
2257 // Make sure that arrays who aren't supposed to have a sentinel still parse without one.
2258 try testing.expectEqual(@as(?i32, null), std.meta.sentinel(@TypeOf(r.simple_data)));
2259 try testing.expectEqual(@as(?u8, null), std.meta.sentinel(@TypeOf(r.language_without_sentinel)));
2260}
2261
22192262test "parse into struct with duplicate field" {
22202263 // allow allocator to detect double frees by keeping bucket in use
22212264 const ballast = try testing.allocator.alloc(u64, 1);
lib/std/testing.zig+42
......@@ -299,6 +299,48 @@ pub fn expectEqualSlices(comptime T: type, expected: []const T, actual: []const
299299 }
300300}
301301
302/// This function is intended to be used only in tests. Checks that two slices or two arrays are equal,
303/// including that their sentinel (if any) are the same. Will error if given another type.
304pub fn expectEqualSentinel(comptime T: type, comptime sentinel: T, expected: [:sentinel]const T, actual: [:sentinel]const T) !void {
305 try expectEqualSlices(T, expected, actual);
306
307 const expected_value_sentinel = blk: {
308 switch (@typeInfo(@TypeOf(expected))) {
309 .Pointer => {
310 break :blk expected[expected.len];
311 },
312 .Array => |array_info| {
313 const indexable_outside_of_bounds = @as([]const array_info.child, &expected);
314 break :blk indexable_outside_of_bounds[indexable_outside_of_bounds.len];
315 },
316 else => {},
317 }
318 };
319
320 const actual_value_sentinel = blk: {
321 switch (@typeInfo(@TypeOf(actual))) {
322 .Pointer => {
323 break :blk actual[actual.len];
324 },
325 .Array => |array_info| {
326 const indexable_outside_of_bounds = @as([]const array_info.child, &actual);
327 break :blk indexable_outside_of_bounds[indexable_outside_of_bounds.len];
328 },
329 else => {},
330 }
331 };
332
333 if (!std.meta.eql(sentinel, expected_value_sentinel)) {
334 std.debug.print("expectEqualSentinel: 'expected' sentinel in memory is different from its type sentinel. type sentinel {}, in memory sentinel {}\n", .{ sentinel, expected_value_sentinel });
335 return error.TestExpectedEqual;
336 }
337
338 if (!std.meta.eql(sentinel, actual_value_sentinel)) {
339 std.debug.print("expectEqualSentinel: 'actual' sentinel in memory is different from its type sentinel. type sentinel {}, in memory sentinel {}\n", .{ sentinel, actual_value_sentinel });
340 return error.TestExpectedEqual;
341 }
342}
343
302344/// This function is intended to be used only in tests. When `ok` is false, the test fails.
303345/// A message is printed to stderr and then abort is called.
304346pub fn expect(ok: bool) !void {