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
16using namespace clang::ast_matchers;
17
18namespace clang::tidy::readability {
19
20static 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(Loc: Beginning, Result&: 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
33static SourceLocation
34forwardSkipWhitespaceAndComments(SourceLocation Loc, const SourceManager &SM,
35 const LangOptions &LangOpts) {
36 assert(Loc.isValid());
37 for (;;) {
38 while (isWhitespace(c: *SM.getCharacterData(SL: Loc)))
39 Loc = Loc.getLocWithOffset(Offset: 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, Offset: 0, SM, LangOpts);
47 }
48}
49
50BracesAroundStatementsCheck::BracesAroundStatementsCheck(
51 StringRef Name, ClangTidyContext *Context)
52 : ClangTidyCheck(Name, Context),
53 // Always add braces by default.
54 ShortStatementLines(Options.get(LocalName: "ShortStatementLines", Default: 0U)) {}
55
56void BracesAroundStatementsCheck::storeOptions(
57 ClangTidyOptions::OptionMap &Opts) {
58 Options.store(Options&: Opts, LocalName: "ShortStatementLines", Value: ShortStatementLines);
59}
60
61void BracesAroundStatementsCheck::registerMatchers(MatchFinder *Finder) {
62 Finder->addMatcher(NodeMatch: ifStmt().bind(ID: "if"), Action: this);
63 Finder->addMatcher(NodeMatch: whileStmt().bind(ID: "while"), Action: this);
64 Finder->addMatcher(NodeMatch: doStmt().bind(ID: "do"), Action: this);
65 Finder->addMatcher(NodeMatch: forStmt().bind(ID: "for"), Action: this);
66 Finder->addMatcher(NodeMatch: cxxForRangeStmt().bind(ID: "for-range"), Action: this);
67}
68
69void 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>(ID: "for")) {
76 checkStmt(Result, S: S->getBody(), StartLoc: S->getRParenLoc());
77 } else if (const auto *S =
78 Result.Nodes.getNodeAs<CXXForRangeStmt>(ID: "for-range")) {
79 checkStmt(Result, S: S->getBody(), StartLoc: S->getRParenLoc());
80 } else if (const auto *S = Result.Nodes.getNodeAs<DoStmt>(ID: "do")) {
81 checkStmt(Result, S: S->getBody(), StartLoc: S->getDoLoc(), EndLocHint: S->getWhileLoc());
82 } else if (const auto *S = Result.Nodes.getNodeAs<WhileStmt>(ID: "while")) {
83 SourceLocation StartLoc = findRParenLoc(S, SM, LangOpts: Context->getLangOpts());
84 if (StartLoc.isInvalid())
85 return;
86 checkStmt(Result, S: S->getBody(), StartLoc);
87 } else if (const auto *S = Result.Nodes.getNodeAs<IfStmt>(ID: "if")) {
88 // "if consteval" always has braces.
89 if (S->isConsteval())
90 return;
91
92 SourceLocation StartLoc = findRParenLoc(S, SM, LangOpts: Context->getLangOpts());
93 if (StartLoc.isInvalid())
94 return;
95 if (ForceBracesStmts.erase(S))
96 ForceBracesStmts.insert(x: S->getThen());
97 bool BracedIf = checkStmt(Result, S: S->getThen(), StartLoc, EndLocHint: S->getElseLoc());
98 const Stmt *Else = S->getElse();
99 if (Else && BracedIf)
100 ForceBracesStmts.insert(x: Else);
101 if (Else && !isa<IfStmt>(Val: Else)) {
102 // Omit 'else if' statements here, they will be handled directly.
103 checkStmt(Result, S: Else, StartLoc: S->getElseLoc());
104 }
105 } else {
106 llvm_unreachable("Invalid match");
107 }
108}
109
110/// Find location of right parenthesis closing condition.
111template <typename IfOrWhileStmt>
112SourceLocation
113BracesAroundStatementsCheck::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(Loc: CondEndLoc, Offset: 0, SM, LangOpts);
130 if (PastCondEndLoc.isInvalid())
131 return {};
132 SourceLocation RParenLoc =
133 forwardSkipWhitespaceAndComments(Loc: PastCondEndLoc, SM, LangOpts);
134 if (RParenLoc.isInvalid())
135 return {};
136 tok::TokenKind TokKind = getTokenKind(Loc: 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.
144bool BracesAroundStatementsCheck::checkStmt(
145 const MatchFinder::MatchResult &Result, const Stmt *S,
146 SourceLocation StartLoc, SourceLocation EndLocHint) {
147 while (const auto *AS = dyn_cast<AttributedStmt>(Val: S))
148 S = AS->getSubStmt();
149
150 const auto BraceInsertionHints = utils::getBraceInsertionsHints(
151 S, LangOpts: Result.Context->getLangOpts(), SM: *Result.SourceManager, StartLoc,
152 EndLocHint);
153 if (BraceInsertionHints) {
154 if (ShortStatementLines && !ForceBracesStmts.erase(x: S) &&
155 BraceInsertionHints.resultingCompoundLineExtent(SourceMgr: *Result.SourceManager) <
156 ShortStatementLines)
157 return false;
158 auto Diag = diag(Loc: BraceInsertionHints.DiagnosticPos,
159 Description: "statement should be inside braces");
160 if (BraceInsertionHints.offersFixIts())
161 Diag << BraceInsertionHints.openingBraceFixIt()
162 << BraceInsertionHints.closingBraceFixIt();
163 }
164 return true;
165}
166
167void BracesAroundStatementsCheck::onEndOfTranslationUnit() {
168 ForceBracesStmts.clear();
169}
170
171} // namespace clang::tidy::readability
172

source code of clang-tools-extra/clang-tidy/readability/BracesAroundStatementsCheck.cpp