mirror of
https://github.com/ziglang/zig.git
synced 2026-01-20 22:35:24 +00:00
std.json: correctly handle sentinel terminated slices
This commit is contained in:
parent
4b9fd57aa8
commit
e3c2cc1443
@ -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);
|
||||
|
||||
@ -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 {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user