1//===--- SuspiciousEnumUsageCheck.cpp - clang-tidy-------------------------===//
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-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "SuspiciousEnumUsageCheck.h"
10#include "clang/AST/ASTContext.h"
11#include "clang/ASTMatchers/ASTMatchFinder.h"
12#include <algorithm>
13
14using namespace clang::ast_matchers;
15
16namespace clang::tidy::bugprone {
17
18static const char DifferentEnumErrorMessage[] =
19 "enum values are from different enum types";
20
21static const char BitmaskErrorMessage[] =
22 "enum type seems like a bitmask (contains mostly "
23 "power-of-2 literals), but this literal is not a "
24 "power-of-2";
25
26static 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 "
29 "power-of-2";
30
31static const char BitmaskNoteMessage[] = "used here as a bitmask";
32
33/// Stores a min and a max value which describe an interval.
34struct ValueRange {
35 llvm::APSInt MinVal;
36 llvm::APSInt MaxVal;
37
38 ValueRange(const EnumDecl *EnumDec) {
39 const auto MinMaxVal = std::minmax_element(
40 first: EnumDec->enumerator_begin(), last: EnumDec->enumerator_end(),
41 comp: [](const EnumConstantDecl *E1, const EnumConstantDecl *E2) {
42 return llvm::APSInt::compareValues(I1: E1->getInitVal(),
43 I2: E2->getInitVal()) < 0;
44 });
45 MinVal = MinMaxVal.first->getInitVal();
46 MaxVal = MinMaxVal.second->getInitVal();
47 }
48};
49
50/// Return the number of EnumConstantDecls in an EnumDecl.
51static int enumLength(const EnumDecl *EnumDec) {
52 return std::distance(first: EnumDec->enumerator_begin(), last: EnumDec->enumerator_end());
53}
54
55static bool hasDisjointValueRange(const EnumDecl *Enum1,
56 const EnumDecl *Enum2) {
57 ValueRange Range1(Enum1), Range2(Enum2);
58 return llvm::APSInt::compareValues(I1: Range1.MaxVal, I2: Range2.MinVal) < 0 ||
59 llvm::APSInt::compareValues(I1: Range2.MaxVal, I2: Range1.MinVal) < 0;
60}
61
62static bool isNonPowerOf2NorNullLiteral(const EnumConstantDecl *EnumConst) {
63 const llvm::APSInt &Val = EnumConst->getInitVal();
64 if (Val.isPowerOf2() || !Val.getBoolValue())
65 return false;
66 const Expr *InitExpr = EnumConst->getInitExpr();
67 if (!InitExpr)
68 return true;
69 return isa<IntegerLiteral>(Val: InitExpr->IgnoreImpCasts());
70}
71
72static bool isMaxValAllBitSetLiteral(const EnumDecl *EnumDec) {
73 auto EnumConst = std::max_element(
74 first: EnumDec->enumerator_begin(), last: EnumDec->enumerator_end(),
75 comp: [](const EnumConstantDecl *E1, const EnumConstantDecl *E2) {
76 return E1->getInitVal() < E2->getInitVal();
77 });
78
79 if (const Expr *InitExpr = EnumConst->getInitExpr()) {
80 return EnumConst->getInitVal().countr_one() ==
81 EnumConst->getInitVal().getActiveBits() &&
82 isa<IntegerLiteral>(Val: InitExpr->IgnoreImpCasts());
83 }
84 return false;
85}
86
87static int countNonPowOfTwoLiteralNum(const EnumDecl *EnumDec) {
88 return llvm::count_if(Range: EnumDec->enumerators(), P: isNonPowerOf2NorNullLiteral);
89}
90
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.
96static 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));
104}
105
106SuspiciousEnumUsageCheck::SuspiciousEnumUsageCheck(StringRef Name,
107 ClangTidyContext *Context)
108 : ClangTidyCheck(Name, Context),
109 StrictMode(Options.getLocalOrGlobal(LocalName: "StrictMode", Default: false)) {}
110
111void SuspiciousEnumUsageCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
112 Options.store(Options&: Opts, LocalName: "StrictMode", Value: StrictMode);
113}
114
115void SuspiciousEnumUsageCheck::registerMatchers(MatchFinder *Finder) {
116 const auto EnumExpr = [](StringRef RefName, StringRef DeclName) {
117 return expr(hasType(InnerMatcher: enumDecl().bind(ID: DeclName))).bind(ID: RefName);
118 };
119
120 Finder->addMatcher(
121 NodeMatch: binaryOperator(
122 hasOperatorName(Name: "|"), hasLHS(InnerMatcher: hasType(InnerMatcher: enumDecl().bind(ID: "enumDecl"))),
123 hasRHS(InnerMatcher: hasType(InnerMatcher: enumDecl(unless(equalsBoundNode(ID: "enumDecl")))
124 .bind(ID: "otherEnumDecl"))))
125 .bind(ID: "diffEnumOp"),
126 Action: this);
127
128 Finder->addMatcher(
129 NodeMatch: binaryOperator(hasAnyOperatorName("+", "|"),
130 hasLHS(InnerMatcher: EnumExpr("lhsExpr", "enumDecl")),
131 hasRHS(InnerMatcher: expr(hasType(InnerMatcher: enumDecl(equalsBoundNode(ID: "enumDecl"))))
132 .bind(ID: "rhsExpr"))),
133 Action: this);
134
135 Finder->addMatcher(
136 NodeMatch: binaryOperator(
137 hasAnyOperatorName("+", "|"),
138 hasOperands(Matcher1: expr(hasType(InnerMatcher: isInteger()), unless(hasType(InnerMatcher: enumDecl()))),
139 Matcher2: EnumExpr("enumExpr", "enumDecl"))),
140 Action: this);
141
142 Finder->addMatcher(NodeMatch: binaryOperator(hasAnyOperatorName("|=", "+="),
143 hasRHS(InnerMatcher: EnumExpr("enumExpr", "enumDecl"))),
144 Action: this);
145}
146
147void SuspiciousEnumUsageCheck::checkSuspiciousBitmaskUsage(
148 const Expr *NodeExpr, const EnumDecl *EnumDec) {
149 const auto *EnumExpr = dyn_cast<DeclRefExpr>(Val: NodeExpr);
150 const auto *EnumConst =
151 EnumExpr ? dyn_cast<EnumConstantDecl>(Val: EnumExpr->getDecl()) : nullptr;
152
153 // Report the parameter if necessary.
154 if (!EnumConst) {
155 diag(EnumDec->getInnerLocStart(), BitmaskVarErrorMessage)
156 << countNonPowOfTwoLiteralNum(EnumDec);
157 diag(EnumExpr->getExprLoc(), BitmaskNoteMessage, DiagnosticIDs::Note);
158 } else if (isNonPowerOf2NorNullLiteral(EnumConst)) {
159 diag(Loc: EnumConst->getSourceRange().getBegin(), Description: BitmaskErrorMessage);
160 diag(EnumExpr->getExprLoc(), BitmaskNoteMessage, DiagnosticIDs::Note);
161 }
162}
163
164void 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>(ID: "diffEnumOp")) {
168 const auto *EnumDec = Result.Nodes.getNodeAs<EnumDecl>(ID: "enumDecl");
169 const auto *OtherEnumDec =
170 Result.Nodes.getNodeAs<EnumDecl>(ID: "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->enumerator_begin() == EnumDec->enumerator_end() ||
175 OtherEnumDec->enumerator_begin() == OtherEnumDec->enumerator_end())
176 return;
177
178 if (!hasDisjointValueRange(Enum1: EnumDec, Enum2: OtherEnumDec))
179 diag(Loc: DiffEnumOp->getOperatorLoc(), Description: DifferentEnumErrorMessage);
180 return;
181 }
182
183 // Case 2 and 3 only checked in strict mode. The checker tries to detect
184 // suspicious bitmasks which contains values initialized by non power-of-2
185 // literals.
186 if (!StrictMode)
187 return;
188 const auto *EnumDec = Result.Nodes.getNodeAs<EnumDecl>(ID: "enumDecl");
189 if (!isPossiblyBitMask(EnumDec))
190 return;
191
192 // Case 2:
193 // a. Investigating the right hand side of `+=` or `|=` operator.
194 // b. When the operator is `|` or `+` but only one of them is an EnumExpr
195 if (const auto *EnumExpr = Result.Nodes.getNodeAs<Expr>(ID: "enumExpr")) {
196 checkSuspiciousBitmaskUsage(NodeExpr: EnumExpr, EnumDec);
197 return;
198 }
199
200 // Case 3:
201 // '|' or '+' operator where both argument comes from the same enum type
202 const auto *LhsExpr = Result.Nodes.getNodeAs<Expr>(ID: "lhsExpr");
203 checkSuspiciousBitmaskUsage(NodeExpr: LhsExpr, EnumDec);
204
205 const auto *RhsExpr = Result.Nodes.getNodeAs<Expr>(ID: "rhsExpr");
206 checkSuspiciousBitmaskUsage(NodeExpr: RhsExpr, EnumDec);
207}
208
209} // namespace clang::tidy::bugprone
210

source code of clang-tools-extra/clang-tidy/bugprone/SuspiciousEnumUsageCheck.cpp