//===--- EnumInitialValueCheck.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 "EnumInitialValueCheck.h" #include "../utils/LexerUtils.h" #include "clang/AST/Decl.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "clang/Basic/Diagnostic.h" #include "clang/Basic/SourceLocation.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallString.h" using namespace clang::ast_matchers; namespace clang::tidy::readability { static bool isNoneEnumeratorsInitialized(const EnumDecl &Node) { return llvm::all_of(Node.enumerators(), [](const EnumConstantDecl *ECD) { return ECD->getInitExpr() == nullptr; }); } static bool isOnlyFirstEnumeratorInitialized(const EnumDecl &Node) { bool IsFirst = true; for (const EnumConstantDecl *ECD : Node.enumerators()) { if ((IsFirst && ECD->getInitExpr() == nullptr) || (!IsFirst && ECD->getInitExpr() != nullptr)) return false; IsFirst = false; } return !IsFirst; } static bool areAllEnumeratorsInitialized(const EnumDecl &Node) { return llvm::all_of(Node.enumerators(), [](const EnumConstantDecl *ECD) { return ECD->getInitExpr() != nullptr; }); } /// Check if \p Enumerator is initialized with a (potentially negated) \c /// IntegerLiteral. static bool isInitializedByLiteral(const EnumConstantDecl *Enumerator) { const Expr *const Init = Enumerator->getInitExpr(); if (!Init) return false; return Init->isIntegerConstantExpr(Enumerator->getASTContext()); } static void cleanInitialValue(DiagnosticBuilder &Diag, const EnumConstantDecl *ECD, const SourceManager &SM, const LangOptions &LangOpts) { const SourceRange InitExprRange = ECD->getInitExpr()->getSourceRange(); if (InitExprRange.isInvalid() || InitExprRange.getBegin().isMacroID() || InitExprRange.getEnd().isMacroID()) return; std::optional EqualToken = utils::lexer::findNextTokenSkippingComments( ECD->getLocation(), SM, LangOpts); if (!EqualToken.has_value() || EqualToken.value().getKind() != tok::TokenKind::equal) return; const SourceLocation EqualLoc{EqualToken->getLocation()}; if (EqualLoc.isInvalid() || EqualLoc.isMacroID()) return; Diag << FixItHint::CreateRemoval(EqualLoc) << FixItHint::CreateRemoval(InitExprRange); return; } namespace { AST_MATCHER(EnumDecl, isMacro) { SourceLocation Loc = Node.getBeginLoc(); return Loc.isMacroID(); } AST_MATCHER(EnumDecl, hasConsistentInitialValues) { return isNoneEnumeratorsInitialized(Node) || isOnlyFirstEnumeratorInitialized(Node) || areAllEnumeratorsInitialized(Node); } AST_MATCHER(EnumDecl, hasZeroInitialValueForFirstEnumerator) { const EnumDecl::enumerator_range Enumerators = Node.enumerators(); if (Enumerators.empty()) return false; const EnumConstantDecl *ECD = *Enumerators.begin(); return isOnlyFirstEnumeratorInitialized(Node) && isInitializedByLiteral(ECD) && ECD->getInitVal().isZero(); } /// Excludes bitfields because enumerators initialized with the result of a /// bitwise operator on enumeration values or any other expr that is not a /// potentially negative integer literal. /// Enumerations where it is not directly clear if they are used with /// bitmask, evident when enumerators are only initialized with (potentially /// negative) integer literals, are ignored. This is also the case when all /// enumerators are powers of two (e.g., 0, 1, 2). AST_MATCHER(EnumDecl, hasSequentialInitialValues) { const EnumDecl::enumerator_range Enumerators = Node.enumerators(); if (Enumerators.empty()) return false; const EnumConstantDecl *const FirstEnumerator = *Node.enumerator_begin(); llvm::APSInt PrevValue = FirstEnumerator->getInitVal(); if (!isInitializedByLiteral(FirstEnumerator)) return false; bool AllEnumeratorsArePowersOfTwo = true; for (const EnumConstantDecl *Enumerator : llvm::drop_begin(Enumerators)) { const llvm::APSInt NewValue = Enumerator->getInitVal(); if (NewValue != ++PrevValue) return false; if (!isInitializedByLiteral(Enumerator)) return false; PrevValue = NewValue; AllEnumeratorsArePowersOfTwo &= NewValue.isPowerOf2(); } return !AllEnumeratorsArePowersOfTwo; } std::string getName(const EnumDecl *Decl) { if (!Decl->getDeclName()) return ""; return Decl->getQualifiedNameAsString(); } } // namespace EnumInitialValueCheck::EnumInitialValueCheck(StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context), AllowExplicitZeroFirstInitialValue( Options.get("AllowExplicitZeroFirstInitialValue", true)), AllowExplicitSequentialInitialValues( Options.get("AllowExplicitSequentialInitialValues", true)) {} void EnumInitialValueCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { Options.store(Opts, "AllowExplicitZeroFirstInitialValue", AllowExplicitZeroFirstInitialValue); Options.store(Opts, "AllowExplicitSequentialInitialValues", AllowExplicitSequentialInitialValues); } void EnumInitialValueCheck::registerMatchers(MatchFinder *Finder) { Finder->addMatcher(enumDecl(isDefinition(), unless(isMacro()), unless(hasConsistentInitialValues())) .bind("inconsistent"), this); if (!AllowExplicitZeroFirstInitialValue) Finder->addMatcher( enumDecl(isDefinition(), hasZeroInitialValueForFirstEnumerator()) .bind("zero_first"), this); if (!AllowExplicitSequentialInitialValues) Finder->addMatcher(enumDecl(isDefinition(), unless(isMacro()), hasSequentialInitialValues()) .bind("sequential"), this); } void EnumInitialValueCheck::check(const MatchFinder::MatchResult &Result) { if (const auto *Enum = Result.Nodes.getNodeAs("inconsistent")) { DiagnosticBuilder Diag = diag( Enum->getBeginLoc(), "initial values in enum '%0' are not consistent, consider explicit " "initialization of all, none or only the first enumerator") << getName(Enum); for (const EnumConstantDecl *ECD : Enum->enumerators()) if (ECD->getInitExpr() == nullptr) { const SourceLocation EndLoc = Lexer::getLocForEndOfToken( ECD->getLocation(), 0, *Result.SourceManager, getLangOpts()); if (EndLoc.isMacroID()) continue; llvm::SmallString<8> Str{" = "}; ECD->getInitVal().toString(Str); Diag << FixItHint::CreateInsertion(EndLoc, Str); } return; } if (const auto *Enum = Result.Nodes.getNodeAs("zero_first")) { const EnumConstantDecl *ECD = *Enum->enumerator_begin(); const SourceLocation Loc = ECD->getLocation(); if (Loc.isInvalid() || Loc.isMacroID()) return; DiagnosticBuilder Diag = diag(Loc, "zero initial value for the first " "enumerator in '%0' can be disregarded") << getName(Enum); cleanInitialValue(Diag, ECD, *Result.SourceManager, getLangOpts()); return; } if (const auto *Enum = Result.Nodes.getNodeAs("sequential")) { DiagnosticBuilder Diag = diag(Enum->getBeginLoc(), "sequential initial value in '%0' can be ignored") << getName(Enum); for (const EnumConstantDecl *ECD : llvm::drop_begin(Enum->enumerators())) cleanInitialValue(Diag, ECD, *Result.SourceManager, getLangOpts()); return; } } } // namespace clang::tidy::readability