clang-tools  16.0.0git
VirtualClassDestructorCheck.cpp
Go to the documentation of this file.
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 
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 
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 
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)
181  "public", *Destructor, *Result.SourceManager, getLangOpts());
182  diag(MatchedClassOrStruct->getLocation(),
183  /*Description=*/"make it protected", DiagnosticIDs::Note)
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
Loc
SourceLocation Loc
Definition: KernelNameRestrictionCheck.cpp:45
clang::clangd::check
bool check(llvm::StringRef File, llvm::Optional< Range > LineRange, const ThreadsafeFS &TFS, const ClangdLSPServer::Options &Opts, bool EnableCodeCompletion)
Definition: Check.cpp:275
clang::tidy::cppcoreguidelines::getPublicASDecl
static const AccessSpecDecl * getPublicASDecl(const CXXRecordDecl &StructOrClass)
Definition: VirtualClassDestructorCheck.cpp:73
clang::tidy::cppcoreguidelines::getSourceText
static std::string getSourceText(const CXXDestructorDecl &Destructor)
Definition: VirtualClassDestructorCheck.cpp:115
clang::tidy::cppcoreguidelines::getVirtualKeywordRange
static Optional< CharSourceRange > getVirtualKeywordRange(const CXXDestructorDecl &Destructor, const SourceManager &SM, const LangOptions &LangOpts)
Definition: VirtualClassDestructorCheck.cpp:51
Fix
static cl::opt< bool > Fix("fix", cl::desc(R"( Apply suggested fixes. Without -fix-errors clang-tidy will bail out if any compilation errors were found. )"), cl::init(false), cl::cat(ClangTidyCategory))
clang::ast_matchers
Definition: AbseilMatcher.h:14
clang::ast_matchers::AST_MATCHER
AST_MATCHER(Decl, declHasNoReturnAttr)
matches a Decl if it has a "no return" attribute of any kind
Definition: InfiniteLoopCheck.cpp:24
clang::tidy::cppcoreguidelines::eraseKeyword
static std::string eraseKeyword(std::string &DestructorString, const std::string &Keyword)
Definition: VirtualClassDestructorCheck.cpp:122
VirtualClassDestructorCheck.h
clang
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Definition: ApplyReplacements.h:27
LangOpts
const LangOptions * LangOpts
Definition: ExtractFunction.cpp:366
clang::tidy::utils::lexer::findNextTerminator
SourceLocation findNextTerminator(SourceLocation Start, const SourceManager &SM, const LangOptions &LangOpts)
Definition: LexerUtils.cpp:75
clang::tidy::cppcoreguidelines::generateUserDeclaredDestructor
static FixItHint generateUserDeclaredDestructor(const CXXRecordDecl &StructOrClass, const SourceManager &SourceManager)
Definition: VirtualClassDestructorCheck.cpp:87
clang::tidy::cppcoreguidelines::changePrivateDestructorVisibilityTo
static FixItHint changePrivateDestructorVisibilityTo(const std::string &Visibility, const CXXDestructorDecl &Destructor, const SourceManager &SM, const LangOptions &LangOpts)
Definition: VirtualClassDestructorCheck.cpp:130