clang-tools 22.0.0git
EnumSizeCheck.cpp
Go to the documentation of this file.
1//===----------------------------------------------------------------------===//
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"
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
23
24namespace {
25
26AST_MATCHER(EnumDecl, hasEnumerators) { return !Node.enumerators().empty(); }
27
28const std::uint64_t Min8 =
29 std::imaxabs(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(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(std::numeric_limits<std::int32_t>::min());
36const std::uint64_t Max32 = std::numeric_limits<std::int32_t>::max();
37
38} // namespace
39static std::pair<const char *, std::uint32_t>
40getNewType(std::size_t Size, std::uint64_t Min, std::uint64_t Max) noexcept {
41 if (Min) {
42 if (Min <= Min8 && Max <= Max8) {
43 return {"std::int8_t", sizeof(std::int8_t)};
44 }
45
46 if (Min <= Min16 && Max <= Max16 && Size > sizeof(std::int16_t)) {
47 return {"std::int16_t", sizeof(std::int16_t)};
48 }
49
50 if (Min <= Min32 && Max <= Max32 && Size > sizeof(std::int32_t)) {
51 return {"std::int32_t", sizeof(std::int32_t)};
52 }
53
54 return {};
55 }
56
57 if (Max) {
58 if (Max <= std::numeric_limits<std::uint8_t>::max()) {
59 return {"std::uint8_t", sizeof(std::uint8_t)};
60 }
61
62 if (Max <= std::numeric_limits<std::uint16_t>::max() &&
63 Size > sizeof(std::uint16_t)) {
64 return {"std::uint16_t", sizeof(std::uint16_t)};
65 }
66
67 if (Max <= std::numeric_limits<std::uint32_t>::max() &&
68 Size > sizeof(std::uint32_t)) {
69 return {"std::uint32_t", sizeof(std::uint32_t)};
70 }
71
72 return {};
73 }
74
75 // Zero case
76 return {"std::uint8_t", sizeof(std::uint8_t)};
77}
78
80 : ClangTidyCheck(Name, Context),
81 EnumIgnoreList(
82 utils::options::parseStringList(Options.get("EnumIgnoreList", ""))) {}
83
85 Options.store(Opts, "EnumIgnoreList",
87}
88
90 const LangOptions &LangOpts) const {
91 return LangOpts.CPlusPlus11;
92}
93
94void EnumSizeCheck::registerMatchers(MatchFinder *Finder) {
95 Finder->addMatcher(
96 enumDecl(unless(isExpansionInSystemHeader()), isDefinition(),
97 hasEnumerators(),
98 unless(matchers::matchesAnyListedName(EnumIgnoreList)))
99 .bind("e"),
100 this);
101}
102
103void EnumSizeCheck::check(const MatchFinder::MatchResult &Result) {
104 const auto *MatchedDecl = Result.Nodes.getNodeAs<EnumDecl>("e");
105 const QualType BaseType = MatchedDecl->getIntegerType().getCanonicalType();
106 if (!BaseType->isIntegerType())
107 return;
108
109 const std::uint32_t Size = Result.Context->getTypeSize(BaseType) / 8U;
110 if (1U == Size)
111 return;
112
113 std::uint64_t MinV = 0U;
114 std::uint64_t MaxV = 0U;
115
116 for (const auto &It : MatchedDecl->enumerators()) {
117 const llvm::APSInt &InitVal = It->getInitVal();
118 if ((InitVal.isUnsigned() || InitVal.isNonNegative())) {
119 MaxV = std::max<std::uint64_t>(MaxV, InitVal.getZExtValue());
120 } else {
121 MinV = std::max<std::uint64_t>(MinV, InitVal.abs().getZExtValue());
122 }
123 }
124
125 auto NewType = getNewType(Size, MinV, MaxV);
126 if (!NewType.first || Size <= NewType.second)
127 return;
128
129 diag(MatchedDecl->getLocation(),
130 "enum %0 uses a larger base type (%1, size: %2 %select{byte|bytes}5) "
131 "than necessary for its value set, consider using '%3' (%4 "
132 "%select{byte|bytes}6) as the base type to reduce its size")
133 << MatchedDecl << MatchedDecl->getIntegerType() << Size << NewType.first
134 << NewType.second << (Size > 1U) << (NewType.second > 1U);
135}
136
137} // namespace clang::tidy::performance
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
void registerMatchers(ast_matchers::MatchFinder *Finder) override
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
EnumSizeCheck(StringRef Name, ClangTidyContext *Context)
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override
AST_MATCHER(BinaryOperator, isRelationalOperator)
inline ::clang::ast_matchers::internal::Matcher< NamedDecl > matchesAnyListedName(llvm::ArrayRef< StringRef > NameList)
static std::pair< const char *, std::uint32_t > getNewType(std::size_t Size, std::uint64_t Min, std::uint64_t Max) noexcept
std::string serializeStringList(ArrayRef< StringRef > Strings)
Serialize a sequence of names that can be parsed by parseStringList.
llvm::StringMap< ClangTidyValue > OptionMap