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