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

source code of clang-tools-extra/clang-tidy/bugprone/MacroParenthesesCheck.cpp