std.ArrayList: fix shrinkAndFree

Fixes a regression introduced in
e35f297aeb.

Now there is test coverage for ArrayList.shrinkAndFree in the case when
resizing fails.
This commit is contained in:
Andrew Kelley 2022-11-30 15:42:59 -07:00
parent c84bc3a06b
commit 16caea38d1
2 changed files with 33 additions and 34 deletions

View file

@ -314,32 +314,9 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {
/// Reduce allocated capacity to `new_len`. /// Reduce allocated capacity to `new_len`.
/// May invalidate element pointers. /// May invalidate element pointers.
pub fn shrinkAndFree(self: *Self, new_len: usize) void { pub fn shrinkAndFree(self: *Self, new_len: usize) void {
assert(new_len <= self.items.len); var unmanaged = self.moveToUnmanaged();
unmanaged.shrinkAndFree(self.allocator, new_len);
if (@sizeOf(T) == 0) { self.* = unmanaged.toManaged(self.allocator);
self.items.len = new_len;
return;
}
const old_memory = self.allocatedSlice();
if (self.allocator.resize(old_memory, new_len)) {
self.capacity = new_len;
self.items.len = new_len;
return;
}
const new_memory = self.allocator.alignedAlloc(T, alignment, new_len) catch |e| switch (e) {
error.OutOfMemory => {
// No problem, capacity is still correct then.
self.items.len = new_len;
return;
},
};
mem.copy(T, new_memory, self.items);
self.allocator.free(old_memory);
self.items = new_memory;
self.capacity = new_memory.len;
} }
/// Reduce length to `new_len`. /// Reduce length to `new_len`.
@ -782,7 +759,7 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ
}, },
}; };
mem.copy(T, new_memory, self.items); mem.copy(T, new_memory, self.items[0..new_len]);
allocator.free(old_memory); allocator.free(old_memory);
self.items = new_memory; self.items = new_memory;
self.capacity = new_memory.len; self.capacity = new_memory.len;
@ -1488,14 +1465,18 @@ test "std.ArrayListUnmanaged(u8) implements writer" {
} }
} }
test "std.ArrayList/ArrayListUnmanaged.shrink still sets length on error.OutOfMemory" { test "shrink still sets length when resizing is disabled" {
// use an arena allocator to make sure realloc returns error.OutOfMemory // Use the testing allocator but with resize disabled.
var arena = std.heap.ArenaAllocator.init(testing.allocator); var a = testing.allocator;
defer arena.deinit(); a.vtable = &.{
const a = arena.allocator(); .alloc = a.vtable.alloc,
.resize = Allocator.noResize,
.free = a.vtable.free,
};
{ {
var list = ArrayList(i32).init(a); var list = ArrayList(i32).init(a);
defer list.deinit();
try list.append(1); try list.append(1);
try list.append(2); try list.append(2);
@ -1506,6 +1487,7 @@ test "std.ArrayList/ArrayListUnmanaged.shrink still sets length on error.OutOfMe
} }
{ {
var list = ArrayListUnmanaged(i32){}; var list = ArrayListUnmanaged(i32){};
defer list.deinit(a);
try list.append(a, 1); try list.append(a, 1);
try list.append(a, 2); try list.append(a, 2);
@ -1516,6 +1498,22 @@ test "std.ArrayList/ArrayListUnmanaged.shrink still sets length on error.OutOfMe
} }
} }
test "shrinkAndFree with a copy" {
// Use the testing allocator but with resize disabled.
var a = testing.allocator;
a.vtable = &.{
.alloc = a.vtable.alloc,
.resize = Allocator.noResize,
.free = a.vtable.free,
};
var list = ArrayList(i32).init(a);
defer list.deinit();
try list.appendNTimes(3, 16);
list.shrinkAndFree(4);
try testing.expect(mem.eql(i32, list.items, &.{ 3, 3, 3, 3 }));
}
test "std.ArrayList/ArrayListUnmanaged.addManyAsArray" { test "std.ArrayList/ArrayListUnmanaged.addManyAsArray" {
const a = std.testing.allocator; const a = std.testing.allocator;
{ {

View file

@ -107,8 +107,9 @@ pub const ArenaAllocator = struct {
const cur_node = self.state.buffer_list.first orelse return false; const cur_node = self.state.buffer_list.first orelse return false;
const cur_buf = cur_node.data[@sizeOf(BufNode)..]; const cur_buf = cur_node.data[@sizeOf(BufNode)..];
if (@ptrToInt(cur_buf.ptr) + self.state.end_index != @ptrToInt(buf.ptr) + buf.len) { if (@ptrToInt(cur_buf.ptr) + self.state.end_index != @ptrToInt(buf.ptr) + buf.len) {
if (new_len > buf.len) return false; // It's not the most recent allocation, so it cannot be expanded,
return true; // but it's fine if they want to make it smaller.
return new_len <= buf.len;
} }
if (buf.len >= new_len) { if (buf.len >= new_len) {