//! An allocator that never grows or shrinks a block in place, so every
//! growth is a fresh allocation an allocation-failure walk can count.
//!
//! Since Zig 0.17, `Allocator.print` and list growth first try an in-place
//! remap, and whether that succeeds depends on heap state left by earlier
//! runs and on the platform. `std.testing.checkAllAllocationFailures` needs
//! the same allocation count on every run, so the walks that trip on this
//! wrap `std.testing.allocator` in one of these.
const std = @import("std");
const NoGrowAllocator = @This();
inner: std.mem.Allocator,
pub fn init(inner: std.mem.Allocator) NoGrowAllocator {
return .{ .inner = inner };
}
pub fn allocator(self: *NoGrowAllocator) std.mem.Allocator {
return .{ .ptr = self, .vtable = &.{ .alloc = alloc, .resize = resize, .remap = remap, .free = free } };
}
fn alloc(ctx: *anyopaque, len: usize, alignment: std.mem.Alignment, ret_addr: usize) ?[*]u8 {
const self: *NoGrowAllocator = @ptrCast(@alignCast(ctx));
return self.inner.rawAlloc(len, alignment, ret_addr);
}
fn resize(_: *anyopaque, _: []u8, _: std.mem.Alignment, _: usize, _: usize) bool {
return false;
}
fn remap(_: *anyopaque, _: []u8, _: std.mem.Alignment, _: usize, _: usize) ?[*]u8 {
return null;
}
fn free(ctx: *anyopaque, memory: []u8, alignment: std.mem.Alignment, ret_addr: usize) void {
const self: *NoGrowAllocator = @ptrCast(@alignCast(ctx));
self.inner.rawFree(memory, alignment, ret_addr);
}
test "every growth is a fresh allocation" {
var no_grow = NoGrowAllocator.init(std.testing.allocator);
const gpa = no_grow.allocator();
var list: std.ArrayList(u8) = .empty;
defer list.deinit(gpa);
try list.appendSlice(gpa, "abc");
const before = list.items.ptr;
try list.ensureTotalCapacity(gpa, 4096);
try std.testing.expect(list.items.ptr != before);
try std.testing.expectEqualStrings("abc", list.items);
}