clang-tools 23.0.0git
SuspiciousEnumUsageCheck.cpp
Go to the documentation of this file.
1//===----------------------------------------------------------------------===//
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
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
33namespace {
34
35/// Stores a min and a max value which describe an interval.
36struct ValueRange {
37 llvm::APSInt MinVal;
38 llvm::APSInt MaxVal;
39
40 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} // namespace
53
54/// Return the number of EnumConstantDecls in an EnumDecl.
55static int enumLength(const EnumDecl *EnumDec) {
56 return std::distance(EnumDec->enumerator_begin(), EnumDec->enumerator_end());
57}
58
59static bool hasDisjointValueRange(const EnumDecl *Enum1,
60 const EnumDecl *Enum2) {
61 const ValueRange Range1(Enum1), Range2(Enum2);
62 return llvm::APSInt::compareValues(Range1.MaxVal, Range2.MinVal) < 0 ||
63 llvm::APSInt::compareValues(Range2.MaxVal, Range1.MinVal) < 0;
64}
65
66static bool isNonPowerOf2NorNullLiteral(const EnumConstantDecl *EnumConst) {
67 const llvm::APSInt &Val = EnumConst->getInitVal();
68 if (Val.isPowerOf2() || !Val.getBoolValue())
69 return false;
70 const Expr *InitExpr = EnumConst->getInitExpr();
71 if (!InitExpr)
72 return true;
73 return isa<IntegerLiteral>(InitExpr->IgnoreImpCasts());
74}
75
76static bool isMaxValAllBitSetLiteral(const EnumDecl *EnumDec) {
77 auto EnumConst = std::max_element(
78 EnumDec->enumerator_begin(), EnumDec->enumerator_end(),
79 [](const EnumConstantDecl *E1, const EnumConstantDecl *E2) {
80 return E1->getInitVal() < E2->getInitVal();
81 });
82
83 if (const Expr *InitExpr = EnumConst->getInitExpr()) {
84 return EnumConst->getInitVal().countr_one() ==
85 EnumConst->getInitVal().getActiveBits() &&
86 isa<IntegerLiteral>(InitExpr->IgnoreImpCasts());
87 }
88 return false;
89}
90
91static int countNonPowOfTwoLiteralNum(const EnumDecl *EnumDec) {
92 return llvm::count_if(EnumDec->enumerators(), isNonPowerOf2NorNullLiteral);
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.
100static bool isPossiblyBitMask(const EnumDecl *EnumDec) {
101 const ValueRange VR(EnumDec);
102 const int EnumLen = enumLength(EnumDec);
103 const 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
111 ClangTidyContext *Context)
112 : ClangTidyCheck(Name, Context),
113 StrictMode(Options.get("StrictMode", false)) {}
114
116 Options.store(Opts, "StrictMode", StrictMode);
117}
118
120 const auto EnumExpr = [](StringRef RefName, StringRef DeclName) {
121 return expr(hasType(enumDecl().bind(DeclName))).bind(RefName);
122 };
123
124 Finder->addMatcher(
125 binaryOperator(
126 hasOperatorName("|"), hasLHS(hasType(enumDecl().bind("enumDecl"))),
127 hasRHS(hasType(enumDecl(unless(equalsBoundNode("enumDecl")))
128 .bind("otherEnumDecl"))))
129 .bind("diffEnumOp"),
130 this);
131
132 Finder->addMatcher(
133 binaryOperator(hasAnyOperatorName("+", "|"),
134 hasLHS(EnumExpr("lhsExpr", "enumDecl")),
135 hasRHS(expr(hasType(enumDecl(equalsBoundNode("enumDecl"))))
136 .bind("rhsExpr"))),
137 this);
138
139 Finder->addMatcher(
140 binaryOperator(
141 hasAnyOperatorName("+", "|"),
142 hasOperands(expr(hasType(isInteger()), unless(hasType(enumDecl()))),
143 EnumExpr("enumExpr", "enumDecl"))),
144 this);
145
146 Finder->addMatcher(binaryOperator(hasAnyOperatorName("|=", "+="),
147 hasRHS(EnumExpr("enumExpr", "enumDecl"))),
148 this);
149}
150
151void SuspiciousEnumUsageCheck::checkSuspiciousBitmaskUsage(
152 const Expr *NodeExpr, const EnumDecl *EnumDec) {
153 const auto *EnumExpr = dyn_cast<DeclRefExpr>(NodeExpr);
154 const auto *EnumConst =
155 EnumExpr ? dyn_cast<EnumConstantDecl>(EnumExpr->getDecl()) : nullptr;
156
157 // Report the parameter if necessary.
158 if (!EnumConst) {
159 diag(EnumDec->getInnerLocStart(), BitmaskVarErrorMessage)
160 << countNonPowOfTwoLiteralNum(EnumDec);
161 diag(EnumExpr->getExprLoc(), BitmaskNoteMessage, DiagnosticIDs::Note);
162 } else if (isNonPowerOf2NorNullLiteral(EnumConst)) {
163 diag(EnumConst->getSourceRange().getBegin(), BitmaskErrorMessage);
164 diag(EnumExpr->getExprLoc(), BitmaskNoteMessage, DiagnosticIDs::Note);
165 }
166}
167
168void SuspiciousEnumUsageCheck::check(const MatchFinder::MatchResult &Result) {
169 // Case 1: The two enum values come from different types.
170 if (const auto *DiffEnumOp =
171 Result.Nodes.getNodeAs<BinaryOperator>("diffEnumOp")) {
172 const auto *EnumDec = Result.Nodes.getNodeAs<EnumDecl>("enumDecl");
173 const auto *OtherEnumDec =
174 Result.Nodes.getNodeAs<EnumDecl>("otherEnumDecl");
175 // Skip when one of the parameters is an empty enum. The
176 // hasDisjointValueRange function could not decide the values properly in
177 // case of an empty enum.
178 if (EnumDec->enumerators().empty() || OtherEnumDec->enumerators().empty())
179 return;
180
181 if (!hasDisjointValueRange(EnumDec, OtherEnumDec))
182 diag(DiffEnumOp->getOperatorLoc(), DifferentEnumErrorMessage);
183 return;
184 }
185
186 // Case 2 and 3 only checked in strict mode. The checker tries to detect
187 // suspicious bitmasks which contains values initialized by non power-of-2
188 // literals.
189 if (!StrictMode)
190 return;
191 const auto *EnumDec = Result.Nodes.getNodeAs<EnumDecl>("enumDecl");
192 if (!isPossiblyBitMask(EnumDec))
193 return;
194
195 // Case 2:
196 // a. Investigating the right hand side of `+=` or `|=` operator.
197 // b. When the operator is `|` or `+` but only one of them is an EnumExpr
198 if (const auto *EnumExpr = Result.Nodes.getNodeAs<Expr>("enumExpr")) {
199 checkSuspiciousBitmaskUsage(EnumExpr, EnumDec);
200 return;
201 }
202
203 // Case 3:
204 // '|' or '+' operator where both argument comes from the same enum type
205 const auto *LhsExpr = Result.Nodes.getNodeAs<Expr>("lhsExpr");
206 checkSuspiciousBitmaskUsage(LhsExpr, EnumDec);
207
208 const auto *RhsExpr = Result.Nodes.getNodeAs<Expr>("rhsExpr");
209 checkSuspiciousBitmaskUsage(RhsExpr, EnumDec);
210}
211
212} // namespace clang::tidy::bugprone
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
void registerMatchers(ast_matchers::MatchFinder *Finder) override
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
SuspiciousEnumUsageCheck(StringRef Name, ClangTidyContext *Context)
static bool hasDisjointValueRange(const EnumDecl *Enum1, const EnumDecl *Enum2)
static bool isPossiblyBitMask(const EnumDecl *EnumDec)
Check if there is one or two enumerators that are not a power of 2 and are initialized by a literal i...
static bool isMaxValAllBitSetLiteral(const EnumDecl *EnumDec)
static const char DifferentEnumErrorMessage[]
static const char BitmaskNoteMessage[]
static const char BitmaskVarErrorMessage[]
static int enumLength(const EnumDecl *EnumDec)
Return the number of EnumConstantDecls in an EnumDecl.
static int countNonPowOfTwoLiteralNum(const EnumDecl *EnumDec)
static bool isNonPowerOf2NorNullLiteral(const EnumConstantDecl *EnumConst)
static const char BitmaskErrorMessage[]
llvm::StringMap< ClangTidyValue > OptionMap