brintos

brintos / llvm-project-archived public Read only

0
0
Text · 12.4 KiB · 4ef28c8 Raw
325 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 "ElseAfterReturnCheck.h"10#include "clang/AST/ASTContext.h"11#include "clang/ASTMatchers/ASTMatchFinder.h"12#include "clang/Lex/Lexer.h"13#include "clang/Lex/Preprocessor.h"14#include "clang/Tooling/FixIt.h"15#include "llvm/ADT/SmallVector.h"16 17using namespace clang::ast_matchers;18 19namespace clang::tidy::readability {20 21namespace {22 23class PPConditionalCollector : public PPCallbacks {24public:25  PPConditionalCollector(26      ElseAfterReturnCheck::ConditionalBranchMap &Collections,27      const SourceManager &SM)28      : Collections(Collections), SM(SM) {}29  void Endif(SourceLocation Loc, SourceLocation IfLoc) override {30    if (!SM.isWrittenInSameFile(Loc, IfLoc))31      return;32    SmallVectorImpl<SourceRange> &Collection = Collections[SM.getFileID(Loc)];33    assert(Collection.empty() || Collection.back().getEnd() < Loc);34    Collection.emplace_back(IfLoc, Loc);35  }36 37private:38  ElseAfterReturnCheck::ConditionalBranchMap &Collections;39  const SourceManager &SM;40};41 42} // namespace43 44static const char InterruptingStr[] = "interrupting";45static const char WarningMessage[] = "do not use 'else' after '%0'";46static const char WarnOnUnfixableStr[] = "WarnOnUnfixable";47static const char WarnOnConditionVariablesStr[] = "WarnOnConditionVariables";48 49static const DeclRefExpr *findUsage(const Stmt *Node, int64_t DeclIdentifier) {50  if (!Node)51    return nullptr;52  if (const auto *DeclRef = dyn_cast<DeclRefExpr>(Node)) {53    if (DeclRef->getDecl()->getID() == DeclIdentifier)54      return DeclRef;55  } else {56    for (const Stmt *ChildNode : Node->children()) {57      if (const DeclRefExpr *Result = findUsage(ChildNode, DeclIdentifier))58        return Result;59    }60  }61  return nullptr;62}63 64static const DeclRefExpr *65findUsageRange(const Stmt *Node,66               const llvm::ArrayRef<int64_t> &DeclIdentifiers) {67  if (!Node)68    return nullptr;69  if (const auto *DeclRef = dyn_cast<DeclRefExpr>(Node)) {70    if (llvm::is_contained(DeclIdentifiers, DeclRef->getDecl()->getID()))71      return DeclRef;72  } else {73    for (const Stmt *ChildNode : Node->children()) {74      if (const DeclRefExpr *Result =75              findUsageRange(ChildNode, DeclIdentifiers))76        return Result;77    }78  }79  return nullptr;80}81 82static const DeclRefExpr *checkInitDeclUsageInElse(const IfStmt *If) {83  const auto *InitDeclStmt = dyn_cast_or_null<DeclStmt>(If->getInit());84  if (!InitDeclStmt)85    return nullptr;86  if (InitDeclStmt->isSingleDecl()) {87    const Decl *InitDecl = InitDeclStmt->getSingleDecl();88    assert(isa<VarDecl>(InitDecl) && "SingleDecl must be a VarDecl");89    return findUsage(If->getElse(), InitDecl->getID());90  }91  llvm::SmallVector<int64_t, 4> DeclIdentifiers;92  for (const Decl *ChildDecl : InitDeclStmt->decls()) {93    assert(isa<VarDecl>(ChildDecl) && "Init Decls must be a VarDecl");94    DeclIdentifiers.push_back(ChildDecl->getID());95  }96  return findUsageRange(If->getElse(), DeclIdentifiers);97}98 99static const DeclRefExpr *checkConditionVarUsageInElse(const IfStmt *If) {100  if (const VarDecl *CondVar = If->getConditionVariable())101    return findUsage(If->getElse(), CondVar->getID());102  return nullptr;103}104 105static bool containsDeclInScope(const Stmt *Node) {106  if (isa<DeclStmt>(Node))107    return true;108  if (const auto *Compound = dyn_cast<CompoundStmt>(Node))109    return llvm::any_of(Compound->body(), [](const Stmt *SubNode) {110      return isa<DeclStmt>(SubNode);111    });112  return false;113}114 115static void removeElseAndBrackets(DiagnosticBuilder &Diag, ASTContext &Context,116                                  const Stmt *Else, SourceLocation ElseLoc) {117  auto Remap = [&](SourceLocation Loc) {118    return Context.getSourceManager().getExpansionLoc(Loc);119  };120  auto TokLen = [&](SourceLocation Loc) {121    return Lexer::MeasureTokenLength(Loc, Context.getSourceManager(),122                                     Context.getLangOpts());123  };124 125  if (const auto *CS = dyn_cast<CompoundStmt>(Else)) {126    Diag << tooling::fixit::createRemoval(ElseLoc);127    const SourceLocation LBrace = CS->getLBracLoc();128    const SourceLocation RBrace = CS->getRBracLoc();129    const SourceLocation RangeStart =130        Remap(LBrace).getLocWithOffset(TokLen(LBrace) + 1);131    const SourceLocation RangeEnd = Remap(RBrace).getLocWithOffset(-1);132 133    const llvm::StringRef Repl = Lexer::getSourceText(134        CharSourceRange::getTokenRange(RangeStart, RangeEnd),135        Context.getSourceManager(), Context.getLangOpts());136    Diag << tooling::fixit::createReplacement(CS->getSourceRange(), Repl);137  } else {138    const SourceLocation ElseExpandedLoc = Remap(ElseLoc);139    const SourceLocation EndLoc = Remap(Else->getEndLoc());140 141    const llvm::StringRef Repl = Lexer::getSourceText(142        CharSourceRange::getTokenRange(143            ElseExpandedLoc.getLocWithOffset(TokLen(ElseLoc) + 1), EndLoc),144        Context.getSourceManager(), Context.getLangOpts());145    Diag << tooling::fixit::createReplacement(146        SourceRange(ElseExpandedLoc, EndLoc), Repl);147  }148}149 150ElseAfterReturnCheck::ElseAfterReturnCheck(StringRef Name,151                                           ClangTidyContext *Context)152    : ClangTidyCheck(Name, Context),153      WarnOnUnfixable(Options.get(WarnOnUnfixableStr, true)),154      WarnOnConditionVariables(Options.get(WarnOnConditionVariablesStr, true)) {155}156 157void ElseAfterReturnCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {158  Options.store(Opts, WarnOnUnfixableStr, WarnOnUnfixable);159  Options.store(Opts, WarnOnConditionVariablesStr, WarnOnConditionVariables);160}161 162void ElseAfterReturnCheck::registerPPCallbacks(const SourceManager &SM,163                                               Preprocessor *PP,164                                               Preprocessor *ModuleExpanderPP) {165  PP->addPPCallbacks(166      std::make_unique<PPConditionalCollector>(this->PPConditionals, SM));167}168 169void ElseAfterReturnCheck::registerMatchers(MatchFinder *Finder) {170  const auto InterruptsControlFlow = stmt(anyOf(171      returnStmt().bind(InterruptingStr), continueStmt().bind(InterruptingStr),172      breakStmt().bind(InterruptingStr), cxxThrowExpr().bind(InterruptingStr)));173  Finder->addMatcher(174      compoundStmt(175          forEach(ifStmt(unless(isConstexpr()), unless(isConsteval()),176                         hasThen(stmt(177                             anyOf(InterruptsControlFlow,178                                   compoundStmt(has(InterruptsControlFlow))))),179                         hasElse(stmt().bind("else")))180                      .bind("if")))181          .bind("cs"),182      this);183}184 185static bool hasPreprocessorBranchEndBetweenLocations(186    const ElseAfterReturnCheck::ConditionalBranchMap &ConditionalBranchMap,187    const SourceManager &SM, SourceLocation StartLoc, SourceLocation EndLoc) {188  const SourceLocation ExpandedStartLoc = SM.getExpansionLoc(StartLoc);189  const SourceLocation ExpandedEndLoc = SM.getExpansionLoc(EndLoc);190  if (!SM.isWrittenInSameFile(ExpandedStartLoc, ExpandedEndLoc))191    return false;192 193  // StartLoc and EndLoc expand to the same macro.194  if (ExpandedStartLoc == ExpandedEndLoc)195    return false;196 197  assert(ExpandedStartLoc < ExpandedEndLoc);198 199  auto Iter = ConditionalBranchMap.find(SM.getFileID(ExpandedEndLoc));200 201  if (Iter == ConditionalBranchMap.end() || Iter->getSecond().empty())202    return false;203 204  const SmallVectorImpl<SourceRange> &ConditionalBranches = Iter->getSecond();205 206  assert(llvm::is_sorted(ConditionalBranches,207                         [](const SourceRange &LHS, const SourceRange &RHS) {208                           return LHS.getEnd() < RHS.getEnd();209                         }));210 211  // First conditional block that ends after ExpandedStartLoc.212  const auto *Begin =213      llvm::lower_bound(ConditionalBranches, ExpandedStartLoc,214                        [](const SourceRange &LHS, const SourceLocation &RHS) {215                          return LHS.getEnd() < RHS;216                        });217  const auto *End = ConditionalBranches.end();218  for (; Begin != End && Begin->getEnd() < ExpandedEndLoc; ++Begin)219    if (Begin->getBegin() < ExpandedStartLoc)220      return true;221  return false;222}223 224static StringRef getControlFlowString(const Stmt &Stmt) {225  if (isa<ReturnStmt>(Stmt))226    return "return";227  if (isa<ContinueStmt>(Stmt))228    return "continue";229  if (isa<BreakStmt>(Stmt))230    return "break";231  if (isa<CXXThrowExpr>(Stmt))232    return "throw";233  llvm_unreachable("Unknown control flow interrupter");234}235 236void ElseAfterReturnCheck::check(const MatchFinder::MatchResult &Result) {237  const auto *If = Result.Nodes.getNodeAs<IfStmt>("if");238  const auto *Else = Result.Nodes.getNodeAs<Stmt>("else");239  const auto *OuterScope = Result.Nodes.getNodeAs<CompoundStmt>("cs");240  const auto *Interrupt = Result.Nodes.getNodeAs<Stmt>(InterruptingStr);241  const SourceLocation ElseLoc = If->getElseLoc();242 243  if (hasPreprocessorBranchEndBetweenLocations(244          PPConditionals, *Result.SourceManager, Interrupt->getBeginLoc(),245          ElseLoc))246    return;247 248  const bool IsLastInScope = OuterScope->body_back() == If;249  const StringRef ControlFlowInterrupter = getControlFlowString(*Interrupt);250 251  if (!IsLastInScope && containsDeclInScope(Else)) {252    if (WarnOnUnfixable) {253      // Warn, but don't attempt an autofix.254      diag(ElseLoc, WarningMessage) << ControlFlowInterrupter;255    }256    return;257  }258 259  if (checkConditionVarUsageInElse(If) != nullptr) {260    if (!WarnOnConditionVariables)261      return;262    if (IsLastInScope) {263      // If the if statement is the last statement of its enclosing statements264      // scope, we can pull the decl out of the if statement.265      DiagnosticBuilder Diag = diag(ElseLoc, WarningMessage)266                               << ControlFlowInterrupter267                               << SourceRange(ElseLoc);268      if (checkInitDeclUsageInElse(If) != nullptr) {269        Diag << tooling::fixit::createReplacement(270                    SourceRange(If->getIfLoc()),271                    (tooling::fixit::getText(*If->getInit(), *Result.Context) +272                     llvm::StringRef("\n"))273                        .str())274             << tooling::fixit::createRemoval(If->getInit()->getSourceRange());275      }276      const DeclStmt *VDeclStmt = If->getConditionVariableDeclStmt();277      const VarDecl *VDecl = If->getConditionVariable();278      const std::string Repl =279          (tooling::fixit::getText(*VDeclStmt, *Result.Context) +280           llvm::StringRef(";\n") +281           tooling::fixit::getText(If->getIfLoc(), *Result.Context))282              .str();283      Diag << tooling::fixit::createReplacement(SourceRange(If->getIfLoc()),284                                                Repl)285           << tooling::fixit::createReplacement(VDeclStmt->getSourceRange(),286                                                VDecl->getName());287      removeElseAndBrackets(Diag, *Result.Context, Else, ElseLoc);288    } else if (WarnOnUnfixable) {289      // Warn, but don't attempt an autofix.290      diag(ElseLoc, WarningMessage) << ControlFlowInterrupter;291    }292    return;293  }294 295  if (checkInitDeclUsageInElse(If) != nullptr) {296    if (!WarnOnConditionVariables)297      return;298    if (IsLastInScope) {299      // If the if statement is the last statement of its enclosing statements300      // scope, we can pull the decl out of the if statement.301      DiagnosticBuilder Diag = diag(ElseLoc, WarningMessage)302                               << ControlFlowInterrupter303                               << SourceRange(ElseLoc);304      Diag << tooling::fixit::createReplacement(305                  SourceRange(If->getIfLoc()),306                  (tooling::fixit::getText(*If->getInit(), *Result.Context) +307                   "\n" +308                   tooling::fixit::getText(If->getIfLoc(), *Result.Context))309                      .str())310           << tooling::fixit::createRemoval(If->getInit()->getSourceRange());311      removeElseAndBrackets(Diag, *Result.Context, Else, ElseLoc);312    } else if (WarnOnUnfixable) {313      // Warn, but don't attempt an autofix.314      diag(ElseLoc, WarningMessage) << ControlFlowInterrupter;315    }316    return;317  }318 319  DiagnosticBuilder Diag = diag(ElseLoc, WarningMessage)320                           << ControlFlowInterrupter << SourceRange(ElseLoc);321  removeElseAndBrackets(Diag, *Result.Context, Else, ElseLoc);322}323 324} // namespace clang::tidy::readability325