brintos

brintos / llvm-project-archived public Read only

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