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(...@@ -1870,20 +1870,34 @@ fn parseInternal(
1870 const v = try parseInternal(ptrInfo.child, tok, tokens, options);1870 const v = try parseInternal(ptrInfo.child, tok, tokens, options);
1871 arraylist.appendAssumeCapacity(v);1871 arraylist.appendAssumeCapacity(v);
1872 }1872 }
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
1873 return arraylist.toOwnedSlice();1881 return arraylist.toOwnedSlice();
1874 },1882 },
1875 .String => |stringToken| {1883 .String => |stringToken| {
1876 if (ptrInfo.child != u8) return error.UnexpectedToken;1884 if (ptrInfo.child != u8) return error.UnexpectedToken;
1877 const source_slice = stringToken.slice(tokens.slice, tokens.i - 1);1885 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);
1878 switch (stringToken.escapes) {1889 switch (stringToken.escapes) {
1879 .None => return allocator.dupe(u8, source_slice),1890 .None => mem.copy(u8, output, source_slice),
1880 .Some => {1891 .Some => try unescapeValidString(output, source_slice),
1881 const output = try allocator.alloc(u8, stringToken.decodedLength());
1882 errdefer allocator.free(output);
1883 try unescapeValidString(output, source_slice);
1884 return output;
1885 },
1886 }1892 }
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;
1887 },1901 },
1888 else => return error.UnexpectedToken,1902 else => return error.UnexpectedToken,
1889 }1903 }
...@@ -2216,6 +2230,35 @@ test "parse into struct with misc fields" {...@@ -2216,6 +2230,35 @@ test "parse into struct with misc fields" {
2216 try testing.expectEqual(T.Union{ .float = 100000 }, r.a_union);2230 try testing.expectEqual(T.Union{ .float = 100000 }, r.a_union);
2217}2231}
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
2219test "parse into struct with duplicate field" {2262test "parse into struct with duplicate field" {
2220 // allow allocator to detect double frees by keeping bucket in use2263 // allow allocator to detect double frees by keeping bucket in use
2221 const ballast = try testing.allocator.alloc(u64, 1);2264 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...@@ -299,6 +299,48 @@ pub fn expectEqualSlices(comptime T: type, expected: []const T, actual: []const
299 }299 }
300}300}
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
302/// This function is intended to be used only in tests. When `ok` is false, the test fails.344/// This function is intended to be used only in tests. When `ok` is false, the test fails.
303/// A message is printed to stderr and then abort is called.345/// A message is printed to stderr and then abort is called.
304pub fn expect(ok: bool) !void {346pub fn expect(ok: bool) !void {