xref: /llvm-project/clang-tools-extra/clang-tidy/readability/BracesAroundStatementsCheck.cpp (revision 7a4e89761a13bfad27a2614ecea5e8698f50336c)
1 //===--- BracesAroundStatementsCheck.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 "BracesAroundStatementsCheck.h"
10 #include "../utils/BracesAroundStatement.h"
11 #include "../utils/LexerUtils.h"
12 #include "clang/AST/ASTContext.h"
13 #include "clang/ASTMatchers/ASTMatchers.h"
14 #include "clang/Lex/Lexer.h"
15 
16 using namespace clang::ast_matchers;
17 
18 namespace clang::tidy::readability {
19 
getTokenKind(SourceLocation Loc,const SourceManager & SM,const LangOptions & LangOpts)20 static tok::TokenKind getTokenKind(SourceLocation Loc, const SourceManager &SM,
21                                    const LangOptions &LangOpts) {
22   Token Tok;
23   SourceLocation Beginning = Lexer::GetBeginningOfToken(Loc, SM, LangOpts);
24   const bool Invalid = Lexer::getRawToken(Beginning, Tok, SM, LangOpts);
25   assert(!Invalid && "Expected a valid token.");
26 
27   if (Invalid)
28     return tok::NUM_TOKENS;
29 
30   return Tok.getKind();
31 }
32 
33 static SourceLocation
forwardSkipWhitespaceAndComments(SourceLocation Loc,const SourceManager & SM,const LangOptions & LangOpts)34 forwardSkipWhitespaceAndComments(SourceLocation Loc, const SourceManager &SM,
35                                  const LangOptions &LangOpts) {
36   assert(Loc.isValid());
37   for (;;) {
38     while (isWhitespace(*SM.getCharacterData(Loc)))
39       Loc = Loc.getLocWithOffset(1);
40 
41     tok::TokenKind TokKind = getTokenKind(Loc, SM, LangOpts);
42     if (TokKind != tok::comment)
43       return Loc;
44 
45     // Fast-forward current token.
46     Loc = Lexer::getLocForEndOfToken(Loc, 0, SM, LangOpts);
47   }
48 }
49 
BracesAroundStatementsCheck(StringRef Name,ClangTidyContext * Context)50 BracesAroundStatementsCheck::BracesAroundStatementsCheck(
51     StringRef Name, ClangTidyContext *Context)
52     : ClangTidyCheck(Name, Context),
53       // Always add braces by default.
54       ShortStatementLines(Options.get("ShortStatementLines", 0U)) {}
55 
storeOptions(ClangTidyOptions::OptionMap & Opts)56 void BracesAroundStatementsCheck::storeOptions(
57     ClangTidyOptions::OptionMap &Opts) {
58   Options.store(Opts, "ShortStatementLines", ShortStatementLines);
59 }
60 
registerMatchers(MatchFinder * Finder)61 void BracesAroundStatementsCheck::registerMatchers(MatchFinder *Finder) {
62   Finder->addMatcher(ifStmt().bind("if"), this);
63   Finder->addMatcher(whileStmt().bind("while"), this);
64   Finder->addMatcher(doStmt().bind("do"), this);
65   Finder->addMatcher(forStmt().bind("for"), this);
66   Finder->addMatcher(cxxForRangeStmt().bind("for-range"), this);
67 }
68 
check(const MatchFinder::MatchResult & Result)69 void BracesAroundStatementsCheck::check(
70     const MatchFinder::MatchResult &Result) {
71   const SourceManager &SM = *Result.SourceManager;
72   const ASTContext *Context = Result.Context;
73 
74   // Get location of closing parenthesis or 'do' to insert opening brace.
75   if (const auto *S = Result.Nodes.getNodeAs<ForStmt>("for")) {
76     checkStmt(Result, S->getBody(), S->getRParenLoc());
77   } else if (const auto *S =
78                  Result.Nodes.getNodeAs<CXXForRangeStmt>("for-range")) {
79     checkStmt(Result, S->getBody(), S->getRParenLoc());
80   } else if (const auto *S = Result.Nodes.getNodeAs<DoStmt>("do")) {
81     checkStmt(Result, S->getBody(), S->getDoLoc(), S->getWhileLoc());
82   } else if (const auto *S = Result.Nodes.getNodeAs<WhileStmt>("while")) {
83     SourceLocation StartLoc = findRParenLoc(S, SM, Context->getLangOpts());
84     if (StartLoc.isInvalid())
85       return;
86     checkStmt(Result, S->getBody(), StartLoc);
87   } else if (const auto *S = Result.Nodes.getNodeAs<IfStmt>("if")) {
88     // "if consteval" always has braces.
89     if (S->isConsteval())
90       return;
91 
92     SourceLocation StartLoc = findRParenLoc(S, SM, Context->getLangOpts());
93     if (StartLoc.isInvalid())
94       return;
95     if (ForceBracesStmts.erase(S))
96       ForceBracesStmts.insert(S->getThen());
97     bool BracedIf = checkStmt(Result, S->getThen(), StartLoc, S->getElseLoc());
98     const Stmt *Else = S->getElse();
99     if (Else && BracedIf)
100       ForceBracesStmts.insert(Else);
101     if (Else && !isa<IfStmt>(Else)) {
102       // Omit 'else if' statements here, they will be handled directly.
103       checkStmt(Result, Else, S->getElseLoc());
104     }
105   } else {
106     llvm_unreachable("Invalid match");
107   }
108 }
109 
110 /// Find location of right parenthesis closing condition.
111 template <typename IfOrWhileStmt>
112 SourceLocation
findRParenLoc(const IfOrWhileStmt * S,const SourceManager & SM,const LangOptions & LangOpts)113 BracesAroundStatementsCheck::findRParenLoc(const IfOrWhileStmt *S,
114                                            const SourceManager &SM,
115                                            const LangOptions &LangOpts) {
116   // Skip macros.
117   if (S->getBeginLoc().isMacroID())
118     return {};
119 
120   SourceLocation CondEndLoc = S->getCond()->getEndLoc();
121   if (const DeclStmt *CondVar = S->getConditionVariableDeclStmt())
122     CondEndLoc = CondVar->getEndLoc();
123 
124   if (!CondEndLoc.isValid()) {
125     return {};
126   }
127 
128   SourceLocation PastCondEndLoc =
129       Lexer::getLocForEndOfToken(CondEndLoc, 0, SM, LangOpts);
130   if (PastCondEndLoc.isInvalid())
131     return {};
132   SourceLocation RParenLoc =
133       forwardSkipWhitespaceAndComments(PastCondEndLoc, SM, LangOpts);
134   if (RParenLoc.isInvalid())
135     return {};
136   tok::TokenKind TokKind = getTokenKind(RParenLoc, SM, LangOpts);
137   if (TokKind != tok::r_paren)
138     return {};
139   return RParenLoc;
140 }
141 
142 /// Determine if the statement needs braces around it, and add them if it does.
143 /// Returns true if braces where added.
checkStmt(const MatchFinder::MatchResult & Result,const Stmt * S,SourceLocation StartLoc,SourceLocation EndLocHint)144 bool BracesAroundStatementsCheck::checkStmt(
145     const MatchFinder::MatchResult &Result, const Stmt *S,
146     SourceLocation StartLoc, SourceLocation EndLocHint) {
147   while (const auto *AS = dyn_cast<AttributedStmt>(S))
148     S = AS->getSubStmt();
149 
150   const auto BraceInsertionHints = utils::getBraceInsertionsHints(
151       S, Result.Context->getLangOpts(), *Result.SourceManager, StartLoc,
152       EndLocHint);
153   if (BraceInsertionHints) {
154     if (ShortStatementLines && !ForceBracesStmts.erase(S) &&
155         BraceInsertionHints.resultingCompoundLineExtent(*Result.SourceManager) <
156             ShortStatementLines)
157       return false;
158     auto Diag = diag(BraceInsertionHints.DiagnosticPos,
159                      "statement should be inside braces");
160     if (BraceInsertionHints.offersFixIts())
161       Diag << BraceInsertionHints.openingBraceFixIt()
162            << BraceInsertionHints.closingBraceFixIt();
163   }
164   return true;
165 }
166 
onEndOfTranslationUnit()167 void BracesAroundStatementsCheck::onEndOfTranslationUnit() {
168   ForceBracesStmts.clear();
169 }
170 
171 } // namespace clang::tidy::readability
172