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