138 lines · cpp
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-exception6//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(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} // namespace39static 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 case76 return {"std::uint8_t", sizeof(std::uint8_t)};77}78 79EnumSizeCheck::EnumSizeCheck(StringRef Name, ClangTidyContext *Context)80 : ClangTidyCheck(Name, Context),81 EnumIgnoreList(82 utils::options::parseStringList(Options.get("EnumIgnoreList", ""))) {}83 84void EnumSizeCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {85 Options.store(Opts, "EnumIgnoreList",86 utils::options::serializeStringList(EnumIgnoreList));87}88 89bool EnumSizeCheck::isLanguageVersionSupported(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.first134 << NewType.second << (Size > 1U) << (NewType.second > 1U);135}136 137} // namespace clang::tidy::performance138