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
20using namespace clang::ast_matchers;
21
22namespace clang::tidy::performance {
23
24namespace {
25
26AST_MATCHER(EnumDecl, hasEnumerators) { return !Node.enumerators().empty(); }
27
28const std::uint64_t Min8 =
29 std::imaxabs(n: std::numeric_limits<std::int8_t>::min());
30const std::uint64_t Max8 = std::numeric_limits<std::int8_t>::max();
31const std::uint64_t Min16 =
32 std::imaxabs(n: std::numeric_limits<std::int16_t>::min());
33const std::uint64_t Max16 = std::numeric_limits<std::int16_t>::max();
34const std::uint64_t Min32 =
35 std::imaxabs(n: std::numeric_limits<std::int32_t>::min());
36const std::uint64_t Max32 = std::numeric_limits<std::int32_t>::max();
37
38std::pair<const char *, std::uint32_t>
39getNewType(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
80EnumSizeCheck::EnumSizeCheck(StringRef Name, ClangTidyContext *Context)
81 : ClangTidyCheck(Name, Context),
82 EnumIgnoreList(
83 utils::options::parseStringList(Option: Options.get(LocalName: "EnumIgnoreList", Default: ""))) {}
84
85void EnumSizeCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
86 Options.store(Options&: Opts, LocalName: "EnumIgnoreList",
87 Value: utils::options::serializeStringList(Strings: EnumIgnoreList));
88}
89
90bool EnumSizeCheck::isLanguageVersionSupported(
91 const LangOptions &LangOpts) const {
92 return LangOpts.CPlusPlus11;
93}
94
95void EnumSizeCheck::registerMatchers(MatchFinder *Finder) {
96 Finder->addMatcher(
97 NodeMatch: enumDecl(unless(isExpansionInSystemHeader()), isDefinition(),
98 hasEnumerators(),
99 unless(matchers::matchesAnyListedName(NameList: EnumIgnoreList)))
100 .bind(ID: "e"),
101 Action: this);
102}
103
104void EnumSizeCheck::check(const MatchFinder::MatchResult &Result) {
105 const auto *MatchedDecl = Result.Nodes.getNodeAs<EnumDecl>(ID: "e");
106 const QualType BaseType = MatchedDecl->getIntegerType().getCanonicalType();
107 if (!BaseType->isIntegerType())
108 return;
109
110 const std::uint32_t Size = Result.Context->getTypeSize(T: 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>(a: MaxV, b: InitVal.getZExtValue());
121 } else {
122 MinV = std::max<std::uint64_t>(a: MinV, b: InitVal.abs().getZExtValue());
123 }
124 }
125
126 auto NewType = getNewType(Size, Min: MinV, Max: 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

source code of clang-tools-extra/clang-tidy/performance/EnumSizeCheck.cpp