Commit b1dd4b17d8

John Schmidt <john.schmidt.h@gmail.com>
2023-02-04 14:42:42
std.json: don't free struct default values
Closes https://github.com/ziglang/zig/issues/9509.
1 parent b42caff
Changed files (2)
lib
lib/std/json/test.zig
@@ -2246,6 +2246,31 @@ test "parse into struct with default const pointer field" {
     try testing.expectEqual(T{}, try parse(T, &ts, .{}));
 }
 
+const test_default_usize: usize = 123;
+const test_default_usize_ptr: *align(1) const usize = &test_default_usize;
+const test_default_str: []const u8 = "test str";
+const test_default_str_slice: [2][]const u8 = [_][]const u8{
+    "test1",
+    "test2",
+};
+
+test "freeing parsed structs with pointers to default values" {
+    const T = struct {
+        int: *const usize = &test_default_usize,
+        int_ptr: *allowzero align(1) const usize = test_default_usize_ptr,
+        str: []const u8 = test_default_str,
+        str_slice: []const []const u8 = &test_default_str_slice,
+    };
+
+    var ts = json.TokenStream.init("{}");
+    const options = .{ .allocator = std.heap.page_allocator };
+    const parsed = try json.parse(T, &ts, options);
+
+    try testing.expectEqual(T{}, parsed);
+
+    json.parseFree(T, parsed, options);
+}
+
 test "parse into struct where destination and source lengths mismatch" {
     const T = struct { a: [2]u8 };
     var ts = TokenStream.init("{\"a\": \"bbb\"}");
lib/std/json.zig
@@ -1745,7 +1745,37 @@ pub fn parseFree(comptime T: type, value: T, options: ParseOptions) void {
         .Struct => |structInfo| {
             inline for (structInfo.fields) |field| {
                 if (!field.is_comptime) {
-                    parseFree(field.type, @field(value, field.name), options);
+                    var should_free = true;
+                    if (field.default_value) |default| {
+                        switch (@typeInfo(field.type)) {
+                            // We must not attempt to free pointers to struct default values
+                            .Pointer => |fieldPtrInfo| {
+                                const field_value = @field(value, field.name);
+                                const field_ptr = switch (fieldPtrInfo.size) {
+                                    .One => field_value,
+                                    .Slice => field_value.ptr,
+                                    else => unreachable, // Other pointer types are not parseable
+                                };
+                                const field_addr = @ptrToInt(field_ptr);
+
+                                const casted_default = @ptrCast(*const field.type, @alignCast(@alignOf(field.type), default)).*;
+                                const default_ptr = switch (fieldPtrInfo.size) {
+                                    .One => casted_default,
+                                    .Slice => casted_default.ptr,
+                                    else => unreachable, // Other pointer types are not parseable
+                                };
+                                const default_addr = @ptrToInt(default_ptr);
+
+                                if (field_addr == default_addr) {
+                                    should_free = false;
+                                }
+                            },
+                            else => {},
+                        }
+                    }
+                    if (should_free) {
+                        parseFree(field.type, @field(value, field.name), options);
+                    }
                 }
             }
         },