clang-tools 22.0.0git
BracesAroundStatementsCheck.cpp
Go to the documentation of this file.
1//===----------------------------------------------------------------------===//
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
11#include "../utils/LexerUtils.h"
12#include "clang/AST/ASTContext.h"
13#include "clang/ASTMatchers/ASTMatchers.h"
14#include "clang/Lex/Lexer.h"
15
16using namespace clang::ast_matchers;
17
19
20static tok::TokenKind getTokenKind(SourceLocation Loc, const SourceManager &SM,
21 const LangOptions &LangOpts) {
22 Token Tok;
23 const SourceLocation Beginning =
24 Lexer::GetBeginningOfToken(Loc, SM, LangOpts);
25 const bool Invalid = Lexer::getRawToken(Beginning, Tok, SM, LangOpts);
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 LangOptions &LangOpts) {
37 assert(Loc.isValid());
38 for (;;) {
39 while (isWhitespace(*SM.getCharacterData(Loc)))
40 Loc = Loc.getLocWithOffset(1);
41
42 const tok::TokenKind TokKind = getTokenKind(Loc, SM, LangOpts);
43 if (TokKind != tok::comment)
44 return Loc;
45
46 // Fast-forward current token.
47 Loc = Lexer::getLocForEndOfToken(Loc, 0, SM, LangOpts);
48 }
49}
50
52 StringRef Name, ClangTidyContext *Context)
53 : ClangTidyCheck(Name, Context),
54 // Always add braces by default.
55 ShortStatementLines(Options.get("ShortStatementLines", 0U)) {}
56
59 Options.store(Opts, "ShortStatementLines", ShortStatementLines);
60}
61
63 Finder->addMatcher(ifStmt().bind("if"), this);
64 Finder->addMatcher(whileStmt().bind("while"), this);
65 Finder->addMatcher(doStmt().bind("do"), this);
66 Finder->addMatcher(forStmt().bind("for"), this);
67 Finder->addMatcher(cxxForRangeStmt().bind("for-range"), this);
68}
69
71 const MatchFinder::MatchResult &Result) {
72 const SourceManager &SM = *Result.SourceManager;
73 const ASTContext *Context = Result.Context;
74
75 // Get location of closing parenthesis or 'do' to insert opening brace.
76 if (const auto *S = Result.Nodes.getNodeAs<ForStmt>("for")) {
77 checkStmt(Result, S->getBody(), S->getRParenLoc());
78 } else if (const auto *S =
79 Result.Nodes.getNodeAs<CXXForRangeStmt>("for-range")) {
80 checkStmt(Result, S->getBody(), S->getRParenLoc());
81 } else if (const auto *S = Result.Nodes.getNodeAs<DoStmt>("do")) {
82 checkStmt(Result, S->getBody(), S->getDoLoc(), S->getWhileLoc());
83 } else if (const auto *S = Result.Nodes.getNodeAs<WhileStmt>("while")) {
84 const SourceLocation StartLoc =
85 findRParenLoc(S, SM, Context->getLangOpts());
86 if (StartLoc.isInvalid())
87 return;
88 checkStmt(Result, S->getBody(), StartLoc);
89 } else if (const auto *S = Result.Nodes.getNodeAs<IfStmt>("if")) {
90 // "if consteval" always has braces.
91 if (S->isConsteval())
92 return;
93
94 const SourceLocation StartLoc =
95 findRParenLoc(S, SM, Context->getLangOpts());
96 if (StartLoc.isInvalid())
97 return;
98 if (ForceBracesStmts.erase(S))
99 ForceBracesStmts.insert(S->getThen());
100 const bool BracedIf =
101 checkStmt(Result, S->getThen(), StartLoc, S->getElseLoc());
102 const Stmt *Else = S->getElse();
103 if (Else && BracedIf)
104 ForceBracesStmts.insert(Else);
105 if (Else && !isa<IfStmt>(Else)) {
106 // Omit 'else if' statements here, they will be handled directly.
107 checkStmt(Result, Else, S->getElseLoc());
108 }
109 } else {
110 llvm_unreachable("Invalid match");
111 }
112}
113
114/// Find location of right parenthesis closing condition.
115template <typename IfOrWhileStmt>
116SourceLocation
117BracesAroundStatementsCheck::findRParenLoc(const IfOrWhileStmt *S,
118 const SourceManager &SM,
119 const LangOptions &LangOpts) {
120 // Skip macros.
121 if (S->getBeginLoc().isMacroID())
122 return {};
123
124 SourceLocation CondEndLoc = S->getCond()->getEndLoc();
125 if (const DeclStmt *CondVar = S->getConditionVariableDeclStmt())
126 CondEndLoc = CondVar->getEndLoc();
127
128 if (!CondEndLoc.isValid()) {
129 return {};
130 }
131
132 const SourceLocation PastCondEndLoc =
133 Lexer::getLocForEndOfToken(CondEndLoc, 0, SM, LangOpts);
134 if (PastCondEndLoc.isInvalid())
135 return {};
136 SourceLocation RParenLoc =
137 forwardSkipWhitespaceAndComments(PastCondEndLoc, SM, LangOpts);
138 if (RParenLoc.isInvalid())
139 return {};
140 const tok::TokenKind TokKind = getTokenKind(RParenLoc, SM, LangOpts);
141 if (TokKind != tok::r_paren)
142 return {};
143 return RParenLoc;
144}
145
146/// Determine if the statement needs braces around it, and add them if it does.
147/// Returns true if braces where added.
148bool BracesAroundStatementsCheck::checkStmt(
149 const MatchFinder::MatchResult &Result, const Stmt *S,
150 SourceLocation StartLoc, SourceLocation EndLocHint) {
151 while (const auto *AS = dyn_cast<AttributedStmt>(S))
152 S = AS->getSubStmt();
153
154 const auto BraceInsertionHints = utils::getBraceInsertionsHints(
155 S, Result.Context->getLangOpts(), *Result.SourceManager, StartLoc,
156 EndLocHint);
157 if (BraceInsertionHints) {
158 if (ShortStatementLines && !ForceBracesStmts.erase(S) &&
159 BraceInsertionHints.resultingCompoundLineExtent(*Result.SourceManager) <
160 ShortStatementLines)
161 return false;
162 auto Diag = diag(BraceInsertionHints.DiagnosticPos,
163 "statement should be inside braces");
164 if (BraceInsertionHints.offersFixIts())
165 Diag << BraceInsertionHints.openingBraceFixIt()
166 << BraceInsertionHints.closingBraceFixIt();
167 }
168 return true;
169}
170
172 ForceBracesStmts.clear();
173}
174
175} // namespace clang::tidy::readability
This file provides utilities to put braces around a statement.
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
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
void registerMatchers(ast_matchers::MatchFinder *Finder) override
static SourceLocation forwardSkipWhitespaceAndComments(SourceLocation Loc, const SourceManager &SM, const LangOptions &LangOpts)
static tok::TokenKind getTokenKind(SourceLocation Loc, const SourceManager &SM, const LangOptions &LangOpts)
BraceInsertionHints getBraceInsertionsHints(const Stmt *const S, const LangOptions &LangOpts, const SourceManager &SM, SourceLocation StartLoc, SourceLocation EndLocHint)
Create fix-it hints for braces that wrap the given statement when applied.
llvm::StringMap< ClangTidyValue > OptionMap