public inbox for gcc-patches@gcc.gnu.org
 help / color / mirror / Atom feed
* [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

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).