clang-tools 19.0.0git
BracesAroundStatementsCheck.cpp
Go to the documentation of this file.
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
10#include "../utils/LexerUtils.h"
11#include "clang/AST/ASTContext.h"
12#include "clang/ASTMatchers/ASTMatchers.h"
13#include "clang/Lex/Lexer.h"
14
15using namespace clang::ast_matchers;
16
18
19static tok::TokenKind getTokenKind(SourceLocation Loc, const SourceManager &SM,
20 const ASTContext *Context) {
21 Token Tok;
22 SourceLocation Beginning =
23 Lexer::GetBeginningOfToken(Loc, SM, Context->getLangOpts());
24 const bool Invalid =
25 Lexer::getRawToken(Beginning, Tok, SM, Context->getLangOpts());
26 assert(!Invalid && "Expected a valid token.");
27
28 if (Invalid)
29 return tok::NUM_TOKENS;
30
31 return Tok.getKind();
32}
33
34static SourceLocation
35forwardSkipWhitespaceAndComments(SourceLocation Loc, const SourceManager &SM,
36 const ASTContext *Context) {
37 assert(Loc.isValid());
38 for (;;) {
39 while (isWhitespace(*SM.getCharacterData(Loc)))
40 Loc = Loc.getLocWithOffset(1);
41
42 tok::TokenKind TokKind = getTokenKind(Loc, SM, Context);
43 if (TokKind != tok::comment)
44 return Loc;
45
46 // Fast-forward current token.
47 Loc = Lexer::getLocForEndOfToken(Loc, 0, SM, Context->getLangOpts());
48 }
49}
50
51static SourceLocation findEndLocation(const Stmt &S, const SourceManager &SM,
52 const ASTContext *Context) {
53 SourceLocation Loc =
54 utils::lexer::getUnifiedEndLoc(S, SM, Context->getLangOpts());
55 if (!Loc.isValid())
56 return Loc;
57
58 // Start searching right after S.
59 Loc = Loc.getLocWithOffset(1);
60
61 for (;;) {
62 assert(Loc.isValid());
63 while (isHorizontalWhitespace(*SM.getCharacterData(Loc))) {
64 Loc = Loc.getLocWithOffset(1);
65 }
66
67 if (isVerticalWhitespace(*SM.getCharacterData(Loc))) {
68 // EOL, insert brace before.
69 break;
70 }
71 tok::TokenKind TokKind = getTokenKind(Loc, SM, Context);
72 if (TokKind != tok::comment) {
73 // Non-comment token, insert brace before.
74 break;
75 }
76
77 SourceLocation TokEndLoc =
78 Lexer::getLocForEndOfToken(Loc, 0, SM, Context->getLangOpts());
79 SourceRange TokRange(Loc, TokEndLoc);
80 StringRef Comment = Lexer::getSourceText(
81 CharSourceRange::getTokenRange(TokRange), SM, Context->getLangOpts());
82 if (Comment.starts_with("/*") && Comment.contains('\n')) {
83 // Multi-line block comment, insert brace before.
84 break;
85 }
86 // else: Trailing comment, insert brace after the newline.
87
88 // Fast-forward current token.
89 Loc = TokEndLoc;
90 }
91 return Loc;
92}
93
95 StringRef Name, ClangTidyContext *Context)
96 : ClangTidyCheck(Name, Context),
97 // Always add braces by default.
98 ShortStatementLines(Options.get("ShortStatementLines", 0U)) {}
99
102 Options.store(Opts, "ShortStatementLines", ShortStatementLines);
103}
104
106 Finder->addMatcher(ifStmt().bind("if"), this);
107 Finder->addMatcher(whileStmt().bind("while"), this);
108 Finder->addMatcher(doStmt().bind("do"), this);
109 Finder->addMatcher(forStmt().bind("for"), this);
110 Finder->addMatcher(cxxForRangeStmt().bind("for-range"), this);
111}
112
114 const MatchFinder::MatchResult &Result) {
115 const SourceManager &SM = *Result.SourceManager;
116 const ASTContext *Context = Result.Context;
117
118 // Get location of closing parenthesis or 'do' to insert opening brace.
119 if (const auto *S = Result.Nodes.getNodeAs<ForStmt>("for")) {
120 checkStmt(Result, S->getBody(), S->getRParenLoc());
121 } else if (const auto *S =
122 Result.Nodes.getNodeAs<CXXForRangeStmt>("for-range")) {
123 checkStmt(Result, S->getBody(), S->getRParenLoc());
124 } else if (const auto *S = Result.Nodes.getNodeAs<DoStmt>("do")) {
125 checkStmt(Result, S->getBody(), S->getDoLoc(), S->getWhileLoc());
126 } else if (const auto *S = Result.Nodes.getNodeAs<WhileStmt>("while")) {
127 SourceLocation StartLoc = findRParenLoc(S, SM, Context);
128 if (StartLoc.isInvalid())
129 return;
130 checkStmt(Result, S->getBody(), StartLoc);
131 } else if (const auto *S = Result.Nodes.getNodeAs<IfStmt>("if")) {
132 // "if consteval" always has braces.
133 if (S->isConsteval())
134 return;
135
136 SourceLocation StartLoc = findRParenLoc(S, SM, Context);
137 if (StartLoc.isInvalid())
138 return;
139 if (ForceBracesStmts.erase(S))
140 ForceBracesStmts.insert(S->getThen());
141 bool BracedIf = checkStmt(Result, S->getThen(), StartLoc, S->getElseLoc());
142 const Stmt *Else = S->getElse();
143 if (Else && BracedIf)
144 ForceBracesStmts.insert(Else);
145 if (Else && !isa<IfStmt>(Else)) {
146 // Omit 'else if' statements here, they will be handled directly.
147 checkStmt(Result, Else, S->getElseLoc());
148 }
149 } else {
150 llvm_unreachable("Invalid match");
151 }
152}
153
154/// Find location of right parenthesis closing condition.
155template <typename IfOrWhileStmt>
156SourceLocation
157BracesAroundStatementsCheck::findRParenLoc(const IfOrWhileStmt *S,
158 const SourceManager &SM,
159 const ASTContext *Context) {
160 // Skip macros.
161 if (S->getBeginLoc().isMacroID())
162 return {};
163
164 SourceLocation CondEndLoc = S->getCond()->getEndLoc();
165 if (const DeclStmt *CondVar = S->getConditionVariableDeclStmt())
166 CondEndLoc = CondVar->getEndLoc();
167
168 if (!CondEndLoc.isValid()) {
169 return {};
170 }
171
172 SourceLocation PastCondEndLoc =
173 Lexer::getLocForEndOfToken(CondEndLoc, 0, SM, Context->getLangOpts());
174 if (PastCondEndLoc.isInvalid())
175 return {};
176 SourceLocation RParenLoc =
177 forwardSkipWhitespaceAndComments(PastCondEndLoc, SM, Context);
178 if (RParenLoc.isInvalid())
179 return {};
180 tok::TokenKind TokKind = getTokenKind(RParenLoc, SM, Context);
181 if (TokKind != tok::r_paren)
182 return {};
183 return RParenLoc;
184}
185
186/// Determine if the statement needs braces around it, and add them if it does.
187/// Returns true if braces where added.
188bool BracesAroundStatementsCheck::checkStmt(
189 const MatchFinder::MatchResult &Result, const Stmt *S,
190 SourceLocation StartLoc, SourceLocation EndLocHint) {
191
192 while (const auto *AS = dyn_cast<AttributedStmt>(S))
193 S = AS->getSubStmt();
194
195 const SourceManager &SM = *Result.SourceManager;
196 const ASTContext *Context = Result.Context;
197
198 // 1) If there's a corresponding "else" or "while", the check inserts "} "
199 // right before that token.
200 // 2) If there's a multi-line block comment starting on the same line after
201 // the location we're inserting the closing brace at, or there's a non-comment
202 // token, the check inserts "\n}" right before that token.
203 // 3) Otherwise the check finds the end of line (possibly after some block or
204 // line comments) and inserts "\n}" right before that EOL.
205 if (!S || isa<CompoundStmt>(S)) {
206 // Already inside braces.
207 return false;
208 }
209
210 // When TreeTransform, Stmt in constexpr IfStmt will be transform to NullStmt.
211 // This NullStmt can be detected according to beginning token.
212 const SourceLocation StmtBeginLoc = S->getBeginLoc();
213 if (isa<NullStmt>(S) && StmtBeginLoc.isValid() &&
214 getTokenKind(StmtBeginLoc, SM, Context) == tok::l_brace)
215 return false;
216
217 if (StartLoc.isInvalid())
218 return false;
219
220 // Convert StartLoc to file location, if it's on the same macro expansion
221 // level as the start of the statement. We also need file locations for
222 // Lexer::getLocForEndOfToken working properly.
223 StartLoc = Lexer::makeFileCharRange(
224 CharSourceRange::getCharRange(StartLoc, S->getBeginLoc()), SM,
225 Context->getLangOpts())
226 .getBegin();
227 if (StartLoc.isInvalid())
228 return false;
229 StartLoc =
230 Lexer::getLocForEndOfToken(StartLoc, 0, SM, Context->getLangOpts());
231
232 // StartLoc points at the location of the opening brace to be inserted.
233 SourceLocation EndLoc;
234 std::string ClosingInsertion;
235 if (EndLocHint.isValid()) {
236 EndLoc = EndLocHint;
237 ClosingInsertion = "} ";
238 } else {
239 EndLoc = findEndLocation(*S, SM, Context);
240 ClosingInsertion = "\n}";
241 }
242
243 assert(StartLoc.isValid());
244
245 // Don't require braces for statements spanning less than certain number of
246 // lines.
247 if (ShortStatementLines && !ForceBracesStmts.erase(S)) {
248 unsigned StartLine = SM.getSpellingLineNumber(StartLoc);
249 unsigned EndLine = SM.getSpellingLineNumber(EndLoc);
250 if (EndLine - StartLine < ShortStatementLines)
251 return false;
252 }
253
254 auto Diag = diag(StartLoc, "statement should be inside braces");
255
256 // Change only if StartLoc and EndLoc are on the same macro expansion level.
257 // This will also catch invalid EndLoc.
258 // Example: LLVM_DEBUG( for(...) do_something() );
259 // In this case fix-it cannot be provided as the semicolon which is not
260 // visible here is part of the macro. Adding braces here would require adding
261 // another semicolon.
262 if (Lexer::makeFileCharRange(
263 CharSourceRange::getTokenRange(SourceRange(
264 SM.getSpellingLoc(StartLoc), SM.getSpellingLoc(EndLoc))),
265 SM, Context->getLangOpts())
266 .isInvalid())
267 return false;
268
269 Diag << FixItHint::CreateInsertion(StartLoc, " {")
270 << FixItHint::CreateInsertion(EndLoc, ClosingInsertion);
271 return true;
272}
273
275 ForceBracesStmts.clear();
276}
277
278} // namespace clang::tidy::readability
llvm::SmallString< 256U > Name
SourceLocation Loc
void store(ClangTidyOptions::OptionMap &Options, StringRef LocalName, StringRef Value) const
Stores an option with the check-local name LocalName with string value Value to Options.
Base class for all clang-tidy checks.
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check's name.
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
BracesAroundStatementsCheck(StringRef Name, ClangTidyContext *Context)
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
Should store all options supported by this check with their current values or default values for opti...
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
static SourceLocation forwardSkipWhitespaceAndComments(SourceLocation Loc, const SourceManager &SM, const ASTContext *Context)
static tok::TokenKind getTokenKind(SourceLocation Loc, const SourceManager &SM, const ASTContext *Context)
static SourceLocation findEndLocation(const Stmt &S, const SourceManager &SM, const ASTContext *Context)
SourceLocation getUnifiedEndLoc(const Stmt &S, const SourceManager &SM, const LangOptions &LangOpts)
Stmt->getEndLoc does not always behave the same way depending on Token type.
Definition: LexerUtils.cpp:236
llvm::StringMap< ClangTidyValue > OptionMap