clang-tools  14.0.0git
SuspiciousEnumUsageCheck.cpp
Go to the documentation of this file.
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 
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 
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 /// 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 llvm::APSInt::compareValues(Range1.MaxVal, Range2.MinVal) < 0 ||
61  llvm::APSInt::compareValues(Range2.MaxVal, Range1.MinVal) < 0;
62 }
63 
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 
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 
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.
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 
110 SuspiciousEnumUsageCheck::SuspiciousEnumUsageCheck(StringRef Name,
111  ClangTidyContext *Context)
112  : ClangTidyCheck(Name, Context),
113  StrictMode(Options.getLocalOrGlobal("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 
151 void 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 
168 void 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->enumerator_begin() == EnumDec->enumerator_end() ||
179  OtherEnumDec->enumerator_begin() == OtherEnumDec->enumerator_end())
180  return;
181 
182  if (!hasDisjointValueRange(EnumDec, OtherEnumDec))
183  diag(DiffEnumOp->getOperatorLoc(), DifferentEnumErrorMessage);
184  return;
185  }
186 
187  // Case 2 and 3 only checked in strict mode. The checker tries to detect
188  // suspicious bitmasks which contains values initialized by non power-of-2
189  // literals.
190  if (!StrictMode)
191  return;
192  const auto *EnumDec = Result.Nodes.getNodeAs<EnumDecl>("enumDecl");
193  if (!isPossiblyBitMask(EnumDec))
194  return;
195 
196  // Case 2:
197  // a. Investigating the right hand side of `+=` or `|=` operator.
198  // b. When the operator is `|` or `+` but only one of them is an EnumExpr
199  if (const auto *EnumExpr = Result.Nodes.getNodeAs<Expr>("enumExpr")) {
200  checkSuspiciousBitmaskUsage(EnumExpr, EnumDec);
201  return;
202  }
203 
204  // Case 3:
205  // '|' or '+' operator where both argument comes from the same enum type
206  const auto *LhsExpr = Result.Nodes.getNodeAs<Expr>("lhsExpr");
207  checkSuspiciousBitmaskUsage(LhsExpr, EnumDec);
208 
209  const auto *RhsExpr = Result.Nodes.getNodeAs<Expr>("rhsExpr");
210  checkSuspiciousBitmaskUsage(RhsExpr, EnumDec);
211 }
212 
213 } // namespace bugprone
214 } // namespace tidy
215 } // namespace clang
clang::tidy::bugprone::hasDisjointValueRange
static bool hasDisjointValueRange(const EnumDecl *Enum1, const EnumDecl *Enum2)
Definition: SuspiciousEnumUsageCheck.cpp:57
clang::tidy::ClangTidyOptions::OptionMap
llvm::StringMap< ClangTidyValue > OptionMap
Definition: ClangTidyOptions.h:115
E
const Expr * E
Definition: AvoidBindCheck.cpp:88
clang::tidy::bugprone::ValueRange
Stores a min and a max value which describe an interval.
Definition: SuspiciousEnumUsageCheck.cpp:36
clang::tidy::bugprone::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:115
clang::tidy::bugprone::enumLength
static int enumLength(const EnumDecl *EnumDec)
Return the number of EnumConstantDecls in an EnumDecl.
Definition: SuspiciousEnumUsageCheck.cpp:53
clang::tidy::ClangTidyCheck
Base class for all clang-tidy checks.
Definition: ClangTidyCheck.h:54
clang::ast_matchers
Definition: AbseilMatcher.h:14
clang::tidy::bugprone::ValueRange::MinVal
llvm::APSInt MinVal
Definition: SuspiciousEnumUsageCheck.cpp:37
clang::tidy::bugprone::isMaxValAllBitSetLiteral
static bool isMaxValAllBitSetLiteral(const EnumDecl *EnumDec)
Definition: SuspiciousEnumUsageCheck.cpp:74
clang::tidy::bugprone::BitmaskErrorMessage
static const char BitmaskErrorMessage[]
Definition: SuspiciousEnumUsageCheck.cpp:23
clang::tidy::ClangTidyCheck::Options
OptionsView Options
Definition: ClangTidyCheck.h:416
clang::tidy::ClangTidyContext
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
Definition: ClangTidyDiagnosticConsumer.h:71
Name
static constexpr llvm::StringLiteral Name
Definition: UppercaseLiteralSuffixCheck.cpp:28
clang::tidy::ClangTidyCheck::diag
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check's name.
Definition: ClangTidyCheck.cpp:25
clang::tidy::bugprone::BitmaskVarErrorMessage
static const char BitmaskVarErrorMessage[]
Definition: SuspiciousEnumUsageCheck.cpp:28
clang::tidy::bugprone::BitmaskNoteMessage
static const char BitmaskNoteMessage[]
Definition: SuspiciousEnumUsageCheck.cpp:33
clang::tidy::bugprone::SuspiciousEnumUsageCheck::registerMatchers
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
Definition: SuspiciousEnumUsageCheck.cpp:119
clang
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Definition: ApplyReplacements.h:27
clang::tidy::bugprone::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:168
clang::tidy::bugprone::ValueRange::ValueRange
ValueRange(const EnumDecl *EnumDec)
Definition: SuspiciousEnumUsageCheck.cpp:40
clang::tidy::bugprone::ValueRange::MaxVal
llvm::APSInt MaxVal
Definition: SuspiciousEnumUsageCheck.cpp:38
clang::tidy::ClangTidyCheck::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: ClangTidyCheck.cpp:120
SuspiciousEnumUsageCheck.h
clang::tidy::bugprone::countNonPowOfTwoLiteralNum
static int countNonPowOfTwoLiteralNum(const EnumDecl *EnumDec)
Definition: SuspiciousEnumUsageCheck.cpp:89
clang::tidy::bugprone::isNonPowerOf2NorNullLiteral
static bool isNonPowerOf2NorNullLiteral(const EnumConstantDecl *EnumConst)
Definition: SuspiciousEnumUsageCheck.cpp:64
clang::tidy::bugprone::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:100
clang::tidy::bugprone::DifferentEnumErrorMessage
static const char DifferentEnumErrorMessage[]
Definition: SuspiciousEnumUsageCheck.cpp:20