From zig-dev
Detects potential memory leaks in Zig code by checking allocation/deallocation patterns, defer statements, and test cleanup. Use when reviewing code changes, writing new code with allocations, or debugging memory issues.
How this skill is triggered — by the user, by Claude, or both
Slash command
/zig-dev:memory-leak-detectorThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
This skill helps identify potential memory leaks in Zig code by analyzing allocation patterns and ensuring proper cleanup.
This skill helps identify potential memory leaks in Zig code by analyzing allocation patterns and ensuring proper cleanup.
Every allocation must have a corresponding deallocation:
Common allocation patterns:
allocator.alloc() -> requires allocator.free()allocator.create() -> requires allocator.destroy()Type.init(allocator) -> requires instance.deinit()ArrayList.init() -> requires list.deinit()HashMap.init() -> requires map.deinit()Proper cleanup pattern:
// GOOD: Immediate defer after allocation
const buffer = try allocator.alloc(u8, 100);
defer allocator.free(buffer);
const instance = try allocator.create(MyType);
defer allocator.destroy(instance);
var list = ArrayList(u8).init(allocator);
defer list.deinit();
Missing cleanup:
// BAD: No cleanup
const buffer = try allocator.alloc(u8, 100);
// Missing: defer allocator.free(buffer)
const instance = MyType.init(allocator);
// Missing: defer instance.deinit()
All test functions must clean up allocated resources:
Proper test cleanup:
test "my feature" {
const allocator = std.testing.allocator;
var server = try Server.init(allocator);
defer server.deinit(); // Cleanup before test ends
const value = try Value.create(allocator, "test");
defer value.deinit(); // Cleanup allocated value
// Test logic here
}
Missing test cleanup:
test "my feature" {
const allocator = std.testing.allocator;
var server = try Server.init(allocator);
// Missing: defer server.deinit()
const value = try Value.create(allocator, data);
// Missing: defer value.deinit()
// Test will leak memory!
}
Handle cleanup in error paths:
Proper error path cleanup:
// GOOD: Arena allocator for automatic cleanup
var arena = std.heap.ArenaAllocator.init(parent_allocator);
defer arena.deinit();
const allocator = arena.allocator();
// All allocations automatically freed on arena.deinit()
Or manual error handling:
// GOOD: Manual cleanup on error
const a = try allocator.alloc(u8, 10);
errdefer allocator.free(a);
const b = try allocator.alloc(u8, 20);
errdefer allocator.free(b);
// If second allocation fails, first is cleaned up
Missing error cleanup:
// BAD: Leak on error
const a = try allocator.alloc(u8, 10);
const b = try allocator.alloc(u8, 20); // If this fails, 'a' leaks
defer allocator.free(a);
defer allocator.free(b);
Types with resources must implement proper cleanup:
Required pattern:
pub const MyType = struct {
allocator: Allocator,
buffer: []u8,
pub fn init(allocator: Allocator, size: usize) !MyType {
const buffer = try allocator.alloc(u8, size);
return MyType{
.allocator = allocator,
.buffer = buffer,
};
}
pub fn deinit(self: *MyType) void {
self.allocator.free(self.buffer);
self.* = undefined; // Safety: prevent use-after-free
}
};
Usage:
var instance = try MyType.init(allocator, 100);
defer instance.deinit(); // Required
For temporary allocations, prefer arena allocators:
Recommended pattern:
pub fn processData(parent_allocator: Allocator) !Result {
var arena = std.heap.ArenaAllocator.init(parent_allocator);
defer arena.deinit(); // Single cleanup for all allocations
const allocator = arena.allocator();
// All temporary allocations use arena
const temp1 = try allocator.alloc(u8, 100);
const temp2 = try allocator.alloc(u8, 200);
// No need for individual defer statements
// All freed automatically on arena.deinit()
return result;
}
// RED FLAG
const data = try allocator.alloc(u8, size);
// Next line should be: defer allocator.free(data)
// RED FLAG: Allocates on every iteration
for (items) |item| {
const temp = try allocator.alloc(u8, item.size);
// Missing cleanup - leaks on each iteration
}
// BETTER: Use arena or defer inside loop
for (items) |item| {
const temp = try allocator.alloc(u8, item.size);
defer allocator.free(temp); // Cleanup each iteration
}
// This is OK - defer runs on all exit paths
const data = try allocator.alloc(u8, 100);
defer allocator.free(data);
if (condition) {
return error.Failed; // defer will run
}
But watch for:
// ACTUAL RED FLAG
const data = try allocator.alloc(u8, 100);
if (condition) {
return error.Failed; // Leaks data
}
defer allocator.free(data); // Too late for early returns
// WATCH OUT: Who owns the memory?
var list = ArrayList(*MyType).init(allocator);
defer list.deinit(); // Only frees the list, not items
const item = try allocator.create(MyType);
try list.append(item); // Must free item separately
// PROPER CLEANUP
defer {
for (list.items) |item| {
allocator.destroy(item);
}
list.deinit();
}
For every allocation:
defer statement?defer immediately after the allocation?For every test:
std.testing.allocator?For every struct with resources:
deinit() method?deinit() free all owned resources?For complex functions:
errdefer used appropriately?Zig's test allocator detects leaks automatically:
test "no leaks" {
const allocator = std.testing.allocator;
// If any allocation isn't freed, test fails
const data = try allocator.alloc(u8, 100);
defer allocator.free(data);
}
Run tests to detect leaks:
zig build test
Leaks will show as test failures with allocation tracking info.
When writing new code:
alloc/create/init has corresponding free/destroy/deinitdefer statement immediately follows allocationerrdeferWhen reviewing code:
defer statementsdeinit implementationsnpx claudepluginhub code0100fun/botfiles --plugin zig-devGuides Zig allocator selection, memory lifecycle management, leak debugging, and patterns like arena, page, and fixed-buffer allocation.
Guides Zig memory management and allocator selection. Covers arena, page, fixed-buffer allocators, memory lifecycle, and leak debugging.
Detects function-local misuse of memory and resource APIs in C, C++, and Rust unsafe — unchecked allocations, double-frees, uninitialized locks, and fd leaks across exec. Use when writing or reviewing low-level code that calls malloc, mmap, pthread_mutex, fopen, or raw FFI pointer APIs.