1 //===--- TaggedUnionMemberCountCheck.cpp - clang-tidy ---------------------===//
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
7 //===----------------------------------------------------------------------===//
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"
15 using namespace clang::ast_matchers
;
17 namespace clang::tidy::bugprone
{
19 static constexpr llvm::StringLiteral StrictModeOptionName
= "StrictMode";
20 static constexpr llvm::StringLiteral EnableCountingEnumHeuristicOptionName
=
21 "EnableCountingEnumHeuristic";
22 static constexpr llvm::StringLiteral CountingEnumPrefixesOptionName
=
23 "CountingEnumPrefixes";
24 static constexpr llvm::StringLiteral CountingEnumSuffixesOptionName
=
25 "CountingEnumSuffixes";
27 static constexpr bool StrictModeOptionDefaultValue
= false;
28 static constexpr bool EnableCountingEnumHeuristicOptionDefaultValue
= true;
29 static constexpr llvm::StringLiteral CountingEnumPrefixesOptionDefaultValue
=
31 static constexpr llvm::StringLiteral CountingEnumSuffixesOptionDefaultValue
=
34 static constexpr llvm::StringLiteral RootMatchBindName
= "root";
35 static constexpr llvm::StringLiteral UnionMatchBindName
= "union";
36 static constexpr llvm::StringLiteral TagMatchBindName
= "tags";
40 AST_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.
47 // For precedence, see commit: 5b07de1a5faf4a22ae6fd982b877c5e7e3a76559
48 clang::ast_matchers::internal::BoundNodesTreeBuilder TempBuilder
;
50 const FieldDecl
*FirstMatch
= nullptr;
51 for (const FieldDecl
*Field
: Node
.fields()) {
52 if (InnerMatcher
.matches(*Field
, Finder
, &TempBuilder
)) {
62 Builder
->setBinding(BindName
, clang::DynTypedNode::create(*FirstMatch
));
70 TaggedUnionMemberCountCheck::TaggedUnionMemberCountCheck(
71 StringRef Name
, ClangTidyContext
*Context
)
72 : ClangTidyCheck(Name
, Context
),
74 Options
.get(StrictModeOptionName
, StrictModeOptionDefaultValue
)),
75 EnableCountingEnumHeuristic(
76 Options
.get(EnableCountingEnumHeuristicOptionName
,
77 EnableCountingEnumHeuristicOptionDefaultValue
)),
78 CountingEnumPrefixes(utils::options::parseStringList(
79 Options
.get(CountingEnumPrefixesOptionName
,
80 CountingEnumPrefixesOptionDefaultValue
))),
81 CountingEnumSuffixes(utils::options::parseStringList(
82 Options
.get(CountingEnumSuffixesOptionName
,
83 CountingEnumSuffixesOptionDefaultValue
))) {
84 if (!EnableCountingEnumHeuristic
) {
85 if (Options
.get(CountingEnumPrefixesOptionName
))
86 configurationDiag("%0: Counting enum heuristic is disabled but "
88 << Name
<< CountingEnumPrefixesOptionName
;
89 if (Options
.get(CountingEnumSuffixesOptionName
))
90 configurationDiag("%0: Counting enum heuristic is disabled but "
92 << Name
<< CountingEnumSuffixesOptionName
;
96 void TaggedUnionMemberCountCheck::storeOptions(
97 ClangTidyOptions::OptionMap
&Opts
) {
98 Options
.store(Opts
, StrictModeOptionName
, StrictMode
);
99 Options
.store(Opts
, EnableCountingEnumHeuristicOptionName
,
100 EnableCountingEnumHeuristic
);
101 Options
.store(Opts
, CountingEnumPrefixesOptionName
,
102 utils::options::serializeStringList(CountingEnumPrefixes
));
103 Options
.store(Opts
, CountingEnumSuffixesOptionName
,
104 utils::options::serializeStringList(CountingEnumSuffixes
));
107 void TaggedUnionMemberCountCheck::registerMatchers(MatchFinder
*Finder
) {
109 auto UnionField
= fieldDecl(hasType(qualType(
110 hasCanonicalType(recordType(hasDeclaration(recordDecl(isUnion())))))));
112 auto EnumField
= fieldDecl(hasType(
113 qualType(hasCanonicalType(enumType(hasDeclaration(enumDecl()))))));
115 auto hasOneUnionField
= fieldCountOfKindIsOne(UnionField
, UnionMatchBindName
);
116 auto hasOneEnumField
= fieldCountOfKindIsOne(EnumField
, TagMatchBindName
);
118 Finder
->addMatcher(recordDecl(anyOf(isStruct(), isClass()), hasOneUnionField
,
119 hasOneEnumField
, unless(isImplicit()))
120 .bind(RootMatchBindName
),
124 bool TaggedUnionMemberCountCheck::isCountingEnumLikeName(StringRef Name
) const {
125 if (llvm::any_of(CountingEnumPrefixes
, [Name
](StringRef Prefix
) -> bool {
126 return Name
.starts_with_insensitive(Prefix
);
129 if (llvm::any_of(CountingEnumSuffixes
, [Name
](StringRef Suffix
) -> bool {
130 return Name
.ends_with_insensitive(Suffix
);
136 std::pair
<const std::size_t, const EnumConstantDecl
*>
137 TaggedUnionMemberCountCheck::getNumberOfEnumValues(const EnumDecl
*ED
) {
138 llvm::SmallSet
<llvm::APSInt
, 16> EnumValues
;
140 const EnumConstantDecl
*LastEnumConstant
= nullptr;
141 for (const EnumConstantDecl
*Enumerator
: ED
->enumerators()) {
142 EnumValues
.insert(Enumerator
->getInitVal());
143 LastEnumConstant
= Enumerator
;
146 if (EnableCountingEnumHeuristic
&& LastEnumConstant
&&
147 isCountingEnumLikeName(LastEnumConstant
->getName()) &&
148 (LastEnumConstant
->getInitVal() == (EnumValues
.size() - 1))) {
149 return {EnumValues
.size() - 1, LastEnumConstant
};
152 return {EnumValues
.size(), nullptr};
155 void TaggedUnionMemberCountCheck::check(
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
);
162 assert(Root
&& "Root is missing!");
163 assert(UnionField
&& "UnionField is missing!");
164 assert(TagField
&& "TagField is missing!");
165 if (!Root
|| !UnionField
|| !TagField
)
168 const auto *UnionDef
=
169 UnionField
->getType().getCanonicalType().getTypePtr()->getAsRecordDecl();
170 const auto *EnumDef
= llvm::dyn_cast
<EnumDecl
>(
171 TagField
->getType().getCanonicalType().getTypePtr()->getAsTagDecl());
173 assert(UnionDef
&& "UnionDef is missing!");
174 assert(EnumDef
&& "EnumDef is missing!");
175 if (!UnionDef
|| !EnumDef
)
178 const std::size_t UnionMemberCount
= llvm::range_size(UnionDef
->fields());
179 auto [TagCount
, CountingEnumConstantDecl
] = getNumberOfEnumValues(EnumDef
);
181 if (UnionMemberCount
> TagCount
) {
182 diag(Root
->getLocation(),
183 "tagged union has more data members (%0) than tags (%1)!")
184 << UnionMemberCount
<< TagCount
;
185 } else if (StrictMode
&& UnionMemberCount
< TagCount
) {
186 diag(Root
->getLocation(),
187 "tagged union has fewer data members (%0) than tags (%1)!")
188 << UnionMemberCount
<< TagCount
;
191 if (CountingEnumConstantDecl
) {
192 diag(CountingEnumConstantDecl
->getLocation(),
193 "assuming that this constant is just an auxiliary value and not "
194 "used for indicating a valid union data member",
195 DiagnosticIDs::Note
);
199 } // namespace clang::tidy::bugprone