139 lines
4.4 KiB
C++
139 lines
4.4 KiB
C++
|
//===--- EnumSizeCheck.cpp - clang-tidy -----------------------------------===//
|
||
|
//
|
||
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||
|
// See https://llvm.org/LICENSE.txt for license information.
|
||
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||
|
//
|
||
|
//===----------------------------------------------------------------------===//
|
||
|
|
||
|
#include "EnumSizeCheck.h"
|
||
|
#include "../utils/Matchers.h"
|
||
|
#include "../utils/OptionsUtils.h"
|
||
|
#include "clang/AST/ASTContext.h"
|
||
|
#include "clang/ASTMatchers/ASTMatchFinder.h"
|
||
|
#include <algorithm>
|
||
|
#include <cinttypes>
|
||
|
#include <cstdint>
|
||
|
#include <limits>
|
||
|
#include <utility>
|
||
|
|
||
|
using namespace clang::ast_matchers;
|
||
|
|
||
|
namespace clang::tidy::performance {
|
||
|
|
||
|
namespace {
|
||
|
|
||
|
AST_MATCHER(EnumDecl, hasEnumerators) { return !Node.enumerators().empty(); }
|
||
|
|
||
|
const std::uint64_t Min8 =
|
||
|
std::imaxabs(std::numeric_limits<std::int8_t>::min());
|
||
|
const std::uint64_t Max8 = std::numeric_limits<std::int8_t>::max();
|
||
|
const std::uint64_t Min16 =
|
||
|
std::imaxabs(std::numeric_limits<std::int16_t>::min());
|
||
|
const std::uint64_t Max16 = std::numeric_limits<std::int16_t>::max();
|
||
|
const std::uint64_t Min32 =
|
||
|
std::imaxabs(std::numeric_limits<std::int32_t>::min());
|
||
|
const std::uint64_t Max32 = std::numeric_limits<std::int32_t>::max();
|
||
|
|
||
|
std::pair<const char *, std::uint32_t>
|
||
|
getNewType(std::size_t Size, std::uint64_t Min, std::uint64_t Max) noexcept {
|
||
|
if (Min) {
|
||
|
if (Min <= Min8 && Max <= Max8) {
|
||
|
return {"std::int8_t", sizeof(std::int8_t)};
|
||
|
}
|
||
|
|
||
|
if (Min <= Min16 && Max <= Max16 && Size > sizeof(std::int16_t)) {
|
||
|
return {"std::int16_t", sizeof(std::int16_t)};
|
||
|
}
|
||
|
|
||
|
if (Min <= Min32 && Max <= Max32 && Size > sizeof(std::int32_t)) {
|
||
|
return {"std::int32_t", sizeof(std::int32_t)};
|
||
|
}
|
||
|
|
||
|
return {};
|
||
|
}
|
||
|
|
||
|
if (Max) {
|
||
|
if (Max <= std::numeric_limits<std::uint8_t>::max()) {
|
||
|
return {"std::uint8_t", sizeof(std::uint8_t)};
|
||
|
}
|
||
|
|
||
|
if (Max <= std::numeric_limits<std::uint16_t>::max() &&
|
||
|
Size > sizeof(std::uint16_t)) {
|
||
|
return {"std::uint16_t", sizeof(std::uint16_t)};
|
||
|
}
|
||
|
|
||
|
if (Max <= std::numeric_limits<std::uint32_t>::max() &&
|
||
|
Size > sizeof(std::uint32_t)) {
|
||
|
return {"std::uint32_t", sizeof(std::uint32_t)};
|
||
|
}
|
||
|
|
||
|
return {};
|
||
|
}
|
||
|
|
||
|
// Zero case
|
||
|
return {"std::uint8_t", sizeof(std::uint8_t)};
|
||
|
}
|
||
|
|
||
|
} // namespace
|
||
|
|
||
|
EnumSizeCheck::EnumSizeCheck(StringRef Name, ClangTidyContext *Context)
|
||
|
: ClangTidyCheck(Name, Context),
|
||
|
EnumIgnoreList(
|
||
|
utils::options::parseStringList(Options.get("EnumIgnoreList", ""))) {}
|
||
|
|
||
|
void EnumSizeCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
|
||
|
Options.store(Opts, "EnumIgnoreList",
|
||
|
utils::options::serializeStringList(EnumIgnoreList));
|
||
|
}
|
||
|
|
||
|
bool EnumSizeCheck::isLanguageVersionSupported(
|
||
|
const LangOptions &LangOpts) const {
|
||
|
return LangOpts.CPlusPlus11;
|
||
|
}
|
||
|
|
||
|
void EnumSizeCheck::registerMatchers(MatchFinder *Finder) {
|
||
|
Finder->addMatcher(
|
||
|
enumDecl(unless(isExpansionInSystemHeader()), isDefinition(),
|
||
|
hasEnumerators(),
|
||
|
unless(matchers::matchesAnyListedName(EnumIgnoreList)))
|
||
|
.bind("e"),
|
||
|
this);
|
||
|
}
|
||
|
|
||
|
void EnumSizeCheck::check(const MatchFinder::MatchResult &Result) {
|
||
|
const auto *MatchedDecl = Result.Nodes.getNodeAs<EnumDecl>("e");
|
||
|
const QualType BaseType = MatchedDecl->getIntegerType().getCanonicalType();
|
||
|
if (!BaseType->isIntegerType())
|
||
|
return;
|
||
|
|
||
|
const std::uint32_t Size = Result.Context->getTypeSize(BaseType) / 8U;
|
||
|
if (1U == Size)
|
||
|
return;
|
||
|
|
||
|
std::uint64_t MinV = 0U;
|
||
|
std::uint64_t MaxV = 0U;
|
||
|
|
||
|
for (const auto &It : MatchedDecl->enumerators()) {
|
||
|
const llvm::APSInt &InitVal = It->getInitVal();
|
||
|
if ((InitVal.isUnsigned() || InitVal.isNonNegative())) {
|
||
|
MaxV = std::max<std::uint64_t>(MaxV, InitVal.getZExtValue());
|
||
|
} else {
|
||
|
MinV = std::max<std::uint64_t>(MinV, InitVal.abs().getZExtValue());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
auto NewType = getNewType(Size, MinV, MaxV);
|
||
|
if (!NewType.first || Size <= NewType.second)
|
||
|
return;
|
||
|
|
||
|
diag(MatchedDecl->getLocation(),
|
||
|
"enum %0 uses a larger base type (%1, size: %2 %select{byte|bytes}5) "
|
||
|
"than necessary for its value set, consider using '%3' (%4 "
|
||
|
"%select{byte|bytes}6) as the base type to reduce its size")
|
||
|
<< MatchedDecl << MatchedDecl->getIntegerType() << Size << NewType.first
|
||
|
<< NewType.second << (Size > 1U) << (NewType.second > 1U);
|
||
|
}
|
||
|
|
||
|
} // namespace clang::tidy::performance
|