blob: e1955dec482674c91e90bea0b58fb30bc301e206 [file] [log] [blame]
Daniel Marjamaki302275a2015-06-16 14:27:31 +00001//===--- MacroParenthesesCheck.cpp - clang-tidy----------------------------===//
2//
3// The LLVM Compiler Infrastructure
4//
5// This file is distributed under the University of Illinois Open Source
6// License. See LICENSE.TXT for details.
7//
8//===----------------------------------------------------------------------===//
9
10#include "MacroParenthesesCheck.h"
11#include "clang/Frontend/CompilerInstance.h"
12#include "clang/Lex/PPCallbacks.h"
13#include "clang/Lex/Preprocessor.h"
14
15namespace clang {
16namespace tidy {
17
18namespace {
19class MacroParenthesesPPCallbacks : public PPCallbacks {
20public:
21 explicit MacroParenthesesPPCallbacks(Preprocessor *PP,
22 MacroParenthesesCheck *Check)
23 : PP(PP), Check(Check) {}
24
25 void MacroDefined(const Token &MacroNameTok,
26 const MacroDirective *MD) override {
27 replacementList(MacroNameTok, MD->getMacroInfo());
28 argument(MacroNameTok, MD->getMacroInfo());
29 }
30
31private:
32 /// Replacement list with calculations should be enclosed in parentheses.
33 void replacementList(const Token &MacroNameTok, const MacroInfo *MI);
34
35 /// Arguments should be enclosed in parentheses.
36 void argument(const Token &MacroNameTok, const MacroInfo *MI);
37
38 Preprocessor *PP;
39 MacroParenthesesCheck *Check;
40};
Daniel Marjamakibf5e59c2015-07-01 13:29:27 +000041} // namespace
Daniel Marjamaki302275a2015-06-16 14:27:31 +000042
43/// Is argument surrounded properly with parentheses/braces/squares/commas?
Daniel Marjamakibf5e59c2015-07-01 13:29:27 +000044static bool isSurroundedLeft(const Token &T) {
Daniel Marjamaki302275a2015-06-16 14:27:31 +000045 return T.isOneOf(tok::l_paren, tok::l_brace, tok::l_square, tok::comma,
46 tok::semi);
47}
48
49/// Is argument surrounded properly with parentheses/braces/squares/commas?
Daniel Marjamakibf5e59c2015-07-01 13:29:27 +000050static bool isSurroundedRight(const Token &T) {
Daniel Marjamaki302275a2015-06-16 14:27:31 +000051 return T.isOneOf(tok::r_paren, tok::r_brace, tok::r_square, tok::comma,
52 tok::semi);
53}
54
55/// Is given TokenKind a keyword?
Daniel Marjamakibf5e59c2015-07-01 13:29:27 +000056static bool isKeyword(const Token &T) {
Alexander Kornienkof8ed0a82015-08-27 18:01:58 +000057 // FIXME: better matching of keywords to avoid false positives.
Daniel Marjamaki302275a2015-06-16 14:27:31 +000058 return T.isOneOf(tok::kw_case, tok::kw_const, tok::kw_struct);
59}
60
61/// Warning is written when one of these operators are not within parentheses.
Daniel Marjamakibf5e59c2015-07-01 13:29:27 +000062static bool isWarnOp(const Token &T) {
Alexander Kornienkof8ed0a82015-08-27 18:01:58 +000063 // FIXME: This is an initial list of operators. It can be tweaked later to
64 // get more positives or perhaps avoid some false positive.
Daniel Marjamaki302275a2015-06-16 14:27:31 +000065 return T.isOneOf(tok::plus, tok::minus, tok::star, tok::slash, tok::percent,
66 tok::amp, tok::pipe, tok::caret);
67}
Daniel Marjamaki302275a2015-06-16 14:27:31 +000068
69void MacroParenthesesPPCallbacks::replacementList(const Token &MacroNameTok,
70 const MacroInfo *MI) {
71 // Count how deep we are in parentheses/braces/squares.
72 int Count = 0;
73
74 // SourceLocation for error
75 SourceLocation Loc;
76
77 for (auto TI = MI->tokens_begin(), TE = MI->tokens_end(); TI != TE; ++TI) {
78 const Token &Tok = *TI;
79 // Replacement list contains keywords, don't warn about it.
80 if (isKeyword(Tok))
81 return;
82 // When replacement list contains comma/semi don't warn about it.
83 if (Count == 0 && Tok.isOneOf(tok::comma, tok::semi))
84 return;
85 if (Tok.isOneOf(tok::l_paren, tok::l_brace, tok::l_square)) {
86 ++Count;
87 } else if (Tok.isOneOf(tok::r_paren, tok::r_brace, tok::r_square)) {
88 --Count;
89 // If there are unbalanced parentheses don't write any warning
90 if (Count < 0)
91 return;
92 } else if (Count == 0 && isWarnOp(Tok)) {
93 // Heuristic for macros that are clearly not intended to be enclosed in
94 // parentheses, macro starts with operator. For example:
95 // #define X *10
96 if (TI == MI->tokens_begin() && (TI + 1) != TE &&
97 !Tok.isOneOf(tok::plus, tok::minus))
98 return;
99 // Don't warn about this macro if the last token is a star. For example:
100 // #define X void *
101 if ((TE - 1)->is(tok::star))
102 return;
103
104 Loc = Tok.getLocation();
105 }
106 }
107 if (Loc.isValid()) {
108 const Token &Last = *(MI->tokens_end() - 1);
109 Check->diag(Loc, "macro replacement list should be enclosed in parentheses")
110 << FixItHint::CreateInsertion(MI->tokens_begin()->getLocation(), "(")
111 << FixItHint::CreateInsertion(Last.getLocation().getLocWithOffset(
112 PP->getSpelling(Last).length()),
113 ")");
114 }
115}
116
117void MacroParenthesesPPCallbacks::argument(const Token &MacroNameTok,
118 const MacroInfo *MI) {
119
120 for (auto TI = MI->tokens_begin(), TE = MI->tokens_end(); TI != TE; ++TI) {
121 // First token.
122 if (TI == MI->tokens_begin())
123 continue;
124
125 // Last token.
126 if ((TI + 1) == MI->tokens_end())
127 continue;
128
129 const Token &Prev = *(TI - 1);
130 const Token &Next = *(TI + 1);
131
132 const Token &Tok = *TI;
133
134 // Only interested in identifiers.
135 if (!Tok.isOneOf(tok::identifier, tok::raw_identifier))
136 continue;
137
138 // Only interested in macro arguments.
139 if (MI->getArgumentNum(Tok.getIdentifierInfo()) < 0)
140 continue;
141
142 // Argument is surrounded with parentheses/squares/braces/commas.
143 if (isSurroundedLeft(Prev) && isSurroundedRight(Next))
144 continue;
145
146 // Don't warn after hash/hashhash or before hashhash.
147 if (Prev.isOneOf(tok::hash, tok::hashhash) || Next.is(tok::hashhash))
148 continue;
149
150 // Argument is a struct member.
Daniel Marjamaki1e9ef812015-11-10 14:32:25 +0000151 if (Prev.isOneOf(tok::period, tok::arrow, tok::coloncolon, tok::arrowstar,
152 tok::periodstar))
Daniel Marjamaki302275a2015-06-16 14:27:31 +0000153 continue;
154
Daniel Marjamaki4a38b0b2015-06-29 12:18:11 +0000155 // Argument is a namespace or class.
156 if (Next.is(tok::coloncolon))
157 continue;
158
Daniel Marjamaki302275a2015-06-16 14:27:31 +0000159 // String concatenation.
160 if (isStringLiteral(Prev.getKind()) || isStringLiteral(Next.getKind()))
161 continue;
162
163 // Type/Var.
164 if (isAnyIdentifier(Prev.getKind()) || isKeyword(Prev) ||
165 isAnyIdentifier(Next.getKind()) || isKeyword(Next))
166 continue;
167
168 // Initialization.
169 if (Next.is(tok::l_paren))
170 continue;
171
172 // Cast.
173 if (Prev.is(tok::l_paren) && Next.is(tok::star) &&
174 TI + 2 != MI->tokens_end() && (TI + 2)->is(tok::r_paren))
175 continue;
176
Daniel Marjamaki91d35a52015-06-23 12:45:14 +0000177 // Assignment/return, i.e. '=x;' or 'return x;'.
178 if (Prev.isOneOf(tok::equal, tok::kw_return) && Next.is(tok::semi))
Daniel Marjamaki302275a2015-06-16 14:27:31 +0000179 continue;
180
Daniel Marjamaki4a38b0b2015-06-29 12:18:11 +0000181 // C++ template parameters.
182 if (PP->getLangOpts().CPlusPlus && Prev.isOneOf(tok::comma, tok::less) &&
183 Next.isOneOf(tok::comma, tok::greater))
184 continue;
185
Daniel Marjamaki302275a2015-06-16 14:27:31 +0000186 Check->diag(Tok.getLocation(), "macro argument should be enclosed in "
187 "parentheses")
188 << FixItHint::CreateInsertion(Tok.getLocation(), "(")
189 << FixItHint::CreateInsertion(Tok.getLocation().getLocWithOffset(
190 PP->getSpelling(Tok).length()),
191 ")");
192 }
193}
194
195void MacroParenthesesCheck::registerPPCallbacks(CompilerInstance &Compiler) {
196 Compiler.getPreprocessor().addPPCallbacks(
197 llvm::make_unique<MacroParenthesesPPCallbacks>(
198 &Compiler.getPreprocessor(), this));
199}
200
201} // namespace tidy
202} // namespace clang