Daniel Marjamaki | 302275a | 2015-06-16 14:27:31 +0000 | [diff] [blame] | 1 | //===--- 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 | |
| 15 | namespace clang { |
| 16 | namespace tidy { |
| 17 | |
| 18 | namespace { |
| 19 | class MacroParenthesesPPCallbacks : public PPCallbacks { |
| 20 | public: |
| 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 | |
| 31 | private: |
| 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 Marjamaki | bf5e59c | 2015-07-01 13:29:27 +0000 | [diff] [blame] | 41 | } // namespace |
Daniel Marjamaki | 302275a | 2015-06-16 14:27:31 +0000 | [diff] [blame] | 42 | |
| 43 | /// Is argument surrounded properly with parentheses/braces/squares/commas? |
Daniel Marjamaki | bf5e59c | 2015-07-01 13:29:27 +0000 | [diff] [blame] | 44 | static bool isSurroundedLeft(const Token &T) { |
Daniel Marjamaki | 302275a | 2015-06-16 14:27:31 +0000 | [diff] [blame] | 45 | 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 Marjamaki | bf5e59c | 2015-07-01 13:29:27 +0000 | [diff] [blame] | 50 | static bool isSurroundedRight(const Token &T) { |
Daniel Marjamaki | 302275a | 2015-06-16 14:27:31 +0000 | [diff] [blame] | 51 | 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 Marjamaki | bf5e59c | 2015-07-01 13:29:27 +0000 | [diff] [blame] | 56 | static bool isKeyword(const Token &T) { |
Daniel Marjamaki | 302275a | 2015-06-16 14:27:31 +0000 | [diff] [blame] | 57 | /// \TODO better matching of keywords to avoid false positives |
| 58 | 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 Marjamaki | bf5e59c | 2015-07-01 13:29:27 +0000 | [diff] [blame] | 62 | static bool isWarnOp(const Token &T) { |
Daniel Marjamaki | 4a38b0b | 2015-06-29 12:18:11 +0000 | [diff] [blame] | 63 | /// \TODO This is an initial list of operators. It can be tweaked later to |
| 64 | /// get more positives or perhaps avoid some false positive. |
Daniel Marjamaki | 302275a | 2015-06-16 14:27:31 +0000 | [diff] [blame] | 65 | return T.isOneOf(tok::plus, tok::minus, tok::star, tok::slash, tok::percent, |
| 66 | tok::amp, tok::pipe, tok::caret); |
| 67 | } |
Daniel Marjamaki | 302275a | 2015-06-16 14:27:31 +0000 | [diff] [blame] | 68 | |
| 69 | void 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 | |
| 117 | void 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 Marjamaki | 91d35a5 | 2015-06-23 12:45:14 +0000 | [diff] [blame] | 151 | if (Prev.isOneOf(tok::period, tok::arrow, tok::coloncolon)) |
Daniel Marjamaki | 302275a | 2015-06-16 14:27:31 +0000 | [diff] [blame] | 152 | continue; |
| 153 | |
Daniel Marjamaki | 4a38b0b | 2015-06-29 12:18:11 +0000 | [diff] [blame] | 154 | // Argument is a namespace or class. |
| 155 | if (Next.is(tok::coloncolon)) |
| 156 | continue; |
| 157 | |
Daniel Marjamaki | 302275a | 2015-06-16 14:27:31 +0000 | [diff] [blame] | 158 | // String concatenation. |
| 159 | if (isStringLiteral(Prev.getKind()) || isStringLiteral(Next.getKind())) |
| 160 | continue; |
| 161 | |
| 162 | // Type/Var. |
| 163 | if (isAnyIdentifier(Prev.getKind()) || isKeyword(Prev) || |
| 164 | isAnyIdentifier(Next.getKind()) || isKeyword(Next)) |
| 165 | continue; |
| 166 | |
| 167 | // Initialization. |
| 168 | if (Next.is(tok::l_paren)) |
| 169 | continue; |
| 170 | |
| 171 | // Cast. |
| 172 | if (Prev.is(tok::l_paren) && Next.is(tok::star) && |
| 173 | TI + 2 != MI->tokens_end() && (TI + 2)->is(tok::r_paren)) |
| 174 | continue; |
| 175 | |
Daniel Marjamaki | 91d35a5 | 2015-06-23 12:45:14 +0000 | [diff] [blame] | 176 | // Assignment/return, i.e. '=x;' or 'return x;'. |
| 177 | if (Prev.isOneOf(tok::equal, tok::kw_return) && Next.is(tok::semi)) |
Daniel Marjamaki | 302275a | 2015-06-16 14:27:31 +0000 | [diff] [blame] | 178 | continue; |
| 179 | |
Daniel Marjamaki | 4a38b0b | 2015-06-29 12:18:11 +0000 | [diff] [blame] | 180 | // C++ template parameters. |
| 181 | if (PP->getLangOpts().CPlusPlus && Prev.isOneOf(tok::comma, tok::less) && |
| 182 | Next.isOneOf(tok::comma, tok::greater)) |
| 183 | continue; |
| 184 | |
Daniel Marjamaki | 302275a | 2015-06-16 14:27:31 +0000 | [diff] [blame] | 185 | Check->diag(Tok.getLocation(), "macro argument should be enclosed in " |
| 186 | "parentheses") |
| 187 | << FixItHint::CreateInsertion(Tok.getLocation(), "(") |
| 188 | << FixItHint::CreateInsertion(Tok.getLocation().getLocWithOffset( |
| 189 | PP->getSpelling(Tok).length()), |
| 190 | ")"); |
| 191 | } |
| 192 | } |
| 193 | |
| 194 | void MacroParenthesesCheck::registerPPCallbacks(CompilerInstance &Compiler) { |
| 195 | Compiler.getPreprocessor().addPPCallbacks( |
| 196 | llvm::make_unique<MacroParenthesesPPCallbacks>( |
| 197 | &Compiler.getPreprocessor(), this)); |
| 198 | } |
| 199 | |
| 200 | } // namespace tidy |
| 201 | } // namespace clang |