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