Skip to content

Commit 2ff370f

Browse files
authored
Warn about virtual methods in final classes (#131188)
There's never any point to adding a `virtual` specifier to methods in a `final` class, since the class can't be subclassed. This adds a warning when we notice this happening, as suggested in #131108. We don't currently implement the second part of the suggestion, to warn on `virtual` methods which are never overridden anywhere. Although it's feasible to do this for things with internal linkage (so we can check at the end of the TU), it's more complicated to implement and it's not clear it's worth the effort. I tested the warning by compiling chromium and clang itself. Chromium resulted in [277 warnings across 109 files](https://github.com/user-attachments/files/19234889/warnings-chromium.txt), while clang had [38 warnings across 29 files](https://github.com/user-attachments/files/19234888/warnings-clang.txt). I inspected a subset of the warning sites manually, and they all seemed legitimate. This warning is very easy to fix (just remove the `virtual` specifier) and I haven't seen any false positives, so it's suitable for on-by-default. However, I've currently made it off-by-default because it fires at several places in the repo. I plan to submit a followup PR fixing those places and enabling the warning by default.
1 parent 1c3a9a8 commit 2ff370f

File tree

5 files changed

+60
-2
lines changed

5 files changed

+60
-2
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,9 @@ Improvements to Clang's diagnostics
251251
- The ``-Wsign-compare`` warning now treats expressions with bitwise not(~) and minus(-) as signed integers
252252
except for the case where the operand is an unsigned integer
253253
and throws warning if they are compared with unsigned integers (##18878).
254+
- The ``-Wunnecessary-virtual-specifier`` warning has been added to warn about
255+
methods which are marked as virtual inside a ``final`` class, and hence can
256+
never be overridden.
254257

255258
- Improve the diagnostics for chained comparisons to report actual expressions and operators (#GH129069).
256259

clang/include/clang/Basic/DiagnosticGroups.td

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,19 @@ def CXX11WarnInconsistentOverrideMethod :
372372
def CXX11WarnSuggestOverrideDestructor : DiagGroup<"suggest-destructor-override">;
373373
def CXX11WarnSuggestOverride : DiagGroup<"suggest-override">;
374374

375+
def WarnUnnecessaryVirtualSpecifier : DiagGroup<"unnecessary-virtual-specifier"> {
376+
code Documentation = [{
377+
Warns when a ``final`` class contains a virtual method (including virtual
378+
destructors). Since ``final`` classes cannot be subclassed, their methods
379+
cannot be overridden, and hence the ``virtual`` specifier is useless.
380+
381+
The warning also detects virtual methods in classes whose destructor is
382+
``final``, for the same reason.
383+
384+
The warning does not fire on virtual methods which are also marked ``override``.
385+
}];
386+
}
387+
375388
// Original name of this warning in Clang
376389
def : DiagGroup<"c++0x-narrowing", [CXX11Narrowing]>;
377390

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2706,6 +2706,9 @@ def warn_final_dtor_non_final_class : Warning<
27062706
InGroup<FinalDtorNonFinalClass>;
27072707
def note_final_dtor_non_final_class_silence : Note<
27082708
"mark %0 as '%select{final|sealed}1' to silence this warning">;
2709+
def warn_unnecessary_virtual_specifier : Warning<
2710+
"virtual method %0 is inside a 'final' class and can never be overridden">,
2711+
InGroup<WarnUnnecessaryVirtualSpecifier>, DefaultIgnore;
27092712

27102713
// C++11 attributes
27112714
def err_repeat_attribute : Error<"%0 attribute cannot be repeated">;

clang/lib/Sema/SemaDeclCXX.cpp

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7193,10 +7193,18 @@ void Sema::CheckCompletedCXXClass(Scope *S, CXXRecordDecl *Record) {
71937193
// class without overriding any.
71947194
if (!M->isStatic())
71957195
DiagnoseHiddenVirtualMethods(M);
7196-
if (M->hasAttr<OverrideAttr>())
7196+
7197+
if (M->hasAttr<OverrideAttr>()) {
71977198
HasMethodWithOverrideControl = true;
7198-
else if (M->size_overridden_methods() > 0)
7199+
} else if (M->size_overridden_methods() > 0) {
71997200
HasOverridingMethodWithoutOverrideControl = true;
7201+
} else {
7202+
// Warn on newly-declared virtual methods in `final` classes
7203+
if (M->isVirtualAsWritten() && Record->isEffectivelyFinal()) {
7204+
Diag(M->getLocation(), diag::warn_unnecessary_virtual_specifier)
7205+
<< M;
7206+
}
7207+
}
72007208
}
72017209

72027210
if (!isa<CXXDestructorDecl>(M))
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// RUN: %clang_cc1 -fsyntax-only -verify -Wunnecessary-virtual-specifier -Wno-inconsistent-missing-override %s
2+
3+
struct Foo final {
4+
Foo() = default;
5+
virtual ~Foo() = default; // expected-warning {{virtual method}}
6+
virtual Foo& operator=(Foo& other) = default; // expected-warning {{virtual method}}
7+
virtual Foo& operator=(Foo&& other) = default; // expected-warning {{virtual method}}
8+
void f();
9+
virtual void f(int); // expected-warning {{virtual method}}
10+
int g(int x) { return x; };
11+
virtual int g(bool); // expected-warning {{virtual method}}
12+
static int s();
13+
};
14+
15+
struct BarBase {
16+
virtual ~BarBase() = delete;
17+
virtual void virt() {}
18+
virtual int virt2(int);
19+
virtual bool virt3(bool);
20+
int nonvirt();
21+
};
22+
23+
struct Bar final : BarBase {
24+
~Bar() override = delete;
25+
void virt() override {};
26+
virtual int virt2(int) override; // `virtual ... override;` is a common pattern, so don't warn
27+
virtual bool virt3(bool); // Already virtual in the base class; triggers
28+
// -Winconsistent-missing-override or -Wsuggest-override instead
29+
virtual int new_virt(bool); // expected-warning {{virtual method}}
30+
int nonvirt();
31+
};

0 commit comments

Comments
 (0)