* [PATCH] c++: implement [[gnu::non_owning]] [PR110358] @ 2024-01-26 1:37 Marek Polacek 2024-01-26 2:34 ` Marek Polacek 2024-01-26 21:04 ` Jason Merrill 0 siblings, 2 replies; 15+ messages in thread From: Marek Polacek @ 2024-01-26 1:37 UTC (permalink / raw) To: GCC Patches, Jason Merrill Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk? -- >8 -- 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. Such an attribute should not be tied to this particular warning though. [*] The warning bogusly triggers for classes that are like std::span, std::reference_wrapper, and std::ranges::ref_view. The common property seems to be that these classes are only wrappers around some data. So I chose the name non_owning, but I'm not attached to it. I hope that in the future the attribute can be used for something other than this diagnostic. [*] As I'm typing this, it's occurring to me that we might consider having a general attribute allowing users to do [[gnu::ignore("-Wfoo")]]. PR c++/110358 PR c++/109642 gcc/cp/ChangeLog: * call.cc (do_warn_dangling_reference): Don't warn when the function or its enclosing class has attribute gnu::non_owning. * tree.cc (cxx_gnu_attributes): Add gnu::non_owning. (handle_non_owning_attribute): New. gcc/ChangeLog: * doc/extend.texi: Document gnu::non_owning. * doc/invoke.texi: Mention that gnu::non_owning disables -Wdangling-reference. gcc/testsuite/ChangeLog: * g++.dg/ext/attr-non-owning1.C: New test. * g++.dg/ext/attr-non-owning2.C: New test. * g++.dg/ext/attr-non-owning3.C: New test. * g++.dg/ext/attr-non-owning4.C: New test. * g++.dg/ext/attr-non-owning5.C: New test. --- gcc/cp/call.cc | 9 ++++- gcc/cp/tree.cc | 20 +++++++++++ gcc/doc/extend.texi | 15 ++++++++ gcc/doc/invoke.texi | 21 ++++++++++++ gcc/testsuite/g++.dg/ext/attr-non-owning1.C | 38 +++++++++++++++++++++ gcc/testsuite/g++.dg/ext/attr-non-owning2.C | 28 +++++++++++++++ gcc/testsuite/g++.dg/ext/attr-non-owning3.C | 24 +++++++++++++ gcc/testsuite/g++.dg/ext/attr-non-owning4.C | 14 ++++++++ gcc/testsuite/g++.dg/ext/attr-non-owning5.C | 29 ++++++++++++++++ 9 files changed, 197 insertions(+), 1 deletion(-) create mode 100644 gcc/testsuite/g++.dg/ext/attr-non-owning1.C create mode 100644 gcc/testsuite/g++.dg/ext/attr-non-owning2.C create mode 100644 gcc/testsuite/g++.dg/ext/attr-non-owning3.C create mode 100644 gcc/testsuite/g++.dg/ext/attr-non-owning4.C create mode 100644 gcc/testsuite/g++.dg/ext/attr-non-owning5.C diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc index 9de0d77c423..88ddba825a9 100644 --- a/gcc/cp/call.cc +++ b/gcc/cp/call.cc @@ -14157,9 +14157,16 @@ 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)) + || lookup_attribute ("non_owning", + TYPE_ATTRIBUTES (TREE_TYPE (fndecl)))) return NULL_TREE; + if (tree ctx = CP_DECL_CONTEXT (fndecl)) + if (TYPE_P (ctx) + && lookup_attribute ("non_owning", TYPE_ATTRIBUTES (ctx))) + return NULL_TREE; + tree rettype = TREE_TYPE (TREE_TYPE (fndecl)); /* If the function doesn't return a reference, don't warn. This can be e.g. diff --git a/gcc/cp/tree.cc b/gcc/cp/tree.cc index 77f57e0f9ac..2adf59b22d4 100644 --- a/gcc/cp/tree.cc +++ b/gcc/cp/tree.cc @@ -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_non_owning_attribute (tree *, tree, tree, int, bool *); /* If REF is an lvalue, returns the kind of lvalue that REF is. Otherwise, returns clk_none. */ @@ -5096,6 +5097,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 }, + { "non_owning", 0, 0, false, true, false, false, + handle_non_owning_attribute, NULL }, }; const scoped_attribute_specs cxx_gnu_attribute_table = @@ -5385,6 +5388,23 @@ handle_contract_attribute (tree *ARG_UNUSED (node), tree ARG_UNUSED (name), return NULL_TREE; } +/* Handle a "non_owning" attribute; arguments as in + struct attribute_spec.handler. */ + +tree +handle_non_owning_attribute (tree *node, tree name, tree args, int, + bool *no_add_attrs) +{ + 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. */ diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi index 18c485c11b0..09e68cc8759 100644 --- a/gcc/doc/extend.texi +++ b/gcc/doc/extend.texi @@ -29299,6 +29299,21 @@ Some_Class B __attribute__ ((init_priority (543))); Note that the particular values of @var{priority} do not matter; only their relative ordering. +@cindex @code{non_owning} type attribute +@item non_owning + +This attribute can be applied on a class type, function, or member +function and indicates that it does not own its associated data. For +example, classes like @code{std::span} or @code{std::reference_wrapper} +are considered non-owning. + +@smallexample +class [[gnu::non_owning]] S @{ @dots{} @}; +@end smallexample + +Currently, the only effect this attribute has is to suppress the +@option{-Wdangling-reference} diagnostic. + @cindex @code{warn_unused} type attribute @item warn_unused diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi index 6ec56493e59..9986c1add39 100644 --- a/gcc/doc/invoke.texi +++ b/gcc/doc/invoke.texi @@ -3906,6 +3906,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 @@ -3916,6 +3919,24 @@ where @code{std::minmax} returns @code{std::pair<const int&, const int&>}, and both references dangle after the end of the full expression that contains the call to @code{std::minmax}. +The warning can be disabled by using the @code{gnu::non_owning} attribute, +which can be applied on the enclosing class type (in which case it disables +the warning for all its member functions), member function, or a regular +function. For example: + +@smallexample +class [[gnu::non_owning]] A @{ + int *p; + int &foo() @{ return *p; @} +@}; + +[[gnu::non_owning]] const int & +foo (const int &i) +@{ + @dots{} +@} +@end smallexample + This warning is enabled by @option{-Wall}. @opindex Wdelete-non-virtual-dtor diff --git a/gcc/testsuite/g++.dg/ext/attr-non-owning1.C b/gcc/testsuite/g++.dg/ext/attr-non-owning1.C new file mode 100644 index 00000000000..000f7f4019d --- /dev/null +++ b/gcc/testsuite/g++.dg/ext/attr-non-owning1.C @@ -0,0 +1,38 @@ +// { dg-do compile { target c++11 } } +// { dg-options "-Wdangling-reference" } + +int g = 42; + +struct [[gnu::non_owning]] A { + int *i; + int &foo() { return *i; } +}; + +struct A2 { + int *i; + [[gnu::non_owning]] int &foo() { return *i; } + [[gnu::non_owning]] static int &bar (const int &) { return *&g; } +}; + +union [[gnu::non_owning]] U { }; + +A a() { return A{&g}; } +A2 a2() { return A2{&g}; } + +class X { }; +const X x1; +const X x2; + +[[gnu::non_owning]] 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" } +} diff --git a/gcc/testsuite/g++.dg/ext/attr-non-owning2.C b/gcc/testsuite/g++.dg/ext/attr-non-owning2.C new file mode 100644 index 00000000000..6f1d649736a --- /dev/null +++ b/gcc/testsuite/g++.dg/ext/attr-non-owning2.C @@ -0,0 +1,28 @@ +// { dg-do compile { target c++11 } } +// Negative tests. + +struct [[non_owning]] A { // { dg-warning "ignored" } + [[non_owning]] int &foo (int &); // { dg-warning "ignored" } +}; + +[[non_owning]] int &bar (int &); // { dg-warning "ignored" } + +[[gnu::non_owning]] int i; // { dg-warning "ignored" } +[[gnu::non_owning]] double d; // { dg-warning "ignored" } +[[gnu::non_owning]] typedef int T; // { dg-warning "ignored" } + +[[gnu::non_owning()]] int &fn1 (int &); // { dg-error "attribute does not take any arguments" } +[[gnu::non_owning("a")]] int &fn2 (int &); // { dg-error "attribute does not take any arguments" } + +enum [[gnu::non_owning]] E { // { dg-warning "ignored" } + X [[gnu::non_owning]] // { dg-warning "ignored" } +}; + +[[gnu::non_owning]]; // { dg-warning "ignored" } + +void +g () +{ + goto L; +[[gnu::non_owning]] L:; // { dg-warning "ignored" } +} diff --git a/gcc/testsuite/g++.dg/ext/attr-non-owning3.C b/gcc/testsuite/g++.dg/ext/attr-non-owning3.C new file mode 100644 index 00000000000..81c6491643b --- /dev/null +++ b/gcc/testsuite/g++.dg/ext/attr-non-owning3.C @@ -0,0 +1,24 @@ +// { dg-do compile { target c++11 } } +// { dg-options "-Wdangling-reference" } + +template <typename T> +struct [[gnu::non_owning]] 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; +} diff --git a/gcc/testsuite/g++.dg/ext/attr-non-owning4.C b/gcc/testsuite/g++.dg/ext/attr-non-owning4.C new file mode 100644 index 00000000000..c50382c3e8f --- /dev/null +++ b/gcc/testsuite/g++.dg/ext/attr-non-owning4.C @@ -0,0 +1,14 @@ +// { dg-do compile { target c++11 } } + +#if !__has_attribute(non_owning) +#error unsupported +#endif + +#ifdef __has_cpp_attribute +# if !__has_cpp_attribute(non_owning) +# error non_owning +# endif +#endif + +struct [[gnu::non_owning]] S { }; +static_assert (__builtin_has_attribute (S, non_owning), ""); diff --git a/gcc/testsuite/g++.dg/ext/attr-non-owning5.C b/gcc/testsuite/g++.dg/ext/attr-non-owning5.C new file mode 100644 index 00000000000..6122d583ccc --- /dev/null +++ b/gcc/testsuite/g++.dg/ext/attr-non-owning5.C @@ -0,0 +1,29 @@ +// PR c++/110358 +// { dg-do compile { target c++20 } } +// { dg-options "-Wdangling-reference" } + +template <typename T> +struct Span { + T* data_; + int len_; + + [[nodiscard]] constexpr auto operator[](int n) const noexcept -> T& { return data_[n]; } +}; + +template <> +struct [[gnu::non_owning]] Span<int> { + int* data_; + int len_; + + [[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" } +} base-commit: fd620bd3351c6b9821c299035ed17e655d7954b5 -- 2.43.0 ^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH] c++: implement [[gnu::non_owning]] [PR110358] 2024-01-26 1:37 [PATCH] c++: implement [[gnu::non_owning]] [PR110358] Marek Polacek @ 2024-01-26 2:34 ` Marek Polacek 2024-01-26 21:04 ` Jason Merrill 1 sibling, 0 replies; 15+ messages in thread From: Marek Polacek @ 2024-01-26 2:34 UTC (permalink / raw) To: GCC Patches, Jason Merrill On Thu, Jan 25, 2024 at 08:37:36PM -0500, Marek Polacek wrote: > +/* Handle a "non_owning" attribute; arguments as in > + struct attribute_spec.handler. */ > + > +tree > +handle_non_owning_attribute (tree *node, tree name, tree args, int, > + bool *no_add_attrs) I overlooked error: unused parameter 'args'. Fixed. Marek ^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH] c++: implement [[gnu::non_owning]] [PR110358] 2024-01-26 1:37 [PATCH] c++: implement [[gnu::non_owning]] [PR110358] Marek Polacek 2024-01-26 2:34 ` Marek Polacek @ 2024-01-26 21:04 ` Jason Merrill 2024-02-22 0:35 ` [PATCH v2] " Marek Polacek 1 sibling, 1 reply; 15+ messages in thread From: Jason Merrill @ 2024-01-26 21:04 UTC (permalink / raw) To: Marek Polacek, GCC Patches On 1/25/24 20:37, Marek Polacek wrote: > Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk? > > -- >8 -- > 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. Such > an attribute should not be tied to this particular warning though. [*] > > The warning bogusly triggers for classes that are like std::span, > std::reference_wrapper, and std::ranges::ref_view. The common property > seems to be that these classes are only wrappers around some data. So > I chose the name non_owning, but I'm not attached to it. I hope that > in the future the attribute can be used for something other than this > diagnostic. You decided not to pursue Barry's request for a bool argument to the attribute? Might it be more useful for the attribute to make reference_like_class_p return true, so that we still warn about a temporary of another type passing through it? Jason ^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH v2] c++: implement [[gnu::non_owning]] [PR110358] 2024-01-26 21:04 ` Jason Merrill @ 2024-02-22 0:35 ` Marek Polacek 2024-02-28 23:03 ` Jason Merrill 0 siblings, 1 reply; 15+ messages in thread From: Marek Polacek @ 2024-02-22 0:35 UTC (permalink / raw) To: Jason Merrill; +Cc: GCC Patches On Fri, Jan 26, 2024 at 04:04:35PM -0500, Jason Merrill wrote: > On 1/25/24 20:37, Marek Polacek wrote: > > Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk? > > > > -- >8 -- > > 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. Such > > an attribute should not be tied to this particular warning though. [*] > > > > The warning bogusly triggers for classes that are like std::span, > > std::reference_wrapper, and std::ranges::ref_view. The common property > > seems to be that these classes are only wrappers around some data. So > > I chose the name non_owning, but I'm not attached to it. I hope that > > in the future the attribute can be used for something other than this > > diagnostic. > > You decided not to pursue Barry's request for a bool argument to the > attribute? At first I thought it'd be an unnecessary complication but it was actually pretty easy. Better to accept the optional argument from the get-go otherwise people would have to add > GCC 14 checks. > Might it be more useful for the attribute to make reference_like_class_p > return true, so that we still warn about a temporary of another type passing > through it? Good point. Fixed. Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk? -- >8 -- 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. Such an attribute should not be tied to this particular warning though. The warning bogusly triggers for classes that are like std::span, std::reference_wrapper, and std::ranges::ref_view. The common property seems to be that these classes are only wrappers around some data. So I chose the name non_owning, but I'm not attached to it. I hope that in the future the attribute can be used for something other than this diagnostic. This attribute takes an optional bool argument to support cases like: template <typename T> struct [[gnu::non_owning(std::is_reference_v<T>)]] S { // ... }; PR c++/110358 PR c++/109642 gcc/cp/ChangeLog: * call.cc (non_owning_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::non_owning. * tree.cc (cxx_gnu_attributes): Add gnu::non_owning. (handle_non_owning_attribute): New. gcc/ChangeLog: * doc/extend.texi: Document gnu::non_owning. * doc/invoke.texi: Mention that gnu::non_owning disables -Wdangling-reference. gcc/testsuite/ChangeLog: * g++.dg/ext/attr-non-owning1.C: New test. * g++.dg/ext/attr-non-owning2.C: New test. * g++.dg/ext/attr-non-owning3.C: New test. * g++.dg/ext/attr-non-owning4.C: New test. * g++.dg/ext/attr-non-owning5.C: New test. * g++.dg/ext/attr-non-owning6.C: New test. * g++.dg/ext/attr-non-owning7.C: New test. * g++.dg/ext/attr-non-owning8.C: New test. * g++.dg/ext/attr-non-owning9.C: New test. --- gcc/cp/call.cc | 38 ++++++++++-- gcc/cp/tree.cc | 26 +++++++++ gcc/doc/extend.texi | 25 ++++++++ gcc/doc/invoke.texi | 21 +++++++ gcc/testsuite/g++.dg/ext/attr-non-owning1.C | 38 ++++++++++++ gcc/testsuite/g++.dg/ext/attr-non-owning2.C | 29 +++++++++ gcc/testsuite/g++.dg/ext/attr-non-owning3.C | 24 ++++++++ gcc/testsuite/g++.dg/ext/attr-non-owning4.C | 14 +++++ gcc/testsuite/g++.dg/ext/attr-non-owning5.C | 31 ++++++++++ gcc/testsuite/g++.dg/ext/attr-non-owning6.C | 65 +++++++++++++++++++++ gcc/testsuite/g++.dg/ext/attr-non-owning7.C | 31 ++++++++++ gcc/testsuite/g++.dg/ext/attr-non-owning8.C | 30 ++++++++++ gcc/testsuite/g++.dg/ext/attr-non-owning9.C | 25 ++++++++ 13 files changed, 391 insertions(+), 6 deletions(-) create mode 100644 gcc/testsuite/g++.dg/ext/attr-non-owning1.C create mode 100644 gcc/testsuite/g++.dg/ext/attr-non-owning2.C create mode 100644 gcc/testsuite/g++.dg/ext/attr-non-owning3.C create mode 100644 gcc/testsuite/g++.dg/ext/attr-non-owning4.C create mode 100644 gcc/testsuite/g++.dg/ext/attr-non-owning5.C create mode 100644 gcc/testsuite/g++.dg/ext/attr-non-owning6.C create mode 100644 gcc/testsuite/g++.dg/ext/attr-non-owning7.C create mode 100644 gcc/testsuite/g++.dg/ext/attr-non-owning8.C create mode 100644 gcc/testsuite/g++.dg/ext/attr-non-owning9.C diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc index 1dac1470d3b..e4bf9c963bd 100644 --- a/gcc/cp/call.cc +++ b/gcc/cp/call.cc @@ -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 + non-owning. */ + +static bool +non_owning_p (tree t) +{ + t = lookup_attribute ("non_owning", 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 (non_owning_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)) + || non_owning_p (TREE_TYPE (fndecl))) return NULL_TREE; tree rettype = TREE_TYPE (TREE_TYPE (fndecl)); diff --git a/gcc/cp/tree.cc b/gcc/cp/tree.cc index ad312710f68..017da7e294a 100644 --- a/gcc/cp/tree.cc +++ b/gcc/cp/tree.cc @@ -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_non_owning_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 }, + { "non_owning", 0, 1, false, true, false, false, + handle_non_owning_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 "non_owning" attribute; arguments as in + struct attribute_spec.handler. */ + +tree +handle_non_owning_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. */ diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi index 2135dfde9c8..9132add8267 100644 --- a/gcc/doc/extend.texi +++ b/gcc/doc/extend.texi @@ -29320,6 +29320,31 @@ Some_Class B __attribute__ ((init_priority (543))); Note that the particular values of @var{priority} do not matter; only their relative ordering. +@cindex @code{non_owning} type attribute +@item non_owning + +This attribute can be applied on a class type, function, or member +function and indicates that it does not own its associated data. For +example, classes like @code{std::span} or @code{std::reference_wrapper} +are considered non-owning. + +@smallexample +class [[gnu::non_owning]] S @{ @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::non_owning(std::is_reference_v<T>)]] S @{ + @dots{} +@}; +@end smallexample + +Currently, the only effect this attribute has is to suppress the +@option{-Wdangling-reference} diagnostic. + @cindex @code{warn_unused} type attribute @item warn_unused diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi index b4e4ee9fb81..0df2e2ded34 100644 --- a/gcc/doc/invoke.texi +++ b/gcc/doc/invoke.texi @@ -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,24 @@ 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::non_owning} attribute, +which can be applied on the enclosing class type (in which case it disables +the warning for all its member functions), member function, or a regular +function. For example: + +@smallexample +class [[gnu::non_owning]] A @{ + int *p; + int &foo() @{ return *p; @} +@}; + +[[gnu::non_owning]] const int & +foo (const int &i) +@{ + @dots{} +@} +@end smallexample + This warning is enabled by @option{-Wall}. @opindex Wdelete-non-virtual-dtor diff --git a/gcc/testsuite/g++.dg/ext/attr-non-owning1.C b/gcc/testsuite/g++.dg/ext/attr-non-owning1.C new file mode 100644 index 00000000000..000f7f4019d --- /dev/null +++ b/gcc/testsuite/g++.dg/ext/attr-non-owning1.C @@ -0,0 +1,38 @@ +// { dg-do compile { target c++11 } } +// { dg-options "-Wdangling-reference" } + +int g = 42; + +struct [[gnu::non_owning]] A { + int *i; + int &foo() { return *i; } +}; + +struct A2 { + int *i; + [[gnu::non_owning]] int &foo() { return *i; } + [[gnu::non_owning]] static int &bar (const int &) { return *&g; } +}; + +union [[gnu::non_owning]] U { }; + +A a() { return A{&g}; } +A2 a2() { return A2{&g}; } + +class X { }; +const X x1; +const X x2; + +[[gnu::non_owning]] 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" } +} diff --git a/gcc/testsuite/g++.dg/ext/attr-non-owning2.C b/gcc/testsuite/g++.dg/ext/attr-non-owning2.C new file mode 100644 index 00000000000..4d2037e8b78 --- /dev/null +++ b/gcc/testsuite/g++.dg/ext/attr-non-owning2.C @@ -0,0 +1,29 @@ +// { dg-do compile { target c++11 } } +// Negative tests. + +struct [[non_owning]] A { // { dg-warning "ignored" } + [[non_owning]] int &foo (int &); // { dg-warning "ignored" } +}; + +[[non_owning]] int &bar (int &); // { dg-warning "ignored" } + +[[gnu::non_owning]] int i; // { dg-warning "ignored" } +[[gnu::non_owning]] double d; // { dg-warning "ignored" } +[[gnu::non_owning]] typedef int T; // { dg-warning "ignored" } + +[[gnu::non_owning()]] int &fn1 (int &); // { dg-error "parentheses" } +[[gnu::non_owning("a")]] int &fn2 (int &); // { dg-error "must be an expression" } +[[gnu::non_owning(true, true)]] int &fn3 (int &); // { dg-error "wrong number of arguments" } + +enum [[gnu::non_owning]] E { // { dg-warning "ignored" } + X [[gnu::non_owning]] // { dg-warning "ignored" } +}; + +[[gnu::non_owning]]; // { dg-warning "ignored" } + +void +g () +{ + goto L; +[[gnu::non_owning]] L:; // { dg-warning "ignored" } +} diff --git a/gcc/testsuite/g++.dg/ext/attr-non-owning3.C b/gcc/testsuite/g++.dg/ext/attr-non-owning3.C new file mode 100644 index 00000000000..81c6491643b --- /dev/null +++ b/gcc/testsuite/g++.dg/ext/attr-non-owning3.C @@ -0,0 +1,24 @@ +// { dg-do compile { target c++11 } } +// { dg-options "-Wdangling-reference" } + +template <typename T> +struct [[gnu::non_owning]] 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; +} diff --git a/gcc/testsuite/g++.dg/ext/attr-non-owning4.C b/gcc/testsuite/g++.dg/ext/attr-non-owning4.C new file mode 100644 index 00000000000..c50382c3e8f --- /dev/null +++ b/gcc/testsuite/g++.dg/ext/attr-non-owning4.C @@ -0,0 +1,14 @@ +// { dg-do compile { target c++11 } } + +#if !__has_attribute(non_owning) +#error unsupported +#endif + +#ifdef __has_cpp_attribute +# if !__has_cpp_attribute(non_owning) +# error non_owning +# endif +#endif + +struct [[gnu::non_owning]] S { }; +static_assert (__builtin_has_attribute (S, non_owning), ""); diff --git a/gcc/testsuite/g++.dg/ext/attr-non-owning5.C b/gcc/testsuite/g++.dg/ext/attr-non-owning5.C new file mode 100644 index 00000000000..d297bb615fe --- /dev/null +++ b/gcc/testsuite/g++.dg/ext/attr-non-owning5.C @@ -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::non_owning]] 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" } +} diff --git a/gcc/testsuite/g++.dg/ext/attr-non-owning6.C b/gcc/testsuite/g++.dg/ext/attr-non-owning6.C new file mode 100644 index 00000000000..10ce1842852 --- /dev/null +++ b/gcc/testsuite/g++.dg/ext/attr-non-owning6.C @@ -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::non_owning(T::value)]] +const X& get (const int& i) +{ + return i == 0 ? x1 : x2; +} + +template<bool B = true> +[[gnu::non_owning(B)]] +const X& foo (const int& i) +{ + return i == 0 ? x1 : x2; +} + +[[gnu::non_owning(val ())]] +const X& bar (const int& i) +{ + return i == 0 ? x1 : x2; +} + +[[gnu::non_owning(!val ())]] +const X& baz (const int& i) +{ + return i == 0 ? x1 : x2; +} + +template <typename T> +struct [[gnu::non_owning(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" } +} diff --git a/gcc/testsuite/g++.dg/ext/attr-non-owning7.C b/gcc/testsuite/g++.dg/ext/attr-non-owning7.C new file mode 100644 index 00000000000..bba69871918 --- /dev/null +++ b/gcc/testsuite/g++.dg/ext/attr-non-owning7.C @@ -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::non_owning(N)]] const X& get(const int& i); // { dg-error "parameter packs not expanded" } + +template<typename T> +[[gnu::non_owning(T::x)]] // { dg-error "member" } +const X& foo(const int& i); + +bool val () { return true; } + +[[gnu::non_owning(val ())]] // { dg-error "call" } +const X& bar (const int& i); + +[[gnu::non_owning(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" } +} + diff --git a/gcc/testsuite/g++.dg/ext/attr-non-owning8.C b/gcc/testsuite/g++.dg/ext/attr-non-owning8.C new file mode 100644 index 00000000000..1bfed4ed33c --- /dev/null +++ b/gcc/testsuite/g++.dg/ext/attr-non-owning8.C @@ -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::non_owning(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::non_owning(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" } +} + diff --git a/gcc/testsuite/g++.dg/ext/attr-non-owning9.C b/gcc/testsuite/g++.dg/ext/attr-non-owning9.C new file mode 100644 index 00000000000..6f6a72736ce --- /dev/null +++ b/gcc/testsuite/g++.dg/ext/attr-non-owning9.C @@ -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::non_owning(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" } +} + base-commit: 98004ca00e4bf7a513cf3de65d3c3d9ad373872e -- 2.43.2 ^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v2] c++: implement [[gnu::non_owning]] [PR110358] 2024-02-22 0:35 ` [PATCH v2] " Marek Polacek @ 2024-02-28 23:03 ` Jason Merrill 2024-03-01 0:12 ` [PATCH v3] " Marek Polacek 0 siblings, 1 reply; 15+ messages in thread From: Jason Merrill @ 2024-02-28 23:03 UTC (permalink / raw) To: Marek Polacek; +Cc: GCC Patches On 2/21/24 19:35, Marek Polacek wrote: > On Fri, Jan 26, 2024 at 04:04:35PM -0500, Jason Merrill wrote: >> On 1/25/24 20:37, Marek Polacek wrote: >>> Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk? >>> >>> -- >8 -- >>> 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. Such >>> an attribute should not be tied to this particular warning though. [*] >>> >>> The warning bogusly triggers for classes that are like std::span, >>> std::reference_wrapper, and std::ranges::ref_view. The common property >>> seems to be that these classes are only wrappers around some data. So >>> I chose the name non_owning, but I'm not attached to it. I hope that >>> in the future the attribute can be used for something other than this >>> diagnostic. >> >> You decided not to pursue Barry's request for a bool argument to the >> attribute? > > At first I thought it'd be an unnecessary complication but it was actually > pretty easy. Better to accept the optional argument from the get-go > otherwise people would have to add > GCC 14 checks. > >> Might it be more useful for the attribute to make reference_like_class_p >> return true, so that we still warn about a temporary of another type passing >> through it? > > Good point. Fixed. > > Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk? > > -- >8 -- > 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. Such > an attribute should not be tied to this particular warning though. > > The warning bogusly triggers for classes that are like std::span, > std::reference_wrapper, and std::ranges::ref_view. The common property > seems to be that these classes are only wrappers around some data. So > I chose the name non_owning, but I'm not attached to it. I hope that > in the future the attribute can be used for something other than this > diagnostic. > > This attribute takes an optional bool argument to support cases like: > > template <typename T> > struct [[gnu::non_owning(std::is_reference_v<T>)]] S { > // ... > }; > > PR c++/110358 > PR c++/109642 > > gcc/cp/ChangeLog: > > * call.cc (non_owning_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::non_owning. > * tree.cc (cxx_gnu_attributes): Add gnu::non_owning. > (handle_non_owning_attribute): New. > > gcc/ChangeLog: > > * doc/extend.texi: Document gnu::non_owning. > * doc/invoke.texi: Mention that gnu::non_owning disables > -Wdangling-reference. > > gcc/testsuite/ChangeLog: > > * g++.dg/ext/attr-non-owning1.C: New test. > * g++.dg/ext/attr-non-owning2.C: New test. > * g++.dg/ext/attr-non-owning3.C: New test. > * g++.dg/ext/attr-non-owning4.C: New test. > * g++.dg/ext/attr-non-owning5.C: New test. > * g++.dg/ext/attr-non-owning6.C: New test. > * g++.dg/ext/attr-non-owning7.C: New test. > * g++.dg/ext/attr-non-owning8.C: New test. > * g++.dg/ext/attr-non-owning9.C: New test. > --- > gcc/cp/call.cc | 38 ++++++++++-- > gcc/cp/tree.cc | 26 +++++++++ > gcc/doc/extend.texi | 25 ++++++++ > gcc/doc/invoke.texi | 21 +++++++ > gcc/testsuite/g++.dg/ext/attr-non-owning1.C | 38 ++++++++++++ > gcc/testsuite/g++.dg/ext/attr-non-owning2.C | 29 +++++++++ > gcc/testsuite/g++.dg/ext/attr-non-owning3.C | 24 ++++++++ > gcc/testsuite/g++.dg/ext/attr-non-owning4.C | 14 +++++ > gcc/testsuite/g++.dg/ext/attr-non-owning5.C | 31 ++++++++++ > gcc/testsuite/g++.dg/ext/attr-non-owning6.C | 65 +++++++++++++++++++++ > gcc/testsuite/g++.dg/ext/attr-non-owning7.C | 31 ++++++++++ > gcc/testsuite/g++.dg/ext/attr-non-owning8.C | 30 ++++++++++ > gcc/testsuite/g++.dg/ext/attr-non-owning9.C | 25 ++++++++ > 13 files changed, 391 insertions(+), 6 deletions(-) > create mode 100644 gcc/testsuite/g++.dg/ext/attr-non-owning1.C > create mode 100644 gcc/testsuite/g++.dg/ext/attr-non-owning2.C > create mode 100644 gcc/testsuite/g++.dg/ext/attr-non-owning3.C > create mode 100644 gcc/testsuite/g++.dg/ext/attr-non-owning4.C > create mode 100644 gcc/testsuite/g++.dg/ext/attr-non-owning5.C > create mode 100644 gcc/testsuite/g++.dg/ext/attr-non-owning6.C > create mode 100644 gcc/testsuite/g++.dg/ext/attr-non-owning7.C > create mode 100644 gcc/testsuite/g++.dg/ext/attr-non-owning8.C > create mode 100644 gcc/testsuite/g++.dg/ext/attr-non-owning9.C > > diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc > index 1dac1470d3b..e4bf9c963bd 100644 > --- a/gcc/cp/call.cc > +++ b/gcc/cp/call.cc > @@ -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 > + non-owning. */ > + > +static bool > +non_owning_p (tree t) > +{ > + t = lookup_attribute ("non_owning", 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 (non_owning_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)) > + || non_owning_p (TREE_TYPE (fndecl))) > return NULL_TREE; > > tree rettype = TREE_TYPE (TREE_TYPE (fndecl)); > diff --git a/gcc/cp/tree.cc b/gcc/cp/tree.cc > index ad312710f68..017da7e294a 100644 > --- a/gcc/cp/tree.cc > +++ b/gcc/cp/tree.cc > @@ -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_non_owning_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 }, > + { "non_owning", 0, 1, false, true, false, false, > + handle_non_owning_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 "non_owning" attribute; arguments as in > + struct attribute_spec.handler. */ > + > +tree > +handle_non_owning_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. */ > > diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi > index 2135dfde9c8..9132add8267 100644 > --- a/gcc/doc/extend.texi > +++ b/gcc/doc/extend.texi > @@ -29320,6 +29320,31 @@ Some_Class B __attribute__ ((init_priority (543))); > Note that the particular values of @var{priority} do not matter; only their > relative ordering. > > +@cindex @code{non_owning} type attribute > +@item non_owning > + > +This attribute can be applied on a class type, function, or member > +function and indicates that it does not own its associated data. For > +example, classes like @code{std::span} or @code{std::reference_wrapper} > +are considered non-owning. > + > +@smallexample > +class [[gnu::non_owning]] S @{ @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::non_owning(std::is_reference_v<T>)]] S @{ > + @dots{} > +@}; > +@end smallexample > + > +Currently, the only effect this attribute has is to suppress the > +@option{-Wdangling-reference} diagnostic. > + > @cindex @code{warn_unused} type attribute > @item warn_unused > > diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi > index b4e4ee9fb81..0df2e2ded34 100644 > --- a/gcc/doc/invoke.texi > +++ b/gcc/doc/invoke.texi > @@ -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,24 @@ 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::non_owning} attribute, > +which can be applied on the enclosing class type (in which case it disables > +the warning for all its member functions), member function, or a regular > +function. For example: Hmm, if we're also going to allow the attribute to be applied to a function, the name doesn't make so much sense. For a class, it says that the class refers to its initializer; for a function, it says that the function return value *doesn't* refer to its argument. If we want something that can apply to both classes and functions, we're probably back to an attribute that just suppresses the warning, with a different name. Or I guess we could have two attributes, but that seems like a lot. WDYT? Jason ^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH v3] c++: implement [[gnu::non_owning]] [PR110358] 2024-02-28 23:03 ` Jason Merrill @ 2024-03-01 0:12 ` Marek Polacek 2024-03-01 0:30 ` Jason Merrill 0 siblings, 1 reply; 15+ messages in thread From: Marek Polacek @ 2024-03-01 0:12 UTC (permalink / raw) To: Jason Merrill; +Cc: GCC Patches On Wed, Feb 28, 2024 at 06:03:54PM -0500, Jason Merrill wrote: > On 2/21/24 19:35, Marek Polacek wrote: > > On Fri, Jan 26, 2024 at 04:04:35PM -0500, Jason Merrill wrote: > > > On 1/25/24 20:37, Marek Polacek wrote: > > > > Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk? > > > > > > > > -- >8 -- > > > > 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. Such > > > > an attribute should not be tied to this particular warning though. [*] > > > > > > > > The warning bogusly triggers for classes that are like std::span, > > > > std::reference_wrapper, and std::ranges::ref_view. The common property > > > > seems to be that these classes are only wrappers around some data. So > > > > I chose the name non_owning, but I'm not attached to it. I hope that > > > > in the future the attribute can be used for something other than this > > > > diagnostic. > > > > > > You decided not to pursue Barry's request for a bool argument to the > > > attribute? > > > > At first I thought it'd be an unnecessary complication but it was actually > > pretty easy. Better to accept the optional argument from the get-go > > otherwise people would have to add > GCC 14 checks. > > > Might it be more useful for the attribute to make reference_like_class_p > > > return true, so that we still warn about a temporary of another type passing > > > through it? > > > > Good point. Fixed. > > > > Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk? > > > > -- >8 -- > > 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. Such > > an attribute should not be tied to this particular warning though. > > > > The warning bogusly triggers for classes that are like std::span, > > std::reference_wrapper, and std::ranges::ref_view. The common property > > seems to be that these classes are only wrappers around some data. So > > I chose the name non_owning, but I'm not attached to it. I hope that > > in the future the attribute can be used for something other than this > > diagnostic. > > > > This attribute takes an optional bool argument to support cases like: > > > > template <typename T> > > struct [[gnu::non_owning(std::is_reference_v<T>)]] S { > > // ... > > }; > > > > PR c++/110358 > > PR c++/109642 > > > > gcc/cp/ChangeLog: > > > > * call.cc (non_owning_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::non_owning. > > * tree.cc (cxx_gnu_attributes): Add gnu::non_owning. > > (handle_non_owning_attribute): New. > > > > gcc/ChangeLog: > > > > * doc/extend.texi: Document gnu::non_owning. > > * doc/invoke.texi: Mention that gnu::non_owning disables > > -Wdangling-reference. > > > > gcc/testsuite/ChangeLog: > > > > * g++.dg/ext/attr-non-owning1.C: New test. > > * g++.dg/ext/attr-non-owning2.C: New test. > > * g++.dg/ext/attr-non-owning3.C: New test. > > * g++.dg/ext/attr-non-owning4.C: New test. > > * g++.dg/ext/attr-non-owning5.C: New test. > > * g++.dg/ext/attr-non-owning6.C: New test. > > * g++.dg/ext/attr-non-owning7.C: New test. > > * g++.dg/ext/attr-non-owning8.C: New test. > > * g++.dg/ext/attr-non-owning9.C: New test. > > --- > > gcc/cp/call.cc | 38 ++++++++++-- > > gcc/cp/tree.cc | 26 +++++++++ > > gcc/doc/extend.texi | 25 ++++++++ > > gcc/doc/invoke.texi | 21 +++++++ > > gcc/testsuite/g++.dg/ext/attr-non-owning1.C | 38 ++++++++++++ > > gcc/testsuite/g++.dg/ext/attr-non-owning2.C | 29 +++++++++ > > gcc/testsuite/g++.dg/ext/attr-non-owning3.C | 24 ++++++++ > > gcc/testsuite/g++.dg/ext/attr-non-owning4.C | 14 +++++ > > gcc/testsuite/g++.dg/ext/attr-non-owning5.C | 31 ++++++++++ > > gcc/testsuite/g++.dg/ext/attr-non-owning6.C | 65 +++++++++++++++++++++ > > gcc/testsuite/g++.dg/ext/attr-non-owning7.C | 31 ++++++++++ > > gcc/testsuite/g++.dg/ext/attr-non-owning8.C | 30 ++++++++++ > > gcc/testsuite/g++.dg/ext/attr-non-owning9.C | 25 ++++++++ > > 13 files changed, 391 insertions(+), 6 deletions(-) > > create mode 100644 gcc/testsuite/g++.dg/ext/attr-non-owning1.C > > create mode 100644 gcc/testsuite/g++.dg/ext/attr-non-owning2.C > > create mode 100644 gcc/testsuite/g++.dg/ext/attr-non-owning3.C > > create mode 100644 gcc/testsuite/g++.dg/ext/attr-non-owning4.C > > create mode 100644 gcc/testsuite/g++.dg/ext/attr-non-owning5.C > > create mode 100644 gcc/testsuite/g++.dg/ext/attr-non-owning6.C > > create mode 100644 gcc/testsuite/g++.dg/ext/attr-non-owning7.C > > create mode 100644 gcc/testsuite/g++.dg/ext/attr-non-owning8.C > > create mode 100644 gcc/testsuite/g++.dg/ext/attr-non-owning9.C > > > > diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc > > index 1dac1470d3b..e4bf9c963bd 100644 > > --- a/gcc/cp/call.cc > > +++ b/gcc/cp/call.cc > > @@ -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 > > + non-owning. */ > > + > > +static bool > > +non_owning_p (tree t) > > +{ > > + t = lookup_attribute ("non_owning", 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 (non_owning_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)) > > + || non_owning_p (TREE_TYPE (fndecl))) > > return NULL_TREE; > > tree rettype = TREE_TYPE (TREE_TYPE (fndecl)); > > diff --git a/gcc/cp/tree.cc b/gcc/cp/tree.cc > > index ad312710f68..017da7e294a 100644 > > --- a/gcc/cp/tree.cc > > +++ b/gcc/cp/tree.cc > > @@ -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_non_owning_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 }, > > + { "non_owning", 0, 1, false, true, false, false, > > + handle_non_owning_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 "non_owning" attribute; arguments as in > > + struct attribute_spec.handler. */ > > + > > +tree > > +handle_non_owning_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. */ > > diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi > > index 2135dfde9c8..9132add8267 100644 > > --- a/gcc/doc/extend.texi > > +++ b/gcc/doc/extend.texi > > @@ -29320,6 +29320,31 @@ Some_Class B __attribute__ ((init_priority (543))); > > Note that the particular values of @var{priority} do not matter; only their > > relative ordering. > > +@cindex @code{non_owning} type attribute > > +@item non_owning > > + > > +This attribute can be applied on a class type, function, or member > > +function and indicates that it does not own its associated data. For > > +example, classes like @code{std::span} or @code{std::reference_wrapper} > > +are considered non-owning. > > + > > +@smallexample > > +class [[gnu::non_owning]] S @{ @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::non_owning(std::is_reference_v<T>)]] S @{ > > + @dots{} > > +@}; > > +@end smallexample > > + > > +Currently, the only effect this attribute has is to suppress the > > +@option{-Wdangling-reference} diagnostic. > > + > > @cindex @code{warn_unused} type attribute > > @item warn_unused > > diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi > > index b4e4ee9fb81..0df2e2ded34 100644 > > --- a/gcc/doc/invoke.texi > > +++ b/gcc/doc/invoke.texi > > @@ -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,24 @@ 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::non_owning} attribute, > > +which can be applied on the enclosing class type (in which case it disables > > +the warning for all its member functions), member function, or a regular > > +function. For example: > > Hmm, if we're also going to allow the attribute to be applied to a function, > the name doesn't make so much sense. For a class, it says that the class > refers to its initializer; for a function, it says that the function return > value *doesn't* refer to its argument. Yeah, that's a fair point; I guess "non_owning" would be too perplexing. > If we want something that can apply to both classes and functions, we're > probably back to an attribute that just suppresses the warning, with a > different name. > > Or I guess we could have two attributes, but that seems like a lot. > > WDYT? I think we don't want two separate attributes, and we do want that one attribute to apply to both fns and classes. We could implement something like [[gnu::no_warning("Wdangling-reference")]] [[gnu::no_warning("Wdangling-reference", bool)]] but first, that's a lot of typing, second, it would be confusing because it wouldn't work for any other warning. We already have [[unused]] and [[maybe_unused]] whose effect is to suppress a warning. It think our best bet is to do the most straightforward thing: [[gnu::no_dangling]], which this patch implements. I didn't call it no_dangling_reference in the hope that it can, some day, be also used for some -Wdangling-pointer purposes. Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk? -- >8 -- 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. --- gcc/cp/call.cc | 38 ++++++++++-- gcc/cp/tree.cc | 26 ++++++++ gcc/doc/extend.texi | 21 +++++++ gcc/doc/invoke.texi | 21 +++++++ gcc/testsuite/g++.dg/ext/attr-no-dangling1.C | 38 ++++++++++++ gcc/testsuite/g++.dg/ext/attr-no-dangling2.C | 29 +++++++++ gcc/testsuite/g++.dg/ext/attr-no-dangling3.C | 24 ++++++++ gcc/testsuite/g++.dg/ext/attr-no-dangling4.C | 14 +++++ gcc/testsuite/g++.dg/ext/attr-no-dangling5.C | 31 ++++++++++ gcc/testsuite/g++.dg/ext/attr-no-dangling6.C | 65 ++++++++++++++++++++ gcc/testsuite/g++.dg/ext/attr-no-dangling7.C | 31 ++++++++++ gcc/testsuite/g++.dg/ext/attr-no-dangling8.C | 30 +++++++++ gcc/testsuite/g++.dg/ext/attr-no-dangling9.C | 25 ++++++++ 13 files changed, 387 insertions(+), 6 deletions(-) create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling1.C create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling2.C create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling3.C create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling4.C create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling5.C create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling6.C create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling7.C create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling8.C create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling9.C diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc index c40ef2e3028..9e4c8073600 100644 --- a/gcc/cp/call.cc +++ b/gcc/cp/call.cc @@ -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)); diff --git a/gcc/cp/tree.cc b/gcc/cp/tree.cc index ad312710f68..e75be9a4e66 100644 --- a/gcc/cp/tree.cc +++ b/gcc/cp/tree.cc @@ -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. */ diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi index efd78014d1a..571655bf39a 100644 --- a/gcc/doc/extend.texi +++ b/gcc/doc/extend.texi @@ -29317,6 +29317,27 @@ 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 +@item no_dangling + +This attribute can be applied on a class type, function, or member +function. Entities marked with this attribute will have the +@option{-Wdangling-reference} diagnostic suppressed. + +@smallexample +class [[gnu::no_dangling]] S @{ @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 + @cindex @code{warn_unused} type attribute @item warn_unused diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi index 7862c751801..9022a413dd4 100644 --- a/gcc/doc/invoke.texi +++ b/gcc/doc/invoke.texi @@ -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,24 @@ 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, +which can be applied on the enclosing class type (in which case it disables +the warning for all its member functions), member function, or a regular +function. For example: + +@smallexample +class [[gnu::no_dangling]] A @{ + int *p; + int &foo() @{ return *p; @} +@}; + +[[gnu::no_dangling]] const int & +foo (const int &i) +@{ + @dots{} +@} +@end smallexample + This warning is enabled by @option{-Wall}. @opindex Wdelete-non-virtual-dtor diff --git a/gcc/testsuite/g++.dg/ext/attr-no-dangling1.C b/gcc/testsuite/g++.dg/ext/attr-no-dangling1.C new file mode 100644 index 00000000000..02eabbc5003 --- /dev/null +++ b/gcc/testsuite/g++.dg/ext/attr-no-dangling1.C @@ -0,0 +1,38 @@ +// { dg-do compile { target c++11 } } +// { dg-options "-Wdangling-reference" } + +int g = 42; + +struct [[gnu::no_dangling]] A { + int *i; + int &foo() { return *i; } +}; + +struct 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" } +} diff --git a/gcc/testsuite/g++.dg/ext/attr-no-dangling2.C b/gcc/testsuite/g++.dg/ext/attr-no-dangling2.C new file mode 100644 index 00000000000..4cdc97ea7c4 --- /dev/null +++ b/gcc/testsuite/g++.dg/ext/attr-no-dangling2.C @@ -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" } +} diff --git a/gcc/testsuite/g++.dg/ext/attr-no-dangling3.C b/gcc/testsuite/g++.dg/ext/attr-no-dangling3.C new file mode 100644 index 00000000000..764b104fd3c --- /dev/null +++ b/gcc/testsuite/g++.dg/ext/attr-no-dangling3.C @@ -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; +} diff --git a/gcc/testsuite/g++.dg/ext/attr-no-dangling4.C b/gcc/testsuite/g++.dg/ext/attr-no-dangling4.C new file mode 100644 index 00000000000..e910723d985 --- /dev/null +++ b/gcc/testsuite/g++.dg/ext/attr-no-dangling4.C @@ -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), ""); diff --git a/gcc/testsuite/g++.dg/ext/attr-no-dangling5.C b/gcc/testsuite/g++.dg/ext/attr-no-dangling5.C new file mode 100644 index 00000000000..ec5075482c4 --- /dev/null +++ b/gcc/testsuite/g++.dg/ext/attr-no-dangling5.C @@ -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" } +} diff --git a/gcc/testsuite/g++.dg/ext/attr-no-dangling6.C b/gcc/testsuite/g++.dg/ext/attr-no-dangling6.C new file mode 100644 index 00000000000..235a5fd86c5 --- /dev/null +++ b/gcc/testsuite/g++.dg/ext/attr-no-dangling6.C @@ -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" } +} diff --git a/gcc/testsuite/g++.dg/ext/attr-no-dangling7.C b/gcc/testsuite/g++.dg/ext/attr-no-dangling7.C new file mode 100644 index 00000000000..3c392ed409f --- /dev/null +++ b/gcc/testsuite/g++.dg/ext/attr-no-dangling7.C @@ -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" } +} + diff --git a/gcc/testsuite/g++.dg/ext/attr-no-dangling8.C b/gcc/testsuite/g++.dg/ext/attr-no-dangling8.C new file mode 100644 index 00000000000..8208d751a4b --- /dev/null +++ b/gcc/testsuite/g++.dg/ext/attr-no-dangling8.C @@ -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" } +} + diff --git a/gcc/testsuite/g++.dg/ext/attr-no-dangling9.C b/gcc/testsuite/g++.dg/ext/attr-no-dangling9.C new file mode 100644 index 00000000000..65b4f7145a9 --- /dev/null +++ b/gcc/testsuite/g++.dg/ext/attr-no-dangling9.C @@ -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" } +} + base-commit: cda3836161834c5f21f264885891fe4d0ce90da1 -- 2.43.2 ^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v3] c++: implement [[gnu::non_owning]] [PR110358] 2024-03-01 0:12 ` [PATCH v3] " Marek Polacek @ 2024-03-01 0:30 ` Jason Merrill 2024-03-01 17:39 ` [PATCH v4] " Marek Polacek 0 siblings, 1 reply; 15+ messages in thread From: Jason Merrill @ 2024-03-01 0:30 UTC (permalink / raw) To: Marek Polacek; +Cc: GCC Patches On 2/29/24 19:12, Marek Polacek wrote: > On Wed, Feb 28, 2024 at 06:03:54PM -0500, Jason Merrill wrote: > >> Hmm, if we're also going to allow the attribute to be applied to a function, >> the name doesn't make so much sense. For a class, it says that the class >> refers to its initializer; for a function, it says that the function return >> value *doesn't* refer to its argument. > > Yeah, that's a fair point; I guess "non_owning" would be too perplexing. > >> If we want something that can apply to both classes and functions, we're >> probably back to an attribute that just suppresses the warning, with a >> different name. >> >> Or I guess we could have two attributes, but that seems like a lot. >> >> WDYT? > > I think we don't want two separate attributes, and we do want that one > attribute to apply to both fns and classes. We could implement something > like > > [[gnu::no_warning("Wdangling-reference")]] > [[gnu::no_warning("Wdangling-reference", bool)]] > > but first, that's a lot of typing, second, it would be confusing because > it wouldn't work for any other warning. We already have [[unused]] and > [[maybe_unused]] whose effect is to suppress a warning. It think our > best bet is to do the most straightforward thing: [[gnu::no_dangling]], > which this patch implements. I didn't call it no_dangling_reference in > the hope that it can, some day, be also used for some -Wdangling-pointer > purposes. > > > Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk? > > -- >8 -- > 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. > --- > gcc/cp/call.cc | 38 ++++++++++-- > gcc/cp/tree.cc | 26 ++++++++ > gcc/doc/extend.texi | 21 +++++++ > gcc/doc/invoke.texi | 21 +++++++ > gcc/testsuite/g++.dg/ext/attr-no-dangling1.C | 38 ++++++++++++ > gcc/testsuite/g++.dg/ext/attr-no-dangling2.C | 29 +++++++++ > gcc/testsuite/g++.dg/ext/attr-no-dangling3.C | 24 ++++++++ > gcc/testsuite/g++.dg/ext/attr-no-dangling4.C | 14 +++++ > gcc/testsuite/g++.dg/ext/attr-no-dangling5.C | 31 ++++++++++ > gcc/testsuite/g++.dg/ext/attr-no-dangling6.C | 65 ++++++++++++++++++++ > gcc/testsuite/g++.dg/ext/attr-no-dangling7.C | 31 ++++++++++ > gcc/testsuite/g++.dg/ext/attr-no-dangling8.C | 30 +++++++++ > gcc/testsuite/g++.dg/ext/attr-no-dangling9.C | 25 ++++++++ > 13 files changed, 387 insertions(+), 6 deletions(-) > create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling1.C > create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling2.C > create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling3.C > create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling4.C > create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling5.C > create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling6.C > create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling7.C > create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling8.C > create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling9.C > > diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc > index c40ef2e3028..9e4c8073600 100644 > --- a/gcc/cp/call.cc > +++ b/gcc/cp/call.cc > @@ -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)); > diff --git a/gcc/cp/tree.cc b/gcc/cp/tree.cc > index ad312710f68..e75be9a4e66 100644 > --- a/gcc/cp/tree.cc > +++ b/gcc/cp/tree.cc > @@ -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. */ > > diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi > index efd78014d1a..571655bf39a 100644 > --- a/gcc/doc/extend.texi > +++ b/gcc/doc/extend.texi > @@ -29317,6 +29317,27 @@ 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 > +@item no_dangling > + > +This attribute can be applied on a class type, function, or member > +function. Entities marked with this attribute will have the > +@option{-Wdangling-reference} diagnostic suppressed. > + > +@smallexample > +class [[gnu::no_dangling]] S @{ @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 > > @cindex @code{warn_unused} type attribute > @item warn_unused > > diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi > index 7862c751801..9022a413dd4 100644 > --- a/gcc/doc/invoke.texi > +++ b/gcc/doc/invoke.texi > @@ -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,24 @@ 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, Can this be an xref, and put all the documentation for the usage and effect of the attribute in the attribute section? > +which can be applied on the enclosing class type (in which case it disables > +the warning for all its member functions), I think this misrepresents the effect of putting the attribute on the class. Rather, it means that we won't warn about a dangling reference to the class. > member function, or a regular > +function. For example: Jason ^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH v4] c++: implement [[gnu::non_owning]] [PR110358] 2024-03-01 0:30 ` Jason Merrill @ 2024-03-01 17:39 ` Marek Polacek 2024-03-01 18:19 ` Jason Merrill 0 siblings, 1 reply; 15+ messages in thread From: Marek Polacek @ 2024-03-01 17:39 UTC (permalink / raw) To: Jason Merrill; +Cc: GCC Patches On Thu, Feb 29, 2024 at 07:30:02PM -0500, Jason Merrill wrote: > On 2/29/24 19:12, Marek Polacek wrote: > > On Wed, Feb 28, 2024 at 06:03:54PM -0500, Jason Merrill wrote: > > > > > Hmm, if we're also going to allow the attribute to be applied to a function, > > > the name doesn't make so much sense. For a class, it says that the class > > > refers to its initializer; for a function, it says that the function return > > > value *doesn't* refer to its argument. > > > > Yeah, that's a fair point; I guess "non_owning" would be too perplexing. > > > > > If we want something that can apply to both classes and functions, we're > > > probably back to an attribute that just suppresses the warning, with a > > > different name. > > > > > > Or I guess we could have two attributes, but that seems like a lot. > > > > > > WDYT? > > > > I think we don't want two separate attributes, and we do want that one > > attribute to apply to both fns and classes. We could implement something > > like > > > > [[gnu::no_warning("Wdangling-reference")]] > > [[gnu::no_warning("Wdangling-reference", bool)]] > > > > but first, that's a lot of typing, second, it would be confusing because > > it wouldn't work for any other warning. We already have [[unused]] and > > [[maybe_unused]] whose effect is to suppress a warning. It think our > > best bet is to do the most straightforward thing: [[gnu::no_dangling]], > > which this patch implements. I didn't call it no_dangling_reference in > > the hope that it can, some day, be also used for some -Wdangling-pointer > > purposes. > > > > > > Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk? > > > > -- >8 -- > > 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. > > --- > > gcc/cp/call.cc | 38 ++++++++++-- > > gcc/cp/tree.cc | 26 ++++++++ > > gcc/doc/extend.texi | 21 +++++++ > > gcc/doc/invoke.texi | 21 +++++++ > > gcc/testsuite/g++.dg/ext/attr-no-dangling1.C | 38 ++++++++++++ > > gcc/testsuite/g++.dg/ext/attr-no-dangling2.C | 29 +++++++++ > > gcc/testsuite/g++.dg/ext/attr-no-dangling3.C | 24 ++++++++ > > gcc/testsuite/g++.dg/ext/attr-no-dangling4.C | 14 +++++ > > gcc/testsuite/g++.dg/ext/attr-no-dangling5.C | 31 ++++++++++ > > gcc/testsuite/g++.dg/ext/attr-no-dangling6.C | 65 ++++++++++++++++++++ > > gcc/testsuite/g++.dg/ext/attr-no-dangling7.C | 31 ++++++++++ > > gcc/testsuite/g++.dg/ext/attr-no-dangling8.C | 30 +++++++++ > > gcc/testsuite/g++.dg/ext/attr-no-dangling9.C | 25 ++++++++ > > 13 files changed, 387 insertions(+), 6 deletions(-) > > create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling1.C > > create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling2.C > > create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling3.C > > create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling4.C > > create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling5.C > > create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling6.C > > create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling7.C > > create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling8.C > > create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling9.C > > > > diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc > > index c40ef2e3028..9e4c8073600 100644 > > --- a/gcc/cp/call.cc > > +++ b/gcc/cp/call.cc > > @@ -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)); > > diff --git a/gcc/cp/tree.cc b/gcc/cp/tree.cc > > index ad312710f68..e75be9a4e66 100644 > > --- a/gcc/cp/tree.cc > > +++ b/gcc/cp/tree.cc > > @@ -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. */ > > diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi > > index efd78014d1a..571655bf39a 100644 > > --- a/gcc/doc/extend.texi > > +++ b/gcc/doc/extend.texi > > @@ -29317,6 +29317,27 @@ 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 > > +@item no_dangling > > + > > +This attribute can be applied on a class type, function, or member > > +function. Entities marked with this attribute will have the > > +@option{-Wdangling-reference} diagnostic suppressed. > > + > > +@smallexample > > +class [[gnu::no_dangling]] S @{ @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 > > > @cindex @code{warn_unused} type attribute > > @item warn_unused > > diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi > > index 7862c751801..9022a413dd4 100644 > > --- a/gcc/doc/invoke.texi > > +++ b/gcc/doc/invoke.texi > > @@ -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,24 @@ 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, > > Can this be an xref, and put all the documentation for the usage and effect > of the attribute in the attribute section? Yes. And I need an entry both for the type and function attribute. > > +which can be applied on the enclosing class type (in which case it disables > > +the warning for all its member functions), > > I think this misrepresents the effect of putting the attribute on the class. > Rather, it means that we won't warn about a dangling reference to the class. Updated, NFC: -- >8 -- 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. --- gcc/cp/call.cc | 38 ++++++++++-- gcc/cp/tree.cc | 26 ++++++++ gcc/doc/extend.texi | 51 +++++++++++++++ gcc/doc/invoke.texi | 7 +++ gcc/testsuite/g++.dg/ext/attr-no-dangling1.C | 38 ++++++++++++ gcc/testsuite/g++.dg/ext/attr-no-dangling2.C | 29 +++++++++ gcc/testsuite/g++.dg/ext/attr-no-dangling3.C | 24 ++++++++ gcc/testsuite/g++.dg/ext/attr-no-dangling4.C | 14 +++++ gcc/testsuite/g++.dg/ext/attr-no-dangling5.C | 31 ++++++++++ gcc/testsuite/g++.dg/ext/attr-no-dangling6.C | 65 ++++++++++++++++++++ gcc/testsuite/g++.dg/ext/attr-no-dangling7.C | 31 ++++++++++ gcc/testsuite/g++.dg/ext/attr-no-dangling8.C | 30 +++++++++ gcc/testsuite/g++.dg/ext/attr-no-dangling9.C | 25 ++++++++ 13 files changed, 403 insertions(+), 6 deletions(-) create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling1.C create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling2.C create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling3.C create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling4.C create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling5.C create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling6.C create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling7.C create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling8.C create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling9.C diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc index c40ef2e3028..9e4c8073600 100644 --- a/gcc/cp/call.cc +++ b/gcc/cp/call.cc @@ -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)); diff --git a/gcc/cp/tree.cc b/gcc/cp/tree.cc index ad312710f68..e75be9a4e66 100644 --- a/gcc/cp/tree.cc +++ b/gcc/cp/tree.cc @@ -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. */ diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi index 6c2c7ae5d8a..5bd3933f011 100644 --- a/gcc/doc/extend.texi +++ b/gcc/doc/extend.texi @@ -4380,6 +4380,36 @@ visibility of their template. If both the template and enclosing class have explicit visibility, the visibility from the template is used. +@cindex @code{no_dangling} function attribute +@item no_dangling + +The @code{gnu::no_dangling} attribute can be used to suppress the +@option{-Wdangling-reference} diagnostic on a function or a member +function. For example: + +@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> +[[gnu::no_dangling(std::is_reference_v<T>)]] int foo (T& t) @{ + @dots{} +@}; +@end smallexample + @cindex @code{warn_unused_result} function attribute @item warn_unused_result The @code{warn_unused_result} attribute causes a warning to be emitted @@ -29327,6 +29357,27 @@ 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 +@item no_dangling + +This attribute can be applied on a class type. Dangling references to +classes marked with this attribute will have the @option{-Wdangling-reference} +diagnostic suppressed. + +@smallexample +class [[gnu::no_dangling]] S @{ @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 + @cindex @code{warn_unused} type attribute @item warn_unused diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi index dc5fd863ca4..0422b2c400a 100644 --- a/gcc/doc/invoke.texi +++ b/gcc/doc/invoke.texi @@ -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,10 @@ 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 +on a function (@pxref{Common Function Attributes}), or a class type +(@pxref{C++ Attributes}). + This warning is enabled by @option{-Wall}. @opindex Wdelete-non-virtual-dtor diff --git a/gcc/testsuite/g++.dg/ext/attr-no-dangling1.C b/gcc/testsuite/g++.dg/ext/attr-no-dangling1.C new file mode 100644 index 00000000000..02eabbc5003 --- /dev/null +++ b/gcc/testsuite/g++.dg/ext/attr-no-dangling1.C @@ -0,0 +1,38 @@ +// { dg-do compile { target c++11 } } +// { dg-options "-Wdangling-reference" } + +int g = 42; + +struct [[gnu::no_dangling]] A { + int *i; + int &foo() { return *i; } +}; + +struct 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" } +} diff --git a/gcc/testsuite/g++.dg/ext/attr-no-dangling2.C b/gcc/testsuite/g++.dg/ext/attr-no-dangling2.C new file mode 100644 index 00000000000..4cdc97ea7c4 --- /dev/null +++ b/gcc/testsuite/g++.dg/ext/attr-no-dangling2.C @@ -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" } +} diff --git a/gcc/testsuite/g++.dg/ext/attr-no-dangling3.C b/gcc/testsuite/g++.dg/ext/attr-no-dangling3.C new file mode 100644 index 00000000000..764b104fd3c --- /dev/null +++ b/gcc/testsuite/g++.dg/ext/attr-no-dangling3.C @@ -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; +} diff --git a/gcc/testsuite/g++.dg/ext/attr-no-dangling4.C b/gcc/testsuite/g++.dg/ext/attr-no-dangling4.C new file mode 100644 index 00000000000..e910723d985 --- /dev/null +++ b/gcc/testsuite/g++.dg/ext/attr-no-dangling4.C @@ -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), ""); diff --git a/gcc/testsuite/g++.dg/ext/attr-no-dangling5.C b/gcc/testsuite/g++.dg/ext/attr-no-dangling5.C new file mode 100644 index 00000000000..ec5075482c4 --- /dev/null +++ b/gcc/testsuite/g++.dg/ext/attr-no-dangling5.C @@ -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" } +} diff --git a/gcc/testsuite/g++.dg/ext/attr-no-dangling6.C b/gcc/testsuite/g++.dg/ext/attr-no-dangling6.C new file mode 100644 index 00000000000..235a5fd86c5 --- /dev/null +++ b/gcc/testsuite/g++.dg/ext/attr-no-dangling6.C @@ -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" } +} diff --git a/gcc/testsuite/g++.dg/ext/attr-no-dangling7.C b/gcc/testsuite/g++.dg/ext/attr-no-dangling7.C new file mode 100644 index 00000000000..3c392ed409f --- /dev/null +++ b/gcc/testsuite/g++.dg/ext/attr-no-dangling7.C @@ -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" } +} + diff --git a/gcc/testsuite/g++.dg/ext/attr-no-dangling8.C b/gcc/testsuite/g++.dg/ext/attr-no-dangling8.C new file mode 100644 index 00000000000..8208d751a4b --- /dev/null +++ b/gcc/testsuite/g++.dg/ext/attr-no-dangling8.C @@ -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" } +} + diff --git a/gcc/testsuite/g++.dg/ext/attr-no-dangling9.C b/gcc/testsuite/g++.dg/ext/attr-no-dangling9.C new file mode 100644 index 00000000000..65b4f7145a9 --- /dev/null +++ b/gcc/testsuite/g++.dg/ext/attr-no-dangling9.C @@ -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" } +} + base-commit: 4894c82b0c3cf0d6ec4bc1e96709b6140ec11f6e -- 2.44.0 ^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v4] c++: implement [[gnu::non_owning]] [PR110358] 2024-03-01 17:39 ` [PATCH v4] " Marek Polacek @ 2024-03-01 18:19 ` Jason Merrill 2024-03-01 19:24 ` [PATCH v5] " Marek Polacek 0 siblings, 1 reply; 15+ messages in thread From: Jason Merrill @ 2024-03-01 18:19 UTC (permalink / raw) To: Marek Polacek; +Cc: GCC Patches On 3/1/24 12:39, Marek Polacek wrote: > On Thu, Feb 29, 2024 at 07:30:02PM -0500, Jason Merrill wrote: >> On 2/29/24 19:12, Marek Polacek wrote: >>> On Wed, Feb 28, 2024 at 06:03:54PM -0500, Jason Merrill wrote: >>> >>>> Hmm, if we're also going to allow the attribute to be applied to a function, >>>> the name doesn't make so much sense. For a class, it says that the class >>>> refers to its initializer; for a function, it says that the function return >>>> value *doesn't* refer to its argument. >>> >>> Yeah, that's a fair point; I guess "non_owning" would be too perplexing. >>> >>>> If we want something that can apply to both classes and functions, we're >>>> probably back to an attribute that just suppresses the warning, with a >>>> different name. >>>> >>>> Or I guess we could have two attributes, but that seems like a lot. >>>> >>>> WDYT? >>> >>> I think we don't want two separate attributes, and we do want that one >>> attribute to apply to both fns and classes. We could implement something >>> like >>> >>> [[gnu::no_warning("Wdangling-reference")]] >>> [[gnu::no_warning("Wdangling-reference", bool)]] >>> >>> but first, that's a lot of typing, second, it would be confusing because >>> it wouldn't work for any other warning. We already have [[unused]] and >>> [[maybe_unused]] whose effect is to suppress a warning. It think our >>> best bet is to do the most straightforward thing: [[gnu::no_dangling]], >>> which this patch implements. I didn't call it no_dangling_reference in >>> the hope that it can, some day, be also used for some -Wdangling-pointer >>> purposes. >>> >>> >>> Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk? >>> >>> -- >8 -- >>> 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. >>> --- >>> gcc/cp/call.cc | 38 ++++++++++-- >>> gcc/cp/tree.cc | 26 ++++++++ >>> gcc/doc/extend.texi | 21 +++++++ >>> gcc/doc/invoke.texi | 21 +++++++ >>> gcc/testsuite/g++.dg/ext/attr-no-dangling1.C | 38 ++++++++++++ >>> gcc/testsuite/g++.dg/ext/attr-no-dangling2.C | 29 +++++++++ >>> gcc/testsuite/g++.dg/ext/attr-no-dangling3.C | 24 ++++++++ >>> gcc/testsuite/g++.dg/ext/attr-no-dangling4.C | 14 +++++ >>> gcc/testsuite/g++.dg/ext/attr-no-dangling5.C | 31 ++++++++++ >>> gcc/testsuite/g++.dg/ext/attr-no-dangling6.C | 65 ++++++++++++++++++++ >>> gcc/testsuite/g++.dg/ext/attr-no-dangling7.C | 31 ++++++++++ >>> gcc/testsuite/g++.dg/ext/attr-no-dangling8.C | 30 +++++++++ >>> gcc/testsuite/g++.dg/ext/attr-no-dangling9.C | 25 ++++++++ >>> 13 files changed, 387 insertions(+), 6 deletions(-) >>> create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling1.C >>> create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling2.C >>> create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling3.C >>> create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling4.C >>> create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling5.C >>> create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling6.C >>> create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling7.C >>> create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling8.C >>> create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling9.C >>> >>> diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc >>> index c40ef2e3028..9e4c8073600 100644 >>> --- a/gcc/cp/call.cc >>> +++ b/gcc/cp/call.cc >>> @@ -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)); >>> diff --git a/gcc/cp/tree.cc b/gcc/cp/tree.cc >>> index ad312710f68..e75be9a4e66 100644 >>> --- a/gcc/cp/tree.cc >>> +++ b/gcc/cp/tree.cc >>> @@ -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. */ >>> diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi >>> index efd78014d1a..571655bf39a 100644 >>> --- a/gcc/doc/extend.texi >>> +++ b/gcc/doc/extend.texi >>> @@ -29317,6 +29317,27 @@ 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 >>> +@item no_dangling >>> + >>> +This attribute can be applied on a class type, function, or member >>> +function. Entities marked with this attribute will have the >>> +@option{-Wdangling-reference} diagnostic suppressed. >>> + >>> +@smallexample >>> +class [[gnu::no_dangling]] S @{ @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 > >>> @cindex @code{warn_unused} type attribute >>> @item warn_unused >>> diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi >>> index 7862c751801..9022a413dd4 100644 >>> --- a/gcc/doc/invoke.texi >>> +++ b/gcc/doc/invoke.texi >>> @@ -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,24 @@ 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, >> >> Can this be an xref, and put all the documentation for the usage and effect >> of the attribute in the attribute section? > > Yes. And I need an entry both for the type and function attribute. > >>> +which can be applied on the enclosing class type (in which case it disables >>> +the warning for all its member functions), >> >> I think this misrepresents the effect of putting the attribute on the class. >> Rather, it means that we won't warn about a dangling reference to the class. > > Updated, NFC: > > -- >8 -- > 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. > --- > gcc/cp/call.cc | 38 ++++++++++-- > gcc/cp/tree.cc | 26 ++++++++ > gcc/doc/extend.texi | 51 +++++++++++++++ > gcc/doc/invoke.texi | 7 +++ > gcc/testsuite/g++.dg/ext/attr-no-dangling1.C | 38 ++++++++++++ > gcc/testsuite/g++.dg/ext/attr-no-dangling2.C | 29 +++++++++ > gcc/testsuite/g++.dg/ext/attr-no-dangling3.C | 24 ++++++++ > gcc/testsuite/g++.dg/ext/attr-no-dangling4.C | 14 +++++ > gcc/testsuite/g++.dg/ext/attr-no-dangling5.C | 31 ++++++++++ > gcc/testsuite/g++.dg/ext/attr-no-dangling6.C | 65 ++++++++++++++++++++ > gcc/testsuite/g++.dg/ext/attr-no-dangling7.C | 31 ++++++++++ > gcc/testsuite/g++.dg/ext/attr-no-dangling8.C | 30 +++++++++ > gcc/testsuite/g++.dg/ext/attr-no-dangling9.C | 25 ++++++++ > 13 files changed, 403 insertions(+), 6 deletions(-) > create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling1.C > create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling2.C > create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling3.C > create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling4.C > create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling5.C > create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling6.C > create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling7.C > create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling8.C > create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling9.C > > diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc > index c40ef2e3028..9e4c8073600 100644 > --- a/gcc/cp/call.cc > +++ b/gcc/cp/call.cc > @@ -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)); > diff --git a/gcc/cp/tree.cc b/gcc/cp/tree.cc > index ad312710f68..e75be9a4e66 100644 > --- a/gcc/cp/tree.cc > +++ b/gcc/cp/tree.cc > @@ -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. */ > > diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi > index 6c2c7ae5d8a..5bd3933f011 100644 > --- a/gcc/doc/extend.texi > +++ b/gcc/doc/extend.texi > @@ -4380,6 +4380,36 @@ visibility of their template. > If both the template and enclosing class have explicit visibility, the > visibility from the template is used. > > +@cindex @code{no_dangling} function attribute > +@item no_dangling > + > +The @code{gnu::no_dangling} attribute can be used to suppress the > +@option{-Wdangling-reference} diagnostic on a function or a member > +function. For example: > + > +@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> > +[[gnu::no_dangling(std::is_reference_v<T>)]] int foo (T& t) @{ > + @dots{} > +@}; > +@end smallexample > + > @cindex @code{warn_unused_result} function attribute > @item warn_unused_result > The @code{warn_unused_result} attribute causes a warning to be emitted > @@ -29327,6 +29357,27 @@ 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 > +@item no_dangling > + > +This attribute can be applied on a class type. Dangling references to > +classes marked with this attribute will have the @option{-Wdangling-reference} > +diagnostic suppressed. > + > +@smallexample > +class [[gnu::no_dangling]] S @{ @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 > + > @cindex @code{warn_unused} type attribute > @item warn_unused > > diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi > index dc5fd863ca4..0422b2c400a 100644 > --- a/gcc/doc/invoke.texi > +++ b/gcc/doc/invoke.texi > @@ -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,10 @@ 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 > +on a function (@pxref{Common Function Attributes}), or a class type > +(@pxref{C++ Attributes}). It seems surprising that one is in a generic attributes section and the other in the C++-specific section. Maybe both uses could be covered in the C++ attributes section? > This warning is enabled by @option{-Wall}. > > @opindex Wdelete-non-virtual-dtor > diff --git a/gcc/testsuite/g++.dg/ext/attr-no-dangling1.C b/gcc/testsuite/g++.dg/ext/attr-no-dangling1.C > new file mode 100644 > index 00000000000..02eabbc5003 > --- /dev/null > +++ b/gcc/testsuite/g++.dg/ext/attr-no-dangling1.C > @@ -0,0 +1,38 @@ > +// { dg-do compile { target c++11 } } > +// { dg-options "-Wdangling-reference" } > + > +int g = 42; > + > +struct [[gnu::no_dangling]] A { > + int *i; > + int &foo() { return *i; } > +}; > + > +struct 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" } > +} Do you want to add destructors to A/A2 like you did in other tests? > diff --git a/gcc/testsuite/g++.dg/ext/attr-no-dangling2.C b/gcc/testsuite/g++.dg/ext/attr-no-dangling2.C > new file mode 100644 > index 00000000000..4cdc97ea7c4 > --- /dev/null > +++ b/gcc/testsuite/g++.dg/ext/attr-no-dangling2.C > @@ -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" } > +} > diff --git a/gcc/testsuite/g++.dg/ext/attr-no-dangling3.C b/gcc/testsuite/g++.dg/ext/attr-no-dangling3.C > new file mode 100644 > index 00000000000..764b104fd3c > --- /dev/null > +++ b/gcc/testsuite/g++.dg/ext/attr-no-dangling3.C > @@ -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; > +} > diff --git a/gcc/testsuite/g++.dg/ext/attr-no-dangling4.C b/gcc/testsuite/g++.dg/ext/attr-no-dangling4.C > new file mode 100644 > index 00000000000..e910723d985 > --- /dev/null > +++ b/gcc/testsuite/g++.dg/ext/attr-no-dangling4.C > @@ -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), ""); > diff --git a/gcc/testsuite/g++.dg/ext/attr-no-dangling5.C b/gcc/testsuite/g++.dg/ext/attr-no-dangling5.C > new file mode 100644 > index 00000000000..ec5075482c4 > --- /dev/null > +++ b/gcc/testsuite/g++.dg/ext/attr-no-dangling5.C > @@ -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" } > +} > diff --git a/gcc/testsuite/g++.dg/ext/attr-no-dangling6.C b/gcc/testsuite/g++.dg/ext/attr-no-dangling6.C > new file mode 100644 > index 00000000000..235a5fd86c5 > --- /dev/null > +++ b/gcc/testsuite/g++.dg/ext/attr-no-dangling6.C > @@ -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" } > +} > diff --git a/gcc/testsuite/g++.dg/ext/attr-no-dangling7.C b/gcc/testsuite/g++.dg/ext/attr-no-dangling7.C > new file mode 100644 > index 00000000000..3c392ed409f > --- /dev/null > +++ b/gcc/testsuite/g++.dg/ext/attr-no-dangling7.C > @@ -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" } > +} > + > diff --git a/gcc/testsuite/g++.dg/ext/attr-no-dangling8.C b/gcc/testsuite/g++.dg/ext/attr-no-dangling8.C > new file mode 100644 > index 00000000000..8208d751a4b > --- /dev/null > +++ b/gcc/testsuite/g++.dg/ext/attr-no-dangling8.C > @@ -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" } > +} > + > diff --git a/gcc/testsuite/g++.dg/ext/attr-no-dangling9.C b/gcc/testsuite/g++.dg/ext/attr-no-dangling9.C > new file mode 100644 > index 00000000000..65b4f7145a9 > --- /dev/null > +++ b/gcc/testsuite/g++.dg/ext/attr-no-dangling9.C > @@ -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" } > +} > + > > base-commit: 4894c82b0c3cf0d6ec4bc1e96709b6140ec11f6e ^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH v5] c++: implement [[gnu::non_owning]] [PR110358] 2024-03-01 18:19 ` Jason Merrill @ 2024-03-01 19:24 ` Marek Polacek 2024-03-01 20:38 ` Jason Merrill 0 siblings, 1 reply; 15+ messages in thread From: Marek Polacek @ 2024-03-01 19:24 UTC (permalink / raw) To: Jason Merrill; +Cc: GCC Patches On Fri, Mar 01, 2024 at 01:19:40PM -0500, Jason Merrill wrote: > On 3/1/24 12:39, Marek Polacek wrote: > > @option{-Wdangling-reference} also warns about code like > > @smallexample > > @@ -3932,6 +3935,10 @@ 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 > > +on a function (@pxref{Common Function Attributes}), or a class type > > +(@pxref{C++ Attributes}). > > It seems surprising that one is in a generic attributes section and the > other in the C++-specific section. Maybe both uses could be covered in the > C++ attributes section? Arg yes, definitely. Done here. > > This warning is enabled by @option{-Wall}. > > @opindex Wdelete-non-virtual-dtor > > diff --git a/gcc/testsuite/g++.dg/ext/attr-no-dangling1.C b/gcc/testsuite/g++.dg/ext/attr-no-dangling1.C > > new file mode 100644 > > index 00000000000..02eabbc5003 > > --- /dev/null > > +++ b/gcc/testsuite/g++.dg/ext/attr-no-dangling1.C > > @@ -0,0 +1,38 @@ > > +// { dg-do compile { target c++11 } } > > +// { dg-options "-Wdangling-reference" } > > + > > +int g = 42; > > + > > +struct [[gnu::no_dangling]] A { > > + int *i; > > + int &foo() { return *i; } > > +}; > > + > > +struct 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" } > > +} > > Do you want to add destructors to A/A2 like you did in other tests? Added. I think this test predates the recent heuristic. Ok for trunk? -- >8 -- 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. --- gcc/cp/call.cc | 38 ++++++++++-- gcc/cp/tree.cc | 26 ++++++++ gcc/doc/extend.texi | 47 ++++++++++++++ gcc/doc/invoke.texi | 6 ++ gcc/testsuite/g++.dg/ext/attr-no-dangling1.C | 40 ++++++++++++ gcc/testsuite/g++.dg/ext/attr-no-dangling2.C | 29 +++++++++ gcc/testsuite/g++.dg/ext/attr-no-dangling3.C | 24 ++++++++ gcc/testsuite/g++.dg/ext/attr-no-dangling4.C | 14 +++++ gcc/testsuite/g++.dg/ext/attr-no-dangling5.C | 31 ++++++++++ gcc/testsuite/g++.dg/ext/attr-no-dangling6.C | 65 ++++++++++++++++++++ gcc/testsuite/g++.dg/ext/attr-no-dangling7.C | 31 ++++++++++ gcc/testsuite/g++.dg/ext/attr-no-dangling8.C | 30 +++++++++ gcc/testsuite/g++.dg/ext/attr-no-dangling9.C | 25 ++++++++ 13 files changed, 400 insertions(+), 6 deletions(-) create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling1.C create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling2.C create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling3.C create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling4.C create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling5.C create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling6.C create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling7.C create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling8.C create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling9.C diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc index c40ef2e3028..9e4c8073600 100644 --- a/gcc/cp/call.cc +++ b/gcc/cp/call.cc @@ -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)); diff --git a/gcc/cp/tree.cc b/gcc/cp/tree.cc index ad312710f68..e75be9a4e66 100644 --- a/gcc/cp/tree.cc +++ b/gcc/cp/tree.cc @@ -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. */ diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi index 6c2c7ae5d8a..8e1751eae6c 100644 --- a/gcc/doc/extend.texi +++ b/gcc/doc/extend.texi @@ -29327,6 +29327,53 @@ 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 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 diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi index dc5fd863ca4..bdf05be387d 100644 --- a/gcc/doc/invoke.texi +++ b/gcc/doc/invoke.texi @@ -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 diff --git a/gcc/testsuite/g++.dg/ext/attr-no-dangling1.C b/gcc/testsuite/g++.dg/ext/attr-no-dangling1.C new file mode 100644 index 00000000000..dff34e89436 --- /dev/null +++ b/gcc/testsuite/g++.dg/ext/attr-no-dangling1.C @@ -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" } +} diff --git a/gcc/testsuite/g++.dg/ext/attr-no-dangling2.C b/gcc/testsuite/g++.dg/ext/attr-no-dangling2.C new file mode 100644 index 00000000000..4cdc97ea7c4 --- /dev/null +++ b/gcc/testsuite/g++.dg/ext/attr-no-dangling2.C @@ -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" } +} diff --git a/gcc/testsuite/g++.dg/ext/attr-no-dangling3.C b/gcc/testsuite/g++.dg/ext/attr-no-dangling3.C new file mode 100644 index 00000000000..764b104fd3c --- /dev/null +++ b/gcc/testsuite/g++.dg/ext/attr-no-dangling3.C @@ -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; +} diff --git a/gcc/testsuite/g++.dg/ext/attr-no-dangling4.C b/gcc/testsuite/g++.dg/ext/attr-no-dangling4.C new file mode 100644 index 00000000000..e910723d985 --- /dev/null +++ b/gcc/testsuite/g++.dg/ext/attr-no-dangling4.C @@ -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), ""); diff --git a/gcc/testsuite/g++.dg/ext/attr-no-dangling5.C b/gcc/testsuite/g++.dg/ext/attr-no-dangling5.C new file mode 100644 index 00000000000..ec5075482c4 --- /dev/null +++ b/gcc/testsuite/g++.dg/ext/attr-no-dangling5.C @@ -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" } +} diff --git a/gcc/testsuite/g++.dg/ext/attr-no-dangling6.C b/gcc/testsuite/g++.dg/ext/attr-no-dangling6.C new file mode 100644 index 00000000000..235a5fd86c5 --- /dev/null +++ b/gcc/testsuite/g++.dg/ext/attr-no-dangling6.C @@ -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" } +} diff --git a/gcc/testsuite/g++.dg/ext/attr-no-dangling7.C b/gcc/testsuite/g++.dg/ext/attr-no-dangling7.C new file mode 100644 index 00000000000..3c392ed409f --- /dev/null +++ b/gcc/testsuite/g++.dg/ext/attr-no-dangling7.C @@ -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" } +} + diff --git a/gcc/testsuite/g++.dg/ext/attr-no-dangling8.C b/gcc/testsuite/g++.dg/ext/attr-no-dangling8.C new file mode 100644 index 00000000000..8208d751a4b --- /dev/null +++ b/gcc/testsuite/g++.dg/ext/attr-no-dangling8.C @@ -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" } +} + diff --git a/gcc/testsuite/g++.dg/ext/attr-no-dangling9.C b/gcc/testsuite/g++.dg/ext/attr-no-dangling9.C new file mode 100644 index 00000000000..65b4f7145a9 --- /dev/null +++ b/gcc/testsuite/g++.dg/ext/attr-no-dangling9.C @@ -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" } +} + base-commit: 64221c7bffbdd399e49554b0fb08b38325657596 -- 2.44.0 ^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v5] c++: implement [[gnu::non_owning]] [PR110358] 2024-03-01 19:24 ` [PATCH v5] " Marek Polacek @ 2024-03-01 20:38 ` Jason Merrill 2024-03-01 21:23 ` Patrick Palka 2024-03-04 11:00 ` Jonathan Wakely 0 siblings, 2 replies; 15+ messages in thread From: Jason Merrill @ 2024-03-01 20:38 UTC (permalink / raw) To: Marek Polacek; +Cc: GCC Patches On 3/1/24 14:24, Marek Polacek wrote: > On Fri, Mar 01, 2024 at 01:19:40PM -0500, Jason Merrill wrote: >> On 3/1/24 12:39, Marek Polacek wrote: >>> @option{-Wdangling-reference} also warns about code like >>> @smallexample >>> @@ -3932,6 +3935,10 @@ 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 >>> +on a function (@pxref{Common Function Attributes}), or a class type >>> +(@pxref{C++ Attributes}). >> >> It seems surprising that one is in a generic attributes section and the >> other in the C++-specific section. Maybe both uses could be covered in the >> C++ attributes section? > > Arg yes, definitely. Done here. > >>> This warning is enabled by @option{-Wall}. >>> @opindex Wdelete-non-virtual-dtor >>> diff --git a/gcc/testsuite/g++.dg/ext/attr-no-dangling1.C b/gcc/testsuite/g++.dg/ext/attr-no-dangling1.C >>> new file mode 100644 >>> index 00000000000..02eabbc5003 >>> --- /dev/null >>> +++ b/gcc/testsuite/g++.dg/ext/attr-no-dangling1.C >>> @@ -0,0 +1,38 @@ >>> +// { dg-do compile { target c++11 } } >>> +// { dg-options "-Wdangling-reference" } >>> + >>> +int g = 42; >>> + >>> +struct [[gnu::no_dangling]] A { >>> + int *i; >>> + int &foo() { return *i; } >>> +}; >>> + >>> +struct 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" } >>> +} >> >> Do you want to add destructors to A/A2 like you did in other tests? > > Added. I think this test predates the recent heuristic. > > Ok for trunk? > > -- >8 -- > 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. > --- > gcc/cp/call.cc | 38 ++++++++++-- > gcc/cp/tree.cc | 26 ++++++++ > gcc/doc/extend.texi | 47 ++++++++++++++ > gcc/doc/invoke.texi | 6 ++ > gcc/testsuite/g++.dg/ext/attr-no-dangling1.C | 40 ++++++++++++ > gcc/testsuite/g++.dg/ext/attr-no-dangling2.C | 29 +++++++++ > gcc/testsuite/g++.dg/ext/attr-no-dangling3.C | 24 ++++++++ > gcc/testsuite/g++.dg/ext/attr-no-dangling4.C | 14 +++++ > gcc/testsuite/g++.dg/ext/attr-no-dangling5.C | 31 ++++++++++ > gcc/testsuite/g++.dg/ext/attr-no-dangling6.C | 65 ++++++++++++++++++++ > gcc/testsuite/g++.dg/ext/attr-no-dangling7.C | 31 ++++++++++ > gcc/testsuite/g++.dg/ext/attr-no-dangling8.C | 30 +++++++++ > gcc/testsuite/g++.dg/ext/attr-no-dangling9.C | 25 ++++++++ > 13 files changed, 400 insertions(+), 6 deletions(-) > create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling1.C > create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling2.C > create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling3.C > create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling4.C > create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling5.C > create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling6.C > create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling7.C > create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling8.C > create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling9.C > > diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc > index c40ef2e3028..9e4c8073600 100644 > --- a/gcc/cp/call.cc > +++ b/gcc/cp/call.cc > @@ -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)); > diff --git a/gcc/cp/tree.cc b/gcc/cp/tree.cc > index ad312710f68..e75be9a4e66 100644 > --- a/gcc/cp/tree.cc > +++ b/gcc/cp/tree.cc > @@ -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. */ > > diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi > index 6c2c7ae5d8a..8e1751eae6c 100644 > --- a/gcc/doc/extend.texi > +++ b/gcc/doc/extend.texi > @@ -29327,6 +29327,53 @@ 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 the @code{gnu::no_dangling}-marked functions. For example: ...; so will references returned from... > +@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) @{ I think this function should return a reference. OK with those changes, thanks. Jason ^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v5] c++: implement [[gnu::non_owning]] [PR110358] 2024-03-01 20:38 ` Jason Merrill @ 2024-03-01 21:23 ` Patrick Palka 2024-03-01 21:31 ` Jason Merrill 2024-03-04 11:00 ` Jonathan Wakely 1 sibling, 1 reply; 15+ messages in thread From: Patrick Palka @ 2024-03-01 21:23 UTC (permalink / raw) To: Jason Merrill; +Cc: Marek Polacek, GCC Patches On Fri, 1 Mar 2024, Jason Merrill wrote: > On 3/1/24 14:24, Marek Polacek wrote: > > On Fri, Mar 01, 2024 at 01:19:40PM -0500, Jason Merrill wrote: > > > On 3/1/24 12:39, Marek Polacek wrote: > > > > @option{-Wdangling-reference} also warns about code like > > > > @smallexample > > > > @@ -3932,6 +3935,10 @@ 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 > > > > +on a function (@pxref{Common Function Attributes}), or a class type > > > > +(@pxref{C++ Attributes}). > > > > > > It seems surprising that one is in a generic attributes section and the > > > other in the C++-specific section. Maybe both uses could be covered in > > > the > > > C++ attributes section? > > > > Arg yes, definitely. Done here. > > > > > > This warning is enabled by @option{-Wall}. > > > > @opindex Wdelete-non-virtual-dtor > > > > diff --git a/gcc/testsuite/g++.dg/ext/attr-no-dangling1.C > > > > b/gcc/testsuite/g++.dg/ext/attr-no-dangling1.C > > > > new file mode 100644 > > > > index 00000000000..02eabbc5003 > > > > --- /dev/null > > > > +++ b/gcc/testsuite/g++.dg/ext/attr-no-dangling1.C > > > > @@ -0,0 +1,38 @@ > > > > +// { dg-do compile { target c++11 } } > > > > +// { dg-options "-Wdangling-reference" } > > > > + > > > > +int g = 42; > > > > + > > > > +struct [[gnu::no_dangling]] A { > > > > + int *i; > > > > + int &foo() { return *i; } > > > > +}; > > > > + > > > > +struct 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" } > > > > +} > > > > > > Do you want to add destructors to A/A2 like you did in other tests? > > > > Added. I think this test predates the recent heuristic. > > > > Ok for trunk? > > > > -- >8 -- > > 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. > > --- > > gcc/cp/call.cc | 38 ++++++++++-- > > gcc/cp/tree.cc | 26 ++++++++ > > gcc/doc/extend.texi | 47 ++++++++++++++ > > gcc/doc/invoke.texi | 6 ++ > > gcc/testsuite/g++.dg/ext/attr-no-dangling1.C | 40 ++++++++++++ > > gcc/testsuite/g++.dg/ext/attr-no-dangling2.C | 29 +++++++++ > > gcc/testsuite/g++.dg/ext/attr-no-dangling3.C | 24 ++++++++ > > gcc/testsuite/g++.dg/ext/attr-no-dangling4.C | 14 +++++ > > gcc/testsuite/g++.dg/ext/attr-no-dangling5.C | 31 ++++++++++ > > gcc/testsuite/g++.dg/ext/attr-no-dangling6.C | 65 ++++++++++++++++++++ > > gcc/testsuite/g++.dg/ext/attr-no-dangling7.C | 31 ++++++++++ > > gcc/testsuite/g++.dg/ext/attr-no-dangling8.C | 30 +++++++++ > > gcc/testsuite/g++.dg/ext/attr-no-dangling9.C | 25 ++++++++ > > 13 files changed, 400 insertions(+), 6 deletions(-) > > create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling1.C > > create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling2.C > > create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling3.C > > create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling4.C > > create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling5.C > > create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling6.C > > create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling7.C > > create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling8.C > > create mode 100644 gcc/testsuite/g++.dg/ext/attr-no-dangling9.C > > > > diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc > > index c40ef2e3028..9e4c8073600 100644 > > --- a/gcc/cp/call.cc > > +++ b/gcc/cp/call.cc > > @@ -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)); > > diff --git a/gcc/cp/tree.cc b/gcc/cp/tree.cc > > index ad312710f68..e75be9a4e66 100644 > > --- a/gcc/cp/tree.cc > > +++ b/gcc/cp/tree.cc > > @@ -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)) Sorry for not asking this sooner, but does it matter whether we attach the attribute to the function type rather than the function declaration? I noticed e.g. nodiscard gets attached to the decl. > > + { > > + 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. */ > > diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi > > index 6c2c7ae5d8a..8e1751eae6c 100644 > > --- a/gcc/doc/extend.texi > > +++ b/gcc/doc/extend.texi > > @@ -29327,6 +29327,53 @@ 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 And we document it as a function attribute despite attaching it to the function type. > > +@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 the @code{gnu::no_dangling}-marked functions. For example: > > ...; so will references returned from... > > > +@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) @{ > > I think this function should return a reference. > > OK with those changes, thanks. > > Jason > > ^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v5] c++: implement [[gnu::non_owning]] [PR110358] 2024-03-01 21:23 ` Patrick Palka @ 2024-03-01 21:31 ` Jason Merrill 0 siblings, 0 replies; 15+ messages in thread From: Jason Merrill @ 2024-03-01 21:31 UTC (permalink / raw) To: Patrick Palka; +Cc: Marek Polacek, GCC Patches On 3/1/24 16:23, Patrick Palka wrote: > > Sorry for not asking this sooner, but does it matter whether we attach > the attribute to the function type rather than the function declaration? > I noticed e.g. nodiscard gets attached to the decl. > > And we document it as a function attribute despite attaching it to the > function type. I think it doesn't matter much, some attributes are represented on the type and some on the decl. Might be a bit better on the decl but I wasn't worrying about it. Jason ^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v5] c++: implement [[gnu::non_owning]] [PR110358] 2024-03-01 20:38 ` Jason Merrill 2024-03-01 21:23 ` Patrick Palka @ 2024-03-04 11:00 ` Jonathan Wakely 2024-03-04 16:30 ` Marek Polacek 1 sibling, 1 reply; 15+ messages in thread From: Jonathan Wakely @ 2024-03-04 11:00 UTC (permalink / raw) To: Marek Polacek; +Cc: GCC Patches, Jason Merrill On 01/03/24 15:38 -0500, Jason Merrill wrote: >On 3/1/24 14:24, Marek Polacek wrote: >>+@smallexample >>+template <typename T> >>+[[gnu::no_dangling(std::is_reference_v<T>)]] int foo (T& t) @{ > >I think this function should return a reference. The condition in the attribute can only ever be true if you call this function with an explicit template argument list: foo<int&>(i). Is that intentional? And if T is non-const it can't be called with a temporary and so dangling seems less of a problem for this function anyway, right? Would it make more sense as something like this? template <typename T> [[gnu::no_dangling(std::is_lvalue_reference_v<T>)]] decltype(auto) foo(T&& t) { ... } Or is this getting too complex/subtle for a simple example? ^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v5] c++: implement [[gnu::non_owning]] [PR110358] 2024-03-04 11:00 ` Jonathan Wakely @ 2024-03-04 16:30 ` Marek Polacek 0 siblings, 0 replies; 15+ messages in thread From: Marek Polacek @ 2024-03-04 16:30 UTC (permalink / raw) To: Jonathan Wakely; +Cc: GCC Patches, Jason Merrill On Mon, Mar 04, 2024 at 11:00:18AM +0000, Jonathan Wakely wrote: > On 01/03/24 15:38 -0500, Jason Merrill wrote: > > On 3/1/24 14:24, Marek Polacek wrote: > > > +@smallexample > > > +template <typename T> > > > +[[gnu::no_dangling(std::is_reference_v<T>)]] int foo (T& t) @{ > > > > I think this function should return a reference. > > The condition in the attribute can only ever be true if you call this > function with an explicit template argument list: foo<int&>(i). Is > that intentional? Not intentional. I just wanted to make it clear that the user can use something like std::is_reference as the attribute argument, but I didn't think about it very long. > And if T is non-const it can't be called with a temporary and so > dangling seems less of a problem for this function anyway, right? Right. > Would it make more sense as something like this? > > template <typename T> > [[gnu::no_dangling(std::is_lvalue_reference_v<T>)]] > decltype(auto) foo(T&& t) { > ... > } > > Or is this getting too complex/subtle for a simple example? I like your example; it's only slightly more complex than the original one and most likely more realistic. I'm pushing the following patch. Thanks! [pushed] doc: update [[gnu::no_dangling]] ...to offer a more realistic example. gcc/ChangeLog: * doc/extend.texi: Update [[gnu::no_dangling]]. --- gcc/doc/extend.texi | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi index f679c81acf2..df0982fdfda 100644 --- a/gcc/doc/extend.texi +++ b/gcc/doc/extend.texi @@ -29370,7 +29370,8 @@ Or: @smallexample template <typename T> -[[gnu::no_dangling(std::is_reference_v<T>)]] int& foo (T& t) @{ +[[gnu::no_dangling(std::is_lvalue_reference_v<T>)]] +decltype(auto) foo(T&& t) @{ @dots{} @}; @end smallexample base-commit: 77eb86be8841989651b3150a020dd1a95910cc00 -- 2.44.0 ^ permalink raw reply [flat|nested] 15+ messages in thread
end of thread, other threads:[~2024-03-04 16:30 UTC | newest] Thread overview: 15+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2024-01-26 1:37 [PATCH] c++: implement [[gnu::non_owning]] [PR110358] Marek Polacek 2024-01-26 2:34 ` Marek Polacek 2024-01-26 21:04 ` Jason Merrill 2024-02-22 0:35 ` [PATCH v2] " Marek Polacek 2024-02-28 23:03 ` Jason Merrill 2024-03-01 0:12 ` [PATCH v3] " Marek Polacek 2024-03-01 0:30 ` Jason Merrill 2024-03-01 17:39 ` [PATCH v4] " Marek Polacek 2024-03-01 18:19 ` Jason Merrill 2024-03-01 19:24 ` [PATCH v5] " Marek Polacek 2024-03-01 20:38 ` Jason Merrill 2024-03-01 21:23 ` Patrick Palka 2024-03-01 21:31 ` Jason Merrill 2024-03-04 11:00 ` Jonathan Wakely 2024-03-04 16:30 ` Marek Polacek
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).