Skip to content

Commit abecc8a

Browse files
authored
Merge branch 'main' into tvarghese/xxevalnfc
2 parents 53a3b6a + 740758a commit abecc8a

File tree

12 files changed

+523
-3
lines changed

12 files changed

+523
-3
lines changed

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
#include "IncorrectRoundingsCheck.h"
3939
#include "InfiniteLoopCheck.h"
4040
#include "IntegerDivisionCheck.h"
41+
#include "InvalidEnumDefaultInitializationCheck.h"
4142
#include "LambdaFunctionNameCheck.h"
4243
#include "MacroParenthesesCheck.h"
4344
#include "MacroRepeatedSideEffectsCheck.h"
@@ -165,6 +166,8 @@ class BugproneModule : public ClangTidyModule {
165166
CheckFactories.registerCheck<InfiniteLoopCheck>("bugprone-infinite-loop");
166167
CheckFactories.registerCheck<IntegerDivisionCheck>(
167168
"bugprone-integer-division");
169+
CheckFactories.registerCheck<InvalidEnumDefaultInitializationCheck>(
170+
"bugprone-invalid-enum-default-initialization");
168171
CheckFactories.registerCheck<LambdaFunctionNameCheck>(
169172
"bugprone-lambda-function-name");
170173
CheckFactories.registerCheck<MacroParenthesesCheck>(

clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt

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

clang-tools-extra/docs/ReleaseNotes.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,12 @@ Improvements to clang-tidy
108108
New checks
109109
^^^^^^^^^^
110110

111+
- New :doc:`bugprone-invalid-enum-default-initialization
112+
<clang-tidy/checks/bugprone/invalid-enum-default-initialization>` check.
113+
114+
Detects default initialization (to 0) of variables with ``enum`` type where
115+
the enum has no enumerator with value of 0.
116+
111117
- New :doc:`llvm-mlir-op-builder
112118
<clang-tidy/checks/llvm/use-new-mlir-op-builder>` check.
113119

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
.. title:: clang-tidy - bugprone-invalid-enum-default-initialization
2+
3+
bugprone-invalid-enum-default-initialization
4+
============================================
5+
6+
Detects default initialization (to 0) of variables with ``enum`` type where
7+
the enum has no enumerator with value of 0.
8+
9+
In C++ a default initialization is performed if a variable is initialized with
10+
initializer list or in other implicit ways, and no value is specified at the
11+
initialization. In such cases the value 0 is used for the initialization.
12+
This also applies to enumerations even if it does not have an enumerator with
13+
value 0. In this way a variable with the ``enum`` type may contain initially an
14+
invalid value (if the program expects that it contains only the listed
15+
enumerator values).
16+
17+
The check emits a warning only if an ``enum`` variable is default-initialized
18+
(contrary to not initialized) and the ``enum`` does not have an enumerator with
19+
value of 0. The type can be a scoped or non-scoped ``enum``. Unions are not
20+
handled by the check (if it contains a member of enumeration type).
21+
22+
.. code-block:: c++
23+
24+
enum class Enum1: int {
25+
A = 1,
26+
B
27+
};
28+
29+
enum class Enum0: int {
30+
A = 0,
31+
B
32+
};
33+
34+
void f() {
35+
Enum1 X1{}; // warn: 'X1' is initialized to 0
36+
Enum1 X2 = Enum1(); // warn: 'X2' is initialized to 0
37+
Enum1 X3; // no warning: 'X3' is not initialized
38+
Enum0 X4{}; // no warning: type has an enumerator with value of 0
39+
}
40+
41+
struct S1 {
42+
Enum1 A;
43+
S(): A() {} // warn: 'A' is initialized to 0
44+
};
45+
46+
struct S2 {
47+
int A;
48+
Enum1 B;
49+
};
50+
51+
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

clang-tools-extra/docs/clang-tidy/checks/list.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ Clang-Tidy Checks
106106
:doc:`bugprone-incorrect-roundings <bugprone/incorrect-roundings>`,
107107
:doc:`bugprone-infinite-loop <bugprone/infinite-loop>`,
108108
:doc:`bugprone-integer-division <bugprone/integer-division>`,
109+
:doc:`bugprone-invalid-enum-default-initialization <bugprone/invalid-enum-default-initialization>`,
109110
:doc:`bugprone-lambda-function-name <bugprone/lambda-function-name>`,
110111
:doc:`bugprone-macro-parentheses <bugprone/macro-parentheses>`, "Yes"
111112
:doc:`bugprone-macro-repeated-side-effects <bugprone/macro-repeated-side-effects>`,
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] = {};

0 commit comments

Comments
 (0)