10#include "../utils/LexerUtils.h"
11#include "clang/AST/ASTContext.h"
12#include "clang/ASTMatchers/ASTMatchFinder.h"
13#include "clang/Lex/Lexer.h"
21AST_MATCHER(CXXRecordDecl, hasPublicVirtualOrProtectedNonVirtualDestructor) {
26 const CXXDestructorDecl *Destructor =
Node.getDestructor();
30 return (((Destructor->getAccess() == AccessSpecifier::AS_public) &&
31 Destructor->isVirtual()) ||
32 ((Destructor->getAccess() == AccessSpecifier::AS_protected) &&
33 !Destructor->isVirtual()));
37 ast_matchers::internal::Matcher<CXXRecordDecl> InheritsVirtualMethod =
38 hasAnyBase(hasType(cxxRecordDecl(has(cxxMethodDecl(isVirtual())))));
42 anyOf(has(cxxMethodDecl(isVirtual())), InheritsVirtualMethod),
44 unless(hasPublicVirtualOrProtectedNonVirtualDestructor()))
45 .bind(
"ProblematicClassOrStruct"),
49static std::optional<CharSourceRange>
51 const SourceManager &SM,
const LangOptions &LangOpts) {
52 if (Destructor.getLocation().isMacroID())
55 SourceLocation VirtualBeginLoc = Destructor.getBeginLoc();
56 SourceLocation VirtualBeginSpellingLoc =
57 SM.getSpellingLoc(Destructor.getBeginLoc());
58 SourceLocation VirtualEndLoc = VirtualBeginSpellingLoc.getLocWithOffset(
59 Lexer::MeasureTokenLength(VirtualBeginSpellingLoc, SM, LangOpts));
63 std::optional<Token> NextToken =
64 Lexer::findNextToken(VirtualEndLoc, SM, LangOpts);
67 SourceLocation StartOfNextToken = NextToken->getLocation();
69 return CharSourceRange::getCharRange(VirtualBeginLoc, StartOfNextToken);
72static const AccessSpecDecl *
74 for (DeclContext::specific_decl_iterator<AccessSpecDecl>
75 AS{StructOrClass.decls_begin()},
76 ASEnd{StructOrClass.decls_end()};
78 AccessSpecDecl *ASDecl = *AS;
79 if (ASDecl->getAccess() == AccessSpecifier::AS_public)
88 const SourceManager &SourceManager) {
89 std::string DestructorString;
91 bool AppendLineBreak =
false;
95 if (!AccessSpecDecl) {
96 if (StructOrClass.isClass()) {
97 Loc = StructOrClass.getEndLoc();
98 DestructorString =
"public:";
99 AppendLineBreak =
true;
101 Loc = StructOrClass.getBraceRange().getBegin().getLocWithOffset(1);
104 Loc = AccessSpecDecl->getEndLoc().getLocWithOffset(1);
107 DestructorString = (llvm::Twine(DestructorString) +
"\nvirtual ~" +
108 StructOrClass.getName().str() +
"() = default;" +
109 (AppendLineBreak ?
"\n" :
""))
112 return FixItHint::CreateInsertion(
Loc, DestructorString);
116 std::string SourceText;
117 llvm::raw_string_ostream DestructorStream(SourceText);
118 Destructor.print(DestructorStream);
123 const std::string &Keyword) {
124 size_t KeywordIndex = DestructorString.find(Keyword);
125 if (KeywordIndex != std::string::npos)
126 DestructorString.erase(KeywordIndex, Keyword.length());
127 return DestructorString;
131 const std::string &Visibility,
const CXXDestructorDecl &Destructor,
132 const SourceManager &SM,
const LangOptions &LangOpts) {
133 std::string DestructorString =
134 (llvm::Twine() + Visibility +
":\n" +
135 (Visibility ==
"public" && !Destructor.isVirtual() ?
"virtual " :
""))
139 if (Visibility ==
"protected" && Destructor.isVirtualAsWritten())
140 OriginalDestructor =
eraseKeyword(OriginalDestructor,
"virtual ");
143 (llvm::Twine(DestructorString) + OriginalDestructor +
144 (Destructor.isExplicitlyDefaulted() ?
";\n" :
"") +
"private:")
150 SourceLocation EndLocation;
151 if (Destructor.isExplicitlyDefaulted())
154 .getLocWithOffset(1);
156 EndLocation = Destructor.getEndLoc().getLocWithOffset(1);
158 auto OriginalDestructorRange =
159 CharSourceRange::getCharRange(Destructor.getBeginLoc(), EndLocation);
160 return FixItHint::CreateReplacement(OriginalDestructorRange,
165 const MatchFinder::MatchResult &Result) {
167 const auto *MatchedClassOrStruct =
168 Result.Nodes.getNodeAs<CXXRecordDecl>(
"ProblematicClassOrStruct");
170 const CXXDestructorDecl *Destructor = MatchedClassOrStruct->getDestructor();
174 if (Destructor->getAccess() == AccessSpecifier::AS_private) {
175 diag(MatchedClassOrStruct->getLocation(),
176 "destructor of %0 is private and prevents using the type")
177 << MatchedClassOrStruct;
178 diag(MatchedClassOrStruct->getLocation(),
179 "make it public and virtual", DiagnosticIDs::Note)
181 "public", *Destructor, *Result.SourceManager,
getLangOpts());
182 diag(MatchedClassOrStruct->getLocation(),
183 "make it protected", DiagnosticIDs::Note)
185 "protected", *Destructor, *Result.SourceManager,
getLangOpts());
191 bool ProtectedAndVirtual =
false;
194 if (MatchedClassOrStruct->hasUserDeclaredDestructor()) {
195 if (Destructor->getAccess() == AccessSpecifier::AS_public) {
196 Fix = FixItHint::CreateInsertion(Destructor->getLocation(),
"virtual ");
197 }
else if (Destructor->getAccess() == AccessSpecifier::AS_protected) {
198 ProtectedAndVirtual =
true;
199 if (
const auto MaybeRange =
201 Result.Context->getLangOpts()))
202 Fix = FixItHint::CreateRemoval(*MaybeRange);
206 *Result.SourceManager);
209 diag(MatchedClassOrStruct->getLocation(),
210 "destructor of %0 is %select{public and non-virtual|protected and "
212 << MatchedClassOrStruct << ProtectedAndVirtual;
213 diag(MatchedClassOrStruct->getLocation(),
214 "make it %select{public and virtual|protected and non-virtual}0",
216 << ProtectedAndVirtual <<
Fix;
static cl::opt< bool > Fix("fix", desc(R"(
Apply suggested fixes. Without -fix-errors
clang-tidy will bail out if any compilation
errors were found.
)"), cl::init(false), cl::cat(ClangTidyCategory))
::clang::DynTypedNode Node
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check's name.
const LangOptions & getLangOpts() const
Returns the language options from the context.
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
static std::string eraseKeyword(std::string &DestructorString, const std::string &Keyword)
static const AccessSpecDecl * getPublicASDecl(const CXXRecordDecl &StructOrClass)
AST_MATCHER(CXXRecordDecl, hasPublicVirtualOrProtectedNonVirtualDestructor)
static FixItHint changePrivateDestructorVisibilityTo(const std::string &Visibility, const CXXDestructorDecl &Destructor, const SourceManager &SM, const LangOptions &LangOpts)
static std::string getSourceText(const CXXDestructorDecl &Destructor)
static std::optional< CharSourceRange > getVirtualKeywordRange(const CXXDestructorDecl &Destructor, const SourceManager &SM, const LangOptions &LangOpts)
static FixItHint generateUserDeclaredDestructor(const CXXRecordDecl &StructOrClass, const SourceManager &SourceManager)
SourceLocation findNextTerminator(SourceLocation Start, const SourceManager &SM, const LangOptions &LangOpts)