209 lines · cpp
1//===----------------------------------------------------------------------===//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-exception6//7//===----------------------------------------------------------------------===//8 9#include "SuspiciousEnumUsageCheck.h"10#include "clang/AST/ASTContext.h"11#include "clang/ASTMatchers/ASTMatchFinder.h"12#include <algorithm>13 14using namespace clang::ast_matchers;15 16namespace clang::tidy::bugprone {17 18static const char DifferentEnumErrorMessage[] =19 "enum values are from different enum types";20 21static 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 26static 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 31static const char BitmaskNoteMessage[] = "used here as a bitmask";32 33/// Stores a min and a max value which describe an interval.34struct ValueRange {35 llvm::APSInt MinVal;36 llvm::APSInt MaxVal;37 38 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.51static int enumLength(const EnumDecl *EnumDec) {52 return std::distance(EnumDec->enumerator_begin(), EnumDec->enumerator_end());53}54 55static bool hasDisjointValueRange(const EnumDecl *Enum1,56 const EnumDecl *Enum2) {57 const 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 62static 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 72static 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 87static 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 are92/// initialized by a literal in the enum type, and that the enumeration contains93/// enough elements to reasonably act as a bitmask. Exclude the case where the94/// last enumerator is the sum of the lesser values (and initialized by a95/// literal) or when it could contain consecutive values.96static bool isPossiblyBitMask(const EnumDecl *EnumDec) {97 const ValueRange VR(EnumDec);98 const int EnumLen = enumLength(EnumDec);99 const 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 106SuspiciousEnumUsageCheck::SuspiciousEnumUsageCheck(StringRef Name,107 ClangTidyContext *Context)108 : ClangTidyCheck(Name, Context),109 StrictMode(Options.get("StrictMode", false)) {}110 111void SuspiciousEnumUsageCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {112 Options.store(Opts, "StrictMode", StrictMode);113}114 115void 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 147void 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 164void 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. The172 // hasDisjointValueRange function could not decide the values properly in173 // 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 detect183 // suspicious bitmasks which contains values initialized by non power-of-2184 // 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 EnumExpr194 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 type201 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::bugprone209