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 "clang/AST/ASTContext.h"
11 #include "clang/ASTMatchers/ASTMatchers.h"
12 #include "clang/Lex/Lexer.h"
13
14 using namespace clang::ast_matchers;
15
16 namespace clang {
17 namespace tidy {
18 namespace readability {
19 namespace {
20
getTokenKind(SourceLocation Loc,const SourceManager & SM,const ASTContext * Context)21 tok::TokenKind getTokenKind(SourceLocation Loc, const SourceManager &SM,
22 const ASTContext *Context) {
23 Token Tok;
24 SourceLocation Beginning =
25 Lexer::GetBeginningOfToken(Loc, SM, Context->getLangOpts());
26 const bool Invalid =
27 Lexer::getRawToken(Beginning, Tok, SM, Context->getLangOpts());
28 assert(!Invalid && "Expected a valid token.");
29
30 if (Invalid)
31 return tok::NUM_TOKENS;
32
33 return Tok.getKind();
34 }
35
forwardSkipWhitespaceAndComments(SourceLocation Loc,const SourceManager & SM,const ASTContext * Context)36 SourceLocation forwardSkipWhitespaceAndComments(SourceLocation Loc,
37 const SourceManager &SM,
38 const ASTContext *Context) {
39 assert(Loc.isValid());
40 for (;;) {
41 while (isWhitespace(*SM.getCharacterData(Loc)))
42 Loc = Loc.getLocWithOffset(1);
43
44 tok::TokenKind TokKind = getTokenKind(Loc, SM, Context);
45 if (TokKind != tok::comment)
46 return Loc;
47
48 // Fast-forward current token.
49 Loc = Lexer::getLocForEndOfToken(Loc, 0, SM, Context->getLangOpts());
50 }
51 }
52
findEndLocation(SourceLocation LastTokenLoc,const SourceManager & SM,const ASTContext * Context)53 SourceLocation findEndLocation(SourceLocation LastTokenLoc,
54 const SourceManager &SM,
55 const ASTContext *Context) {
56 SourceLocation Loc =
57 Lexer::GetBeginningOfToken(LastTokenLoc, SM, Context->getLangOpts());
58 // Loc points to the beginning of the last (non-comment non-ws) token
59 // before end or ';'.
60 assert(Loc.isValid());
61 bool SkipEndWhitespaceAndComments = true;
62 tok::TokenKind TokKind = getTokenKind(Loc, SM, Context);
63 if (TokKind == tok::NUM_TOKENS || TokKind == tok::semi ||
64 TokKind == tok::r_brace) {
65 // If we are at ";" or "}", we found the last token. We could use as well
66 // `if (isa<NullStmt>(S))`, but it wouldn't work for nested statements.
67 SkipEndWhitespaceAndComments = false;
68 }
69
70 Loc = Lexer::getLocForEndOfToken(Loc, 0, SM, Context->getLangOpts());
71 // Loc points past the last token before end or after ';'.
72 if (SkipEndWhitespaceAndComments) {
73 Loc = forwardSkipWhitespaceAndComments(Loc, SM, Context);
74 tok::TokenKind TokKind = getTokenKind(Loc, SM, Context);
75 if (TokKind == tok::semi)
76 Loc = Lexer::getLocForEndOfToken(Loc, 0, SM, Context->getLangOpts());
77 }
78
79 for (;;) {
80 assert(Loc.isValid());
81 while (isHorizontalWhitespace(*SM.getCharacterData(Loc))) {
82 Loc = Loc.getLocWithOffset(1);
83 }
84
85 if (isVerticalWhitespace(*SM.getCharacterData(Loc))) {
86 // EOL, insert brace before.
87 break;
88 }
89 tok::TokenKind TokKind = getTokenKind(Loc, SM, Context);
90 if (TokKind != tok::comment) {
91 // Non-comment token, insert brace before.
92 break;
93 }
94
95 SourceLocation TokEndLoc =
96 Lexer::getLocForEndOfToken(Loc, 0, SM, Context->getLangOpts());
97 SourceRange TokRange(Loc, TokEndLoc);
98 StringRef Comment = Lexer::getSourceText(
99 CharSourceRange::getTokenRange(TokRange), SM, Context->getLangOpts());
100 if (Comment.startswith("/*") && Comment.find('\n') != StringRef::npos) {
101 // Multi-line block comment, insert brace before.
102 break;
103 }
104 // else: Trailing comment, insert brace after the newline.
105
106 // Fast-forward current token.
107 Loc = TokEndLoc;
108 }
109 return Loc;
110 }
111
112 } // namespace
113
BracesAroundStatementsCheck(StringRef Name,ClangTidyContext * Context)114 BracesAroundStatementsCheck::BracesAroundStatementsCheck(
115 StringRef Name, ClangTidyContext *Context)
116 : ClangTidyCheck(Name, Context),
117 // Always add braces by default.
118 ShortStatementLines(Options.get("ShortStatementLines", 0U)) {}
119
storeOptions(ClangTidyOptions::OptionMap & Opts)120 void BracesAroundStatementsCheck::storeOptions(
121 ClangTidyOptions::OptionMap &Opts) {
122 Options.store(Opts, "ShortStatementLines", ShortStatementLines);
123 }
124
registerMatchers(MatchFinder * Finder)125 void BracesAroundStatementsCheck::registerMatchers(MatchFinder *Finder) {
126 Finder->addMatcher(
127 ifStmt(unless(allOf(isConstexpr(), isInTemplateInstantiation())))
128 .bind("if"),
129 this);
130 Finder->addMatcher(whileStmt().bind("while"), this);
131 Finder->addMatcher(doStmt().bind("do"), this);
132 Finder->addMatcher(forStmt().bind("for"), this);
133 Finder->addMatcher(cxxForRangeStmt().bind("for-range"), this);
134 }
135
check(const MatchFinder::MatchResult & Result)136 void BracesAroundStatementsCheck::check(
137 const MatchFinder::MatchResult &Result) {
138 const SourceManager &SM = *Result.SourceManager;
139 const ASTContext *Context = Result.Context;
140
141 // Get location of closing parenthesis or 'do' to insert opening brace.
142 if (auto S = Result.Nodes.getNodeAs<ForStmt>("for")) {
143 checkStmt(Result, S->getBody(), S->getRParenLoc());
144 } else if (auto S = Result.Nodes.getNodeAs<CXXForRangeStmt>("for-range")) {
145 checkStmt(Result, S->getBody(), S->getRParenLoc());
146 } else if (auto S = Result.Nodes.getNodeAs<DoStmt>("do")) {
147 checkStmt(Result, S->getBody(), S->getDoLoc(), S->getWhileLoc());
148 } else if (auto S = Result.Nodes.getNodeAs<WhileStmt>("while")) {
149 SourceLocation StartLoc = findRParenLoc(S, SM, Context);
150 if (StartLoc.isInvalid())
151 return;
152 checkStmt(Result, S->getBody(), StartLoc);
153 } else if (auto S = Result.Nodes.getNodeAs<IfStmt>("if")) {
154 SourceLocation StartLoc = findRParenLoc(S, SM, Context);
155 if (StartLoc.isInvalid())
156 return;
157 if (ForceBracesStmts.erase(S))
158 ForceBracesStmts.insert(S->getThen());
159 bool BracedIf = checkStmt(Result, S->getThen(), StartLoc, S->getElseLoc());
160 const Stmt *Else = S->getElse();
161 if (Else && BracedIf)
162 ForceBracesStmts.insert(Else);
163 if (Else && !isa<IfStmt>(Else)) {
164 // Omit 'else if' statements here, they will be handled directly.
165 checkStmt(Result, Else, S->getElseLoc());
166 }
167 } else {
168 llvm_unreachable("Invalid match");
169 }
170 }
171
172 /// Find location of right parenthesis closing condition.
173 template <typename IfOrWhileStmt>
174 SourceLocation
findRParenLoc(const IfOrWhileStmt * S,const SourceManager & SM,const ASTContext * Context)175 BracesAroundStatementsCheck::findRParenLoc(const IfOrWhileStmt *S,
176 const SourceManager &SM,
177 const ASTContext *Context) {
178 // Skip macros.
179 if (S->getBeginLoc().isMacroID())
180 return SourceLocation();
181
182 SourceLocation CondEndLoc = S->getCond()->getEndLoc();
183 if (const DeclStmt *CondVar = S->getConditionVariableDeclStmt())
184 CondEndLoc = CondVar->getEndLoc();
185
186 if (!CondEndLoc.isValid()) {
187 return SourceLocation();
188 }
189
190 SourceLocation PastCondEndLoc =
191 Lexer::getLocForEndOfToken(CondEndLoc, 0, SM, Context->getLangOpts());
192 if (PastCondEndLoc.isInvalid())
193 return SourceLocation();
194 SourceLocation RParenLoc =
195 forwardSkipWhitespaceAndComments(PastCondEndLoc, SM, Context);
196 if (RParenLoc.isInvalid())
197 return SourceLocation();
198 tok::TokenKind TokKind = getTokenKind(RParenLoc, SM, Context);
199 if (TokKind != tok::r_paren)
200 return SourceLocation();
201 return RParenLoc;
202 }
203
204 /// Determine if the statement needs braces around it, and add them if it does.
205 /// Returns true if braces where added.
checkStmt(const MatchFinder::MatchResult & Result,const Stmt * S,SourceLocation InitialLoc,SourceLocation EndLocHint)206 bool BracesAroundStatementsCheck::checkStmt(
207 const MatchFinder::MatchResult &Result, const Stmt *S,
208 SourceLocation InitialLoc, SourceLocation EndLocHint) {
209 // 1) If there's a corresponding "else" or "while", the check inserts "} "
210 // right before that token.
211 // 2) If there's a multi-line block comment starting on the same line after
212 // the location we're inserting the closing brace at, or there's a non-comment
213 // token, the check inserts "\n}" right before that token.
214 // 3) Otherwise the check finds the end of line (possibly after some block or
215 // line comments) and inserts "\n}" right before that EOL.
216 if (!S || isa<CompoundStmt>(S)) {
217 // Already inside braces.
218 return false;
219 }
220
221 if (!InitialLoc.isValid())
222 return false;
223 const SourceManager &SM = *Result.SourceManager;
224 const ASTContext *Context = Result.Context;
225
226 // Treat macros.
227 CharSourceRange FileRange = Lexer::makeFileCharRange(
228 CharSourceRange::getTokenRange(S->getSourceRange()), SM,
229 Context->getLangOpts());
230 if (FileRange.isInvalid())
231 return false;
232
233 // Convert InitialLoc to file location, if it's on the same macro expansion
234 // level as the start of the statement. We also need file locations for
235 // Lexer::getLocForEndOfToken working properly.
236 InitialLoc = Lexer::makeFileCharRange(
237 CharSourceRange::getCharRange(InitialLoc, S->getBeginLoc()),
238 SM, Context->getLangOpts())
239 .getBegin();
240 if (InitialLoc.isInvalid())
241 return false;
242 SourceLocation StartLoc =
243 Lexer::getLocForEndOfToken(InitialLoc, 0, SM, Context->getLangOpts());
244
245 // StartLoc points at the location of the opening brace to be inserted.
246 SourceLocation EndLoc;
247 std::string ClosingInsertion;
248 if (EndLocHint.isValid()) {
249 EndLoc = EndLocHint;
250 ClosingInsertion = "} ";
251 } else {
252 const auto FREnd = FileRange.getEnd().getLocWithOffset(-1);
253 EndLoc = findEndLocation(FREnd, SM, Context);
254 ClosingInsertion = "\n}";
255 }
256
257 assert(StartLoc.isValid());
258 assert(EndLoc.isValid());
259 // Don't require braces for statements spanning less than certain number of
260 // lines.
261 if (ShortStatementLines && !ForceBracesStmts.erase(S)) {
262 unsigned StartLine = SM.getSpellingLineNumber(StartLoc);
263 unsigned EndLine = SM.getSpellingLineNumber(EndLoc);
264 if (EndLine - StartLine < ShortStatementLines)
265 return false;
266 }
267
268 auto Diag = diag(StartLoc, "statement should be inside braces");
269 Diag << FixItHint::CreateInsertion(StartLoc, " {")
270 << FixItHint::CreateInsertion(EndLoc, ClosingInsertion);
271 return true;
272 }
273
onEndOfTranslationUnit()274 void BracesAroundStatementsCheck::onEndOfTranslationUnit() {
275 ForceBracesStmts.clear();
276 }
277
278 } // namespace readability
279 } // namespace tidy
280 } // namespace clang
281