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