Skip to content

Commit 83e0e23

Browse files
squeek502andrewrk
authored andcommitted
ArrayList.toOwnedSlice: Fix potential for leaks when using errdefer
#13666 introduced a footgun when using `toOwnedSlice` with `errdefer array_list.deinit()`, since `toOwnedSlice` could retain capacity if `resize` failed, meaning it would leak without `deinit` being called. This meant that the only correct way to use `toOwnedSlice` was with `defer` instead of `errdefer` to ensure that the ArrayList would get cleaned up. Now, toOwnedSlice will now behave similarly to how it did before #13666, in that it will always clear the ArrayList's capacity if the resize/realloc succeeds. This also reverts commit 05890a1, which was contingent on the modified toOwnedSlice behavior. Closes #13946
1 parent 8ff9284 commit 83e0e23

1 file changed

Lines changed: 11 additions & 13 deletions

File tree

lib/std/array_list.zig

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {
5151
return if (alignment) |a| ([:s]align(a) T) else [:s]T;
5252
}
5353

54-
/// Deinitialize with `deinit`.
54+
/// Deinitialize with `deinit` or use `toOwnedSlice`.
5555
pub fn init(allocator: Allocator) Self {
5656
return Self{
5757
.items = &[_]T{},
@@ -62,7 +62,7 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {
6262

6363
/// Initialize with capacity to hold at least `num` elements.
6464
/// The resulting capacity is likely to be equal to `num`.
65-
/// Deinitialize with `deinit`.
65+
/// Deinitialize with `deinit` or use `toOwnedSlice`.
6666
pub fn initCapacity(allocator: Allocator, num: usize) Allocator.Error!Self {
6767
var self = Self.init(allocator);
6868
try self.ensureTotalCapacityPrecise(num);
@@ -78,7 +78,7 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {
7878

7979
/// ArrayList takes ownership of the passed in slice. The slice must have been
8080
/// allocated with `allocator`.
81-
/// Deinitialize with `deinit`.
81+
/// Deinitialize with `deinit` or use `toOwnedSlice`.
8282
pub fn fromOwnedSlice(allocator: Allocator, slice: Slice) Self {
8383
return Self{
8484
.items = slice,
@@ -97,8 +97,7 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {
9797
}
9898

9999
/// The caller owns the returned memory. Empties this ArrayList,
100-
/// however its capacity may or may not be cleared and deinit() is
101-
/// still required to clean up its memory.
100+
/// Its capacity is cleared, making deinit() safe but unnecessary to call.
102101
pub fn toOwnedSlice(self: *Self) Allocator.Error!Slice {
103102
const allocator = self.allocator;
104103

@@ -112,7 +111,7 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {
112111
const new_memory = try allocator.alignedAlloc(T, alignment, self.items.len);
113112
mem.copy(T, new_memory, self.items);
114113
@memset(@ptrCast([*]u8, self.items.ptr), undefined, self.items.len * @sizeOf(T));
115-
self.items.len = 0;
114+
self.clearAndFree();
116115
return new_memory;
117116
}
118117

@@ -475,15 +474,15 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {
475474
/// An ArrayList, but the allocator is passed as a parameter to the relevant functions
476475
/// rather than stored in the struct itself. The same allocator **must** be used throughout
477476
/// the entire lifetime of an ArrayListUnmanaged. Initialize directly or with
478-
/// `initCapacity`, and deinitialize with `deinit`.
477+
/// `initCapacity`, and deinitialize with `deinit` or use `toOwnedSlice`.
479478
pub fn ArrayListUnmanaged(comptime T: type) type {
480479
return ArrayListAlignedUnmanaged(T, null);
481480
}
482481

483482
/// An ArrayListAligned, but the allocator is passed as a parameter to the relevant
484483
/// functions rather than stored in the struct itself. The same allocator **must**
485484
/// be used throughout the entire lifetime of an ArrayListAlignedUnmanaged.
486-
/// Initialize directly or with `initCapacity`, and deinitialize with `deinit`.
485+
/// Initialize directly or with `initCapacity`, and deinitialize with `deinit` or use `toOwnedSlice`.
487486
pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) type {
488487
if (alignment) |a| {
489488
if (a == @alignOf(T)) {
@@ -514,7 +513,7 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ
514513

515514
/// Initialize with capacity to hold at least num elements.
516515
/// The resulting capacity is likely to be equal to `num`.
517-
/// Deinitialize with `deinit`.
516+
/// Deinitialize with `deinit` or use `toOwnedSlice`.
518517
pub fn initCapacity(allocator: Allocator, num: usize) Allocator.Error!Self {
519518
var self = Self{};
520519
try self.ensureTotalCapacityPrecise(allocator, num);
@@ -533,9 +532,8 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ
533532
return .{ .items = self.items, .capacity = self.capacity, .allocator = allocator };
534533
}
535534

536-
/// The caller owns the returned memory. Empties this ArrayList,
537-
/// however its capacity may or may not be cleared and deinit() is
538-
/// still required to clean up its memory.
535+
/// The caller owns the returned memory. Empties this ArrayList.
536+
/// Its capacity is cleared, making deinit() safe but unnecessary to call.
539537
pub fn toOwnedSlice(self: *Self, allocator: Allocator) Allocator.Error!Slice {
540538
const old_memory = self.allocatedSlice();
541539
if (allocator.resize(old_memory, self.items.len)) {
@@ -547,7 +545,7 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ
547545
const new_memory = try allocator.alignedAlloc(T, alignment, self.items.len);
548546
mem.copy(T, new_memory, self.items);
549547
@memset(@ptrCast([*]u8, self.items.ptr), undefined, self.items.len * @sizeOf(T));
550-
self.items.len = 0;
548+
self.clearAndFree(allocator);
551549
return new_memory;
552550
}
553551

0 commit comments

Comments
 (0)