I am a newcomer to zig. I wrote such a piece of code. The main function is to loop http request paging data. I found that when I use gpa, it will report the following error:
Segmentation fault at address 0x1fee06b0010
E:\scoop\global\apps\zig-dev\current\lib\std\comptime_string_map.zig:79:28: 0x7ff6ca7575f0 in get (exchange2.exe.obj)
if (mem.eql(u8, kv.key, str))
^
E:\scoop\global\apps\zig-dev\current\lib\std\http\Client.zig:993:38: 0x7ff6ca75293c in request (exchange2.exe.obj)
const protocol = protocol_map.get(uri.scheme) orelse return error.UnsupportedUrlScheme;
^
E:\code\me\test-zig\exchange2.zig:76:41: 0x7ff6ca7520f3 in main (exchange2.exe.obj)
var req = try client.request(.GET, uri.?, headers, .{});
^
E:\scoop\global\apps\zig-dev\current\lib\std\start.zig:378:65: 0x7ff6ca7555bc in WinStartup (exchange2.exe.obj)
std.os.windows.kernel32.ExitProcess(initEventLoopAndCallMain());
^
???:?:?: 0x7ffcd64926ac in ??? (KERNEL32.DLL)
???:?:?: 0x7ffcd754aa67 in ??? (ntdll.dll)
Uri.parse keeps a reference to the string that was passed into it.
However as soon as the end of scope is reached, that string is deallocated(you called defer parsed.deinit() where parsed is owning the next_uri string)
So when the next iteration of the loop starts you are reading invalid memory, causing a Segmentation fault.
To solve this you need to make a local copy of the string and make sure that it gets freed. I would do something like this:
var uri_string: ?[]const u8 = try allocator.dupe(u8, "https://pokeapi.co/api/v2/pokemon/"); // Duping the original string as well to simplify the code
...
while (uri_string != null) {
defer allocator.free(uri_string.?); // Don't forget to free it.
const uri = try Uri.parse(uri_string.?); // Doing this at the beginning of the loop reduces code duplication
...
switch (response_status) {
.ok => {
...
uri_string = try allocator.dupe(next_uri.?); // Duplicate the string to avoid segfault
},
else => |s| {
...
uri_string = null; // Why not use a `break` here by the way?
},
}
Thanks for the detailed answer, I also wonder why I don’t have this problem with other allocators?
The GeneralPurposeAllocator is designed to make catching use-after-free errors easier in debug mode. Other allocators may just write something else into the freed memory without you noticing. This is undefined behavior. You just got lucky with the other allocators, likely because you didn’t do any other allocations in the mean-time.
var uri_string: ?[]const u8 = try allocator.dupe(u8, "https://pokeapi.co/api/v2/pokemon/"); // Duping the original string as well to simplify the code
...
while (uri_string != null) {
const uri = try Uri.parse(uri_string.?); // Doing this at the beginning of the loop reduces code duplication
...
switch (response_status) {
.ok => {
...
allocator.free(uri_string.?); // Need to free it before changing it.
uri_string = try allocator.dupe(next_uri.?); // Duplicate the string to avoid segfault
},
else => |s| {
...
allocator.free(uri_string.?); // Need to free it before changing it.
uri_string = null; // Why not use a `break` here by the way?
},
}
That’s not always true. Arena allocators sometimes deallocate memory if the freed memory is at the end of the underlying data structure(which happens if you are freeing the last allocation). Check out the code of ArenaAllocator.free