210 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 "EnumInitialValueCheck.h"10#include "../utils/LexerUtils.h"11#include "clang/AST/Decl.h"12#include "clang/ASTMatchers/ASTMatchFinder.h"13#include "clang/ASTMatchers/ASTMatchers.h"14#include "clang/Basic/Diagnostic.h"15#include "clang/Basic/SourceLocation.h"16#include "llvm/ADT/STLExtras.h"17#include "llvm/ADT/SmallString.h"18 19using namespace clang::ast_matchers;20 21namespace clang::tidy::readability {22 23static bool isNoneEnumeratorsInitialized(const EnumDecl &Node) {24 return llvm::all_of(Node.enumerators(), [](const EnumConstantDecl *ECD) {25 return ECD->getInitExpr() == nullptr;26 });27}28 29static bool isOnlyFirstEnumeratorInitialized(const EnumDecl &Node) {30 bool IsFirst = true;31 for (const EnumConstantDecl *ECD : Node.enumerators()) {32 if ((IsFirst && ECD->getInitExpr() == nullptr) ||33 (!IsFirst && ECD->getInitExpr() != nullptr))34 return false;35 IsFirst = false;36 }37 return !IsFirst;38}39 40static bool areAllEnumeratorsInitialized(const EnumDecl &Node) {41 return llvm::all_of(Node.enumerators(), [](const EnumConstantDecl *ECD) {42 return ECD->getInitExpr() != nullptr;43 });44}45 46/// Check if \p Enumerator is initialized with a (potentially negated) \c47/// IntegerLiteral.48static bool isInitializedByLiteral(const EnumConstantDecl *Enumerator) {49 const Expr *const Init = Enumerator->getInitExpr();50 if (!Init)51 return false;52 return Init->isIntegerConstantExpr(Enumerator->getASTContext());53}54 55static void cleanInitialValue(DiagnosticBuilder &Diag,56 const EnumConstantDecl *ECD,57 const SourceManager &SM,58 const LangOptions &LangOpts) {59 const SourceRange InitExprRange = ECD->getInitExpr()->getSourceRange();60 if (InitExprRange.isInvalid() || InitExprRange.getBegin().isMacroID() ||61 InitExprRange.getEnd().isMacroID())62 return;63 std::optional<Token> EqualToken = utils::lexer::findNextTokenSkippingComments(64 ECD->getLocation(), SM, LangOpts);65 if (!EqualToken.has_value() ||66 EqualToken.value().getKind() != tok::TokenKind::equal)67 return;68 const SourceLocation EqualLoc{EqualToken->getLocation()};69 if (EqualLoc.isInvalid() || EqualLoc.isMacroID())70 return;71 Diag << FixItHint::CreateRemoval(EqualLoc)72 << FixItHint::CreateRemoval(InitExprRange);73}74 75namespace {76 77AST_MATCHER(EnumDecl, isMacro) {78 const SourceLocation Loc = Node.getBeginLoc();79 return Loc.isMacroID();80}81 82AST_MATCHER(EnumDecl, hasConsistentInitialValues) {83 return isNoneEnumeratorsInitialized(Node) ||84 isOnlyFirstEnumeratorInitialized(Node) ||85 areAllEnumeratorsInitialized(Node);86}87 88AST_MATCHER(EnumDecl, hasZeroInitialValueForFirstEnumerator) {89 const EnumDecl::enumerator_range Enumerators = Node.enumerators();90 if (Enumerators.empty())91 return false;92 const EnumConstantDecl *ECD = *Enumerators.begin();93 return isOnlyFirstEnumeratorInitialized(Node) &&94 isInitializedByLiteral(ECD) && ECD->getInitVal().isZero();95}96 97/// Excludes bitfields because enumerators initialized with the result of a98/// bitwise operator on enumeration values or any other expr that is not a99/// potentially negative integer literal.100/// Enumerations where it is not directly clear if they are used with101/// bitmask, evident when enumerators are only initialized with (potentially102/// negative) integer literals, are ignored. This is also the case when all103/// enumerators are powers of two (e.g., 0, 1, 2).104AST_MATCHER(EnumDecl, hasSequentialInitialValues) {105 const EnumDecl::enumerator_range Enumerators = Node.enumerators();106 if (Enumerators.empty())107 return false;108 const EnumConstantDecl *const FirstEnumerator = *Node.enumerator_begin();109 llvm::APSInt PrevValue = FirstEnumerator->getInitVal();110 if (!isInitializedByLiteral(FirstEnumerator))111 return false;112 bool AllEnumeratorsArePowersOfTwo = true;113 for (const EnumConstantDecl *Enumerator : llvm::drop_begin(Enumerators)) {114 const llvm::APSInt NewValue = Enumerator->getInitVal();115 if (NewValue != ++PrevValue)116 return false;117 if (!isInitializedByLiteral(Enumerator))118 return false;119 PrevValue = NewValue;120 AllEnumeratorsArePowersOfTwo &= NewValue.isPowerOf2();121 }122 return !AllEnumeratorsArePowersOfTwo;123}124 125} // namespace126 127static std::string getName(const EnumDecl *Decl) {128 if (!Decl->getDeclName())129 return "<unnamed>";130 131 return Decl->getQualifiedNameAsString();132}133 134EnumInitialValueCheck::EnumInitialValueCheck(StringRef Name,135 ClangTidyContext *Context)136 : ClangTidyCheck(Name, Context),137 AllowExplicitZeroFirstInitialValue(138 Options.get("AllowExplicitZeroFirstInitialValue", true)),139 AllowExplicitSequentialInitialValues(140 Options.get("AllowExplicitSequentialInitialValues", true)) {}141 142void EnumInitialValueCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {143 Options.store(Opts, "AllowExplicitZeroFirstInitialValue",144 AllowExplicitZeroFirstInitialValue);145 Options.store(Opts, "AllowExplicitSequentialInitialValues",146 AllowExplicitSequentialInitialValues);147}148 149void EnumInitialValueCheck::registerMatchers(MatchFinder *Finder) {150 Finder->addMatcher(enumDecl(isDefinition(), unless(isMacro()),151 unless(hasConsistentInitialValues()))152 .bind("inconsistent"),153 this);154 if (!AllowExplicitZeroFirstInitialValue)155 Finder->addMatcher(156 enumDecl(isDefinition(), hasZeroInitialValueForFirstEnumerator())157 .bind("zero_first"),158 this);159 if (!AllowExplicitSequentialInitialValues)160 Finder->addMatcher(enumDecl(isDefinition(), unless(isMacro()),161 hasSequentialInitialValues())162 .bind("sequential"),163 this);164}165 166void EnumInitialValueCheck::check(const MatchFinder::MatchResult &Result) {167 if (const auto *Enum = Result.Nodes.getNodeAs<EnumDecl>("inconsistent")) {168 const DiagnosticBuilder Diag =169 diag(170 Enum->getBeginLoc(),171 "initial values in enum '%0' are not consistent, consider explicit "172 "initialization of all, none or only the first enumerator")173 << getName(Enum);174 for (const EnumConstantDecl *ECD : Enum->enumerators())175 if (ECD->getInitExpr() == nullptr) {176 const SourceLocation EndLoc = Lexer::getLocForEndOfToken(177 ECD->getLocation(), 0, *Result.SourceManager, getLangOpts());178 if (EndLoc.isMacroID())179 continue;180 llvm::SmallString<8> Str{" = "};181 ECD->getInitVal().toString(Str);182 Diag << FixItHint::CreateInsertion(EndLoc, Str);183 }184 return;185 }186 187 if (const auto *Enum = Result.Nodes.getNodeAs<EnumDecl>("zero_first")) {188 const EnumConstantDecl *ECD = *Enum->enumerator_begin();189 const SourceLocation Loc = ECD->getLocation();190 if (Loc.isInvalid() || Loc.isMacroID())191 return;192 DiagnosticBuilder Diag = diag(Loc, "zero initial value for the first "193 "enumerator in '%0' can be disregarded")194 << getName(Enum);195 cleanInitialValue(Diag, ECD, *Result.SourceManager, getLangOpts());196 return;197 }198 if (const auto *Enum = Result.Nodes.getNodeAs<EnumDecl>("sequential")) {199 DiagnosticBuilder Diag =200 diag(Enum->getBeginLoc(),201 "sequential initial value in '%0' can be ignored")202 << getName(Enum);203 for (const EnumConstantDecl *ECD : llvm::drop_begin(Enum->enumerators()))204 cleanInitialValue(Diag, ECD, *Result.SourceManager, getLangOpts());205 return;206 }207}208 209} // namespace clang::tidy::readability210