diff --git a/libcxx/docs/ReleaseNotes/20.rst b/libcxx/docs/ReleaseNotes/20.rst index 9039c6f046445..7375c378cd31b 100644 --- a/libcxx/docs/ReleaseNotes/20.rst +++ b/libcxx/docs/ReleaseNotes/20.rst @@ -69,6 +69,10 @@ Improvements and New Features - The ``_LIBCPP_ABI_BOUNDED_ITERATORS_IN_STD_ARRAY`` ABI configuration was added, which allows storing valid bounds in ``std::array::iterator`` and detecting OOB accesses when the appropriate hardening mode is enabled. +- ``std::set_intersection`` and ``std::ranges::set_intersection`` will now validate that inputs are sorted when compiled + in :ref:`debug hardening mode mode `. Results from these functions were changed in LLVM 19 + with this class of invalid inputs, the new validation helps identify the source of undefined behavior. + Deprecations and Removals ------------------------- diff --git a/libcxx/include/__algorithm/is_sorted_until.h b/libcxx/include/__algorithm/is_sorted_until.h index b64fb65e84e4e..8d9ec9a98739e 100644 --- a/libcxx/include/__algorithm/is_sorted_until.h +++ b/libcxx/include/__algorithm/is_sorted_until.h @@ -20,18 +20,18 @@ _LIBCPP_BEGIN_NAMESPACE_STD -template +template _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _ForwardIterator -__is_sorted_until(_ForwardIterator __first, _ForwardIterator __last, _Compare __comp) { +__is_sorted_until(_ForwardIterator __first, _Sent __last, _Compare&& __comp) { if (__first != __last) { - _ForwardIterator __i = __first; - while (++__i != __last) { - if (__comp(*__i, *__first)) - return __i; - __first = __i; + _ForwardIterator __prev = __first; + while (++__first != __last) { + if (__comp(*__first, *__prev)) + return __first; + __prev = __first; } } - return __last; + return __first; } template diff --git a/libcxx/include/__algorithm/set_intersection.h b/libcxx/include/__algorithm/set_intersection.h index 6246e24b9ca4e..eecb2c0917177 100644 --- a/libcxx/include/__algorithm/set_intersection.h +++ b/libcxx/include/__algorithm/set_intersection.h @@ -11,12 +11,15 @@ #include <__algorithm/comp.h> #include <__algorithm/comp_ref_type.h> +#include <__algorithm/is_sorted_until.h> #include <__algorithm/iterator_operations.h> #include <__algorithm/lower_bound.h> +#include <__assert> #include <__config> #include <__functional/identity.h> #include <__iterator/iterator_traits.h> #include <__iterator/next.h> +#include <__type_traits/is_constant_evaluated.h> #include <__type_traits/is_same.h> #include <__utility/exchange.h> #include <__utility/forward.h> @@ -96,6 +99,12 @@ __set_intersection( _Compare&& __comp, std::forward_iterator_tag, std::forward_iterator_tag) { +#if _LIBCPP_HARDENING_MODE == _LIBCPP_HARDENING_MODE_DEBUG + _LIBCPP_ASSERT_SEMANTIC_REQUIREMENT( + std::__is_sorted_until(__first1, __last1, __comp) == __last1, "set_intersection: input range 1 must be sorted"); + _LIBCPP_ASSERT_SEMANTIC_REQUIREMENT( + std::__is_sorted_until(__first2, __last2, __comp) == __last2, "set_intersection: input range 2 must be sorted"); +#endif _LIBCPP_CONSTEXPR std::__identity __proj; bool __prev_may_be_equal = false; diff --git a/libcxx/test/std/algorithms/alg.sorting/alg.set.operations/set.intersection/set_intersection_complexity.pass.cpp b/libcxx/test/std/algorithms/alg.sorting/alg.set.operations/set.intersection/set_intersection_complexity.pass.cpp index ddf4087ddd6cd..83d78c6163fee 100644 --- a/libcxx/test/std/algorithms/alg.sorting/alg.set.operations/set.intersection/set_intersection_complexity.pass.cpp +++ b/libcxx/test/std/algorithms/alg.sorting/alg.set.operations/set.intersection/set_intersection_complexity.pass.cpp @@ -43,16 +43,14 @@ #include "test_iterators.h" -namespace { - -// __debug_less will perform an additional comparison in an assertion -static constexpr unsigned std_less_comparison_count_multiplier() noexcept { -#if _LIBCPP_HARDENING_MODE == _LIBCPP_HARDENING_MODE_DEBUG - return 2; +// We don't check number of operations in Debug mode because they are not stable enough due to additional validations. +#if defined(_LIBCPP_HARDENING_MODE_DEBUG) && _LIBCPP_HARDENING_MODE_DEBUG +# define ASSERT_COMPLEXITY(expression) (void)(expression) #else - return 1; +# define ASSERT_COMPLEXITY(expression) assert(expression) #endif -} + +namespace { struct [[nodiscard]] OperationCounts { std::size_t comparisons{}; @@ -60,16 +58,16 @@ struct [[nodiscard]] OperationCounts { std::size_t proj{}; IteratorOpCounts iterops; - [[nodiscard]] constexpr bool isNotBetterThan(const PerInput& other) { + [[nodiscard]] constexpr bool isNotBetterThan(const PerInput& other) const noexcept { return proj >= other.proj && iterops.increments + iterops.decrements + iterops.zero_moves >= other.iterops.increments + other.iterops.decrements + other.iterops.zero_moves; } }; std::array in; - [[nodiscard]] constexpr bool isNotBetterThan(const OperationCounts& expect) { - return std_less_comparison_count_multiplier() * comparisons >= expect.comparisons && - in[0].isNotBetterThan(expect.in[0]) && in[1].isNotBetterThan(expect.in[1]); + [[nodiscard]] constexpr bool isNotBetterThan(const OperationCounts& expect) const noexcept { + return comparisons >= expect.comparisons && in[0].isNotBetterThan(expect.in[0]) && + in[1].isNotBetterThan(expect.in[1]); } }; @@ -80,16 +78,17 @@ struct counted_set_intersection_result { constexpr counted_set_intersection_result() = default; - constexpr explicit counted_set_intersection_result(std::array&& contents) : result{contents} {} + constexpr explicit counted_set_intersection_result(std::array&& contents) noexcept + : result{contents} {} - constexpr void assertNotBetterThan(const counted_set_intersection_result& other) { + constexpr void assertNotBetterThan(const counted_set_intersection_result& other) const noexcept { assert(result == other.result); - assert(opcounts.isNotBetterThan(other.opcounts)); + ASSERT_COMPLEXITY(opcounts.isNotBetterThan(other.opcounts)); } }; template -counted_set_intersection_result(std::array) -> counted_set_intersection_result; +counted_set_intersection_result(std::array) noexcept -> counted_set_intersection_result; template