File indexing completed on 2024-05-12 05:41:03

0001 /*
0002     SPDX-FileCopyrightText: 2021 Klarälvdalens Datakonsult AB a KDAB Group company info@kdab.com
0003     SPDX-FileContributor: Waqar Ahmed <waqar.ahmed@kdab.com>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "unexpected-flag-enumerator-value.h"
0009 #include "HierarchyUtils.h"
0010 #include "SourceCompatibilityHelpers.h"
0011 
0012 #include <clang/AST/AST.h>
0013 
0014 #include <algorithm>
0015 
0016 using namespace clang;
0017 
0018 UnexpectedFlagEnumeratorValue::UnexpectedFlagEnumeratorValue(const std::string &name, ClazyContext *context)
0019     : CheckBase(name, context)
0020 {
0021 }
0022 
0023 static ConstantExpr *getConstantExpr(EnumConstantDecl *enCD)
0024 {
0025     auto *cexpr = dyn_cast_or_null<ConstantExpr>(enCD->getInitExpr());
0026     if (cexpr) {
0027         return cexpr;
0028     }
0029     return clazy::getFirstChildOfType<ConstantExpr>(enCD->getInitExpr());
0030 }
0031 
0032 static bool isBinaryOperatorExpression(ConstantExpr *cexpr)
0033 {
0034     return clazy::getFirstChildOfType<BinaryOperator>(cexpr);
0035 }
0036 
0037 static bool isReferenceToEnumerator(ConstantExpr *cexpr)
0038 {
0039     return dyn_cast_or_null<DeclRefExpr>(cexpr->getSubExpr());
0040 }
0041 
0042 static bool isIntentionallyNotPowerOf2(EnumConstantDecl *en)
0043 {
0044     constexpr unsigned MinOnesToQualifyAsMask = 3;
0045 
0046     const auto val = en->getInitVal();
0047     if (val.isMask() && val.countTrailingOnes() >= MinOnesToQualifyAsMask) {
0048         return true;
0049     }
0050 
0051 #if LLVM_VERSION_MAJOR >= 17
0052     if (val.isShiftedMask() && val.popcount() >= MinOnesToQualifyAsMask) {
0053         return true;
0054     }
0055 #else
0056     if (val.isShiftedMask() && val.countPopulation() >= MinOnesToQualifyAsMask) {
0057         return true;
0058     }
0059 #endif
0060 
0061     if (clazy::contains_lower(en->getName(), "mask")) {
0062         return true;
0063     }
0064 
0065     auto *cexpr = getConstantExpr(en);
0066     if (!cexpr) {
0067         return false;
0068     }
0069 
0070     if (isBinaryOperatorExpression(cexpr)) {
0071         return true;
0072     }
0073 
0074     if (isReferenceToEnumerator(cexpr)) {
0075         return true;
0076     }
0077 
0078     return false;
0079 }
0080 
0081 static SmallVector<EnumConstantDecl *, 16> getEnumerators(EnumDecl *enDecl)
0082 {
0083     SmallVector<EnumConstantDecl *, 16> ret;
0084     for (auto *enumerator : enDecl->enumerators()) {
0085         ret.push_back(enumerator);
0086     }
0087     return ret;
0088 }
0089 
0090 static uint64_t getIntegerValue(EnumConstantDecl *e)
0091 {
0092     return e->getInitVal().getLimitedValue();
0093 }
0094 
0095 static bool hasConsecutiveValues(const SmallVector<EnumConstantDecl *, 16> &enumerators)
0096 {
0097     auto val = getIntegerValue(enumerators.front());
0098     const size_t until = std::min<size_t>(4, enumerators.size());
0099     for (size_t i = 1; i < until; ++i) {
0100         val++;
0101         if (getIntegerValue(enumerators[i]) != val) {
0102             return false;
0103         }
0104     }
0105     return true;
0106 }
0107 
0108 static bool hasInitExprs(const SmallVector<EnumConstantDecl *, 16> &enumerators)
0109 {
0110     size_t enumeratorsWithInitExpr = 0;
0111     for (auto *enumerator : enumerators) {
0112         if (enumerator->getInitExpr()) {
0113             enumeratorsWithInitExpr++;
0114         }
0115     }
0116 
0117     return enumeratorsWithInitExpr == enumerators.size();
0118 }
0119 
0120 static bool isFlagEnum(const SmallVector<EnumConstantDecl *, 16> &enumerators)
0121 {
0122     if (enumerators.size() < 4) {
0123         return false;
0124     }
0125 
0126     // For an enum to be considered "flag like", all enumerators
0127     // must have an explicit init value / expr
0128     if (!hasInitExprs(enumerators)) {
0129         return false;
0130     }
0131 
0132     if (hasConsecutiveValues(enumerators)) {
0133         return false;
0134     }
0135 
0136     llvm::SmallVector<bool, 16> enumValues;
0137     for (auto *enumerator : enumerators) {
0138         enumValues.push_back(enumerator->getInitVal().isPowerOf2());
0139     }
0140 
0141     const size_t count = std::count(enumValues.begin(), enumValues.end(), false);
0142 
0143     // If half of our values were power-of-2, this is probably a flag enum
0144     return count <= (enumerators.size() / 2);
0145 }
0146 
0147 void UnexpectedFlagEnumeratorValue::VisitDecl(clang::Decl *decl)
0148 {
0149     auto *enDecl = dyn_cast_or_null<EnumDecl>(decl);
0150     if (!enDecl || !enDecl->hasNameForLinkage()) {
0151         return;
0152     }
0153 
0154     const SmallVector<EnumConstantDecl *, 16> enumerators = getEnumerators(enDecl);
0155 
0156     if (!isFlagEnum(enumerators)) {
0157         return;
0158     }
0159 
0160     for (EnumConstantDecl *enumerator : enumerators) {
0161         const auto &initVal = enumerator->getInitVal();
0162 #if LLVM_VERSION_MAJOR >= 17
0163         if (!initVal.isPowerOf2() && !initVal.isZero() && !initVal.isNegative()) {
0164 #else
0165         if (!initVal.isPowerOf2() && !initVal.isNullValue() && !initVal.isNegative()) {
0166 #endif
0167             if (isIntentionallyNotPowerOf2(enumerator)) {
0168                 continue;
0169             }
0170             const auto value = enumerator->getInitVal().getLimitedValue();
0171             Expr *initExpr = enumerator->getInitExpr();
0172             emitWarning(initExpr ? initExpr->getBeginLoc() : enumerator->getBeginLoc(), "Unexpected non power-of-2 enumerator value: " + std::to_string(value));
0173         }
0174     }
0175 }