From 3b25a09f0af9df7a6b0ad1218cc8896b828a5bf3 Mon Sep 17 00:00:00 2001 From: Isaac Freund Date: Thu, 27 Feb 2025 16:50:45 +0100 Subject: [PATCH 1/2] std: add mem.absorbSentinel() This will allow replacing (currently buggy) mem.sliceAsBytes() usage in the mem.Allocator implementation with, for example: const bytes: []u8 = @constCast(@ptrCast(mem.absorbSentinel(allocation))); References: https://github.com/ziglang/zig/pull/22706 --- lib/std/mem.zig | 53 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/lib/std/mem.zig b/lib/std/mem.zig index 311b5701a0..3b05a11f6b 100644 --- a/lib/std/mem.zig +++ b/lib/std/mem.zig @@ -4418,6 +4418,59 @@ test "sliceAsBytes preserves pointer attributes" { try testing.expectEqual(in.alignment, out.alignment); } +fn AbsorbSentinelReturnType(comptime Slice: type) type { + const info = @typeInfo(Slice).pointer; + assert(info.size == .slice); + return @Type(.{ + .pointer = .{ + .size = info.size, + .is_const = info.is_const, + .is_volatile = info.is_volatile, + .is_allowzero = info.is_allowzero, + .alignment = info.alignment, + .address_space = info.address_space, + .child = info.child, + .sentinel_ptr = null, + }, + }); +} + +/// If the provided slice is not sentinel terminated, do nothing and return that slice. +/// If it is sentinel-terminated, return a non-sentinel-terminated slice with the +/// length increased by one to include the absorbed sentinel element. +pub fn absorbSentinel(slice: anytype) AbsorbSentinelReturnType(@TypeOf(slice)) { + const info = @typeInfo(@TypeOf(slice)).pointer; + assert(info.size == .slice); + if (info.sentinel_ptr == null) { + return slice; + } else { + return slice.ptr[0 .. slice.len + 1]; + } +} + +test absorbSentinel { + { + var buffer: [3:0]u8 = .{ 1, 2, 3 }; + const foo: [:0]const u8 = &buffer; + const bar: []const u8 = &buffer; + try testing.expectEqual([]const u8, @TypeOf(absorbSentinel(foo))); + try testing.expectEqual([]const u8, @TypeOf(absorbSentinel(bar))); + try testing.expectEqualSlices(u8, &.{ 1, 2, 3, 0 }, absorbSentinel(foo)); + try testing.expectEqualSlices(u8, &.{ 1, 2, 3 }, absorbSentinel(bar)); + } + { + var buffer: [3:0]u8 = .{ 1, 2, 3 }; + const foo: [:0]u8 = &buffer; + const bar: []u8 = &buffer; + try testing.expectEqual([]u8, @TypeOf(absorbSentinel(foo))); + try testing.expectEqual([]u8, @TypeOf(absorbSentinel(bar))); + var expected_foo = [_]u8{ 1, 2, 3, 0 }; + try testing.expectEqualSlices(u8, &expected_foo, absorbSentinel(foo)); + var expected_bar = [_]u8{ 1, 2, 3 }; + try testing.expectEqualSlices(u8, &expected_bar, absorbSentinel(bar)); + } +} + /// Round an address down to the next (or current) aligned address. /// Unlike `alignForward`, `alignment` can be any positive number, not just a power of 2. pub fn alignForwardAnyAlign(comptime T: type, addr: T, alignment: T) T { From 7099274cb86d43e2b90cb6fd44f0bc02325cb47c Mon Sep 17 00:00:00 2001 From: Isaac Freund Date: Wed, 26 Mar 2025 14:12:25 +0100 Subject: [PATCH 2/2] std: fix sentinel handling in Allocator interface Currently the only function that handles sentinel terminated slices properly is free. All other uses of mem.sliceAsBytes() in the allocator interface lack proper handling of a possible sentinel. This commit changes the Allocator interface to use @ptrCast() plus the new mem.absorbSentinel() instead. This also makes incorrectly passing a pointer to array to Allocator.free() a compile error. The proper function to free a pointer to an array is Allocator.destroy(). Reported-by: David Vanderson References: https://github.com/ziglang/zig/pull/19984 References: https://github.com/ziglang/zig/pull/22706 References: https://github.com/ziglang/zig/pull/23020 --- lib/std/mem/Allocator.zig | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/lib/std/mem/Allocator.zig b/lib/std/mem/Allocator.zig index dc3e3f0325..44b02143a5 100644 --- a/lib/std/mem/Allocator.zig +++ b/lib/std/mem/Allocator.zig @@ -304,7 +304,7 @@ pub fn resize(self: Allocator, allocation: anytype, new_len: usize) bool { if (allocation.len == 0) { return false; } - const old_memory = mem.sliceAsBytes(allocation); + const old_memory: []u8 = @constCast(@ptrCast(mem.absorbSentinel(allocation))); // I would like to use saturating multiplication here, but LLVM cannot lower it // on WebAssembly: https://github.com/ziglang/zig/issues/9660 //const new_len_bytes = new_len *| @sizeOf(T); @@ -348,7 +348,7 @@ pub fn remap(self: Allocator, allocation: anytype, new_len: usize) t: { new_memory.len = new_len; return new_memory; } - const old_memory = mem.sliceAsBytes(allocation); + const old_memory: []u8 = @constCast(@ptrCast(mem.absorbSentinel(allocation))); // I would like to use saturating multiplication here, but LLVM cannot lower it // on WebAssembly: https://github.com/ziglang/zig/issues/9660 //const new_len_bytes = new_len *| @sizeOf(T); @@ -397,7 +397,7 @@ pub fn reallocAdvanced( return @as([*]align(Slice.alignment) T, @ptrFromInt(ptr))[0..0]; } - const old_byte_slice = mem.sliceAsBytes(old_mem); + const old_byte_slice: []u8 = @constCast(@ptrCast(mem.absorbSentinel(old_mem))); const byte_count = math.mul(usize, @sizeOf(T), new_n) catch return Error.OutOfMemory; // Note: can't set shrunk memory to undefined as memory shouldn't be modified on realloc failure if (self.rawRemap(old_byte_slice, .fromByteUnits(Slice.alignment), byte_count, return_address)) |p| { @@ -421,12 +421,10 @@ pub fn reallocAdvanced( /// To free a single item, see `destroy`. pub fn free(self: Allocator, memory: anytype) void { const Slice = @typeInfo(@TypeOf(memory)).pointer; - const bytes = mem.sliceAsBytes(memory); - const bytes_len = bytes.len + if (Slice.sentinel() != null) @sizeOf(Slice.child) else 0; - if (bytes_len == 0) return; - const non_const_ptr = @constCast(bytes.ptr); - @memset(non_const_ptr[0..bytes_len], undefined); - self.rawFree(non_const_ptr[0..bytes_len], .fromByteUnits(Slice.alignment), @returnAddress()); + const bytes: []u8 = @constCast(@ptrCast(mem.absorbSentinel(memory))); + if (bytes.len == 0) return; + @memset(bytes, undefined); + self.rawFree(bytes, .fromByteUnits(Slice.alignment), @returnAddress()); } /// Copies `m` to newly allocated memory. Caller owns the memory.