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:
parent
64221c7bff
commit
c7607c4cf1
13 changed files with 401 additions and 6 deletions
|
@ -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));
|
||||
|
|
|
@ -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. */
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
40
gcc/testsuite/g++.dg/ext/attr-no-dangling1.C
Normal file
40
gcc/testsuite/g++.dg/ext/attr-no-dangling1.C
Normal 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" }
|
||||
}
|
29
gcc/testsuite/g++.dg/ext/attr-no-dangling2.C
Normal file
29
gcc/testsuite/g++.dg/ext/attr-no-dangling2.C
Normal 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" }
|
||||
}
|
24
gcc/testsuite/g++.dg/ext/attr-no-dangling3.C
Normal file
24
gcc/testsuite/g++.dg/ext/attr-no-dangling3.C
Normal 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;
|
||||
}
|
14
gcc/testsuite/g++.dg/ext/attr-no-dangling4.C
Normal file
14
gcc/testsuite/g++.dg/ext/attr-no-dangling4.C
Normal 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), "");
|
31
gcc/testsuite/g++.dg/ext/attr-no-dangling5.C
Normal file
31
gcc/testsuite/g++.dg/ext/attr-no-dangling5.C
Normal 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" }
|
||||
}
|
65
gcc/testsuite/g++.dg/ext/attr-no-dangling6.C
Normal file
65
gcc/testsuite/g++.dg/ext/attr-no-dangling6.C
Normal 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" }
|
||||
}
|
31
gcc/testsuite/g++.dg/ext/attr-no-dangling7.C
Normal file
31
gcc/testsuite/g++.dg/ext/attr-no-dangling7.C
Normal 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" }
|
||||
}
|
||||
|
30
gcc/testsuite/g++.dg/ext/attr-no-dangling8.C
Normal file
30
gcc/testsuite/g++.dg/ext/attr-no-dangling8.C
Normal 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" }
|
||||
}
|
||||
|
25
gcc/testsuite/g++.dg/ext/attr-no-dangling9.C
Normal file
25
gcc/testsuite/g++.dg/ext/attr-no-dangling9.C
Normal 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" }
|
||||
}
|
||||
|
Loading…
Add table
Reference in a new issue