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 | |
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(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 | |
33 | static SourceLocation |
34 | forwardSkipWhitespaceAndComments(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 | |
50 | BracesAroundStatementsCheck::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 | |
56 | void BracesAroundStatementsCheck::storeOptions( |
57 | ClangTidyOptions::OptionMap &Opts) { |
58 | Options.store(Options&: Opts, LocalName: "ShortStatementLines" , Value: ShortStatementLines); |
59 | } |
60 | |
61 | void 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 | |
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>(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. |
111 | template <typename IfOrWhileStmt> |
112 | SourceLocation |
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(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. |
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>(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 | |
167 | void BracesAroundStatementsCheck::onEndOfTranslationUnit() { |
168 | ForceBracesStmts.clear(); |
169 | } |
170 | |
171 | } // namespace clang::tidy::readability |
172 | |