authorgravatar for mail@isaacfreund.comIsaac Freund <mail@isaacfreund.com> 2023-01-22 16:40:00+01:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-01-29 15:07:06-05:00
log23b7d28896609e3f01765730599119baf53a56c9
treecebad2d68d4c0a3d7059f181af6c2b7e1fff0116
parent7c2ba950a758b86893bfbe73521b29895f7ac4f0

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.

9 files changed, 56 insertions(+), 94 deletions(-)

lib/std/Thread.zig+1-1
......@@ -166,7 +166,7 @@ pub const GetNameError = error{
166166
167167pub fn getName(self: Thread, buffer_ptr: *[max_name_len:0]u8) GetNameError!?[]const u8 {
168168 buffer_ptr[max_name_len] = 0;
169 var buffer = std.mem.span(buffer_ptr);
169 var buffer: [:0]u8 = buffer_ptr;
170170
171171 switch (target.os.tag) {
172172 .linux => if (use_pthreads and is_gnu) {
lib/std/bounded_array.zig+5-1
......@@ -29,7 +29,11 @@ pub fn BoundedArray(comptime T: type, comptime buffer_capacity: usize) type {
2929 }
3030
3131 /// View the internal array as a slice whose size was previously set.
32 pub fn slice(self: anytype) mem.Span(@TypeOf(&self.buffer)) {
32 pub fn slice(self: anytype) switch (@TypeOf(&self.buffer)) {
33 *[buffer_capacity]T => []T,
34 *const [buffer_capacity]T => []const T,
35 else => unreachable,
36 } {
3337 return self.buffer[0..self.len];
3438 }
3539
lib/std/cstr.zig-1
......@@ -28,7 +28,6 @@ test "cstr fns" {
2828
2929fn testCStrFnsImpl() !void {
3030 try testing.expect(cmp("aoeu", "aoez") == -1);
31 try testing.expect(mem.len("123456789") == 9);
3231}
3332
3433/// Returns a mutable, null-terminated slice with the same length as `slice`.
lib/std/fs.zig+1-1
......@@ -834,7 +834,7 @@ pub const IterableDir = struct {
834834 self.end_index = self.index; // Force fd_readdir in the next loop.
835835 continue :start_over;
836836 }
837 const name = mem.span(self.buf[name_index .. name_index + entry.d_namlen]);
837 const name = self.buf[name_index .. name_index + entry.d_namlen];
838838
839839 const next_index = name_index + entry.d_namlen;
840840 self.index = next_index;
lib/std/io/fixed_buffer_stream.zig+19-6
......@@ -113,14 +113,27 @@ pub fn FixedBufferStream(comptime Buffer: type) type {
113113 };
114114}
115115
116pub fn fixedBufferStream(buffer: anytype) FixedBufferStream(NonSentinelSpan(@TypeOf(buffer))) {
117 return .{ .buffer = mem.span(buffer), .pos = 0 };
116pub fn fixedBufferStream(buffer: anytype) FixedBufferStream(Slice(@TypeOf(buffer))) {
117 return .{ .buffer = buffer, .pos = 0 };
118118}
119119
120fn NonSentinelSpan(comptime T: type) type {
121 var ptr_info = @typeInfo(mem.Span(T)).Pointer;
122 ptr_info.sentinel = null;
123 return @Type(.{ .Pointer = ptr_info });
120fn Slice(comptime T: type) type {
121 switch (@typeInfo(T)) {
122 .Pointer => |ptr_info| {
123 var new_ptr_info = ptr_info;
124 switch (ptr_info.size) {
125 .Slice => {},
126 .One => switch (@typeInfo(ptr_info.child)) {
127 .Array => |info| new_ptr_info.child = info.child,
128 else => @compileError("invalid type given to fixedBufferStream"),
129 },
130 else => @compileError("invalid type given to fixedBufferStream"),
131 }
132 new_ptr_info.size = .Slice;
133 return @Type(.{ .Pointer = new_ptr_info });
134 },
135 else => @compileError("invalid type given to fixedBufferStream"),
136 }
124137}
125138
126139test "FixedBufferStream output" {
lib/std/mem.zig+24-78
......@@ -636,12 +636,9 @@ test "indexOfDiff" {
636636 try testing.expectEqual(indexOfDiff(u8, "xne", "one"), 0);
637637}
638638
639/// Takes a pointer to an array, a sentinel-terminated pointer, or a slice, and
640/// returns a slice. If there is a sentinel on the input type, there will be a
641/// sentinel on the output type. The constness of the output type matches
642/// the constness of the input type. `[*c]` pointers are assumed to be 0-terminated,
643/// and assumed to not allow null.
644pub fn Span(comptime T: type) type {
639/// Takes a sentinel-terminated pointer and returns a slice preserving pointer attributes.
640/// `[*c]` pointers are assumed to be 0-terminated and assumed to not be allowzero.
641fn Span(comptime T: type) type {
645642 switch (@typeInfo(T)) {
646643 .Optional => |optional_info| {
647644 return ?Span(optional_info.child);
......@@ -649,39 +646,22 @@ pub fn Span(comptime T: type) type {
649646 .Pointer => |ptr_info| {
650647 var new_ptr_info = ptr_info;
651648 switch (ptr_info.size) {
652 .One => switch (@typeInfo(ptr_info.child)) {
653 .Array => |info| {
654 new_ptr_info.child = info.child;
655 new_ptr_info.sentinel = info.sentinel;
656 },
657 else => @compileError("invalid type given to std.mem.Span"),
658 },
659649 .C => {
660650 new_ptr_info.sentinel = &@as(ptr_info.child, 0);
661651 new_ptr_info.is_allowzero = false;
662652 },
663 .Many, .Slice => {},
653 .Many => if (ptr_info.sentinel == null) @compileError("invalid type given to std.mem.span: " ++ @typeName(T)),
654 .One, .Slice => @compileError("invalid type given to std.mem.span: " ++ @typeName(T)),
664655 }
665656 new_ptr_info.size = .Slice;
666657 return @Type(.{ .Pointer = new_ptr_info });
667658 },
668 else => @compileError("invalid type given to std.mem.Span"),
659 else => {},
669660 }
661 @compileError("invalid type given to std.mem.span: " ++ @typeName(T));
670662}
671663
672664test "Span" {
673 try testing.expect(Span(*[5]u16) == []u16);
674 try testing.expect(Span(?*[5]u16) == ?[]u16);
675 try testing.expect(Span(*const [5]u16) == []const u16);
676 try testing.expect(Span(?*const [5]u16) == ?[]const u16);
677 try testing.expect(Span([]u16) == []u16);
678 try testing.expect(Span(?[]u16) == ?[]u16);
679 try testing.expect(Span([]const u8) == []const u8);
680 try testing.expect(Span(?[]const u8) == ?[]const u8);
681 try testing.expect(Span([:1]u16) == [:1]u16);
682 try testing.expect(Span(?[:1]u16) == ?[:1]u16);
683 try testing.expect(Span([:1]const u8) == [:1]const u8);
684 try testing.expect(Span(?[:1]const u8) == ?[:1]const u8);
685665 try testing.expect(Span([*:1]u16) == [:1]u16);
686666 try testing.expect(Span(?[*:1]u16) == ?[:1]u16);
687667 try testing.expect(Span([*:1]const u8) == [:1]const u8);
......@@ -692,13 +672,10 @@ test "Span" {
692672 try testing.expect(Span(?[*c]const u8) == ?[:0]const u8);
693673}
694674
695/// Takes a pointer to an array, a sentinel-terminated pointer, or a slice, and
696/// returns a slice. If there is a sentinel on the input type, there will be a
697/// sentinel on the output type. The constness of the output type matches
698/// the constness of the input type.
699///
700/// When there is both a sentinel and an array length or slice length, the
701/// length value is used instead of the sentinel.
675/// Takes a sentinel-terminated pointer and returns a slice, iterating over the
676/// memory to find the sentinel and determine the length.
677/// Ponter attributes such as const are preserved.
678/// `[*c]` pointers are assumed to be non-null and 0-terminated.
702679pub fn span(ptr: anytype) Span(@TypeOf(ptr)) {
703680 if (@typeInfo(@TypeOf(ptr)) == .Optional) {
704681 if (ptr) |non_null| {
......@@ -722,7 +699,6 @@ test "span" {
722699 var array: [5]u16 = [_]u16{ 1, 2, 3, 4, 5 };
723700 const ptr = @as([*:3]u16, array[0..2 :3]);
724701 try testing.expect(eql(u16, span(ptr), &[_]u16{ 1, 2 }));
725 try testing.expect(eql(u16, span(&array), &[_]u16{ 1, 2, 3, 4, 5 }));
726702 try testing.expectEqual(@as(?[:0]u16, null), span(@as(?[*:0]u16, null)));
727703}
728704
......@@ -919,22 +895,15 @@ test "lenSliceTo" {
919895 }
920896}
921897
922/// Takes a pointer to an array, an array, a vector, a sentinel-terminated pointer,
923/// a slice or a tuple, and returns the length.
924/// In the case of a sentinel-terminated array, it uses the array length.
925/// For C pointers it assumes it is a pointer-to-many with a 0 sentinel.
898/// Takes a sentinel-terminated pointer and iterates over the memory to find the
899/// sentinel and determine the length.
900/// `[*c]` pointers are assumed to be non-null and 0-terminated.
926901pub fn len(value: anytype) usize {
927 return switch (@typeInfo(@TypeOf(value))) {
928 .Array => |info| info.len,
929 .Vector => |info| info.len,
902 switch (@typeInfo(@TypeOf(value))) {
930903 .Pointer => |info| switch (info.size) {
931 .One => switch (@typeInfo(info.child)) {
932 .Array => value.len,
933 else => @compileError("invalid type given to std.mem.len"),
934 },
935904 .Many => {
936905 const sentinel_ptr = info.sentinel orelse
937 @compileError("length of pointer with no sentinel");
906 @compileError("invalid type given to std.mem.len: " ++ @typeName(@TypeOf(value)));
938907 const sentinel = @ptrCast(*align(1) const info.child, sentinel_ptr).*;
939908 return indexOfSentinel(info.child, sentinel, value);
940909 },
......@@ -942,41 +911,18 @@ pub fn len(value: anytype) usize {
942911 assert(value != null);
943912 return indexOfSentinel(info.child, 0, value);
944913 },
945 .Slice => value.len,
914 else => @compileError("invalid type given to std.mem.len: " ++ @typeName(@TypeOf(value))),
946915 },
947 .Struct => |info| if (info.is_tuple) {
948 return info.fields.len;
949 } else @compileError("invalid type given to std.mem.len"),
950 else => @compileError("invalid type given to std.mem.len"),
951 };
916 else => @compileError("invalid type given to std.mem.len: " ++ @typeName(@TypeOf(value))),
917 }
952918}
953919
954920test "len" {
955 try testing.expect(len("aoeu") == 4);
956
957 {
958 var array: [5]u16 = [_]u16{ 1, 2, 3, 4, 5 };
959 try testing.expect(len(&array) == 5);
960 try testing.expect(len(array[0..3]) == 3);
961 array[2] = 0;
962 const ptr = @as([*:0]u16, array[0..2 :0]);
963 try testing.expect(len(ptr) == 2);
964 }
965 {
966 var array: [5:0]u16 = [_:0]u16{ 1, 2, 3, 4, 5 };
967 try testing.expect(len(&array) == 5);
968 array[2] = 0;
969 try testing.expect(len(&array) == 5);
970 }
971 {
972 const vector: meta.Vector(2, u32) = [2]u32{ 1, 2 };
973 try testing.expect(len(vector) == 2);
974 }
975 {
976 const tuple = .{ 1, 2 };
977 try testing.expect(len(tuple) == 2);
978 try testing.expect(tuple[0] == 1);
979 }
921 var array: [5]u16 = [_]u16{ 1, 2, 0, 4, 5 };
922 const ptr = @as([*:4]u16, array[0..3 :4]);
923 try testing.expect(len(ptr) == 3);
924 const c_ptr = @as([*c]u16, ptr);
925 try testing.expect(len(c_ptr) == 2);
980926}
981927
982928pub fn indexOfSentinel(comptime Elem: type, comptime sentinel: Elem, ptr: [*:sentinel]const Elem) usize {
src/link/MachO/load_commands.zig+1-1
......@@ -12,7 +12,7 @@ pub const default_dyld_path: [*:0]const u8 = "/usr/lib/dyld";
1212
1313fn calcInstallNameLen(cmd_size: u64, name: []const u8, assume_max_path_len: bool) u64 {
1414 const darwin_path_max = 1024;
15 const name_len = if (assume_max_path_len) darwin_path_max else std.mem.len(name) + 1;
15 const name_len = if (assume_max_path_len) darwin_path_max else name.len + 1;
1616 return mem.alignForwardGeneric(u64, cmd_size + name_len, @alignOf(u64));
1717}
1818
src/main.zig+4-4
......@@ -893,7 +893,7 @@ fn buildOutputType(
893893 i: usize = 0,
894894 fn next(it: *@This()) ?[]const u8 {
895895 if (it.i >= it.args.len) {
896 if (it.resp_file) |*resp| return if (resp.next()) |sentinel| std.mem.span(sentinel) else null;
896 if (it.resp_file) |*resp| return resp.next();
897897 return null;
898898 }
899899 defer it.i += 1;
......@@ -901,7 +901,7 @@ fn buildOutputType(
901901 }
902902 fn nextOrFatal(it: *@This()) []const u8 {
903903 if (it.i >= it.args.len) {
904 if (it.resp_file) |*resp| if (resp.next()) |sentinel| return std.mem.span(sentinel);
904 if (it.resp_file) |*resp| if (resp.next()) |ret| return ret;
905905 fatal("expected parameter after {s}", .{it.args[it.i - 1]});
906906 }
907907 defer it.i += 1;
......@@ -4973,7 +4973,7 @@ pub const ClangArgIterator = struct {
49734973 // rather than an argument to a parameter.
49744974 // We adjust the len below when necessary.
49754975 self.other_args = (self.argv.ptr + self.next_index)[0..1];
4976 var arg = mem.span(self.argv[self.next_index]);
4976 var arg = self.argv[self.next_index];
49774977 self.incrementArgIndex();
49784978
49794979 if (mem.startsWith(u8, arg, "@")) {
......@@ -5017,7 +5017,7 @@ pub const ClangArgIterator = struct {
50175017
50185018 self.has_next = true;
50195019 self.other_args = (self.argv.ptr + self.next_index)[0..1]; // We adjust len below when necessary.
5020 arg = mem.span(self.argv[self.next_index]);
5020 arg = self.argv[self.next_index];
50215021 self.incrementArgIndex();
50225022 }
50235023
test/behavior/basic.zig+1-1
......@@ -703,7 +703,7 @@ test "string concatenation" {
703703 comptime try expect(@TypeOf(a) == *const [12:0]u8);
704704 comptime try expect(@TypeOf(b) == *const [12:0]u8);
705705
706 const len = mem.len(b);
706 const len = b.len;
707707 const len_with_null = len + 1;
708708 {
709709 var i: u32 = 0;