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 void VirtualClassDestructorCheck::registerMatchers(MatchFinder *Finder) { 23 ast_matchers::internal::Matcher<CXXRecordDecl> InheritsVirtualMethod = 24 hasAnyBase(hasType(cxxRecordDecl(has(cxxMethodDecl(isVirtual()))))); 25 26 Finder->addMatcher( 27 cxxRecordDecl( 28 anyOf(has(cxxMethodDecl(isVirtual())), InheritsVirtualMethod), 29 unless(anyOf( 30 has(cxxDestructorDecl(isPublic(), isVirtual())), 31 has(cxxDestructorDecl(isProtected(), unless(isVirtual())))))) 32 .bind("ProblematicClassOrStruct"), 33 this); 34 } 35 36 static CharSourceRange 37 getVirtualKeywordRange(const CXXDestructorDecl &Destructor, 38 const SourceManager &SM, const LangOptions &LangOpts) { 39 SourceLocation VirtualBeginLoc = Destructor.getBeginLoc(); 40 SourceLocation VirtualEndLoc = VirtualBeginLoc.getLocWithOffset( 41 Lexer::MeasureTokenLength(VirtualBeginLoc, SM, LangOpts)); 42 43 /// Range ends with \c StartOfNextToken so that any whitespace after \c 44 /// virtual is included. 45 SourceLocation StartOfNextToken = 46 Lexer::findNextToken(VirtualEndLoc, SM, LangOpts) 47 .getValue() 48 .getLocation(); 49 50 return CharSourceRange::getCharRange(VirtualBeginLoc, StartOfNextToken); 51 } 52 53 static const AccessSpecDecl * 54 getPublicASDecl(const CXXRecordDecl &StructOrClass) { 55 for (DeclContext::specific_decl_iterator<AccessSpecDecl> 56 AS{StructOrClass.decls_begin()}, 57 ASEnd{StructOrClass.decls_end()}; 58 AS != ASEnd; ++AS) { 59 AccessSpecDecl *ASDecl = *AS; 60 if (ASDecl->getAccess() == AccessSpecifier::AS_public) 61 return ASDecl; 62 } 63 64 return nullptr; 65 } 66 67 static FixItHint 68 generateUserDeclaredDestructor(const CXXRecordDecl &StructOrClass, 69 const SourceManager &SourceManager) { 70 std::string DestructorString; 71 SourceLocation Loc; 72 bool AppendLineBreak = false; 73 74 const AccessSpecDecl *AccessSpecDecl = getPublicASDecl(StructOrClass); 75 76 if (!AccessSpecDecl) { 77 if (StructOrClass.isClass()) { 78 Loc = StructOrClass.getEndLoc(); 79 DestructorString = "public:"; 80 AppendLineBreak = true; 81 } else { 82 Loc = StructOrClass.getBraceRange().getBegin().getLocWithOffset(1); 83 } 84 } else { 85 Loc = AccessSpecDecl->getEndLoc().getLocWithOffset(1); 86 } 87 88 DestructorString = (llvm::Twine(DestructorString) + "\nvirtual ~" + 89 StructOrClass.getName().str() + "() = default;" + 90 (AppendLineBreak ? "\n" : "")) 91 .str(); 92 93 return FixItHint::CreateInsertion(Loc, DestructorString); 94 } 95 96 static std::string getSourceText(const CXXDestructorDecl &Destructor) { 97 std::string SourceText; 98 llvm::raw_string_ostream DestructorStream(SourceText); 99 Destructor.print(DestructorStream); 100 return SourceText; 101 } 102 103 static std::string eraseKeyword(std::string &DestructorString, 104 const std::string &Keyword) { 105 size_t KeywordIndex = DestructorString.find(Keyword); 106 if (KeywordIndex != std::string::npos) 107 DestructorString.erase(KeywordIndex, Keyword.length()); 108 return DestructorString; 109 } 110 111 static FixItHint changePrivateDestructorVisibilityTo( 112 const std::string &Visibility, const CXXDestructorDecl &Destructor, 113 const SourceManager &SM, const LangOptions &LangOpts) { 114 std::string DestructorString = 115 (llvm::Twine() + Visibility + ":\n" + 116 (Visibility == "public" && !Destructor.isVirtual() ? "virtual " : "")) 117 .str(); 118 119 std::string OriginalDestructor = getSourceText(Destructor); 120 if (Visibility == "protected" && Destructor.isVirtualAsWritten()) 121 OriginalDestructor = eraseKeyword(OriginalDestructor, "virtual "); 122 123 DestructorString = 124 (llvm::Twine(DestructorString) + OriginalDestructor + 125 (Destructor.isExplicitlyDefaulted() ? ";\n" : "") + "private:") 126 .str(); 127 128 /// Semicolons ending an explicitly defaulted destructor have to be deleted. 129 /// Otherwise, the left-over semicolon trails the \c private: access 130 /// specifier. 131 SourceLocation EndLocation; 132 if (Destructor.isExplicitlyDefaulted()) 133 EndLocation = 134 utils::lexer::findNextTerminator(Destructor.getEndLoc(), SM, LangOpts) 135 .getLocWithOffset(1); 136 else 137 EndLocation = Destructor.getEndLoc().getLocWithOffset(1); 138 139 auto OriginalDestructorRange = 140 CharSourceRange::getCharRange(Destructor.getBeginLoc(), EndLocation); 141 return FixItHint::CreateReplacement(OriginalDestructorRange, 142 DestructorString); 143 } 144 145 void VirtualClassDestructorCheck::check( 146 const MatchFinder::MatchResult &Result) { 147 148 const auto *MatchedClassOrStruct = 149 Result.Nodes.getNodeAs<CXXRecordDecl>("ProblematicClassOrStruct"); 150 151 const CXXDestructorDecl *Destructor = MatchedClassOrStruct->getDestructor(); 152 if (!Destructor) 153 return; 154 155 if (Destructor->getAccess() == AccessSpecifier::AS_private) { 156 diag(MatchedClassOrStruct->getLocation(), 157 "destructor of %0 is private and prevents using the type") 158 << MatchedClassOrStruct; 159 diag(MatchedClassOrStruct->getLocation(), 160 /*FixDescription=*/"make it public and virtual", DiagnosticIDs::Note) 161 << changePrivateDestructorVisibilityTo( 162 "public", *Destructor, *Result.SourceManager, getLangOpts()); 163 diag(MatchedClassOrStruct->getLocation(), 164 /*FixDescription=*/"make it protected", DiagnosticIDs::Note) 165 << changePrivateDestructorVisibilityTo( 166 "protected", *Destructor, *Result.SourceManager, getLangOpts()); 167 168 return; 169 } 170 171 // Implicit destructors are public and non-virtual for classes and structs. 172 bool ProtectedAndVirtual = false; 173 FixItHint Fix; 174 175 if (MatchedClassOrStruct->hasUserDeclaredDestructor()) { 176 if (Destructor->getAccess() == AccessSpecifier::AS_public) { 177 Fix = FixItHint::CreateInsertion(Destructor->getLocation(), "virtual "); 178 } else if (Destructor->getAccess() == AccessSpecifier::AS_protected) { 179 ProtectedAndVirtual = true; 180 Fix = FixItHint::CreateRemoval(getVirtualKeywordRange( 181 *Destructor, *Result.SourceManager, Result.Context->getLangOpts())); 182 } 183 } else { 184 Fix = generateUserDeclaredDestructor(*MatchedClassOrStruct, 185 *Result.SourceManager); 186 } 187 188 diag(MatchedClassOrStruct->getLocation(), 189 "destructor of %0 is %select{public and non-virtual|protected and " 190 "virtual}1") 191 << MatchedClassOrStruct << ProtectedAndVirtual; 192 diag(MatchedClassOrStruct->getLocation(), 193 "make it %select{public and virtual|protected and non-virtual}0", 194 DiagnosticIDs::Note) 195 << ProtectedAndVirtual << Fix; 196 } 197 198 } // namespace cppcoreguidelines 199 } // namespace tidy 200 } // namespace clang 201