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