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
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"
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;
28static constexpr bool EnableCountingEnumHeuristicOptionDefaultValue = true;
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(Node: *Field, Finder, Builder: &TempBuilder)) {
53 if (FirstMatch) {
54 return false;
55 }
56 FirstMatch = Field;
57 }
58 }
59
60 if (FirstMatch) {
61 Builder->setBinding(Id: BindName, DynNode: clang::DynTypedNode::create(Node: *FirstMatch));
62 return true;
63 }
64 return false;
65}
66
67} // namespace
68
69TaggedUnionMemberCountCheck::TaggedUnionMemberCountCheck(
70 StringRef Name, ClangTidyContext *Context)
71 : ClangTidyCheck(Name, Context),
72 StrictMode(
73 Options.get(LocalName: StrictModeOptionName, Default: StrictModeOptionDefaultValue)),
74 EnableCountingEnumHeuristic(
75 Options.get(LocalName: EnableCountingEnumHeuristicOptionName,
76 Default: EnableCountingEnumHeuristicOptionDefaultValue)),
77 CountingEnumPrefixes(utils::options::parseStringList(
78 Option: Options.get(LocalName: CountingEnumPrefixesOptionName,
79 Default: CountingEnumPrefixesOptionDefaultValue))),
80 CountingEnumSuffixes(utils::options::parseStringList(
81 Option: Options.get(LocalName: CountingEnumSuffixesOptionName,
82 Default: CountingEnumSuffixesOptionDefaultValue))) {
83 if (!EnableCountingEnumHeuristic) {
84 if (Options.get(LocalName: CountingEnumPrefixesOptionName))
85 configurationDiag(Description: "%0: Counting enum heuristic is disabled but "
86 "%1 is set")
87 << Name << CountingEnumPrefixesOptionName;
88 if (Options.get(LocalName: CountingEnumSuffixesOptionName))
89 configurationDiag(Description: "%0: Counting enum heuristic is disabled but "
90 "%1 is set")
91 << Name << CountingEnumSuffixesOptionName;
92 }
93}
94
95void TaggedUnionMemberCountCheck::storeOptions(
96 ClangTidyOptions::OptionMap &Opts) {
97 Options.store(Options&: Opts, LocalName: StrictModeOptionName, Value: StrictMode);
98 Options.store(Options&: Opts, LocalName: EnableCountingEnumHeuristicOptionName,
99 Value: EnableCountingEnumHeuristic);
100 Options.store(Options&: Opts, LocalName: CountingEnumPrefixesOptionName,
101 Value: utils::options::serializeStringList(Strings: CountingEnumPrefixes));
102 Options.store(Options&: Opts, LocalName: CountingEnumSuffixesOptionName,
103 Value: utils::options::serializeStringList(Strings: CountingEnumSuffixes));
104}
105
106void TaggedUnionMemberCountCheck::registerMatchers(MatchFinder *Finder) {
107
108 auto UnionField = fieldDecl(hasType(InnerMatcher: qualType(
109 hasCanonicalType(InnerMatcher: recordType(hasDeclaration(InnerMatcher: recordDecl(isUnion())))))));
110
111 auto EnumField = fieldDecl(hasType(
112 InnerMatcher: qualType(hasCanonicalType(InnerMatcher: enumType(hasDeclaration(InnerMatcher: enumDecl()))))));
113
114 auto HasOneUnionField = fieldCountOfKindIsOne(InnerMatcher: UnionField, BindName: UnionMatchBindName);
115 auto HasOneEnumField = fieldCountOfKindIsOne(InnerMatcher: EnumField, BindName: TagMatchBindName);
116
117 Finder->addMatcher(NodeMatch: recordDecl(anyOf(isStruct(), isClass()), HasOneUnionField,
118 HasOneEnumField, unless(isImplicit()))
119 .bind(ID: RootMatchBindName),
120 Action: this);
121}
122
123bool TaggedUnionMemberCountCheck::isCountingEnumLikeName(StringRef Name) const {
124 if (llvm::any_of(Range: CountingEnumPrefixes, P: [Name](StringRef Prefix) -> bool {
125 return Name.starts_with_insensitive(Prefix);
126 }))
127 return true;
128 if (llvm::any_of(Range: CountingEnumSuffixes, P: [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(V: Enumerator->getInitVal());
142 LastEnumConstant = Enumerator;
143 }
144
145 if (EnableCountingEnumHeuristic && LastEnumConstant &&
146 isCountingEnumLikeName(Name: LastEnumConstant->getName()) &&
147 (LastEnumConstant->getInitVal() == (EnumValues.size() - 1))) {
148 return {EnumValues.size() - 1, LastEnumConstant};
149 }
150
151 return {EnumValues.size(), nullptr};
152}
153
154void TaggedUnionMemberCountCheck::check(
155 const MatchFinder::MatchResult &Result) {
156 const auto *Root = Result.Nodes.getNodeAs<RecordDecl>(ID: RootMatchBindName);
157 const auto *UnionField =
158 Result.Nodes.getNodeAs<FieldDecl>(ID: UnionMatchBindName);
159 const auto *TagField = Result.Nodes.getNodeAs<FieldDecl>(ID: TagMatchBindName);
160
161 assert(Root && "Root is missing!");
162 assert(UnionField && "UnionField is missing!");
163 assert(TagField && "TagField is missing!");
164 if (!Root || !UnionField || !TagField)
165 return;
166
167 const auto *UnionDef =
168 UnionField->getType().getCanonicalType().getTypePtr()->getAsRecordDecl();
169 const auto *EnumDef = llvm::dyn_cast<EnumDecl>(
170 TagField->getType().getCanonicalType().getTypePtr()->getAsTagDecl());
171
172 assert(UnionDef && "UnionDef is missing!");
173 assert(EnumDef && "EnumDef is missing!");
174 if (!UnionDef || !EnumDef)
175 return;
176
177 const std::size_t UnionMemberCount = llvm::range_size(UnionDef->fields());
178 auto [TagCount, CountingEnumConstantDecl] = getNumberOfEnumValues(ED: EnumDef);
179
180 if (UnionMemberCount > TagCount) {
181 diag(Root->getLocation(),
182 "tagged union has more data members (%0) than tags (%1)!")
183 << UnionMemberCount << TagCount;
184 } else if (StrictMode && UnionMemberCount < TagCount) {
185 diag(Root->getLocation(),
186 "tagged union has fewer data members (%0) than tags (%1)!")
187 << UnionMemberCount << TagCount;
188 }
189
190 if (CountingEnumConstantDecl) {
191 diag(CountingEnumConstantDecl->getLocation(),
192 "assuming that this constant is just an auxiliary value and not "
193 "used for indicating a valid union data member",
194 DiagnosticIDs::Note);
195 }
196}
197
198} // namespace clang::tidy::bugprone
199

Provided by KDAB

Privacy Policy
Improve your Profiling and Debugging skills
Find out more

source code of clang-tools-extra/clang-tidy/bugprone/TaggedUnionMemberCountCheck.cpp