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