//===--- SuspiciousEnumUsageCheck.cpp - clang-tidy-------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "SuspiciousEnumUsageCheck.h" #include "clang/AST/ASTContext.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include using namespace clang::ast_matchers; namespace clang { namespace tidy { namespace bugprone { static const char DifferentEnumErrorMessage[] = "enum values are from different enum types"; static const char BitmaskErrorMessage[] = "enum type seems like a bitmask (contains mostly " "power-of-2 literals), but this literal is not a " "power-of-2"; static const char BitmaskVarErrorMessage[] = "enum type seems like a bitmask (contains mostly " "power-of-2 literals) but %plural{1:a literal is|:some literals are}0 not " "power-of-2"; static const char BitmaskNoteMessage[] = "used here as a bitmask"; /// Stores a min and a max value which describe an interval. struct ValueRange { llvm::APSInt MinVal; llvm::APSInt MaxVal; ValueRange(const EnumDecl *EnumDec) { const auto MinMaxVal = std::minmax_element( EnumDec->enumerator_begin(), EnumDec->enumerator_end(), [](const EnumConstantDecl *E1, const EnumConstantDecl *E2) { return llvm::APSInt::compareValues(E1->getInitVal(), E2->getInitVal()) < 0; }); MinVal = MinMaxVal.first->getInitVal(); MaxVal = MinMaxVal.second->getInitVal(); } }; /// Return the number of EnumConstantDecls in an EnumDecl. static int enumLength(const EnumDecl *EnumDec) { return std::distance(EnumDec->enumerator_begin(), EnumDec->enumerator_end()); } static bool hasDisjointValueRange(const EnumDecl *Enum1, const EnumDecl *Enum2) { ValueRange Range1(Enum1), Range2(Enum2); return llvm::APSInt::compareValues(Range1.MaxVal, Range2.MinVal) < 0 || llvm::APSInt::compareValues(Range2.MaxVal, Range1.MinVal) < 0; } static bool isNonPowerOf2NorNullLiteral(const EnumConstantDecl *EnumConst) { llvm::APSInt Val = EnumConst->getInitVal(); if (Val.isPowerOf2() || !Val.getBoolValue()) return false; const Expr *InitExpr = EnumConst->getInitExpr(); if (!InitExpr) return true; return isa(InitExpr->IgnoreImpCasts()); } static bool isMaxValAllBitSetLiteral(const EnumDecl *EnumDec) { auto EnumConst = std::max_element( EnumDec->enumerator_begin(), EnumDec->enumerator_end(), [](const EnumConstantDecl *E1, const EnumConstantDecl *E2) { return E1->getInitVal() < E2->getInitVal(); }); if (const Expr *InitExpr = EnumConst->getInitExpr()) { return EnumConst->getInitVal().countTrailingOnes() == EnumConst->getInitVal().getActiveBits() && isa(InitExpr->IgnoreImpCasts()); } return false; } static int countNonPowOfTwoLiteralNum(const EnumDecl *EnumDec) { return std::count_if( EnumDec->enumerator_begin(), EnumDec->enumerator_end(), [](const EnumConstantDecl *E) { return isNonPowerOf2NorNullLiteral(E); }); } /// Check if there is one or two enumerators that are not a power of 2 and are /// initialized by a literal in the enum type, and that the enumeration contains /// enough elements to reasonably act as a bitmask. Exclude the case where the /// last enumerator is the sum of the lesser values (and initialized by a /// literal) or when it could contain consecutive values. static bool isPossiblyBitMask(const EnumDecl *EnumDec) { ValueRange VR(EnumDec); int EnumLen = enumLength(EnumDec); int NonPowOfTwoCounter = countNonPowOfTwoLiteralNum(EnumDec); return NonPowOfTwoCounter >= 1 && NonPowOfTwoCounter <= 2 && NonPowOfTwoCounter < EnumLen / 2 && (VR.MaxVal - VR.MinVal != EnumLen - 1) && !(NonPowOfTwoCounter == 1 && isMaxValAllBitSetLiteral(EnumDec)); } SuspiciousEnumUsageCheck::SuspiciousEnumUsageCheck(StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context), StrictMode(Options.getLocalOrGlobal("StrictMode", 0)) {} void SuspiciousEnumUsageCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { Options.store(Opts, "StrictMode", StrictMode); } void SuspiciousEnumUsageCheck::registerMatchers(MatchFinder *Finder) { const auto enumExpr = [](StringRef RefName, StringRef DeclName) { return expr(ignoringImpCasts(expr().bind(RefName)), ignoringImpCasts(hasType(enumDecl().bind(DeclName)))); }; Finder->addMatcher( binaryOperator(hasOperatorName("|"), hasLHS(enumExpr("", "enumDecl")), hasRHS(expr(enumExpr("", "otherEnumDecl"), ignoringImpCasts(hasType(enumDecl( unless(equalsBoundNode("enumDecl")))))))) .bind("diffEnumOp"), this); Finder->addMatcher( binaryOperator(anyOf(hasOperatorName("+"), hasOperatorName("|")), hasLHS(enumExpr("lhsExpr", "enumDecl")), hasRHS(expr(enumExpr("rhsExpr", ""), ignoringImpCasts(hasType( enumDecl(equalsBoundNode("enumDecl"))))))), this); Finder->addMatcher( binaryOperator(anyOf(hasOperatorName("+"), hasOperatorName("|")), hasEitherOperand( expr(hasType(isInteger()), unless(enumExpr("", "")))), hasEitherOperand(enumExpr("enumExpr", "enumDecl"))), this); Finder->addMatcher( binaryOperator(anyOf(hasOperatorName("|="), hasOperatorName("+=")), hasRHS(enumExpr("enumExpr", "enumDecl"))), this); } void SuspiciousEnumUsageCheck::checkSuspiciousBitmaskUsage( const Expr *NodeExpr, const EnumDecl *EnumDec) { const auto *EnumExpr = dyn_cast(NodeExpr); const auto *EnumConst = EnumExpr ? dyn_cast(EnumExpr->getDecl()) : nullptr; // Report the parameter if neccessary. if (!EnumConst) { diag(EnumDec->getInnerLocStart(), BitmaskVarErrorMessage) << countNonPowOfTwoLiteralNum(EnumDec); diag(EnumExpr->getExprLoc(), BitmaskNoteMessage, DiagnosticIDs::Note); } else if (isNonPowerOf2NorNullLiteral(EnumConst)) { diag(EnumConst->getSourceRange().getBegin(), BitmaskErrorMessage); diag(EnumExpr->getExprLoc(), BitmaskNoteMessage, DiagnosticIDs::Note); } } void SuspiciousEnumUsageCheck::check(const MatchFinder::MatchResult &Result) { // Case 1: The two enum values come from different types. if (const auto *DiffEnumOp = Result.Nodes.getNodeAs("diffEnumOp")) { const auto *EnumDec = Result.Nodes.getNodeAs("enumDecl"); const auto *OtherEnumDec = Result.Nodes.getNodeAs("otherEnumDecl"); // Skip when one of the parameters is an empty enum. The // hasDisjointValueRange function could not decide the values properly in // case of an empty enum. if (EnumDec->enumerator_begin() == EnumDec->enumerator_end() || OtherEnumDec->enumerator_begin() == OtherEnumDec->enumerator_end()) return; if (!hasDisjointValueRange(EnumDec, OtherEnumDec)) diag(DiffEnumOp->getOperatorLoc(), DifferentEnumErrorMessage); return; } // Case 2 and 3 only checked in strict mode. The checker tries to detect // suspicious bitmasks which contains values initialized by non power-of-2 // literals. if (!StrictMode) return; const auto *EnumDec = Result.Nodes.getNodeAs("enumDecl"); if (!isPossiblyBitMask(EnumDec)) return; // Case 2: // a. Investigating the right hand side of `+=` or `|=` operator. // b. When the operator is `|` or `+` but only one of them is an EnumExpr if (const auto *EnumExpr = Result.Nodes.getNodeAs("enumExpr")) { checkSuspiciousBitmaskUsage(EnumExpr, EnumDec); return; } // Case 3: // '|' or '+' operator where both argument comes from the same enum type const auto *LhsExpr = Result.Nodes.getNodeAs("lhsExpr"); checkSuspiciousBitmaskUsage(LhsExpr, EnumDec); const auto *RhsExpr = Result.Nodes.getNodeAs("rhsExpr"); checkSuspiciousBitmaskUsage(RhsExpr, EnumDec); } } // namespace bugprone } // namespace tidy } // namespace clang