Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

std: add pointer stability locks to array list #19676

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
92 changes: 92 additions & 0 deletions lib/std/array_list.zig
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {
capacity: usize,
allocator: Allocator,

/// Used to detect memory safety violations.
pointer_stability: debug.SafetyLock = .{},

pub const Slice = if (alignment) |a| ([]align(a) T) else []T;

pub fn SentinelSlice(comptime s: T) type {
Expand All @@ -69,11 +72,29 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {

/// Release all allocated memory.
pub fn deinit(self: Self) void {
self.pointer_stability.assertUnlocked();
if (@sizeOf(T) > 0) {
self.allocator.free(self.allocatedSlice());
}
}

/// Puts the array list into a state where any method call that would
/// cause an existing value pointer to become invalidated will
/// instead trigger an assertion.
///
/// An additional call to `lockPointers` in such state also triggers an
/// assertion.
///
/// `unlockPointers` returns the array list to the previous state.
pub fn lockPointers(self: *Self) void {
self.pointer_stability.lock();
}

/// Undoes a call to `lockPointers`.
pub fn unlockPointers(self: *Self) void {
self.pointer_stability.unlock();
}

/// ArrayList takes ownership of the passed in slice. The slice must have been
/// allocated with `allocator`.
/// Deinitialize with `deinit` or use `toOwnedSlice`.
Expand All @@ -99,6 +120,7 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {
/// Initializes an ArrayListUnmanaged with the `items` and `capacity` fields
/// of this ArrayList. Empties this ArrayList.
pub fn moveToUnmanaged(self: *Self) ArrayListAlignedUnmanaged(T, alignment) {
self.pointer_stability.assertUnlocked();
const allocator = self.allocator;
const result = .{ .items = self.items, .capacity = self.capacity };
self.* = init(allocator);
Expand Down Expand Up @@ -135,6 +157,7 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {

/// Creates a copy of this ArrayList, using the same allocator.
pub fn clone(self: Self) Allocator.Error!Self {
self.pointer_stability.assertUnlocked();
var cloned = try Self.initCapacity(self.allocator, self.capacity);
cloned.appendSliceAssumeCapacity(self.items);
return cloned;
Expand All @@ -157,6 +180,9 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {
/// Asserts that there is enough capacity for the new item.
/// Asserts that the index is in bounds or equal to the length.
pub fn insertAssumeCapacity(self: *Self, i: usize, item: T) void {
self.pointer_stability.lock();
defer self.pointer_stability.unlock();

assert(self.items.len < self.capacity);
self.items.len += 1;

Expand Down Expand Up @@ -190,6 +216,8 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {
return addManyAtAssumeCapacity(self, index, count);
}

self.pointer_stability.lock();
defer self.pointer_stability.unlock();
// Make a new allocation, avoiding `ensureTotalCapacity` in order
// to avoid extra memory copies.
const new_memory = try self.allocator.alignedAlloc(T, alignment, new_capacity);
Expand All @@ -213,6 +241,9 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {
/// does not invalidate any before that.
/// Asserts that the index is in bounds or equal to the length.
pub fn addManyAtAssumeCapacity(self: *Self, index: usize, count: usize) []T {
self.pointer_stability.lock();
defer self.pointer_stability.unlock();

const new_len = self.items.len + count;
assert(self.capacity >= new_len);
const to_move = self.items[index..];
Expand Down Expand Up @@ -403,17 +434,26 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {
/// Invalidates element pointers for the elements `items[new_len..]`.
/// Asserts that the new length is less than or equal to the previous length.
pub fn shrinkRetainingCapacity(self: *Self, new_len: usize) void {
self.pointer_stability.lock();
defer self.pointer_stability.unlock();

assert(new_len <= self.items.len);
self.items.len = new_len;
}

/// Invalidates all element pointers.
pub fn clearRetainingCapacity(self: *Self) void {
self.pointer_stability.lock();
defer self.pointer_stability.unlock();

self.items.len = 0;
}

/// Invalidates all element pointers.
pub fn clearAndFree(self: *Self) void {
self.pointer_stability.lock();
self.pointer_stability.unlock();

self.allocator.free(self.allocatedSlice());
self.items.len = 0;
self.capacity = 0;
Expand All @@ -438,6 +478,9 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {
/// modify the array so that it can hold exactly `new_capacity` items.
/// Invalidates element pointers if additional memory is needed.
pub fn ensureTotalCapacityPrecise(self: *Self, new_capacity: usize) Allocator.Error!void {
self.pointer_stability.lock();
defer self.pointer_stability.unlock();

if (@sizeOf(T) == 0) {
self.capacity = math.maxInt(usize);
return;
Expand Down Expand Up @@ -542,6 +585,9 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {
/// Invalidates element pointers to the removed element.
/// Asserts that the list is not empty.
pub fn pop(self: *Self) T {
self.pointer_stability.lock();
defer self.pointer_stability.unlock();

const val = self.items[self.items.len - 1];
self.items.len -= 1;
return val;
Expand Down Expand Up @@ -620,6 +666,9 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ
/// additional memory.
capacity: usize = 0,

/// Used to detect memory safety violations.
pointer_stability: debug.SafetyLock = .{},

pub const Slice = if (alignment) |a| ([]align(a) T) else []T;

pub fn SentinelSlice(comptime s: T) type {
Expand Down Expand Up @@ -648,10 +697,28 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ

/// Release all allocated memory.
pub fn deinit(self: *Self, allocator: Allocator) void {
self.pointer_stability.assertUnlocked();
allocator.free(self.allocatedSlice());
self.* = undefined;
}

/// Puts the unmanaged array list into a state where any method call that would
/// cause an existing value pointer to become invalidated will
/// instead trigger an assertion.
///
/// An additional call to `lockPointers` in such state also triggers an
/// assertion.
///
/// `unlockPointers` returns the unmanaged array list to the previous state.
pub fn lockPointers(self: *Self) void {
self.pointer_stability.lock();
}

/// Undoes a call to `lockPointers`.
pub fn unlockPointers(self: *Self) void {
self.pointer_stability.unlock();
}

/// Convert this list into an analogous memory-managed one.
/// The returned list has ownership of the underlying memory.
pub fn toManaged(self: *Self, allocator: Allocator) ArrayListAligned(T, alignment) {
Expand Down Expand Up @@ -706,6 +773,7 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ

/// Creates a copy of this ArrayList.
pub fn clone(self: Self, allocator: Allocator) Allocator.Error!Self {
self.pointer_stability.assertUnlocked();
var cloned = try Self.initCapacity(allocator, self.capacity);
cloned.appendSliceAssumeCapacity(self.items);
return cloned;
Expand All @@ -727,6 +795,9 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ
/// Asserts that the list has capacity for one additional item.
/// Asserts that the index is in bounds or equal to the length.
pub fn insertAssumeCapacity(self: *Self, i: usize, item: T) void {
self.pointer_stability.lock();
defer self.pointer_stability.unlock();

assert(self.items.len < self.capacity);
self.items.len += 1;

Expand Down Expand Up @@ -762,6 +833,9 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ
/// Asserts that the list has capacity for the additional items.
/// Asserts that the index is in bounds or equal to the length.
pub fn addManyAtAssumeCapacity(self: *Self, index: usize, count: usize) []T {
self.pointer_stability.lock();
defer self.pointer_stability.unlock();

const new_len = self.items.len + count;
assert(self.capacity >= new_len);
const to_move = self.items[index..];
Expand Down Expand Up @@ -830,6 +904,9 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ
const dst = self.addManyAtAssumeCapacity(after_range, rest.len);
@memcpy(dst, rest);
} else {
self.pointer_stability.lock();
defer self.pointer_stability.unlock();

const extra = range.len - new_items.len;
@memcpy(range[0..new_items.len], new_items);
std.mem.copyForwards(
Expand Down Expand Up @@ -1032,17 +1109,26 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ
/// Keeps capacity the same.
/// Asserts that the new length is less than or equal to the previous length.
pub fn shrinkRetainingCapacity(self: *Self, new_len: usize) void {
self.pointer_stability.lock();
defer self.pointer_stability.unlock();

assert(new_len <= self.items.len);
self.items.len = new_len;
}

/// Invalidates all element pointers.
pub fn clearRetainingCapacity(self: *Self) void {
self.pointer_stability.lock();
defer self.pointer_stability.unlock();

self.items.len = 0;
}

/// Invalidates all element pointers.
pub fn clearAndFree(self: *Self, allocator: Allocator) void {
self.pointer_stability.lock();
defer self.pointer_stability.unlock();

allocator.free(self.allocatedSlice());
self.items.len = 0;
self.capacity = 0;
Expand All @@ -1062,6 +1148,9 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ
/// modify the array so that it can hold exactly `new_capacity` items.
/// Invalidates element pointers if additional memory is needed.
pub fn ensureTotalCapacityPrecise(self: *Self, allocator: Allocator, new_capacity: usize) Allocator.Error!void {
self.pointer_stability.lock();
defer self.pointer_stability.unlock();

if (@sizeOf(T) == 0) {
self.capacity = math.maxInt(usize);
return;
Expand Down Expand Up @@ -1170,6 +1259,9 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ
/// Invalidates pointers to last element.
/// Asserts that the list is not empty.
pub fn pop(self: *Self) T {
self.pointer_stability.lock();
defer self.pointer_stability.unlock();

const val = self.items[self.items.len - 1];
self.items.len -= 1;
return val;
Expand Down