clang-tools 23.0.0git
MacroParenthesesCheck.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
10#include "clang/Frontend/CompilerInstance.h"
11#include "clang/Lex/PPCallbacks.h"
12#include "clang/Lex/Preprocessor.h"
13
14namespace clang::tidy::bugprone {
15
16namespace {
17class MacroParenthesesPPCallbacks : public PPCallbacks {
18public:
19 MacroParenthesesPPCallbacks(Preprocessor *PP, MacroParenthesesCheck *Check)
20 : PP(PP), Check(Check) {}
21
22 void MacroDefined(const Token &MacroNameTok,
23 const MacroDirective *MD) override {
24 replacementList(MacroNameTok, MD->getMacroInfo());
25 argument(MacroNameTok, MD->getMacroInfo());
26 }
27
28private:
29 /// Replacement list with calculations should be enclosed in parentheses.
30 void replacementList(const Token &MacroNameTok, const MacroInfo *MI);
31
32 /// Arguments should be enclosed in parentheses.
33 void argument(const Token &MacroNameTok, const MacroInfo *MI);
34
35 Preprocessor *PP;
36 MacroParenthesesCheck *Check;
37};
38} // namespace
39
40/// Is argument surrounded properly with parentheses/braces/squares/commas?
41static bool isSurroundedLeft(const Token &T) {
42 return T.isOneOf(tok::l_paren, tok::l_brace, tok::l_square, tok::comma,
43 tok::semi);
44}
45
46/// Is argument surrounded properly with parentheses/braces/squares/commas?
47static bool isSurroundedRight(const Token &T) {
48 return T.isOneOf(tok::r_paren, tok::r_brace, tok::r_square, tok::comma,
49 tok::semi);
50}
51
52/// Is given TokenKind a keyword?
53static bool isKeyword(const Token &T) {
54 // FIXME: better matching of keywords to avoid false positives.
55 return T.isOneOf(tok::kw_if, tok::kw_case, tok::kw_const, tok::kw_volatile,
56 tok::kw_struct);
57}
58
59/// Warning is written when one of these operators are not within parentheses.
60static bool isWarnOp(const Token &T) {
61 // FIXME: This is an initial list of operators. It can be tweaked later to
62 // get more positives or perhaps avoid some false positive.
63 return T.isOneOf(tok::plus, tok::minus, tok::star, tok::slash, tok::percent,
64 tok::amp, tok::pipe, tok::caret);
65}
66
67/// Is given Token a keyword that is used in variable declarations?
68static bool isVarDeclKeyword(const Token &T) {
69 return T.isOneOf(tok::kw_bool, tok::kw_char, tok::kw_short, tok::kw_int,
70 tok::kw_long, tok::kw_float, tok::kw_double, tok::kw_const,
71 tok::kw_enum, tok::kw_inline, tok::kw_static, tok::kw_struct,
72 tok::kw_signed, tok::kw_unsigned);
73}
74
75/// Is there a possible variable declaration at Tok?
76static bool possibleVarDecl(const MacroInfo *MI, const Token *Tok) {
77 if (Tok == MI->tokens_end())
78 return false;
79
80 // If we see int/short/struct/etc., just assume this is a variable
81 // declaration.
82 if (isVarDeclKeyword(*Tok))
83 return true;
84
85 // Variable declarations start with identifier or coloncolon.
86 if (!Tok->isOneOf(tok::identifier, tok::raw_identifier, tok::coloncolon))
87 return false;
88
89 // Skip possible types, etc
90 while (Tok != MI->tokens_end() &&
91 Tok->isOneOf(tok::identifier, tok::raw_identifier, tok::coloncolon,
92 tok::star, tok::amp, tok::ampamp, tok::less,
93 tok::greater))
94 Tok++;
95
96 // Return true for possible variable declarations.
97 return Tok == MI->tokens_end() ||
98 Tok->isOneOf(tok::equal, tok::semi, tok::l_square, tok::l_paren) ||
99 isVarDeclKeyword(*Tok);
100}
101
102static StringRef getMacroText(const MacroInfo *MI, Preprocessor *PP) {
103 if (MI->tokens_empty())
104 return {};
105 return Lexer::getSourceText(
106 CharSourceRange::getTokenRange(MI->tokens_begin()->getLocation(),
107 MI->tokens().back().getLocation()),
108 PP->getSourceManager(), PP->getLangOpts());
109}
110
111void MacroParenthesesPPCallbacks::replacementList(const Token &MacroNameTok,
112 const MacroInfo *MI) {
113 // Make sure macro replacement isn't a variable declaration.
114 if (possibleVarDecl(MI, MI->tokens_begin()))
115 return;
116
117 // Count how deep we are in parentheses/braces/squares.
118 int Count = 0;
119
120 // SourceLocation for error
121 SourceLocation Loc;
122
123 for (auto TI = MI->tokens_begin(), TE = MI->tokens_end(); TI != TE; ++TI) {
124 const Token &Tok = *TI;
125 // Replacement list contains keywords, don't warn about it.
126 if (isKeyword(Tok))
127 return;
128 // When replacement list contains comma/semi don't warn about it.
129 if (Count == 0 && Tok.isOneOf(tok::comma, tok::semi))
130 return;
131 if (Tok.isOneOf(tok::l_paren, tok::l_brace, tok::l_square)) {
132 ++Count;
133 } else if (Tok.isOneOf(tok::r_paren, tok::r_brace, tok::r_square)) {
134 --Count;
135 // If there are unbalanced parentheses don't write any warning
136 if (Count < 0)
137 return;
138 } else if (Count == 0 && isWarnOp(Tok)) {
139 // Heuristic for macros that are clearly not intended to be enclosed in
140 // parentheses, macro starts with operator. For example:
141 // #define X *10
142 if (TI == MI->tokens_begin() && std::next(TI) != TE &&
143 !Tok.isOneOf(tok::plus, tok::minus))
144 return;
145 // Don't warn about this macro if the last token is a star. For example:
146 // #define X void *
147 if (std::prev(TE)->is(tok::star))
148 return;
149
150 Loc = Tok.getLocation();
151 }
152 }
153 if (Loc.isValid()) {
154 const Token &Last = *std::prev(MI->tokens_end());
155 if (PP->getSourceManager().isWrittenInCommandLineFile(Loc)) {
156 Check->diag(Loc, "macro replacement list should be enclosed in "
157 "parentheses; macro '%0' defined as '%1'")
158 << PP->getSpelling(MacroNameTok) << getMacroText(MI, PP)
159 << FixItHint::CreateInsertion(MI->tokens_begin()->getLocation(), "(")
160 << FixItHint::CreateInsertion(Last.getLocation().getLocWithOffset(
161 PP->getSpelling(Last).length()),
162 ")");
163 } else {
164 Check->diag(Loc,
165 "macro replacement list should be enclosed in parentheses")
166 << FixItHint::CreateInsertion(MI->tokens_begin()->getLocation(), "(")
167 << FixItHint::CreateInsertion(Last.getLocation().getLocWithOffset(
168 PP->getSpelling(Last).length()),
169 ")");
170 }
171 }
172}
173
174void MacroParenthesesPPCallbacks::argument(const Token &MacroNameTok,
175 const MacroInfo *MI) {
176 // Skip variable declaration.
177 bool VarDecl = possibleVarDecl(MI, MI->tokens_begin());
178
179 // Skip the goto argument with an arbitrary number of subsequent stars.
180 bool FoundGoto = false;
181
182 for (auto TI = MI->tokens_begin(), TE = MI->tokens_end(); TI != TE; ++TI) {
183 // First token.
184 if (TI == MI->tokens_begin())
185 continue;
186
187 // Last token.
188 if (std::next(TI) == MI->tokens_end())
189 continue;
190
191 const Token &Prev = *std::prev(TI);
192 const Token &Next = *std::next(TI);
193
194 const Token &Tok = *TI;
195
196 // There should not be extra parentheses in possible variable declaration.
197 if (VarDecl) {
198 if (Tok.isOneOf(tok::equal, tok::semi, tok::l_square, tok::l_paren))
199 VarDecl = false;
200 continue;
201 }
202
203 // There should not be extra parentheses for the goto argument.
204 if (Tok.is(tok::kw_goto)) {
205 FoundGoto = true;
206 continue;
207 }
208
209 // Only interested in identifiers.
210 if (!Tok.isOneOf(tok::identifier, tok::raw_identifier)) {
211 FoundGoto = false;
212 continue;
213 }
214
215 // Only interested in macro arguments.
216 if (MI->getParameterNum(Tok.getIdentifierInfo()) < 0)
217 continue;
218
219 // Argument is surrounded with parentheses/squares/braces/commas.
220 if (isSurroundedLeft(Prev) && isSurroundedRight(Next))
221 continue;
222
223 // Don't warn after hash/hashhash or before hashhash.
224 if (Prev.isOneOf(tok::hash, tok::hashhash) || Next.is(tok::hashhash))
225 continue;
226
227 // Argument is a struct member.
228 if (Prev.isOneOf(tok::period, tok::arrow, tok::coloncolon, tok::arrowstar,
229 tok::periodstar))
230 continue;
231
232 // Argument is a namespace or class.
233 if (Next.is(tok::coloncolon))
234 continue;
235
236 // String concatenation.
237 if (isStringLiteral(Prev.getKind()) || isStringLiteral(Next.getKind()))
238 continue;
239
240 // Type/Var.
241 if (isAnyIdentifier(Prev.getKind()) || isKeyword(Prev) ||
242 isAnyIdentifier(Next.getKind()) || isKeyword(Next))
243 continue;
244
245 // Initialization.
246 if (Next.is(tok::l_paren))
247 continue;
248
249 // Cast.
250 if (Prev.is(tok::l_paren) && Next.is(tok::star) &&
251 std::next(TI, 2) != MI->tokens_end() &&
252 std::next(TI, 2)->is(tok::r_paren))
253 continue;
254
255 // Assignment/return, i.e. '=x;' or 'return x;'.
256 if (Prev.isOneOf(tok::equal, tok::kw_return) && Next.is(tok::semi))
257 continue;
258
259 // C++ template parameters.
260 if (PP->getLangOpts().CPlusPlus && Prev.isOneOf(tok::comma, tok::less)) {
261 const auto *NextIt =
262 std::find_if_not(std::next(TI), MI->tokens_end(), [](const Token &T) {
263 return T.isOneOf(tok::star, tok::amp, tok::ampamp, tok::kw_const,
264 tok::kw_volatile);
265 });
266
267 if (NextIt != MI->tokens_end() &&
268 NextIt->isOneOf(tok::comma, tok::greater))
269 continue;
270 }
271
272 // Namespaces.
273 if (Prev.is(tok::kw_namespace))
274 continue;
275
276 // Variadic templates
277 if (MI->isVariadic())
278 continue;
279
280 if (!FoundGoto) {
281 Check->diag(Tok.getLocation(), "macro argument should be enclosed in "
282 "parentheses")
283 << FixItHint::CreateInsertion(Tok.getLocation(), "(")
284 << FixItHint::CreateInsertion(Tok.getLocation().getLocWithOffset(
285 PP->getSpelling(Tok).length()),
286 ")");
287 }
288
289 FoundGoto = false;
290 }
291}
292
294 const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) {
295 PP->addPPCallbacks(std::make_unique<MacroParenthesesPPCallbacks>(PP, this));
296}
297
298} // namespace clang::tidy::bugprone
void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) override
static bool isKeyword(const Token &T)
Is given TokenKind a keyword?
static bool isSurroundedRight(const Token &T)
Is argument surrounded properly with parentheses/braces/squares/commas?
static StringRef getMacroText(const MacroInfo *MI, Preprocessor *PP)
static bool isWarnOp(const Token &T)
Warning is written when one of these operators are not within parentheses.
static bool isVarDeclKeyword(const Token &T)
Is given Token a keyword that is used in variable declarations?
static bool isSurroundedLeft(const Token &T)
Is argument surrounded properly with parentheses/braces/squares/commas?
static bool possibleVarDecl(const MacroInfo *MI, const Token *Tok)
Is there a possible variable declaration at Tok?