//===--- BracesAroundStatementsCheck.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 "BracesAroundStatementsCheck.h" #include "../utils/BracesAroundStatement.h" #include "../utils/LexerUtils.h" #include "clang/AST/ASTContext.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "clang/Lex/Lexer.h" using namespace clang::ast_matchers; namespace clang::tidy::readability { static tok::TokenKind getTokenKind(SourceLocation Loc, const SourceManager &SM, const LangOptions &LangOpts) { Token Tok; SourceLocation Beginning = Lexer::GetBeginningOfToken(Loc, SM, LangOpts); const bool Invalid = Lexer::getRawToken(Beginning, Tok, SM, LangOpts); assert(!Invalid && "Expected a valid token."); if (Invalid) return tok::NUM_TOKENS; return Tok.getKind(); } static SourceLocation forwardSkipWhitespaceAndComments(SourceLocation Loc, const SourceManager &SM, const LangOptions &LangOpts) { assert(Loc.isValid()); for (;;) { while (isWhitespace(*SM.getCharacterData(Loc))) Loc = Loc.getLocWithOffset(1); tok::TokenKind TokKind = getTokenKind(Loc, SM, LangOpts); if (TokKind != tok::comment) return Loc; // Fast-forward current token. Loc = Lexer::getLocForEndOfToken(Loc, 0, SM, LangOpts); } } BracesAroundStatementsCheck::BracesAroundStatementsCheck( StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context), // Always add braces by default. ShortStatementLines(Options.get("ShortStatementLines", 0U)) {} void BracesAroundStatementsCheck::storeOptions( ClangTidyOptions::OptionMap &Opts) { Options.store(Opts, "ShortStatementLines", ShortStatementLines); } void BracesAroundStatementsCheck::registerMatchers(MatchFinder *Finder) { Finder->addMatcher(ifStmt().bind("if"), this); Finder->addMatcher(whileStmt().bind("while"), this); Finder->addMatcher(doStmt().bind("do"), this); Finder->addMatcher(forStmt().bind("for"), this); Finder->addMatcher(cxxForRangeStmt().bind("for-range"), this); } void BracesAroundStatementsCheck::check( const MatchFinder::MatchResult &Result) { const SourceManager &SM = *Result.SourceManager; const ASTContext *Context = Result.Context; // Get location of closing parenthesis or 'do' to insert opening brace. if (const auto *S = Result.Nodes.getNodeAs("for")) { checkStmt(Result, S->getBody(), S->getRParenLoc()); } else if (const auto *S = Result.Nodes.getNodeAs("for-range")) { checkStmt(Result, S->getBody(), S->getRParenLoc()); } else if (const auto *S = Result.Nodes.getNodeAs("do")) { checkStmt(Result, S->getBody(), S->getDoLoc(), S->getWhileLoc()); } else if (const auto *S = Result.Nodes.getNodeAs("while")) { SourceLocation StartLoc = findRParenLoc(S, SM, Context->getLangOpts()); if (StartLoc.isInvalid()) return; checkStmt(Result, S->getBody(), StartLoc); } else if (const auto *S = Result.Nodes.getNodeAs("if")) { // "if consteval" always has braces. if (S->isConsteval()) return; SourceLocation StartLoc = findRParenLoc(S, SM, Context->getLangOpts()); if (StartLoc.isInvalid()) return; if (ForceBracesStmts.erase(S)) ForceBracesStmts.insert(S->getThen()); bool BracedIf = checkStmt(Result, S->getThen(), StartLoc, S->getElseLoc()); const Stmt *Else = S->getElse(); if (Else && BracedIf) ForceBracesStmts.insert(Else); if (Else && !isa(Else)) { // Omit 'else if' statements here, they will be handled directly. checkStmt(Result, Else, S->getElseLoc()); } } else { llvm_unreachable("Invalid match"); } } /// Find location of right parenthesis closing condition. template SourceLocation BracesAroundStatementsCheck::findRParenLoc(const IfOrWhileStmt *S, const SourceManager &SM, const LangOptions &LangOpts) { // Skip macros. if (S->getBeginLoc().isMacroID()) return {}; SourceLocation CondEndLoc = S->getCond()->getEndLoc(); if (const DeclStmt *CondVar = S->getConditionVariableDeclStmt()) CondEndLoc = CondVar->getEndLoc(); if (!CondEndLoc.isValid()) { return {}; } SourceLocation PastCondEndLoc = Lexer::getLocForEndOfToken(CondEndLoc, 0, SM, LangOpts); if (PastCondEndLoc.isInvalid()) return {}; SourceLocation RParenLoc = forwardSkipWhitespaceAndComments(PastCondEndLoc, SM, LangOpts); if (RParenLoc.isInvalid()) return {}; tok::TokenKind TokKind = getTokenKind(RParenLoc, SM, LangOpts); if (TokKind != tok::r_paren) return {}; return RParenLoc; } /// Determine if the statement needs braces around it, and add them if it does. /// Returns true if braces where added. bool BracesAroundStatementsCheck::checkStmt( const MatchFinder::MatchResult &Result, const Stmt *S, SourceLocation StartLoc, SourceLocation EndLocHint) { while (const auto *AS = dyn_cast(S)) S = AS->getSubStmt(); const auto BraceInsertionHints = utils::getBraceInsertionsHints( S, Result.Context->getLangOpts(), *Result.SourceManager, StartLoc, EndLocHint); if (BraceInsertionHints) { if (ShortStatementLines && !ForceBracesStmts.erase(S) && BraceInsertionHints.resultingCompoundLineExtent(*Result.SourceManager) < ShortStatementLines) return false; auto Diag = diag(BraceInsertionHints.DiagnosticPos, "statement should be inside braces"); if (BraceInsertionHints.offersFixIts()) Diag << BraceInsertionHints.openingBraceFixIt() << BraceInsertionHints.closingBraceFixIt(); } return true; } void BracesAroundStatementsCheck::onEndOfTranslationUnit() { ForceBracesStmts.clear(); } } // namespace clang::tidy::readability