[clang-tidy] Support parenthesized literals in modernize-macro-to-enum

When scanning a macro expansion to examine it as a candidate enum,
first strip off arbitrary matching parentheses from the outside in,
then examine what remains to see if it is Lit, +Lit, -Lit or ~Lit.
If not, reject it as a possible enum candidate.

Differential Revision: https://reviews.llvm.org/D123479

Fixes #54843
This commit is contained in:
Richard 2022-04-10 19:15:22 -06:00
parent cbcb3bcee3
commit d563c2d0e5
3 changed files with 84 additions and 12 deletions

View File

@ -324,18 +324,51 @@ void MacroToEnumCallbacks::MacroDefined(const Token &MacroNameTok,
return;
const MacroInfo *Info = MD->getMacroInfo();
if (Info->isFunctionLike() || Info->isBuiltinMacro() ||
Info->tokens().empty() || Info->tokens().size() > 2)
ArrayRef<Token> MacroTokens = Info->tokens();
if (Info->isFunctionLike() || Info->isBuiltinMacro() || MacroTokens.empty())
return;
// It can be +Lit, -Lit or just Lit.
Token Tok = Info->tokens().front();
if (Info->tokens().size() == 2) {
if (!Tok.isOneOf(tok::TokenKind::minus, tok::TokenKind::plus,
tok::TokenKind::tilde))
// Return Lit when +Lit, -Lit or ~Lit; otherwise return Unknown.
Token Unknown;
Unknown.setKind(tok::TokenKind::unknown);
auto GetUnopArg = [Unknown](Token First, Token Second) {
return First.isOneOf(tok::TokenKind::minus, tok::TokenKind::plus,
tok::TokenKind::tilde)
? Second
: Unknown;
};
// It could just be a single token.
Token Tok = MacroTokens.front();
// It can be any arbitrary nesting of matched parentheses around
// +Lit, -Lit, ~Lit or Lit.
if (MacroTokens.size() > 2) {
// Strip off matching '(', ..., ')' token pairs.
size_t Begin = 0;
size_t End = MacroTokens.size() - 1;
assert(End >= 2U);
for (; Begin < MacroTokens.size() / 2; ++Begin, --End) {
if (!MacroTokens[Begin].is(tok::TokenKind::l_paren) ||
!MacroTokens[End].is(tok::TokenKind::r_paren))
break;
}
size_t Size = End >= Begin ? (End - Begin + 1U) : 0U;
// It was a single token inside matching parens.
if (Size == 1)
Tok = MacroTokens[Begin];
else if (Size == 2)
// It can be +Lit, -Lit or ~Lit.
Tok = GetUnopArg(MacroTokens[Begin], MacroTokens[End]);
else
// Zero or too many tokens after we stripped matching parens.
return;
Tok = Info->tokens().back();
} else if (MacroTokens.size() == 2) {
// It can be +Lit, -Lit, or ~Lit.
Tok = GetUnopArg(MacroTokens.front(), MacroTokens.back());
}
if (!Tok.isLiteral() || isStringLiteral(Tok.getKind()) ||
!isIntegralConstant(Tok))
return;

View File

@ -14,10 +14,12 @@ within the constraints outlined below.
Potential macros for replacement must meet the following constraints:
- Macros must expand only to integral literal tokens. The unary operators
plus, minus and tilde are recognized to allow for positive, negative
and bitwise negated integers. More complicated integral constant
expressions are not currently recognized by this check.
- Macros must expand only to integral literal tokens or simple expressions
of literal tokens. The unary operators plus, minus and tilde are
recognized to allow for positive, negative and bitwise negated integers.
The above expressions may also be surrounded by matching pairs of
parentheses. More complicated integral constant expressions are not
recognized by this check.
- Macros must be defined on sequential source file lines, or with
only comment lines in between macro definitions.
- Macros must all be defined in the same source file.
@ -43,6 +45,7 @@ Examples:
#define GREEN 0x00FF00
#define BLUE 0x0000FF
#define TM_NONE (-1) // No method selected.
#define TM_ONE 1 // Use tailored method one.
#define TM_TWO 2 // Use tailored method two. Method two
// is preferable to method one.
@ -59,6 +62,7 @@ becomes
};
enum {
TM_NONE = (-1), // No method selected.
TM_ONE = 1, // Use tailored method one.
TM_TWO = 2, // Use tailored method two. Method two
// is preferable to method one.

View File

@ -137,6 +137,41 @@
// CHECK-FIXES-NEXT: SUFFIX5 = +1ULL
// CHECK-FIXES-NEXT: };
// A limited form of constant expression is recognized: a parenthesized
// literal or a parenthesized literal with the unary operators +, - or ~.
#define PAREN1 (-1)
#define PAREN2 (1)
#define PAREN3 (+1)
#define PAREN4 (~1)
// CHECK-MESSAGES: :[[@LINE-4]]:1: warning: replace macro with enum
// CHECK-MESSAGES: :[[@LINE-5]]:9: warning: macro 'PAREN1' defines an integral constant; prefer an enum instead
// CHECK-MESSAGES: :[[@LINE-5]]:9: warning: macro 'PAREN2' defines an integral constant; prefer an enum instead
// CHECK-MESSAGES: :[[@LINE-5]]:9: warning: macro 'PAREN3' defines an integral constant; prefer an enum instead
// CHECK-MESSAGES: :[[@LINE-5]]:9: warning: macro 'PAREN4' defines an integral constant; prefer an enum instead
// CHECK-FIXES: enum {
// CHECK-FIXES-NEXT: PAREN1 = (-1),
// CHECK-FIXES-NEXT: PAREN2 = (1),
// CHECK-FIXES-NEXT: PAREN3 = (+1),
// CHECK-FIXES-NEXT: PAREN4 = (~1)
// CHECK-FIXES-NEXT: };
// More complicated parenthesized expressions are excluded.
// Expansions that are not surrounded by parentheses are excluded.
// Nested matching parentheses are stripped.
#define COMPLEX_PAREN1 (x+1)
#define COMPLEX_PAREN2 (x+1
#define COMPLEX_PAREN3 (())
#define COMPLEX_PAREN4 ()
#define COMPLEX_PAREN5 (+1)
#define COMPLEX_PAREN6 ((+1))
// CHECK-MESSAGES: :[[@LINE-2]]:1: warning: replace macro with enum
// CHECK-MESSAGES: :[[@LINE-3]]:9: warning: macro 'COMPLEX_PAREN5' defines an integral constant; prefer an enum instead
// CHECK-MESSAGES: :[[@LINE-3]]:9: warning: macro 'COMPLEX_PAREN6' defines an integral constant; prefer an enum instead
// CHECK-FIXES: enum {
// CHECK-FIXES-NEXT: COMPLEX_PAREN5 = (+1),
// CHECK-FIXES-NEXT: COMPLEX_PAREN6 = ((+1))
// CHECK-FIXES-NEXT: };
// Macros appearing in conditional expressions can't be replaced
// by enums.
#define USE_FOO 1