13365d621SCongcong Cai //===--- EnumInitialValueCheck.cpp - clang-tidy ---------------------------===// 23365d621SCongcong Cai // 33365d621SCongcong Cai // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 43365d621SCongcong Cai // See https://llvm.org/LICENSE.txt for license information. 53365d621SCongcong Cai // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 63365d621SCongcong Cai // 73365d621SCongcong Cai //===----------------------------------------------------------------------===// 83365d621SCongcong Cai 93365d621SCongcong Cai #include "EnumInitialValueCheck.h" 103365d621SCongcong Cai #include "../utils/LexerUtils.h" 113365d621SCongcong Cai #include "clang/AST/Decl.h" 123365d621SCongcong Cai #include "clang/ASTMatchers/ASTMatchFinder.h" 133365d621SCongcong Cai #include "clang/ASTMatchers/ASTMatchers.h" 143365d621SCongcong Cai #include "clang/Basic/Diagnostic.h" 153365d621SCongcong Cai #include "clang/Basic/SourceLocation.h" 163365d621SCongcong Cai #include "llvm/ADT/STLExtras.h" 173365d621SCongcong Cai #include "llvm/ADT/SmallString.h" 183365d621SCongcong Cai 193365d621SCongcong Cai using namespace clang::ast_matchers; 203365d621SCongcong Cai 213365d621SCongcong Cai namespace clang::tidy::readability { 223365d621SCongcong Cai 233365d621SCongcong Cai static bool isNoneEnumeratorsInitialized(const EnumDecl &Node) { 243365d621SCongcong Cai return llvm::all_of(Node.enumerators(), [](const EnumConstantDecl *ECD) { 253365d621SCongcong Cai return ECD->getInitExpr() == nullptr; 263365d621SCongcong Cai }); 273365d621SCongcong Cai } 283365d621SCongcong Cai 293365d621SCongcong Cai static bool isOnlyFirstEnumeratorInitialized(const EnumDecl &Node) { 303365d621SCongcong Cai bool IsFirst = true; 313365d621SCongcong Cai for (const EnumConstantDecl *ECD : Node.enumerators()) { 323365d621SCongcong Cai if ((IsFirst && ECD->getInitExpr() == nullptr) || 333365d621SCongcong Cai (!IsFirst && ECD->getInitExpr() != nullptr)) 343365d621SCongcong Cai return false; 353365d621SCongcong Cai IsFirst = false; 363365d621SCongcong Cai } 373365d621SCongcong Cai return !IsFirst; 383365d621SCongcong Cai } 393365d621SCongcong Cai 403365d621SCongcong Cai static bool areAllEnumeratorsInitialized(const EnumDecl &Node) { 413365d621SCongcong Cai return llvm::all_of(Node.enumerators(), [](const EnumConstantDecl *ECD) { 423365d621SCongcong Cai return ECD->getInitExpr() != nullptr; 433365d621SCongcong Cai }); 443365d621SCongcong Cai } 453365d621SCongcong Cai 463365d621SCongcong Cai /// Check if \p Enumerator is initialized with a (potentially negated) \c 473365d621SCongcong Cai /// IntegerLiteral. 483365d621SCongcong Cai static bool isInitializedByLiteral(const EnumConstantDecl *Enumerator) { 493365d621SCongcong Cai const Expr *const Init = Enumerator->getInitExpr(); 503365d621SCongcong Cai if (!Init) 513365d621SCongcong Cai return false; 523365d621SCongcong Cai return Init->isIntegerConstantExpr(Enumerator->getASTContext()); 533365d621SCongcong Cai } 543365d621SCongcong Cai 553365d621SCongcong Cai static void cleanInitialValue(DiagnosticBuilder &Diag, 563365d621SCongcong Cai const EnumConstantDecl *ECD, 573365d621SCongcong Cai const SourceManager &SM, 583365d621SCongcong Cai const LangOptions &LangOpts) { 593365d621SCongcong Cai const SourceRange InitExprRange = ECD->getInitExpr()->getSourceRange(); 603365d621SCongcong Cai if (InitExprRange.isInvalid() || InitExprRange.getBegin().isMacroID() || 613365d621SCongcong Cai InitExprRange.getEnd().isMacroID()) 623365d621SCongcong Cai return; 633365d621SCongcong Cai std::optional<Token> EqualToken = utils::lexer::findNextTokenSkippingComments( 643365d621SCongcong Cai ECD->getLocation(), SM, LangOpts); 653365d621SCongcong Cai if (!EqualToken.has_value() || 663365d621SCongcong Cai EqualToken.value().getKind() != tok::TokenKind::equal) 673365d621SCongcong Cai return; 683365d621SCongcong Cai const SourceLocation EqualLoc{EqualToken->getLocation()}; 693365d621SCongcong Cai if (EqualLoc.isInvalid() || EqualLoc.isMacroID()) 703365d621SCongcong Cai return; 713365d621SCongcong Cai Diag << FixItHint::CreateRemoval(EqualLoc) 723365d621SCongcong Cai << FixItHint::CreateRemoval(InitExprRange); 733365d621SCongcong Cai return; 743365d621SCongcong Cai } 753365d621SCongcong Cai 763365d621SCongcong Cai namespace { 773365d621SCongcong Cai 783365d621SCongcong Cai AST_MATCHER(EnumDecl, isMacro) { 793365d621SCongcong Cai SourceLocation Loc = Node.getBeginLoc(); 803365d621SCongcong Cai return Loc.isMacroID(); 813365d621SCongcong Cai } 823365d621SCongcong Cai 833365d621SCongcong Cai AST_MATCHER(EnumDecl, hasConsistentInitialValues) { 843365d621SCongcong Cai return isNoneEnumeratorsInitialized(Node) || 853365d621SCongcong Cai isOnlyFirstEnumeratorInitialized(Node) || 863365d621SCongcong Cai areAllEnumeratorsInitialized(Node); 873365d621SCongcong Cai } 883365d621SCongcong Cai 893365d621SCongcong Cai AST_MATCHER(EnumDecl, hasZeroInitialValueForFirstEnumerator) { 903365d621SCongcong Cai const EnumDecl::enumerator_range Enumerators = Node.enumerators(); 913365d621SCongcong Cai if (Enumerators.empty()) 923365d621SCongcong Cai return false; 933365d621SCongcong Cai const EnumConstantDecl *ECD = *Enumerators.begin(); 943365d621SCongcong Cai return isOnlyFirstEnumeratorInitialized(Node) && 953365d621SCongcong Cai isInitializedByLiteral(ECD) && ECD->getInitVal().isZero(); 963365d621SCongcong Cai } 973365d621SCongcong Cai 983365d621SCongcong Cai /// Excludes bitfields because enumerators initialized with the result of a 993365d621SCongcong Cai /// bitwise operator on enumeration values or any other expr that is not a 1003365d621SCongcong Cai /// potentially negative integer literal. 1013365d621SCongcong Cai /// Enumerations where it is not directly clear if they are used with 1023365d621SCongcong Cai /// bitmask, evident when enumerators are only initialized with (potentially 1033365d621SCongcong Cai /// negative) integer literals, are ignored. This is also the case when all 1043365d621SCongcong Cai /// enumerators are powers of two (e.g., 0, 1, 2). 1053365d621SCongcong Cai AST_MATCHER(EnumDecl, hasSequentialInitialValues) { 1063365d621SCongcong Cai const EnumDecl::enumerator_range Enumerators = Node.enumerators(); 1073365d621SCongcong Cai if (Enumerators.empty()) 1083365d621SCongcong Cai return false; 1093365d621SCongcong Cai const EnumConstantDecl *const FirstEnumerator = *Node.enumerator_begin(); 1103365d621SCongcong Cai llvm::APSInt PrevValue = FirstEnumerator->getInitVal(); 1113365d621SCongcong Cai if (!isInitializedByLiteral(FirstEnumerator)) 1123365d621SCongcong Cai return false; 1133365d621SCongcong Cai bool AllEnumeratorsArePowersOfTwo = true; 1143365d621SCongcong Cai for (const EnumConstantDecl *Enumerator : llvm::drop_begin(Enumerators)) { 1153365d621SCongcong Cai const llvm::APSInt NewValue = Enumerator->getInitVal(); 1163365d621SCongcong Cai if (NewValue != ++PrevValue) 1173365d621SCongcong Cai return false; 1183365d621SCongcong Cai if (!isInitializedByLiteral(Enumerator)) 1193365d621SCongcong Cai return false; 1203365d621SCongcong Cai PrevValue = NewValue; 1213365d621SCongcong Cai AllEnumeratorsArePowersOfTwo &= NewValue.isPowerOf2(); 1223365d621SCongcong Cai } 1233365d621SCongcong Cai return !AllEnumeratorsArePowersOfTwo; 1243365d621SCongcong Cai } 1253365d621SCongcong Cai 126*f5ff3a56SDiscookie std::string getName(const EnumDecl *Decl) { 127*f5ff3a56SDiscookie if (!Decl->getDeclName()) 128*f5ff3a56SDiscookie return "<unnamed>"; 129*f5ff3a56SDiscookie 130*f5ff3a56SDiscookie return Decl->getQualifiedNameAsString(); 131*f5ff3a56SDiscookie } 132*f5ff3a56SDiscookie 1333365d621SCongcong Cai } // namespace 1343365d621SCongcong Cai 1353365d621SCongcong Cai EnumInitialValueCheck::EnumInitialValueCheck(StringRef Name, 1363365d621SCongcong Cai ClangTidyContext *Context) 1373365d621SCongcong Cai : ClangTidyCheck(Name, Context), 1383365d621SCongcong Cai AllowExplicitZeroFirstInitialValue( 1393365d621SCongcong Cai Options.get("AllowExplicitZeroFirstInitialValue", true)), 1403365d621SCongcong Cai AllowExplicitSequentialInitialValues( 1413365d621SCongcong Cai Options.get("AllowExplicitSequentialInitialValues", true)) {} 1423365d621SCongcong Cai 1433365d621SCongcong Cai void EnumInitialValueCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { 1443365d621SCongcong Cai Options.store(Opts, "AllowExplicitZeroFirstInitialValue", 1453365d621SCongcong Cai AllowExplicitZeroFirstInitialValue); 1463365d621SCongcong Cai Options.store(Opts, "AllowExplicitSequentialInitialValues", 1473365d621SCongcong Cai AllowExplicitSequentialInitialValues); 1483365d621SCongcong Cai } 1493365d621SCongcong Cai 1503365d621SCongcong Cai void EnumInitialValueCheck::registerMatchers(MatchFinder *Finder) { 151caaac84aSJulian Schmidt Finder->addMatcher(enumDecl(isDefinition(), unless(isMacro()), 152caaac84aSJulian Schmidt unless(hasConsistentInitialValues())) 1533365d621SCongcong Cai .bind("inconsistent"), 1543365d621SCongcong Cai this); 1553365d621SCongcong Cai if (!AllowExplicitZeroFirstInitialValue) 1563365d621SCongcong Cai Finder->addMatcher( 157caaac84aSJulian Schmidt enumDecl(isDefinition(), hasZeroInitialValueForFirstEnumerator()) 158caaac84aSJulian Schmidt .bind("zero_first"), 1593365d621SCongcong Cai this); 1603365d621SCongcong Cai if (!AllowExplicitSequentialInitialValues) 161caaac84aSJulian Schmidt Finder->addMatcher(enumDecl(isDefinition(), unless(isMacro()), 162caaac84aSJulian Schmidt hasSequentialInitialValues()) 1633365d621SCongcong Cai .bind("sequential"), 1643365d621SCongcong Cai this); 1653365d621SCongcong Cai } 1663365d621SCongcong Cai 1673365d621SCongcong Cai void EnumInitialValueCheck::check(const MatchFinder::MatchResult &Result) { 1683365d621SCongcong Cai if (const auto *Enum = Result.Nodes.getNodeAs<EnumDecl>("inconsistent")) { 1693365d621SCongcong Cai DiagnosticBuilder Diag = 170*f5ff3a56SDiscookie diag( 171*f5ff3a56SDiscookie Enum->getBeginLoc(), 172*f5ff3a56SDiscookie "initial values in enum '%0' are not consistent, consider explicit " 1733365d621SCongcong Cai "initialization of all, none or only the first enumerator") 174*f5ff3a56SDiscookie << getName(Enum); 1753365d621SCongcong Cai for (const EnumConstantDecl *ECD : Enum->enumerators()) 1763365d621SCongcong Cai if (ECD->getInitExpr() == nullptr) { 1773365d621SCongcong Cai const SourceLocation EndLoc = Lexer::getLocForEndOfToken( 1783365d621SCongcong Cai ECD->getLocation(), 0, *Result.SourceManager, getLangOpts()); 1793365d621SCongcong Cai if (EndLoc.isMacroID()) 1803365d621SCongcong Cai continue; 1813365d621SCongcong Cai llvm::SmallString<8> Str{" = "}; 1823365d621SCongcong Cai ECD->getInitVal().toString(Str); 1833365d621SCongcong Cai Diag << FixItHint::CreateInsertion(EndLoc, Str); 1843365d621SCongcong Cai } 1853365d621SCongcong Cai return; 1863365d621SCongcong Cai } 1873365d621SCongcong Cai 1883365d621SCongcong Cai if (const auto *Enum = Result.Nodes.getNodeAs<EnumDecl>("zero_first")) { 1893365d621SCongcong Cai const EnumConstantDecl *ECD = *Enum->enumerator_begin(); 1903365d621SCongcong Cai const SourceLocation Loc = ECD->getLocation(); 1913365d621SCongcong Cai if (Loc.isInvalid() || Loc.isMacroID()) 1923365d621SCongcong Cai return; 1933365d621SCongcong Cai DiagnosticBuilder Diag = diag(Loc, "zero initial value for the first " 194*f5ff3a56SDiscookie "enumerator in '%0' can be disregarded") 195*f5ff3a56SDiscookie << getName(Enum); 1963365d621SCongcong Cai cleanInitialValue(Diag, ECD, *Result.SourceManager, getLangOpts()); 1973365d621SCongcong Cai return; 1983365d621SCongcong Cai } 1993365d621SCongcong Cai if (const auto *Enum = Result.Nodes.getNodeAs<EnumDecl>("sequential")) { 2003365d621SCongcong Cai DiagnosticBuilder Diag = 2013365d621SCongcong Cai diag(Enum->getBeginLoc(), 202*f5ff3a56SDiscookie "sequential initial value in '%0' can be ignored") 203*f5ff3a56SDiscookie << getName(Enum); 2043365d621SCongcong Cai for (const EnumConstantDecl *ECD : llvm::drop_begin(Enum->enumerators())) 2053365d621SCongcong Cai cleanInitialValue(Diag, ECD, *Result.SourceManager, getLangOpts()); 2063365d621SCongcong Cai return; 2073365d621SCongcong Cai } 2083365d621SCongcong Cai } 2093365d621SCongcong Cai 2103365d621SCongcong Cai } // namespace clang::tidy::readability 211