Bug Summary

File:build/llvm-toolchain-snapshot-16~++20220903111405+bd956b7db398/clang-tools-extra/clang-tidy/bugprone/SuspiciousEnumUsageCheck.cpp
Warning:line 161, column 10
Called C++ object pointer is null

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple x86_64-pc-linux-gnu -analyze -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name SuspiciousEnumUsageCheck.cpp -analyzer-checker=core -analyzer-checker=apiModeling -analyzer-checker=unix -analyzer-checker=deadcode -analyzer-checker=cplusplus -analyzer-checker=security.insecureAPI.UncheckedReturn -analyzer-checker=security.insecureAPI.getpw -analyzer-checker=security.insecureAPI.gets -analyzer-checker=security.insecureAPI.mktemp -analyzer-checker=security.insecureAPI.mkstemp -analyzer-checker=security.insecureAPI.vfork -analyzer-checker=nullability.NullPassedToNonnull -analyzer-checker=nullability.NullReturnedFromNonnull -analyzer-output plist -w -setup-static-analyzer -analyzer-config-compatibility-mode=true -mrelocation-model pic -pic-level 2 -mframe-pointer=none -relaxed-aliasing -fmath-errno -ffp-contract=on -fno-rounding-math -mconstructor-aliases -funwind-tables=2 -target-cpu x86-64 -tune-cpu generic -debugger-tuning=gdb -ffunction-sections -fdata-sections -fcoverage-compilation-dir=/build/llvm-toolchain-snapshot-16~++20220903111405+bd956b7db398/build-llvm/tools/clang/stage2-bins -resource-dir /usr/lib/llvm-16/lib/clang/16.0.0 -D _DEBUG -D _GNU_SOURCE -D __STDC_CONSTANT_MACROS -D __STDC_FORMAT_MACROS -D __STDC_LIMIT_MACROS -I tools/clang/tools/extra/clang-tidy/bugprone -I /build/llvm-toolchain-snapshot-16~++20220903111405+bd956b7db398/clang-tools-extra/clang-tidy/bugprone -I tools/clang/tools/extra/clang-tidy -I /build/llvm-toolchain-snapshot-16~++20220903111405+bd956b7db398/clang/include -I tools/clang/include -I include -I /build/llvm-toolchain-snapshot-16~++20220903111405+bd956b7db398/llvm/include -D _FORTIFY_SOURCE=2 -D NDEBUG -U NDEBUG -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/10/../../../../include/c++/10 -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/10/../../../../include/x86_64-linux-gnu/c++/10 -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/10/../../../../include/c++/10/backward -internal-isystem /usr/lib/llvm-16/lib/clang/16.0.0/include -internal-isystem /usr/local/include -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/10/../../../../x86_64-linux-gnu/include -internal-externc-isystem /usr/include/x86_64-linux-gnu -internal-externc-isystem /include -internal-externc-isystem /usr/include -fmacro-prefix-map=/build/llvm-toolchain-snapshot-16~++20220903111405+bd956b7db398/build-llvm/tools/clang/stage2-bins=build-llvm/tools/clang/stage2-bins -fmacro-prefix-map=/build/llvm-toolchain-snapshot-16~++20220903111405+bd956b7db398/= -fcoverage-prefix-map=/build/llvm-toolchain-snapshot-16~++20220903111405+bd956b7db398/build-llvm/tools/clang/stage2-bins=build-llvm/tools/clang/stage2-bins -fcoverage-prefix-map=/build/llvm-toolchain-snapshot-16~++20220903111405+bd956b7db398/= -O2 -Wno-unused-command-line-argument -Wno-unused-parameter -Wwrite-strings -Wno-missing-field-initializers -Wno-long-long -Wno-maybe-uninitialized -Wno-class-memaccess -Wno-redundant-move -Wno-pessimizing-move -Wno-noexcept-type -Wno-comment -Wno-misleading-indentation -std=c++17 -fdeprecated-macro -fdebug-compilation-dir=/build/llvm-toolchain-snapshot-16~++20220903111405+bd956b7db398/build-llvm/tools/clang/stage2-bins -fdebug-prefix-map=/build/llvm-toolchain-snapshot-16~++20220903111405+bd956b7db398/build-llvm/tools/clang/stage2-bins=build-llvm/tools/clang/stage2-bins -fdebug-prefix-map=/build/llvm-toolchain-snapshot-16~++20220903111405+bd956b7db398/= -ferror-limit 19 -fvisibility-inlines-hidden -stack-protector 2 -fgnuc-version=4.2.1 -fcolor-diagnostics -vectorize-loops -vectorize-slp -analyzer-output=html -analyzer-config stable-report-filename=true -faddrsig -D__GCC_HAVE_DWARF2_CFI_ASM=1 -o /tmp/scan-build-2022-09-03-142322-15842-1 -x c++ /build/llvm-toolchain-snapshot-16~++20220903111405+bd956b7db398/clang-tools-extra/clang-tidy/bugprone/SuspiciousEnumUsageCheck.cpp
1//===--- SuspiciousEnumUsageCheck.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 "SuspiciousEnumUsageCheck.h"
10#include "clang/AST/ASTContext.h"
11#include "clang/ASTMatchers/ASTMatchFinder.h"
12#include <algorithm>
13
14using namespace clang::ast_matchers;
15
16namespace clang {
17namespace tidy {
18namespace bugprone {
19
20static const char DifferentEnumErrorMessage[] =
21 "enum values are from different enum types";
22
23static const char BitmaskErrorMessage[] =
24 "enum type seems like a bitmask (contains mostly "
25 "power-of-2 literals), but this literal is not a "
26 "power-of-2";
27
28static const char BitmaskVarErrorMessage[] =
29 "enum type seems like a bitmask (contains mostly "
30 "power-of-2 literals) but %plural{1:a literal is|:some literals are}0 not "
31 "power-of-2";
32
33static const char BitmaskNoteMessage[] = "used here as a bitmask";
34
35/// Stores a min and a max value which describe an interval.
36struct ValueRange {
37 llvm::APSInt MinVal;
38 llvm::APSInt MaxVal;
39
40 ValueRange(const EnumDecl *EnumDec) {
41 const auto MinMaxVal = std::minmax_element(
42 EnumDec->enumerator_begin(), EnumDec->enumerator_end(),
43 [](const EnumConstantDecl *E1, const EnumConstantDecl *E2) {
44 return llvm::APSInt::compareValues(E1->getInitVal(),
45 E2->getInitVal()) < 0;
46 });
47 MinVal = MinMaxVal.first->getInitVal();
48 MaxVal = MinMaxVal.second->getInitVal();
49 }
50};
51
52/// Return the number of EnumConstantDecls in an EnumDecl.
53static int enumLength(const EnumDecl *EnumDec) {
54 return std::distance(EnumDec->enumerator_begin(), EnumDec->enumerator_end());
55}
56
57static bool hasDisjointValueRange(const EnumDecl *Enum1,
58 const EnumDecl *Enum2) {
59 ValueRange Range1(Enum1), Range2(Enum2);
60 return llvm::APSInt::compareValues(Range1.MaxVal, Range2.MinVal) < 0 ||
61 llvm::APSInt::compareValues(Range2.MaxVal, Range1.MinVal) < 0;
62}
63
64static bool isNonPowerOf2NorNullLiteral(const EnumConstantDecl *EnumConst) {
65 llvm::APSInt Val = EnumConst->getInitVal();
66 if (Val.isPowerOf2() || !Val.getBoolValue())
67 return false;
68 const Expr *InitExpr = EnumConst->getInitExpr();
69 if (!InitExpr)
70 return true;
71 return isa<IntegerLiteral>(InitExpr->IgnoreImpCasts());
72}
73
74static bool isMaxValAllBitSetLiteral(const EnumDecl *EnumDec) {
75 auto EnumConst = std::max_element(
76 EnumDec->enumerator_begin(), EnumDec->enumerator_end(),
77 [](const EnumConstantDecl *E1, const EnumConstantDecl *E2) {
78 return E1->getInitVal() < E2->getInitVal();
79 });
80
81 if (const Expr *InitExpr = EnumConst->getInitExpr()) {
82 return EnumConst->getInitVal().countTrailingOnes() ==
83 EnumConst->getInitVal().getActiveBits() &&
84 isa<IntegerLiteral>(InitExpr->IgnoreImpCasts());
85 }
86 return false;
87}
88
89static int countNonPowOfTwoLiteralNum(const EnumDecl *EnumDec) {
90 return std::count_if(
91 EnumDec->enumerator_begin(), EnumDec->enumerator_end(),
92 [](const EnumConstantDecl *E) { return isNonPowerOf2NorNullLiteral(E); });
93}
94
95/// Check if there is one or two enumerators that are not a power of 2 and are
96/// initialized by a literal in the enum type, and that the enumeration contains
97/// enough elements to reasonably act as a bitmask. Exclude the case where the
98/// last enumerator is the sum of the lesser values (and initialized by a
99/// literal) or when it could contain consecutive values.
100static bool isPossiblyBitMask(const EnumDecl *EnumDec) {
101 ValueRange VR(EnumDec);
102 int EnumLen = enumLength(EnumDec);
103 int NonPowOfTwoCounter = countNonPowOfTwoLiteralNum(EnumDec);
104 return NonPowOfTwoCounter >= 1 && NonPowOfTwoCounter <= 2 &&
105 NonPowOfTwoCounter < EnumLen / 2 &&
106 (VR.MaxVal - VR.MinVal != EnumLen - 1) &&
107 !(NonPowOfTwoCounter == 1 && isMaxValAllBitSetLiteral(EnumDec));
108}
109
110SuspiciousEnumUsageCheck::SuspiciousEnumUsageCheck(StringRef Name,
111 ClangTidyContext *Context)
112 : ClangTidyCheck(Name, Context),
113 StrictMode(Options.getLocalOrGlobal("StrictMode", false)) {}
114
115void SuspiciousEnumUsageCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
116 Options.store(Opts, "StrictMode", StrictMode);
117}
118
119void SuspiciousEnumUsageCheck::registerMatchers(MatchFinder *Finder) {
120 const auto EnumExpr = [](StringRef RefName, StringRef DeclName) {
121 return expr(hasType(enumDecl().bind(DeclName))).bind(RefName);
122 };
123
124 Finder->addMatcher(
125 binaryOperator(
126 hasOperatorName("|"), hasLHS(hasType(enumDecl().bind("enumDecl"))),
127 hasRHS(hasType(enumDecl(unless(equalsBoundNode("enumDecl")))
128 .bind("otherEnumDecl"))))
129 .bind("diffEnumOp"),
130 this);
131
132 Finder->addMatcher(
133 binaryOperator(hasAnyOperatorName("+", "|"),
134 hasLHS(EnumExpr("lhsExpr", "enumDecl")),
135 hasRHS(expr(hasType(enumDecl(equalsBoundNode("enumDecl"))))
136 .bind("rhsExpr"))),
137 this);
138
139 Finder->addMatcher(
140 binaryOperator(
141 hasAnyOperatorName("+", "|"),
142 hasOperands(expr(hasType(isInteger()), unless(hasType(enumDecl()))),
143 EnumExpr("enumExpr", "enumDecl"))),
144 this);
145
146 Finder->addMatcher(binaryOperator(hasAnyOperatorName("|=", "+="),
147 hasRHS(EnumExpr("enumExpr", "enumDecl"))),
148 this);
149}
150
151void SuspiciousEnumUsageCheck::checkSuspiciousBitmaskUsage(
152 const Expr *NodeExpr, const EnumDecl *EnumDec) {
153 const auto *EnumExpr = dyn_cast<DeclRefExpr>(NodeExpr);
1
Assuming 'NodeExpr' is not a 'CastReturnType'
2
'EnumExpr' initialized to a null pointer value
154 const auto *EnumConst =
155 EnumExpr
2.1
'EnumExpr' is null
? dyn_cast<EnumConstantDecl>(EnumExpr->getDecl()) : nullptr;
3
'?' condition is false
156
157 // Report the parameter if necessary.
158 if (!EnumConst
3.1
'EnumConst' is null
) {
4
Taking true branch
159 diag(EnumDec->getInnerLocStart(), BitmaskVarErrorMessage)
160 << countNonPowOfTwoLiteralNum(EnumDec);
161 diag(EnumExpr->getExprLoc(), BitmaskNoteMessage, DiagnosticIDs::Note);
5
Called C++ object pointer is null
162 } else if (isNonPowerOf2NorNullLiteral(EnumConst)) {
163 diag(EnumConst->getSourceRange().getBegin(), BitmaskErrorMessage);
164 diag(EnumExpr->getExprLoc(), BitmaskNoteMessage, DiagnosticIDs::Note);
165 }
166}
167
168void SuspiciousEnumUsageCheck::check(const MatchFinder::MatchResult &Result) {
169 // Case 1: The two enum values come from different types.
170 if (const auto *DiffEnumOp =
171 Result.Nodes.getNodeAs<BinaryOperator>("diffEnumOp")) {
172 const auto *EnumDec = Result.Nodes.getNodeAs<EnumDecl>("enumDecl");
173 const auto *OtherEnumDec =
174 Result.Nodes.getNodeAs<EnumDecl>("otherEnumDecl");
175 // Skip when one of the parameters is an empty enum. The
176 // hasDisjointValueRange function could not decide the values properly in
177 // case of an empty enum.
178 if (EnumDec->enumerator_begin() == EnumDec->enumerator_end() ||
179 OtherEnumDec->enumerator_begin() == OtherEnumDec->enumerator_end())
180 return;
181
182 if (!hasDisjointValueRange(EnumDec, OtherEnumDec))
183 diag(DiffEnumOp->getOperatorLoc(), DifferentEnumErrorMessage);
184 return;
185 }
186
187 // Case 2 and 3 only checked in strict mode. The checker tries to detect
188 // suspicious bitmasks which contains values initialized by non power-of-2
189 // literals.
190 if (!StrictMode)
191 return;
192 const auto *EnumDec = Result.Nodes.getNodeAs<EnumDecl>("enumDecl");
193 if (!isPossiblyBitMask(EnumDec))
194 return;
195
196 // Case 2:
197 // a. Investigating the right hand side of `+=` or `|=` operator.
198 // b. When the operator is `|` or `+` but only one of them is an EnumExpr
199 if (const auto *EnumExpr = Result.Nodes.getNodeAs<Expr>("enumExpr")) {
200 checkSuspiciousBitmaskUsage(EnumExpr, EnumDec);
201 return;
202 }
203
204 // Case 3:
205 // '|' or '+' operator where both argument comes from the same enum type
206 const auto *LhsExpr = Result.Nodes.getNodeAs<Expr>("lhsExpr");
207 checkSuspiciousBitmaskUsage(LhsExpr, EnumDec);
208
209 const auto *RhsExpr = Result.Nodes.getNodeAs<Expr>("rhsExpr");
210 checkSuspiciousBitmaskUsage(RhsExpr, EnumDec);
211}
212
213} // namespace bugprone
214} // namespace tidy
215} // namespace clang