xref: /llvm-project/clang-tools-extra/clang-tidy/bugprone/MacroParenthesesCheck.cpp (revision 7d2ea6c422d3f5712b7253407005e1a465a76946)
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:
MacroParenthesesPPCallbacks(Preprocessor * PP,MacroParenthesesCheck * Check)19   MacroParenthesesPPCallbacks(Preprocessor *PP, MacroParenthesesCheck *Check)
20       : PP(PP), Check(Check) {}
21 
MacroDefined(const Token & MacroNameTok,const MacroDirective * MD)22   void MacroDefined(const Token &MacroNameTok,
23                     const MacroDirective *MD) override {
24     replacementList(MacroNameTok, MD->getMacroInfo());
25     argument(MacroNameTok, 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?
isSurroundedLeft(const Token & T)41 static 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?
isSurroundedRight(const Token & T)47 static 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?
isKeyword(const Token & T)53 static 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_struct);
56 }
57 
58 /// Warning is written when one of these operators are not within parentheses.
isWarnOp(const Token & T)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(tok::plus, tok::minus, tok::star, tok::slash, tok::percent,
63                    tok::amp, tok::pipe, tok::caret);
64 }
65 
66 /// Is given Token a keyword that is used in variable declarations?
isVarDeclKeyword(const Token & T)67 static bool isVarDeclKeyword(const Token &T) {
68   return T.isOneOf(tok::kw_bool, tok::kw_char, tok::kw_short, tok::kw_int,
69                    tok::kw_long, tok::kw_float, tok::kw_double, tok::kw_const,
70                    tok::kw_enum, tok::kw_inline, tok::kw_static, tok::kw_struct,
71                    tok::kw_signed, tok::kw_unsigned);
72 }
73 
74 /// Is there a possible variable declaration at Tok?
possibleVarDecl(const MacroInfo * MI,const Token * 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(*Tok))
82     return true;
83 
84   // Variable declarations start with identifier or coloncolon.
85   if (!Tok->isOneOf(tok::identifier, tok::raw_identifier, tok::coloncolon))
86     return false;
87 
88   // Skip possible types, etc
89   while (Tok != MI->tokens_end() &&
90          Tok->isOneOf(tok::identifier, tok::raw_identifier, tok::coloncolon,
91                       tok::star, tok::amp, tok::ampamp, tok::less,
92                       tok::greater))
93     Tok++;
94 
95   // Return true for possible variable declarations.
96   return Tok == MI->tokens_end() ||
97          Tok->isOneOf(tok::equal, tok::semi, tok::l_square, tok::l_paren) ||
98          isVarDeclKeyword(*Tok);
99 }
100 
replacementList(const Token & MacroNameTok,const MacroInfo * MI)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, 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(Tok))
117       return;
118     // When replacement list contains comma/semi don't warn about it.
119     if (Count == 0 && Tok.isOneOf(tok::comma, tok::semi))
120       return;
121     if (Tok.isOneOf(tok::l_paren, tok::l_brace, tok::l_square)) {
122       ++Count;
123     } else if (Tok.isOneOf(tok::r_paren, tok::r_brace, 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(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(tok::plus, 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(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, "macro replacement list should be enclosed in parentheses")
146         << FixItHint::CreateInsertion(MI->tokens_begin()->getLocation(), "(")
147         << FixItHint::CreateInsertion(Last.getLocation().getLocWithOffset(
148                                           PP->getSpelling(Last).length()),
149                                       ")");
150   }
151 }
152 
argument(const Token & MacroNameTok,const MacroInfo * MI)153 void MacroParenthesesPPCallbacks::argument(const Token &MacroNameTok,
154                                            const MacroInfo *MI) {
155 
156   // Skip variable declaration.
157   bool VarDecl = possibleVarDecl(MI, 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(tok::equal, tok::semi, tok::l_square, 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(tok::kw_goto)) {
185       FoundGoto = true;
186       continue;
187     }
188 
189     // Only interested in identifiers.
190     if (!Tok.isOneOf(tok::identifier, tok::raw_identifier)) {
191       FoundGoto = false;
192       continue;
193     }
194 
195     // Only interested in macro arguments.
196     if (MI->getParameterNum(Tok.getIdentifierInfo()) < 0)
197       continue;
198 
199     // Argument is surrounded with parentheses/squares/braces/commas.
200     if (isSurroundedLeft(Prev) && isSurroundedRight(Next))
201       continue;
202 
203     // Don't warn after hash/hashhash or before hashhash.
204     if (Prev.isOneOf(tok::hash, tok::hashhash) || Next.is(tok::hashhash))
205       continue;
206 
207     // Argument is a struct member.
208     if (Prev.isOneOf(tok::period, tok::arrow, tok::coloncolon, tok::arrowstar,
209                      tok::periodstar))
210       continue;
211 
212     // Argument is a namespace or class.
213     if (Next.is(tok::coloncolon))
214       continue;
215 
216     // String concatenation.
217     if (isStringLiteral(Prev.getKind()) || isStringLiteral(Next.getKind()))
218       continue;
219 
220     // Type/Var.
221     if (isAnyIdentifier(Prev.getKind()) || isKeyword(Prev) ||
222         isAnyIdentifier(Next.getKind()) || isKeyword(Next))
223       continue;
224 
225     // Initialization.
226     if (Next.is(tok::l_paren))
227       continue;
228 
229     // Cast.
230     if (Prev.is(tok::l_paren) && Next.is(tok::star) &&
231         TI + 2 != MI->tokens_end() && (TI + 2)->is(tok::r_paren))
232       continue;
233 
234     // Assignment/return, i.e. '=x;' or 'return x;'.
235     if (Prev.isOneOf(tok::equal, tok::kw_return) && Next.is(tok::semi))
236       continue;
237 
238     // C++ template parameters.
239     if (PP->getLangOpts().CPlusPlus && Prev.isOneOf(tok::comma, tok::less) &&
240         Next.isOneOf(tok::comma, tok::greater))
241       continue;
242 
243     // Namespaces.
244     if (Prev.is(tok::kw_namespace))
245       continue;
246 
247     // Variadic templates
248     if (MI->isVariadic())
249       continue;
250 
251     if (!FoundGoto) {
252       Check->diag(Tok.getLocation(), "macro argument should be enclosed in "
253                                      "parentheses")
254           << FixItHint::CreateInsertion(Tok.getLocation(), "(")
255           << FixItHint::CreateInsertion(Tok.getLocation().getLocWithOffset(
256                                             PP->getSpelling(Tok).length()),
257                                         ")");
258     }
259 
260     FoundGoto = false;
261   }
262 }
263 
registerPPCallbacks(const SourceManager & SM,Preprocessor * PP,Preprocessor * ModuleExpanderPP)264 void MacroParenthesesCheck::registerPPCallbacks(
265     const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) {
266   PP->addPPCallbacks(std::make_unique<MacroParenthesesPPCallbacks>(PP, this));
267 }
268 
269 } // namespace clang::tidy::bugprone
270