1 //===--- VirtualClassDestructorCheck.cpp - clang-tidy -----------------===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "VirtualClassDestructorCheck.h" 10 #include "../utils/LexerUtils.h" 11 #include "clang/AST/ASTContext.h" 12 #include "clang/ASTMatchers/ASTMatchFinder.h" 13 #include "clang/Lex/Lexer.h" 14 #include <string> 15 16 using namespace clang::ast_matchers; 17 18 namespace clang { 19 namespace tidy { 20 namespace cppcoreguidelines { 21 22 AST_MATCHER(CXXRecordDecl, hasPublicVirtualOrProtectedNonVirtualDestructor) { 23 // We need to call Node.getDestructor() instead of matching a 24 // CXXDestructorDecl. Otherwise, tests will fail for class templates, since 25 // the primary template (not the specialization) always gets a non-virtual 26 // CXXDestructorDecl in the AST. https://bugs.llvm.org/show_bug.cgi?id=51912 27 const CXXDestructorDecl *Destructor = Node.getDestructor(); 28 if (!Destructor) 29 return false; 30 31 return (((Destructor->getAccess() == AccessSpecifier::AS_public) && 32 Destructor->isVirtual()) || 33 ((Destructor->getAccess() == AccessSpecifier::AS_protected) && 34 !Destructor->isVirtual())); 35 } 36 37 void VirtualClassDestructorCheck::registerMatchers(MatchFinder *Finder) { 38 ast_matchers::internal::Matcher<CXXRecordDecl> InheritsVirtualMethod = 39 hasAnyBase(hasType(cxxRecordDecl(has(cxxMethodDecl(isVirtual()))))); 40 41 Finder->addMatcher( 42 cxxRecordDecl( 43 anyOf(has(cxxMethodDecl(isVirtual())), InheritsVirtualMethod), 44 unless(isFinal()), 45 unless(hasPublicVirtualOrProtectedNonVirtualDestructor())) 46 .bind("ProblematicClassOrStruct"), 47 this); 48 } 49 50 static Optional<CharSourceRange> 51 getVirtualKeywordRange(const CXXDestructorDecl &Destructor, 52 const SourceManager &SM, const LangOptions &LangOpts) { 53 if (Destructor.getLocation().isMacroID()) 54 return None; 55 56 SourceLocation VirtualBeginLoc = Destructor.getBeginLoc(); 57 SourceLocation VirtualEndLoc = VirtualBeginLoc.getLocWithOffset( 58 Lexer::MeasureTokenLength(VirtualBeginLoc, SM, LangOpts)); 59 60 /// Range ends with \c StartOfNextToken so that any whitespace after \c 61 /// virtual is included. 62 SourceLocation StartOfNextToken = 63 Lexer::findNextToken(VirtualEndLoc, SM, LangOpts)->getLocation(); 64 65 return CharSourceRange::getCharRange(VirtualBeginLoc, StartOfNextToken); 66 } 67 68 static const AccessSpecDecl * 69 getPublicASDecl(const CXXRecordDecl &StructOrClass) { 70 for (DeclContext::specific_decl_iterator<AccessSpecDecl> 71 AS{StructOrClass.decls_begin()}, 72 ASEnd{StructOrClass.decls_end()}; 73 AS != ASEnd; ++AS) { 74 AccessSpecDecl *ASDecl = *AS; 75 if (ASDecl->getAccess() == AccessSpecifier::AS_public) 76 return ASDecl; 77 } 78 79 return nullptr; 80 } 81 82 static FixItHint 83 generateUserDeclaredDestructor(const CXXRecordDecl &StructOrClass, 84 const SourceManager &SourceManager) { 85 std::string DestructorString; 86 SourceLocation Loc; 87 bool AppendLineBreak = false; 88 89 const AccessSpecDecl *AccessSpecDecl = getPublicASDecl(StructOrClass); 90 91 if (!AccessSpecDecl) { 92 if (StructOrClass.isClass()) { 93 Loc = StructOrClass.getEndLoc(); 94 DestructorString = "public:"; 95 AppendLineBreak = true; 96 } else { 97 Loc = StructOrClass.getBraceRange().getBegin().getLocWithOffset(1); 98 } 99 } else { 100 Loc = AccessSpecDecl->getEndLoc().getLocWithOffset(1); 101 } 102 103 DestructorString = (llvm::Twine(DestructorString) + "\nvirtual ~" + 104 StructOrClass.getName().str() + "() = default;" + 105 (AppendLineBreak ? "\n" : "")) 106 .str(); 107 108 return FixItHint::CreateInsertion(Loc, DestructorString); 109 } 110 111 static std::string getSourceText(const CXXDestructorDecl &Destructor) { 112 std::string SourceText; 113 llvm::raw_string_ostream DestructorStream(SourceText); 114 Destructor.print(DestructorStream); 115 return SourceText; 116 } 117 118 static std::string eraseKeyword(std::string &DestructorString, 119 const std::string &Keyword) { 120 size_t KeywordIndex = DestructorString.find(Keyword); 121 if (KeywordIndex != std::string::npos) 122 DestructorString.erase(KeywordIndex, Keyword.length()); 123 return DestructorString; 124 } 125 126 static FixItHint changePrivateDestructorVisibilityTo( 127 const std::string &Visibility, const CXXDestructorDecl &Destructor, 128 const SourceManager &SM, const LangOptions &LangOpts) { 129 std::string DestructorString = 130 (llvm::Twine() + Visibility + ":\n" + 131 (Visibility == "public" && !Destructor.isVirtual() ? "virtual " : "")) 132 .str(); 133 134 std::string OriginalDestructor = getSourceText(Destructor); 135 if (Visibility == "protected" && Destructor.isVirtualAsWritten()) 136 OriginalDestructor = eraseKeyword(OriginalDestructor, "virtual "); 137 138 DestructorString = 139 (llvm::Twine(DestructorString) + OriginalDestructor + 140 (Destructor.isExplicitlyDefaulted() ? ";\n" : "") + "private:") 141 .str(); 142 143 /// Semicolons ending an explicitly defaulted destructor have to be deleted. 144 /// Otherwise, the left-over semicolon trails the \c private: access 145 /// specifier. 146 SourceLocation EndLocation; 147 if (Destructor.isExplicitlyDefaulted()) 148 EndLocation = 149 utils::lexer::findNextTerminator(Destructor.getEndLoc(), SM, LangOpts) 150 .getLocWithOffset(1); 151 else 152 EndLocation = Destructor.getEndLoc().getLocWithOffset(1); 153 154 auto OriginalDestructorRange = 155 CharSourceRange::getCharRange(Destructor.getBeginLoc(), EndLocation); 156 return FixItHint::CreateReplacement(OriginalDestructorRange, 157 DestructorString); 158 } 159 160 void VirtualClassDestructorCheck::check( 161 const MatchFinder::MatchResult &Result) { 162 163 const auto *MatchedClassOrStruct = 164 Result.Nodes.getNodeAs<CXXRecordDecl>("ProblematicClassOrStruct"); 165 166 const CXXDestructorDecl *Destructor = MatchedClassOrStruct->getDestructor(); 167 if (!Destructor) 168 return; 169 170 if (Destructor->getAccess() == AccessSpecifier::AS_private) { 171 diag(MatchedClassOrStruct->getLocation(), 172 "destructor of %0 is private and prevents using the type") 173 << MatchedClassOrStruct; 174 diag(MatchedClassOrStruct->getLocation(), 175 /*Description=*/"make it public and virtual", DiagnosticIDs::Note) 176 << changePrivateDestructorVisibilityTo( 177 "public", *Destructor, *Result.SourceManager, getLangOpts()); 178 diag(MatchedClassOrStruct->getLocation(), 179 /*Description=*/"make it protected", DiagnosticIDs::Note) 180 << changePrivateDestructorVisibilityTo( 181 "protected", *Destructor, *Result.SourceManager, getLangOpts()); 182 183 return; 184 } 185 186 // Implicit destructors are public and non-virtual for classes and structs. 187 bool ProtectedAndVirtual = false; 188 FixItHint Fix; 189 190 if (MatchedClassOrStruct->hasUserDeclaredDestructor()) { 191 if (Destructor->getAccess() == AccessSpecifier::AS_public) { 192 Fix = FixItHint::CreateInsertion(Destructor->getLocation(), "virtual "); 193 } else if (Destructor->getAccess() == AccessSpecifier::AS_protected) { 194 ProtectedAndVirtual = true; 195 if (const auto MaybeRange = 196 getVirtualKeywordRange(*Destructor, *Result.SourceManager, 197 Result.Context->getLangOpts())) 198 Fix = FixItHint::CreateRemoval(*MaybeRange); 199 } 200 } else { 201 Fix = generateUserDeclaredDestructor(*MatchedClassOrStruct, 202 *Result.SourceManager); 203 } 204 205 diag(MatchedClassOrStruct->getLocation(), 206 "destructor of %0 is %select{public and non-virtual|protected and " 207 "virtual}1") 208 << MatchedClassOrStruct << ProtectedAndVirtual; 209 diag(MatchedClassOrStruct->getLocation(), 210 "make it %select{public and virtual|protected and non-virtual}0", 211 DiagnosticIDs::Note) 212 << ProtectedAndVirtual << Fix; 213 } 214 215 } // namespace cppcoreguidelines 216 } // namespace tidy 217 } // namespace clang 218