Panic: Invalid free when freeing a struct's allocated memory

Hi everyone,

I’m building a struct with two fields (for now) []const i64 and []const u8 (plus the allocator).
Everything works, tests passes BUT freeing the []const u8 panics:

thread 165960 panic: Invalid free
/usr/lib/zig/std/heap/general_purpose_allocator.zig:652:21: 0x1048fac in freeLarge (test)
                    @panic("Invalid free");
                    ^
/usr/lib/zig/std/heap/general_purpose_allocator.zig:865:31: 0x104415c in free (test)
                self.freeLarge(old_mem, log2_old_align, ret_addr);

I’ve spent two days trying to understand that with no success.

  • the panic message does not give me a lot of information
  • it does not seem to be related to data size or alignment
  • in the source code of the freeLarge function (general_purpose_allocator.zig), I can imagine why it panics (no entry in the allocations table), but that does not help me neither
  • if I remove the free for the u8 slice, that works but of course indicates there’s a memory leak

Here is the struct and deinit definitions:

const Tz = struct {
    /// Allocator
    allocator: std.mem.Allocator,
    /// transition times timestamps table
    tzh_timecnt_data: []const i64,
    // indices for the next field
    tzh_timecnt_indices: []const u8,
    // a struct containing UTC offset, daylight saving time, abbreviation index
    //tzh_typecnt: []const Ttinfo,
    // abbreviations table
    //tz_abbr: []const u8),

    pub fn deinit(self: *const Tz) void {
        self.allocator.free(self.tzh_timecnt_data);
        self.allocator.free(self.tzh_timecnt_indices);
    }
};

If needed, the complete source is there:

I’m new at Zig, this is a learning project; porting a library I wrote previously in another language.
Until now I found answers to all my questions, but with this one I’m stuck :slight_smile:
I would greatly appreciate some help !

First up, before I get to the actual problem, a note on debugging errors like this:
Invalid free means that you are trying to free data that was never allocated with that allocator.
So in order to figure out the error, you need to trace back the source of the allocation, being tzh_timecnt_indices, in the code.

This leads us to the following piece of code:

    var tzh_timecnt_indices = try allocator.alloc(u8, tzh_timecnt_len);
    errdefer allocator.free(tzh_timecnt_indices);

    tzh_timecnt_indices = buffer[HEADER_LEN + header.v2_header_start + header.tzh_timecnt * 8 .. tzh_timecnt_end];

    // Returning the Tz struct
    return Tz{ .allocator = allocator, .tzh_timecnt_data = tzh_timecnt_data, .tzh_timecnt_indices = tzh_timecnt_indices };

The first red flag is var tzh_timecnt_indices, if you allocate something, then you don’t want to change the pointer again, only the thing it’s pointing to, so it should be const.

And you do overwrite the pointer with tzh_timecnt_indices = buffer[...];
After this line, tzh_timecnt_indices no longer points to the allocated memory, instead it points into buffer which is a temporary piece of memory.

If you want to copy the values inside the buffer into the newly allocated memory, then you should use @memcpy instead.

6 Likes

Thanks a lot ! That easily solved my problem :slight_smile: