From 23b7d28896609e3f01765730599119baf53a56c9 Mon Sep 17 00:00:00 2001 From: Isaac Freund Date: Sun, 22 Jan 2023 16:40:00 +0100 Subject: std: restrict mem.span() and mem.len() to sentinel terminated pointers These functions are currently footgunny when working with pointers to arrays and slices. They just return the stated length of the array/slice without iterating and looking for the first sentinel, even if the array/slice is a sentinel terminated type. From looking at the quite small list of places in the standard library/compiler that this change breaks existing code, the new code looks to be more readable in all cases. The usage of std.mem.span/len was totally unneeded in most of the cases affected by this breaking change. We could remove these functions entirely in favor of other existing functions in std.mem such as std.mem.sliceTo(), but that would be a somewhat nasty breaking change as std.mem.span() is very widely used for converting sentinel terminated pointers to slices. It is however not at all widely used for anything else. Therefore I think it is better to break these few non-standard and potentially incorrect usages of these functions now and at some later time, if deemed worthwhile, finally remove these functions. If we wait for at least a full release cycle so that everyone adapts to this change first, updating for the removal could be a simple find and replace without needing to worry about the semantics. --- lib/std/mem.zig | 102 +++++++++++++------------------------------------------- 1 file changed, 24 insertions(+), 78 deletions(-) (limited to 'lib/std/mem.zig') diff --git a/lib/std/mem.zig b/lib/std/mem.zig index 8691c5bbad..371ef8fd8d 100644 --- a/lib/std/mem.zig +++ b/lib/std/mem.zig @@ -636,12 +636,9 @@ test "indexOfDiff" { try testing.expectEqual(indexOfDiff(u8, "xne", "one"), 0); } -/// Takes a pointer to an array, a sentinel-terminated pointer, or a slice, and -/// returns a slice. If there is a sentinel on the input type, there will be a -/// sentinel on the output type. The constness of the output type matches -/// the constness of the input type. `[*c]` pointers are assumed to be 0-terminated, -/// and assumed to not allow null. -pub fn Span(comptime T: type) type { +/// Takes a sentinel-terminated pointer and returns a slice preserving pointer attributes. +/// `[*c]` pointers are assumed to be 0-terminated and assumed to not be allowzero. +fn Span(comptime T: type) type { switch (@typeInfo(T)) { .Optional => |optional_info| { return ?Span(optional_info.child); @@ -649,39 +646,22 @@ pub fn Span(comptime T: type) type { .Pointer => |ptr_info| { var new_ptr_info = ptr_info; switch (ptr_info.size) { - .One => switch (@typeInfo(ptr_info.child)) { - .Array => |info| { - new_ptr_info.child = info.child; - new_ptr_info.sentinel = info.sentinel; - }, - else => @compileError("invalid type given to std.mem.Span"), - }, .C => { new_ptr_info.sentinel = &@as(ptr_info.child, 0); new_ptr_info.is_allowzero = false; }, - .Many, .Slice => {}, + .Many => if (ptr_info.sentinel == null) @compileError("invalid type given to std.mem.span: " ++ @typeName(T)), + .One, .Slice => @compileError("invalid type given to std.mem.span: " ++ @typeName(T)), } new_ptr_info.size = .Slice; return @Type(.{ .Pointer = new_ptr_info }); }, - else => @compileError("invalid type given to std.mem.Span"), + else => {}, } + @compileError("invalid type given to std.mem.span: " ++ @typeName(T)); } test "Span" { - try testing.expect(Span(*[5]u16) == []u16); - try testing.expect(Span(?*[5]u16) == ?[]u16); - try testing.expect(Span(*const [5]u16) == []const u16); - try testing.expect(Span(?*const [5]u16) == ?[]const u16); - try testing.expect(Span([]u16) == []u16); - try testing.expect(Span(?[]u16) == ?[]u16); - try testing.expect(Span([]const u8) == []const u8); - try testing.expect(Span(?[]const u8) == ?[]const u8); - try testing.expect(Span([:1]u16) == [:1]u16); - try testing.expect(Span(?[:1]u16) == ?[:1]u16); - try testing.expect(Span([:1]const u8) == [:1]const u8); - try testing.expect(Span(?[:1]const u8) == ?[:1]const u8); try testing.expect(Span([*:1]u16) == [:1]u16); try testing.expect(Span(?[*:1]u16) == ?[:1]u16); try testing.expect(Span([*:1]const u8) == [:1]const u8); @@ -692,13 +672,10 @@ test "Span" { try testing.expect(Span(?[*c]const u8) == ?[:0]const u8); } -/// Takes a pointer to an array, a sentinel-terminated pointer, or a slice, and -/// returns a slice. If there is a sentinel on the input type, there will be a -/// sentinel on the output type. The constness of the output type matches -/// the constness of the input type. -/// -/// When there is both a sentinel and an array length or slice length, the -/// length value is used instead of the sentinel. +/// Takes a sentinel-terminated pointer and returns a slice, iterating over the +/// memory to find the sentinel and determine the length. +/// Ponter attributes such as const are preserved. +/// `[*c]` pointers are assumed to be non-null and 0-terminated. pub fn span(ptr: anytype) Span(@TypeOf(ptr)) { if (@typeInfo(@TypeOf(ptr)) == .Optional) { if (ptr) |non_null| { @@ -722,7 +699,6 @@ test "span" { var array: [5]u16 = [_]u16{ 1, 2, 3, 4, 5 }; const ptr = @as([*:3]u16, array[0..2 :3]); try testing.expect(eql(u16, span(ptr), &[_]u16{ 1, 2 })); - try testing.expect(eql(u16, span(&array), &[_]u16{ 1, 2, 3, 4, 5 })); try testing.expectEqual(@as(?[:0]u16, null), span(@as(?[*:0]u16, null))); } @@ -919,22 +895,15 @@ test "lenSliceTo" { } } -/// Takes a pointer to an array, an array, a vector, a sentinel-terminated pointer, -/// a slice or a tuple, and returns the length. -/// In the case of a sentinel-terminated array, it uses the array length. -/// For C pointers it assumes it is a pointer-to-many with a 0 sentinel. +/// Takes a sentinel-terminated pointer and iterates over the memory to find the +/// sentinel and determine the length. +/// `[*c]` pointers are assumed to be non-null and 0-terminated. pub fn len(value: anytype) usize { - return switch (@typeInfo(@TypeOf(value))) { - .Array => |info| info.len, - .Vector => |info| info.len, + switch (@typeInfo(@TypeOf(value))) { .Pointer => |info| switch (info.size) { - .One => switch (@typeInfo(info.child)) { - .Array => value.len, - else => @compileError("invalid type given to std.mem.len"), - }, .Many => { const sentinel_ptr = info.sentinel orelse - @compileError("length of pointer with no sentinel"); + @compileError("invalid type given to std.mem.len: " ++ @typeName(@TypeOf(value))); const sentinel = @ptrCast(*align(1) const info.child, sentinel_ptr).*; return indexOfSentinel(info.child, sentinel, value); }, @@ -942,41 +911,18 @@ pub fn len(value: anytype) usize { assert(value != null); return indexOfSentinel(info.child, 0, value); }, - .Slice => value.len, + else => @compileError("invalid type given to std.mem.len: " ++ @typeName(@TypeOf(value))), }, - .Struct => |info| if (info.is_tuple) { - return info.fields.len; - } else @compileError("invalid type given to std.mem.len"), - else => @compileError("invalid type given to std.mem.len"), - }; + else => @compileError("invalid type given to std.mem.len: " ++ @typeName(@TypeOf(value))), + } } test "len" { - try testing.expect(len("aoeu") == 4); - - { - var array: [5]u16 = [_]u16{ 1, 2, 3, 4, 5 }; - try testing.expect(len(&array) == 5); - try testing.expect(len(array[0..3]) == 3); - array[2] = 0; - const ptr = @as([*:0]u16, array[0..2 :0]); - try testing.expect(len(ptr) == 2); - } - { - var array: [5:0]u16 = [_:0]u16{ 1, 2, 3, 4, 5 }; - try testing.expect(len(&array) == 5); - array[2] = 0; - try testing.expect(len(&array) == 5); - } - { - const vector: meta.Vector(2, u32) = [2]u32{ 1, 2 }; - try testing.expect(len(vector) == 2); - } - { - const tuple = .{ 1, 2 }; - try testing.expect(len(tuple) == 2); - try testing.expect(tuple[0] == 1); - } + var array: [5]u16 = [_]u16{ 1, 2, 0, 4, 5 }; + const ptr = @as([*:4]u16, array[0..3 :4]); + try testing.expect(len(ptr) == 3); + const c_ptr = @as([*c]u16, ptr); + try testing.expect(len(c_ptr) == 2); } pub fn indexOfSentinel(comptime Elem: type, comptime sentinel: Elem, ptr: [*:sentinel]const Elem) usize { -- cgit v1.2.3