Several internal algorithms drove their main loop with a condition of the
form
while (__first != __last && PREDICATE_CALL(...))
where the predicate/comparator is the user-supplied functor, passed
through unwrapped. When that functor returns a class type and the
program declares a namespace-scope operator&&(bool, T) (or operator!)
reachable by ADL, overload resolution selects the user operator&& for
the loop condition. An overloaded operator&& does not short-circuit, so
the right-hand operand -- which dereferences *__first -- is evaluated
even when __first == __last. For an empty range (or once the scan
reaches the end) this dereferences the past-the-end iterator;
AddressSanitizer reports it as a stack-buffer-overflow read for e.g. an
empty std::list, and UBSan as a null/ invalid reference binding.
Force the predicate result to bool in these loop conditions (the same
idiom already used elsewhere, e.g. in lower_bound and search_n), so the
'&&' is the built-in, short-circuiting operator and the iterator is
never dereferenced once __first == __last. The ranges:: versions are
unaffected because their comparator/predicate wrappers already return
bool.
PR libstdc++/125981
libstdc++-v3/ChangeLog:
* include/bits/stl_algobase.h (__find_if): Convert 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 | 14 +++-
libstdc++-v3/include/bits/stl_heap.h | 5 +-
.../find_if/overloaded_logical_ops.cc | 76 ++++++++++++++++++
.../mismatch/overloaded_logical_ops.cc | 80 +++++++++++++++++++
4 files changed, 171 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..a4090cb70 100644
--- a/libstdc++-v3/include/bits/stl_algobase.h
+++ b/libstdc++-v3/include/bits/stl_algobase.h
@@ -1930,7 +1930,10 @@ _GLIBCXX_BEGIN_NAMESPACE_ALGO
__mismatch(_InputIterator1 __first1, _InputIterator1 __last1,
_InputIterator2 __first2, _BinaryPredicate __binary_pred)
{
- while (__first1 != __last1 && __binary_pred(*__first1, *__first2))
+ // The bool conversion prevents an ADL-reachable operator&& from making
+ // '&&' non-short-circuiting and dereferencing past-the-end iterators
+ // (see std::__find_if).
+ while (__first1 != __last1 && bool(__binary_pred(*__first1, *__first2)))
{
++__first1;
++__first2;
@@ -2010,8 +2013,9 @@ _GLIBCXX_BEGIN_NAMESPACE_ALGO
_InputIterator2 __first2, _InputIterator2 __last2,
_BinaryPredicate __binary_pred)
{
+ // See the __mismatch overload above for the bool conversion.
while (__first1 != __last1 && __first2 != __last2
- && __binary_pred(*__first1, *__first2))
+ && bool(__binary_pred(*__first1, *__first2)))
{
++__first1;
++__first2;
@@ -2097,7 +2101,11 @@ _GLIBCXX_END_NAMESPACE_ALGO
__find_if(_Iterator __first, _Iterator __last, _Predicate __pred)
{
#pragma GCC unroll 4
- while (__first != __last && !__pred(*__first))
+ // Convert the predicate result to bool: if its type has an
+ // ADL-reachable operator&& or operator!, those overloads are not
+ // short-circuiting and would evaluate (and so dereference) the
+ // past-the-end iterator even when __first == __last.
+ 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..bb6a3d159 100644
--- a/libstdc++-v3/include/bits/stl_heap.h
+++ b/libstdc++-v3/include/bits/stl_heap.h
@@ -143,7 +143,10 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
_Compare& __comp)
{
_Distance __parent = (__holeIndex - 1) / 2;
- while (__holeIndex > __topIndex && __comp(*(__first + __parent),
__value))
+ // The bool conversion prevents an ADL-reachable operator&& from making
+ // '&&' non-short-circuiting (see std::__find_if).
+ 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..bb7c197f1
--- /dev/null
+++ b/libstdc++-v3/testsuite/25_algorithms/find_if/overloaded_logical_ops.cc
@@ -0,0 +1,76 @@
+// Copyright (C) 2026 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library. This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3. If not see
+// <http://www.gnu.org/licenses/>.
+
+// 25.5.5 [alg.find] find_if
+
+// The internal std::__find_if must not dereference the past-the-end
+// iterator. It used to combine the loop guard with the predicate as
+// 'first != last && !pred(*first)'. When the predicate returns a class
+// type with a user-provided operator! and there is a namespace-scope
+// operator&&(bool, T) findable by ADL, that operator&& is selected and is
+// *not* short-circuiting, so '!pred(*first)' (and hence '*first') was
+// evaluated even when first == last.
+
+#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(); }
+};
+
+// Non-short-circuiting operator&& findable by ADL on Logic.
+bool operator&&(bool, Logic) { return false; }
+
+struct Pred
+{
+ Logic operator()(Value v) const { return v > v; }
+};
+
+void
+test01()
+{
+ Value arr[1] = { };
+
+ // Empty range: the predicate must never be applied and *first (the
+ // past-the-end iterator) must never be dereferenced.
+ test_container<Value, forward_iterator_wrapper> empty(arr, arr);
+ VERIFY( std::find_if(empty.begin(), empty.end(), Pred()).ptr == arr );
+
+ // No match: scanning reaches last; *last must not be dereferenced.
+ 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..55de86c79
--- /dev/null
+++ b/libstdc++-v3/testsuite/25_algorithms/mismatch/overloaded_logical_ops.cc
@@ -0,0 +1,80 @@
+// Copyright (C) 2026 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library. This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3. If not see
+// <http://www.gnu.org/licenses/>.
+
+// 25.5.9 [mismatch]
+
+// std::__mismatch must not dereference the past-the-end iterator. It used
+// to combine the loop guard with the predicate as
+// 'first1 != last1 && pred(*first1, *first2)'. When the predicate returns
+// a class type and a namespace-scope operator&&(bool, T) is findable by
+// ADL, that operator&& is selected and is *not* short-circuiting, so
+// 'pred(*first1, *first2)' (and hence the dereferences) was evaluated even
+// when first1 == last1.
+
+#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 { };
+
+// Non-short-circuiting operator&& findable by ADL on Logic.
+bool operator&&(bool, Logic) { return false; }
+
+struct Eq
+{
+ Logic operator()(Value, Value) const { return Logic(); }
+};
+
+void
+test01()
+{
+ Value arr[1] = { };
+
+ // Empty first range: the predicate must never be applied and the
+ // past-the-end iterators must never be dereferenced.
+ 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
+ // Four-argument form: stop when either range is exhausted, without
+ // dereferencing the past-the-end iterator of the shorter range.
+ 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