Thank you! I sent the updated patch with the code comments and copyrights removed. In both tests I added a note that the tested case is UB handled as QoI.
пт, 26 июн. 2026 г. в 22:47, Yan Churkin <[email protected]>: > std::__find_if, std::__mismatch and std::__push_heap drove their loops > with a condition of the form > > while (__first != __last && PREDICATE_CALL(...)) > > If the predicate/comparator result type has an ADL-reachable > operator&&(bool, T), overload resolution selects that user-defined > operator&& for the loop condition. It does not short-circuit, so the > operand that dereferences *__first is evaluated even when > __first == __last, dereferencing the past-the-end iterator. > > Such a result type does not model boolean-testable, so this is undefined > behaviour and not a conformance issue. Handle it anyway as a QoI > extension, consistent with std::equal, std::binary_search and > std::__partition, by forcing the predicate result to bool so the > built-in && is used. This has no effect on well-behaved predicates; the > ranges:: versions are unaffected because their wrappers already return > bool. > > PR libstdc++/125981 > > libstdc++-v3/ChangeLog: > > * include/bits/stl_algobase.h (__find_if): Force the predicate > result to bool so an ADL-found, non-short-circuiting operator&& / > operator! cannot dereference the past-the-end iterator. > (__mismatch): Likewise for both overloads. > * include/bits/stl_heap.h (__push_heap): Likewise for the > comparator result. > * testsuite/25_algorithms/find_if/overloaded_logical_ops.cc: New > test. > * testsuite/25_algorithms/mismatch/overloaded_logical_ops.cc: New > test. > > Signed-off-by: Yan Churkin <[email protected]> > --- > libstdc++-v3/include/bits/stl_algobase.h | 6 +-- > libstdc++-v3/include/bits/stl_heap.h | 3 +- > .../find_if/overloaded_logical_ops.cc | 50 +++++++++++++++++ > .../mismatch/overloaded_logical_ops.cc | 53 +++++++++++++++++++ > 4 files changed, 108 insertions(+), 4 deletions(-) > create mode 100644 > libstdc++-v3/testsuite/25_algorithms/find_if/overloaded_logical_ops.cc > create mode 100644 > libstdc++-v3/testsuite/25_algorithms/mismatch/overloaded_logical_ops.cc > > diff --git a/libstdc++-v3/include/bits/stl_algobase.h > b/libstdc++-v3/include/bits/stl_algobase.h > index 1350736a8..33b117781 100644 > --- a/libstdc++-v3/include/bits/stl_algobase.h > +++ b/libstdc++-v3/include/bits/stl_algobase.h > @@ -1930,7 +1930,7 @@ _GLIBCXX_BEGIN_NAMESPACE_ALGO > __mismatch(_InputIterator1 __first1, _InputIterator1 __last1, > _InputIterator2 __first2, _BinaryPredicate __binary_pred) > { > - while (__first1 != __last1 && __binary_pred(*__first1, *__first2)) > + while (__first1 != __last1 && bool(__binary_pred(*__first1, > *__first2))) > { > ++__first1; > ++__first2; > @@ -2011,7 +2011,7 @@ _GLIBCXX_BEGIN_NAMESPACE_ALGO > _BinaryPredicate __binary_pred) > { > while (__first1 != __last1 && __first2 != __last2 > - && __binary_pred(*__first1, *__first2)) > + && bool(__binary_pred(*__first1, *__first2))) > { > ++__first1; > ++__first2; > @@ -2097,7 +2097,7 @@ _GLIBCXX_END_NAMESPACE_ALGO > __find_if(_Iterator __first, _Iterator __last, _Predicate __pred) > { > #pragma GCC unroll 4 > - while (__first != __last && !__pred(*__first)) > + while (__first != __last && !bool(__pred(*__first))) > ++__first; > return __first; > } > diff --git a/libstdc++-v3/include/bits/stl_heap.h > b/libstdc++-v3/include/bits/stl_heap.h > index 8c5c5df52..3c26e6a53 100644 > --- a/libstdc++-v3/include/bits/stl_heap.h > +++ b/libstdc++-v3/include/bits/stl_heap.h > @@ -143,7 +143,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > _Compare& __comp) > { > _Distance __parent = (__holeIndex - 1) / 2; > - while (__holeIndex > __topIndex && __comp(*(__first + __parent), > __value)) > + while (__holeIndex > __topIndex > + && bool(__comp(*(__first + __parent), __value))) > { > *(__first + __holeIndex) = _GLIBCXX_MOVE(*(__first + __parent)); > __holeIndex = __parent; > diff --git > a/libstdc++-v3/testsuite/25_algorithms/find_if/overloaded_logical_ops.cc > b/libstdc++-v3/testsuite/25_algorithms/find_if/overloaded_logical_ops.cc > new file mode 100644 > index 000000000..a26f4d557 > --- /dev/null > +++ > b/libstdc++-v3/testsuite/25_algorithms/find_if/overloaded_logical_ops.cc > @@ -0,0 +1,50 @@ > +// The predicate's result type does not model boolean-testable (it has an > +// ADL-reachable operator&& and operator!), so this is undefined > behaviour. > +// libstdc++ supports it as a QoI extension: std::find_if does not > evaluate > +// the predicate on, or dereference, the past-the-end iterator. > + > +#include <algorithm> > +#include <testsuite_hooks.h> > +#include <testsuite_iterators.h> > + > +using __gnu_test::test_container; > +using __gnu_test::forward_iterator_wrapper; > + > +int truth = 0; > + > +struct Logic > +{ > + Logic operator!() const { return Logic(); } > + operator bool() const { return truth != 0; } > +}; > + > +struct Value > +{ > + Logic operator>(Value) const { return Logic(); } > +}; > + > +bool operator&&(bool, Logic) { return false; } > + > +struct Pred > +{ > + Logic operator()(Value v) const { return v > v; } > +}; > + > +void > +test01() > +{ > + Value arr[1] = { }; > + > + test_container<Value, forward_iterator_wrapper> empty(arr, arr); > + VERIFY( std::find_if(empty.begin(), empty.end(), Pred()).ptr == arr ); > + > + test_container<Value, forward_iterator_wrapper> con(arr, arr + 1); > + VERIFY( std::find_if(con.begin(), con.end(), Pred()).ptr == arr + 1 ); > +} > + > +int > +main() > +{ > + test01(); > + return 0; > +} > diff --git > a/libstdc++-v3/testsuite/25_algorithms/mismatch/overloaded_logical_ops.cc > b/libstdc++-v3/testsuite/25_algorithms/mismatch/overloaded_logical_ops.cc > new file mode 100644 > index 000000000..dd52d070b > --- /dev/null > +++ > b/libstdc++-v3/testsuite/25_algorithms/mismatch/overloaded_logical_ops.cc > @@ -0,0 +1,53 @@ > +// The predicate's result type does not model boolean-testable (it has an > +// ADL-reachable operator&& and operator!), so this is undefined > behaviour. > +// libstdc++ supports it as a QoI extension: std::mismatch does not > evaluate > +// the predicate on, or dereference, the past-the-end iterator. > + > +#include <algorithm> > +#include <testsuite_hooks.h> > +#include <testsuite_iterators.h> > + > +using __gnu_test::test_container; > +using __gnu_test::forward_iterator_wrapper; > + > +int truth = 0; > + > +struct Logic > +{ > + Logic operator!() const { return Logic(); } > + operator bool() const { return truth != 0; } > +}; > + > +struct Value { }; > + > +bool operator&&(bool, Logic) { return false; } > + > +struct Eq > +{ > + Logic operator()(Value, Value) const { return Logic(); } > +}; > + > +void > +test01() > +{ > + Value arr[1] = { }; > + > + test_container<Value, forward_iterator_wrapper> empty(arr, arr); > + test_container<Value, forward_iterator_wrapper> other(arr, arr + 1); > + auto r = std::mismatch(empty.begin(), empty.end(), other.begin(), Eq()); > + VERIFY( r.first.ptr == arr ); > + > +#if __cplusplus >= 201402L > + test_container<Value, forward_iterator_wrapper> e2(arr, arr); > + test_container<Value, forward_iterator_wrapper> o2(arr, arr + 1); > + auto r2 = std::mismatch(e2.begin(), e2.end(), o2.begin(), o2.end(), > Eq()); > + VERIFY( r2.first.ptr == arr ); > +#endif > +} > + > +int > +main() > +{ > + test01(); > + return 0; > +} > -- > 2.43.0 > >
