| ... | ... | @@ -210,6 +210,7 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type { |
| 210 | 210 | |
| 211 | 211 | const LargeAlloc = struct { |
| 212 | 212 | bytes: []u8, |
| 213 | requested_size: if (config.enable_memory_limit) usize else void, |
| 213 | 214 | stack_addresses: [trace_n][stack_n]usize, |
| 214 | 215 | freed: if (config.retain_metadata) bool else void, |
| 215 | 216 | ptr_align: if (config.never_unmap and config.retain_metadata) u29 else void, |
| ... | ... | @@ -528,13 +529,9 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type { |
| 528 | 529 | if (config.retain_metadata and entry.value_ptr.freed) { |
| 529 | 530 | if (config.safety) { |
| 530 | 531 | reportDoubleFree(ret_addr, entry.value_ptr.getStackTrace(.alloc), entry.value_ptr.getStackTrace(.free)); |
| 531 | | if (new_size == 0) { |
| 532 | | // Recoverable. Restore self.total_requested_bytes if needed. |
| 533 | | if (config.enable_memory_limit) { |
| 534 | | self.total_requested_bytes += old_mem.len; |
| 535 | | } |
| 532 | // Recoverable if this is a free. |
| 533 | if (new_size == 0) |
| 536 | 534 | return @as(usize, 0); |
| 537 | | } |
| 538 | 535 | @panic("Unrecoverable double free"); |
| 539 | 536 | } else { |
| 540 | 537 | unreachable; |
| ... | ... | @@ -556,7 +553,29 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type { |
| 556 | 553 | }); |
| 557 | 554 | } |
| 558 | 555 | |
| 559 | | const result_len = if (config.never_unmap and new_size == 0) 0 else try self.backing_allocator.resizeFn(self.backing_allocator, old_mem, old_align, new_size, len_align, ret_addr); |
| 556 | // Do memory limit accounting with requested sizes rather than what backing_allocator returns |
| 557 | // because if we want to return error.OutOfMemory, we have to leave allocation untouched, and |
| 558 | // that is impossible to guarantee after calling backing_allocator.resizeFn. |
| 559 | const prev_req_bytes = self.total_requested_bytes; |
| 560 | if (config.enable_memory_limit) { |
| 561 | const new_req_bytes = prev_req_bytes + new_size - entry.value_ptr.requested_size; |
| 562 | if (new_req_bytes > prev_req_bytes and new_req_bytes > self.requested_memory_limit) { |
| 563 | return error.OutOfMemory; |
| 564 | } |
| 565 | self.total_requested_bytes = new_req_bytes; |
| 566 | } |
| 567 | errdefer if (config.enable_memory_limit) { |
| 568 | self.total_requested_bytes = prev_req_bytes; |
| 569 | }; |
| 570 | |
| 571 | const result_len = if (config.never_unmap and new_size == 0) |
| 572 | 0 |
| 573 | else |
| 574 | try self.backing_allocator.resizeFn(self.backing_allocator, old_mem, old_align, new_size, len_align, ret_addr); |
| 575 | |
| 576 | if (config.enable_memory_limit) { |
| 577 | entry.value_ptr.requested_size = new_size; |
| 578 | } |
| 560 | 579 | |
| 561 | 580 | if (result_len == 0) { |
| 562 | 581 | if (config.verbose_log) { |
| ... | ... | @@ -599,18 +618,6 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type { |
| 599 | 618 | const held = self.mutex.acquire(); |
| 600 | 619 | defer held.release(); |
| 601 | 620 | |
| 602 | | const prev_req_bytes = self.total_requested_bytes; |
| 603 | | if (config.enable_memory_limit) { |
| 604 | | const new_req_bytes = prev_req_bytes + new_size - old_mem.len; |
| 605 | | if (new_req_bytes > prev_req_bytes and new_req_bytes > self.requested_memory_limit) { |
| 606 | | return error.OutOfMemory; |
| 607 | | } |
| 608 | | self.total_requested_bytes = new_req_bytes; |
| 609 | | } |
| 610 | | errdefer if (config.enable_memory_limit) { |
| 611 | | self.total_requested_bytes = prev_req_bytes; |
| 612 | | }; |
| 613 | | |
| 614 | 621 | assert(old_mem.len != 0); |
| 615 | 622 | |
| 616 | 623 | const aligned_size = math.max(old_mem.len, old_align); |
| ... | ... | @@ -651,19 +658,28 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type { |
| 651 | 658 | if (!is_used) { |
| 652 | 659 | if (config.safety) { |
| 653 | 660 | reportDoubleFree(ret_addr, bucketStackTrace(bucket, size_class, slot_index, .alloc), bucketStackTrace(bucket, size_class, slot_index, .free)); |
| 654 | | if (new_size == 0) { |
| 655 | | // Recoverable. Restore self.total_requested_bytes if needed, as we |
| 656 | | // don't return an error value so the errdefer above does not run. |
| 657 | | if (config.enable_memory_limit) { |
| 658 | | self.total_requested_bytes = prev_req_bytes; |
| 659 | | } |
| 661 | // Recoverable if this is a free. |
| 662 | if (new_size == 0) |
| 660 | 663 | return @as(usize, 0); |
| 661 | | } |
| 662 | 664 | @panic("Unrecoverable double free"); |
| 663 | 665 | } else { |
| 664 | 666 | unreachable; |
| 665 | 667 | } |
| 666 | 668 | } |
| 669 | |
| 670 | // Definitely an in-use small alloc now. |
| 671 | const prev_req_bytes = self.total_requested_bytes; |
| 672 | if (config.enable_memory_limit) { |
| 673 | const new_req_bytes = prev_req_bytes + new_size - old_mem.len; |
| 674 | if (new_req_bytes > prev_req_bytes and new_req_bytes > self.requested_memory_limit) { |
| 675 | return error.OutOfMemory; |
| 676 | } |
| 677 | self.total_requested_bytes = new_req_bytes; |
| 678 | } |
| 679 | errdefer if (config.enable_memory_limit) { |
| 680 | self.total_requested_bytes = prev_req_bytes; |
| 681 | }; |
| 682 | |
| 667 | 683 | if (new_size == 0) { |
| 668 | 684 | // Capture stack trace to be the "first free", in case a double free happens. |
| 669 | 685 | bucket.captureStackTrace(ret_addr, size_class, slot_index, .free); |
| ... | ... | @@ -745,21 +761,15 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type { |
| 745 | 761 | const held = self.mutex.acquire(); |
| 746 | 762 | defer held.release(); |
| 747 | 763 | |
| 764 | if (!self.isAllocationAllowed(len)) { |
| 765 | return error.OutOfMemory; |
| 766 | } |
| 767 | |
| 748 | 768 | const new_aligned_size = math.max(len, ptr_align); |
| 749 | 769 | if (new_aligned_size > largest_bucket_object_size) { |
| 750 | 770 | try self.large_allocations.ensureUnusedCapacity(self.backing_allocator, 1); |
| 751 | | |
| 752 | 771 | const slice = try self.backing_allocator.allocFn(self.backing_allocator, len, ptr_align, len_align, ret_addr); |
| 753 | 772 | |
| 754 | | // The backing allocator may return a memory block bigger than |
| 755 | | // `len`, use the effective size for bookkeeping purposes |
| 756 | | if (!self.isAllocationAllowed(slice.len)) { |
| 757 | | // Free the block so no memory is leaked |
| 758 | | const new_len = try self.backing_allocator.resizeFn(self.backing_allocator, slice, ptr_align, 0, 0, ret_addr); |
| 759 | | assert(new_len == 0); |
| 760 | | return error.OutOfMemory; |
| 761 | | } |
| 762 | | |
| 763 | 773 | const gop = self.large_allocations.getOrPutAssumeCapacity(@ptrToInt(slice.ptr)); |
| 764 | 774 | if (config.retain_metadata and !config.never_unmap) { |
| 765 | 775 | // Backing allocator may be reusing memory that we're retaining metadata for |
| ... | ... | @@ -768,6 +778,8 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type { |
| 768 | 778 | assert(!gop.found_existing); // This would mean the kernel double-mapped pages. |
| 769 | 779 | } |
| 770 | 780 | gop.value_ptr.bytes = slice; |
| 781 | if (config.enable_memory_limit) |
| 782 | gop.value_ptr.requested_size = len; |
| 771 | 783 | gop.value_ptr.captureStackTrace(ret_addr, .alloc); |
| 772 | 784 | if (config.retain_metadata) { |
| 773 | 785 | gop.value_ptr.freed = false; |
| ... | ... | @@ -782,10 +794,6 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type { |
| 782 | 794 | return slice; |
| 783 | 795 | } |
| 784 | 796 | |
| 785 | | if (!self.isAllocationAllowed(len)) { |
| 786 | | return error.OutOfMemory; |
| 787 | | } |
| 788 | | |
| 789 | 797 | const new_size_class = math.ceilPowerOfTwoAssert(usize, new_aligned_size); |
| 790 | 798 | const ptr = try self.allocSlot(new_size_class, ret_addr); |
| 791 | 799 | if (config.verbose_log) { |
| ... | ... | @@ -1183,3 +1191,14 @@ test "double frees" { |
| 1183 | 1191 | try std.testing.expect(gpa.large_allocations.contains(@ptrToInt(normal_large.ptr))); |
| 1184 | 1192 | try std.testing.expect(!gpa.large_allocations.contains(@ptrToInt(large.ptr))); |
| 1185 | 1193 | } |
| 1194 | |
| 1195 | test "bug 9995 fix, large allocs count requested size not backing size" { |
| 1196 | // with AtLeast, buffer likely to be larger than requested, especially when shrinking |
| 1197 | var gpa = GeneralPurposeAllocator(.{ .enable_memory_limit = true }){}; |
| 1198 | var buf = try gpa.allocator.allocAdvanced(u8, 1, page_size + 1, .at_least); |
| 1199 | try std.testing.expect(gpa.total_requested_bytes == page_size + 1); |
| 1200 | buf = try gpa.allocator.reallocAtLeast(buf, 1); |
| 1201 | try std.testing.expect(gpa.total_requested_bytes == 1); |
| 1202 | buf = try gpa.allocator.reallocAtLeast(buf, 2); |
| 1203 | try std.testing.expect(gpa.total_requested_bytes == 2); |
| 1204 | } |