c++: implement [[gnu::no_dangling]] [PR110358]

Since -Wdangling-reference has false positives that can't be
prevented, we should offer an easy way to suppress the warning.
Currently, that is only possible by using a #pragma, either around the
enclosing class or around the call site.  But #pragma GCC diagnostic tend
to be onerous.  A better solution would be to have an attribute.

To that end, this patch adds a new attribute, [[gnu::no_dangling]].
This attribute takes an optional bool argument to support cases like:

  template <typename T>
  struct [[gnu::no_dangling(std::is_reference_v<T>)]] S {
     // ...
  };

	PR c++/110358
	PR c++/109642

gcc/cp/ChangeLog:

	* call.cc (no_dangling_p): New.
	(reference_like_class_p): Use it.
	(do_warn_dangling_reference): Use it.  Don't warn when the function
	or its enclosing class has attribute gnu::no_dangling.
	* tree.cc (cxx_gnu_attributes): Add gnu::no_dangling.
	(handle_no_dangling_attribute): New.

gcc/ChangeLog:

	* doc/extend.texi: Document gnu::no_dangling.
	* doc/invoke.texi: Mention that gnu::no_dangling disables
	-Wdangling-reference.

gcc/testsuite/ChangeLog:

	* g++.dg/ext/attr-no-dangling1.C: New test.
	* g++.dg/ext/attr-no-dangling2.C: New test.
	* g++.dg/ext/attr-no-dangling3.C: New test.
	* g++.dg/ext/attr-no-dangling4.C: New test.
	* g++.dg/ext/attr-no-dangling5.C: New test.
	* g++.dg/ext/attr-no-dangling6.C: New test.
	* g++.dg/ext/attr-no-dangling7.C: New test.
	* g++.dg/ext/attr-no-dangling8.C: New test.
	* g++.dg/ext/attr-no-dangling9.C: New test.
This commit is contained in:
Marek Polacek 2024-01-25 16:38:51 -05:00
parent 64221c7bff
commit c7607c4cf1
13 changed files with 401 additions and 6 deletions

View file

@ -14033,11 +14033,7 @@ std_pair_ref_ref_p (tree t)
return true;
}
/* Return true if a class CTYPE is either std::reference_wrapper or
std::ref_view, or a reference wrapper class. We consider a class
a reference wrapper class if it has a reference member. We no
longer check that it has a constructor taking the same reference type
since that approach still generated too many false positives. */
/* Return true if a class T has a reference member. */
static bool
class_has_reference_member_p (tree t)
@ -14061,12 +14057,41 @@ class_has_reference_member_p_r (tree binfo, void *)
? integer_one_node : NULL_TREE);
}
/* Return true if T (either a class or a function) has been marked as
not-dangling. */
static bool
no_dangling_p (tree t)
{
t = lookup_attribute ("no_dangling", TYPE_ATTRIBUTES (t));
if (!t)
return false;
t = TREE_VALUE (t);
if (!t)
return true;
t = build_converted_constant_bool_expr (TREE_VALUE (t), tf_warning_or_error);
t = cxx_constant_value (t);
return t == boolean_true_node;
}
/* Return true if a class CTYPE is either std::reference_wrapper or
std::ref_view, or a reference wrapper class. We consider a class
a reference wrapper class if it has a reference member. We no
longer check that it has a constructor taking the same reference type
since that approach still generated too many false positives. */
static bool
reference_like_class_p (tree ctype)
{
if (!CLASS_TYPE_P (ctype))
return false;
if (no_dangling_p (ctype))
return true;
/* Also accept a std::pair<const T&, const T&>. */
if (std_pair_ref_ref_p (ctype))
return true;
@ -14173,7 +14198,8 @@ do_warn_dangling_reference (tree expr, bool arg_p)
but probably not to one of its arguments. */
|| (DECL_OBJECT_MEMBER_FUNCTION_P (fndecl)
&& DECL_OVERLOADED_OPERATOR_P (fndecl)
&& DECL_OVERLOADED_OPERATOR_IS (fndecl, INDIRECT_REF)))
&& DECL_OVERLOADED_OPERATOR_IS (fndecl, INDIRECT_REF))
|| no_dangling_p (TREE_TYPE (fndecl)))
return NULL_TREE;
tree rettype = TREE_TYPE (TREE_TYPE (fndecl));

View file

@ -47,6 +47,7 @@ static tree verify_stmt_tree_r (tree *, int *, void *);
static tree handle_init_priority_attribute (tree *, tree, tree, int, bool *);
static tree handle_abi_tag_attribute (tree *, tree, tree, int, bool *);
static tree handle_contract_attribute (tree *, tree, tree, int, bool *);
static tree handle_no_dangling_attribute (tree *, tree, tree, int, bool *);
/* If REF is an lvalue, returns the kind of lvalue that REF is.
Otherwise, returns clk_none. */
@ -5102,6 +5103,8 @@ static const attribute_spec cxx_gnu_attributes[] =
handle_init_priority_attribute, NULL },
{ "abi_tag", 1, -1, false, false, false, true,
handle_abi_tag_attribute, NULL },
{ "no_dangling", 0, 1, false, true, false, false,
handle_no_dangling_attribute, NULL },
};
const scoped_attribute_specs cxx_gnu_attribute_table =
@ -5391,6 +5394,29 @@ handle_contract_attribute (tree *ARG_UNUSED (node), tree ARG_UNUSED (name),
return NULL_TREE;
}
/* Handle a "no_dangling" attribute; arguments as in
struct attribute_spec.handler. */
tree
handle_no_dangling_attribute (tree *node, tree name, tree args, int,
bool *no_add_attrs)
{
if (args && TREE_CODE (TREE_VALUE (args)) == STRING_CST)
{
error ("%qE attribute argument must be an expression that evaluates "
"to true or false", name);
*no_add_attrs = true;
}
else if (!FUNC_OR_METHOD_TYPE_P (*node)
&& !RECORD_OR_UNION_TYPE_P (*node))
{
warning (OPT_Wattributes, "%qE attribute ignored", name);
*no_add_attrs = true;
}
return NULL_TREE;
}
/* Return a new PTRMEM_CST of the indicated TYPE. The MEMBER is the
thing pointed to by the constant. */

View file

@ -29327,6 +29327,54 @@ Some_Class B __attribute__ ((init_priority (543)));
Note that the particular values of @var{priority} do not matter; only their
relative ordering.
@cindex @code{no_dangling} type attribute
@cindex @code{no_dangling} function attribute
@item no_dangling
This attribute can be applied on a class type, function, or member
function. Dangling references to classes marked with this attribute
will have the @option{-Wdangling-reference} diagnostic suppressed; so
will references returned from the @code{gnu::no_dangling}-marked
functions. For example:
@smallexample
class [[gnu::no_dangling]] S @{ @dots{} @};
@end smallexample
Or:
@smallexample
class A @{
int *p;
[[gnu::no_dangling]] int &foo() @{ return *p; @}
@};
[[gnu::no_dangling]] const int &
foo (const int &i)
@{
@dots{}
@}
@end smallexample
This attribute takes an optional argument, which must be an expression that
evaluates to true or false:
@smallexample
template <typename T>
struct [[gnu::no_dangling(std::is_reference_v<T>)]] S @{
@dots{}
@};
@end smallexample
Or:
@smallexample
template <typename T>
[[gnu::no_dangling(std::is_reference_v<T>)]] int& foo (T& t) @{
@dots{}
@};
@end smallexample
@cindex @code{warn_unused} type attribute
@item warn_unused

View file

@ -3908,6 +3908,9 @@ const T& foo (const T&) @{ @dots{} @}
#pragma GCC diagnostic pop
@end smallexample
The @code{#pragma} can also surround the class; in that case, the warning
will be disabled for all the member functions.
@option{-Wdangling-reference} also warns about code like
@smallexample
@ -3932,6 +3935,9 @@ struct Span @{
as @code{std::span}-like; that is, the class is a non-union class
that has a pointer data member and a trivial destructor.
The warning can be disabled by using the @code{gnu::no_dangling} attribute
(@pxref{C++ Attributes}).
This warning is enabled by @option{-Wall}.
@opindex Wdelete-non-virtual-dtor

View file

@ -0,0 +1,40 @@
// { dg-do compile { target c++11 } }
// { dg-options "-Wdangling-reference" }
int g = 42;
struct [[gnu::no_dangling]] A {
~A();
int *i;
int &foo() { return *i; }
};
struct A2 {
~A2();
int *i;
[[gnu::no_dangling]] int &foo() { return *i; }
[[gnu::no_dangling]] static int &bar (const int &) { return *&g; }
};
union [[gnu::no_dangling]] U { };
A a() { return A{&g}; }
A2 a2() { return A2{&g}; }
class X { };
const X x1;
const X x2;
[[gnu::no_dangling]] const X& get(const int& i)
{
return i == 0 ? x1 : x2;
}
void
test ()
{
[[maybe_unused]] const X& x = get (10); // { dg-bogus "dangling" }
[[maybe_unused]] const int &i = a().foo(); // { dg-bogus "dangling" }
[[maybe_unused]] const int &j = a2().foo(); // { dg-bogus "dangling" }
[[maybe_unused]] const int &k = a2().bar(10); // { dg-bogus "dangling" }
}

View file

@ -0,0 +1,29 @@
// { dg-do compile { target c++11 } }
// Negative tests.
struct [[no_dangling]] A { // { dg-warning "ignored" }
[[no_dangling]] int &foo (int &); // { dg-warning "ignored" }
};
[[no_dangling]] int &bar (int &); // { dg-warning "ignored" }
[[gnu::no_dangling]] int i; // { dg-warning "ignored" }
[[gnu::no_dangling]] double d; // { dg-warning "ignored" }
[[gnu::no_dangling]] typedef int T; // { dg-warning "ignored" }
[[gnu::no_dangling()]] int &fn1 (int &); // { dg-error "parentheses" }
[[gnu::no_dangling("a")]] int &fn2 (int &); // { dg-error "must be an expression" }
[[gnu::no_dangling(true, true)]] int &fn3 (int &); // { dg-error "wrong number of arguments" }
enum [[gnu::no_dangling]] E { // { dg-warning "ignored" }
X [[gnu::no_dangling]] // { dg-warning "ignored" }
};
[[gnu::no_dangling]]; // { dg-warning "ignored" }
void
g ()
{
goto L;
[[gnu::no_dangling]] L:; // { dg-warning "ignored" }
}

View file

@ -0,0 +1,24 @@
// { dg-do compile { target c++11 } }
// { dg-options "-Wdangling-reference" }
template <typename T>
struct [[gnu::no_dangling]] Span {
T* data_;
int len_;
// So that our heuristic doesn't suppress the warning anyway.
~Span();
[[nodiscard]] constexpr auto operator[](int n) const noexcept -> T& { return data_[n]; }
[[nodiscard]] constexpr auto front() const noexcept -> T& { return data_[0]; }
[[nodiscard]] constexpr auto back() const noexcept -> T& { return data_[len_ - 1]; }
};
auto get() -> Span<int>;
auto f() -> int {
int const& a = get().front(); // { dg-bogus "dangling" }
int const& b = get().back(); // { dg-bogus "dangling" }
int const& c = get()[0]; // { dg-bogus "dangling" }
return a + b + c;
}

View file

@ -0,0 +1,14 @@
// { dg-do compile { target c++11 } }
#if !__has_attribute(no_dangling)
#error unsupported
#endif
#ifdef __has_cpp_attribute
# if !__has_cpp_attribute(no_dangling)
# error no_dangling
# endif
#endif
struct [[gnu::no_dangling]] S { };
static_assert (__builtin_has_attribute (S, no_dangling), "");

View file

@ -0,0 +1,31 @@
// PR c++/110358
// { dg-do compile { target c++20 } }
// { dg-options "-Wdangling-reference" }
template <typename T>
struct Span {
T* data_;
int len_;
~Span();
[[nodiscard]] constexpr auto operator[](int n) const noexcept -> T& { return data_[n]; }
};
template <>
struct [[gnu::no_dangling]] Span<int> {
int* data_;
int len_;
~Span();
[[nodiscard]] constexpr auto operator[](int n) const noexcept -> int& { return data_[n]; }
};
auto getch() -> Span<char>;
auto geti() -> Span<int>;
void
f ()
{
[[maybe_unused]] const auto &a = getch()[0]; // { dg-warning "dangling reference" }
[[maybe_unused]] const auto &b = geti()[0]; // { dg-bogus "dangling reference" }
}

View file

@ -0,0 +1,65 @@
// PR c++/110358
// { dg-do compile { target c++20 } }
// { dg-options "-Wdangling-reference" }
class X { };
const X x1;
const X x2;
constexpr bool val () { return true; }
struct ST { static constexpr bool value = true; };
struct SF { static constexpr bool value = false; };
template<typename T>
[[gnu::no_dangling(T::value)]]
const X& get (const int& i)
{
return i == 0 ? x1 : x2;
}
template<bool B = true>
[[gnu::no_dangling(B)]]
const X& foo (const int& i)
{
return i == 0 ? x1 : x2;
}
[[gnu::no_dangling(val ())]]
const X& bar (const int& i)
{
return i == 0 ? x1 : x2;
}
[[gnu::no_dangling(!val ())]]
const X& baz (const int& i)
{
return i == 0 ? x1 : x2;
}
template <typename T>
struct [[gnu::no_dangling(T::value)]]
Span {
T* data_;
int len_;
~Span();
[[nodiscard]] constexpr auto operator[](int n) const noexcept -> T& { return data_[n]; }
};
auto geti() -> Span<ST>;
auto gety() -> Span<SF>;
void
test ()
{
[[maybe_unused]] const X& x1 = get<ST> (10); // { dg-bogus "dangling" }
[[maybe_unused]] const X& x2 = get<SF> (10); // { dg-warning "dangling" }
[[maybe_unused]] const X& x3 = foo<true> (10); // { dg-bogus "dangling" }
[[maybe_unused]] const X& x4 = foo<false> (10); // { dg-warning "dangling" }
[[maybe_unused]] const X& x7 = foo<> (10); // { dg-bogus "dangling" }
[[maybe_unused]] const X& x5 = bar (10); // { dg-bogus "dangling" }
[[maybe_unused]] const X& x6 = baz (10); // { dg-warning "dangling" }
[[maybe_unused]] const auto &b1 = geti()[0]; // { dg-bogus "dangling" }
[[maybe_unused]] const auto &b2 = gety()[0]; // { dg-warning "dangling" }
}

View file

@ -0,0 +1,31 @@
// PR c++/110358
// { dg-do compile { target c++20 } }
// { dg-options "-Wdangling-reference" }
class X { };
const X x1;
const X x2;
template<bool... N>
[[gnu::no_dangling(N)]] const X& get(const int& i); // { dg-error "parameter packs not expanded" }
template<typename T>
[[gnu::no_dangling(T::x)]] // { dg-error "member" }
const X& foo(const int& i);
bool val () { return true; }
[[gnu::no_dangling(val ())]] // { dg-error "call" }
const X& bar (const int& i);
[[gnu::no_dangling(20)]] const X& fn1 (const int &);
void
test ()
{
[[maybe_unused]] const X& x1 = bar (10); // { dg-warning "dangling" }
[[maybe_unused]] const X& x2 = foo<int> (10); // { dg-error "no matching" }
[[maybe_unused]] const X& x3 // { dg-warning "dangling" }
= fn1 (10); // { dg-error "narrowing" }
}

View file

@ -0,0 +1,30 @@
// PR c++/110358
// { dg-do compile { target c++20 } }
// { dg-options "-Wdangling-reference" }
template<class T> constexpr bool is_reference_v = false;
template<class T> constexpr bool is_reference_v<T&> = true;
template<class T> constexpr bool is_reference_v<T&&> = true;
template <typename T>
struct [[gnu::no_dangling(is_reference_v<T>)]] S {
int &foo (const int &);
};
template <typename T1, typename T2>
struct X {
template <typename U1 = T1, typename U2 = T2>
struct [[gnu::no_dangling(is_reference_v<U1> && is_reference_v<U2>)]] Y {
int &foo (const int &);
};
};
void
g ()
{
[[maybe_unused]] const int &x0 = S<int&>().foo (42); // { dg-bogus "dangling" }
[[maybe_unused]] const int &x1 = S<int>().foo (42); // { dg-warning "dangling" }
[[maybe_unused]] const auto &x2 = X<int, int&>::Y<>().foo (42); // { dg-warning "dangling" }
[[maybe_unused]] const auto &x3 = X<int&, int&>::Y<>().foo (42); // { dg-bogus "dangling" }
}

View file

@ -0,0 +1,25 @@
// PR c++/110358
// { dg-do compile { target c++20 } }
// { dg-options "-Wdangling-reference" }
template<bool B>
struct bool_constant {
static constexpr bool value = B;
constexpr operator bool() const { return value; }
};
using true_type = bool_constant<true>;
using false_type = bool_constant<false>;
struct S {
template<bool B>
[[gnu::no_dangling(B)]] int &foo (const int &);
};
void
g ()
{
[[maybe_unused]] const int &x0 = S().foo<false_type{}> (42); // { dg-warning "dangling" }
[[maybe_unused]] const int &x1 = S().foo<true_type{}> (42); // { dg-bogus "dangling" }
}