clang-tools 6.0.0

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