1 //===--- EnumSizeCheck.cpp - clang-tidy -----------------------------------===//
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
7 //===----------------------------------------------------------------------===//
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"
20 using namespace clang::ast_matchers
;
22 namespace clang::tidy::performance
{
26 AST_MATCHER(EnumDecl
, hasEnumerators
) { return !Node
.enumerators().empty(); }
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();
38 std::pair
<const char *, std::uint32_t>
39 getNewType(std::size_t Size
, std::uint64_t Min
, std::uint64_t Max
) noexcept
{
41 if (Min
<= Min8
&& Max
<= Max8
) {
42 return {"std::int8_t", sizeof(std::int8_t)};
45 if (Min
<= Min16
&& Max
<= Max16
&& Size
> sizeof(std::int16_t)) {
46 return {"std::int16_t", sizeof(std::int16_t)};
49 if (Min
<= Min32
&& Max
<= Max32
&& Size
> sizeof(std::int32_t)) {
50 return {"std::int32_t", sizeof(std::int32_t)};
57 if (Max
<= std::numeric_limits
<std::uint8_t>::max()) {
58 return {"std::uint8_t", sizeof(std::uint8_t)};
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)};
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)};
75 return {"std::uint8_t", sizeof(std::uint8_t)};
80 EnumSizeCheck::EnumSizeCheck(StringRef Name
, ClangTidyContext
*Context
)
81 : ClangTidyCheck(Name
, Context
),
83 utils::options::parseStringList(Options
.get("EnumIgnoreList", ""))) {}
85 void EnumSizeCheck::storeOptions(ClangTidyOptions::OptionMap
&Opts
) {
86 Options
.store(Opts
, "EnumIgnoreList",
87 utils::options::serializeStringList(EnumIgnoreList
));
90 bool EnumSizeCheck::isLanguageVersionSupported(
91 const LangOptions
&LangOpts
) const {
92 return LangOpts
.CPlusPlus11
;
95 void EnumSizeCheck::registerMatchers(MatchFinder
*Finder
) {
97 enumDecl(unless(isExpansionInSystemHeader()), isDefinition(),
99 unless(matchers::matchesAnyListedName(EnumIgnoreList
)))
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())
110 const std::uint32_t Size
= Result
.Context
->getTypeSize(BaseType
) / 8U;
114 std::uint64_t MinV
= 0U;
115 std::uint64_t MaxV
= 0U;
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());
122 MinV
= std::max
<std::uint64_t>(MinV
, InitVal
.abs().getZExtValue());
126 auto NewType
= getNewType(Size
, MinV
, MaxV
);
127 if (!NewType
.first
|| Size
<= NewType
.second
)
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);
138 } // namespace clang::tidy::performance