Skip to content

Commit 3bf322e

Browse files
committed
[clang-tidy] Add bugprone-non-zero-enum-to-bool-conversion check
Detect implicit and explicit conversions of enum to bool, when enum doesn't have a enumerator with value equal to 0. In theory such conversion should always return TRUE. Reviewed By: carlosgalvezp Differential Revision: https://reviews.llvm.org/D144036
1 parent 4bac5f8 commit 3bf322e

File tree

9 files changed

+396
-1
lines changed

9 files changed

+396
-1
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 "MoveForwardingReferenceCheck.h"
3939
#include "MultipleStatementMacroCheck.h"
4040
#include "NoEscapeCheck.h"
41+
#include "NonZeroEnumToBoolConversionCheck.h"
4142
#include "NotNullTerminatedResultCheck.h"
4243
#include "ParentVirtualCallCheck.h"
4344
#include "PosixReturnCheck.h"
@@ -139,6 +140,8 @@ class BugproneModule : public ClangTidyModule {
139140
CheckFactories.registerCheck<cppcoreguidelines::NarrowingConversionsCheck>(
140141
"bugprone-narrowing-conversions");
141142
CheckFactories.registerCheck<NoEscapeCheck>("bugprone-no-escape");
143+
CheckFactories.registerCheck<NonZeroEnumToBoolConversionCheck>(
144+
"bugprone-non-zero-enum-to-bool-conversion");
142145
CheckFactories.registerCheck<NotNullTerminatedResultCheck>(
143146
"bugprone-not-null-terminated-result");
144147
CheckFactories.registerCheck<ParentVirtualCallCheck>(

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,17 +33,18 @@ add_clang_library(clangTidyBugproneModule
3333
MoveForwardingReferenceCheck.cpp
3434
MultipleStatementMacroCheck.cpp
3535
NoEscapeCheck.cpp
36+
NonZeroEnumToBoolConversionCheck.cpp
3637
NotNullTerminatedResultCheck.cpp
3738
ParentVirtualCallCheck.cpp
3839
PosixReturnCheck.cpp
3940
RedundantBranchConditionCheck.cpp
4041
ReservedIdentifierCheck.cpp
4142
SharedPtrArrayMismatchCheck.cpp
42-
SmartPtrArrayMismatchCheck.cpp
4343
SignalHandlerCheck.cpp
4444
SignedCharMisuseCheck.cpp
4545
SizeofContainerCheck.cpp
4646
SizeofExpressionCheck.cpp
47+
SmartPtrArrayMismatchCheck.cpp
4748
SpuriouslyWakeUpFunctionsCheck.cpp
4849
StandaloneEmptyCheck.cpp
4950
StringConstructorCheck.cpp
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
//===--- NonZeroEnumToBoolConversionCheck.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 "NonZeroEnumToBoolConversionCheck.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+
} // namespace
33+
34+
NonZeroEnumToBoolConversionCheck::NonZeroEnumToBoolConversionCheck(
35+
StringRef Name, ClangTidyContext *Context)
36+
: ClangTidyCheck(Name, Context),
37+
EnumIgnoreList(
38+
utils::options::parseStringList(Options.get("EnumIgnoreList", ""))) {}
39+
40+
void NonZeroEnumToBoolConversionCheck::storeOptions(
41+
ClangTidyOptions::OptionMap &Opts) {
42+
Options.store(Opts, "EnumIgnoreList",
43+
utils::options::serializeStringList(EnumIgnoreList));
44+
}
45+
46+
bool NonZeroEnumToBoolConversionCheck::isLanguageVersionSupported(
47+
const LangOptions &LangOpts) const {
48+
return LangOpts.CPlusPlus;
49+
}
50+
51+
void NonZeroEnumToBoolConversionCheck::registerMatchers(MatchFinder *Finder) {
52+
Finder->addMatcher(
53+
castExpr(hasCastKind(CK_IntegralToBoolean),
54+
unless(isExpansionInSystemHeader()), hasType(booleanType()),
55+
hasSourceExpression(
56+
expr(hasType(qualType(hasCanonicalType(hasDeclaration(
57+
enumDecl(isCompleteAndHasNoZeroValue(),
58+
unless(matchers::matchesAnyListedName(
59+
EnumIgnoreList)))
60+
.bind("enum"))))),
61+
unless(declRefExpr(to(enumConstantDecl()))))),
62+
unless(hasAncestor(staticAssertDecl())))
63+
.bind("cast"),
64+
this);
65+
}
66+
67+
void NonZeroEnumToBoolConversionCheck::check(
68+
const MatchFinder::MatchResult &Result) {
69+
const auto *Cast = Result.Nodes.getNodeAs<CastExpr>("cast");
70+
const auto *Enum = Result.Nodes.getNodeAs<EnumDecl>("enum");
71+
72+
diag(Cast->getExprLoc(), "conversion of %0 into 'bool' will always return "
73+
"'true', enum doesn't have a zero-value enumerator")
74+
<< Enum;
75+
diag(Enum->getLocation(), "enum is defined here", DiagnosticIDs::Note);
76+
}
77+
78+
} // namespace clang::tidy::bugprone
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
//===--- NonZeroNonZeroEnumToBoolConversionCheck.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_NONZEROENUMTOBOOLCONVERSIONCHECK_H
10+
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_NONZEROENUMTOBOOLCONVERSIONCHECK_H
11+
12+
#include "../ClangTidyCheck.h"
13+
#include <vector>
14+
15+
namespace clang::tidy::bugprone {
16+
17+
/// Detect implicit and explicit casts of `enum` type into `bool` where
18+
/// `enum` type doesn't have a zero-value enumerator.
19+
///
20+
/// For the user-facing documentation see:
21+
/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone/non-zero-enum-to-bool-conversion.html
22+
class NonZeroEnumToBoolConversionCheck : public ClangTidyCheck {
23+
public:
24+
NonZeroEnumToBoolConversionCheck(StringRef Name, ClangTidyContext *Context);
25+
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
26+
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
27+
void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
28+
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override;
29+
30+
private:
31+
const std::vector<StringRef> EnumIgnoreList;
32+
};
33+
34+
} // namespace clang::tidy::bugprone
35+
36+
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_NONZEROENUMTOBOOLCONVERSIONCHECK_H

clang-tools-extra/docs/ReleaseNotes.rst

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

109+
- New :doc:`bugprone-non-zero-enum-to-bool-conversion
110+
<clang-tidy/checks/bugprone/non-zero-enum-to-bool-conversion>` check.
111+
112+
Detect implicit and explicit casts of ``enum`` type into ``bool`` where ``enum`` type
113+
doesn't have a zero-value enumerator.
114+
109115
- New :doc:`bugprone-unsafe-functions
110116
<clang-tidy/checks/bugprone/unsafe-functions>` check.
111117

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
.. title:: clang-tidy - bugprone-non-zero-enum-to-bool-conversion
2+
3+
bugprone-non-zero-enum-to-bool-conversion
4+
=========================================
5+
6+
Detect implicit and explicit casts of ``enum`` type into ``bool`` where ``enum``
7+
type doesn't have a zero-value enumerator. If the ``enum`` is used only to hold
8+
values equal to its enumerators, then conversion to ``bool`` will always result
9+
in ``true`` value. This can lead to unnecessary code that reduces readability
10+
and maintainability and can result in bugs.
11+
12+
May produce false positives if the ``enum`` is used to store other values
13+
(used as a bit-mask or zero-initialized on purpose). To deal with them,
14+
``// NOLINT`` or casting first to the underlying type before casting to ``bool``
15+
can be used.
16+
17+
It is important to note that this check will not generate warnings if the
18+
definition of the enumeration type is not available.
19+
Additionally, C++11 enumeration classes are supported by this check.
20+
21+
Overall, this check serves to improve code quality and readability by identifying
22+
and flagging instances where implicit or explicit casts from enumeration types to
23+
boolean could cause potential issues.
24+
25+
Example
26+
-------
27+
28+
.. code-block:: c++
29+
30+
enum EStatus {
31+
OK = 1,
32+
NOT_OK,
33+
UNKNOWN
34+
};
35+
36+
void process(EStatus status) {
37+
if (!status) {
38+
// this true-branch won't be executed
39+
return;
40+
}
41+
// proceed with "valid data"
42+
}
43+
44+
Options
45+
-------
46+
47+
.. option:: EnumIgnoreList
48+
49+
Option is used to ignore certain enum types when checking for
50+
implicit/explicit casts to bool. It accepts a semicolon-separated list of
51+
(fully qualified) enum type names or regular expressions that match the enum
52+
type names.
53+
The default value is an empty string, which means no enums will be ignored.

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ Clang-Tidy Checks
104104
`bugprone-move-forwarding-reference <bugprone/move-forwarding-reference.html>`_, "Yes"
105105
`bugprone-multiple-statement-macro <bugprone/multiple-statement-macro.html>`_,
106106
`bugprone-no-escape <bugprone/no-escape.html>`_,
107+
`bugprone-non-zero-enum-to-bool-conversion <bugprone/non-zero-enum-to-bool-conversion.html>`_,
107108
`bugprone-not-null-terminated-result <bugprone/not-null-terminated-result.html>`_, "Yes"
108109
`bugprone-parent-virtual-call <bugprone/parent-virtual-call.html>`_, "Yes"
109110
`bugprone-posix-return <bugprone/posix-return.html>`_, "Yes"
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
// RUN: %check_clang_tidy -std=c++11-or-later %s bugprone-non-zero-enum-to-bool-conversion %t
2+
3+
namespace with::issue {
4+
5+
enum class EStatusC : char {
6+
SUCCESS = 1,
7+
FAILURE = 2,
8+
INVALID_PARAM = 3,
9+
UNKNOWN = 4
10+
};
11+
12+
bool testEnumConversion(EStatusC value) {
13+
// CHECK-MESSAGES: :[[@LINE+1]]:10: warning: conversion of 'EStatusC' into 'bool' will always return 'true', enum doesn't have a zero-value enumerator [bugprone-non-zero-enum-to-bool-conversion]
14+
return static_cast<bool>(value);
15+
}
16+
17+
enum class EStatusS : short {
18+
SUCCESS = 1,
19+
FAILURE = 2,
20+
INVALID_PARAM = 3,
21+
UNKNOWN = 4
22+
};
23+
24+
bool testEnumConversion(EStatusS value) {
25+
// CHECK-MESSAGES: :[[@LINE+1]]:10: warning: conversion of 'EStatusS' into 'bool' will always return 'true', enum doesn't have a zero-value enumerator [bugprone-non-zero-enum-to-bool-conversion]
26+
return static_cast<bool>(value);
27+
}
28+
29+
30+
enum class EStatusI : int {
31+
SUCCESS = 1,
32+
FAILURE = 2,
33+
INVALID_PARAM = 3,
34+
UNKNOWN = 4
35+
};
36+
37+
bool testEnumConversion(EStatusI value) {
38+
// CHECK-MESSAGES: :[[@LINE+1]]:10: warning: conversion of 'EStatusI' into 'bool' will always return 'true', enum doesn't have a zero-value enumerator [bugprone-non-zero-enum-to-bool-conversion]
39+
return static_cast<bool>(value);
40+
}
41+
42+
enum class EStatus {
43+
SUCCESS = 1,
44+
FAILURE = 2,
45+
INVALID_PARAM = 3,
46+
UNKNOWN = 4
47+
};
48+
49+
bool testEnumConversion(EStatus value) {
50+
// CHECK-MESSAGES: :[[@LINE+1]]:10: warning: conversion of 'EStatus' into 'bool' will always return 'true', enum doesn't have a zero-value enumerator [bugprone-non-zero-enum-to-bool-conversion]
51+
return static_cast<bool>(value);
52+
}
53+
54+
namespace enum_int {
55+
56+
enum EResult : int {
57+
OK = 1,
58+
NOT_OK
59+
};
60+
61+
bool testEnumConversion(const EResult& value) {
62+
// CHECK-MESSAGES: :[[@LINE+1]]:10: warning: conversion of 'EResult' into 'bool' will always return 'true', enum doesn't have a zero-value enumerator [bugprone-non-zero-enum-to-bool-conversion]
63+
return value;
64+
}
65+
66+
}
67+
68+
namespace enum_short {
69+
70+
enum EResult : short {
71+
OK = 1,
72+
NOT_OK
73+
};
74+
75+
bool testEnumConversion(const EResult& value) {
76+
// CHECK-MESSAGES: :[[@LINE+1]]:10: warning: conversion of 'EResult' into 'bool' will always return 'true', enum doesn't have a zero-value enumerator [bugprone-non-zero-enum-to-bool-conversion]
77+
return value;
78+
}
79+
80+
}
81+
82+
namespace enum_char {
83+
84+
enum EResult : char {
85+
OK = 1,
86+
NOT_OK
87+
};
88+
89+
bool testEnumConversion(const EResult& value) {
90+
// CHECK-MESSAGES: :[[@LINE+1]]:10: warning: conversion of 'EResult' into 'bool' will always return 'true', enum doesn't have a zero-value enumerator [bugprone-non-zero-enum-to-bool-conversion]
91+
return value;
92+
}
93+
94+
}
95+
96+
namespace enum_default {
97+
98+
enum EResult {
99+
OK = 1,
100+
NOT_OK
101+
};
102+
103+
bool testEnumConversion(const EResult& value) {
104+
// CHECK-MESSAGES: :[[@LINE+1]]:10: warning: conversion of 'EResult' into 'bool' will always return 'true', enum doesn't have a zero-value enumerator [bugprone-non-zero-enum-to-bool-conversion]
105+
return value;
106+
}
107+
108+
}
109+
110+
}

0 commit comments

Comments
 (0)