I am learning Zig a little bit and I encountered this interesting case:
fn handleConnection(conn: std.Io.net.Stream, io: std.Io) !void {
var buffer: [1024]u8 = .{0} ** 1024;
var inBuf: [1024]u8 = .{0} ** 1024;
const reader = conn.reader(io, &inBuf);
var writer = conn.writer(io, &buffer);
var ri = reader.interface;
var http_server = std.http.Server.init(&ri, &writer.interface);
// TODO: read a protobuf message (:
var req = try http_server.receiveHead();
defer _ = conn.shutdown(io, std.Io.net.ShutdownHow.both) catch {};
try req.respond("hello world\n", std.http.Server.Request.RespondOptions{});
}
Why this crashes? The crash message looks like:
Segmentation fault at address 0x328
/home/gstatkevicius/Downloads/zig-x86_64-linux-0.16.0/lib/std/Io/net.zig:1305:40: 0x11f6b84 in readVec (std.zig)
const n = io.vtable.netRead(io.userdata, r.stream.socket.handle, dest) catch |err| {
^
/home/gstatkevicius/Downloads/zig-x86_64-linux-0.16.0/lib/std/Io/Reader.zig:1135:29: 0x107df7b in fillMore (std.zig)
_ = try r.vtable.readVec(r, &bufs);
^
/home/gstatkevicius/Downloads/zig-x86_64-linux-0.16.0/lib/std/http.zig:398:28: 0x11f171d in receiveHead (std.zig)
in.fillMore() catch |err| switch (err) {
^
/home/gstatkevicius/Downloads/zig-x86_64-linux-0.16.0/lib/std/http/Server.zig:47:49: 0x11efaca in receiveHead (std.zig)
const head_buffer = try s.reader.receiveHead();
^
/home/gstatkevicius/dev/zetrics/src/main.zig:52:42: 0x11d754e in handleConnection (main.zig)
var req = try http_server.receiveHead();
^
/home/gstatkevicius/dev/zetrics/src/main.zig:38:29: 0x11d7e51 in main (main.zig)
try handleConnection(try server.accept(io), io);
^
/home/gstatkevicius/Downloads/zig-x86_64-linux-0.16.0/lib/std/start.zig:737:30: 0x11d8a5e in callMain (std.zig)
return wrapMain(root.main(.{
^
/home/gstatkevicius/Downloads/zig-x86_64-linux-0.16.0/lib/std/start.zig:190:5: 0x11d7201 in _start (std.zig)
asm volatile (switch (native_arch) {
^
fish: Job 1, 'zig run src/main.zig' terminated by signal SIGABRT (Abort)
This version doesn’t crash:
fn handleConnection(conn: std.Io.net.Stream, io: std.Io) !void {
var buffer: [1024]u8 = .{0} ** 1024;
var inBuf: [1024]u8 = .{0} ** 1024;
var reader = conn.reader(io, &inBuf);
var writer = conn.writer(io, &buffer);
var http_server = std.http.Server.init(&reader.interface, &writer.interface);
// TODO: read a protobuf message (:
var req = try http_server.receiveHead();
defer _ = conn.shutdown(io, std.Io.net.ShutdownHow.both) catch {};
try req.respond("hello world\n", std.http.Server.Request.RespondOptions{});
}
readVec has this interesting code:
const r: *Reader = @alignCast(@fieldParentPtr("interface", io_r));
const io = r.io;
It first gets the *Reader which must contain a field called interface. Then
it access io of that *Reader. So, if you copy the interface as a separate
variable, it becomes “detached”. Hence, when later io.userdata is being
accessed, it points to somewhere out of bounds (?). In other words, in the
crashing example you are taking the address of another interface and not the
original one so the “trick” with @fieldParentPtr doesn’t work anymore. I am
not sure if this is the correct term or the explanation is 100% correct but just
wanted to share my learning.
It also kind of reminds me of the container_of macro in the Linux kernel. If
you have a structure and want to get the “containing” structure then it does the
same thing.