xref: /llvm-project/clang-tools-extra/clang-tidy/performance/EnumSizeCheck.cpp (revision 3716b5b4bac6ab41291b6558ad0444cbcca04aa3)
1 //===--- EnumSizeCheck.cpp - clang-tidy -----------------------------------===//
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-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #include "EnumSizeCheck.h"
10 #include "../utils/Matchers.h"
11 #include "../utils/OptionsUtils.h"
12 #include "clang/AST/ASTContext.h"
13 #include "clang/ASTMatchers/ASTMatchFinder.h"
14 #include <algorithm>
15 #include <cinttypes>
16 #include <cstdint>
17 #include <limits>
18 #include <utility>
19 
20 using namespace clang::ast_matchers;
21 
22 namespace clang::tidy::performance {
23 
24 namespace {
25 
AST_MATCHER(EnumDecl,hasEnumerators)26 AST_MATCHER(EnumDecl, hasEnumerators) { return !Node.enumerators().empty(); }
27 
28 const std::uint64_t Min8 =
29     std::imaxabs(std::numeric_limits<std::int8_t>::min());
30 const std::uint64_t Max8 = std::numeric_limits<std::int8_t>::max();
31 const std::uint64_t Min16 =
32     std::imaxabs(std::numeric_limits<std::int16_t>::min());
33 const std::uint64_t Max16 = std::numeric_limits<std::int16_t>::max();
34 const std::uint64_t Min32 =
35     std::imaxabs(std::numeric_limits<std::int32_t>::min());
36 const std::uint64_t Max32 = std::numeric_limits<std::int32_t>::max();
37 
38 std::pair<const char *, std::uint32_t>
getNewType(std::size_t Size,std::uint64_t Min,std::uint64_t Max)39 getNewType(std::size_t Size, std::uint64_t Min, std::uint64_t Max) noexcept {
40   if (Min) {
41     if (Min <= Min8 && Max <= Max8) {
42       return {"std::int8_t", sizeof(std::int8_t)};
43     }
44 
45     if (Min <= Min16 && Max <= Max16 && Size > sizeof(std::int16_t)) {
46       return {"std::int16_t", sizeof(std::int16_t)};
47     }
48 
49     if (Min <= Min32 && Max <= Max32 && Size > sizeof(std::int32_t)) {
50       return {"std::int32_t", sizeof(std::int32_t)};
51     }
52 
53     return {};
54   }
55 
56   if (Max) {
57     if (Max <= std::numeric_limits<std::uint8_t>::max()) {
58       return {"std::uint8_t", sizeof(std::uint8_t)};
59     }
60 
61     if (Max <= std::numeric_limits<std::uint16_t>::max() &&
62         Size > sizeof(std::uint16_t)) {
63       return {"std::uint16_t", sizeof(std::uint16_t)};
64     }
65 
66     if (Max <= std::numeric_limits<std::uint32_t>::max() &&
67         Size > sizeof(std::uint32_t)) {
68       return {"std::uint32_t", sizeof(std::uint32_t)};
69     }
70 
71     return {};
72   }
73 
74   // Zero case
75   return {"std::uint8_t", sizeof(std::uint8_t)};
76 }
77 
78 } // namespace
79 
EnumSizeCheck(StringRef Name,ClangTidyContext * Context)80 EnumSizeCheck::EnumSizeCheck(StringRef Name, ClangTidyContext *Context)
81     : ClangTidyCheck(Name, Context),
82       EnumIgnoreList(
83           utils::options::parseStringList(Options.get("EnumIgnoreList", ""))) {}
84 
storeOptions(ClangTidyOptions::OptionMap & Opts)85 void EnumSizeCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
86   Options.store(Opts, "EnumIgnoreList",
87                 utils::options::serializeStringList(EnumIgnoreList));
88 }
89 
isLanguageVersionSupported(const LangOptions & LangOpts) const90 bool EnumSizeCheck::isLanguageVersionSupported(
91     const LangOptions &LangOpts) const {
92   return LangOpts.CPlusPlus11;
93 }
94 
registerMatchers(MatchFinder * Finder)95 void EnumSizeCheck::registerMatchers(MatchFinder *Finder) {
96   Finder->addMatcher(
97       enumDecl(unless(isExpansionInSystemHeader()), isDefinition(),
98                hasEnumerators(),
99                unless(matchers::matchesAnyListedName(EnumIgnoreList)))
100           .bind("e"),
101       this);
102 }
103 
check(const MatchFinder::MatchResult & Result)104 void EnumSizeCheck::check(const MatchFinder::MatchResult &Result) {
105   const auto *MatchedDecl = Result.Nodes.getNodeAs<EnumDecl>("e");
106   const QualType BaseType = MatchedDecl->getIntegerType().getCanonicalType();
107   if (!BaseType->isIntegerType())
108     return;
109 
110   const std::uint32_t Size = Result.Context->getTypeSize(BaseType) / 8U;
111   if (1U == Size)
112     return;
113 
114   std::uint64_t MinV = 0U;
115   std::uint64_t MaxV = 0U;
116 
117   for (const auto &It : MatchedDecl->enumerators()) {
118     const llvm::APSInt &InitVal = It->getInitVal();
119     if ((InitVal.isUnsigned() || InitVal.isNonNegative())) {
120       MaxV = std::max<std::uint64_t>(MaxV, InitVal.getZExtValue());
121     } else {
122       MinV = std::max<std::uint64_t>(MinV, InitVal.abs().getZExtValue());
123     }
124   }
125 
126   auto NewType = getNewType(Size, MinV, MaxV);
127   if (!NewType.first || Size <= NewType.second)
128     return;
129 
130   diag(MatchedDecl->getLocation(),
131        "enum %0 uses a larger base type (%1, size: %2 %select{byte|bytes}5) "
132        "than necessary for its value set, consider using '%3' (%4 "
133        "%select{byte|bytes}6) as the base type to reduce its size")
134       << MatchedDecl << MatchedDecl->getIntegerType() << Size << NewType.first
135       << NewType.second << (Size > 1U) << (NewType.second > 1U);
136 }
137 
138 } // namespace clang::tidy::performance
139