clang-tools 24.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 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 FirstMatch = Field;
54 }
55 }
56
57 if (FirstMatch) {
58 Builder->setBinding(BindName, DynTypedNode::create(*FirstMatch));
59 return true;
60 }
61 return false;
62}
63
64} // namespace
65
67 StringRef Name, ClangTidyContext *Context)
68 : ClangTidyCheck(Name, Context),
69 StrictMode(
71 EnableCountingEnumHeuristic(
74 CountingEnumPrefixes(utils::options::parseStringList(
77 CountingEnumSuffixes(utils::options::parseStringList(
80 if (!EnableCountingEnumHeuristic) {
81 if (Options.get(CountingEnumPrefixesOptionName))
82 configurationDiag("%0: Counting enum heuristic is disabled but "
83 "%1 is set")
85 if (Options.get(CountingEnumSuffixesOptionName))
86 configurationDiag("%0: Counting enum heuristic is disabled but "
87 "%1 is set")
89 }
90}
91
94 Options.store(Opts, StrictModeOptionName, StrictMode);
95 Options.store(Opts, EnableCountingEnumHeuristicOptionName,
96 EnableCountingEnumHeuristic);
97 Options.store(Opts, CountingEnumPrefixesOptionName,
98 utils::options::serializeStringList(CountingEnumPrefixes));
99 Options.store(Opts, CountingEnumSuffixesOptionName,
100 utils::options::serializeStringList(CountingEnumSuffixes));
101}
102
104 const auto NotFromSystemHeaderOrStdNamespace =
105 unless(anyOf(isExpansionInSystemHeader(), isInStdNamespace()));
106
107 const auto UnionField =
108 fieldDecl(hasType(qualType(hasCanonicalType(recordType(hasDeclaration(
109 recordDecl(isUnion(), NotFromSystemHeaderOrStdNamespace)))))));
110
111 const auto EnumField = fieldDecl(hasType(qualType(hasCanonicalType(
112 enumType(hasDeclaration(enumDecl(NotFromSystemHeaderOrStdNamespace)))))));
113
114 const auto HasOneUnionField =
115 fieldCountOfKindIsOne(UnionField, UnionMatchBindName);
116 const auto HasOneEnumField =
117 fieldCountOfKindIsOne(EnumField, TagMatchBindName);
118
119 Finder->addMatcher(recordDecl(anyOf(isStruct(), isClass()), HasOneUnionField,
120 HasOneEnumField, unless(isImplicit()))
121 .bind(RootMatchBindName),
122 this);
123}
124
125bool TaggedUnionMemberCountCheck::isCountingEnumLikeName(StringRef Name) const {
126 if (llvm::any_of(CountingEnumPrefixes, [Name](StringRef Prefix) -> bool {
127 return Name.starts_with_insensitive(Prefix);
128 }))
129 return true;
130 if (llvm::any_of(CountingEnumSuffixes, [Name](StringRef Suffix) -> bool {
131 return Name.ends_with_insensitive(Suffix);
132 }))
133 return true;
134 return false;
135}
136
137std::pair<const std::size_t, const EnumConstantDecl *>
138TaggedUnionMemberCountCheck::getNumberOfEnumValues(const EnumDecl *ED) {
139 llvm::SmallSet<llvm::APSInt, 16> EnumValues;
140
141 const EnumConstantDecl *LastEnumConstant = nullptr;
142 for (const EnumConstantDecl *Enumerator : ED->enumerators()) {
143 EnumValues.insert(Enumerator->getInitVal());
144 LastEnumConstant = Enumerator;
145 }
146
147 if (EnableCountingEnumHeuristic && LastEnumConstant &&
148 isCountingEnumLikeName(LastEnumConstant->getName()) &&
149 llvm::APSInt::isSameValue(LastEnumConstant->getInitVal(),
150 llvm::APSInt::get(EnumValues.size() - 1))) {
151 return {EnumValues.size() - 1, LastEnumConstant};
152 }
153
154 return {EnumValues.size(), nullptr};
155}
156
158 const MatchFinder::MatchResult &Result) {
159 const auto *Root = Result.Nodes.getNodeAs<RecordDecl>(RootMatchBindName);
160 const auto *UnionField =
161 Result.Nodes.getNodeAs<FieldDecl>(UnionMatchBindName);
162 const auto *TagField = Result.Nodes.getNodeAs<FieldDecl>(TagMatchBindName);
163
164 assert(Root && "Root is missing!");
165 assert(UnionField && "UnionField is missing!");
166 assert(TagField && "TagField is missing!");
167 if (!Root || !UnionField || !TagField)
168 return;
169
170 const auto *UnionDef = UnionField->getType()->castAsRecordDecl();
171 const auto *EnumDef = TagField->getType()->castAsEnumDecl();
172
173 const std::size_t UnionMemberCount = llvm::range_size(UnionDef->fields());
174 auto [TagCount, CountingEnumConstantDecl] = getNumberOfEnumValues(EnumDef);
175
176 if (UnionMemberCount > TagCount) {
177 diag(Root->getLocation(),
178 "tagged union has more data members (%0) than tags (%1)!")
179 << UnionMemberCount << TagCount;
180 } else if (StrictMode && UnionMemberCount < TagCount) {
181 diag(Root->getLocation(),
182 "tagged union has fewer data members (%0) than tags (%1)!")
183 << UnionMemberCount << TagCount;
184 }
185
186 if (CountingEnumConstantDecl) {
187 diag(CountingEnumConstantDecl->getLocation(),
188 "assuming that this constant is just an auxiliary value and not "
189 "used for indicating a valid union data member",
190 DiagnosticIDs::Note);
191 }
192}
193
194} // 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