diff --git a/lib/compiler/resinator/compile.zig b/lib/compiler/resinator/compile.zig index f9e211a4dc27..b941fde29e9a 100644 --- a/lib/compiler/resinator/compile.zig +++ b/lib/compiler/resinator/compile.zig @@ -1795,7 +1795,7 @@ pub const Compiler = struct { if (optional_statement_values.caption) |caption| { const parsed = try self.parseQuotedStringAsWideString(caption); defer self.allocator.free(parsed); - try data_writer.writeAll(std.mem.sliceAsBytes(parsed[0 .. parsed.len + 1])); + try data_writer.writeAll(std.mem.sliceAsBytes(parsed)); } else { try data_writer.writeInt(u16, 0, .little); } @@ -2089,7 +2089,7 @@ pub const Compiler = struct { const typeface = try self.parseQuotedStringAsWideString(node.typeface); defer self.allocator.free(typeface); - try writer.writeAll(std.mem.sliceAsBytes(typeface[0 .. typeface.len + 1])); + try writer.writeAll(std.mem.sliceAsBytes(typeface)); } pub fn writeMenu(self: *Compiler, node: *Node.Menu, writer: anytype) !void { @@ -2191,9 +2191,9 @@ pub const Compiler = struct { var result = evaluateNumberExpression(menu_item.result, self.source, self.input_code_pages); try writer.writeInt(u16, result.asWord(), .little); - var text = try self.parseQuotedStringAsWideString(menu_item.text); + const text = try self.parseQuotedStringAsWideString(menu_item.text); defer self.allocator.free(text); - try writer.writeAll(std.mem.sliceAsBytes(text[0 .. text.len + 1])); + try writer.writeAll(std.mem.sliceAsBytes(text)); }, .popup => { const popup: *Node.Popup = @alignCast(@fieldParentPtr("base", node)); @@ -2206,9 +2206,9 @@ pub const Compiler = struct { if (is_last_of_parent) flags.markLast(); try writer.writeInt(u16, flags.value, .little); - var text = try self.parseQuotedStringAsWideString(popup.text); + const text = try self.parseQuotedStringAsWideString(popup.text); defer self.allocator.free(text); - try writer.writeAll(std.mem.sliceAsBytes(text[0 .. text.len + 1])); + try writer.writeAll(std.mem.sliceAsBytes(text)); for (popup.items, 0..) |item, i| { const is_last = i == popup.items.len - 1; @@ -2245,9 +2245,9 @@ pub const Compiler = struct { if (node_type == .popup_ex) flags |= 0x01; try writer.writeInt(u16, flags, .little); - var text = try self.parseQuotedStringAsWideString(menu_item.text); + const text = try self.parseQuotedStringAsWideString(menu_item.text); defer self.allocator.free(text); - try writer.writeAll(std.mem.sliceAsBytes(text[0 .. text.len + 1])); + try writer.writeAll(std.mem.sliceAsBytes(text)); // Only the combination of the flags u16 and the text bytes can cause // non-DWORD alignment, so we can just use the byte length of those @@ -2284,7 +2284,7 @@ pub const Compiler = struct { try data_writer.writeInt(u16, 0, .little); // placeholder size try data_writer.writeInt(u16, res.FixedFileInfo.byte_len, .little); try data_writer.writeInt(u16, res.VersionNode.type_binary, .little); - const key_bytes = std.mem.sliceAsBytes(res.FixedFileInfo.key[0 .. res.FixedFileInfo.key.len + 1]); + const key_bytes = std.mem.sliceAsBytes(res.FixedFileInfo.key); try data_writer.writeAll(key_bytes); // The number of bytes written up to this point is always the same, since the name // of the node is a constant (FixedFileInfo.key). The total number of bytes @@ -2421,7 +2421,7 @@ pub const Compiler = struct { defer self.allocator.free(parsed_key); const parsed_key_to_first_null = std.mem.sliceTo(parsed_key, 0); - try writer.writeAll(std.mem.sliceAsBytes(parsed_key_to_first_null[0 .. parsed_key_to_first_null.len + 1])); + try writer.writeAll(std.mem.sliceAsBytes(parsed_key_to_first_null)); var has_number_value: bool = false; for (block_or_value.values) |value_value_node_uncasted| { diff --git a/lib/compiler/resinator/res.zig b/lib/compiler/resinator/res.zig index 991e0b8fb8a1..5733c4e8072b 100644 --- a/lib/compiler/resinator/res.zig +++ b/lib/compiler/resinator/res.zig @@ -246,7 +246,7 @@ pub const NameOrOrdinal = union(enum) { pub fn write(self: NameOrOrdinal, writer: anytype) !void { switch (self) { .name => |name| { - try writer.writeAll(std.mem.sliceAsBytes(name[0 .. name.len + 1])); + try writer.writeAll(std.mem.sliceAsBytes(name)); }, .ordinal => |ordinal| { try writer.writeInt(u16, 0xffff, .little); diff --git a/lib/std/heap/general_purpose_allocator.zig b/lib/std/heap/general_purpose_allocator.zig index b760c9d85de4..20402339ba77 100644 --- a/lib/std/heap/general_purpose_allocator.zig +++ b/lib/std/heap/general_purpose_allocator.zig @@ -1470,6 +1470,30 @@ test "bug 9995 fix, large allocs count requested size not backing size" { try std.testing.expect(gpa.total_requested_bytes == 2); } +test "resize with sentinel" { + var gpa = GeneralPurposeAllocator(test_config){}; + defer std.testing.expect(gpa.deinit() == .ok) catch @panic("leak"); + const allocator = gpa.allocator(); + + var buf = try allocator.allocWithOptions(u8, 6, null, 5); + try std.testing.expect(allocator.resize(buf, 3)); + buf[2] = 5; + buf = buf[0..2 :5]; + + allocator.free(buf); +} + +test "realloc with sentinel" { + var gpa = GeneralPurposeAllocator(test_config){}; + defer std.testing.expect(gpa.deinit() == .ok) catch @panic("leak"); + const allocator = gpa.allocator(); + + const buf = try allocator.allocWithOptions(u8, 6, null, 5); + const buf2 = try allocator.realloc(buf, 3); + + allocator.free(buf2); +} + test "retain metadata and never unmap" { var gpa = std.heap.GeneralPurposeAllocator(.{ .safety = true, diff --git a/lib/std/mem.zig b/lib/std/mem.zig index b4580b1aa4c1..faf7dc12f596 100644 --- a/lib/std/mem.zig +++ b/lib/std/mem.zig @@ -3947,6 +3947,7 @@ fn CopyPtrAttrs( comptime source: type, comptime size: std.builtin.Type.Pointer.Size, comptime child: type, + comptime sentinel: ?child, ) type { const info = @typeInfo(source).pointer; return @Type(.{ @@ -3958,14 +3959,14 @@ fn CopyPtrAttrs( .alignment = info.alignment, .address_space = info.address_space, .child = child, - .sentinel = null, + .sentinel = if (sentinel) |s| &s else null, }, }); } fn AsBytesReturnType(comptime P: type) type { const size = @sizeOf(std.meta.Child(P)); - return CopyPtrAttrs(P, .One, [size]u8); + return CopyPtrAttrs(P, .One, [size]u8, null); } /// Given a pointer to a single item, returns a slice of the underlying bytes, preserving pointer attributes. @@ -4048,7 +4049,7 @@ test toBytes { } fn BytesAsValueReturnType(comptime T: type, comptime B: type) type { - return CopyPtrAttrs(B, .One, T); + return CopyPtrAttrs(B, .One, T, null); } /// Given a pointer to an array of bytes, returns a pointer to a value of the specified type @@ -4126,24 +4127,34 @@ test bytesToValue { try testing.expect(deadbeef == @as(u32, 0xDEADBEEF)); } -fn BytesAsSliceReturnType(comptime T: type, comptime bytesType: type) type { - return CopyPtrAttrs(bytesType, .Slice, T); +fn BytesAsSliceReturnType(comptime T: type, comptime bytesType: type, comptime sentinel: ?T) type { + return CopyPtrAttrs(bytesType, .Slice, T, sentinel); } /// Given a slice of bytes, returns a slice of the specified type /// backed by those bytes, preserving pointer attributes. -pub fn bytesAsSlice(comptime T: type, bytes: anytype) BytesAsSliceReturnType(T, @TypeOf(bytes)) { +pub fn bytesAsSlice(comptime T: type, bytes: anytype) BytesAsSliceReturnType(T, @TypeOf(bytes), null) { // let's not give an undefined pointer to @ptrCast // it may be equal to zero and fail a null check if (bytes.len == 0) { return &[0]T{}; } - const cast_target = CopyPtrAttrs(@TypeOf(bytes), .Many, T); + const cast_target = CopyPtrAttrs(@TypeOf(bytes), .Many, T, null); return @as(cast_target, @ptrCast(bytes))[0..@divExact(bytes.len, @sizeOf(T))]; } +/// Given a slice of bytes, returns a sentinel-terminated slice of the +/// specified type backed by those bytes, preserving pointer attributes. +/// +/// The final bytes must equal the sentinel value, otherwise safety-protected +/// undefined behavior results. +pub fn bytesAsSliceSentinel(comptime T: type, bytes: anytype, comptime sentinel: T) BytesAsSliceReturnType(T, @TypeOf(bytes), sentinel) { + const slice = bytesAsSlice(T, bytes); + return slice[0 .. slice.len - 1 :sentinel]; +} + test bytesAsSlice { { const bytes = [_]u8{ 0xDE, 0xAD, 0xBE, 0xEF }; @@ -4213,24 +4224,41 @@ test "bytesAsSlice preserves pointer attributes" { try testing.expectEqual(in.alignment, out.alignment); } +test "bytesAsSliceSentinel" { + { + const bytes = [_]u8{ 0xDE, 0xAD, 0xBE, 0xEF, 0x01, 0x01 }; + const slice = bytesAsSliceSentinel(u16, bytes[0..], 0x0101); + try testing.expect(slice.len == 2); + try testing.expect(bigToNative(u16, slice[0]) == 0xDEAD); + try testing.expect(bigToNative(u16, slice[1]) == 0xBEEF); + try testing.expect(bigToNative(u16, slice[slice.len]) == 0x0101); + } +} + fn SliceAsBytesReturnType(comptime Slice: type) type { - return CopyPtrAttrs(Slice, .Slice, u8); + return CopyPtrAttrs(Slice, .Slice, u8, null); } /// Given a slice, returns a slice of the underlying bytes, preserving pointer attributes. +/// +/// If slice is sentinel-terminated the sentinel value is included. pub fn sliceAsBytes(slice: anytype) SliceAsBytesReturnType(@TypeOf(slice)) { const Slice = @TypeOf(slice); // a slice of zero-bit values always occupies zero bytes if (@sizeOf(std.meta.Elem(Slice)) == 0) return &[0]u8{}; + const has_sentinel = comptime std.meta.sentinel(Slice) != null; + // let's not give an undefined pointer to @ptrCast // it may be equal to zero and fail a null check - if (slice.len == 0 and std.meta.sentinel(Slice) == null) return &[0]u8{}; + if (slice.len == 0 and !has_sentinel) return &[0]u8{}; + + const cast_target = CopyPtrAttrs(Slice, .Many, u8, null); - const cast_target = CopyPtrAttrs(Slice, .Many, u8); + const length = if (has_sentinel) slice.len + 1 else slice.len; - return @as(cast_target, @ptrCast(slice))[0 .. slice.len * @sizeOf(std.meta.Elem(Slice))]; + return @as(cast_target, @ptrCast(slice))[0 .. length * @sizeOf(std.meta.Elem(Slice))]; } test sliceAsBytes { @@ -4243,8 +4271,18 @@ test sliceAsBytes { })); } -test "sliceAsBytes with sentinel slice" { - const empty_string: [:0]const u8 = ""; +test "sliceAsBytes with sentinel" { + const bytes = [_:1]u16{ 0xDEAD, 0xBEEF }; + const slice = sliceAsBytes(bytes[0..]); + try testing.expect(slice.len == 6); + try testing.expect(eql(u8, slice, switch (native_endian) { + .big => "\xDE\xAD\xBE\xEF\x00\x01", + .little => "\xAD\xDE\xEF\xBE\x01\x00", + })); +} + +test "sliceAsBytes with empty slice" { + const empty_string: []const u8 = &[0]u8{}; const bytes = sliceAsBytes(empty_string); try testing.expect(bytes.len == 0); } @@ -4312,6 +4350,28 @@ test "sliceAsBytes preserves pointer attributes" { try testing.expectEqual(in.alignment, out.alignment); } +test "sliceAsBytes and bytesAsSliceSentinel roundtrip with sentinel" { + try testing.expect(@sizeOf(i32) == 4); + + var array = [_:5]i32{ 1, 2, 3, 4 }; + const slice: [:5]i32 = array[0..]; + + const bytes = sliceAsBytes(slice); + try testing.expect(bytes.len == 4 * 5); + + var new_bytes = [_]u8{0} ** (4 * 5); + @memcpy(&new_bytes, bytes); + + const remade = bytesAsSliceSentinel(i32, &new_bytes, 5); + + try testing.expect(remade.len == 4); + try testing.expect(remade[0] == 1); + try testing.expect(remade[1] == 2); + try testing.expect(remade[2] == 3); + try testing.expect(remade[3] == 4); + try testing.expect(remade[remade.len] == 5); +} + /// 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 { diff --git a/lib/std/mem/Allocator.zig b/lib/std/mem/Allocator.zig index 0d4ab9141f3a..d98e8fa031f2 100644 --- a/lib/std/mem/Allocator.zig +++ b/lib/std/mem/Allocator.zig @@ -306,12 +306,11 @@ pub fn reallocAdvanced( 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; + if (bytes.len == 0) return; const non_const_ptr = @constCast(bytes.ptr); // TODO: https://github.com/ziglang/zig/issues/4298 - @memset(non_const_ptr[0..bytes_len], undefined); - self.rawFree(non_const_ptr[0..bytes_len], log2a(Slice.alignment), @returnAddress()); + @memset(non_const_ptr[0..bytes.len], undefined); + self.rawFree(non_const_ptr[0..bytes.len], log2a(Slice.alignment), @returnAddress()); } /// Copies `m` to newly allocated memory. Caller owns the memory. diff --git a/lib/std/unicode.zig b/lib/std/unicode.zig index 4c6ec1294ba1..724415401b6f 100644 --- a/lib/std/unicode.zig +++ b/lib/std/unicode.zig @@ -1312,13 +1312,13 @@ test utf8ToUtf16LeAllocZ { { const utf16 = try utf8ToUtf16LeAllocZ(testing.allocator, "𐐷"); defer testing.allocator.free(utf16); - try testing.expectEqualSlices(u8, "\x01\xd8\x37\xdc", mem.sliceAsBytes(utf16)); + try testing.expectEqualSlices(u8, "\x01\xd8\x37\xdc\x00\x00", mem.sliceAsBytes(utf16)); try testing.expect(utf16[2] == 0); } { const utf16 = try utf8ToUtf16LeAllocZ(testing.allocator, "\u{10FFFF}"); defer testing.allocator.free(utf16); - try testing.expectEqualSlices(u8, "\xff\xdb\xff\xdf", mem.sliceAsBytes(utf16)); + try testing.expectEqualSlices(u8, "\xff\xdb\xff\xdf\x00\x00", mem.sliceAsBytes(utf16)); try testing.expect(utf16[2] == 0); } { @@ -1338,6 +1338,7 @@ test utf8ToUtf16LeAllocZ { ' ', 0, 'w', 0, 'i', 0, 't', 0, 'h', 0, ' ', 0, 'o', 0, 'n', 0, 'e', 0, ' ', 0, 'h', 0, 'u', 0, 'n', 0, 'd', 0, 'r', 0, 'e', 0, 'd', 0, ' ', 0, 't', 0, 'w', 0, 'e', 0, 'n', 0, 't', 0, 'y', 0, '-', 0, 's', 0, 'e', 0, 'v', 0, 'e', 0, 'n', 0, ' ', 0, 'A', 0, 'S', 0, 'C', 0, 'I', 0, 'I', 0, ' ', 0, 'c', 0, 'h', 0, 'a', 0, 'r', 0, 'a', 0, 'c', 0, 't', 0, 'e', 0, 'r', 0, 's', 0, '¡', 0, + 0, 0, }, mem.sliceAsBytes(utf16)); } }