-
Notifications
You must be signed in to change notification settings - Fork 13.4k
[libc++] Use correct size for deallocation of arrays in shared_ptr #68233
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
Conversation
Fixes llvm#68051. Current implementation passes the number of `_AlignedStorage` objects when it calls to `allocate` and the number of **bytes** on deallocate. This only applies to allocations that allocate control block and the storage together, i.e. `make_shared` and `allocate_shared`. Found by ASan.
@llvm/pr-subscribers-libcxx ChangesFixes #68051. Current implementation passes the number of Found by ASan. Full diff: https://github.com/llvm/llvm-project/pull/68233.diff 2 Files Affected:
diff --git a/libcxx/include/__memory/shared_ptr.h b/libcxx/include/__memory/shared_ptr.h
index 33a1b95a31ddbd5..d9ddb8a17be273f 100644
--- a/libcxx/include/__memory/shared_ptr.h
+++ b/libcxx/include/__memory/shared_ptr.h
@@ -1137,7 +1137,8 @@ struct __unbounded_array_control_block<_Tp[], _Alloc> : __shared_weak_count
__alloc_.~_Alloc();
size_t __size = __unbounded_array_control_block::__bytes_for(__count_);
_AlignedStorage* __storage = reinterpret_cast<_AlignedStorage*>(this);
- allocator_traits<_StorageAlloc>::deallocate(__tmp, _PointerTraits::pointer_to(*__storage), __size);
+ allocator_traits<_StorageAlloc>::deallocate(
+ __tmp, _PointerTraits::pointer_to(*__storage), __size / sizeof(_AlignedStorage));
}
_LIBCPP_NO_UNIQUE_ADDRESS _Alloc __alloc_;
@@ -1220,7 +1221,7 @@ struct __bounded_array_control_block<_Tp[_Count], _Alloc>
_ControlBlockAlloc __tmp(__alloc_);
__alloc_.~_Alloc();
- allocator_traits<_ControlBlockAlloc>::deallocate(__tmp, _PointerTraits::pointer_to(*this), sizeof(*this));
+ allocator_traits<_ControlBlockAlloc>::deallocate(__tmp, _PointerTraits::pointer_to(*this), 1);
}
_LIBCPP_NO_UNIQUE_ADDRESS _Alloc __alloc_;
diff --git a/libcxx/test/libcxx/memory/shared_ptr_array.pass.cpp b/libcxx/test/libcxx/memory/shared_ptr_array.pass.cpp
new file mode 100644
index 000000000000000..f885c7344e41b9b
--- /dev/null
+++ b/libcxx/test/libcxx/memory/shared_ptr_array.pass.cpp
@@ -0,0 +1,25 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// REQUIRES: -fsized-deallocation
+// ADDITIONAL_COMPILE_FLAGS: -fsized-deallocation
+
+// This test will fail with ASan if the implementation passes different sizes
+// to corresponding allocation and deallocation functions.
+
+#include <memory>
+
+int main() {
+ std::allocate_shared<int64_t[]>(std::allocator<int64_t>{}, 10);
+ std::make_shared<int64_t[]>(10);
+
+ std::allocate_shared<int64_t[10]>(std::allocator<int64_t>{});
+ std::make_shared<int64_t[10]>();
+}
|
✅ With the latest revision this PR passed the C/C++ code formatter. |
…_ptr Reformat the test file.
I wonder why ASAN wasn't flagging this externally. Any clues? |
Could you fix the missing include in the failing test, then I think this is good to go! Though it would be nice if we could figure out why ASAN wasn't firing here. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM after getting the test passing (not your failure, but lets get the CI green).
@@ -1220,7 +1221,7 @@ struct __bounded_array_control_block<_Tp[_Count], _Alloc> | |||
|
|||
_ControlBlockAlloc __tmp(__alloc_); | |||
__alloc_.~_Alloc(); | |||
allocator_traits<_ControlBlockAlloc>::deallocate(__tmp, _PointerTraits::pointer_to(*this), sizeof(*this)); | |||
allocator_traits<_ControlBlockAlloc>::deallocate(__tmp, _PointerTraits::pointer_to(*this), 1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oof, that's a nasty bug. Thanks for the fix.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the fix! This is indeed a nasty bug, a bit surprising that it went unnoticed for so long.
@@ -1137,7 +1137,8 @@ struct __unbounded_array_control_block<_Tp[], _Alloc> : __shared_weak_count | |||
__alloc_.~_Alloc(); | |||
size_t __size = __unbounded_array_control_block::__bytes_for(__count_); | |||
_AlignedStorage* __storage = reinterpret_cast<_AlignedStorage*>(this); | |||
allocator_traits<_StorageAlloc>::deallocate(__tmp, _PointerTraits::pointer_to(*__storage), __size); | |||
allocator_traits<_StorageAlloc>::deallocate( | |||
__tmp, _PointerTraits::pointer_to(*__storage), __size / sizeof(_AlignedStorage)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this not always __count_ + 1
? i.e. the number of elements in the array + the size of the control block at the beginning? I guess I'm kinda confused with my own code now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It actually seems to be more complicated.
The aligned storage has the size equal to the alignment of the control block (which is 8 bytes on my machine), so, e.g., we allocate and deallocate 5 elements of AlignedStorage
for int[]
when __count_
is 0
(as the control block is 40 bytes on my machine).
In any case, I believe that reproducing exactly what gets passed to allocate
is the right call here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If alignof(size_t) < sizeof(size_t)
, it will not be __count_ + 1
.
…_ptr - use full signature in main - return 0
…_ptr use int instead of int64_t to avoid the need for an include
I think it's because it requires a rather rare combination of flags. First, we need to enable [1]: it is surprising that |
I'm going to merge the change in as all comments are addressed and this already has LGTM from @EricWF. I hope I don't break any policies or expectations here, but please let me know if that's the case. |
Any objection to backporting this to 17.x branch? |
No, we should totally do that. Do you want to create the cherry-pick issue? |
Sorry, this slipped off my radar. I've filed #72255. |
…68233) Fixes #68051. Current implementation passes the number of `_AlignedStorage` objects when it calls to `allocate` and the number of **bytes** on `deallocate`. This only applies to allocations that allocate control block and the storage together, i.e. `make_shared` and `allocate_shared`. Found by ASan under Clang combined with `-fsized-deallocation`. (cherry picked from commit f722db0)
Fixes #68051.
Current implementation passes the number of
_AlignedStorage
objects when it calls toallocate
and the number of bytes on deallocate. This only applies to allocations that allocate control block and the storage together, i.e.make_shared
andallocate_shared
.Found by ASan.