Files
TheRock/compiler/amd-llvm/clang-tools-extra/clang-tidy/bugprone/MacroParenthesesCheck.cpp
T
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

299 lines
10 KiB
C++

//===----------------------------------------------------------------------===//
//
// 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::tidy::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_volatile,
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);
}
static StringRef getMacroText(const MacroInfo *MI, Preprocessor *PP) {
if (MI->tokens_empty())
return {};
return Lexer::getSourceText(
CharSourceRange::getTokenRange(MI->tokens_begin()->getLocation(),
MI->tokens().back().getLocation()),
PP->getSourceManager(), PP->getLangOpts());
}
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() && std::next(TI) != 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 (std::prev(TE)->is(tok::star))
return;
Loc = Tok.getLocation();
}
}
if (Loc.isValid()) {
const Token &Last = *std::prev(MI->tokens_end());
if (PP->getSourceManager().isWrittenInCommandLineFile(Loc)) {
Check->diag(Loc, "macro replacement list should be enclosed in "
"parentheses; macro '%0' defined as '%1'")
<< PP->getSpelling(MacroNameTok) << getMacroText(MI, PP)
<< FixItHint::CreateInsertion(MI->tokens_begin()->getLocation(), "(")
<< FixItHint::CreateInsertion(Last.getLocation().getLocWithOffset(
PP->getSpelling(Last).length()),
")");
} else {
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 (std::next(TI) == MI->tokens_end())
continue;
const Token &Prev = *std::prev(TI);
const Token &Next = *std::next(TI);
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) &&
std::next(TI, 2) != MI->tokens_end() &&
std::next(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)) {
const auto *NextIt =
std::find_if_not(std::next(TI), MI->tokens_end(), [](const Token &T) {
return T.isOneOf(tok::star, tok::amp, tok::ampamp, tok::kw_const,
tok::kw_volatile);
});
if (NextIt != MI->tokens_end() &&
NextIt->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 clang::tidy::bugprone