]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
c++: Fix overeager Woverloaded-virtual with conversion operators [PR109918]
authorSimon Martin <simon@nasilyan.com>
Tue, 4 Feb 2025 09:58:17 +0000 (10:58 +0100)
committerSimon Martin <simon@nasilyan.com>
Tue, 4 Feb 2025 10:01:05 +0000 (11:01 +0100)
We currently emit an incorrect -Woverloaded-virtual warning upon the
following test case

=== cut here ===
struct A {
  virtual operator int() { return 42; }
  virtual operator char() = 0;
};
struct B : public A {
  operator char() { return 'A'; }
};
=== cut here ===

The problem is that when iterating over ovl_range (fns), warn_hidden
gets confused by the conversion operator marker, concludes that
seen_non_override is true and therefore emits a warning for all
conversion operators in A that do not convert to char, even if
-Woverloaded-virtual is 1 (e.g. with -Wall, the case reported).

A second set of problems is highlighted when -Woverloaded-virtual is 2.

First, with the same test case, since base_fndecls contains all
conversion operators in A (except the one to char, that's been removed
when iterating over ovl_range (fns)), we emit a spurious warning for
the conversion operator to int, even though it's unrelated.

Second, in case there are several conversion operators with different
cv-qualifiers to the same type in A, we rightfully emit a warning,
however the note uses the location of the conversion operator marker
instead of the right one; location_of should go over conv_op_marker.

This patch fixes all these by explicitly keeping track of (1) base
methods that are overriden, as well as (2) base methods that are hidden
but not overriden (and by what), and warning about methods that are in
(2) but not (1). It also ignores non virtual base methods, per
"definition" of -Woverloaded-virtual.

Co-authored-by: Jason Merrill <jason@redhat.com>
PR c++/117114
PR c++/109918

gcc/cp/ChangeLog:

* class.cc (warn_hidden): Keep track of overloaded and of hidden
base methods.
* error.cc (location_of): Skip over conv_op_marker.

gcc/testsuite/ChangeLog:

* g++.dg/warn/Woverloaded-virt1.C: Check that no warning is
emitted for non virtual base methods.
* g++.dg/warn/Woverloaded-virt10.C: New test.
* g++.dg/warn/Woverloaded-virt11.C: New test.
* g++.dg/warn/Woverloaded-virt12.C: New test.
* g++.dg/warn/Woverloaded-virt13.C: New test.
* g++.dg/warn/Woverloaded-virt5.C: New test.
* g++.dg/warn/Woverloaded-virt6.C: New test.
* g++.dg/warn/Woverloaded-virt7.C: New test.
* g++.dg/warn/Woverloaded-virt8.C: New test.
* g++.dg/warn/Woverloaded-virt9.C: New test.

12 files changed:
gcc/cp/class.cc
gcc/cp/error.cc
gcc/testsuite/g++.dg/warn/Woverloaded-virt1.C
gcc/testsuite/g++.dg/warn/Woverloaded-virt10.C [new file with mode: 0644]
gcc/testsuite/g++.dg/warn/Woverloaded-virt11.C [new file with mode: 0644]
gcc/testsuite/g++.dg/warn/Woverloaded-virt12.C [new file with mode: 0644]
gcc/testsuite/g++.dg/warn/Woverloaded-virt13.C [new file with mode: 0644]
gcc/testsuite/g++.dg/warn/Woverloaded-virt5.C [new file with mode: 0644]
gcc/testsuite/g++.dg/warn/Woverloaded-virt6.C [new file with mode: 0644]
gcc/testsuite/g++.dg/warn/Woverloaded-virt7.C [new file with mode: 0644]
gcc/testsuite/g++.dg/warn/Woverloaded-virt8.C [new file with mode: 0644]
gcc/testsuite/g++.dg/warn/Woverloaded-virt9.C [new file with mode: 0644]

index 130fbc9a9724cc76f29f4ee9bd0352ad1f193320..d5ae69b0fdfad6be026c9311d78682d517997e0e 100644 (file)
@@ -3243,10 +3243,16 @@ warn_hidden (tree t)
          continue;
 
        tree name = OVL_NAME (fns);
+       size_t num_fns = 0; /* The number of fndecls in fns.  */
        auto_vec<tree, 20> base_fndecls;
        tree base_binfo;
        tree binfo;
        unsigned j;
+       size_t num_overriders = 0;
+       hash_set<tree> overriden_base_fndecls;
+       /* base_fndecls that are hidden but not overriden. The "value"
+          contains the last fndecl we saw that hides the "key".  */
+       hash_map<tree, tree> hidden_base_fndecls;
 
        if (IDENTIFIER_CDTOR_P (name))
          continue;
@@ -3264,47 +3270,65 @@ warn_hidden (tree t)
        if (base_fndecls.is_empty ())
          continue;
 
-       /* Remove any overridden functions.  */
-       bool seen_non_override = false;
+       /* Find all the base_fndecls that are overridden, as well as those
+          that are hidden, in T.  */
        for (tree fndecl : ovl_range (fns))
          {
-           bool any_override = false;
-           if (TREE_CODE (fndecl) == FUNCTION_DECL
-               && DECL_VINDEX (fndecl))
+           bool template_p = TREE_CODE (fndecl) == TEMPLATE_DECL;
+           bool fndecl_overrides_p = false;
+           fndecl = STRIP_TEMPLATE (fndecl);
+           if (TREE_CODE (fndecl) != FUNCTION_DECL
+               || fndecl == conv_op_marker)
+             continue;
+           num_fns++;
+           for (size_t k = 0; k < base_fndecls.length (); k++)
              {
-               /* If the method from the base class has the same
-                  signature as the method from the derived class, it
-                  has been overridden.  Note that we can't move on
-                  after finding one match: fndecl might override
-                  multiple base fns.  */
-               for (size_t k = 0; k < base_fndecls.length (); k++)
-                 if (base_fndecls[k]
-                     && same_signature_p (fndecl, base_fndecls[k]))
-                   {
-                     base_fndecls[k] = NULL_TREE;
-                     any_override = true;
-                   }
+               if (!base_fndecls[k] || !DECL_VINDEX (base_fndecls[k]))
+                 continue;
+               if (IDENTIFIER_CONV_OP_P (name)
+                   && !same_type_p (DECL_CONV_FN_TYPE (fndecl),
+                                    DECL_CONV_FN_TYPE (base_fndecls[k])))
+                 /* If base_fndecl[k] and fndecl are conversion operators
+                    to different types, they're unrelated.  */
+                 ;
+               else if (!template_p /* Template methods don't override.  */
+                        && same_signature_p (fndecl, base_fndecls[k]))
+                 {
+                   overriden_base_fndecls.add (base_fndecls[k]);
+                   fndecl_overrides_p = true;
+                 }
+               else
+                 {
+                   /* fndecl hides base_fndecls[k].  */
+                   hidden_base_fndecls.put (base_fndecls[k], fndecl);
+                 }
              }
-           if (!any_override)
-             seen_non_override = true;
+           if (fndecl_overrides_p)
+             ++num_overriders;
          }
 
-       if (!seen_non_override && warn_overloaded_virtual == 1)
-         /* All the derived fns override base virtuals.  */
-         return;
+       if (warn_overloaded_virtual == 1 && num_overriders == num_fns)
+         /* All the fns override a base virtual.  */
+         continue;
 
-       /* Now give a warning for all base functions without overriders,
-          as they are hidden.  */
-       for (tree base_fndecl : base_fndecls)
-         if (base_fndecl)
-           {
-             auto_diagnostic_group d;
-             /* Here we know it is a hider, and no overrider exists.  */
-             if (warning_at (location_of (base_fndecl),
-                             OPT_Woverloaded_virtual_,
-                             "%qD was hidden", base_fndecl))
-               inform (location_of (fns), "  by %qD", fns);
-           }
+       /* Now give a warning for all hidden methods. Note that a method that
+          is both in hidden_base_fndecls and overriden_base_fndecls is not
+          hidden.  */
+       for (auto hidden_base_fndecl : hidden_base_fndecls)
+         {
+           tree hidden_fndecl = hidden_base_fndecl.first;
+           if (!hidden_fndecl
+               || overriden_base_fndecls.contains (hidden_fndecl))
+             continue;
+           auto_diagnostic_group d;
+           if (warning_at (location_of (hidden_fndecl),
+                           OPT_Woverloaded_virtual_,
+                           "%qD was hidden", hidden_fndecl))
+             {
+               tree hider = hidden_base_fndecl.second;
+               inform (location_of (hider), "  by %qD", hider);
+             }
+         }
       }
 }
 
index a33afdb3d509871cfcee5f1a886a8d72e4a2119a..72e169585487f922e8fa2398655e2a29d9eeb4b4 100644 (file)
@@ -3402,7 +3402,8 @@ location_of (tree t)
        return input_location;
     }
   else if (TREE_CODE (t) == OVERLOAD)
-    t = OVL_FIRST (t);
+    t = (OVL_FIRST (t) != conv_op_marker ? OVL_FIRST (t)
+        : OVL_FIRST (OVL_CHAIN (t)));
 
   if (DECL_P (t))
     return DECL_SOURCE_LOCATION (t);
index 92f8327b9d0573f44e51577384a387b7888d2f14..9091bfabc967aa0ea1d05813cedd4b0c4b72c02d 100644 (file)
@@ -5,10 +5,12 @@ class Foo
 {
 public:
     virtual void f(int);       // { dg-warning "hidden" }
+    void g(int);               // Not virtual, so no warning
 };
 
 class Bar : public Foo
 {
 public:
     virtual void f(short);     // { dg-message "by" }
+    virtual void g(short);
 };
diff --git a/gcc/testsuite/g++.dg/warn/Woverloaded-virt10.C b/gcc/testsuite/g++.dg/warn/Woverloaded-virt10.C
new file mode 100644 (file)
index 0000000..42b8b0f
--- /dev/null
@@ -0,0 +1,11 @@
+// PR c++/117114 - Test case from PR c++/117114
+// { dg-do compile { target c++11 } }
+// { dg-additional-options -Woverloaded-virtual }
+
+struct Troops { virtual ~Troops(); };
+struct Control { virtual int GetControl() const = 0; };
+struct Army : Troops, Control { int GetControl() const override; };
+
+struct VirtualControl : virtual Control {
+  int GetControl() const override;
+};
diff --git a/gcc/testsuite/g++.dg/warn/Woverloaded-virt11.C b/gcc/testsuite/g++.dg/warn/Woverloaded-virt11.C
new file mode 100644 (file)
index 0000000..4d96b31
--- /dev/null
@@ -0,0 +1,25 @@
+// PR c++/109918 - More tests with multiple inheritance
+// { dg-do compile { target c++11 } }
+// { dg-additional-options -Woverloaded-virtual }
+
+struct Troops { virtual ~Troops(); };
+struct Control {
+  virtual int GetControl() const { return 42; } // { dg-warning "was hidden" }
+};
+struct Army : Troops, Control {
+  template<class T> void GetControl() const; // { dg-message "by" }
+};
+
+
+struct A {
+  virtual void get() const;
+};
+struct B {
+  virtual void get() const;
+  virtual void get(char) const; // { dg-warning "was hidden" }
+};
+
+struct C : A, B {
+  virtual void get() const; // { dg-message "by" }
+  virtual void get(int) const;
+};
diff --git a/gcc/testsuite/g++.dg/warn/Woverloaded-virt12.C b/gcc/testsuite/g++.dg/warn/Woverloaded-virt12.C
new file mode 100644 (file)
index 0000000..3cfe22c
--- /dev/null
@@ -0,0 +1,23 @@
+// PR c++/109918 - Test covariant return types
+// { dg-do compile { target c++11 } }
+// { dg-additional-options -Woverloaded-virtual }
+
+struct Big { virtual ~Big () {} };
+struct Smaller : Big {};
+
+// Single inheritance case
+struct Foo {
+  virtual Big* getMe() const;
+};
+struct Bar : Foo {
+  virtual Smaller* getMe() const;
+};
+
+// Multiple inheritance case
+struct Troops { virtual ~Troops(); };
+struct Control {
+  virtual Big* GetControl() const;
+};
+struct Army : Troops, Control {
+  virtual Smaller* GetControl() const;
+};
diff --git a/gcc/testsuite/g++.dg/warn/Woverloaded-virt13.C b/gcc/testsuite/g++.dg/warn/Woverloaded-virt13.C
new file mode 100644 (file)
index 0000000..fb421e1
--- /dev/null
@@ -0,0 +1,28 @@
+// PR c++/117114 - Reduced version of another bootstrap error
+// Unrelated methods can have the same DECL_VINDEX when the class hierarchy
+// depth is 2 or more.
+// { dg-do compile { target c++11 } }
+// { dg-additional-options -Woverloaded-virtual }
+
+class HIRFullVisitor;
+class HIRTypeVisitor;
+
+struct FullVisitable {
+  virtual void accept_vis (HIRFullVisitor &vis) = 0;
+};
+
+struct Node {
+  virtual ~Node() {}
+};
+
+struct Type : Node, FullVisitable
+{
+  using FullVisitable::accept_vis;
+  virtual void accept_vis (HIRTypeVisitor &vis) = 0;
+};
+
+struct TypePath : Type
+{
+  void accept_vis (HIRFullVisitor &vis) override;
+  void accept_vis (HIRTypeVisitor &vis) override;
+};
diff --git a/gcc/testsuite/g++.dg/warn/Woverloaded-virt5.C b/gcc/testsuite/g++.dg/warn/Woverloaded-virt5.C
new file mode 100644 (file)
index 0000000..01cd562
--- /dev/null
@@ -0,0 +1,12 @@
+// PR c++/109918 - Exact PR testcase
+// { dg-do compile }
+// { dg-additional-options -Wall }
+
+struct A {
+  virtual operator int() { return 42; }
+  virtual operator char() = 0;
+};
+
+struct B : public A {
+  operator char() { return 'A'; }
+};
diff --git a/gcc/testsuite/g++.dg/warn/Woverloaded-virt6.C b/gcc/testsuite/g++.dg/warn/Woverloaded-virt6.C
new file mode 100644 (file)
index 0000000..c18049e
--- /dev/null
@@ -0,0 +1,12 @@
+// PR c++/109918 - PR testcase with -Woverloaded-virtual=2
+// { dg-do compile }
+// { dg-additional-options -Woverloaded-virtual=2 }
+
+struct A {
+  virtual operator int() { return 42; }
+  virtual operator char() = 0;
+};
+
+struct B : public A {
+  operator char() { return 'A'; }
+};
diff --git a/gcc/testsuite/g++.dg/warn/Woverloaded-virt7.C b/gcc/testsuite/g++.dg/warn/Woverloaded-virt7.C
new file mode 100644 (file)
index 0000000..39b0899
--- /dev/null
@@ -0,0 +1,37 @@
+// PR c++/109918 - Test different CV-quals, usage of typedefs, and templates
+// { dg-do compile }
+// { dg-additional-options -Woverloaded-virtual }
+
+struct A {
+  virtual operator char() { return 'a'; }
+  virtual operator char() const { return 'b'; } // { dg-warning "was hidden" }
+  virtual operator int() { return 42; }
+};
+
+struct B : public A {
+  operator char() { return 'A'; } // { dg-note "operator char()" }
+  operator int() { return 43; }
+};
+
+typedef char MyChar;
+struct C : public A {
+  operator MyChar() { return 'A'; } // { dg-note "operator MyChar()" }
+  operator int() { return 43; }
+};
+
+struct D : public A {
+  template<class T>
+  operator T() { return T(); }
+};
+int d = D();
+
+struct AA {
+  virtual char func(char) { return 'a'; }
+  virtual char func(char) const { return 'b'; } // { dg-warning "was hidden" }
+  virtual int func(int) { return 42; }
+};
+
+struct BB : public AA {
+  char func(char) { return 'A'; } // { dg-note "by" }
+  int func(int) { return 43; }
+};
diff --git a/gcc/testsuite/g++.dg/warn/Woverloaded-virt8.C b/gcc/testsuite/g++.dg/warn/Woverloaded-virt8.C
new file mode 100644 (file)
index 0000000..51af2da
--- /dev/null
@@ -0,0 +1,15 @@
+// Identified when investigating PR c++/109918: no warning was emitted due to
+// an incorrect early return in warn_hidden.
+// { dg-additional-options -Wall }
+
+struct Foo
+{
+  virtual void f(int); // { dg-warning "hidden" }
+  virtual void g() {}
+};
+
+struct Bar : Foo
+{
+  virtual void f(short); // { dg-message "by" }
+  virtual void g() {}
+};
diff --git a/gcc/testsuite/g++.dg/warn/Woverloaded-virt9.C b/gcc/testsuite/g++.dg/warn/Woverloaded-virt9.C
new file mode 100644 (file)
index 0000000..6d315c6
--- /dev/null
@@ -0,0 +1,14 @@
+// PR c++/109918: Non virtual overloads in derived classes that don't override
+// anything shouldn't cause warnings, even at -Woverloaded-virtual=2
+// { dg-additional-options -Woverloaded-virtual=2 }
+
+struct Foo
+{
+  virtual void g() {}
+};
+
+struct Bar : Foo
+{
+  virtual void g() {}
+  void g(int) {}
+};