clang-tools 22.0.0git
InvalidEnumDefaultInitializationCheck.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
10#include "../utils/Matchers.h"
12#include "clang/AST/ASTContext.h"
13#include "clang/AST/TypeVisitor.h"
14#include "clang/ASTMatchers/ASTMatchFinder.h"
15#include <algorithm>
16
17using namespace clang::ast_matchers;
18
19namespace clang::tidy::bugprone {
20
21namespace {
22
23// Preserve same name as AST_MATCHER(isCompleteAndHasNoZeroValue)
24// NOLINTNEXTLINE(llvm-prefer-static-over-anonymous-namespace)
25bool isCompleteAndHasNoZeroValue(const EnumDecl *D) {
26 const EnumDecl *Definition = D->getDefinition();
27 return Definition && Definition->isComplete() &&
28 !Definition->enumerators().empty() &&
29 llvm::none_of(Definition->enumerators(),
30 [](const EnumConstantDecl *Value) {
31 return Value->getInitVal().isZero();
32 });
33}
34
35AST_MATCHER(EnumDecl, isCompleteAndHasNoZeroValue) {
36 return isCompleteAndHasNoZeroValue(&Node);
37}
38
39// Find an initialization which initializes the value (if it has enum type) to a
40// default zero value.
41AST_MATCHER(Expr, isEmptyInit) {
42 if (isa<CXXScalarValueInitExpr, ImplicitValueInitExpr>(&Node))
43 return true;
44 if (const auto *Init = dyn_cast<InitListExpr>(&Node)) {
45 if (Init->getNumInits() == 0)
46 return true;
47 }
48 return false;
49}
50
51AST_MATCHER(InitListExpr, hasArrayFiller) { return Node.hasArrayFiller(); }
52
53// Check if any type has a "child" type that is an enum without zero value.
54// The "child" type can be an array element type or member type of a record
55// type (or a recursive combination of these). In this case, if the "root" type
56// is statically initialized, the enum component is initialized to zero.
57class FindEnumMember : public TypeVisitor<FindEnumMember, bool> {
58public:
59 const EnumType *FoundEnum = nullptr;
60
61 bool VisitType(const Type *T) {
62 const Type *DesT = T->getUnqualifiedDesugaredType();
63 if (DesT != T)
64 return Visit(DesT);
65 return false;
66 }
67 bool VisitArrayType(const ArrayType *T) {
68 return Visit(T->getElementType().getTypePtr());
69 }
70 bool VisitConstantArrayType(const ConstantArrayType *T) {
71 return Visit(T->getElementType().getTypePtr());
72 }
73 bool VisitEnumType(const EnumType *T) {
74 if (isCompleteAndHasNoZeroValue(T->getDecl())) {
75 FoundEnum = T;
76 return true;
77 }
78 return false;
79 }
80 bool VisitRecordType(const RecordType *T) {
81 const RecordDecl *RD = T->getDecl()->getDefinition();
82 if (!RD || RD->isUnion())
83 return false;
84 auto VisitField = [this](const FieldDecl *F) {
85 return Visit(F->getType().getTypePtr());
86 };
87 return llvm::any_of(RD->fields(), VisitField);
88 }
89};
90
91} // namespace
92
94 StringRef Name, ClangTidyContext *Context)
95 : ClangTidyCheck(Name, Context),
96 IgnoredEnums(
97 utils::options::parseStringList(Options.get("IgnoredEnums", ""))) {
98 IgnoredEnums.emplace_back("::std::errc");
99}
100
103 Options.store(Opts, "IgnoredEnums",
105}
106
108 MatchFinder *Finder) {
109 auto EnumWithoutZeroValue = enumType(hasDeclaration(
110 enumDecl(isCompleteAndHasNoZeroValue(),
111 unless(matchers::matchesAnyListedName(IgnoredEnums)))
112 .bind("enum")));
113 auto EnumOrArrayOfEnum = qualType(hasUnqualifiedDesugaredType(
114 anyOf(EnumWithoutZeroValue,
115 arrayType(hasElementType(qualType(
116 hasUnqualifiedDesugaredType(EnumWithoutZeroValue)))))));
117 Finder->addMatcher(
118 expr(isEmptyInit(), hasType(EnumOrArrayOfEnum)).bind("expr"), this);
119
120 // Array initialization can contain an "array filler" for the (syntactically)
121 // unspecified elements. This expression is not found by AST matchers and can
122 // have any type (the array's element type). This is an implicitly generated
123 // initialization, so if the type contains somewhere an enum without zero
124 // enumerator, the zero initialization applies here. We search this array
125 // element type for the specific enum type manually when this matcher matches.
126 Finder->addMatcher(initListExpr(hasArrayFiller()).bind("array_filler_expr"),
127 this);
128}
129
131 const MatchFinder::MatchResult &Result) {
132 const auto *InitExpr = Result.Nodes.getNodeAs<Expr>("expr");
133 const auto *Enum = Result.Nodes.getNodeAs<EnumDecl>("enum");
134 if (!InitExpr) {
135 const auto *InitList =
136 Result.Nodes.getNodeAs<InitListExpr>("array_filler_expr");
137 // Initialization of omitted array elements with array filler was found.
138 // Check the type for enum without zero value.
139 // FIXME: In this way only one enum-typed value is found, not all of these.
140 FindEnumMember Finder;
141 if (!Finder.Visit(InitList->getArrayFiller()->getType().getTypePtr()))
142 return;
143 InitExpr = InitList;
144 Enum = Finder.FoundEnum->getDecl();
145 }
146
147 if (!InitExpr || !Enum)
148 return;
149
150 ASTContext &ACtx = Enum->getASTContext();
151 SourceLocation Loc = InitExpr->getExprLoc();
152 if (Loc.isInvalid()) {
153 if (isa<ImplicitValueInitExpr, InitListExpr>(InitExpr)) {
154 const DynTypedNodeList Parents = ACtx.getParents(*InitExpr);
155 if (Parents.empty())
156 return;
157
158 if (const auto *Ctor = Parents[0].get<CXXConstructorDecl>()) {
159 // Try to find member initializer with the found expression and get the
160 // source location from it.
161 CXXCtorInitializer *const *CtorInit = std::find_if(
162 Ctor->init_begin(), Ctor->init_end(),
163 [InitExpr](const CXXCtorInitializer *Init) {
164 return Init->isMemberInitializer() && Init->getInit() == InitExpr;
165 });
166 if (!CtorInit)
167 return;
168 Loc = (*CtorInit)->getLParenLoc();
169 } else if (const auto *InitList = Parents[0].get<InitListExpr>()) {
170 // The expression may be implicitly generated for an initialization.
171 // Search for a parent initialization list with valid source location.
172 while (InitList->getExprLoc().isInvalid()) {
173 const DynTypedNodeList Parents = ACtx.getParents(*InitList);
174 if (Parents.empty())
175 return;
176 InitList = Parents[0].get<InitListExpr>();
177 if (!InitList)
178 return;
179 }
180 Loc = InitList->getExprLoc();
181 }
182 }
183 // If still not found a source location, omit the warning.
184 // Ideally all such cases (if they exist) should be handled to make the
185 // check more precise.
186 if (Loc.isInvalid())
187 return;
188 }
189 diag(Loc, "enum value of type %0 initialized with invalid value of 0, "
190 "enum doesn't have a zero-value enumerator")
191 << Enum;
192 diag(Enum->getLocation(), "enum is defined here", DiagnosticIDs::Note);
193}
194
195} // namespace clang::tidy::bugprone
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
@ Type
An inlay hint that for a type annotation.
Definition Protocol.h:1678
AST_MATCHER(BinaryOperator, isRelationalOperator)
inline ::clang::ast_matchers::internal::Matcher< NamedDecl > matchesAnyListedName(llvm::ArrayRef< StringRef > NameList)
std::string serializeStringList(ArrayRef< StringRef > Strings)
Serialize a sequence of names that can be parsed by parseStringList.
llvm::StringMap< ClangTidyValue > OptionMap