Open
Description
Zig Version
0.14.0-dev.3180+75df7e502
Steps to Reproduce and Observed Behavior
const std = @import("std");
pub fn main() !void {
const config = std.heap.DebugAllocatorConfig{ };
var debug_allocator = std.heap.DebugAllocator(config){};
const allocator = debug_allocator.allocator();
const size = config.page_size >> 2;
const slice1 = try allocator.alloc(u8, size);
const slice2 = try allocator.alloc(u8, size);
_ = try allocator.alloc(u8, size);
allocator.free(slice1);
allocator.free(slice2);
std.debug.print("{}\n", .{debug_allocator.deinit()});
}
Output:
# some stack traces about memory leak
# ...
Segmentation fault at address 0x104b9ff30
aborting due to recursive panic
What cause this is that the singly-linked bucket list is not maintained when a bucket is removed.
// std/heap/debug_allocator.zig
fn free(
context: *anyopaque,
old_memory: []u8,
alignment: mem.Alignment,
return_address: usize,
) void {
// ...
bucket.freed_count += 1;
if (bucket.freed_count == bucket.allocated_count) {
if (self.buckets[size_class_index] == bucket) {
self.buckets[size_class_index] = null;
}
if (!config.never_unmap) {
const page: [*]align(page_size) u8 = @ptrFromInt(page_addr);
self.backing_allocator.rawFree(page[0..page_size], page_align, @returnAddress());
}
}
// ...
}
This can also cause the debug allocator to not report leaked memory correctly. For example:
const std = @import("std");
pub fn main() !void {
const config = std.heap.DebugAllocatorConfig{};
var debug_allocator = std.heap.DebugAllocator(config){};
const allocator = debug_allocator.allocator();
const size = config.page_size >> 2;
_ = try allocator.alloc(u8, size);
_ = try allocator.alloc(u8, size);
const slice = try allocator.alloc(u8, size);
allocator.free(slice);
std.debug.print("{}\n", .{debug_allocator.deinit()});
}
should print:
heap.Check.leak
instead it print:
heap.Check.ok
By the way, should the memory used by the buckets be free in deinit()?
// std/heap/debug_allocator.zig
pub fn deinit(self: *Self) std.heap.Check {
const leaks = if (config.safety) self.detectLeaks() else false;
// Should the leaked buckets be free here
if (config.retain_metadata) self.freeRetainedMetadata();
self.large_allocations.deinit(self.backing_allocator);
self.* = undefined;
return if (leaks) .leak else .ok;
}
Expected Behavior
Should print stack traces about memory leak. Then print:
heap.Check.leak