[clang] Warn about virtual methods in `final` classes (PR #131188)
Devon Loehr via cfe-commits
cfe-commits at lists.llvm.org
Fri Mar 14 11:25:47 PDT 2025
https://github.com/DKLoehr updated https://github.com/llvm/llvm-project/pull/131188
>From fbd474fb5ae3adeaf1644a4d44e916e4d7c66395 Mon Sep 17 00:00:00 2001
From: Devon Loehr <dloehr at google.com>
Date: Thu, 13 Mar 2025 17:34:27 +0000
Subject: [PATCH 1/6] Initial warning commit
---
clang/include/clang/Basic/DiagnosticGroups.td | 2 ++
.../clang/Basic/DiagnosticSemaKinds.td | 3 +++
clang/lib/Sema/SemaDeclCXX.cpp | 5 ++++
.../SemaCXX/unnecessary-virtual-specifier.cpp | 27 +++++++++++++++++++
4 files changed, 37 insertions(+)
create mode 100644 clang/test/SemaCXX/unnecessary-virtual-specifier.cpp
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td
index fac80fb4009aa..65593ddcb8608 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -372,6 +372,8 @@ def CXX11WarnInconsistentOverrideMethod :
def CXX11WarnSuggestOverrideDestructor : DiagGroup<"suggest-destructor-override">;
def CXX11WarnSuggestOverride : DiagGroup<"suggest-override">;
+def WarnUnnecessaryVirtualSpecifier : DiagGroup<"unnecessary-virtual-specifier">;
+
// Original name of this warning in Clang
def : DiagGroup<"c++0x-narrowing", [CXX11Narrowing]>;
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 8e6e6e892cdd7..a87cf7e674b4c 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -2706,6 +2706,9 @@ def warn_final_dtor_non_final_class : Warning<
InGroup<FinalDtorNonFinalClass>;
def note_final_dtor_non_final_class_silence : Note<
"mark %0 as '%select{final|sealed}1' to silence this warning">;
+def warn_unnecessary_virtual_specifier : Warning<
+ "virtual method %0 is inside a 'final' class and can never be overridden">,
+ InGroup<WarnUnnecessaryVirtualSpecifier>;
// C++11 attributes
def err_repeat_attribute : Error<"%0 attribute cannot be repeated">;
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index dd779ee377309..1b2e494956d4b 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -7193,10 +7193,15 @@ void Sema::CheckCompletedCXXClass(Scope *S, CXXRecordDecl *Record) {
// class without overriding any.
if (!M->isStatic())
DiagnoseHiddenVirtualMethods(M);
+
if (M->hasAttr<OverrideAttr>())
HasMethodWithOverrideControl = true;
else if (M->size_overridden_methods() > 0)
HasOverridingMethodWithoutOverrideControl = true;
+
+ // See if a method is marked as virtual inside of a final class.
+ if (M->isVirtualAsWritten() && Record->isEffectivelyFinal())
+ Diag(M->getLocation(), diag::warn_unnecessary_virtual_specifier) << M;
}
if (!isa<CXXDestructorDecl>(M))
diff --git a/clang/test/SemaCXX/unnecessary-virtual-specifier.cpp b/clang/test/SemaCXX/unnecessary-virtual-specifier.cpp
new file mode 100644
index 0000000000000..eb8397d9ade45
--- /dev/null
+++ b/clang/test/SemaCXX/unnecessary-virtual-specifier.cpp
@@ -0,0 +1,27 @@
+// RUN: %clang_cc1 -fsyntax-only -verify -Wunnecessary-virtual-specifier -Wsuggest-override %s
+
+struct Foo final {
+ Foo() = default;
+ virtual ~Foo() = default; // expected-warning {{virtual method}}
+ virtual Foo& operator=(Foo& other) = default; // expected-warning {{virtual method}}
+ virtual Foo& operator=(Foo&& other) = default; // expected-warning {{virtual method}}
+ void f();
+ virtual void f(int); // expected-warning {{virtual method}}
+ int g(int x) { return x; };
+ virtual int g(bool); // expected-warning {{virtual method}}
+ static int s();
+};
+
+struct BarBase {
+ virtual ~BarBase() = delete;
+ virtual void virt() {}
+ virtual int virt(int);
+ int nonvirt();
+};
+
+struct Bar final : BarBase {
+ ~Bar() override = delete;
+ void virt() override {};
+ virtual int virt(int) override; // expected-warning {{virtual method}}
+ int nonvirt();
+};
>From 4a16ef81a4882785708a4973f78586119235e8f5 Mon Sep 17 00:00:00 2001
From: Devon Loehr <dloehr at google.com>
Date: Thu, 13 Mar 2025 17:59:39 +0000
Subject: [PATCH 2/6] Add documentation
---
clang/docs/ReleaseNotes.rst | 3 +++
clang/include/clang/Basic/DiagnosticGroups.td | 11 ++++++++++-
2 files changed, 13 insertions(+), 1 deletion(-)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 8989124611e66..15858631c17b7 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -247,6 +247,9 @@ Improvements to Clang's diagnostics
- The ``-Wsign-compare`` warning now treats expressions with bitwise not(~) and minus(-) as signed integers
except for the case where the operand is an unsigned integer
and throws warning if they are compared with unsigned integers (##18878).
+- The ``-Wunnecessary-virtual-specifier`` warning has been added to warn about
+ methods which are marked as virtual inside a ``final`` class, and hence can
+ never be overridden.
- Improve the diagnostics for chained comparisons to report actual expressions and operators (#GH129069).
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td
index 65593ddcb8608..e785e84205192 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -372,7 +372,16 @@ def CXX11WarnInconsistentOverrideMethod :
def CXX11WarnSuggestOverrideDestructor : DiagGroup<"suggest-destructor-override">;
def CXX11WarnSuggestOverride : DiagGroup<"suggest-override">;
-def WarnUnnecessaryVirtualSpecifier : DiagGroup<"unnecessary-virtual-specifier">;
+def WarnUnnecessaryVirtualSpecifier : DiagGroup<"unnecessary-virtual-specifier"> {
+ code Documentation = [{
+Warns when a ``final`` class contains a virtual method (including virtual
+destructors). Since ``final`` classes cannot be subclassed, their methods
+cannot be overridden, and hence the ``virtual`` specifier is useless.
+
+The warning also detects virtual methods in classes whose destructor is
+``final``, for the same reason.
+ }];
+}
// Original name of this warning in Clang
def : DiagGroup<"c++0x-narrowing", [CXX11Narrowing]>;
>From 11f33b66533f58a5180f58581c21067b9a422b62 Mon Sep 17 00:00:00 2001
From: Devon Loehr <dloehr at google.com>
Date: Thu, 13 Mar 2025 18:18:15 +0000
Subject: [PATCH 3/6] Make DefaultIgnore
---
clang/include/clang/Basic/DiagnosticSemaKinds.td | 2 +-
clang/test/SemaCXX/unnecessary-virtual-specifier.cpp | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index a87cf7e674b4c..ed31c2d225073 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -2708,7 +2708,7 @@ def note_final_dtor_non_final_class_silence : Note<
"mark %0 as '%select{final|sealed}1' to silence this warning">;
def warn_unnecessary_virtual_specifier : Warning<
"virtual method %0 is inside a 'final' class and can never be overridden">,
- InGroup<WarnUnnecessaryVirtualSpecifier>;
+ InGroup<WarnUnnecessaryVirtualSpecifier>, DefaultIgnore;
// C++11 attributes
def err_repeat_attribute : Error<"%0 attribute cannot be repeated">;
diff --git a/clang/test/SemaCXX/unnecessary-virtual-specifier.cpp b/clang/test/SemaCXX/unnecessary-virtual-specifier.cpp
index eb8397d9ade45..00f696bfa0d4f 100644
--- a/clang/test/SemaCXX/unnecessary-virtual-specifier.cpp
+++ b/clang/test/SemaCXX/unnecessary-virtual-specifier.cpp
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 -fsyntax-only -verify -Wunnecessary-virtual-specifier -Wsuggest-override %s
+// RUN: %clang_cc1 -fsyntax-only -verify -Wunnecessary-virtual-specifier %s
struct Foo final {
Foo() = default;
>From eb03ecf974d475021233cdf63e50dad804132bff Mon Sep 17 00:00:00 2001
From: Devon Loehr <dloehr at google.com>
Date: Fri, 14 Mar 2025 14:24:13 +0000
Subject: [PATCH 4/6] Don't warn on virtual...override.
---
clang/include/clang/Basic/DiagnosticGroups.td | 2 ++
clang/lib/Sema/SemaDeclCXX.cpp | 18 +++++++++++-------
.../SemaCXX/unnecessary-virtual-specifier.cpp | 17 +++++++++--------
3 files changed, 22 insertions(+), 15 deletions(-)
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td
index e785e84205192..e54f921741269 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -380,6 +380,8 @@ cannot be overridden, and hence the ``virtual`` specifier is useless.
The warning also detects virtual methods in classes whose destructor is
``final``, for the same reason.
+
+The warning does not fire on virtual methods which are also marked ``override``.
}];
}
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 1b2e494956d4b..381b81b5b832a 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -7194,14 +7194,18 @@ void Sema::CheckCompletedCXXClass(Scope *S, CXXRecordDecl *Record) {
if (!M->isStatic())
DiagnoseHiddenVirtualMethods(M);
- if (M->hasAttr<OverrideAttr>())
+ if (M->hasAttr<OverrideAttr>()) {
HasMethodWithOverrideControl = true;
- else if (M->size_overridden_methods() > 0)
- HasOverridingMethodWithoutOverrideControl = true;
-
- // See if a method is marked as virtual inside of a final class.
- if (M->isVirtualAsWritten() && Record->isEffectivelyFinal())
- Diag(M->getLocation(), diag::warn_unnecessary_virtual_specifier) << M;
+ } else {
+ if (M->size_overridden_methods() > 0)
+ HasOverridingMethodWithoutOverrideControl = true;
+
+ // Warn on virtual methods in final classes, unless they're also
+ // marked `override`.
+ if (M->isVirtualAsWritten() && Record->isEffectivelyFinal())
+ Diag(M->getLocation(), diag::warn_unnecessary_virtual_specifier)
+ << M;
+ }
}
if (!isa<CXXDestructorDecl>(M))
diff --git a/clang/test/SemaCXX/unnecessary-virtual-specifier.cpp b/clang/test/SemaCXX/unnecessary-virtual-specifier.cpp
index 00f696bfa0d4f..b1fa6145e34e1 100644
--- a/clang/test/SemaCXX/unnecessary-virtual-specifier.cpp
+++ b/clang/test/SemaCXX/unnecessary-virtual-specifier.cpp
@@ -2,10 +2,10 @@
struct Foo final {
Foo() = default;
- virtual ~Foo() = default; // expected-warning {{virtual method}}
- virtual Foo& operator=(Foo& other) = default; // expected-warning {{virtual method}}
- virtual Foo& operator=(Foo&& other) = default; // expected-warning {{virtual method}}
- void f();
+ virtual ~Foo() = default; // expected-warning {{virtual method}}
+ virtual Foo& operator=(Foo& other) = default; // expected-warning {{virtual method}}
+ virtual Foo& operator=(Foo&& other) = default; // expected-warning {{virtual method}}
+ void f();
virtual void f(int); // expected-warning {{virtual method}}
int g(int x) { return x; };
virtual int g(bool); // expected-warning {{virtual method}}
@@ -14,14 +14,15 @@ struct Foo final {
struct BarBase {
virtual ~BarBase() = delete;
- virtual void virt() {}
- virtual int virt(int);
+ virtual void virt() {}
+ virtual int virt(int);
int nonvirt();
};
struct Bar final : BarBase {
~Bar() override = delete;
- void virt() override {};
- virtual int virt(int) override; // expected-warning {{virtual method}}
+ void virt() override {};
+ // `virtual ... override;` is a common pattern, so don't warn
+ virtual int virt(int) override;
int nonvirt();
};
>From dcff45307f492c36ed56ece8ad46d0b5a453825c Mon Sep 17 00:00:00 2001
From: Devon Loehr <dloehr at google.com>
Date: Fri, 14 Mar 2025 15:05:30 +0000
Subject: [PATCH 5/6] One more test
---
clang/test/SemaCXX/unnecessary-virtual-specifier.cpp | 1 +
1 file changed, 1 insertion(+)
diff --git a/clang/test/SemaCXX/unnecessary-virtual-specifier.cpp b/clang/test/SemaCXX/unnecessary-virtual-specifier.cpp
index b1fa6145e34e1..954629de51ebb 100644
--- a/clang/test/SemaCXX/unnecessary-virtual-specifier.cpp
+++ b/clang/test/SemaCXX/unnecessary-virtual-specifier.cpp
@@ -24,5 +24,6 @@ struct Bar final : BarBase {
void virt() override {};
// `virtual ... override;` is a common pattern, so don't warn
virtual int virt(int) override;
+ virtual int virt(bool); // expected-warning {{virtual method}}
int nonvirt();
};
>From 59a4fe7348cf2e2095b176abccba86e7772e0e6a Mon Sep 17 00:00:00 2001
From: Devon Loehr <dloehr at google.com>
Date: Fri, 14 Mar 2025 18:24:09 +0000
Subject: [PATCH 6/6] Disable for functions that were already virtual
---
clang/lib/Sema/SemaDeclCXX.cpp | 11 +++++------
.../test/SemaCXX/unnecessary-virtual-specifier.cpp | 14 ++++++++------
2 files changed, 13 insertions(+), 12 deletions(-)
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 381b81b5b832a..1ac294edfb3f0 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -7196,15 +7196,14 @@ void Sema::CheckCompletedCXXClass(Scope *S, CXXRecordDecl *Record) {
if (M->hasAttr<OverrideAttr>()) {
HasMethodWithOverrideControl = true;
+ } else if (M->size_overridden_methods() > 0) {
+ HasOverridingMethodWithoutOverrideControl = true;
} else {
- if (M->size_overridden_methods() > 0)
- HasOverridingMethodWithoutOverrideControl = true;
-
- // Warn on virtual methods in final classes, unless they're also
- // marked `override`.
- if (M->isVirtualAsWritten() && Record->isEffectivelyFinal())
+ // Warn on newly-declared virtual methods in `final` classes
+ if (M->isVirtualAsWritten() && Record->isEffectivelyFinal()) {
Diag(M->getLocation(), diag::warn_unnecessary_virtual_specifier)
<< M;
+ }
}
}
diff --git a/clang/test/SemaCXX/unnecessary-virtual-specifier.cpp b/clang/test/SemaCXX/unnecessary-virtual-specifier.cpp
index 954629de51ebb..789710eccf729 100644
--- a/clang/test/SemaCXX/unnecessary-virtual-specifier.cpp
+++ b/clang/test/SemaCXX/unnecessary-virtual-specifier.cpp
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 -fsyntax-only -verify -Wunnecessary-virtual-specifier %s
+// RUN: %clang_cc1 -fsyntax-only -verify -Wunnecessary-virtual-specifier -Wno-inconsistent-missing-override %s
struct Foo final {
Foo() = default;
@@ -15,15 +15,17 @@ struct Foo final {
struct BarBase {
virtual ~BarBase() = delete;
virtual void virt() {}
- virtual int virt(int);
+ virtual int virt2(int);
+ virtual bool virt3(bool);
int nonvirt();
};
struct Bar final : BarBase {
~Bar() override = delete;
- void virt() override {};
- // `virtual ... override;` is a common pattern, so don't warn
- virtual int virt(int) override;
- virtual int virt(bool); // expected-warning {{virtual method}}
+ void virt() override {};
+ virtual int virt2(int) override; // `virtual ... override;` is a common pattern, so don't warn
+ virtual bool virt3(bool); // Already virtual in the base class; triggers
+ // -Winconsistent-missing-override or -Wsuggest-override instead
+ virtual int new_virt(bool); // expected-warning {{virtual method}}
int nonvirt();
};
More information about the cfe-commits
mailing list