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 | |
14 | namespace clang::tidy::bugprone { |
15 | |
16 | namespace { |
17 | class MacroParenthesesPPCallbacks : public PPCallbacks { |
18 | public: |
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 | |
28 | private: |
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? |
41 | static 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? |
47 | static 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? |
53 | static 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. |
59 | static 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? |
67 | static 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? |
75 | static 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 | |
101 | void 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 | |
153 | void 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 | |
264 | void 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 | |