clang-tools  3.9.0
BracesAroundStatementsCheck.cpp
Go to the documentation of this file.
1 //===--- BracesAroundStatementsCheck.cpp - clang-tidy ---------------------===//
2 //
3 // The LLVM Compiler Infrastructure
4 //
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
7 //
8 //===----------------------------------------------------------------------===//
9 
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 namespace {
21 
22 tok::TokenKind getTokenKind(SourceLocation Loc, const SourceManager &SM,
23  const ASTContext *Context) {
24  Token Tok;
25  SourceLocation Beginning =
26  Lexer::GetBeginningOfToken(Loc, SM, Context->getLangOpts());
27  const bool Invalid =
28  Lexer::getRawToken(Beginning, Tok, SM, Context->getLangOpts());
29  assert(!Invalid && "Expected a valid token.");
30 
31  if (Invalid)
32  return tok::NUM_TOKENS;
33 
34  return Tok.getKind();
35 }
36 
37 SourceLocation forwardSkipWhitespaceAndComments(SourceLocation Loc,
38  const SourceManager &SM,
39  const ASTContext *Context) {
40  assert(Loc.isValid());
41  for (;;) {
42  while (isWhitespace(*FullSourceLoc(Loc, SM).getCharacterData()))
43  Loc = Loc.getLocWithOffset(1);
44 
45  tok::TokenKind TokKind = getTokenKind(Loc, SM, Context);
46  if (TokKind == tok::NUM_TOKENS || TokKind != tok::comment)
47  return Loc;
48 
49  // Fast-forward current token.
50  Loc = Lexer::getLocForEndOfToken(Loc, 0, SM, Context->getLangOpts());
51  }
52 }
53 
54 SourceLocation findEndLocation(SourceLocation LastTokenLoc,
55  const SourceManager &SM,
56  const ASTContext *Context) {
57  SourceLocation Loc = LastTokenLoc;
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 
73  if (SkipEndWhitespaceAndComments) {
74  Loc = forwardSkipWhitespaceAndComments(Loc, SM, Context);
75  tok::TokenKind TokKind = getTokenKind(Loc, SM, Context);
76  if (TokKind == tok::semi)
77  Loc = Lexer::getLocForEndOfToken(Loc, 0, SM, Context->getLangOpts());
78  }
79 
80  for (;;) {
81  assert(Loc.isValid());
82  while (isHorizontalWhitespace(*FullSourceLoc(Loc, SM).getCharacterData()))
83  Loc = Loc.getLocWithOffset(1);
84 
85  if (isVerticalWhitespace(*FullSourceLoc(Loc, SM).getCharacterData())) {
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 
120 void
122  Options.store(Opts, "ShortStatementLines", ShortStatementLines);
123 }
124 
126  Finder->addMatcher(ifStmt().bind("if"), this);
127  Finder->addMatcher(whileStmt().bind("while"), this);
128  Finder->addMatcher(doStmt().bind("do"), this);
129  Finder->addMatcher(forStmt().bind("for"), this);
130  Finder->addMatcher(cxxForRangeStmt().bind("for-range"), this);
131 }
132 
133 void
134 BracesAroundStatementsCheck::check(const MatchFinder::MatchResult &Result) {
135  const SourceManager &SM = *Result.SourceManager;
136  const ASTContext *Context = Result.Context;
137 
138  // Get location of closing parenthesis or 'do' to insert opening brace.
139  if (auto S = Result.Nodes.getNodeAs<ForStmt>("for")) {
140  checkStmt(Result, S->getBody(), S->getRParenLoc());
141  } else if (auto S = Result.Nodes.getNodeAs<CXXForRangeStmt>("for-range")) {
142  checkStmt(Result, S->getBody(), S->getRParenLoc());
143  } else if (auto S = Result.Nodes.getNodeAs<DoStmt>("do")) {
144  checkStmt(Result, S->getBody(), S->getDoLoc(), S->getWhileLoc());
145  } else if (auto S = Result.Nodes.getNodeAs<WhileStmt>("while")) {
146  SourceLocation StartLoc = findRParenLoc(S, SM, Context);
147  if (StartLoc.isInvalid())
148  return;
149  checkStmt(Result, S->getBody(), StartLoc);
150  } else if (auto S = Result.Nodes.getNodeAs<IfStmt>("if")) {
151  SourceLocation StartLoc = findRParenLoc(S, SM, Context);
152  if (StartLoc.isInvalid())
153  return;
154  if (ForceBracesStmts.erase(S))
155  ForceBracesStmts.insert(S->getThen());
156  bool BracedIf = checkStmt(Result, S->getThen(), StartLoc, S->getElseLoc());
157  const Stmt *Else = S->getElse();
158  if (Else && BracedIf)
159  ForceBracesStmts.insert(Else);
160  if (Else && !isa<IfStmt>(Else)) {
161  // Omit 'else if' statements here, they will be handled directly.
162  checkStmt(Result, Else, S->getElseLoc(), SourceLocation());
163  }
164  } else {
165  llvm_unreachable("Invalid match");
166  }
167 }
168 
169 /// Find location of right parenthesis closing condition
170 template <typename IfOrWhileStmt>
171 SourceLocation
172 BracesAroundStatementsCheck::findRParenLoc(const IfOrWhileStmt *S,
173  const SourceManager &SM,
174  const ASTContext *Context) {
175  // Skip macros.
176  if (S->getLocStart().isMacroID())
177  return SourceLocation();
178 
179  SourceLocation CondEndLoc = S->getCond()->getLocEnd();
180  if (const DeclStmt *CondVar = S->getConditionVariableDeclStmt())
181  CondEndLoc = CondVar->getLocEnd();
182 
183  if (!CondEndLoc.isValid()) {
184  return SourceLocation();
185  }
186 
187  SourceLocation PastCondEndLoc =
188  Lexer::getLocForEndOfToken(CondEndLoc, 0, SM, Context->getLangOpts());
189  if (PastCondEndLoc.isInvalid())
190  return SourceLocation();
191  SourceLocation RParenLoc =
192  forwardSkipWhitespaceAndComments(PastCondEndLoc, SM, Context);
193  if (RParenLoc.isInvalid())
194  return SourceLocation();
195  tok::TokenKind TokKind = getTokenKind(RParenLoc, SM, Context);
196  if (TokKind != tok::r_paren)
197  return SourceLocation();
198  return RParenLoc;
199 }
200 
201 /// Determine if the statement needs braces around it, and add them if it does.
202 /// Returns true if braces where added.
203 bool BracesAroundStatementsCheck::checkStmt(
204  const MatchFinder::MatchResult &Result, const Stmt *S,
205  SourceLocation InitialLoc, SourceLocation EndLocHint) {
206  // 1) If there's a corresponding "else" or "while", the check inserts "} "
207  // right before that token.
208  // 2) If there's a multi-line block comment starting on the same line after
209  // the location we're inserting the closing brace at, or there's a non-comment
210  // token, the check inserts "\n}" right before that token.
211  // 3) Otherwise the check finds the end of line (possibly after some block or
212  // line comments) and inserts "\n}" right before that EOL.
213  if (!S || isa<CompoundStmt>(S)) {
214  // Already inside braces.
215  return false;
216  }
217 
218  if (!InitialLoc.isValid())
219  return false;
220  const SourceManager &SM = *Result.SourceManager;
221  const ASTContext *Context = Result.Context;
222 
223  // Treat macros.
224  CharSourceRange FileRange = Lexer::makeFileCharRange(
225  CharSourceRange::getTokenRange(S->getSourceRange()), SM,
226  Context->getLangOpts());
227  if (FileRange.isInvalid())
228  return false;
229 
230  // Convert InitialLoc to file location, if it's on the same macro expansion
231  // level as the start of the statement. We also need file locations for
232  // Lexer::getLocForEndOfToken working properly.
233  InitialLoc = Lexer::makeFileCharRange(
234  CharSourceRange::getCharRange(InitialLoc, S->getLocStart()),
235  SM, Context->getLangOpts())
236  .getBegin();
237  if (InitialLoc.isInvalid())
238  return false;
239  SourceLocation StartLoc =
240  Lexer::getLocForEndOfToken(InitialLoc, 0, SM, Context->getLangOpts());
241 
242  // StartLoc points at the location of the opening brace to be inserted.
243  SourceLocation EndLoc;
244  std::string ClosingInsertion;
245  if (EndLocHint.isValid()) {
246  EndLoc = EndLocHint;
247  ClosingInsertion = "} ";
248  } else {
249  const auto FREnd = FileRange.getEnd().getLocWithOffset(-1);
250  EndLoc = findEndLocation(FREnd, SM, Context);
251  ClosingInsertion = "\n}";
252  }
253 
254  assert(StartLoc.isValid());
255  assert(EndLoc.isValid());
256  // Don't require braces for statements spanning less than certain number of
257  // lines.
258  if (ShortStatementLines && !ForceBracesStmts.erase(S)) {
259  unsigned StartLine = SM.getSpellingLineNumber(StartLoc);
260  unsigned EndLine = SM.getSpellingLineNumber(EndLoc);
261  if (EndLine - StartLine < ShortStatementLines)
262  return false;
263  }
264 
265  auto Diag = diag(StartLoc, "statement should be inside braces");
266  Diag << FixItHint::CreateInsertion(StartLoc, " {")
267  << FixItHint::CreateInsertion(EndLoc, ClosingInsertion);
268  return true;
269 }
270 
272  ForceBracesStmts.clear();
273 }
274 
275 } // namespace readability
276 } // namespace tidy
277 } // namespace clang
SourceLocation Loc
'#' location in the include directive
const std::string Name
Definition: USRFinder.cpp:140
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
std::unique_ptr< ast_matchers::MatchFinder > Finder
Definition: ClangTidy.cpp:210
Base class for all clang-tidy checks.
Definition: ClangTidy.h:110
SourceManager & SM
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: ClangTidy.cpp:385
std::map< std::string, std::string > OptionMap
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
Should store all options supported by this check with their current values or default values for opti...
ClangTidyContext & Context
Definition: ClangTidy.cpp:93
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's name.
Definition: ClangTidy.cpp:352
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
const NamedDecl * Result
Definition: USRFinder.cpp:137