Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

deserialization of comptime structs #22

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 30 additions & 21 deletions src/lib.zig
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,28 @@ pub fn serialize(comptime T: type, data: T, l: *ArrayList(u8)) !void {
}
}

pub fn deserialize_slice(comptime T: type, serialized: []const u8, out: []T) !void {
if (try isFixedSizeObject(T)) {
var i: usize = 0;
const pitch = @sizeOf(T);
while (i < out.len) : (i += pitch) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that used to be inline in the calling code. That made no sense and yet it worked.

try deserialize(T, serialized[i * pitch .. (i + 1) * pitch], &out[i]);
}
} else {
const size = std.mem.readInt(u32, serialized[0..4], std.builtin.Endian.little) / @sizeOf(u32);
const indices = std.mem.bytesAsSlice(u32, serialized[0 .. size * 4]);
var i = @as(usize, 0);
while (i < size) : (i += 1) {
const end = if (i < size - 1) indices[i + 1] else serialized.len;
const start = indices[i];
if (start >= serialized.len or end > serialized.len) {
return error.IndexOutOfBounds;
}
try deserialize(T, serialized[start..end], &out[i]);
}
}
}

/// Takes a byte array containing the serialized payload of type `T` (with
/// possible trailing data) and deserializes it into the `T` object pointed
/// at by `out`.
Expand Down Expand Up @@ -278,25 +300,7 @@ pub fn deserialize(comptime T: type, serialized: []const u8, out: *T) !void {
// the responsibility of the caller.
out.* = serialized[0..];
} else {
if (try isFixedSizeObject(ptr.child)) {
comptime var i = 0;
const pitch = @sizeOf(ptr.child);
inline while (i < out.len) : (i += pitch) {
try deserialize(ptr.child, serialized[i * pitch .. (i + 1) * pitch], &out[i]);
}
} else {
const size = std.mem.readInt(u32, serialized[0..4], std.builtin.Endian.little) / @sizeOf(u32);
const indices = std.mem.bytesAsSlice(u32, serialized[0 .. size * 4]);
var i = @as(usize, 0);
while (i < size) : (i += 1) {
const end = if (i < size - 1) indices[i + 1] else serialized.len;
const start = indices[i];
if (start >= serialized.len or end > serialized.len) {
return error.IndexOutOfBounds;
}
try deserialize(ptr.child, serialized[start..end], &out[i]);
}
}
try deserialize_slice(ptr.child, serialized, out.*);
},
.One => return deserialize(ptr.child, serialized, out.*),
else => return error.UnSupportedPointerType,
Expand Down Expand Up @@ -342,10 +346,15 @@ pub fn deserialize(comptime T: type, serialized: []const u8, out: *T) !void {
inline for (info.Struct.fields) |field| {
// comptime fields are currently not supported, and it's not even
// certain that they can ever be without a change in the language.
if (field.is_comptime) @panic("structure contains comptime field");

switch (@typeInfo(field.type)) {
const tinfo = @typeInfo(field.type);
switch (tinfo) {
.Bool, .Int => {}, // covered by the previous pass
.Pointer => |ptr| switch (ptr.size) {
.One => try deserialize(ptr.child, serialized, @field(out, field.name)),
.Slice => try deserialize_slice(ptr.child, serialized, @field(out.*, field.name)),
else => return error.UnsupportedPointerType,
},
else => {
const end = if (last_index == indices.len - 1) serialized.len else indices[last_index + 1];
try deserialize(field.type, serialized[indices[last_index]..end], &@field(out.*, field.name));
Expand Down
35 changes: 27 additions & 8 deletions src/tests.zig
Original file line number Diff line number Diff line change
Expand Up @@ -143,17 +143,36 @@ test "serializes a structure without variable fields" {
try expect(std.mem.eql(u8, list.items, serialized_data[0..]));
}

test "(de)serializes a structure with variable fields" {
test "(de)serializes a runtime structure with variable-size fields" {
// Taken from ssz.cr
const Person = struct {
name: []const u8,
name: []u8,
age: u8,
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

interestingly, this also causes the same "comptime ref" issue, and so I need a more complete workaround for it.

company: []const u8,
company: []u8,
};
var data = Person{
.name = "James",
.name = try std.testing.allocator.alloc(u8, 5),
.age = 32,
.company = try std.testing.allocator.alloc(u8, 8),
};
@memcpy(data.name, "James");
@memcpy(data.company, "DEV Inc.");
Comment on lines +154 to +159
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this can be fixed using @as(, @constCast

const serialized_data = [_]u8{ 9, 0, 0, 0, 32, 14, 0, 0, 0, 74, 97, 109, 101, 115, 68, 69, 86, 32, 73, 110, 99, 46 };

var list = ArrayList(u8).init(std.testing.allocator);
defer list.deinit();
// Note the `&data` - this is so that `data` is not considered const.
try serialize(@TypeOf(&data), &data, &list);
try expect(std.mem.eql(u8, list.items, serialized_data[0..]));
var out: @TypeOf(data) = undefined;
try deserialize(@TypeOf(data), list.items, &out);
}

test "(de)serializes a comptime structure with variable-size fields" {
// Taken from ssz.cr
var data = .{
.age = 32,
.company = "DEV Inc.",
.company = @as([]u8, @constCast("DEV Inc.")),
};
const serialized_data = [_]u8{ 9, 0, 0, 0, 32, 14, 0, 0, 0, 74, 97, 109, 101, 115, 68, 69, 86, 32, 73, 110, 99, 46 };

Expand Down Expand Up @@ -347,17 +366,17 @@ test "deserializes a string" {
}

const Pastry = struct {
name: []const u8,
name: []u8,
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

deserialization only worked because it was returning a direct pointer to the serialized payload, which is []const u8 itself.

weight: u16,
};

const pastries = [_]Pastry{
Pastry{
.name = "croissant",
.name = @as([]u8, @constCast("croissant")),
.weight = 20,
},
Pastry{
.name = "Herrentorte",
.name = @as([]u8, @constCast("Herrentorte")),
.weight = 500,
},
};
Expand Down
Loading