Skip to content

Commit b726e03

Browse files
committed
improvements for initialization lists
1 parent cea08d8 commit b726e03

File tree

5 files changed

+175
-19
lines changed

5 files changed

+175
-19
lines changed

clang-tools-extra/clang-tidy/bugprone/InvalidEnumDefaultInitializationCheck.cpp

Lines changed: 85 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
#include "InvalidEnumDefaultInitializationCheck.h"
1010
#include "clang/AST/ASTContext.h"
11+
#include "clang/AST/TypeVisitor.h"
1112
#include "clang/ASTMatchers/ASTMatchFinder.h"
1213
#include <algorithm>
1314

@@ -17,51 +18,118 @@ namespace clang::tidy::bugprone {
1718

1819
namespace {
1920

20-
AST_MATCHER(EnumDecl, isCompleteAndHasNoZeroValue) {
21-
const EnumDecl *Definition = Node.getDefinition();
22-
return Definition && Node.isComplete() && !Node.enumerators().empty() &&
21+
bool isCompleteAndHasNoZeroValue(const EnumDecl *D) {
22+
const EnumDecl *Definition = D->getDefinition();
23+
return Definition && Definition->isComplete() &&
24+
!Definition->enumerators().empty() &&
2325
std::none_of(Definition->enumerator_begin(),
2426
Definition->enumerator_end(),
2527
[](const EnumConstantDecl *Value) {
2628
return Value->getInitVal().isZero();
2729
});
2830
}
2931

32+
AST_MATCHER(EnumDecl, isCompleteAndHasNoZeroValue) {
33+
return isCompleteAndHasNoZeroValue(&Node);
34+
}
35+
36+
// Find an initialization which initializes the value (if it has enum type) to a
37+
// default zero value.
3038
AST_MATCHER(Expr, isEmptyInit) {
3139
if (isa<CXXScalarValueInitExpr>(&Node))
3240
return true;
3341
if (isa<ImplicitValueInitExpr>(&Node))
3442
return true;
35-
if (const auto *Init = dyn_cast<InitListExpr>(&Node))
36-
return Init->getNumInits() == 0;
43+
if (const auto *Init = dyn_cast<InitListExpr>(&Node)) {
44+
if (Init->getNumInits() == 0)
45+
return true;
46+
}
3747
return false;
3848
}
3949

50+
AST_MATCHER(InitListExpr, hasArrayFiller) { return Node.hasArrayFiller(); }
51+
52+
// Check if any type has a "child" type that is an enum without zero value.
53+
// The "child" type can be an array element type or member type of a record
54+
// type (or a recursive combination of these). In this case, if the "root" type
55+
// is statically initialized, the enum component is initialized to zero.
56+
class FindEnumMember : public TypeVisitor<FindEnumMember, bool> {
57+
public:
58+
const EnumType *FoundEnum = nullptr;
59+
60+
bool VisitType(const Type *T) {
61+
const Type *DesT = T->getUnqualifiedDesugaredType();
62+
if (DesT != T)
63+
return Visit(DesT);
64+
return false;
65+
}
66+
bool VisitArrayType(const ArrayType *T) {
67+
return Visit(T->getElementType()->getUnqualifiedDesugaredType());
68+
}
69+
bool VisitConstantArrayType(const ConstantArrayType *T) {
70+
return Visit(T->getElementType()->getUnqualifiedDesugaredType());
71+
}
72+
bool VisitEnumType(const EnumType *T) {
73+
if (isCompleteAndHasNoZeroValue(T->getDecl())) {
74+
FoundEnum = T;
75+
return true;
76+
}
77+
return false;
78+
}
79+
bool VisitRecordType(const RecordType *T) {
80+
const RecordDecl *RD = T->getDecl();
81+
if (RD->isUnion())
82+
return false;
83+
auto VisitField = [this](const FieldDecl *F) {
84+
return Visit(F->getType()->getUnqualifiedDesugaredType());
85+
};
86+
return llvm::any_of(RD->fields(), VisitField);
87+
}
88+
};
89+
4090
} // namespace
4191

4292
InvalidEnumDefaultInitializationCheck::InvalidEnumDefaultInitializationCheck(
4393
StringRef Name, ClangTidyContext *Context)
4494
: ClangTidyCheck(Name, Context) {}
4595

46-
bool InvalidEnumDefaultInitializationCheck::isLanguageVersionSupported(
47-
const LangOptions &LangOpts) const {
48-
return LangOpts.CPlusPlus;
49-
}
50-
5196
void InvalidEnumDefaultInitializationCheck::registerMatchers(
5297
MatchFinder *Finder) {
98+
auto EnumWithoutZeroValue = enumType(
99+
hasDeclaration(enumDecl(isCompleteAndHasNoZeroValue()).bind("enum")));
100+
auto EnumOrArrayOfEnum = qualType(hasUnqualifiedDesugaredType(
101+
anyOf(EnumWithoutZeroValue,
102+
arrayType(hasElementType(qualType(
103+
hasUnqualifiedDesugaredType(EnumWithoutZeroValue)))))));
53104
Finder->addMatcher(
54-
expr(isEmptyInit(),
55-
hasType(hasUnqualifiedDesugaredType(enumType(hasDeclaration(
56-
enumDecl(isCompleteAndHasNoZeroValue()).bind("enum"))))))
57-
.bind("expr"),
58-
this);
105+
expr(isEmptyInit(), hasType(EnumOrArrayOfEnum)).bind("expr"), this);
106+
107+
// Array initialization can contain an "array filler" for the (syntactically)
108+
// unspecified elements. This expression is not found by AST matchers and can
109+
// have any type (the array's element type). This is an implicitly generated
110+
// initialization, so if the type contains somewhere an enum without zero
111+
// enumerator, the zero initialization applies here. We search this array
112+
// element type for the specific enum type manually when this matcher matches.
113+
Finder->addMatcher(initListExpr(hasArrayFiller()).bind("array_filler_expr"),
114+
this);
59115
}
60116

61117
void InvalidEnumDefaultInitializationCheck::check(
62118
const MatchFinder::MatchResult &Result) {
63119
const auto *InitExpr = Result.Nodes.getNodeAs<Expr>("expr");
64120
const auto *Enum = Result.Nodes.getNodeAs<EnumDecl>("enum");
121+
if (!InitExpr) {
122+
const auto *InitList =
123+
Result.Nodes.getNodeAs<InitListExpr>("array_filler_expr");
124+
// Initialization of omitted array elements with array filler was found.
125+
// Check the type for enum without zero value.
126+
FindEnumMember Finder;
127+
if (!Finder.Visit(InitList->getArrayFiller()->getType().getTypePtr()))
128+
return;
129+
InitExpr = InitList;
130+
Enum = Finder.FoundEnum->getDecl();
131+
}
132+
65133
if (!InitExpr || !Enum)
66134
return;
67135

@@ -99,7 +167,8 @@ void InvalidEnumDefaultInitializationCheck::check(
99167
}
100168
}
101169
// If still not found a source location, omit the warning.
102-
// FIXME: All such cases should be fixed to make the checker more precise.
170+
// Ideally all such cases (if they exist) should be handled to make the
171+
// check more precise.
103172
if (Loc.isInvalid())
104173
return;
105174
}

clang-tools-extra/clang-tidy/bugprone/InvalidEnumDefaultInitializationCheck.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ class InvalidEnumDefaultInitializationCheck : public ClangTidyCheck {
2424
ClangTidyContext *Context);
2525
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
2626
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
27-
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override;
2827
};
2928

3029
} // namespace clang::tidy::bugprone

clang-tools-extra/docs/clang-tidy/checks/bugprone/invalid-enum-default-initialization.rst

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ enumerator values).
1616

1717
The checker emits a warning only if an enum variable is default-initialized
1818
(contrary to not initialized) and the enum type does not have an enumerator with
19-
value of 0. The enum type can be scoped or non-scoped enum.
19+
value of 0. The enum type can be scoped or non-scoped enum. Unions are not
20+
handled by the check (if it contains a member of enum type).
2021

2122
.. code-block:: c++
2223

@@ -48,3 +49,24 @@ value of 0. The enum type can be scoped or non-scoped enum.
4849
};
4950

5051
S2 VarS2{}; // warn: member 'B' is initialized to 0
52+
53+
The check applies to initialization of arrays or structures with initialization
54+
lists in C code too. In these cases elements not specified in the list (and have
55+
enum type) are set to 0.
56+
57+
.. code-block:: c
58+
59+
enum Enum1 {
60+
Enum1_A = 1,
61+
Enum1_B
62+
};
63+
struct Struct1 {
64+
int a;
65+
enum Enum1 b;
66+
};
67+
68+
enum Enum1 Array1[2] = {Enum1_A}; // warn: omitted elements are initialized to 0
69+
enum Enum1 Array2[2][2] = {{Enum1_A}, {Enum1_A}}; // warn: last element of both nested arrays is initialized to 0
70+
enum Enum1 Array3[2][2] = {{Enum1_A, Enum1_A}}; // warn: elements of second array are initialized to 0
71+
72+
struct Struct1 S1 = {1}; // warn: element 'b' is initialized to 0
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// RUN: %check_clang_tidy %s bugprone-invalid-enum-default-initialization %t
2+
3+
enum Enum1 {
4+
Enum1_A = 1,
5+
Enum1_B
6+
};
7+
8+
struct Struct1 {
9+
int a;
10+
enum Enum1 b;
11+
};
12+
13+
struct Struct2 {
14+
struct Struct1 a;
15+
char b;
16+
};
17+
18+
enum Enum1 E1 = {};
19+
// CHECK-NOTES: :[[@LINE-1]]:17: warning: enum value of type 'Enum1' initialized with invalid value of 0, enum doesn't have a zero-value enumerator
20+
// CHECK-NOTES: :3:6: note: enum is defined here
21+
enum Enum1 E2[10] = {};
22+
// CHECK-NOTES: :[[@LINE-1]]:21: warning: enum value of type 'Enum1' initialized with invalid value of 0, enum doesn't have a zero-value enumerator
23+
// CHECK-NOTES: :3:6: note: enum is defined here
24+
enum Enum1 E3[10] = {Enum1_A};
25+
// CHECK-NOTES: :[[@LINE-1]]:21: warning: enum value of type 'Enum1' initialized with invalid value of 0, enum doesn't have a zero-value enumerator
26+
// CHECK-NOTES: :3:6: note: enum is defined here
27+
enum Enum1 E4[2][2] = {{Enum1_A}, {Enum1_A}};
28+
// CHECK-NOTES: :[[@LINE-1]]:24: warning: enum value of type 'Enum1' initialized with invalid value of 0, enum doesn't have a zero-value enumerator
29+
// CHECK-NOTES: :3:6: note: enum is defined here
30+
// CHECK-NOTES: :[[@LINE-3]]:35: warning: enum value of type 'Enum1' initialized with invalid value of 0, enum doesn't have a zero-value enumerator
31+
// CHECK-NOTES: :3:6: note: enum is defined here
32+
enum Enum1 E5[2][2] = {{Enum1_A, Enum1_A}};
33+
// CHECK-NOTES: :[[@LINE-1]]:23: warning: enum value of type 'Enum1' initialized with invalid value of 0, enum doesn't have a zero-value enumerator
34+
// CHECK-NOTES: :3:6: note: enum is defined here
35+
36+
37+
struct Struct1 S1[2][2] = {{{1, Enum1_A}, {2, Enum1_A}}};
38+
// CHECK-NOTES: :[[@LINE-1]]:27: warning: enum value of type 'Enum1' initialized with invalid value of 0, enum doesn't have a zero-value enumerator
39+
// CHECK-NOTES: :3:6: note: enum is defined here
40+
41+
struct Struct2 S2[3] = {{1}};
42+
// CHECK-NOTES: :[[@LINE-1]]:24: warning: enum value of type 'Enum1' initialized with invalid value of 0, enum doesn't have a zero-value enumerator
43+
// CHECK-NOTES: :3:6: note: enum is defined here
44+
// CHECK-NOTES: :[[@LINE-3]]:26: warning: enum value of type 'Enum1' initialized with invalid value of 0, enum doesn't have a zero-value enumerator
45+
// CHECK-NOTES: :3:6: note: enum is defined here
46+
47+
union Union1 {
48+
enum Enum1 a;
49+
int b;
50+
};
51+
52+
// no warnings for union
53+
union Union1 U1 = {};
54+
union Union1 U2[3] = {};

clang-tools-extra/test/clang-tidy/checkers/bugprone/invalid-enum-default-initialization.cpp

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,13 @@ void f2() {
5757
// CHECK-NOTES: :[[@LINE-1]]:13: warning: enum value of type 'Enum1' initialized with invalid value of 0, enum doesn't have a zero-value enumerator
5858
// CHECK-NOTES: :8:12: note: enum is defined here
5959
Enum1 C[5] = {{}};
60-
// CHECK-NOTES: :[[@LINE-1]]:17: warning: enum value of type 'Enum1' initialized with invalid value of 0, enum doesn't have a zero-value enumerator
60+
// CHECK-NOTES: :[[@LINE-1]]:16: warning: enum value of type 'Enum1' initialized with invalid value of 0, enum doesn't have a zero-value enumerator
61+
// CHECK-NOTES: :8:12: note: enum is defined here
62+
// CHECK-NOTES: :[[@LINE-3]]:17: warning: enum value of type 'Enum1' initialized with invalid value of 0, enum doesn't have a zero-value enumerator
6163
// CHECK-NOTES: :8:12: note: enum is defined here
6264
Enum1 D[5] = {}; // FIMXE: warn for this?
65+
// CHECK-NOTES: :[[@LINE-1]]:16: warning: enum value of type 'Enum1' initialized with invalid value of 0, enum doesn't have a zero-value enumerator
66+
// CHECK-NOTES: :8:12: note: enum is defined here
6367
}
6468

6569
struct S1 {
@@ -99,6 +103,11 @@ struct S4 : public S3 {
99103
int Z;
100104
};
101105

106+
struct S5 {
107+
S2 X[3];
108+
int Y;
109+
};
110+
102111
S2 VarS2{};
103112
// CHECK-NOTES: :[[@LINE-1]]:9: warning: enum value of type 'Enum1' initialized with invalid value of 0
104113
// CHECK-NOTES: :8:12: note: enum is defined here
@@ -114,6 +123,9 @@ S4 VarS4{};
114123
// CHECK-NOTES: :8:12: note: enum is defined here
115124
// CHECK-NOTES: :[[@LINE-3]]:10: warning: enum value of type 'Enum2' initialized with invalid value of 0
116125
// CHECK-NOTES: :13:6: note: enum is defined here
126+
S5 VarS5{};
127+
// CHECK-NOTES: :[[@LINE-1]]:10: warning: enum value of type 'Enum1' initialized with invalid value of 0
128+
// CHECK-NOTES: :8:12: note: enum is defined here
117129

118130
enum class EnumFwd;
119131

0 commit comments

Comments
 (0)