1 //===--- MacroParenthesesCheck.cpp - clang-tidy----------------------------===//
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
7 //===----------------------------------------------------------------------===//
9 #include "MacroParenthesesCheck.h"
10 #include "clang/Frontend/CompilerInstance.h"
11 #include "clang/Lex/PPCallbacks.h"
12 #include "clang/Lex/Preprocessor.h"
14 namespace clang::tidy::bugprone
{
17 class MacroParenthesesPPCallbacks
: public PPCallbacks
{
19 MacroParenthesesPPCallbacks(Preprocessor
*PP
, MacroParenthesesCheck
*Check
)
20 : PP(PP
), Check(Check
) {}
22 void MacroDefined(const Token
&MacroNameTok
,
23 const MacroDirective
*MD
) override
{
24 replacementList(MacroNameTok
, MD
->getMacroInfo());
25 argument(MacroNameTok
, MD
->getMacroInfo());
29 /// Replacement list with calculations should be enclosed in parentheses.
30 void replacementList(const Token
&MacroNameTok
, const MacroInfo
*MI
);
32 /// Arguments should be enclosed in parentheses.
33 void argument(const Token
&MacroNameTok
, const MacroInfo
*MI
);
36 MacroParenthesesCheck
*Check
;
40 /// Is argument surrounded properly with parentheses/braces/squares/commas?
41 static bool isSurroundedLeft(const Token
&T
) {
42 return T
.isOneOf(tok::l_paren
, tok::l_brace
, tok::l_square
, tok::comma
,
46 /// Is argument surrounded properly with parentheses/braces/squares/commas?
47 static bool isSurroundedRight(const Token
&T
) {
48 return T
.isOneOf(tok::r_paren
, tok::r_brace
, tok::r_square
, tok::comma
,
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(tok::kw_if
, tok::kw_case
, tok::kw_const
, tok::kw_struct
);
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(tok::plus
, tok::minus
, tok::star
, tok::slash
, tok::percent
,
63 tok::amp
, tok::pipe
, tok::caret
);
66 /// Is given Token a keyword that is used in variable declarations?
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
);
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())
79 // If we see int/short/struct/etc., just assume this is a variable
81 if (isVarDeclKeyword(*Tok
))
84 // Variable declarations start with identifier or coloncolon.
85 if (!Tok
->isOneOf(tok::identifier
, tok::raw_identifier
, tok::coloncolon
))
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
,
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
);
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()))
107 // Count how deep we are in parentheses/braces/squares.
110 // SourceLocation for error
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.
118 // When replacement list contains comma/semi don't warn about it.
119 if (Count
== 0 && Tok
.isOneOf(tok::comma
, tok::semi
))
121 if (Tok
.isOneOf(tok::l_paren
, tok::l_brace
, tok::l_square
)) {
123 } else if (Tok
.isOneOf(tok::r_paren
, tok::r_brace
, tok::r_square
)) {
125 // If there are unbalanced parentheses don't write any warning
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:
132 if (TI
== MI
->tokens_begin() && (TI
+ 1) != TE
&&
133 !Tok
.isOneOf(tok::plus
, tok::minus
))
135 // Don't warn about this macro if the last token is a star. For example:
137 if ((TE
- 1)->is(tok::star
))
140 Loc
= Tok
.getLocation();
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()),
153 void MacroParenthesesPPCallbacks::argument(const Token
&MacroNameTok
,
154 const MacroInfo
*MI
) {
156 // Skip variable declaration.
157 bool VarDecl
= possibleVarDecl(MI
, MI
->tokens_begin());
159 // Skip the goto argument with an arbitrary number of subsequent stars.
160 bool FoundGoto
= false;
162 for (auto TI
= MI
->tokens_begin(), TE
= MI
->tokens_end(); TI
!= TE
; ++TI
) {
164 if (TI
== MI
->tokens_begin())
168 if ((TI
+ 1) == MI
->tokens_end())
171 const Token
&Prev
= *(TI
- 1);
172 const Token
&Next
= *(TI
+ 1);
174 const Token
&Tok
= *TI
;
176 // There should not be extra parentheses in possible variable declaration.
178 if (Tok
.isOneOf(tok::equal
, tok::semi
, tok::l_square
, tok::l_paren
))
183 // There should not be extra parentheses for the goto argument.
184 if (Tok
.is(tok::kw_goto
)) {
189 // Only interested in identifiers.
190 if (!Tok
.isOneOf(tok::identifier
, tok::raw_identifier
)) {
195 // Only interested in macro arguments.
196 if (MI
->getParameterNum(Tok
.getIdentifierInfo()) < 0)
199 // Argument is surrounded with parentheses/squares/braces/commas.
200 if (isSurroundedLeft(Prev
) && isSurroundedRight(Next
))
203 // Don't warn after hash/hashhash or before hashhash.
204 if (Prev
.isOneOf(tok::hash
, tok::hashhash
) || Next
.is(tok::hashhash
))
207 // Argument is a struct member.
208 if (Prev
.isOneOf(tok::period
, tok::arrow
, tok::coloncolon
, tok::arrowstar
,
212 // Argument is a namespace or class.
213 if (Next
.is(tok::coloncolon
))
216 // String concatenation.
217 if (isStringLiteral(Prev
.getKind()) || isStringLiteral(Next
.getKind()))
221 if (isAnyIdentifier(Prev
.getKind()) || isKeyword(Prev
) ||
222 isAnyIdentifier(Next
.getKind()) || isKeyword(Next
))
226 if (Next
.is(tok::l_paren
))
230 if (Prev
.is(tok::l_paren
) && Next
.is(tok::star
) &&
231 TI
+ 2 != MI
->tokens_end() && (TI
+ 2)->is(tok::r_paren
))
234 // Assignment/return, i.e. '=x;' or 'return x;'.
235 if (Prev
.isOneOf(tok::equal
, tok::kw_return
) && Next
.is(tok::semi
))
238 // C++ template parameters.
239 if (PP
->getLangOpts().CPlusPlus
&& Prev
.isOneOf(tok::comma
, tok::less
) &&
240 Next
.isOneOf(tok::comma
, tok::greater
))
244 if (Prev
.is(tok::kw_namespace
))
247 // Variadic templates
248 if (MI
->isVariadic())
252 Check
->diag(Tok
.getLocation(), "macro argument should be enclosed in "
254 << FixItHint::CreateInsertion(Tok
.getLocation(), "(")
255 << FixItHint::CreateInsertion(Tok
.getLocation().getLocWithOffset(
256 PP
->getSpelling(Tok
).length()),
264 void MacroParenthesesCheck::registerPPCallbacks(
265 const SourceManager
&SM
, Preprocessor
*PP
, Preprocessor
*ModuleExpanderPP
) {
266 PP
->addPPCallbacks(std::make_unique
<MacroParenthesesPPCallbacks
>(PP
, this));
269 } // namespace clang::tidy::bugprone