10 #include "../utils/LexerUtils.h"
11 #include "clang/AST/ASTContext.h"
12 #include "clang/ASTMatchers/ASTMatchFinder.h"
13 #include "clang/Lex/Lexer.h"
20 namespace cppcoreguidelines {
22 AST_MATCHER(CXXRecordDecl, hasPublicVirtualOrProtectedNonVirtualDestructor) {
27 const CXXDestructorDecl *Destructor = Node.getDestructor();
31 return (((Destructor->getAccess() == AccessSpecifier::AS_public) &&
32 Destructor->isVirtual()) ||
33 ((Destructor->getAccess() == AccessSpecifier::AS_protected) &&
34 !Destructor->isVirtual()));
37 void VirtualClassDestructorCheck::registerMatchers(MatchFinder *Finder) {
38 ast_matchers::internal::Matcher<CXXRecordDecl> InheritsVirtualMethod =
39 hasAnyBase(hasType(cxxRecordDecl(has(cxxMethodDecl(isVirtual())))));
43 anyOf(has(cxxMethodDecl(isVirtual())), InheritsVirtualMethod),
44 unless(hasPublicVirtualOrProtectedNonVirtualDestructor()))
45 .bind(
"ProblematicClassOrStruct"),
49 static Optional<CharSourceRange>
51 const SourceManager &SM,
const LangOptions &
LangOpts) {
52 if (Destructor.getLocation().isMacroID())
55 SourceLocation VirtualBeginLoc = Destructor.getBeginLoc();
56 SourceLocation VirtualEndLoc = VirtualBeginLoc.getLocWithOffset(
57 Lexer::MeasureTokenLength(VirtualBeginLoc, SM,
LangOpts));
61 SourceLocation StartOfNextToken =
62 Lexer::findNextToken(VirtualEndLoc, SM,
LangOpts)
66 return CharSourceRange::getCharRange(VirtualBeginLoc, StartOfNextToken);
69 static const AccessSpecDecl *
71 for (DeclContext::specific_decl_iterator<AccessSpecDecl>
72 AS{StructOrClass.decls_begin()},
73 ASEnd{StructOrClass.decls_end()};
75 AccessSpecDecl *ASDecl = *AS;
76 if (ASDecl->getAccess() == AccessSpecifier::AS_public)
85 const SourceManager &SourceManager) {
86 std::string DestructorString;
88 bool AppendLineBreak =
false;
92 if (!AccessSpecDecl) {
93 if (StructOrClass.isClass()) {
94 Loc = StructOrClass.getEndLoc();
95 DestructorString =
"public:";
96 AppendLineBreak =
true;
98 Loc = StructOrClass.getBraceRange().getBegin().getLocWithOffset(1);
101 Loc = AccessSpecDecl->getEndLoc().getLocWithOffset(1);
104 DestructorString = (llvm::Twine(DestructorString) +
"\nvirtual ~" +
105 StructOrClass.getName().str() +
"() = default;" +
106 (AppendLineBreak ?
"\n" :
""))
109 return FixItHint::CreateInsertion(
Loc, DestructorString);
113 std::string SourceText;
114 llvm::raw_string_ostream DestructorStream(SourceText);
115 Destructor.print(DestructorStream);
120 const std::string &Keyword) {
121 size_t KeywordIndex = DestructorString.find(Keyword);
122 if (KeywordIndex != std::string::npos)
123 DestructorString.erase(KeywordIndex, Keyword.length());
124 return DestructorString;
128 const std::string &Visibility,
const CXXDestructorDecl &Destructor,
129 const SourceManager &SM,
const LangOptions &
LangOpts) {
130 std::string DestructorString =
131 (llvm::Twine() + Visibility +
":\n" +
132 (Visibility ==
"public" && !Destructor.isVirtual() ?
"virtual " :
""))
136 if (Visibility ==
"protected" && Destructor.isVirtualAsWritten())
137 OriginalDestructor =
eraseKeyword(OriginalDestructor,
"virtual ");
140 (llvm::Twine(DestructorString) + OriginalDestructor +
141 (Destructor.isExplicitlyDefaulted() ?
";\n" :
"") +
"private:")
147 SourceLocation EndLocation;
148 if (Destructor.isExplicitlyDefaulted())
151 .getLocWithOffset(1);
153 EndLocation = Destructor.getEndLoc().getLocWithOffset(1);
155 auto OriginalDestructorRange =
156 CharSourceRange::getCharRange(Destructor.getBeginLoc(), EndLocation);
157 return FixItHint::CreateReplacement(OriginalDestructorRange,
162 const MatchFinder::MatchResult &Result) {
164 const auto *MatchedClassOrStruct =
165 Result.Nodes.getNodeAs<CXXRecordDecl>(
"ProblematicClassOrStruct");
167 const CXXDestructorDecl *Destructor = MatchedClassOrStruct->getDestructor();
171 if (Destructor->getAccess() == AccessSpecifier::AS_private) {
172 diag(MatchedClassOrStruct->getLocation(),
173 "destructor of %0 is private and prevents using the type")
174 << MatchedClassOrStruct;
175 diag(MatchedClassOrStruct->getLocation(),
176 "make it public and virtual", DiagnosticIDs::Note)
178 "public", *Destructor, *Result.SourceManager, getLangOpts());
179 diag(MatchedClassOrStruct->getLocation(),
180 "make it protected", DiagnosticIDs::Note)
182 "protected", *Destructor, *Result.SourceManager, getLangOpts());
188 bool ProtectedAndVirtual =
false;
191 if (MatchedClassOrStruct->hasUserDeclaredDestructor()) {
192 if (Destructor->getAccess() == AccessSpecifier::AS_public) {
193 Fix = FixItHint::CreateInsertion(Destructor->getLocation(),
"virtual ");
194 }
else if (Destructor->getAccess() == AccessSpecifier::AS_protected) {
195 ProtectedAndVirtual =
true;
196 if (
const auto MaybeRange =
198 Result.Context->getLangOpts()))
199 Fix = FixItHint::CreateRemoval(*MaybeRange);
203 *Result.SourceManager);
206 diag(MatchedClassOrStruct->getLocation(),
207 "destructor of %0 is %select{public and non-virtual|protected and "
209 << MatchedClassOrStruct << ProtectedAndVirtual;
210 diag(MatchedClassOrStruct->getLocation(),
211 "make it %select{public and virtual|protected and non-virtual}0",
213 << ProtectedAndVirtual <<
Fix;