From 3d16c1eb7609fc3793f549e5396fa4da9c2042e9 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 | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/lib/std/mem.zig b/lib/std/mem.zig index 1628466120a7846af0fd51658240471ceff0f84f..9d03ecdce552e28dff38c7511bac048906c64956 100644 --- a/lib/std/mem.zig +++ b/lib/std/mem.zig @@ -4766,6 +4766,54 @@ 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 @Pointer(.slice, .{ + .@"const" = info.is_const, + .@"volatile" = info.is_volatile, + .@"allowzero" = info.is_allowzero, + .@"addrspace" = info.address_space, + .@"align" = info.alignment, + }, info.child, 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; + comptime 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 { -- 2.54.0 From 4848c3a1ad2bfd956419351afb60928e290f2be1 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 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. Reported-by: David Vanderson References: https://github.com/ziglang/zig/pull/19984 References: https://github.com/ziglang/zig/pull/23020 --- lib/std/mem/Allocator.zig | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/std/mem/Allocator.zig b/lib/std/mem/Allocator.zig index caf049f296fbc3f9a7fb1d92dc0dd0aa98e571ff..00298ae71ee144513b3fec1481d0ffb52594b664 100644 --- a/lib/std/mem/Allocator.zig +++ b/lib/std/mem/Allocator.zig @@ -322,7 +322,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 = @ptrCast(@constCast(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); @@ -368,7 +368,7 @@ pub fn remap(self: Allocator, allocation: anytype, new_len: usize) ?@TypeOf(allo new_memory.len = new_len; return new_memory; } - const old_memory = mem.sliceAsBytes(allocation); + const old_memory: []u8 = @ptrCast(@constCast(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); @@ -420,7 +420,7 @@ pub fn reallocAdvanced( return ptr; } - const old_byte_slice = mem.sliceAsBytes(old_mem); + const old_byte_slice: []u8 = @ptrCast(@constCast(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_info.alignment orelse @alignOf(T)), byte_count, return_address)) |p| { @@ -443,8 +443,7 @@ pub fn reallocAdvanced( pub fn free(self: Allocator, memory: anytype) void { const slice_info = @typeInfo(@TypeOf(memory)).pointer; comptime assert(slice_info.size == .slice); - const mem_with_sent = memory[0 .. memory.len + @intFromBool(slice_info.sentinel() != null)]; - const bytes: []u8 = @ptrCast(@constCast(mem_with_sent)); + const bytes: []u8 = @ptrCast(@constCast(mem.absorbSentinel(memory))); if (bytes.len == 0) return; @memset(bytes, undefined); self.rawFree(bytes, .fromByteUnits(slice_info.alignment orelse @alignOf(slice_info.child)), @returnAddress()); -- 2.54.0