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
1819namespace {
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.
3038AST_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
4292InvalidEnumDefaultInitializationCheck::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-
5196void 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
61117void 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 }
0 commit comments