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
14 using namespace clang::ast_matchers;
15
16 namespace clang {
17 namespace tidy {
18 namespace bugprone {
19
20 static const char DifferentEnumErrorMessage[] =
21 "enum values are from different enum types";
22
23 static const char BitmaskErrorMessage[] =
24 "enum type seems like a bitmask (contains mostly "
25 "power-of-2 literals), but this literal is not a "
26 "power-of-2";
27
28 static const char BitmaskVarErrorMessage[] =
29 "enum type seems like a bitmask (contains mostly "
30 "power-of-2 literals) but %plural{1:a literal is|:some literals are}0 not "
31 "power-of-2";
32
33 static const char BitmaskNoteMessage[] = "used here as a bitmask";
34
35 /// Stores a min and a max value which describe an interval.
36 struct ValueRange {
37 llvm::APSInt MinVal;
38 llvm::APSInt MaxVal;
39
ValueRangeclang::tidy::bugprone::ValueRange40 ValueRange(const EnumDecl *EnumDec) {
41 const auto MinMaxVal = std::minmax_element(
42 EnumDec->enumerator_begin(), EnumDec->enumerator_end(),
43 [](const EnumConstantDecl *E1, const EnumConstantDecl *E2) {
44 return llvm::APSInt::compareValues(E1->getInitVal(),
45 E2->getInitVal()) < 0;
46 });
47 MinVal = MinMaxVal.first->getInitVal();
48 MaxVal = MinMaxVal.second->getInitVal();
49 }
50 };
51
52 /// Return the number of EnumConstantDecls in an EnumDecl.
enumLength(const EnumDecl * EnumDec)53 static int enumLength(const EnumDecl *EnumDec) {
54 return std::distance(EnumDec->enumerator_begin(), EnumDec->enumerator_end());
55 }
56
hasDisjointValueRange(const EnumDecl * Enum1,const EnumDecl * Enum2)57 static bool hasDisjointValueRange(const EnumDecl *Enum1,
58 const EnumDecl *Enum2) {
59 ValueRange Range1(Enum1), Range2(Enum2);
60 return llvm::APSInt::compareValues(Range1.MaxVal, Range2.MinVal) < 0 ||
61 llvm::APSInt::compareValues(Range2.MaxVal, Range1.MinVal) < 0;
62 }
63
isNonPowerOf2NorNullLiteral(const EnumConstantDecl * EnumConst)64 static bool isNonPowerOf2NorNullLiteral(const EnumConstantDecl *EnumConst) {
65 llvm::APSInt Val = EnumConst->getInitVal();
66 if (Val.isPowerOf2() || !Val.getBoolValue())
67 return false;
68 const Expr *InitExpr = EnumConst->getInitExpr();
69 if (!InitExpr)
70 return true;
71 return isa<IntegerLiteral>(InitExpr->IgnoreImpCasts());
72 }
73
isMaxValAllBitSetLiteral(const EnumDecl * EnumDec)74 static bool isMaxValAllBitSetLiteral(const EnumDecl *EnumDec) {
75 auto EnumConst = std::max_element(
76 EnumDec->enumerator_begin(), EnumDec->enumerator_end(),
77 [](const EnumConstantDecl *E1, const EnumConstantDecl *E2) {
78 return E1->getInitVal() < E2->getInitVal();
79 });
80
81 if (const Expr *InitExpr = EnumConst->getInitExpr()) {
82 return EnumConst->getInitVal().countTrailingOnes() ==
83 EnumConst->getInitVal().getActiveBits() &&
84 isa<IntegerLiteral>(InitExpr->IgnoreImpCasts());
85 }
86 return false;
87 }
88
countNonPowOfTwoLiteralNum(const EnumDecl * EnumDec)89 static int countNonPowOfTwoLiteralNum(const EnumDecl *EnumDec) {
90 return std::count_if(
91 EnumDec->enumerator_begin(), EnumDec->enumerator_end(),
92 [](const EnumConstantDecl *E) { return isNonPowerOf2NorNullLiteral(E); });
93 }
94
95 /// Check if there is one or two enumerators that are not a power of 2 and are
96 /// initialized by a literal in the enum type, and that the enumeration contains
97 /// enough elements to reasonably act as a bitmask. Exclude the case where the
98 /// last enumerator is the sum of the lesser values (and initialized by a
99 /// literal) or when it could contain consecutive values.
isPossiblyBitMask(const EnumDecl * EnumDec)100 static bool isPossiblyBitMask(const EnumDecl *EnumDec) {
101 ValueRange VR(EnumDec);
102 int EnumLen = enumLength(EnumDec);
103 int NonPowOfTwoCounter = countNonPowOfTwoLiteralNum(EnumDec);
104 return NonPowOfTwoCounter >= 1 && NonPowOfTwoCounter <= 2 &&
105 NonPowOfTwoCounter < EnumLen / 2 &&
106 (VR.MaxVal - VR.MinVal != EnumLen - 1) &&
107 !(NonPowOfTwoCounter == 1 && isMaxValAllBitSetLiteral(EnumDec));
108 }
109
SuspiciousEnumUsageCheck(StringRef Name,ClangTidyContext * Context)110 SuspiciousEnumUsageCheck::SuspiciousEnumUsageCheck(StringRef Name,
111 ClangTidyContext *Context)
112 : ClangTidyCheck(Name, Context),
113 StrictMode(Options.getLocalOrGlobal("StrictMode", false)) {}
114
storeOptions(ClangTidyOptions::OptionMap & Opts)115 void SuspiciousEnumUsageCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
116 Options.store(Opts, "StrictMode", StrictMode);
117 }
118
registerMatchers(MatchFinder * Finder)119 void SuspiciousEnumUsageCheck::registerMatchers(MatchFinder *Finder) {
120 const auto enumExpr = [](StringRef RefName, StringRef DeclName) {
121 return expr(ignoringImpCasts(expr().bind(RefName)),
122 ignoringImpCasts(hasType(enumDecl().bind(DeclName))));
123 };
124
125 Finder->addMatcher(
126 binaryOperator(hasOperatorName("|"), hasLHS(enumExpr("", "enumDecl")),
127 hasRHS(expr(enumExpr("", "otherEnumDecl"),
128 ignoringImpCasts(hasType(enumDecl(
129 unless(equalsBoundNode("enumDecl"))))))))
130 .bind("diffEnumOp"),
131 this);
132
133 Finder->addMatcher(
134 binaryOperator(hasAnyOperatorName("+", "|"),
135 hasLHS(enumExpr("lhsExpr", "enumDecl")),
136 hasRHS(expr(enumExpr("rhsExpr", ""),
137 ignoringImpCasts(hasType(
138 enumDecl(equalsBoundNode("enumDecl"))))))),
139 this);
140
141 Finder->addMatcher(
142 binaryOperator(
143 hasAnyOperatorName("+", "|"),
144 hasOperands(expr(hasType(isInteger()), unless(enumExpr("", ""))),
145 enumExpr("enumExpr", "enumDecl"))),
146 this);
147
148 Finder->addMatcher(binaryOperator(hasAnyOperatorName("|=", "+="),
149 hasRHS(enumExpr("enumExpr", "enumDecl"))),
150 this);
151 }
152
checkSuspiciousBitmaskUsage(const Expr * NodeExpr,const EnumDecl * EnumDec)153 void SuspiciousEnumUsageCheck::checkSuspiciousBitmaskUsage(
154 const Expr *NodeExpr, const EnumDecl *EnumDec) {
155 const auto *EnumExpr = dyn_cast<DeclRefExpr>(NodeExpr);
156 const auto *EnumConst =
157 EnumExpr ? dyn_cast<EnumConstantDecl>(EnumExpr->getDecl()) : nullptr;
158
159 // Report the parameter if necessary.
160 if (!EnumConst) {
161 diag(EnumDec->getInnerLocStart(), BitmaskVarErrorMessage)
162 << countNonPowOfTwoLiteralNum(EnumDec);
163 diag(EnumExpr->getExprLoc(), BitmaskNoteMessage, DiagnosticIDs::Note);
164 } else if (isNonPowerOf2NorNullLiteral(EnumConst)) {
165 diag(EnumConst->getSourceRange().getBegin(), BitmaskErrorMessage);
166 diag(EnumExpr->getExprLoc(), BitmaskNoteMessage, DiagnosticIDs::Note);
167 }
168 }
169
check(const MatchFinder::MatchResult & Result)170 void SuspiciousEnumUsageCheck::check(const MatchFinder::MatchResult &Result) {
171 // Case 1: The two enum values come from different types.
172 if (const auto *DiffEnumOp =
173 Result.Nodes.getNodeAs<BinaryOperator>("diffEnumOp")) {
174 const auto *EnumDec = Result.Nodes.getNodeAs<EnumDecl>("enumDecl");
175 const auto *OtherEnumDec =
176 Result.Nodes.getNodeAs<EnumDecl>("otherEnumDecl");
177 // Skip when one of the parameters is an empty enum. The
178 // hasDisjointValueRange function could not decide the values properly in
179 // case of an empty enum.
180 if (EnumDec->enumerator_begin() == EnumDec->enumerator_end() ||
181 OtherEnumDec->enumerator_begin() == OtherEnumDec->enumerator_end())
182 return;
183
184 if (!hasDisjointValueRange(EnumDec, OtherEnumDec))
185 diag(DiffEnumOp->getOperatorLoc(), DifferentEnumErrorMessage);
186 return;
187 }
188
189 // Case 2 and 3 only checked in strict mode. The checker tries to detect
190 // suspicious bitmasks which contains values initialized by non power-of-2
191 // literals.
192 if (!StrictMode)
193 return;
194 const auto *EnumDec = Result.Nodes.getNodeAs<EnumDecl>("enumDecl");
195 if (!isPossiblyBitMask(EnumDec))
196 return;
197
198 // Case 2:
199 // a. Investigating the right hand side of `+=` or `|=` operator.
200 // b. When the operator is `|` or `+` but only one of them is an EnumExpr
201 if (const auto *EnumExpr = Result.Nodes.getNodeAs<Expr>("enumExpr")) {
202 checkSuspiciousBitmaskUsage(EnumExpr, EnumDec);
203 return;
204 }
205
206 // Case 3:
207 // '|' or '+' operator where both argument comes from the same enum type
208 const auto *LhsExpr = Result.Nodes.getNodeAs<Expr>("lhsExpr");
209 checkSuspiciousBitmaskUsage(LhsExpr, EnumDec);
210
211 const auto *RhsExpr = Result.Nodes.getNodeAs<Expr>("rhsExpr");
212 checkSuspiciousBitmaskUsage(RhsExpr, EnumDec);
213 }
214
215 } // namespace bugprone
216 } // namespace tidy
217 } // namespace clang
218