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 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;
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 instance
45 // is used for matching.
46 //
47 // For precedence, see commit: 5b07de1a5faf4a22ae6fd982b877c5e7e3a76559
48 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} // namespace
68
70 StringRef Name, ClangTidyContext *Context)
71 : ClangTidyCheck(Name, Context),
72 StrictMode(
74 EnableCountingEnumHeuristic(
77 CountingEnumPrefixes(utils::options::parseStringList(
80 CountingEnumSuffixes(utils::options::parseStringList(
83 if (!EnableCountingEnumHeuristic) {
84 if (Options.get(CountingEnumPrefixesOptionName))
85 configurationDiag("%0: Counting enum heuristic is disabled but "
86 "%1 is set")
88 if (Options.get(CountingEnumSuffixesOptionName))
89 configurationDiag("%0: Counting enum heuristic is disabled but "
90 "%1 is set")
92 }
93}
94
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
107
108 auto NotFromSystemHeaderOrStdNamespace =
109 unless(anyOf(isExpansionInSystemHeader(), isInStdNamespace()));
110
111 auto UnionField =
112 fieldDecl(hasType(qualType(hasCanonicalType(recordType(hasDeclaration(
113 recordDecl(isUnion(), NotFromSystemHeaderOrStdNamespace)))))));
114
115 auto EnumField = fieldDecl(hasType(qualType(hasCanonicalType(
116 enumType(hasDeclaration(enumDecl(NotFromSystemHeaderOrStdNamespace)))))));
117
118 auto HasOneUnionField = fieldCountOfKindIsOne(UnionField, UnionMatchBindName);
119 auto HasOneEnumField = fieldCountOfKindIsOne(EnumField, TagMatchBindName);
120
121 Finder->addMatcher(recordDecl(anyOf(isStruct(), isClass()), HasOneUnionField,
122 HasOneEnumField, unless(isImplicit()))
123 .bind(RootMatchBindName),
124 this);
125}
126
127bool TaggedUnionMemberCountCheck::isCountingEnumLikeName(StringRef Name) const {
128 if (llvm::any_of(CountingEnumPrefixes, [Name](StringRef Prefix) -> bool {
129 return Name.starts_with_insensitive(Prefix);
130 }))
131 return true;
132 if (llvm::any_of(CountingEnumSuffixes, [Name](StringRef Suffix) -> bool {
133 return Name.ends_with_insensitive(Suffix);
134 }))
135 return true;
136 return false;
137}
138
139std::pair<const std::size_t, const EnumConstantDecl *>
140TaggedUnionMemberCountCheck::getNumberOfEnumValues(const EnumDecl *ED) {
141 llvm::SmallSet<llvm::APSInt, 16> EnumValues;
142
143 const EnumConstantDecl *LastEnumConstant = nullptr;
144 for (const EnumConstantDecl *Enumerator : ED->enumerators()) {
145 EnumValues.insert(Enumerator->getInitVal());
146 LastEnumConstant = Enumerator;
147 }
148
149 if (EnableCountingEnumHeuristic && LastEnumConstant &&
150 isCountingEnumLikeName(LastEnumConstant->getName()) &&
151 llvm::APSInt::isSameValue(LastEnumConstant->getInitVal(),
152 llvm::APSInt::get(EnumValues.size() - 1))) {
153 return {EnumValues.size() - 1, LastEnumConstant};
154 }
155
156 return {EnumValues.size(), nullptr};
157}
158
160 const MatchFinder::MatchResult &Result) {
161 const auto *Root = Result.Nodes.getNodeAs<RecordDecl>(RootMatchBindName);
162 const auto *UnionField =
163 Result.Nodes.getNodeAs<FieldDecl>(UnionMatchBindName);
164 const auto *TagField = Result.Nodes.getNodeAs<FieldDecl>(TagMatchBindName);
165
166 assert(Root && "Root is missing!");
167 assert(UnionField && "UnionField is missing!");
168 assert(TagField && "TagField is missing!");
169 if (!Root || !UnionField || !TagField)
170 return;
171
172 const auto *UnionDef = UnionField->getType()->castAsRecordDecl();
173 const auto *EnumDef = TagField->getType()->castAsEnumDecl();
174
175 const std::size_t UnionMemberCount = llvm::range_size(UnionDef->fields());
176 auto [TagCount, CountingEnumConstantDecl] = getNumberOfEnumValues(EnumDef);
177
178 if (UnionMemberCount > TagCount) {
179 diag(Root->getLocation(),
180 "tagged union has more data members (%0) than tags (%1)!")
181 << UnionMemberCount << TagCount;
182 } else if (StrictMode && UnionMemberCount < TagCount) {
183 diag(Root->getLocation(),
184 "tagged union has fewer data members (%0) than tags (%1)!")
185 << UnionMemberCount << TagCount;
186 }
187
188 if (CountingEnumConstantDecl) {
189 diag(CountingEnumConstantDecl->getLocation(),
190 "assuming that this constant is just an auxiliary value and not "
191 "used for indicating a valid union data member",
192 DiagnosticIDs::Note);
193 }
194}
195
196} // 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 llvm::StringLiteral CountingEnumSuffixesOptionDefaultValue
static constexpr llvm::StringLiteral CountingEnumSuffixesOptionName
static constexpr bool EnableCountingEnumHeuristicOptionDefaultValue
static constexpr llvm::StringLiteral RootMatchBindName
static constexpr llvm::StringLiteral StrictModeOptionName
static constexpr llvm::StringLiteral EnableCountingEnumHeuristicOptionName
static constexpr bool StrictModeOptionDefaultValue
static constexpr llvm::StringLiteral UnionMatchBindName
static constexpr llvm::StringLiteral TagMatchBindName
static constexpr llvm::StringLiteral CountingEnumPrefixesOptionName
static constexpr llvm::StringLiteral CountingEnumPrefixesOptionDefaultValue
std::string serializeStringList(ArrayRef< StringRef > Strings)
Serialize a sequence of names that can be parsed by parseStringList.
llvm::StringMap< ClangTidyValue > OptionMap