196 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 "TaggedUnionMemberCountCheck.h"10#include "../utils/OptionsUtils.h"11#include "clang/ASTMatchers/ASTMatchFinder.h"12#include "llvm/ADT/STLExtras.h"13#include "llvm/ADT/SmallSet.h"14 15using namespace clang::ast_matchers;16 17namespace clang::tidy::bugprone {18 19static constexpr llvm::StringLiteral StrictModeOptionName = "StrictMode";20static constexpr llvm::StringLiteral EnableCountingEnumHeuristicOptionName =21 "EnableCountingEnumHeuristic";22static constexpr llvm::StringLiteral CountingEnumPrefixesOptionName =23 "CountingEnumPrefixes";24static constexpr llvm::StringLiteral CountingEnumSuffixesOptionName =25 "CountingEnumSuffixes";26 27static constexpr bool StrictModeOptionDefaultValue = false;28static constexpr bool EnableCountingEnumHeuristicOptionDefaultValue = true;29static constexpr llvm::StringLiteral CountingEnumPrefixesOptionDefaultValue =30 "";31static constexpr llvm::StringLiteral CountingEnumSuffixesOptionDefaultValue =32 "count";33 34static constexpr llvm::StringLiteral RootMatchBindName = "root";35static constexpr llvm::StringLiteral UnionMatchBindName = "union";36static constexpr llvm::StringLiteral TagMatchBindName = "tags";37 38namespace {39 40AST_MATCHER_P2(RecordDecl, fieldCountOfKindIsOne,41 ast_matchers::internal::Matcher<FieldDecl>, InnerMatcher,42 StringRef, BindName) {43 // BoundNodesTreeBuilder resets itself when a match occurs.44 // So to avoid losing previously saved binds, a temporary instance45 // is used for matching.46 //47 // For precedence, see commit: 5b07de1a5faf4a22ae6fd982b877c5e7e3a7655948 clang::ast_matchers::internal::BoundNodesTreeBuilder TempBuilder;49 50 const FieldDecl *FirstMatch = nullptr;51 for (const FieldDecl *Field : Node.fields()) {52 if (InnerMatcher.matches(*Field, Finder, &TempBuilder)) {53 if (FirstMatch) {54 return false;55 }56 FirstMatch = Field;57 }58 }59 60 if (FirstMatch) {61 Builder->setBinding(BindName, clang::DynTypedNode::create(*FirstMatch));62 return true;63 }64 return false;65}66 67} // namespace68 69TaggedUnionMemberCountCheck::TaggedUnionMemberCountCheck(70 StringRef Name, ClangTidyContext *Context)71 : ClangTidyCheck(Name, Context),72 StrictMode(73 Options.get(StrictModeOptionName, StrictModeOptionDefaultValue)),74 EnableCountingEnumHeuristic(75 Options.get(EnableCountingEnumHeuristicOptionName,76 EnableCountingEnumHeuristicOptionDefaultValue)),77 CountingEnumPrefixes(utils::options::parseStringList(78 Options.get(CountingEnumPrefixesOptionName,79 CountingEnumPrefixesOptionDefaultValue))),80 CountingEnumSuffixes(utils::options::parseStringList(81 Options.get(CountingEnumSuffixesOptionName,82 CountingEnumSuffixesOptionDefaultValue))) {83 if (!EnableCountingEnumHeuristic) {84 if (Options.get(CountingEnumPrefixesOptionName))85 configurationDiag("%0: Counting enum heuristic is disabled but "86 "%1 is set")87 << Name << CountingEnumPrefixesOptionName;88 if (Options.get(CountingEnumSuffixesOptionName))89 configurationDiag("%0: Counting enum heuristic is disabled but "90 "%1 is set")91 << Name << CountingEnumSuffixesOptionName;92 }93}94 95void TaggedUnionMemberCountCheck::storeOptions(96 ClangTidyOptions::OptionMap &Opts) {97 Options.store(Opts, StrictModeOptionName, StrictMode);98 Options.store(Opts, EnableCountingEnumHeuristicOptionName,99 EnableCountingEnumHeuristic);100 Options.store(Opts, CountingEnumPrefixesOptionName,101 utils::options::serializeStringList(CountingEnumPrefixes));102 Options.store(Opts, CountingEnumSuffixesOptionName,103 utils::options::serializeStringList(CountingEnumSuffixes));104}105 106void TaggedUnionMemberCountCheck::registerMatchers(MatchFinder *Finder) {107 auto NotFromSystemHeaderOrStdNamespace =108 unless(anyOf(isExpansionInSystemHeader(), isInStdNamespace()));109 110 auto UnionField =111 fieldDecl(hasType(qualType(hasCanonicalType(recordType(hasDeclaration(112 recordDecl(isUnion(), NotFromSystemHeaderOrStdNamespace)))))));113 114 auto EnumField = fieldDecl(hasType(qualType(hasCanonicalType(115 enumType(hasDeclaration(enumDecl(NotFromSystemHeaderOrStdNamespace)))))));116 117 auto HasOneUnionField = fieldCountOfKindIsOne(UnionField, UnionMatchBindName);118 auto HasOneEnumField = fieldCountOfKindIsOne(EnumField, TagMatchBindName);119 120 Finder->addMatcher(recordDecl(anyOf(isStruct(), isClass()), HasOneUnionField,121 HasOneEnumField, unless(isImplicit()))122 .bind(RootMatchBindName),123 this);124}125 126bool TaggedUnionMemberCountCheck::isCountingEnumLikeName(StringRef Name) const {127 if (llvm::any_of(CountingEnumPrefixes, [Name](StringRef Prefix) -> bool {128 return Name.starts_with_insensitive(Prefix);129 }))130 return true;131 if (llvm::any_of(CountingEnumSuffixes, [Name](StringRef Suffix) -> bool {132 return Name.ends_with_insensitive(Suffix);133 }))134 return true;135 return false;136}137 138std::pair<const std::size_t, const EnumConstantDecl *>139TaggedUnionMemberCountCheck::getNumberOfEnumValues(const EnumDecl *ED) {140 llvm::SmallSet<llvm::APSInt, 16> EnumValues;141 142 const EnumConstantDecl *LastEnumConstant = nullptr;143 for (const EnumConstantDecl *Enumerator : ED->enumerators()) {144 EnumValues.insert(Enumerator->getInitVal());145 LastEnumConstant = Enumerator;146 }147 148 if (EnableCountingEnumHeuristic && LastEnumConstant &&149 isCountingEnumLikeName(LastEnumConstant->getName()) &&150 llvm::APSInt::isSameValue(LastEnumConstant->getInitVal(),151 llvm::APSInt::get(EnumValues.size() - 1))) {152 return {EnumValues.size() - 1, LastEnumConstant};153 }154 155 return {EnumValues.size(), nullptr};156}157 158void TaggedUnionMemberCountCheck::check(159 const MatchFinder::MatchResult &Result) {160 const auto *Root = Result.Nodes.getNodeAs<RecordDecl>(RootMatchBindName);161 const auto *UnionField =162 Result.Nodes.getNodeAs<FieldDecl>(UnionMatchBindName);163 const auto *TagField = Result.Nodes.getNodeAs<FieldDecl>(TagMatchBindName);164 165 assert(Root && "Root is missing!");166 assert(UnionField && "UnionField is missing!");167 assert(TagField && "TagField is missing!");168 if (!Root || !UnionField || !TagField)169 return;170 171 const auto *UnionDef = UnionField->getType()->castAsRecordDecl();172 const auto *EnumDef = TagField->getType()->castAsEnumDecl();173 174 const std::size_t UnionMemberCount = llvm::range_size(UnionDef->fields());175 auto [TagCount, CountingEnumConstantDecl] = getNumberOfEnumValues(EnumDef);176 177 if (UnionMemberCount > TagCount) {178 diag(Root->getLocation(),179 "tagged union has more data members (%0) than tags (%1)!")180 << UnionMemberCount << TagCount;181 } else if (StrictMode && UnionMemberCount < TagCount) {182 diag(Root->getLocation(),183 "tagged union has fewer data members (%0) than tags (%1)!")184 << UnionMemberCount << TagCount;185 }186 187 if (CountingEnumConstantDecl) {188 diag(CountingEnumConstantDecl->getLocation(),189 "assuming that this constant is just an auxiliary value and not "190 "used for indicating a valid union data member",191 DiagnosticIDs::Note);192 }193}194 195} // namespace clang::tidy::bugprone196