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(ignoringImpCasts(expr().bind(RefName)),
122                 ignoringImpCasts(hasType(enumDecl().bind(DeclName))));
123   };
124 
125   Finder->addMatcher(
126       binaryOperator(hasOperatorName("|"), hasLHS(enumExpr("", "enumDecl")),
127                      hasRHS(expr(enumExpr("", "otherEnumDecl"),
128                                  ignoringImpCasts(hasType(enumDecl(
129                                      unless(equalsBoundNode("enumDecl"))))))))
130           .bind("diffEnumOp"),
131       this);
132 
133   Finder->addMatcher(
134       binaryOperator(hasAnyOperatorName("+", "|"),
135                      hasLHS(enumExpr("lhsExpr", "enumDecl")),
136                      hasRHS(expr(enumExpr("rhsExpr", ""),
137                                  ignoringImpCasts(hasType(
138                                      enumDecl(equalsBoundNode("enumDecl"))))))),
139       this);
140 
141   Finder->addMatcher(
142       binaryOperator(
143           hasAnyOperatorName("+", "|"),
144           hasOperands(expr(hasType(isInteger()), unless(enumExpr("", ""))),
145                       enumExpr("enumExpr", "enumDecl"))),
146       this);
147 
148   Finder->addMatcher(binaryOperator(hasAnyOperatorName("|=", "+="),
149                                     hasRHS(enumExpr("enumExpr", "enumDecl"))),
150                      this);
151 }
152 
checkSuspiciousBitmaskUsage(const Expr * NodeExpr,const EnumDecl * EnumDec)153 void SuspiciousEnumUsageCheck::checkSuspiciousBitmaskUsage(
154     const Expr *NodeExpr, const EnumDecl *EnumDec) {
155   const auto *EnumExpr = dyn_cast<DeclRefExpr>(NodeExpr);
156   const auto *EnumConst =
157       EnumExpr ? dyn_cast<EnumConstantDecl>(EnumExpr->getDecl()) : nullptr;
158 
159   // Report the parameter if necessary.
160   if (!EnumConst) {
161     diag(EnumDec->getInnerLocStart(), BitmaskVarErrorMessage)
162         << countNonPowOfTwoLiteralNum(EnumDec);
163     diag(EnumExpr->getExprLoc(), BitmaskNoteMessage, DiagnosticIDs::Note);
164   } else if (isNonPowerOf2NorNullLiteral(EnumConst)) {
165     diag(EnumConst->getSourceRange().getBegin(), BitmaskErrorMessage);
166     diag(EnumExpr->getExprLoc(), BitmaskNoteMessage, DiagnosticIDs::Note);
167   }
168 }
169 
check(const MatchFinder::MatchResult & Result)170 void SuspiciousEnumUsageCheck::check(const MatchFinder::MatchResult &Result) {
171   // Case 1: The two enum values come from different types.
172   if (const auto *DiffEnumOp =
173           Result.Nodes.getNodeAs<BinaryOperator>("diffEnumOp")) {
174     const auto *EnumDec = Result.Nodes.getNodeAs<EnumDecl>("enumDecl");
175     const auto *OtherEnumDec =
176         Result.Nodes.getNodeAs<EnumDecl>("otherEnumDecl");
177     // Skip when one of the parameters is an empty enum. The
178     // hasDisjointValueRange function could not decide the values properly in
179     // case of an empty enum.
180     if (EnumDec->enumerator_begin() == EnumDec->enumerator_end() ||
181         OtherEnumDec->enumerator_begin() == OtherEnumDec->enumerator_end())
182       return;
183 
184     if (!hasDisjointValueRange(EnumDec, OtherEnumDec))
185       diag(DiffEnumOp->getOperatorLoc(), DifferentEnumErrorMessage);
186     return;
187   }
188 
189   // Case 2 and 3 only checked in strict mode. The checker tries to detect
190   // suspicious bitmasks which contains values initialized by non power-of-2
191   // literals.
192   if (!StrictMode)
193     return;
194   const auto *EnumDec = Result.Nodes.getNodeAs<EnumDecl>("enumDecl");
195   if (!isPossiblyBitMask(EnumDec))
196     return;
197 
198   // Case 2:
199   //   a. Investigating the right hand side of `+=` or `|=` operator.
200   //   b. When the operator is `|` or `+` but only one of them is an EnumExpr
201   if (const auto *EnumExpr = Result.Nodes.getNodeAs<Expr>("enumExpr")) {
202     checkSuspiciousBitmaskUsage(EnumExpr, EnumDec);
203     return;
204   }
205 
206   // Case 3:
207   // '|' or '+' operator where both argument comes from the same enum type
208   const auto *LhsExpr = Result.Nodes.getNodeAs<Expr>("lhsExpr");
209   checkSuspiciousBitmaskUsage(LhsExpr, EnumDec);
210 
211   const auto *RhsExpr = Result.Nodes.getNodeAs<Expr>("rhsExpr");
212   checkSuspiciousBitmaskUsage(RhsExpr, EnumDec);
213 }
214 
215 } // namespace bugprone
216 } // namespace tidy
217 } // namespace clang
218