brintos

brintos / llvm-project-archived public Read only

0
0
Text · 9.0 KiB · 6467fb5 Raw
269 lines · cpp
1//===----------------------------------------------------------------------===//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-exception6//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 14namespace clang::tidy::bugprone {15 16namespace {17class MacroParenthesesPPCallbacks : public PPCallbacks {18public:19  MacroParenthesesPPCallbacks(Preprocessor *PP, MacroParenthesesCheck *Check)20      : PP(PP), Check(Check) {}21 22  void MacroDefined(const Token &MacroNameTok,23                    const MacroDirective *MD) override {24    replacementList(MacroNameTok, MD->getMacroInfo());25    argument(MacroNameTok, MD->getMacroInfo());26  }27 28private: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} // namespace39 40/// Is argument surrounded properly with parentheses/braces/squares/commas?41static 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?47static 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?53static 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.59static bool isWarnOp(const Token &T) {60  // FIXME: This is an initial list of operators. It can be tweaked later to61  // 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?67static 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?75static 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 variable80  // 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, etc89  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 101void 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 error111  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 warning126      if (Count < 0)127        return;128    } else if (Count == 0 && isWarnOp(Tok)) {129      // Heuristic for macros that are clearly not intended to be enclosed in130      // parentheses, macro starts with operator. For example:131      // #define X     *10132      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 153void MacroParenthesesPPCallbacks::argument(const Token &MacroNameTok,154                                           const MacroInfo *MI) {155  // Skip variable declaration.156  bool VarDecl = possibleVarDecl(MI, MI->tokens_begin());157 158  // Skip the goto argument with an arbitrary number of subsequent stars.159  bool FoundGoto = false;160 161  for (auto TI = MI->tokens_begin(), TE = MI->tokens_end(); TI != TE; ++TI) {162    // First token.163    if (TI == MI->tokens_begin())164      continue;165 166    // Last token.167    if ((TI + 1) == MI->tokens_end())168      continue;169 170    const Token &Prev = *(TI - 1);171    const Token &Next = *(TI + 1);172 173    const Token &Tok = *TI;174 175    // There should not be extra parentheses in possible variable declaration.176    if (VarDecl) {177      if (Tok.isOneOf(tok::equal, tok::semi, tok::l_square, tok::l_paren))178        VarDecl = false;179      continue;180    }181 182    // There should not be extra parentheses for the goto argument.183    if (Tok.is(tok::kw_goto)) {184      FoundGoto = true;185      continue;186    }187 188    // Only interested in identifiers.189    if (!Tok.isOneOf(tok::identifier, tok::raw_identifier)) {190      FoundGoto = false;191      continue;192    }193 194    // Only interested in macro arguments.195    if (MI->getParameterNum(Tok.getIdentifierInfo()) < 0)196      continue;197 198    // Argument is surrounded with parentheses/squares/braces/commas.199    if (isSurroundedLeft(Prev) && isSurroundedRight(Next))200      continue;201 202    // Don't warn after hash/hashhash or before hashhash.203    if (Prev.isOneOf(tok::hash, tok::hashhash) || Next.is(tok::hashhash))204      continue;205 206    // Argument is a struct member.207    if (Prev.isOneOf(tok::period, tok::arrow, tok::coloncolon, tok::arrowstar,208                     tok::periodstar))209      continue;210 211    // Argument is a namespace or class.212    if (Next.is(tok::coloncolon))213      continue;214 215    // String concatenation.216    if (isStringLiteral(Prev.getKind()) || isStringLiteral(Next.getKind()))217      continue;218 219    // Type/Var.220    if (isAnyIdentifier(Prev.getKind()) || isKeyword(Prev) ||221        isAnyIdentifier(Next.getKind()) || isKeyword(Next))222      continue;223 224    // Initialization.225    if (Next.is(tok::l_paren))226      continue;227 228    // Cast.229    if (Prev.is(tok::l_paren) && Next.is(tok::star) &&230        TI + 2 != MI->tokens_end() && (TI + 2)->is(tok::r_paren))231      continue;232 233    // Assignment/return, i.e. '=x;' or 'return x;'.234    if (Prev.isOneOf(tok::equal, tok::kw_return) && Next.is(tok::semi))235      continue;236 237    // C++ template parameters.238    if (PP->getLangOpts().CPlusPlus && Prev.isOneOf(tok::comma, tok::less) &&239        Next.isOneOf(tok::comma, tok::greater))240      continue;241 242    // Namespaces.243    if (Prev.is(tok::kw_namespace))244      continue;245 246    // Variadic templates247    if (MI->isVariadic())248      continue;249 250    if (!FoundGoto) {251      Check->diag(Tok.getLocation(), "macro argument should be enclosed in "252                                     "parentheses")253          << FixItHint::CreateInsertion(Tok.getLocation(), "(")254          << FixItHint::CreateInsertion(Tok.getLocation().getLocWithOffset(255                                            PP->getSpelling(Tok).length()),256                                        ")");257    }258 259    FoundGoto = false;260  }261}262 263void MacroParenthesesCheck::registerPPCallbacks(264    const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) {265  PP->addPPCallbacks(std::make_unique<MacroParenthesesPPCallbacks>(PP, this));266}267 268} // namespace clang::tidy::bugprone269