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