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 VirtualBeginSpellingLoc = 58 SM.getSpellingLoc(Destructor.getBeginLoc()); 59 SourceLocation VirtualEndLoc = VirtualBeginSpellingLoc.getLocWithOffset( 60 Lexer::MeasureTokenLength(VirtualBeginSpellingLoc, SM, LangOpts)); 61 62 /// Range ends with \c StartOfNextToken so that any whitespace after \c 63 /// virtual is included. 64 Optional<Token> NextToken = Lexer::findNextToken(VirtualEndLoc, SM, LangOpts); 65 if (!NextToken) 66 return None; 67 SourceLocation StartOfNextToken = NextToken->getLocation(); 68 69 return CharSourceRange::getCharRange(VirtualBeginLoc, StartOfNextToken); 70 } 71 72 static const AccessSpecDecl * 73 getPublicASDecl(const CXXRecordDecl &StructOrClass) { 74 for (DeclContext::specific_decl_iterator<AccessSpecDecl> 75 AS{StructOrClass.decls_begin()}, 76 ASEnd{StructOrClass.decls_end()}; 77 AS != ASEnd; ++AS) { 78 AccessSpecDecl *ASDecl = *AS; 79 if (ASDecl->getAccess() == AccessSpecifier::AS_public) 80 return ASDecl; 81 } 82 83 return nullptr; 84 } 85 86 static FixItHint 87 generateUserDeclaredDestructor(const CXXRecordDecl &StructOrClass, 88 const SourceManager &SourceManager) { 89 std::string DestructorString; 90 SourceLocation Loc; 91 bool AppendLineBreak = false; 92 93 const AccessSpecDecl *AccessSpecDecl = getPublicASDecl(StructOrClass); 94 95 if (!AccessSpecDecl) { 96 if (StructOrClass.isClass()) { 97 Loc = StructOrClass.getEndLoc(); 98 DestructorString = "public:"; 99 AppendLineBreak = true; 100 } else { 101 Loc = StructOrClass.getBraceRange().getBegin().getLocWithOffset(1); 102 } 103 } else { 104 Loc = AccessSpecDecl->getEndLoc().getLocWithOffset(1); 105 } 106 107 DestructorString = (llvm::Twine(DestructorString) + "\nvirtual ~" + 108 StructOrClass.getName().str() + "() = default;" + 109 (AppendLineBreak ? "\n" : "")) 110 .str(); 111 112 return FixItHint::CreateInsertion(Loc, DestructorString); 113 } 114 115 static std::string getSourceText(const CXXDestructorDecl &Destructor) { 116 std::string SourceText; 117 llvm::raw_string_ostream DestructorStream(SourceText); 118 Destructor.print(DestructorStream); 119 return SourceText; 120 } 121 122 static std::string eraseKeyword(std::string &DestructorString, 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; 128 } 129 130 static FixItHint changePrivateDestructorVisibilityTo( 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 " : "")) 136 .str(); 137 138 std::string OriginalDestructor = getSourceText(Destructor); 139 if (Visibility == "protected" && Destructor.isVirtualAsWritten()) 140 OriginalDestructor = eraseKeyword(OriginalDestructor, "virtual "); 141 142 DestructorString = 143 (llvm::Twine(DestructorString) + OriginalDestructor + 144 (Destructor.isExplicitlyDefaulted() ? ";\n" : "") + "private:") 145 .str(); 146 147 /// Semicolons ending an explicitly defaulted destructor have to be deleted. 148 /// Otherwise, the left-over semicolon trails the \c private: access 149 /// specifier. 150 SourceLocation EndLocation; 151 if (Destructor.isExplicitlyDefaulted()) 152 EndLocation = 153 utils::lexer::findNextTerminator(Destructor.getEndLoc(), SM, LangOpts) 154 .getLocWithOffset(1); 155 else 156 EndLocation = Destructor.getEndLoc().getLocWithOffset(1); 157 158 auto OriginalDestructorRange = 159 CharSourceRange::getCharRange(Destructor.getBeginLoc(), EndLocation); 160 return FixItHint::CreateReplacement(OriginalDestructorRange, 161 DestructorString); 162 } 163 164 void VirtualClassDestructorCheck::check( 165 const MatchFinder::MatchResult &Result) { 166 167 const auto *MatchedClassOrStruct = 168 Result.Nodes.getNodeAs<CXXRecordDecl>("ProblematicClassOrStruct"); 169 170 const CXXDestructorDecl *Destructor = MatchedClassOrStruct->getDestructor(); 171 if (!Destructor) 172 return; 173 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 /*Description=*/"make it public and virtual", DiagnosticIDs::Note) 180 << changePrivateDestructorVisibilityTo( 181 "public", *Destructor, *Result.SourceManager, getLangOpts()); 182 diag(MatchedClassOrStruct->getLocation(), 183 /*Description=*/"make it protected", DiagnosticIDs::Note) 184 << changePrivateDestructorVisibilityTo( 185 "protected", *Destructor, *Result.SourceManager, getLangOpts()); 186 187 return; 188 } 189 190 // Implicit destructors are public and non-virtual for classes and structs. 191 bool ProtectedAndVirtual = false; 192 FixItHint Fix; 193 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 = 200 getVirtualKeywordRange(*Destructor, *Result.SourceManager, 201 Result.Context->getLangOpts())) 202 Fix = FixItHint::CreateRemoval(*MaybeRange); 203 } 204 } else { 205 Fix = generateUserDeclaredDestructor(*MatchedClassOrStruct, 206 *Result.SourceManager); 207 } 208 209 diag(MatchedClassOrStruct->getLocation(), 210 "destructor of %0 is %select{public and non-virtual|protected and " 211 "virtual}1") 212 << MatchedClassOrStruct << ProtectedAndVirtual; 213 diag(MatchedClassOrStruct->getLocation(), 214 "make it %select{public and virtual|protected and non-virtual}0", 215 DiagnosticIDs::Note) 216 << ProtectedAndVirtual << Fix; 217 } 218 219 } // namespace cppcoreguidelines 220 } // namespace tidy 221 } // namespace clang 222