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 "../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 
getTokenKind(SourceLocation Loc,const SourceManager & SM,const ASTContext * Context)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
forwardSkipWhitespaceAndComments(SourceLocation Loc,const SourceManager & SM,const ASTContext * Context)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 
findEndLocation(const Stmt & S,const SourceManager & SM,const ASTContext * Context)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 
BracesAroundStatementsCheck(StringRef Name,ClangTidyContext * Context)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 
storeOptions(ClangTidyOptions::OptionMap & Opts)102 void BracesAroundStatementsCheck::storeOptions(
103     ClangTidyOptions::OptionMap &Opts) {
104   Options.store(Opts, "ShortStatementLines", ShortStatementLines);
105 }
106 
registerMatchers(MatchFinder * Finder)107 void BracesAroundStatementsCheck::registerMatchers(MatchFinder *Finder) {
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 
check(const MatchFinder::MatchResult & Result)115 void BracesAroundStatementsCheck::check(
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
findRParenLoc(const IfOrWhileStmt * S,const SourceManager & SM,const ASTContext * Context)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.
checkStmt(const MatchFinder::MatchResult & Result,const Stmt * S,SourceLocation InitialLoc,SourceLocation EndLocHint)186 bool BracesAroundStatementsCheck::checkStmt(
187     const MatchFinder::MatchResult &Result, const Stmt *S,
188     SourceLocation InitialLoc, SourceLocation EndLocHint) {
189   // 1) If there's a corresponding "else" or "while", the check inserts "} "
190   // right before that token.
191   // 2) If there's a multi-line block comment starting on the same line after
192   // the location we're inserting the closing brace at, or there's a non-comment
193   // token, the check inserts "\n}" right before that token.
194   // 3) Otherwise the check finds the end of line (possibly after some block or
195   // line comments) and inserts "\n}" right before that EOL.
196   if (!S || isa<CompoundStmt>(S)) {
197     // Already inside braces.
198     return false;
199   }
200 
201   if (!InitialLoc.isValid())
202     return false;
203   const SourceManager &SM = *Result.SourceManager;
204   const ASTContext *Context = Result.Context;
205 
206   // Convert InitialLoc to file location, if it's on the same macro expansion
207   // level as the start of the statement. We also need file locations for
208   // Lexer::getLocForEndOfToken working properly.
209   InitialLoc = Lexer::makeFileCharRange(
210                    CharSourceRange::getCharRange(InitialLoc, S->getBeginLoc()),
211                    SM, Context->getLangOpts())
212                    .getBegin();
213   if (InitialLoc.isInvalid())
214     return false;
215   SourceLocation StartLoc =
216       Lexer::getLocForEndOfToken(InitialLoc, 0, SM, Context->getLangOpts());
217 
218   // StartLoc points at the location of the opening brace to be inserted.
219   SourceLocation EndLoc;
220   std::string ClosingInsertion;
221   if (EndLocHint.isValid()) {
222     EndLoc = EndLocHint;
223     ClosingInsertion = "} ";
224   } else {
225     EndLoc = findEndLocation(*S, SM, Context);
226     ClosingInsertion = "\n}";
227   }
228 
229   assert(StartLoc.isValid());
230 
231   // Don't require braces for statements spanning less than certain number of
232   // lines.
233   if (ShortStatementLines && !ForceBracesStmts.erase(S)) {
234     unsigned StartLine = SM.getSpellingLineNumber(StartLoc);
235     unsigned EndLine = SM.getSpellingLineNumber(EndLoc);
236     if (EndLine - StartLine < ShortStatementLines)
237       return false;
238   }
239 
240   auto Diag = diag(StartLoc, "statement should be inside braces");
241 
242   // Change only if StartLoc and EndLoc are on the same macro expansion level.
243   // This will also catch invalid EndLoc.
244   // Example: LLVM_DEBUG( for(...) do_something() );
245   // In this case fix-it cannot be provided as the semicolon which is not
246   // visible here is part of the macro. Adding braces here would require adding
247   // another semicolon.
248   if (Lexer::makeFileCharRange(
249           CharSourceRange::getTokenRange(SourceRange(
250               SM.getSpellingLoc(StartLoc), SM.getSpellingLoc(EndLoc))),
251           SM, Context->getLangOpts())
252           .isInvalid())
253     return false;
254 
255   Diag << FixItHint::CreateInsertion(StartLoc, " {")
256        << FixItHint::CreateInsertion(EndLoc, ClosingInsertion);
257   return true;
258 }
259 
onEndOfTranslationUnit()260 void BracesAroundStatementsCheck::onEndOfTranslationUnit() {
261   ForceBracesStmts.clear();
262 }
263 
264 } // namespace readability
265 } // namespace tidy
266 } // namespace clang
267