Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 9 additions & 75 deletions clang-tools-extra/clang-tidy/modernize/UseOverrideCheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,6 @@ parseTokens(CharSourceRange Range, const MatchFinder::MatchResult &Result) {
return Tokens;
}

static StringRef getText(const Token &Tok, const SourceManager &Sources) {
return {Sources.getCharacterData(Tok.getLocation()), Tok.getLength()};
}

void UseOverrideCheck::check(const MatchFinder::MatchResult &Result) {
const auto *Method = Result.Nodes.getNodeAs<FunctionDecl>("method");
const SourceManager &Sources = *Result.SourceManager;
Expand Down Expand Up @@ -141,90 +137,28 @@ void UseOverrideCheck::check(const MatchFinder::MatchResult &Result) {
if (!FileRange.isValid())
return;

// FIXME: Instead of re-lexing and looking for specific macros such as
// 'ABSTRACT', properly store the location of 'virtual' and '= 0' in each
// FunctionDecl.
// FIXME: Instead of re-lexing and looking for the 'virtual' token,
// store the location of 'virtual' in each FunctionDecl.
SmallVector<Token, 16> Tokens = parseTokens(FileRange, Result);

// Add 'override' on inline declarations that don't already have it.
if (!HasFinal && !HasOverride) {
SourceLocation InsertLoc;
std::string ReplacementText = (OverrideSpelling + " ").str();
const SourceLocation MethodLoc = Method->getLocation();

for (const Token T : Tokens) {
if (T.is(tok::kw___attribute) &&
!Sources.isBeforeInTranslationUnit(T.getLocation(), MethodLoc)) {
InsertLoc = T.getLocation();
break;
}
}

if (Method->hasAttrs()) {
for (const clang::Attr *A : Method->getAttrs()) {
if (!A->isImplicit() && !A->isInherited()) {
const SourceLocation Loc =
Sources.getExpansionLoc(A->getRange().getBegin());
if ((!InsertLoc.isValid() ||
Sources.isBeforeInTranslationUnit(Loc, InsertLoc)) &&
!Sources.isBeforeInTranslationUnit(Loc, MethodLoc))
InsertLoc = Loc;
}
}
}

if (InsertLoc.isInvalid() && Method->doesThisDeclarationHaveABody() &&
Method->getBody() && !Method->isDefaulted()) {
// For methods with inline definition, add the override keyword at the
// end of the declaration of the function, but prefer to put it on the
// same line as the declaration if the beginning brace for the start of
// the body falls on the next line.
ReplacementText = (" " + OverrideSpelling).str();
auto *LastTokenIter = std::prev(Tokens.end());
// When try statement is used instead of compound statement as
// method body - insert override keyword before it.
if (LastTokenIter->is(tok::kw_try))
LastTokenIter = std::prev(LastTokenIter);
InsertLoc = LastTokenIter->getEndLoc();
}

if (!InsertLoc.isValid()) {
// For declarations marked with "= 0" or "= [default|delete]", the end
// location will point until after those markings. Therefore, the override
// keyword shouldn't be inserted at the end, but before the '='.
if (Tokens.size() > 2 &&
(getText(Tokens.back(), Sources) == "0" ||
Tokens.back().is(tok::kw_default) ||
Tokens.back().is(tok::kw_delete)) &&
getText(Tokens[Tokens.size() - 2], Sources) == "=") {
InsertLoc = Tokens[Tokens.size() - 2].getLocation();
// Check if we need to insert a space.
if ((Tokens[Tokens.size() - 2].getFlags() & Token::LeadingSpace) == 0)
ReplacementText = (" " + OverrideSpelling + " ").str();
} else if (getText(Tokens.back(), Sources) == "ABSTRACT")
InsertLoc = Tokens.back().getLocation();
}

if (!InsertLoc.isValid()) {
InsertLoc = FileRange.getEnd();
ReplacementText = (" " + OverrideSpelling).str();
}

// If the override macro has been specified just ensure it exists,
// if not don't apply a fixit but keep the warning.
if (OverrideSpelling != "override" &&
!Context.Idents.get(OverrideSpelling).hasMacroDefinition())
return;

Diag << FixItHint::CreateInsertion(InsertLoc, ReplacementText);
Diag << FixItHint::CreateInsertion(
Lexer::getLocForEndOfToken(
Method->getTypeSourceInfo()->getTypeLoc().getEndLoc(), 0, Sources,
getLangOpts()),
(" " + OverrideSpelling).str());
Copy link
Member

@zeyi2 zeyi2 Dec 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: May intoduce excessive whitespace? Although formatting is the job of clang-format.
(Fine if left unaddressed)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I can't think of a case where the added space is undesired; looking through the tests, the resulting formatting looks correct in all the // CHECK-FIXES lines.

}

if (HasFinal && HasOverride && !AllowOverrideAndFinal) {
const SourceLocation OverrideLoc =
Method->getAttr<OverrideAttr>()->getLocation();
if (HasFinal && HasOverride && !AllowOverrideAndFinal)
Diag << FixItHint::CreateRemoval(
CharSourceRange::getTokenRange(OverrideLoc, OverrideLoc));
}
Method->getAttr<OverrideAttr>()->getLocation());

if (HasVirtual) {
for (const Token Tok : Tokens) {
Expand Down
5 changes: 5 additions & 0 deletions clang-tools-extra/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,11 @@ Changes in existing checks
on Windows when the check was enabled with a 32-bit :program:`clang-tidy`
binary.

- Improved :doc:`modernize-use-override
<clang-tidy/checks/modernize/use-override>` by fixing an issue where
the check would sometimes suggest inserting ``override`` in an invalid
place.

- Improved :doc:`modernize-use-ranges
<clang-tidy/checks/modernize/use-ranges>` check to suggest using
the more idiomatic ``std::views::reverse`` where it used to suggest
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// RUN: %check_clang_tidy -std=c++26-or-later %s modernize-use-override,cppcoreguidelines-explicit-virtual-functions %t

struct Base {
virtual void f() = delete("");
};

struct Derived : Base {
virtual void f() = delete("");
// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: prefer using 'override' or (rarely) 'final' instead of 'virtual'
// CHECK-FIXES: void f() override = delete("");
};
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ struct Base {
virtual void t() throw();

virtual void il(IntPair);

virtual void u(int x __attribute__((unused))) {}
};

struct SimpleCases : public Base {
Expand Down Expand Up @@ -82,11 +84,11 @@ struct SimpleCases : public Base {

virtual void f()=0;
// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: prefer using
// CHECK-FIXES: void f() override =0;
// CHECK-FIXES: void f() override=0;

virtual void f2() const=0;
// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: prefer using
// CHECK-FIXES: void f2() const override =0;
// CHECK-FIXES: void f2() const override=0;

virtual void g() ABSTRACT;
// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: prefer using
Expand Down Expand Up @@ -132,6 +134,10 @@ struct SimpleCases : public Base {
virtual /* */ void g2();
// CHECK-MESSAGES: :[[@LINE-1]]:33: warning: prefer using 'override' or (rarely) 'final' instead of 'virtual'
// CHECK-FIXES: /* */ void g2() override;

virtual void u(int x __attribute__((unused)));
// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: prefer using 'override' or (rarely) 'final' instead of 'virtual'
// CHECK-FIXES: void u(int x __attribute__((unused))) override;
};

// CHECK-MESSAGES-NOT: warning:
Expand Down Expand Up @@ -308,7 +314,7 @@ struct MembersOfSpecializations : public Base2 {
// CHECK-FIXES: void a() override;
};
template <> void MembersOfSpecializations<3>::a() {}
void ff() { MembersOfSpecializations<3>().a(); };
void ff() { MembersOfSpecializations<3>().a(); }

// In case try statement is used as a method body,
// make sure that override fix is placed before try keyword.
Expand Down