authorgravatar for justus@klausecker.deJustus Klausecker <justus@klausecker.de> 2026-03-25 11:20:21+01:00
committergravatar for justus@klausecker.deJustus Klausecker <justus@klausecker.de> 2026-03-25 11:20:21+01:00
log9bfe827adedeffea591232fb713bea7b145c0add
treeeb6c963e70e446ec23f2187ab3534ffbb7997f42
parent94355f1920d880837823812481140270d0dc631e

Revert "std.heap.ArenaAllocator: Make `resize` and `free` check whether allocation is within current node more rigorously"

This reverts commit 589bcb2544fed9a3454908adba4a2f97f1405e9c. The scenario presented in the reverted commit cannot actually happen. Even if there are two contiguous arena nodes N1 and N2 and the `end_index` of N1 points to somewhere in N2, a `resize` can never lead to an increase of the `end_index` of N1 since it checks whether it's `<= size` first. A `resize`/`free` *can* decrease `end_index`, but even if it is wrongly assumed that some allocation that belongs to N2 actually belongs to N1 based on the `end_index` of N1, it can only ever be decreased to the start of the buffer of N2. That's because a valid allocation of N2 logically cannot be at any lower address than N2 itself. And any point still in N2 can never also be in N1, so there's no danger of overwriting any other allocations of N1.

1 files changed, 12 insertions(+), 25 deletions(-)

lib/std/heap/ArenaAllocator.zig+12-25
......@@ -319,11 +319,6 @@ fn pushFreeList(arena: *ArenaAllocator, first: *Node, last: *Node) void {
319319 }
320320}
321321
322fn sliceContainsSlice(container: []u8, slice: []u8) bool {
323 return @intFromPtr(slice.ptr) >= @intFromPtr(container.ptr) and
324 @intFromPtr(slice.ptr + slice.len) <= @intFromPtr(container.ptr + container.len);
325}
326
327322fn alignedIndex(buf_ptr: [*]u8, end_index: usize, alignment: Alignment) usize {
328323 // Wrapping arithmetic to avoid overflows since `end_index` isn't bounded by
329324 // `size`. This is always ok since the max alignment in byte units is also
......@@ -548,17 +543,12 @@ fn resize(ctx: *anyopaque, memory: []u8, alignment: Alignment, new_len: usize, r
548543 assert(new_len > 0);
549544
550545 const node = arena.loadFirstNode().?;
551 const buf = node.loadBuf();
552
553 if (!sliceContainsSlice(buf, memory)) {
554 // Not within current node.
555 return new_len <= memory.len;
556 }
546 const buf_ptr = @as([*]u8, @ptrCast(node)) + @sizeOf(Node);
557547
558548 const cur_end_index = @atomicLoad(usize, &node.end_index, .monotonic);
559
560 if (buf.ptr + cur_end_index != memory.ptr + memory.len) {
561 // It's not the most recent allocation, so it cannot be expanded.
549 if (buf_ptr + cur_end_index != memory.ptr + memory.len) {
550 // It's not the most recent allocation, so it cannot be expanded,
551 // but it's fine if they want to make it smaller.
562552 return new_len <= memory.len;
563553 }
564554
......@@ -566,12 +556,15 @@ fn resize(ctx: *anyopaque, memory: []u8, alignment: Alignment, new_len: usize, r
566556 if (memory.len >= new_len) {
567557 break :new_end_index cur_end_index - (memory.len - new_len);
568558 }
569 if (buf.len - cur_end_index >= new_len - memory.len) {
559 const cur_buf_len: usize = node.loadBuf().len;
560 // Saturating arithmetic because `end_index` and `size` are not
561 // guaranteed to be in sync.
562 if (cur_buf_len -| cur_end_index >= new_len - memory.len) {
570563 break :new_end_index cur_end_index + (new_len - memory.len);
571564 }
572565 return false;
573566 };
574 assert(buf.ptr + new_end_index == memory.ptr + new_len);
567 assert(buf_ptr + new_end_index == memory.ptr + new_len);
575568
576569 return null == @cmpxchgStrong(
577570 usize,
......@@ -596,22 +589,16 @@ fn free(ctx: *anyopaque, memory: []u8, alignment: Alignment, ret_addr: usize) vo
596589 assert(memory.len > 0);
597590
598591 const node = arena.loadFirstNode().?;
599 const buf = node.loadBuf();
600
601 if (!sliceContainsSlice(buf, memory)) {
602 // Not within current node; we cannot free it.
603 return;
604 }
592 const buf_ptr = @as([*]u8, @ptrCast(node)) + @sizeOf(Node);
605593
606594 const cur_end_index = @atomicLoad(usize, &node.end_index, .monotonic);
607
608 if (buf.ptr + cur_end_index != memory.ptr + memory.len) {
595 if (buf_ptr + cur_end_index != memory.ptr + memory.len) {
609596 // Not the most recent allocation; we cannot free it.
610597 return;
611598 }
612599
613600 const new_end_index = cur_end_index - memory.len;
614 assert(buf.ptr + new_end_index == memory.ptr);
601 assert(buf_ptr + new_end_index == memory.ptr);
615602
616603 _ = @cmpxchgStrong(
617604 usize,