clang-tools 23.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
28AST_MATCHER(EnumDecl, isExternC) {
29 return Node.getDeclContext()->isExternCContext();
30}
31
32AST_MATCHER_P(EnumDecl, hasTypedefNameForAnonDecl,
33 ast_matchers::internal::Matcher<NamedDecl>, InnerMatcher) {
34 if (const TypedefNameDecl *TD = Node.getTypedefNameForAnonDecl())
35 return InnerMatcher.matches(*TD, Finder, Builder);
36 return false;
37}
38
39const std::uint64_t Min8 =
40 std::imaxabs(std::numeric_limits<std::int8_t>::min());
41const std::uint64_t Max8 = std::numeric_limits<std::int8_t>::max();
42const std::uint64_t Min16 =
43 std::imaxabs(std::numeric_limits<std::int16_t>::min());
44const std::uint64_t Max16 = std::numeric_limits<std::int16_t>::max();
45const std::uint64_t Min32 =
46 std::imaxabs(std::numeric_limits<std::int32_t>::min());
47const std::uint64_t Max32 = std::numeric_limits<std::int32_t>::max();
48
49} // namespace
50static std::pair<const char *, std::uint32_t>
51getNewType(std::size_t Size, std::uint64_t Min, std::uint64_t Max) noexcept {
52 if (Min) {
53 if (Min <= Min8 && Max <= Max8)
54 return {"std::int8_t", sizeof(std::int8_t)};
55
56 if (Min <= Min16 && Max <= Max16 && Size > sizeof(std::int16_t))
57 return {"std::int16_t", sizeof(std::int16_t)};
58
59 if (Min <= Min32 && Max <= Max32 && Size > sizeof(std::int32_t))
60 return {"std::int32_t", sizeof(std::int32_t)};
61
62 return {};
63 }
64
65 if (Max) {
66 if (Max <= std::numeric_limits<std::uint8_t>::max())
67 return {"std::uint8_t", sizeof(std::uint8_t)};
68
69 if (Max <= std::numeric_limits<std::uint16_t>::max() &&
70 Size > sizeof(std::uint16_t)) {
71 return {"std::uint16_t", sizeof(std::uint16_t)};
72 }
73
74 if (Max <= std::numeric_limits<std::uint32_t>::max() &&
75 Size > sizeof(std::uint32_t)) {
76 return {"std::uint32_t", sizeof(std::uint32_t)};
77 }
78
79 return {};
80 }
81
82 // Zero case
83 return {"std::uint8_t", sizeof(std::uint8_t)};
84}
85
87 : ClangTidyCheck(Name, Context),
88 EnumIgnoreList(
89 utils::options::parseStringList(Options.get("EnumIgnoreList", ""))) {}
90
92 Options.store(Opts, "EnumIgnoreList",
94}
95
97 const LangOptions &LangOpts) const {
98 return LangOpts.CPlusPlus11;
99}
100
101void EnumSizeCheck::registerMatchers(MatchFinder *Finder) {
102 Finder->addMatcher(
103 enumDecl(unless(isExpansionInSystemHeader()), isDefinition(),
104 hasEnumerators(), unless(isExternC()),
105 unless(anyOf(
107 hasTypedefNameForAnonDecl(
108 matchers::matchesAnyListedRegexName(EnumIgnoreList)))))
109 .bind("e"),
110 this);
111}
112
113void EnumSizeCheck::check(const MatchFinder::MatchResult &Result) {
114 const auto *MatchedDecl = Result.Nodes.getNodeAs<EnumDecl>("e");
115 const QualType BaseType = MatchedDecl->getIntegerType().getCanonicalType();
116 if (!BaseType->isIntegerType())
117 return;
118
119 const std::uint32_t Size = Result.Context->getTypeSize(BaseType) / 8U;
120 if (1U == Size)
121 return;
122
123 std::uint64_t MinV = 0U;
124 std::uint64_t MaxV = 0U;
125
126 for (const auto &It : MatchedDecl->enumerators()) {
127 const llvm::APSInt &InitVal = It->getInitVal();
128 if ((InitVal.isUnsigned() || InitVal.isNonNegative()))
129 MaxV = std::max<std::uint64_t>(MaxV, InitVal.getZExtValue());
130 else
131 MinV = std::max<std::uint64_t>(MinV, InitVal.abs().getZExtValue());
132 }
133
134 auto NewType = getNewType(Size, MinV, MaxV);
135 if (!NewType.first || Size <= NewType.second)
136 return;
137
138 diag(MatchedDecl->getLocation(),
139 "enum %0 uses a larger base type (%1, size: %2 %select{byte|bytes}5) "
140 "than necessary for its value set, consider using '%3' (%4 "
141 "%select{byte|bytes}6) as the base type to reduce its size")
142 << MatchedDecl << MatchedDecl->getIntegerType() << Size << NewType.first
143 << NewType.second << (Size > 1U) << (NewType.second > 1U);
144}
145
146} // 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_P(Stmt, isStatementIdenticalToBoundNode, std::string, ID)
AST_MATCHER(BinaryOperator, isRelationalOperator)
inline ::clang::ast_matchers::internal::Matcher< NamedDecl > matchesAnyListedRegexName(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