Why do I get an invalid free error?

I have two tests for a function that passes in an allocator. I free the memory created by the function in the test. One of the tests complains about an Invalid free, while the other works fine.

Here are the tests:

test "handles malformed utf8" {
    const allocator = std.testing.allocator;
    // This encodes "Xo’s" with a curly apostrophe
    // \u00e2 is an invalid zig string, so we have to escape the backslash
    const malformed = "Xo\\u00e2\\u0080\\u0099s";

    const result = try fix_insta_json_unicode(allocator, malformed);
    defer allocator.free(result);

    try std.testing.expectEqualSlices(u8, "Xo’s", result);
}

test "does not remove non-unicode sequence backslash" {
    const allocator = std.testing.allocator;
    const text_with_backslash = "C:\\Windows\\Path";

    const result = try fix_insta_json_unicode(allocator, text_with_backslash);
    defer allocator.free(result);

    try std.testing.expectEqualSlices(u8, "C:\\Windows\\Path", result);
}

And here’s the source to the fix_insta_json_unicode function.

If I comment out the defer line in the first test I get a memory address 0xXXXXXX leaked error so I think it does need to be there.

I think the problem might be, that you are freeing less memory, than you are allocating in your fix_insta_json_unicode function. Can it happen that insert_idx is less than text.len?

Basically exhibiting this behavior:

test "free" {
    const allocator = std.testing.allocator;
    const buf = try allocator.alloc(u8, 10);

    defer allocator.free(buf);
}

test "free_less" {
    const allocator = std.testing.allocator;
    const buf = try allocator.alloc(u8, 10);

    defer allocator.free(buf[0..5]);
}

The issue is you’re attempting to free a slice from within the buffer you allocated, not the buffer itself. You’ll need to return the full buffer in order to free it. The “zig way” of handling this (since it appears your allocation is bounded by the input text length, which is known outside the function) would be to allocate the temporary buffer outside the function in question, pass it as an argument, and have the function return a slice from within that pre-allocated buffer.

EDIT: The reason your 2nd test works is mostly likely that the size of the returned slice is equal to the size of the buffer allocated, so as far as the allocator sees, it’s the same as the allocation it created.