clang-tools 23.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 FirstMatch = Field;
54 }
55 }
56
57 if (FirstMatch) {
58 Builder->setBinding(BindName, clang::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 auto NotFromSystemHeaderOrStdNamespace =
105 unless(anyOf(isExpansionInSystemHeader(), isInStdNamespace()));
106
107 auto UnionField =
108 fieldDecl(hasType(qualType(hasCanonicalType(recordType(hasDeclaration(
109 recordDecl(isUnion(), NotFromSystemHeaderOrStdNamespace)))))));
110
111 auto EnumField = fieldDecl(hasType(qualType(hasCanonicalType(
112 enumType(hasDeclaration(enumDecl(NotFromSystemHeaderOrStdNamespace)))))));
113
114 auto HasOneUnionField = fieldCountOfKindIsOne(UnionField, UnionMatchBindName);
115 auto HasOneEnumField = fieldCountOfKindIsOne(EnumField, TagMatchBindName);
116
117 Finder->addMatcher(recordDecl(anyOf(isStruct(), isClass()), HasOneUnionField,
118 HasOneEnumField, unless(isImplicit()))
119 .bind(RootMatchBindName),
120 this);
121}
122
123bool TaggedUnionMemberCountCheck::isCountingEnumLikeName(StringRef Name) const {
124 if (llvm::any_of(CountingEnumPrefixes, [Name](StringRef Prefix) -> bool {
125 return Name.starts_with_insensitive(Prefix);
126 }))
127 return true;
128 if (llvm::any_of(CountingEnumSuffixes, [Name](StringRef Suffix) -> bool {
129 return Name.ends_with_insensitive(Suffix);
130 }))
131 return true;
132 return false;
133}
134
135std::pair<const std::size_t, const EnumConstantDecl *>
136TaggedUnionMemberCountCheck::getNumberOfEnumValues(const EnumDecl *ED) {
137 llvm::SmallSet<llvm::APSInt, 16> EnumValues;
138
139 const EnumConstantDecl *LastEnumConstant = nullptr;
140 for (const EnumConstantDecl *Enumerator : ED->enumerators()) {
141 EnumValues.insert(Enumerator->getInitVal());
142 LastEnumConstant = Enumerator;
143 }
144
145 if (EnableCountingEnumHeuristic && LastEnumConstant &&
146 isCountingEnumLikeName(LastEnumConstant->getName()) &&
147 llvm::APSInt::isSameValue(LastEnumConstant->getInitVal(),
148 llvm::APSInt::get(EnumValues.size() - 1))) {
149 return {EnumValues.size() - 1, LastEnumConstant};
150 }
151
152 return {EnumValues.size(), nullptr};
153}
154
156 const MatchFinder::MatchResult &Result) {
157 const auto *Root = Result.Nodes.getNodeAs<RecordDecl>(RootMatchBindName);
158 const auto *UnionField =
159 Result.Nodes.getNodeAs<FieldDecl>(UnionMatchBindName);
160 const auto *TagField = Result.Nodes.getNodeAs<FieldDecl>(TagMatchBindName);
161
162 assert(Root && "Root is missing!");
163 assert(UnionField && "UnionField is missing!");
164 assert(TagField && "TagField is missing!");
165 if (!Root || !UnionField || !TagField)
166 return;
167
168 const auto *UnionDef = UnionField->getType()->castAsRecordDecl();
169 const auto *EnumDef = TagField->getType()->castAsEnumDecl();
170
171 const std::size_t UnionMemberCount = llvm::range_size(UnionDef->fields());
172 auto [TagCount, CountingEnumConstantDecl] = getNumberOfEnumValues(EnumDef);
173
174 if (UnionMemberCount > TagCount) {
175 diag(Root->getLocation(),
176 "tagged union has more data members (%0) than tags (%1)!")
177 << UnionMemberCount << TagCount;
178 } else if (StrictMode && UnionMemberCount < TagCount) {
179 diag(Root->getLocation(),
180 "tagged union has fewer data members (%0) than tags (%1)!")
181 << UnionMemberCount << TagCount;
182 }
183
184 if (CountingEnumConstantDecl) {
185 diag(CountingEnumConstantDecl->getLocation(),
186 "assuming that this constant is just an auxiliary value and not "
187 "used for indicating a valid union data member",
188 DiagnosticIDs::Note);
189 }
190}
191
192} // 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