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 {
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 
ValueRangeclang::tidy::bugprone::ValueRange40   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.
enumLength(const EnumDecl * EnumDec)53 static int enumLength(const EnumDecl *EnumDec) {
54   return std::distance(EnumDec->enumerator_begin(), EnumDec->enumerator_end());
55 }
56 
hasDisjointValueRange(const EnumDecl * Enum1,const EnumDecl * Enum2)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 
isNonPowerOf2NorNullLiteral(const EnumConstantDecl * EnumConst)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 
isMaxValAllBitSetLiteral(const EnumDecl * EnumDec)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 
countNonPowOfTwoLiteralNum(const EnumDecl * EnumDec)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.
isPossiblyBitMask(const EnumDecl * EnumDec)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 
SuspiciousEnumUsageCheck(StringRef Name,ClangTidyContext * Context)110 SuspiciousEnumUsageCheck::SuspiciousEnumUsageCheck(StringRef Name,
111                                                    ClangTidyContext *Context)
112     : ClangTidyCheck(Name, Context),
113       StrictMode(Options.getLocalOrGlobal("StrictMode", false)) {}
114 
storeOptions(ClangTidyOptions::OptionMap & Opts)115 void SuspiciousEnumUsageCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
116   Options.store(Opts, "StrictMode", StrictMode);
117 }
118 
registerMatchers(MatchFinder * Finder)119 void SuspiciousEnumUsageCheck::registerMatchers(MatchFinder *Finder) {
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 
checkSuspiciousBitmaskUsage(const Expr * NodeExpr,const EnumDecl * EnumDec)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 
check(const MatchFinder::MatchResult & Result)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
216