[gcc r13-6084] libstdc++: Implement P2255R2 dangling checks for std::pair

Jonathan Wakely redi@gcc.gnu.org
Thu Feb 16 14:38:49 GMT 2023


https://gcc.gnu.org/g:916ce577ad109be69a3100fc79b3933d741eb990

commit r13-6084-g916ce577ad109be69a3100fc79b3933d741eb990
Author: Jonathan Wakely <jwakely@redhat.com>
Date:   Wed Feb 8 12:58:45 2023 +0000

    libstdc++: Implement P2255R2 dangling checks for std::pair
    
    This uses the new __reference_constructs_from_temporary built-in to
    identify when a std::pair constructor will bind a reference to a
    temporary that goes out of scope at the end of the constructor.  For
    example, std::pair<const long&, int> p(1, 2); will call the pair<const
    long&, int>::pair(U1&&, U2&&) constructor with U1=int and U2=int. In the
    constructor body a temporary long will be created and the p.first member
    will bind to that temporary. When the constructor returns, the reference
    is immediately dangling. P2255 requires the constructor to be deleted to
    prevent this bug.
    
    Although P2255 was approved for C++23, it fixes a longstanding LWG issue
    in older standards, and it turns silent runtime undefined behaviour into
    a compilation error. Because of that, the dangling checks are applied
    all the way back to C++98.  However, if these changes cause too much
    code to be rejected (e.g. in cases where the dangling reference is never
    used after the constructor returns) then we can consider removing them
    for C++20 and older standards.
    
    The affected constructors are deleted for C++20 and later, when concepts
    are available to simplify the constructor constraints. For C++17 and
    earlier the overload sets are complicated and awkward to maintain, so
    the dangling checks are done in static assertions in the constructor
    bodies, instead of being SFINAE-friendly constraints. The pre-C++17
    assertions are only enabled for Debug Mode, to avoid introducing a
    breaking change in Stage 4. We should consider enabling them by default
    in Stage 1 for GCC 14.
    
    libstdc++-v3/ChangeLog:
    
            * include/bits/stl_pair.h (pair) [C++20]: Add non-dangling
            constraints to constructors and add deleted overloads for the
            dangling cases, as per P2255R2.
            (pair) [!C++20 && _GLIBCXX_DEBUG]: Add static assertions to
            make dangling cases ill-formed.
            * testsuite/20_util/pair/dangling_ref.cc: New test.

Diff:
---
 libstdc++-v3/include/bits/stl_pair.h               | 112 ++++++++++++++++++---
 .../testsuite/20_util/pair/dangling_ref.cc         |  67 ++++++++++++
 2 files changed, 164 insertions(+), 15 deletions(-)

diff --git a/libstdc++-v3/include/bits/stl_pair.h b/libstdc++-v3/include/bits/stl_pair.h
index d0c73410526..3f1624f40b4 100644
--- a/libstdc++-v3/include/bits/stl_pair.h
+++ b/libstdc++-v3/include/bits/stl_pair.h
@@ -281,6 +281,21 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	    return is_convertible_v<_U2, _T2>;
 	  return false;
 	}
+
+      // True if construction from _U1 and _U2 would create a dangling ref.
+      template<typename _U1, typename _U2>
+	static constexpr bool
+	_S_dangles()
+	{
+#if __has_builtin(__reference_constructs_from_temporary)
+	  if constexpr (__reference_constructs_from_temporary(_T1, _U1&&))
+	    return true;
+	  else
+	    return __reference_constructs_from_temporary(_T2, _U2&&);
+#else
+	  return false;
+#endif
+	}
       /// @endcond
 
     public:
@@ -295,25 +310,37 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 
       /// Constructor accepting two values of arbitrary types
       template<typename _U1, typename _U2>
-	requires (_S_constructible<_U1, _U2>())
+	requires (_S_constructible<_U1, _U2>()) && (!_S_dangles<_U1, _U2>())
 	constexpr explicit(!_S_convertible<_U1, _U2>())
 	pair(_U1&& __x, _U2&& __y)
 	noexcept(_S_nothrow_constructible<_U1, _U2>())
 	: first(std::forward<_U1>(__x)), second(std::forward<_U2>(__y))
 	{ }
 
+      template<typename _U1, typename _U2>
+	requires (_S_constructible<_U1, _U2>()) && (_S_dangles<_U1, _U2>())
+	constexpr explicit(!_S_convertible<_U1, _U2>())
+	pair(_U1&&, _U2&&) = delete;
+
       /// Converting constructor from a const `pair<U1, U2>` lvalue
       template<typename _U1, typename _U2>
 	requires (_S_constructible<const _U1&, const _U2&>())
+	  && (!_S_dangles<_U1, _U2>())
 	constexpr explicit(!_S_convertible<const _U1&, const _U2&>())
 	pair(const pair<_U1, _U2>& __p)
 	noexcept(_S_nothrow_constructible<const _U1&, const _U2&>())
 	: first(__p.first), second(__p.second)
 	{ }
 
+      template<typename _U1, typename _U2>
+	requires (_S_constructible<const _U1&, const _U2&>())
+	      && (_S_dangles<const _U1&, const _U2&>())
+	constexpr explicit(!_S_convertible<const _U1&, const _U2&>())
+	pair(const pair<_U1, _U2>&) = delete;
+
       /// Converting constructor from a non-const `pair<U1, U2>` rvalue
       template<typename _U1, typename _U2>
-	requires (_S_constructible<_U1, _U2>())
+	requires (_S_constructible<_U1, _U2>()) && (!_S_dangles<_U1, _U2>())
 	constexpr explicit(!_S_convertible<_U1, _U2>())
 	pair(pair<_U1, _U2>&& __p)
 	noexcept(_S_nothrow_constructible<_U1, _U2>())
@@ -321,25 +348,42 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	  second(std::forward<_U2>(__p.second))
 	{ }
 
+      template<typename _U1, typename _U2>
+	requires (_S_constructible<_U1, _U2>()) && (_S_dangles<_U1, _U2>())
+	constexpr explicit(!_S_convertible<_U1, _U2>())
+	pair(pair<_U1, _U2>&&) = delete;
+
 #if __cplusplus > 202002L
       /// Converting constructor from a non-const `pair<U1, U2>` lvalue
       template<typename _U1, typename _U2>
-	requires (_S_constructible<_U1&, _U2&>())
+	requires (_S_constructible<_U1&, _U2&>()) && (!_S_dangles<_U1&, _U2&>())
 	constexpr explicit(!_S_convertible<_U1&, _U2&>())
 	pair(pair<_U1, _U2>& __p)
 	noexcept(_S_nothrow_constructible<_U1&, _U2&>())
 	: first(__p.first), second(__p.second)
 	{ }
 
+      template<typename _U1, typename _U2>
+	requires (_S_constructible<_U1&, _U2&>()) && (_S_dangles<_U1&, _U2&>())
+	constexpr explicit(!_S_convertible<_U1&, _U2&>())
+	pair(pair<_U1, _U2>&) = delete;
+
       /// Converting constructor from a const `pair<U1, U2>` rvalue
       template<typename _U1, typename _U2>
 	requires (_S_constructible<const _U1, const _U2>())
+	  && (!_S_dangles<const _U1, const _U2>())
 	constexpr explicit(!_S_convertible<const _U1, const _U2>())
 	pair(const pair<_U1, _U2>&& __p)
 	noexcept(_S_nothrow_constructible<const _U1, const _U2>())
 	: first(std::forward<const _U1>(__p.first)),
 	  second(std::forward<const _U2>(__p.second))
 	{ }
+
+      template<typename _U1, typename _U2>
+	requires (_S_constructible<const _U1, const _U2>())
+	  && (_S_dangles<const _U1, const _U2>())
+	constexpr explicit(!_S_convertible<const _U1, const _U2>())
+	pair(const pair<_U1, _U2>&&) = delete;
 #endif // C++23
 
   private:
@@ -463,6 +507,19 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 #else // !__cpp_lib_concepts
       // C++11/14/17 implementation using enable_if, partially constexpr.
 
+      /// @cond undocumented
+      // Error if construction from _U1 and _U2 would create a dangling ref.
+#if __has_builtin(__reference_constructs_from_temporary) \
+      && defined _GLIBCXX_DEBUG
+# define __glibcxx_no_dangling_refs(_U1, _U2) \
+  static_assert(!__reference_constructs_from_temporary(_T1, _U1) \
+	       && !__reference_constructs_from_temporary(_T2, _U2), \
+		"std::pair constructor creates a dangling reference")
+#else
+# define __glibcxx_no_dangling_refs(_U1, _U2)
+#endif
+      /// @endcond
+
       /** The default constructor creates @c first and @c second using their
        *  respective default constructors.  */
       template <typename _U1 = _T1,
@@ -525,8 +582,9 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	                 && _PCCFP<_U1, _U2>::template
 			   _ImplicitlyConvertiblePair<_U1, _U2>(),
 			  bool>::type=true>
-        constexpr pair(const pair<_U1, _U2>& __p)
-        : first(__p.first), second(__p.second) { }
+	constexpr pair(const pair<_U1, _U2>& __p)
+	: first(__p.first), second(__p.second)
+	{ __glibcxx_no_dangling_refs(const _U1&, const _U2&); }
 
       template<typename _U1, typename _U2, typename
 	       enable_if<_PCCFP<_U1, _U2>::template
@@ -535,7 +593,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 			   _ImplicitlyConvertiblePair<_U1, _U2>(),
                          bool>::type=false>
 	explicit constexpr pair(const pair<_U1, _U2>& __p)
-	: first(__p.first), second(__p.second) { }
+	: first(__p.first), second(__p.second)
+	{ __glibcxx_no_dangling_refs(const _U1&, const _U2&); }
 
 #if _GLIBCXX_USE_DEPRECATED
 #if defined(__DEPRECATED)
@@ -575,7 +634,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	_GLIBCXX_DEPRECATED_PAIR_CTOR
 	constexpr
 	pair(_U1&& __x, __zero_as_null_pointer_constant, ...)
-	: first(std::forward<_U1>(__x)), second(nullptr) { }
+	: first(std::forward<_U1>(__x)), second(nullptr)
+	{ __glibcxx_no_dangling_refs(_U1&&, std::nullptr_t); }
 
       template<typename _U1,
 	       __enable_if_t<__and_<__not_<is_reference<_U1>>,
@@ -587,7 +647,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	_GLIBCXX_DEPRECATED_PAIR_CTOR
 	explicit constexpr
 	pair(_U1&& __x, __zero_as_null_pointer_constant, ...)
-	: first(std::forward<_U1>(__x)), second(nullptr) { }
+	: first(std::forward<_U1>(__x)), second(nullptr)
+	{ __glibcxx_no_dangling_refs(_U1&&, std::nullptr_t); }
 
       template<typename _U2,
 	       __enable_if_t<__and_<is_pointer<_T1>,
@@ -599,7 +660,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	_GLIBCXX_DEPRECATED_PAIR_CTOR
 	constexpr
 	pair(__zero_as_null_pointer_constant, _U2&& __y, ...)
-	: first(nullptr), second(std::forward<_U2>(__y)) { }
+	: first(nullptr), second(std::forward<_U2>(__y))
+	{ __glibcxx_no_dangling_refs(std::nullptr_t, _U2&&); }
 
       template<typename _U2,
 	       __enable_if_t<__and_<is_pointer<_T1>,
@@ -611,7 +673,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	_GLIBCXX_DEPRECATED_PAIR_CTOR
 	explicit constexpr
 	pair(__zero_as_null_pointer_constant, _U2&& __y, ...)
-	: first(nullptr), second(std::forward<_U2>(__y)) { }
+	: first(nullptr), second(std::forward<_U2>(__y))
+	{ __glibcxx_no_dangling_refs(std::nullptr_t, _U2&&); }
 #undef _GLIBCXX_DEPRECATED_PAIR_CTOR
 #endif
 
@@ -622,7 +685,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 			   _ImplicitlyMoveConvertiblePair<_U1, _U2>(),
                          bool>::type=true>
 	constexpr pair(_U1&& __x, _U2&& __y)
-	: first(std::forward<_U1>(__x)), second(std::forward<_U2>(__y)) { }
+	: first(std::forward<_U1>(__x)), second(std::forward<_U2>(__y))
+	{ __glibcxx_no_dangling_refs(_U1&&, _U2&&); }
 
       template<typename _U1, typename _U2, typename
 	       enable_if<_PCCP::template
@@ -631,7 +695,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 			   _ImplicitlyMoveConvertiblePair<_U1, _U2>(),
                          bool>::type=false>
 	explicit constexpr pair(_U1&& __x, _U2&& __y)
-	: first(std::forward<_U1>(__x)), second(std::forward<_U2>(__y)) { }
+	: first(std::forward<_U1>(__x)), second(std::forward<_U2>(__y))
+	{ __glibcxx_no_dangling_refs(_U1&&, _U2&&); }
 
 
       template<typename _U1, typename _U2, typename
@@ -642,7 +707,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
                          bool>::type=true>
 	constexpr pair(pair<_U1, _U2>&& __p)
 	: first(std::forward<_U1>(__p.first)),
-	  second(std::forward<_U2>(__p.second)) { }
+	  second(std::forward<_U2>(__p.second))
+	{ __glibcxx_no_dangling_refs(_U1&&, _U2&&); }
 
       template<typename _U1, typename _U2, typename
 	       enable_if<_PCCFP<_U1, _U2>::template
@@ -652,7 +718,10 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
                          bool>::type=false>
 	explicit constexpr pair(pair<_U1, _U2>&& __p)
 	: first(std::forward<_U1>(__p.first)),
-	  second(std::forward<_U2>(__p.second)) { }
+	  second(std::forward<_U2>(__p.second))
+	{ __glibcxx_no_dangling_refs(_U1&&, _U2&&); }
+
+#undef __glibcxx_no_dangling_refs
 
       pair&
       operator=(__conditional_t<__and_<is_copy_assignable<_T1>,
@@ -714,7 +783,20 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       /// Templated constructor to convert from other pairs.
       template<typename _U1, typename _U2>
 	pair(const pair<_U1, _U2>& __p)
-	: first(__p.first), second(__p.second) { }
+	: first(__p.first), second(__p.second)
+	{
+#if __has_builtin(__reference_constructs_from_temporary)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-local-typedefs"
+	  typedef int _DanglingCheck1[
+	    __reference_constructs_from_temporary(_T1, const _U1&) ? -1 : 1
+		      ];
+	  typedef int _DanglingCheck2[
+	    __reference_constructs_from_temporary(_T2, const _U2&) ? -1 : 1
+		      ];
+#pragma GCC diagnostic pop
+#endif
+	}
 #endif // C++11
     };
 
diff --git a/libstdc++-v3/testsuite/20_util/pair/dangling_ref.cc b/libstdc++-v3/testsuite/20_util/pair/dangling_ref.cc
new file mode 100644
index 00000000000..8e0c34816dd
--- /dev/null
+++ b/libstdc++-v3/testsuite/20_util/pair/dangling_ref.cc
@@ -0,0 +1,67 @@
+// { dg-do compile { target c++11 } }
+// { dg-options "-Wno-unused-variable" }
+// { dg-additional-options "-D_GLIBCXX_DEBUG" { target c++17_down } }
+
+#include <utility>
+
+#if __cplusplus >= 202002L
+// For C++20 and later, constructors are constrained to disallow dangling.
+static_assert(!std::is_constructible_v<std::pair<const int&, int>, long, int>);
+static_assert(!std::is_constructible_v<std::pair<int, const int&>, int, long>);
+static_assert(!std::is_constructible_v<std::pair<const int&, int>,
+				       std::pair<long, long>>);
+static_assert(!std::is_constructible_v<std::pair<int, const int&>,
+				       std::pair<long, long>>);
+static_assert(!std::is_constructible_v<std::pair<const int&, int>,
+				       const std::pair<long, long>&>);
+static_assert(!std::is_constructible_v<std::pair<int, const int&>,
+				       const std::pair<long, long>&>);
+#endif
+
+void
+test_binary_ctors()
+{
+  std::pair<const int&, int> p1(1L, 2);
+  // { dg-error "here" "" { target c++17_down } 24 }
+  // { dg-error "use of deleted function" "" { target c++20 } 24 }
+
+  std::pair<int, const int&> p2(1, 2L);
+  // { dg-error "here" "" { target c++17_down } 28 }
+  // { dg-error "use of deleted function" "" { target c++20 } 28 }
+
+  std::pair<const int&, const int&> p3(1L, 2L);
+  // { dg-error "here" "" { target c++17_down } 32 }
+  // { dg-error "use of deleted function" "" { target c++20 } 32 }
+}
+
+void
+test_converting_ctors()
+{
+  std::pair<long, long> p0;
+
+  std::pair<const int&, int> p1(p0);
+  // { dg-error "here" "" { target c++17_down } 42 }
+  // { dg-error "use of deleted function" "" { target c++20 } 42 }
+
+  std::pair<int, const int&> p2(p0);
+  // { dg-error "here" "" { target c++17_down } 46 }
+  // { dg-error "use of deleted function" "" { target c++20 } 46 }
+
+  std::pair<const int&, const int&> p3(p0);
+  // { dg-error "here" "" { target c++17_down } 50 }
+  // { dg-error "use of deleted function" "" { target c++20 } 50 }
+
+  std::pair<const int&, int> p4(std::move(p0));
+  // { dg-error "here" "" { target c++17_down } 54 }
+  // { dg-error "use of deleted function" "" { target c++20 } 54 }
+
+  std::pair<int, const int&> p5(std::move(p0));
+  // { dg-error "here" "" { target c++17_down } 58 }
+  // { dg-error "use of deleted function" "" { target c++20 } 58 }
+
+  std::pair<const int&, const int&> p6(std::move(p0));
+  // { dg-error "here" "" { target c++17_down } 62 }
+  // { dg-error "use of deleted function" "" { target c++20 } 62 }
+}
+
+// { dg-error "static assert.* dangling reference" "" { target { c++17_down } } 0 }


More information about the Libstdc++-cvs mailing list