Skip to content

Commit 4ce7497

Browse files
committed
[clang-tidy] Add check 'bugprone-invalid-enum-default-initialization'
1 parent adfc577 commit 4ce7497

File tree

6 files changed

+316
-0
lines changed

6 files changed

+316
-0
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"
@@ -164,6 +165,8 @@ class BugproneModule : public ClangTidyModule {
164165
CheckFactories.registerCheck<InfiniteLoopCheck>("bugprone-infinite-loop");
165166
CheckFactories.registerCheck<IntegerDivisionCheck>(
166167
"bugprone-integer-division");
168+
CheckFactories.registerCheck<InvalidEnumDefaultInitializationCheck>(
169+
"bugprone-invalid-enum-default-initialization");
167170
CheckFactories.registerCheck<LambdaFunctionNameCheck>(
168171
"bugprone-lambda-function-name");
169172
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: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
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 "../utils/Matchers.h"
11+
// #include "../utils/OptionsUtils.h"
12+
#include "clang/AST/ASTContext.h"
13+
#include "clang/ASTMatchers/ASTMatchFinder.h"
14+
#include <algorithm>
15+
16+
using namespace clang::ast_matchers;
17+
18+
namespace clang::tidy::bugprone {
19+
20+
namespace {
21+
22+
AST_MATCHER(EnumDecl, isCompleteAndHasNoZeroValue) {
23+
const EnumDecl *Definition = Node.getDefinition();
24+
return Definition && Node.isComplete() &&
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(Expr, isEmptyInit) {
33+
if (isa<CXXScalarValueInitExpr>(&Node))
34+
return true;
35+
if (isa<ImplicitValueInitExpr>(&Node))
36+
return true;
37+
if (const auto *Init = dyn_cast<InitListExpr>(&Node))
38+
return Init->getNumInits() == 0;
39+
return false;
40+
}
41+
42+
} // namespace
43+
44+
InvalidEnumDefaultInitializationCheck::InvalidEnumDefaultInitializationCheck(
45+
StringRef Name, ClangTidyContext *Context)
46+
: ClangTidyCheck(Name, Context) {}
47+
48+
bool InvalidEnumDefaultInitializationCheck::isLanguageVersionSupported(
49+
const LangOptions &LangOpts) const {
50+
return LangOpts.CPlusPlus;
51+
}
52+
53+
void InvalidEnumDefaultInitializationCheck::registerMatchers(
54+
MatchFinder *Finder) {
55+
Finder->addMatcher(
56+
expr(isEmptyInit(),
57+
hasType(hasUnqualifiedDesugaredType(enumType(hasDeclaration(
58+
enumDecl(isCompleteAndHasNoZeroValue()).bind("enum"))))))
59+
.bind("expr"),
60+
this);
61+
}
62+
63+
void InvalidEnumDefaultInitializationCheck::check(
64+
const MatchFinder::MatchResult &Result) {
65+
const auto *InitExpr = Result.Nodes.getNodeAs<Expr>("expr");
66+
const auto *Enum = Result.Nodes.getNodeAs<EnumDecl>("enum");
67+
if (!InitExpr || !Enum)
68+
return;
69+
70+
ASTContext &ACtx = Enum->getASTContext();
71+
SourceLocation Loc = InitExpr->getExprLoc();
72+
if (Loc.isInvalid()) {
73+
if (isa<ImplicitValueInitExpr>(InitExpr) || isa<InitListExpr>(InitExpr)) {
74+
DynTypedNodeList Parents = ACtx.getParents(*InitExpr);
75+
if (Parents.empty())
76+
return;
77+
78+
if (const auto *Ctor = Parents[0].get<CXXConstructorDecl>()) {
79+
// Try to find member initializer with the found expression and get the
80+
// source location from it.
81+
CXXCtorInitializer *const *CtorInit = std::find_if(
82+
Ctor->init_begin(), Ctor->init_end(),
83+
[InitExpr](const CXXCtorInitializer *Init) {
84+
return Init->isMemberInitializer() && Init->getInit() == InitExpr;
85+
});
86+
if (!CtorInit)
87+
return;
88+
Loc = (*CtorInit)->getLParenLoc();
89+
} else if (const auto *InitList = Parents[0].get<InitListExpr>()) {
90+
// The expression may be implicitly generated for an initialization.
91+
// Search for a parent initialization list with valid source location.
92+
while (InitList->getExprLoc().isInvalid()) {
93+
DynTypedNodeList Parents = ACtx.getParents(*InitList);
94+
if (Parents.empty())
95+
return;
96+
InitList = Parents[0].get<InitListExpr>();
97+
if (!InitList)
98+
return;
99+
}
100+
Loc = InitList->getExprLoc();
101+
}
102+
}
103+
// If still not found a source location, omit the warning.
104+
// FIXME: All such cases should be fixed to make the checker more precise.
105+
if (Loc.isInvalid())
106+
return;
107+
}
108+
diag(Loc, "enum value of type %0 initialized with invalid value of 0, "
109+
"enum doesn't have a zero-value enumerator")
110+
<< Enum;
111+
diag(Enum->getLocation(), "enum is defined here", DiagnosticIDs::Note);
112+
}
113+
114+
} // namespace clang::tidy::bugprone
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
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+
/// Detect 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/.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+
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override;
28+
};
29+
30+
} // namespace clang::tidy::bugprone
31+
32+
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_INVALIDENUMDEFAULTINITIALIZATIONCHECK_H
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
.. title:: clang-tidy - bugprone-invalid-enum-default-initialization
2+
3+
bugprone-invalid-enum-default-initialization
4+
============================================
5+
6+
Detect 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 checker emits a warning only if an enum variable is default-initialized
18+
(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.
20+
21+
.. code-block:: c++
22+
23+
enum class Enum1: int {
24+
A = 1,
25+
B
26+
};
27+
28+
enum class Enum0: int {
29+
A = 0,
30+
B
31+
};
32+
33+
void f() {
34+
Enum1 X1{}; // warn: 'X1' is initialized to 0
35+
Enum1 X2 = Enum1(); // warn: 'X2' is initialized to 0
36+
Enum1 X3; // no warning: 'X3' is not initialized
37+
Enum0 X4{}; // no warning: type has an enumerator with value of 0
38+
}
39+
40+
struct S1 {
41+
Enum1 A;
42+
S(): A() {} // warn: 'A' is initialized to 0
43+
};
44+
45+
struct S2 {
46+
int A;
47+
Enum1 B;
48+
};
49+
50+
S2 VarS2{}; // warn: member 'B' is initialized to 0
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
// RUN: %check_clang_tidy -std=c++17 %s bugprone-invalid-enum-default-initialization %t
2+
3+
enum class Enum0: int {
4+
A = 0,
5+
B
6+
};
7+
8+
enum class Enum1: int {
9+
A = 1,
10+
B
11+
};
12+
13+
enum Enum2 {
14+
Enum_A = 4,
15+
Enum_B
16+
};
17+
18+
Enum0 E0_1{};
19+
Enum0 E0_2 = Enum0();
20+
Enum0 E0_3;
21+
Enum0 E0_4{0};
22+
Enum0 E0_5{Enum0::A};
23+
Enum0 E0_6{Enum0::B};
24+
25+
Enum1 E1_1{};
26+
// CHECK-NOTES: :[[@LINE-1]]:11: warning: enum value of type 'Enum1' initialized with invalid value of 0, enum doesn't have a zero-value enumerator
27+
// CHECK-NOTES: :8:12: note: enum is defined here
28+
Enum1 E1_2 = Enum1();
29+
// CHECK-NOTES: :[[@LINE-1]]:14: warning: enum value of type 'Enum1' initialized with invalid value of 0, enum doesn't have a zero-value enumerator
30+
// CHECK-NOTES: :8:12: note: enum is defined here
31+
Enum1 E1_3;
32+
Enum1 E1_4{0};
33+
Enum1 E1_5{Enum1::A};
34+
Enum1 E1_6{Enum1::B};
35+
36+
Enum2 E2_1{};
37+
// CHECK-NOTES: :[[@LINE-1]]:11: warning: enum value of type 'Enum2' initialized with invalid value of 0, enum doesn't have a zero-value enumerator
38+
// CHECK-NOTES: :13:6: note: enum is defined here
39+
Enum2 E2_2 = Enum2();
40+
// CHECK-NOTES: :[[@LINE-1]]:14: warning: enum value of type 'Enum2' initialized with invalid value of 0, enum doesn't have a zero-value enumerator
41+
// CHECK-NOTES: :13:6: note: enum is defined here
42+
43+
void f1() {
44+
static Enum1 S; // FIMXE: warn for this?
45+
Enum1 A;
46+
Enum1 B = Enum1();
47+
// 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
48+
// CHECK-NOTES: :8:12: note: enum is defined here
49+
int C = int();
50+
}
51+
52+
void f2() {
53+
Enum1 A{};
54+
// CHECK-NOTES: :[[@LINE-1]]:10: warning: enum value of type 'Enum1' initialized with invalid value of 0, enum doesn't have a zero-value enumerator
55+
// CHECK-NOTES: :8:12: note: enum is defined here
56+
Enum1 B = Enum1();
57+
// 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
58+
// CHECK-NOTES: :8:12: note: enum is defined here
59+
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
61+
// CHECK-NOTES: :8:12: note: enum is defined here
62+
Enum1 D[5] = {}; // FIMXE: warn for this?
63+
}
64+
65+
struct S1 {
66+
Enum1 E_1{};
67+
// CHECK-NOTES: :[[@LINE-1]]:12: warning: enum value of type 'Enum1' initialized with invalid value of 0, enum doesn't have a zero-value enumerator
68+
// CHECK-NOTES: :8:12: note: enum is defined here
69+
Enum1 E_2 = Enum1();
70+
// CHECK-NOTES: :[[@LINE-1]]:15: warning: enum value of type 'Enum1' initialized with invalid value of 0, enum doesn't have a zero-value enumerator
71+
// CHECK-NOTES: :8:12: note: enum is defined here
72+
Enum1 E_3;
73+
Enum1 E_4;
74+
Enum1 E_5;
75+
76+
S1() :
77+
E_3{},
78+
// CHECK-NOTES: :[[@LINE-1]]:8: warning: enum value of type 'Enum1' initialized with invalid value of 0, enum doesn't have a zero-value enumerator
79+
// CHECK-NOTES: :8:12: note: enum is defined here
80+
E_4(),
81+
// CHECK-NOTES: :[[@LINE-1]]:8: warning: enum value of type 'Enum1' initialized with invalid value of 0, enum doesn't have a zero-value enumerator
82+
// CHECK-NOTES: :8:12: note: enum is defined here
83+
E_5{Enum1::B}
84+
{}
85+
};
86+
87+
struct S2 {
88+
Enum0 X;
89+
Enum1 Y;
90+
Enum2 Z;
91+
};
92+
93+
struct S3 {
94+
S2 X;
95+
int Y;
96+
};
97+
98+
struct S4 : public S3 {
99+
int Z;
100+
};
101+
102+
S2 VarS2{};
103+
// CHECK-NOTES: :[[@LINE-1]]:9: warning: enum value of type 'Enum1' initialized with invalid value of 0
104+
// CHECK-NOTES: :8:12: note: enum is defined here
105+
// CHECK-NOTES: :[[@LINE-3]]:9: warning: enum value of type 'Enum2' initialized with invalid value of 0
106+
// CHECK-NOTES: :13:6: note: enum is defined here
107+
S3 VarS3{};
108+
// CHECK-NOTES: :[[@LINE-1]]:10: warning: enum value of type 'Enum1' initialized with invalid value of 0
109+
// CHECK-NOTES: :8:12: note: enum is defined here
110+
// CHECK-NOTES: :[[@LINE-3]]:10: warning: enum value of type 'Enum2' initialized with invalid value of 0
111+
// CHECK-NOTES: :13:6: note: enum is defined here
112+
S4 VarS4{};
113+
// CHECK-NOTES: :[[@LINE-1]]:10: warning: enum value of type 'Enum1' initialized with invalid value of 0
114+
// CHECK-NOTES: :8:12: note: enum is defined here
115+
// CHECK-NOTES: :[[@LINE-3]]:10: warning: enum value of type 'Enum2' initialized with invalid value of 0
116+
// CHECK-NOTES: :13:6: note: enum is defined here

0 commit comments

Comments
 (0)