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