Skip to content

Segmentation fault when calling DebugAllocator's deinit function #22892

Open
@qiuweishengx

Description

@qiuweishengx

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugObserved behavior contradicts documented or intended behaviorstandard libraryThis issue involves writing Zig code for the standard library.

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions