Skip to content

sliceAsBytes: include sentinel #19984

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions lib/compiler/resinator/compile.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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));
Expand All @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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| {
Expand Down
2 changes: 1 addition & 1 deletion lib/compiler/resinator/res.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
24 changes: 24 additions & 0 deletions lib/std/heap/general_purpose_allocator.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
86 changes: 73 additions & 13 deletions lib/std/mem.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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(.{
Expand All @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 };
Expand Down Expand Up @@ -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 {
Expand All @@ -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);
}
Expand Down Expand Up @@ -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 {
Expand Down
7 changes: 3 additions & 4 deletions lib/std/mem/Allocator.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
5 changes: 3 additions & 2 deletions lib/std/unicode.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
{
Expand All @@ -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));
}
}
Expand Down