clang-tools 20.0.0git
TaggedUnionMemberCountCheck.cpp
Go to the documentation of this file.
1//===--- TaggedUnionMemberCountCheck.cpp - clang-tidy ---------------------===//
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
10#include "../utils/OptionsUtils.h"
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 } else {
56 FirstMatch = Field;
57 }
58 }
59 }
60
61 if (FirstMatch) {
62 Builder->setBinding(BindName, clang::DynTypedNode::create(*FirstMatch));
63 return true;
64 }
65 return false;
66}
67
68} // namespace
69
71 StringRef Name, ClangTidyContext *Context)
72 : ClangTidyCheck(Name, Context),
73 StrictMode(
75 EnableCountingEnumHeuristic(
78 CountingEnumPrefixes(utils::options::parseStringList(
81 CountingEnumSuffixes(utils::options::parseStringList(
84 if (!EnableCountingEnumHeuristic) {
86 configurationDiag("%0: Counting enum heuristic is disabled but "
87 "%1 is set")
90 configurationDiag("%0: Counting enum heuristic is disabled but "
91 "%1 is set")
93 }
94}
95
98 Options.store(Opts, StrictModeOptionName, StrictMode);
100 EnableCountingEnumHeuristic);
102 utils::options::serializeStringList(CountingEnumPrefixes));
104 utils::options::serializeStringList(CountingEnumSuffixes));
105}
106
108
109 auto UnionField = fieldDecl(hasType(qualType(
110 hasCanonicalType(recordType(hasDeclaration(recordDecl(isUnion())))))));
111
112 auto EnumField = fieldDecl(hasType(
113 qualType(hasCanonicalType(enumType(hasDeclaration(enumDecl()))))));
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 (LastEnumConstant->getInitVal() == (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 =
169 UnionField->getType().getCanonicalType().getTypePtr()->getAsRecordDecl();
170 const auto *EnumDef = llvm::dyn_cast<EnumDecl>(
171 TagField->getType().getCanonicalType().getTypePtr()->getAsTagDecl());
172
173 assert(UnionDef && "UnionDef is missing!");
174 assert(EnumDef && "EnumDef is missing!");
175 if (!UnionDef || !EnumDef)
176 return;
177
178 const std::size_t UnionMemberCount = llvm::range_size(UnionDef->fields());
179 auto [TagCount, CountingEnumConstantDecl] = getNumberOfEnumValues(EnumDef);
180
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;
189 }
190
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);
196 }
197}
198
199} // namespace clang::tidy::bugprone
std::string Suffix
Definition: AddUsing.cpp:132
llvm::SmallString< 256U > Name
CodeCompletionBuilder Builder
ASTNode Root
Definition: DumpAST.cpp:343
const FieldDecl * Field
::clang::DynTypedNode Node
std::optional< StringRef > get(StringRef LocalName) const
Read a named option from the Context.
void store(ClangTidyOptions::OptionMap &Options, StringRef LocalName, StringRef Value) const
Stores an option with the check-local name LocalName with string value Value to Options.
Base class for all clang-tidy checks.
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check's name.
DiagnosticBuilder configurationDiag(StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning) const
Adds a diagnostic to report errors in the check's configuration.
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
TaggedUnionMemberCountCheck(StringRef Name, ClangTidyContext *Context)
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
Should store all options supported by this check with their current values or default values for opti...
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