xref: /llvm-project/clang-tools-extra/clang-tidy/performance/EnumSizeCheck.cpp (revision 3716b5b4bac6ab41291b6558ad0444cbcca04aa3)
13397dae6SPiotr Zegar //===--- EnumSizeCheck.cpp - clang-tidy -----------------------------------===//
23397dae6SPiotr Zegar //
33397dae6SPiotr Zegar // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
43397dae6SPiotr Zegar // See https://llvm.org/LICENSE.txt for license information.
53397dae6SPiotr Zegar // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
63397dae6SPiotr Zegar //
73397dae6SPiotr Zegar //===----------------------------------------------------------------------===//
83397dae6SPiotr Zegar 
93397dae6SPiotr Zegar #include "EnumSizeCheck.h"
103397dae6SPiotr Zegar #include "../utils/Matchers.h"
113397dae6SPiotr Zegar #include "../utils/OptionsUtils.h"
123397dae6SPiotr Zegar #include "clang/AST/ASTContext.h"
133397dae6SPiotr Zegar #include "clang/ASTMatchers/ASTMatchFinder.h"
143397dae6SPiotr Zegar #include <algorithm>
153397dae6SPiotr Zegar #include <cinttypes>
163397dae6SPiotr Zegar #include <cstdint>
173397dae6SPiotr Zegar #include <limits>
183397dae6SPiotr Zegar #include <utility>
193397dae6SPiotr Zegar 
203397dae6SPiotr Zegar using namespace clang::ast_matchers;
213397dae6SPiotr Zegar 
223397dae6SPiotr Zegar namespace clang::tidy::performance {
233397dae6SPiotr Zegar 
243397dae6SPiotr Zegar namespace {
253397dae6SPiotr Zegar 
AST_MATCHER(EnumDecl,hasEnumerators)26*3716b5b4SPiotr Zegar AST_MATCHER(EnumDecl, hasEnumerators) { return !Node.enumerators().empty(); }
27*3716b5b4SPiotr Zegar 
283397dae6SPiotr Zegar const std::uint64_t Min8 =
293397dae6SPiotr Zegar     std::imaxabs(std::numeric_limits<std::int8_t>::min());
303397dae6SPiotr Zegar const std::uint64_t Max8 = std::numeric_limits<std::int8_t>::max();
313397dae6SPiotr Zegar const std::uint64_t Min16 =
323397dae6SPiotr Zegar     std::imaxabs(std::numeric_limits<std::int16_t>::min());
333397dae6SPiotr Zegar const std::uint64_t Max16 = std::numeric_limits<std::int16_t>::max();
343397dae6SPiotr Zegar const std::uint64_t Min32 =
353397dae6SPiotr Zegar     std::imaxabs(std::numeric_limits<std::int32_t>::min());
363397dae6SPiotr Zegar const std::uint64_t Max32 = std::numeric_limits<std::int32_t>::max();
373397dae6SPiotr Zegar 
383397dae6SPiotr Zegar std::pair<const char *, std::uint32_t>
getNewType(std::size_t Size,std::uint64_t Min,std::uint64_t Max)393397dae6SPiotr Zegar getNewType(std::size_t Size, std::uint64_t Min, std::uint64_t Max) noexcept {
403397dae6SPiotr Zegar   if (Min) {
413397dae6SPiotr Zegar     if (Min <= Min8 && Max <= Max8) {
423397dae6SPiotr Zegar       return {"std::int8_t", sizeof(std::int8_t)};
433397dae6SPiotr Zegar     }
443397dae6SPiotr Zegar 
453397dae6SPiotr Zegar     if (Min <= Min16 && Max <= Max16 && Size > sizeof(std::int16_t)) {
463397dae6SPiotr Zegar       return {"std::int16_t", sizeof(std::int16_t)};
473397dae6SPiotr Zegar     }
483397dae6SPiotr Zegar 
493397dae6SPiotr Zegar     if (Min <= Min32 && Max <= Max32 && Size > sizeof(std::int32_t)) {
503397dae6SPiotr Zegar       return {"std::int32_t", sizeof(std::int32_t)};
513397dae6SPiotr Zegar     }
523397dae6SPiotr Zegar 
533397dae6SPiotr Zegar     return {};
543397dae6SPiotr Zegar   }
553397dae6SPiotr Zegar 
563397dae6SPiotr Zegar   if (Max) {
573397dae6SPiotr Zegar     if (Max <= std::numeric_limits<std::uint8_t>::max()) {
583397dae6SPiotr Zegar       return {"std::uint8_t", sizeof(std::uint8_t)};
593397dae6SPiotr Zegar     }
603397dae6SPiotr Zegar 
613397dae6SPiotr Zegar     if (Max <= std::numeric_limits<std::uint16_t>::max() &&
623397dae6SPiotr Zegar         Size > sizeof(std::uint16_t)) {
633397dae6SPiotr Zegar       return {"std::uint16_t", sizeof(std::uint16_t)};
643397dae6SPiotr Zegar     }
653397dae6SPiotr Zegar 
663397dae6SPiotr Zegar     if (Max <= std::numeric_limits<std::uint32_t>::max() &&
673397dae6SPiotr Zegar         Size > sizeof(std::uint32_t)) {
683397dae6SPiotr Zegar       return {"std::uint32_t", sizeof(std::uint32_t)};
693397dae6SPiotr Zegar     }
703397dae6SPiotr Zegar 
713397dae6SPiotr Zegar     return {};
723397dae6SPiotr Zegar   }
733397dae6SPiotr Zegar 
743397dae6SPiotr Zegar   // Zero case
753397dae6SPiotr Zegar   return {"std::uint8_t", sizeof(std::uint8_t)};
763397dae6SPiotr Zegar }
773397dae6SPiotr Zegar 
783397dae6SPiotr Zegar } // namespace
793397dae6SPiotr Zegar 
EnumSizeCheck(StringRef Name,ClangTidyContext * Context)803397dae6SPiotr Zegar EnumSizeCheck::EnumSizeCheck(StringRef Name, ClangTidyContext *Context)
813397dae6SPiotr Zegar     : ClangTidyCheck(Name, Context),
823397dae6SPiotr Zegar       EnumIgnoreList(
833397dae6SPiotr Zegar           utils::options::parseStringList(Options.get("EnumIgnoreList", ""))) {}
843397dae6SPiotr Zegar 
storeOptions(ClangTidyOptions::OptionMap & Opts)853397dae6SPiotr Zegar void EnumSizeCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
863397dae6SPiotr Zegar   Options.store(Opts, "EnumIgnoreList",
873397dae6SPiotr Zegar                 utils::options::serializeStringList(EnumIgnoreList));
883397dae6SPiotr Zegar }
893397dae6SPiotr Zegar 
isLanguageVersionSupported(const LangOptions & LangOpts) const903397dae6SPiotr Zegar bool EnumSizeCheck::isLanguageVersionSupported(
913397dae6SPiotr Zegar     const LangOptions &LangOpts) const {
923397dae6SPiotr Zegar   return LangOpts.CPlusPlus11;
933397dae6SPiotr Zegar }
943397dae6SPiotr Zegar 
registerMatchers(MatchFinder * Finder)953397dae6SPiotr Zegar void EnumSizeCheck::registerMatchers(MatchFinder *Finder) {
963397dae6SPiotr Zegar   Finder->addMatcher(
973397dae6SPiotr Zegar       enumDecl(unless(isExpansionInSystemHeader()), isDefinition(),
98*3716b5b4SPiotr Zegar                hasEnumerators(),
993397dae6SPiotr Zegar                unless(matchers::matchesAnyListedName(EnumIgnoreList)))
1003397dae6SPiotr Zegar           .bind("e"),
1013397dae6SPiotr Zegar       this);
1023397dae6SPiotr Zegar }
1033397dae6SPiotr Zegar 
check(const MatchFinder::MatchResult & Result)1043397dae6SPiotr Zegar void EnumSizeCheck::check(const MatchFinder::MatchResult &Result) {
1053397dae6SPiotr Zegar   const auto *MatchedDecl = Result.Nodes.getNodeAs<EnumDecl>("e");
1063397dae6SPiotr Zegar   const QualType BaseType = MatchedDecl->getIntegerType().getCanonicalType();
1073397dae6SPiotr Zegar   if (!BaseType->isIntegerType())
1083397dae6SPiotr Zegar     return;
1093397dae6SPiotr Zegar 
1103397dae6SPiotr Zegar   const std::uint32_t Size = Result.Context->getTypeSize(BaseType) / 8U;
1113397dae6SPiotr Zegar   if (1U == Size)
1123397dae6SPiotr Zegar     return;
1133397dae6SPiotr Zegar 
1143397dae6SPiotr Zegar   std::uint64_t MinV = 0U;
1153397dae6SPiotr Zegar   std::uint64_t MaxV = 0U;
1163397dae6SPiotr Zegar 
1173397dae6SPiotr Zegar   for (const auto &It : MatchedDecl->enumerators()) {
1183397dae6SPiotr Zegar     const llvm::APSInt &InitVal = It->getInitVal();
1193397dae6SPiotr Zegar     if ((InitVal.isUnsigned() || InitVal.isNonNegative())) {
1203397dae6SPiotr Zegar       MaxV = std::max<std::uint64_t>(MaxV, InitVal.getZExtValue());
1213397dae6SPiotr Zegar     } else {
1223397dae6SPiotr Zegar       MinV = std::max<std::uint64_t>(MinV, InitVal.abs().getZExtValue());
1233397dae6SPiotr Zegar     }
1243397dae6SPiotr Zegar   }
1253397dae6SPiotr Zegar 
1263397dae6SPiotr Zegar   auto NewType = getNewType(Size, MinV, MaxV);
1273397dae6SPiotr Zegar   if (!NewType.first || Size <= NewType.second)
1283397dae6SPiotr Zegar     return;
1293397dae6SPiotr Zegar 
1303397dae6SPiotr Zegar   diag(MatchedDecl->getLocation(),
1313397dae6SPiotr Zegar        "enum %0 uses a larger base type (%1, size: %2 %select{byte|bytes}5) "
1323397dae6SPiotr Zegar        "than necessary for its value set, consider using '%3' (%4 "
1333397dae6SPiotr Zegar        "%select{byte|bytes}6) as the base type to reduce its size")
1343397dae6SPiotr Zegar       << MatchedDecl << MatchedDecl->getIntegerType() << Size << NewType.first
1353397dae6SPiotr Zegar       << NewType.second << (Size > 1U) << (NewType.second > 1U);
1363397dae6SPiotr Zegar }
1373397dae6SPiotr Zegar 
1383397dae6SPiotr Zegar } // namespace clang::tidy::performance
139