clang-tools  10.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 "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 
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 
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::NUM_TOKENS || 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 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 
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 
122  Options.store(Opts, "ShortStatementLines", ShortStatementLines);
123 }
124 
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 
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
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.
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 
275  ForceBracesStmts.clear();
276 }
277 
278 } // namespace readability
279 } // namespace tidy
280 } // namespace clang
SourceLocation Loc
&#39;#&#39; location in the include directive
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
Base class for all clang-tidy checks.
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.
static constexpr llvm::StringLiteral Name
std::map< std::string, std::string > OptionMap
llvm::Optional< Range > getTokenRange(const SourceManager &SM, const LangOptions &LangOpts, SourceLocation TokLoc)
Returns the taken range at TokLoc.
Definition: SourceCode.cpp:227
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
Should store all options supported by this check with their current values or default values for opti...
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check&#39;s name.
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.