std.json: correctly handle sentinel terminated slices

This commit is contained in:
Benjamin San Souci 2022-03-08 10:43:13 -08:00 committed by GitHub
parent 4b9fd57aa8
commit e3c2cc1443
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 92 additions and 7 deletions

View File

@ -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);

View File

@ -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 {