[gcc r17-2660] libstdc++: Don't dereference past-the-end iterator with overloaded operator&& [PR125981]

Jonathan Wakely via Gcc-cvs <[email protected]>
Newsgroups gmane.comp.gcc.cvs
Message-ID <20260723104933.3BDD24BA7990__41318.6943552986$1784803809$gmane$org@sourceware.org>
https://gcc.gnu.org/g:a4ceb702480b9f46dd9b862c81ad7505ca344286

commit r17-2660-ga4ceb702480b9f46dd9b862c81ad7505ca344286
Author: Yan Churkin <[email protected]>
Date:   Fri Jun 26 22:45:31 2026 +0300

    libstdc++: Don't dereference past-the-end iterator with overloaded operator&& [PR125981]
    
    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 that non-boolean_testable predicates cannot
            cause a past-the-end iterator to be dereferenced.
            (__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]>

Diff:
---
 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             | 54 ++++++++++++++++++++++
 4 files changed, 109 insertions(+), 4 deletions(-)

diff --git a/libstdc++-v3/include/bits/stl_algobase.h b/libstdc++-v3/include/bits/stl_algobase.h
index 1350736a8d8e..33b1177810a9 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 8c5c5df5266d..3c26e6a5332d 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 000000000000..a26f4d557d8c
--- /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 000000000000..02bc86bfca9a
--- /dev/null
+++ b/libstdc++-v3/testsuite/25_algorithms/mismatch/overloaded_logical_ops.cc
@@ -0,0 +1,54 @@
+// 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);
+  forward_iterator_wrapper<Value> r
+    = std::mismatch(empty.begin(), empty.end(), other.begin(), Eq()).first;
+  VERIFY( r.ptr == arr );
+
+#ifdef __cpp_lib_robust_nonmodifying_seq_ops  // C++ >= 14
+  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;
+}
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.