diff --git a/libcxx/docs/ReleaseNotes/24.rst b/libcxx/docs/ReleaseNotes/24.rst index 70c704ff9d326..612fb0d03d0b2 100644 --- a/libcxx/docs/ReleaseNotes/24.rst +++ b/libcxx/docs/ReleaseNotes/24.rst @@ -57,6 +57,10 @@ Potentially breaking changes but causes programs which rely on these includes to not compile anymore. The ``_LIBCPP_KEEP_TRANSITIVE_INCLUDES_LLVM23`` macro that was provided in LLVM 23 to ease the transition has been removed in this release. +- Libc++ now diagnoses when a method of ``std::vector`` is used and ``T`` is an incomplete type. The Standard requires + ``T`` to be complete before any member function is used, however this worked unreliably for a few member functions. In + LLVM 24, this is diagnosed uniformly. + Announcements About Future Releases ----------------------------------- diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt index b40f586161e62..3f7be893a4450 100644 --- a/libcxx/include/CMakeLists.txt +++ b/libcxx/include/CMakeLists.txt @@ -953,6 +953,7 @@ set(files __utility/priority_tag.h __utility/private_constructor_tag.h __utility/rel_ops.h + __utility/require_complete.h __utility/scope_guard.h __utility/small_buffer.h __utility/swap.h diff --git a/libcxx/include/__utility/require_complete.h b/libcxx/include/__utility/require_complete.h new file mode 100644 index 0000000000000..5cb9ea74b500b --- /dev/null +++ b/libcxx/include/__utility/require_complete.h @@ -0,0 +1,42 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP___UTILITY_REQUIRE_COMPLETE_H +#define _LIBCPP___UTILITY_REQUIRE_COMPLETE_H + +#include <__config> +#include <__cstddef/size_t.h> + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +# pragma GCC system_header +#endif + +_LIBCPP_PUSH_MACROS +#include <__undef_macros> + +_LIBCPP_BEGIN_NAMESPACE_STD + +template +_LIBCPP_CONSTEXPR void __require_complete_impl(int) {} + +template +_LIBCPP_CONSTEXPR void __require_complete_impl(long) { + static_assert(_False, "Type is required to be complete"); +} + +// Produce a compiler error if the given type is not complete. +template +_LIBCPP_CONSTEXPR void __require_complete() { + std::__require_complete_impl<_Tp>(0); +} + +_LIBCPP_END_NAMESPACE_STD + +_LIBCPP_POP_MACROS + +#endif // _LIBCPP___UTILITY_REQUIRE_COMPLETE_H diff --git a/libcxx/include/__vector/vector.h b/libcxx/include/__vector/vector.h index 5e9fa4a7d0030..6749ae1aacc03 100644 --- a/libcxx/include/__vector/vector.h +++ b/libcxx/include/__vector/vector.h @@ -65,6 +65,7 @@ #include <__utility/is_pointer_in_range.h> #include <__utility/move.h> #include <__utility/pair.h> +#include <__utility/require_complete.h> #include <__utility/swap.h> #include #include @@ -348,43 +349,55 @@ class vector { // Iterators // [[__nodiscard__]] _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI iterator begin() _NOEXCEPT { + std::__require_complete<_Tp>(); return __make_iter(__add_alignment_assumption(this->__layout_.__begin_ptr())); } [[__nodiscard__]] _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI const_iterator begin() const _NOEXCEPT { + std::__require_complete<_Tp>(); return __make_iter(__add_alignment_assumption(this->__layout_.__begin_ptr())); } [[__nodiscard__]] _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI iterator end() _NOEXCEPT { + std::__require_complete<_Tp>(); return __make_iter(__add_alignment_assumption(__layout_.__end_ptr())); } [[__nodiscard__]] _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI const_iterator end() const _NOEXCEPT { + std::__require_complete<_Tp>(); return __make_iter(__add_alignment_assumption(__layout_.__end_ptr())); } [[__nodiscard__]] _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI reverse_iterator rbegin() _NOEXCEPT { + std::__require_complete<_Tp>(); return reverse_iterator(end()); } [[__nodiscard__]] _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI const_reverse_iterator rbegin() const _NOEXCEPT { + std::__require_complete<_Tp>(); return const_reverse_iterator(end()); } [[__nodiscard__]] _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI reverse_iterator rend() _NOEXCEPT { + std::__require_complete<_Tp>(); return reverse_iterator(begin()); } [[__nodiscard__]] _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI const_reverse_iterator rend() const _NOEXCEPT { + std::__require_complete<_Tp>(); return const_reverse_iterator(begin()); } [[__nodiscard__]] _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI const_iterator cbegin() const _NOEXCEPT { + std::__require_complete<_Tp>(); return begin(); } [[__nodiscard__]] _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI const_iterator cend() const _NOEXCEPT { + std::__require_complete<_Tp>(); return end(); } [[__nodiscard__]] _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI const_reverse_iterator crbegin() const _NOEXCEPT { + std::__require_complete<_Tp>(); return rbegin(); } [[__nodiscard__]] _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI const_reverse_iterator crend() const _NOEXCEPT { + std::__require_complete<_Tp>(); return rend(); } @@ -392,16 +405,20 @@ class vector { // [vector.capacity], capacity // [[__nodiscard__]] _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI size_type size() const _NOEXCEPT { + std::__require_complete<_Tp>(); return __layout_.__size(); } [[__nodiscard__]] _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI size_type capacity() const _NOEXCEPT { + std::__require_complete<_Tp>(); return __layout_.__capacity(); } [[__nodiscard__]] _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI bool empty() const _NOEXCEPT { + std::__require_complete<_Tp>(); return __layout_.__empty(); } [[__nodiscard__]] _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI size_type max_size() const _NOEXCEPT { + std::__require_complete<_Tp>(); return std::min(__alloc_traits::max_size(__layout_.__alloc()), numeric_limits::max()); } _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void reserve(size_type __n); diff --git a/libcxx/include/module.modulemap.in b/libcxx/include/module.modulemap.in index 39b4e0bb986c6..3a2615da37505 100644 --- a/libcxx/include/module.modulemap.in +++ b/libcxx/include/module.modulemap.in @@ -2229,6 +2229,7 @@ module std { module priority_tag { header "__utility/priority_tag.h" } module private_constructor_tag { header "__utility/private_constructor_tag.h" } module rel_ops { header "__utility/rel_ops.h" } + module require_complete { header "__utility/require_complete.h" } module scope_guard { header "__utility/scope_guard.h" } module small_buffer { header "__utility/small_buffer.h" } module swap { header "__utility/swap.h" } diff --git a/libcxx/test/libcxx/containers/sequences/vector/incomplete_type.compile.pass.cpp b/libcxx/test/libcxx/containers/sequences/vector/incomplete_type.compile.pass.cpp deleted file mode 100644 index 029d29eb437f8..0000000000000 --- a/libcxx/test/libcxx/containers/sequences/vector/incomplete_type.compile.pass.cpp +++ /dev/null @@ -1,27 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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 -// -//===----------------------------------------------------------------------===// - -// - -// This test pins down the current libc++ behavior that vector::empty() can be -// called even when T is an incomplete type. The standard does not require this: -// [vector.overview] only guarantees that an incomplete type may be used to -// instantiate vector, and requires the type to be complete before any method is -// called. -// -// However, libc++ made that work previously, and this test pins down that behavior -// to avoid breaking it unintentionally. Note that this is not a guarantee to users -// that we will support this in the future: this merely guards against changing this -// behavior unknowingly. - -#include - -struct Incomplete; - -bool call_empty(std::vector& v) { return v.empty(); } -bool call_empty_const(const std::vector& v) { return v.empty(); } diff --git a/libcxx/test/libcxx/containers/sequences/vector/incomplete_type.verify.cpp b/libcxx/test/libcxx/containers/sequences/vector/incomplete_type.verify.cpp new file mode 100644 index 0000000000000..caabd64c3b279 --- /dev/null +++ b/libcxx/test/libcxx/containers/sequences/vector/incomplete_type.verify.cpp @@ -0,0 +1,30 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +// + +// This test ensures that we diagnose when an incomplete type is used in one of +// vector's methods. The Standard requires that to be the case, and we want to +// uniformly produce an error for that. Note that producing the same diagnostic +// in all cases is difficult, but we at least want to fail to fight back against +// Hyrum's law. + +#include + +struct Incomplete; + +void f(std::vector& v) { + (void)v.empty(); // expected-error@*:* {{}} + (void)v.size(); // expected-error@*:* {{}} + (void)v.begin(); // expected-error@*:* {{}} + (void)v.end(); // expected-error@*:* {{}} + (void)v.cbegin(); // expected-error@*:* {{}} + (void)v.cend(); // expected-error@*:* {{}} + + // etc for other APIs +}