//===--- ElseAfterReturnCheck.cpp - clang-tidy-----------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "ElseAfterReturnCheck.h" #include "clang/AST/ASTContext.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/Lex/Lexer.h" #include "clang/Lex/Preprocessor.h" #include "clang/Tooling/FixIt.h" #include "llvm/ADT/SmallVector.h" using namespace clang::ast_matchers; namespace clang::tidy::readability { namespace { class PPConditionalCollector : public PPCallbacks { public: PPConditionalCollector( ElseAfterReturnCheck::ConditionalBranchMap &Collections, const SourceManager &SM) : Collections(Collections), SM(SM) {} void Endif(SourceLocation Loc, SourceLocation IfLoc) override { if (!SM.isWrittenInSameFile(Loc, IfLoc)) return; SmallVectorImpl &Collection = Collections[SM.getFileID(Loc)]; assert(Collection.empty() || Collection.back().getEnd() < Loc); Collection.emplace_back(IfLoc, Loc); } private: ElseAfterReturnCheck::ConditionalBranchMap &Collections; const SourceManager &SM; }; } // namespace static const char InterruptingStr[] = "interrupting"; static const char WarningMessage[] = "do not use 'else' after '%0'"; static const char WarnOnUnfixableStr[] = "WarnOnUnfixable"; static const char WarnOnConditionVariablesStr[] = "WarnOnConditionVariables"; static const DeclRefExpr *findUsage(const Stmt *Node, int64_t DeclIdentifier) { if (!Node) return nullptr; if (const auto *DeclRef = dyn_cast(Node)) { if (DeclRef->getDecl()->getID() == DeclIdentifier) return DeclRef; } else { for (const Stmt *ChildNode : Node->children()) { if (const DeclRefExpr *Result = findUsage(ChildNode, DeclIdentifier)) return Result; } } return nullptr; } static const DeclRefExpr * findUsageRange(const Stmt *Node, const llvm::ArrayRef &DeclIdentifiers) { if (!Node) return nullptr; if (const auto *DeclRef = dyn_cast(Node)) { if (llvm::is_contained(DeclIdentifiers, DeclRef->getDecl()->getID())) return DeclRef; } else { for (const Stmt *ChildNode : Node->children()) { if (const DeclRefExpr *Result = findUsageRange(ChildNode, DeclIdentifiers)) return Result; } } return nullptr; } static const DeclRefExpr *checkInitDeclUsageInElse(const IfStmt *If) { const auto *InitDeclStmt = dyn_cast_or_null(If->getInit()); if (!InitDeclStmt) return nullptr; if (InitDeclStmt->isSingleDecl()) { const Decl *InitDecl = InitDeclStmt->getSingleDecl(); assert(isa(InitDecl) && "SingleDecl must be a VarDecl"); return findUsage(If->getElse(), InitDecl->getID()); } llvm::SmallVector DeclIdentifiers; for (const Decl *ChildDecl : InitDeclStmt->decls()) { assert(isa(ChildDecl) && "Init Decls must be a VarDecl"); DeclIdentifiers.push_back(ChildDecl->getID()); } return findUsageRange(If->getElse(), DeclIdentifiers); } static const DeclRefExpr *checkConditionVarUsageInElse(const IfStmt *If) { if (const VarDecl *CondVar = If->getConditionVariable()) return findUsage(If->getElse(), CondVar->getID()); return nullptr; } static bool containsDeclInScope(const Stmt *Node) { if (isa(Node)) return true; if (const auto *Compound = dyn_cast(Node)) return llvm::any_of(Compound->body(), [](const Stmt *SubNode) { return isa(SubNode); }); return false; } static void removeElseAndBrackets(DiagnosticBuilder &Diag, ASTContext &Context, const Stmt *Else, SourceLocation ElseLoc) { auto Remap = [&](SourceLocation Loc) { return Context.getSourceManager().getExpansionLoc(Loc); }; auto TokLen = [&](SourceLocation Loc) { return Lexer::MeasureTokenLength(Loc, Context.getSourceManager(), Context.getLangOpts()); }; if (const auto *CS = dyn_cast(Else)) { Diag << tooling::fixit::createRemoval(ElseLoc); SourceLocation LBrace = CS->getLBracLoc(); SourceLocation RBrace = CS->getRBracLoc(); SourceLocation RangeStart = Remap(LBrace).getLocWithOffset(TokLen(LBrace) + 1); SourceLocation RangeEnd = Remap(RBrace).getLocWithOffset(-1); llvm::StringRef Repl = Lexer::getSourceText( CharSourceRange::getTokenRange(RangeStart, RangeEnd), Context.getSourceManager(), Context.getLangOpts()); Diag << tooling::fixit::createReplacement(CS->getSourceRange(), Repl); } else { SourceLocation ElseExpandedLoc = Remap(ElseLoc); SourceLocation EndLoc = Remap(Else->getEndLoc()); llvm::StringRef Repl = Lexer::getSourceText( CharSourceRange::getTokenRange( ElseExpandedLoc.getLocWithOffset(TokLen(ElseLoc) + 1), EndLoc), Context.getSourceManager(), Context.getLangOpts()); Diag << tooling::fixit::createReplacement( SourceRange(ElseExpandedLoc, EndLoc), Repl); } } ElseAfterReturnCheck::ElseAfterReturnCheck(StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context), WarnOnUnfixable(Options.get(WarnOnUnfixableStr, true)), WarnOnConditionVariables(Options.get(WarnOnConditionVariablesStr, true)) { } void ElseAfterReturnCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { Options.store(Opts, WarnOnUnfixableStr, WarnOnUnfixable); Options.store(Opts, WarnOnConditionVariablesStr, WarnOnConditionVariables); } void ElseAfterReturnCheck::registerPPCallbacks(const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) { PP->addPPCallbacks( std::make_unique(this->PPConditionals, SM)); } void ElseAfterReturnCheck::registerMatchers(MatchFinder *Finder) { const auto InterruptsControlFlow = stmt(anyOf( returnStmt().bind(InterruptingStr), continueStmt().bind(InterruptingStr), breakStmt().bind(InterruptingStr), cxxThrowExpr().bind(InterruptingStr))); Finder->addMatcher( compoundStmt( forEach(ifStmt(unless(isConstexpr()), unless(isConsteval()), hasThen(stmt( anyOf(InterruptsControlFlow, compoundStmt(has(InterruptsControlFlow))))), hasElse(stmt().bind("else"))) .bind("if"))) .bind("cs"), this); } static bool hasPreprocessorBranchEndBetweenLocations( const ElseAfterReturnCheck::ConditionalBranchMap &ConditionalBranchMap, const SourceManager &SM, SourceLocation StartLoc, SourceLocation EndLoc) { SourceLocation ExpandedStartLoc = SM.getExpansionLoc(StartLoc); SourceLocation ExpandedEndLoc = SM.getExpansionLoc(EndLoc); if (!SM.isWrittenInSameFile(ExpandedStartLoc, ExpandedEndLoc)) return false; // StartLoc and EndLoc expand to the same macro. if (ExpandedStartLoc == ExpandedEndLoc) return false; assert(ExpandedStartLoc < ExpandedEndLoc); auto Iter = ConditionalBranchMap.find(SM.getFileID(ExpandedEndLoc)); if (Iter == ConditionalBranchMap.end() || Iter->getSecond().empty()) return false; const SmallVectorImpl &ConditionalBranches = Iter->getSecond(); assert(llvm::is_sorted(ConditionalBranches, [](const SourceRange &LHS, const SourceRange &RHS) { return LHS.getEnd() < RHS.getEnd(); })); // First conditional block that ends after ExpandedStartLoc. const auto *Begin = llvm::lower_bound(ConditionalBranches, ExpandedStartLoc, [](const SourceRange &LHS, const SourceLocation &RHS) { return LHS.getEnd() < RHS; }); const auto *End = ConditionalBranches.end(); for (; Begin != End && Begin->getEnd() < ExpandedEndLoc; ++Begin) if (Begin->getBegin() < ExpandedStartLoc) return true; return false; } static StringRef getControlFlowString(const Stmt &Stmt) { if (isa(Stmt)) return "return"; if (isa(Stmt)) return "continue"; if (isa(Stmt)) return "break"; if (isa(Stmt)) return "throw"; llvm_unreachable("Unknown control flow interrupter"); } void ElseAfterReturnCheck::check(const MatchFinder::MatchResult &Result) { const auto *If = Result.Nodes.getNodeAs("if"); const auto *Else = Result.Nodes.getNodeAs("else"); const auto *OuterScope = Result.Nodes.getNodeAs("cs"); const auto *Interrupt = Result.Nodes.getNodeAs(InterruptingStr); SourceLocation ElseLoc = If->getElseLoc(); if (hasPreprocessorBranchEndBetweenLocations( PPConditionals, *Result.SourceManager, Interrupt->getBeginLoc(), ElseLoc)) return; bool IsLastInScope = OuterScope->body_back() == If; const StringRef ControlFlowInterrupter = getControlFlowString(*Interrupt); if (!IsLastInScope && containsDeclInScope(Else)) { if (WarnOnUnfixable) { // Warn, but don't attempt an autofix. diag(ElseLoc, WarningMessage) << ControlFlowInterrupter; } return; } if (checkConditionVarUsageInElse(If) != nullptr) { if (!WarnOnConditionVariables) return; if (IsLastInScope) { // If the if statement is the last statement of its enclosing statements // scope, we can pull the decl out of the if statement. DiagnosticBuilder Diag = diag(ElseLoc, WarningMessage) << ControlFlowInterrupter << SourceRange(ElseLoc); if (checkInitDeclUsageInElse(If) != nullptr) { Diag << tooling::fixit::createReplacement( SourceRange(If->getIfLoc()), (tooling::fixit::getText(*If->getInit(), *Result.Context) + llvm::StringRef("\n")) .str()) << tooling::fixit::createRemoval(If->getInit()->getSourceRange()); } const DeclStmt *VDeclStmt = If->getConditionVariableDeclStmt(); const VarDecl *VDecl = If->getConditionVariable(); std::string Repl = (tooling::fixit::getText(*VDeclStmt, *Result.Context) + llvm::StringRef(";\n") + tooling::fixit::getText(If->getIfLoc(), *Result.Context)) .str(); Diag << tooling::fixit::createReplacement(SourceRange(If->getIfLoc()), Repl) << tooling::fixit::createReplacement(VDeclStmt->getSourceRange(), VDecl->getName()); removeElseAndBrackets(Diag, *Result.Context, Else, ElseLoc); } else if (WarnOnUnfixable) { // Warn, but don't attempt an autofix. diag(ElseLoc, WarningMessage) << ControlFlowInterrupter; } return; } if (checkInitDeclUsageInElse(If) != nullptr) { if (!WarnOnConditionVariables) return; if (IsLastInScope) { // If the if statement is the last statement of its enclosing statements // scope, we can pull the decl out of the if statement. DiagnosticBuilder Diag = diag(ElseLoc, WarningMessage) << ControlFlowInterrupter << SourceRange(ElseLoc); Diag << tooling::fixit::createReplacement( SourceRange(If->getIfLoc()), (tooling::fixit::getText(*If->getInit(), *Result.Context) + "\n" + tooling::fixit::getText(If->getIfLoc(), *Result.Context)) .str()) << tooling::fixit::createRemoval(If->getInit()->getSourceRange()); removeElseAndBrackets(Diag, *Result.Context, Else, ElseLoc); } else if (WarnOnUnfixable) { // Warn, but don't attempt an autofix. diag(ElseLoc, WarningMessage) << ControlFlowInterrupter; } return; } DiagnosticBuilder Diag = diag(ElseLoc, WarningMessage) << ControlFlowInterrupter << SourceRange(ElseLoc); removeElseAndBrackets(Diag, *Result.Context, Else, ElseLoc); } } // namespace clang::tidy::readability