Commit 16caea38d1

Andrew Kelley <andrew@ziglang.org>
2022-11-30 23:42:59
std.ArrayList: fix shrinkAndFree
Fixes a regression introduced in e35f297aeb993ec956ae80379ddf7f86069e109b. Now there is test coverage for ArrayList.shrinkAndFree in the case when resizing fails.
1 parent c84bc3a
Changed files (2)
lib/std/heap/arena_allocator.zig
@@ -107,8 +107,9 @@ pub const ArenaAllocator = struct {
         const cur_node = self.state.buffer_list.first orelse return false;
         const cur_buf = cur_node.data[@sizeOf(BufNode)..];
         if (@ptrToInt(cur_buf.ptr) + self.state.end_index != @ptrToInt(buf.ptr) + buf.len) {
-            if (new_len > buf.len) return false;
-            return true;
+            // It's not the most recent allocation, so it cannot be expanded,
+            // but it's fine if they want to make it smaller.
+            return new_len <= buf.len;
         }
 
         if (buf.len >= new_len) {
lib/std/array_list.zig
@@ -314,32 +314,9 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {
         /// Reduce allocated capacity to `new_len`.
         /// May invalidate element pointers.
         pub fn shrinkAndFree(self: *Self, new_len: usize) void {
-            assert(new_len <= self.items.len);
-
-            if (@sizeOf(T) == 0) {
-                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;
+            var unmanaged = self.moveToUnmanaged();
+            unmanaged.shrinkAndFree(self.allocator, new_len);
+            self.* = unmanaged.toManaged(self.allocator);
         }
 
         /// 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);
             self.items = new_memory;
             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" {
-    // use an arena allocator to make sure realloc returns error.OutOfMemory
-    var arena = std.heap.ArenaAllocator.init(testing.allocator);
-    defer arena.deinit();
-    const a = arena.allocator();
+test "shrink still sets length when resizing is disabled" {
+    // 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.append(1);
         try list.append(2);
@@ -1506,6 +1487,7 @@ test "std.ArrayList/ArrayListUnmanaged.shrink still sets length on error.OutOfMe
     }
     {
         var list = ArrayListUnmanaged(i32){};
+        defer list.deinit(a);
 
         try list.append(a, 1);
         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" {
     const a = std.testing.allocator;
     {