Commit e3c2cc1443

Benjamin San Souci <benjamin.sansouci@gmail.com>
2022-03-08 19:43:13
std.json: correctly handle sentinel terminated slices
1 parent 4b9fd57
Changed files (2)
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);
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 {