Having a hard time tracking down a memory leak

Hi there! I’ve been loving Zig but I think I’ve confused myself with memory management while trying to track down a leak. The GPA’s leak detection isn’t super helpful with the stacktrace here, but I’ve isolated it down to a section of code. I’m not 100% sure where the leak is happening, but I also feel like this probably isnt the right approach for this code, given what it’s trying to do. I was hoping you all wouldn’t mind taking a look and giving feedback on this implementation and also the memory leak.

Here’s the leak stacktrace:

.../zig/0.13.0/lib/std/array_list.zig:457:67: 0x10438d843 in ensureTotalCapacityPrecise (book)
                const new_memory = try self.allocator.alignedAlloc(T, alignment, new_capacity);
                                                                  ^
/Users/bradcypert/.asdf/installs/zig/0.13.0/lib/std/array_list.zig:434:51: 0x10437c83b in ensureTotalCapacity (book)
            return self.ensureTotalCapacityPrecise(better_capacity);
                                                  ^
.../zig/0.13.0/lib/std/array_list.zig:468:44: 0x1043635b7 in ensureUnusedCapacity (book)
            return self.ensureTotalCapacity(try addOrOom(self.items.len, additional_count));
                                           ^
.../zig/0.13.0/lib/std/array_list.zig:305:42: 0x104335d33 in appendSlice (book)
            try self.ensureUnusedCapacity(items.len);
                                         ^
.../zig/0.13.0/lib/std/array_list.zig:358:33: 0x10438d34b in appendWrite (book)
            try self.appendSlice(m);
                                ^
.../zig/0.13.0/lib/std/io.zig:360:27: 0x10437c067 in typeErasedWriteFn (book)
            return writeFn(ptr.*, bytes);

and here’s the code where I’ve isolated the leak towards!

pub const Bookmark = struct {
    value: []const u8,
    path: []const u8,
    tags: []const []const u8,

    pub fn fromLine(allocator: std.mem.Allocator, line: []u8) !Bookmark {
        var splitter = std.mem.split(u8, line, ",");

        var parts = std.ArrayList([]const u8).init(allocator);
        defer parts.deinit();

        while (splitter.next()) |part| {
            try parts.append(try allocator.dupe(u8, part));
        }
        defer {
            for (parts.items) |part| {
                allocator.free(part);
            }
        }

        const value = try allocator.dupe(u8, parts.items[0]);
        const path = try allocator.dupe(u8, parts.items[1]);

        var tags = try allocator.alloc([]const u8, parts.items.len - 2);
        for (parts.items[2..], 0..) |tag, index| {
            tags[index] = try allocator.dupe(u8, tag);
        }

        return Bookmark{
            .value = value,
            .path = path,
            .tags = tags,
        };
    }

    pub fn free(self: Bookmark, allocator: std.mem.Allocator) void {
        for (self.tags) |tag| {
            allocator.free(tag);
        }
        allocator.free(self.tags);
        allocator.free(self.path);
        allocator.free(self.value);
    }
};

Specifically the leak is coming from the fromLine function. I appreciate the help!

You can make the stack trace longer using the GPA config like this:
GeneralPurposeAllocator(.{.stack_trace_frames = 10}})

7 Likes

This was extremely helpful. Thank you for mentioning this! I had seen in the documentation a while back but it totally slipped my mind and pointed me right towards the issue, which was not actually in the fromLine function, but instead the anonymous function that was calling fromLine. Thank you for your help with this one!

4 Likes