274 lines
		
	
	
		
			9.0 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			274 lines
		
	
	
		
			9.0 KiB
		
	
	
	
		
			C++
		
	
	
	
//===--- MacroParenthesesCheck.cpp - clang-tidy----------------------------===//
 | 
						|
//
 | 
						|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
 | 
						|
// See https://llvm.org/LICENSE.txt for license information.
 | 
						|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 | 
						|
//
 | 
						|
//===----------------------------------------------------------------------===//
 | 
						|
 | 
						|
#include "MacroParenthesesCheck.h"
 | 
						|
#include "clang/Frontend/CompilerInstance.h"
 | 
						|
#include "clang/Lex/PPCallbacks.h"
 | 
						|
#include "clang/Lex/Preprocessor.h"
 | 
						|
 | 
						|
namespace clang {
 | 
						|
namespace tidy {
 | 
						|
namespace bugprone {
 | 
						|
 | 
						|
namespace {
 | 
						|
class MacroParenthesesPPCallbacks : public PPCallbacks {
 | 
						|
public:
 | 
						|
  MacroParenthesesPPCallbacks(Preprocessor *PP, MacroParenthesesCheck *Check)
 | 
						|
      : PP(PP), Check(Check) {}
 | 
						|
 | 
						|
  void MacroDefined(const Token &MacroNameTok,
 | 
						|
                    const MacroDirective *MD) override {
 | 
						|
    replacementList(MacroNameTok, MD->getMacroInfo());
 | 
						|
    argument(MacroNameTok, MD->getMacroInfo());
 | 
						|
  }
 | 
						|
 | 
						|
private:
 | 
						|
  /// Replacement list with calculations should be enclosed in parentheses.
 | 
						|
  void replacementList(const Token &MacroNameTok, const MacroInfo *MI);
 | 
						|
 | 
						|
  /// Arguments should be enclosed in parentheses.
 | 
						|
  void argument(const Token &MacroNameTok, const MacroInfo *MI);
 | 
						|
 | 
						|
  Preprocessor *PP;
 | 
						|
  MacroParenthesesCheck *Check;
 | 
						|
};
 | 
						|
} // namespace
 | 
						|
 | 
						|
/// Is argument surrounded properly with parentheses/braces/squares/commas?
 | 
						|
static bool isSurroundedLeft(const Token &T) {
 | 
						|
  return T.isOneOf(tok::l_paren, tok::l_brace, tok::l_square, tok::comma,
 | 
						|
                   tok::semi);
 | 
						|
}
 | 
						|
 | 
						|
/// Is argument surrounded properly with parentheses/braces/squares/commas?
 | 
						|
static bool isSurroundedRight(const Token &T) {
 | 
						|
  return T.isOneOf(tok::r_paren, tok::r_brace, tok::r_square, tok::comma,
 | 
						|
                   tok::semi);
 | 
						|
}
 | 
						|
 | 
						|
/// Is given TokenKind a keyword?
 | 
						|
static bool isKeyword(const Token &T) {
 | 
						|
  // FIXME: better matching of keywords to avoid false positives.
 | 
						|
  return T.isOneOf(tok::kw_if, tok::kw_case, tok::kw_const, tok::kw_struct);
 | 
						|
}
 | 
						|
 | 
						|
/// Warning is written when one of these operators are not within parentheses.
 | 
						|
static bool isWarnOp(const Token &T) {
 | 
						|
  // FIXME: This is an initial list of operators. It can be tweaked later to
 | 
						|
  // get more positives or perhaps avoid some false positive.
 | 
						|
  return T.isOneOf(tok::plus, tok::minus, tok::star, tok::slash, tok::percent,
 | 
						|
                   tok::amp, tok::pipe, tok::caret);
 | 
						|
}
 | 
						|
 | 
						|
/// Is given Token a keyword that is used in variable declarations?
 | 
						|
static bool isVarDeclKeyword(const Token &T) {
 | 
						|
  return T.isOneOf(tok::kw_bool, tok::kw_char, tok::kw_short, tok::kw_int,
 | 
						|
                   tok::kw_long, tok::kw_float, tok::kw_double, tok::kw_const,
 | 
						|
                   tok::kw_enum, tok::kw_inline, tok::kw_static, tok::kw_struct,
 | 
						|
                   tok::kw_signed, tok::kw_unsigned);
 | 
						|
}
 | 
						|
 | 
						|
/// Is there a possible variable declaration at Tok?
 | 
						|
static bool possibleVarDecl(const MacroInfo *MI, const Token *Tok) {
 | 
						|
  if (Tok == MI->tokens_end())
 | 
						|
    return false;
 | 
						|
 | 
						|
  // If we see int/short/struct/etc., just assume this is a variable
 | 
						|
  // declaration.
 | 
						|
  if (isVarDeclKeyword(*Tok))
 | 
						|
    return true;
 | 
						|
 | 
						|
  // Variable declarations start with identifier or coloncolon.
 | 
						|
  if (!Tok->isOneOf(tok::identifier, tok::raw_identifier, tok::coloncolon))
 | 
						|
    return false;
 | 
						|
 | 
						|
  // Skip possible types, etc
 | 
						|
  while (Tok != MI->tokens_end() &&
 | 
						|
         Tok->isOneOf(tok::identifier, tok::raw_identifier, tok::coloncolon,
 | 
						|
                      tok::star, tok::amp, tok::ampamp, tok::less,
 | 
						|
                      tok::greater))
 | 
						|
    Tok++;
 | 
						|
 | 
						|
  // Return true for possible variable declarations.
 | 
						|
  return Tok == MI->tokens_end() ||
 | 
						|
         Tok->isOneOf(tok::equal, tok::semi, tok::l_square, tok::l_paren) ||
 | 
						|
         isVarDeclKeyword(*Tok);
 | 
						|
}
 | 
						|
 | 
						|
void MacroParenthesesPPCallbacks::replacementList(const Token &MacroNameTok,
 | 
						|
                                                  const MacroInfo *MI) {
 | 
						|
  // Make sure macro replacement isn't a variable declaration.
 | 
						|
  if (possibleVarDecl(MI, MI->tokens_begin()))
 | 
						|
    return;
 | 
						|
 | 
						|
  // Count how deep we are in parentheses/braces/squares.
 | 
						|
  int Count = 0;
 | 
						|
 | 
						|
  // SourceLocation for error
 | 
						|
  SourceLocation Loc;
 | 
						|
 | 
						|
  for (auto TI = MI->tokens_begin(), TE = MI->tokens_end(); TI != TE; ++TI) {
 | 
						|
    const Token &Tok = *TI;
 | 
						|
    // Replacement list contains keywords, don't warn about it.
 | 
						|
    if (isKeyword(Tok))
 | 
						|
      return;
 | 
						|
    // When replacement list contains comma/semi don't warn about it.
 | 
						|
    if (Count == 0 && Tok.isOneOf(tok::comma, tok::semi))
 | 
						|
      return;
 | 
						|
    if (Tok.isOneOf(tok::l_paren, tok::l_brace, tok::l_square)) {
 | 
						|
      ++Count;
 | 
						|
    } else if (Tok.isOneOf(tok::r_paren, tok::r_brace, tok::r_square)) {
 | 
						|
      --Count;
 | 
						|
      // If there are unbalanced parentheses don't write any warning
 | 
						|
      if (Count < 0)
 | 
						|
        return;
 | 
						|
    } else if (Count == 0 && isWarnOp(Tok)) {
 | 
						|
      // Heuristic for macros that are clearly not intended to be enclosed in
 | 
						|
      // parentheses, macro starts with operator. For example:
 | 
						|
      // #define X     *10
 | 
						|
      if (TI == MI->tokens_begin() && (TI + 1) != TE &&
 | 
						|
          !Tok.isOneOf(tok::plus, tok::minus))
 | 
						|
        return;
 | 
						|
      // Don't warn about this macro if the last token is a star. For example:
 | 
						|
      // #define X    void *
 | 
						|
      if ((TE - 1)->is(tok::star))
 | 
						|
        return;
 | 
						|
 | 
						|
      Loc = Tok.getLocation();
 | 
						|
    }
 | 
						|
  }
 | 
						|
  if (Loc.isValid()) {
 | 
						|
    const Token &Last = *(MI->tokens_end() - 1);
 | 
						|
    Check->diag(Loc, "macro replacement list should be enclosed in parentheses")
 | 
						|
        << FixItHint::CreateInsertion(MI->tokens_begin()->getLocation(), "(")
 | 
						|
        << FixItHint::CreateInsertion(Last.getLocation().getLocWithOffset(
 | 
						|
                                          PP->getSpelling(Last).length()),
 | 
						|
                                      ")");
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
void MacroParenthesesPPCallbacks::argument(const Token &MacroNameTok,
 | 
						|
                                           const MacroInfo *MI) {
 | 
						|
 | 
						|
  // Skip variable declaration.
 | 
						|
  bool VarDecl = possibleVarDecl(MI, MI->tokens_begin());
 | 
						|
 | 
						|
  // Skip the goto argument with an arbitrary number of subsequent stars.
 | 
						|
  bool FoundGoto = false;
 | 
						|
 | 
						|
  for (auto TI = MI->tokens_begin(), TE = MI->tokens_end(); TI != TE; ++TI) {
 | 
						|
    // First token.
 | 
						|
    if (TI == MI->tokens_begin())
 | 
						|
      continue;
 | 
						|
 | 
						|
    // Last token.
 | 
						|
    if ((TI + 1) == MI->tokens_end())
 | 
						|
      continue;
 | 
						|
 | 
						|
    const Token &Prev = *(TI - 1);
 | 
						|
    const Token &Next = *(TI + 1);
 | 
						|
 | 
						|
    const Token &Tok = *TI;
 | 
						|
 | 
						|
    // There should not be extra parentheses in possible variable declaration.
 | 
						|
    if (VarDecl) {
 | 
						|
      if (Tok.isOneOf(tok::equal, tok::semi, tok::l_square, tok::l_paren))
 | 
						|
        VarDecl = false;
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
    // There should not be extra parentheses for the goto argument.
 | 
						|
    if (Tok.is(tok::kw_goto)) {
 | 
						|
      FoundGoto = true;
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
    // Only interested in identifiers.
 | 
						|
    if (!Tok.isOneOf(tok::identifier, tok::raw_identifier)) {
 | 
						|
      FoundGoto = false;
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
    // Only interested in macro arguments.
 | 
						|
    if (MI->getParameterNum(Tok.getIdentifierInfo()) < 0)
 | 
						|
      continue;
 | 
						|
 | 
						|
    // Argument is surrounded with parentheses/squares/braces/commas.
 | 
						|
    if (isSurroundedLeft(Prev) && isSurroundedRight(Next))
 | 
						|
      continue;
 | 
						|
 | 
						|
    // Don't warn after hash/hashhash or before hashhash.
 | 
						|
    if (Prev.isOneOf(tok::hash, tok::hashhash) || Next.is(tok::hashhash))
 | 
						|
      continue;
 | 
						|
 | 
						|
    // Argument is a struct member.
 | 
						|
    if (Prev.isOneOf(tok::period, tok::arrow, tok::coloncolon, tok::arrowstar,
 | 
						|
                     tok::periodstar))
 | 
						|
      continue;
 | 
						|
 | 
						|
    // Argument is a namespace or class.
 | 
						|
    if (Next.is(tok::coloncolon))
 | 
						|
      continue;
 | 
						|
 | 
						|
    // String concatenation.
 | 
						|
    if (isStringLiteral(Prev.getKind()) || isStringLiteral(Next.getKind()))
 | 
						|
      continue;
 | 
						|
 | 
						|
    // Type/Var.
 | 
						|
    if (isAnyIdentifier(Prev.getKind()) || isKeyword(Prev) ||
 | 
						|
        isAnyIdentifier(Next.getKind()) || isKeyword(Next))
 | 
						|
      continue;
 | 
						|
 | 
						|
    // Initialization.
 | 
						|
    if (Next.is(tok::l_paren))
 | 
						|
      continue;
 | 
						|
 | 
						|
    // Cast.
 | 
						|
    if (Prev.is(tok::l_paren) && Next.is(tok::star) &&
 | 
						|
        TI + 2 != MI->tokens_end() && (TI + 2)->is(tok::r_paren))
 | 
						|
      continue;
 | 
						|
 | 
						|
    // Assignment/return, i.e. '=x;' or 'return x;'.
 | 
						|
    if (Prev.isOneOf(tok::equal, tok::kw_return) && Next.is(tok::semi))
 | 
						|
      continue;
 | 
						|
 | 
						|
    // C++ template parameters.
 | 
						|
    if (PP->getLangOpts().CPlusPlus && Prev.isOneOf(tok::comma, tok::less) &&
 | 
						|
        Next.isOneOf(tok::comma, tok::greater))
 | 
						|
      continue;
 | 
						|
 | 
						|
    // Namespaces.
 | 
						|
    if (Prev.is(tok::kw_namespace))
 | 
						|
      continue;
 | 
						|
 | 
						|
    // Variadic templates
 | 
						|
    if (MI->isVariadic())
 | 
						|
      continue;
 | 
						|
 | 
						|
    if (!FoundGoto) {
 | 
						|
      Check->diag(Tok.getLocation(), "macro argument should be enclosed in "
 | 
						|
                                     "parentheses")
 | 
						|
          << FixItHint::CreateInsertion(Tok.getLocation(), "(")
 | 
						|
          << FixItHint::CreateInsertion(Tok.getLocation().getLocWithOffset(
 | 
						|
                                            PP->getSpelling(Tok).length()),
 | 
						|
                                        ")");
 | 
						|
    }
 | 
						|
 | 
						|
    FoundGoto = false;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
void MacroParenthesesCheck::registerPPCallbacks(
 | 
						|
    const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) {
 | 
						|
  PP->addPPCallbacks(std::make_unique<MacroParenthesesPPCallbacks>(PP, this));
 | 
						|
}
 | 
						|
 | 
						|
} // namespace bugprone
 | 
						|
} // namespace tidy
 | 
						|
} // namespace clang
 |