| ... | @@ -210,6 +210,7 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type { | ... | @@ -210,6 +210,7 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type { |
| 210 | | 210 | |
| 211 | const LargeAlloc = struct { | 211 | const LargeAlloc = struct { |
| 212 | bytes: []u8, | 212 | bytes: []u8, |
| | 213 | requested_size: if (config.enable_memory_limit) usize else void, |
| 213 | stack_addresses: [trace_n][stack_n]usize, | 214 | stack_addresses: [trace_n][stack_n]usize, |
| 214 | freed: if (config.retain_metadata) bool else void, | 215 | freed: if (config.retain_metadata) bool else void, |
| 215 | ptr_align: if (config.never_unmap and config.retain_metadata) u29 else void, | 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,13 +529,9 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type { |
| 528 | if (config.retain_metadata and entry.value_ptr.freed) { | 529 | if (config.retain_metadata and entry.value_ptr.freed) { |
| 529 | if (config.safety) { | 530 | if (config.safety) { |
| 530 | reportDoubleFree(ret_addr, entry.value_ptr.getStackTrace(.alloc), entry.value_ptr.getStackTrace(.free)); | 531 | reportDoubleFree(ret_addr, entry.value_ptr.getStackTrace(.alloc), entry.value_ptr.getStackTrace(.free)); |
| 531 | if (new_size == 0) { | 532 | // Recoverable if this is a free. |
| 532 | // Recoverable. Restore self.total_requested_bytes if needed. | 533 | if (new_size == 0) |
| 533 | if (config.enable_memory_limit) { | | |
| 534 | self.total_requested_bytes += old_mem.len; | | |
| 535 | } | | |
| 536 | return @as(usize, 0); | 534 | return @as(usize, 0); |
| 537 | } | | |
| 538 | @panic("Unrecoverable double free"); | 535 | @panic("Unrecoverable double free"); |
| 539 | } else { | 536 | } else { |
| 540 | unreachable; | 537 | unreachable; |
| ... | @@ -556,7 +553,29 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type { | ... | @@ -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 | if (result_len == 0) { | 580 | if (result_len == 0) { |
| 562 | if (config.verbose_log) { | 581 | if (config.verbose_log) { |
| ... | @@ -599,18 +618,6 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type { | ... | @@ -599,18 +618,6 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type { |
| 599 | const held = self.mutex.acquire(); | 618 | const held = self.mutex.acquire(); |
| 600 | defer held.release(); | 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 | assert(old_mem.len != 0); | 621 | assert(old_mem.len != 0); |
| 615 | | 622 | |
| 616 | const aligned_size = math.max(old_mem.len, old_align); | 623 | const aligned_size = math.max(old_mem.len, old_align); |
| ... | @@ -651,19 +658,28 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type { | ... | @@ -651,19 +658,28 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type { |
| 651 | if (!is_used) { | 658 | if (!is_used) { |
| 652 | if (config.safety) { | 659 | if (config.safety) { |
| 653 | reportDoubleFree(ret_addr, bucketStackTrace(bucket, size_class, slot_index, .alloc), bucketStackTrace(bucket, size_class, slot_index, .free)); | 660 | reportDoubleFree(ret_addr, bucketStackTrace(bucket, size_class, slot_index, .alloc), bucketStackTrace(bucket, size_class, slot_index, .free)); |
| 654 | if (new_size == 0) { | 661 | // Recoverable if this is a free. |
| 655 | // Recoverable. Restore self.total_requested_bytes if needed, as we | 662 | if (new_size == 0) |
| 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 | } | | |
| 660 | return @as(usize, 0); | 663 | return @as(usize, 0); |
| 661 | } | | |
| 662 | @panic("Unrecoverable double free"); | 664 | @panic("Unrecoverable double free"); |
| 663 | } else { | 665 | } else { |
| 664 | unreachable; | 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 | if (new_size == 0) { | 683 | if (new_size == 0) { |
| 668 | // Capture stack trace to be the "first free", in case a double free happens. | 684 | // Capture stack trace to be the "first free", in case a double free happens. |
| 669 | bucket.captureStackTrace(ret_addr, size_class, slot_index, .free); | 685 | bucket.captureStackTrace(ret_addr, size_class, slot_index, .free); |
| ... | @@ -745,21 +761,15 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type { | ... | @@ -745,21 +761,15 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type { |
| 745 | const held = self.mutex.acquire(); | 761 | const held = self.mutex.acquire(); |
| 746 | defer held.release(); | 762 | defer held.release(); |
| 747 | | 763 | |
| | 764 | if (!self.isAllocationAllowed(len)) { |
| | 765 | return error.OutOfMemory; |
| | 766 | } |
| | 767 | |
| 748 | const new_aligned_size = math.max(len, ptr_align); | 768 | const new_aligned_size = math.max(len, ptr_align); |
| 749 | if (new_aligned_size > largest_bucket_object_size) { | 769 | if (new_aligned_size > largest_bucket_object_size) { |
| 750 | try self.large_allocations.ensureUnusedCapacity(self.backing_allocator, 1); | 770 | try self.large_allocations.ensureUnusedCapacity(self.backing_allocator, 1); |
| 751 | | | |
| 752 | const slice = try self.backing_allocator.allocFn(self.backing_allocator, len, ptr_align, len_align, ret_addr); | 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 | const gop = self.large_allocations.getOrPutAssumeCapacity(@ptrToInt(slice.ptr)); | 773 | const gop = self.large_allocations.getOrPutAssumeCapacity(@ptrToInt(slice.ptr)); |
| 764 | if (config.retain_metadata and !config.never_unmap) { | 774 | if (config.retain_metadata and !config.never_unmap) { |
| 765 | // Backing allocator may be reusing memory that we're retaining metadata for | 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,6 +778,8 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type { |
| 768 | assert(!gop.found_existing); // This would mean the kernel double-mapped pages. | 778 | assert(!gop.found_existing); // This would mean the kernel double-mapped pages. |
| 769 | } | 779 | } |
| 770 | gop.value_ptr.bytes = slice; | 780 | gop.value_ptr.bytes = slice; |
| | 781 | if (config.enable_memory_limit) |
| | 782 | gop.value_ptr.requested_size = len; |
| 771 | gop.value_ptr.captureStackTrace(ret_addr, .alloc); | 783 | gop.value_ptr.captureStackTrace(ret_addr, .alloc); |
| 772 | if (config.retain_metadata) { | 784 | if (config.retain_metadata) { |
| 773 | gop.value_ptr.freed = false; | 785 | gop.value_ptr.freed = false; |
| ... | @@ -782,10 +794,6 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type { | ... | @@ -782,10 +794,6 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type { |
| 782 | return slice; | 794 | return slice; |
| 783 | } | 795 | } |
| 784 | | 796 | |
| 785 | if (!self.isAllocationAllowed(len)) { | | |
| 786 | return error.OutOfMemory; | | |
| 787 | } | | |
| 788 | | | |
| 789 | const new_size_class = math.ceilPowerOfTwoAssert(usize, new_aligned_size); | 797 | const new_size_class = math.ceilPowerOfTwoAssert(usize, new_aligned_size); |
| 790 | const ptr = try self.allocSlot(new_size_class, ret_addr); | 798 | const ptr = try self.allocSlot(new_size_class, ret_addr); |
| 791 | if (config.verbose_log) { | 799 | if (config.verbose_log) { |
| ... | @@ -1183,3 +1191,14 @@ test "double frees" { | ... | @@ -1183,3 +1191,14 @@ test "double frees" { |
| 1183 | try std.testing.expect(gpa.large_allocations.contains(@ptrToInt(normal_large.ptr))); | 1191 | try std.testing.expect(gpa.large_allocations.contains(@ptrToInt(normal_large.ptr))); |
| 1184 | try std.testing.expect(!gpa.large_allocations.contains(@ptrToInt(large.ptr))); | 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 | } |