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