1 //===--- SuspiciousEnumUsageCheck.cpp - clang-tidy-------------------------===//
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-exception
7 //===----------------------------------------------------------------------===//
9 #include "SuspiciousEnumUsageCheck.h"
10 #include "clang/AST/ASTContext.h"
11 #include "clang/ASTMatchers/ASTMatchFinder.h"
14 using namespace clang::ast_matchers
;
16 namespace clang::tidy::bugprone
{
18 static const char DifferentEnumErrorMessage
[] =
19 "enum values are from different enum types";
21 static const char BitmaskErrorMessage
[] =
22 "enum type seems like a bitmask (contains mostly "
23 "power-of-2 literals), but this literal is not a "
26 static const char BitmaskVarErrorMessage
[] =
27 "enum type seems like a bitmask (contains mostly "
28 "power-of-2 literals) but %plural{1:a literal is|:some literals are}0 not "
31 static const char BitmaskNoteMessage
[] = "used here as a bitmask";
33 /// Stores a min and a max value which describe an interval.
38 ValueRange(const EnumDecl
*EnumDec
) {
39 const auto MinMaxVal
= std::minmax_element(
40 EnumDec
->enumerator_begin(), EnumDec
->enumerator_end(),
41 [](const EnumConstantDecl
*E1
, const EnumConstantDecl
*E2
) {
42 return llvm::APSInt::compareValues(E1
->getInitVal(),
43 E2
->getInitVal()) < 0;
45 MinVal
= MinMaxVal
.first
->getInitVal();
46 MaxVal
= MinMaxVal
.second
->getInitVal();
50 /// Return the number of EnumConstantDecls in an EnumDecl.
51 static int enumLength(const EnumDecl
*EnumDec
) {
52 return std::distance(EnumDec
->enumerator_begin(), EnumDec
->enumerator_end());
55 static bool hasDisjointValueRange(const EnumDecl
*Enum1
,
56 const EnumDecl
*Enum2
) {
57 ValueRange
Range1(Enum1
), Range2(Enum2
);
58 return llvm::APSInt::compareValues(Range1
.MaxVal
, Range2
.MinVal
) < 0 ||
59 llvm::APSInt::compareValues(Range2
.MaxVal
, Range1
.MinVal
) < 0;
62 static bool isNonPowerOf2NorNullLiteral(const EnumConstantDecl
*EnumConst
) {
63 const llvm::APSInt
&Val
= EnumConst
->getInitVal();
64 if (Val
.isPowerOf2() || !Val
.getBoolValue())
66 const Expr
*InitExpr
= EnumConst
->getInitExpr();
69 return isa
<IntegerLiteral
>(InitExpr
->IgnoreImpCasts());
72 static bool isMaxValAllBitSetLiteral(const EnumDecl
*EnumDec
) {
73 auto EnumConst
= std::max_element(
74 EnumDec
->enumerator_begin(), EnumDec
->enumerator_end(),
75 [](const EnumConstantDecl
*E1
, const EnumConstantDecl
*E2
) {
76 return E1
->getInitVal() < E2
->getInitVal();
79 if (const Expr
*InitExpr
= EnumConst
->getInitExpr()) {
80 return EnumConst
->getInitVal().countr_one() ==
81 EnumConst
->getInitVal().getActiveBits() &&
82 isa
<IntegerLiteral
>(InitExpr
->IgnoreImpCasts());
87 static int countNonPowOfTwoLiteralNum(const EnumDecl
*EnumDec
) {
88 return llvm::count_if(EnumDec
->enumerators(), isNonPowerOf2NorNullLiteral
);
91 /// Check if there is one or two enumerators that are not a power of 2 and are
92 /// initialized by a literal in the enum type, and that the enumeration contains
93 /// enough elements to reasonably act as a bitmask. Exclude the case where the
94 /// last enumerator is the sum of the lesser values (and initialized by a
95 /// literal) or when it could contain consecutive values.
96 static bool isPossiblyBitMask(const EnumDecl
*EnumDec
) {
97 ValueRange
VR(EnumDec
);
98 int EnumLen
= enumLength(EnumDec
);
99 int NonPowOfTwoCounter
= countNonPowOfTwoLiteralNum(EnumDec
);
100 return NonPowOfTwoCounter
>= 1 && NonPowOfTwoCounter
<= 2 &&
101 NonPowOfTwoCounter
< EnumLen
/ 2 &&
102 (VR
.MaxVal
- VR
.MinVal
!= EnumLen
- 1) &&
103 !(NonPowOfTwoCounter
== 1 && isMaxValAllBitSetLiteral(EnumDec
));
106 SuspiciousEnumUsageCheck::SuspiciousEnumUsageCheck(StringRef Name
,
107 ClangTidyContext
*Context
)
108 : ClangTidyCheck(Name
, Context
),
109 StrictMode(Options
.getLocalOrGlobal("StrictMode", false)) {}
111 void SuspiciousEnumUsageCheck::storeOptions(ClangTidyOptions::OptionMap
&Opts
) {
112 Options
.store(Opts
, "StrictMode", StrictMode
);
115 void SuspiciousEnumUsageCheck::registerMatchers(MatchFinder
*Finder
) {
116 const auto EnumExpr
= [](StringRef RefName
, StringRef DeclName
) {
117 return expr(hasType(enumDecl().bind(DeclName
))).bind(RefName
);
122 hasOperatorName("|"), hasLHS(hasType(enumDecl().bind("enumDecl"))),
123 hasRHS(hasType(enumDecl(unless(equalsBoundNode("enumDecl")))
124 .bind("otherEnumDecl"))))
129 binaryOperator(hasAnyOperatorName("+", "|"),
130 hasLHS(EnumExpr("lhsExpr", "enumDecl")),
131 hasRHS(expr(hasType(enumDecl(equalsBoundNode("enumDecl"))))
137 hasAnyOperatorName("+", "|"),
138 hasOperands(expr(hasType(isInteger()), unless(hasType(enumDecl()))),
139 EnumExpr("enumExpr", "enumDecl"))),
142 Finder
->addMatcher(binaryOperator(hasAnyOperatorName("|=", "+="),
143 hasRHS(EnumExpr("enumExpr", "enumDecl"))),
147 void SuspiciousEnumUsageCheck::checkSuspiciousBitmaskUsage(
148 const Expr
*NodeExpr
, const EnumDecl
*EnumDec
) {
149 const auto *EnumExpr
= dyn_cast
<DeclRefExpr
>(NodeExpr
);
150 const auto *EnumConst
=
151 EnumExpr
? dyn_cast
<EnumConstantDecl
>(EnumExpr
->getDecl()) : nullptr;
153 // Report the parameter if necessary.
155 diag(EnumDec
->getInnerLocStart(), BitmaskVarErrorMessage
)
156 << countNonPowOfTwoLiteralNum(EnumDec
);
157 diag(EnumExpr
->getExprLoc(), BitmaskNoteMessage
, DiagnosticIDs::Note
);
158 } else if (isNonPowerOf2NorNullLiteral(EnumConst
)) {
159 diag(EnumConst
->getSourceRange().getBegin(), BitmaskErrorMessage
);
160 diag(EnumExpr
->getExprLoc(), BitmaskNoteMessage
, DiagnosticIDs::Note
);
164 void SuspiciousEnumUsageCheck::check(const MatchFinder::MatchResult
&Result
) {
165 // Case 1: The two enum values come from different types.
166 if (const auto *DiffEnumOp
=
167 Result
.Nodes
.getNodeAs
<BinaryOperator
>("diffEnumOp")) {
168 const auto *EnumDec
= Result
.Nodes
.getNodeAs
<EnumDecl
>("enumDecl");
169 const auto *OtherEnumDec
=
170 Result
.Nodes
.getNodeAs
<EnumDecl
>("otherEnumDecl");
171 // Skip when one of the parameters is an empty enum. The
172 // hasDisjointValueRange function could not decide the values properly in
173 // case of an empty enum.
174 if (EnumDec
->enumerators().empty() || OtherEnumDec
->enumerators().empty())
177 if (!hasDisjointValueRange(EnumDec
, OtherEnumDec
))
178 diag(DiffEnumOp
->getOperatorLoc(), DifferentEnumErrorMessage
);
182 // Case 2 and 3 only checked in strict mode. The checker tries to detect
183 // suspicious bitmasks which contains values initialized by non power-of-2
187 const auto *EnumDec
= Result
.Nodes
.getNodeAs
<EnumDecl
>("enumDecl");
188 if (!isPossiblyBitMask(EnumDec
))
192 // a. Investigating the right hand side of `+=` or `|=` operator.
193 // b. When the operator is `|` or `+` but only one of them is an EnumExpr
194 if (const auto *EnumExpr
= Result
.Nodes
.getNodeAs
<Expr
>("enumExpr")) {
195 checkSuspiciousBitmaskUsage(EnumExpr
, EnumDec
);
200 // '|' or '+' operator where both argument comes from the same enum type
201 const auto *LhsExpr
= Result
.Nodes
.getNodeAs
<Expr
>("lhsExpr");
202 checkSuspiciousBitmaskUsage(LhsExpr
, EnumDec
);
204 const auto *RhsExpr
= Result
.Nodes
.getNodeAs
<Expr
>("rhsExpr");
205 checkSuspiciousBitmaskUsage(RhsExpr
, EnumDec
);
208 } // namespace clang::tidy::bugprone