From c9533046ca37a053f487f4002a999a29a96de517 Mon Sep 17 00:00:00 2001 From: Robbie Lyman Date: Sun, 12 Jul 2026 00:53:34 -0400 Subject: [PATCH] fix: Allocator contract should allow *[len]T in more places This commit continues work begun in #35222. Although after #35222 is now legal to call `Allocator.free` on memory of type *[len]T, code which does still does not compile. Additionally, similarly shaped footguns remain; this commit addresses those. --- lib/std/mem/Allocator.zig | 48 +++++++++++++++++++++++++++++++++++---- 1 file changed, 43 insertions(+), 5 deletions(-) diff --git a/lib/std/mem/Allocator.zig b/lib/std/mem/Allocator.zig index 486c69966e0e3d9bccf1b99e92d404cc36363283..e442427169f56898da818f11e3587ebe4d348dc2 100644 --- a/lib/std/mem/Allocator.zig +++ b/lib/std/mem/Allocator.zig @@ -305,6 +305,25 @@ pub fn allocBytesAligned( return @alignCast(byte_ptr); } +fn SliceType(comptime Pointer: type) type { + const info = @typeInfo(Pointer).pointer; + switch (info.size) { + .slice => return Pointer, + .one => { + const child_info = @typeInfo(info.child); + comptime assert(child_info == .array); + const sentinel_ptr: ?*const child_info.array.child = @ptrCast(@alignCast(child_info.array.sentinel_ptr)); + return @Pointer( + .slice, + info.attrs, + child_info.array.child, + if (sentinel_ptr) |ptr| ptr.* else null, + ); + }, + else => unreachable, + } +} + /// Request to modify the size of an allocation. /// /// It is guaranteed to not move the pointer, however the allocator @@ -316,6 +335,10 @@ pub fn allocBytesAligned( /// `new_len` may be zero, in which case the allocation is freed. pub fn resize(self: Allocator, allocation: anytype, new_len: usize) bool { const slice_info = @typeInfo(@TypeOf(allocation)).pointer; + if (slice_info.size != .slice) { + const slice: SliceType(@TypeOf(allocation)) = allocation; // coerce *[len]T to []T + return resize(self, slice, new_len); + } comptime assert(slice_info.size == .slice); const T = slice_info.child; if (new_len == 0) { @@ -354,8 +377,12 @@ pub fn resize(self: Allocator, allocation: anytype, new_len: usize) bool { /// `new_len` may be zero, in which case the allocation is freed. /// /// If the allocation's elements' type is zero bytes sized, `allocation.len` is set to `new_len`. -pub fn remap(self: Allocator, allocation: anytype, new_len: usize) ?@TypeOf(allocation) { +pub fn remap(self: Allocator, allocation: anytype, new_len: usize) ?SliceType(@TypeOf(allocation)) { const slice_info = @typeInfo(@TypeOf(allocation)).pointer; + if (slice_info.size != .slice) { + const slice: SliceType(@TypeOf(allocation)) = allocation; // coerce *[len]T to []T + return remap(self, slice, new_len); + } comptime assert(slice_info.size == .slice); const T = slice_info.child; @@ -399,7 +426,7 @@ pub fn remap(self: Allocator, allocation: anytype, new_len: usize) ?@TypeOf(allo /// do the realloc more efficiently than the caller /// * `resize` which returns `false` when the `Allocator` implementation cannot /// change the size without relocating the allocation. -pub fn realloc(self: Allocator, old_mem: anytype, new_n: usize) Error!@TypeOf(old_mem) { +pub fn realloc(self: Allocator, old_mem: anytype, new_n: usize) Error!SliceType(@TypeOf(old_mem)) { return self.reallocAdvanced(old_mem, new_n, @returnAddress()); } @@ -408,8 +435,12 @@ pub fn reallocAdvanced( old_mem: anytype, new_n: usize, return_address: usize, -) Error!@TypeOf(old_mem) { +) Error!SliceType(@TypeOf(old_mem)) { const slice_info = @typeInfo(@TypeOf(old_mem)).pointer; + if (slice_info.size != .slice) { + const slice: SliceType(@TypeOf(old_mem)) = old_mem; // coerce *[len]T to []T + return reallocAdvanced(self, slice, new_n, return_address); + } comptime assert(slice_info.size == .slice); const T = slice_info.child; if (old_mem.len == 0) { @@ -446,9 +477,10 @@ pub fn reallocAdvanced( pub fn free(self: Allocator, memory: anytype) void { const slice_info = @typeInfo(@TypeOf(memory)).pointer; if (slice_info.size != .slice) { - // slicing with comptime-known start and end results in *[len]T, which may be free'd - comptime assert(slice_info.size == .one and @typeInfo(slice_info.child) == .array); + const slice: SliceType(@TypeOf(memory)) = memory; // coerce *[len]T to []T + return free(self, slice); } + comptime assert(slice_info.size == .slice); const bytes: []u8 = @ptrCast(@constCast(mem.absorbSentinel(memory))); if (bytes.len == 0) return; @memset(bytes, undefined); @@ -591,3 +623,9 @@ test failing { try std.testing.expectError(error.OutOfMemory, f.alloc(u8, std.math.maxInt(usize))); try std.testing.expectError(error.OutOfMemory, f.allocSentinel(u8, std.math.maxInt(usize) - 1, 0)); } + +test "free single-pointer to array" { + const allocator = std.testing.allocator; + const bytes = allocator.alloc(u32, 128) catch return error.SkipZigTest; + allocator.free(bytes.ptr[0..128]); +} -- 2.54.0