clang-tools 19.0.0git
ImplicitBoolConversionCheck.cpp
Go to the documentation of this file.
1//===--- ImplicitBoolConversionCheck.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
10#include "../utils/FixItHintUtils.h"
11#include "clang/AST/ASTContext.h"
12#include "clang/ASTMatchers/ASTMatchFinder.h"
13#include "clang/Lex/Lexer.h"
14#include "clang/Tooling/FixIt.h"
15#include <queue>
16
17using namespace clang::ast_matchers;
18
20
21namespace {
22
23AST_MATCHER(Stmt, isMacroExpansion) {
24 SourceManager &SM = Finder->getASTContext().getSourceManager();
25 SourceLocation Loc = Node.getBeginLoc();
26 return SM.isMacroBodyExpansion(Loc) || SM.isMacroArgExpansion(Loc);
27}
28
29bool isNULLMacroExpansion(const Stmt *Statement, ASTContext &Context) {
30 SourceManager &SM = Context.getSourceManager();
31 const LangOptions &LO = Context.getLangOpts();
32 SourceLocation Loc = Statement->getBeginLoc();
33 return SM.isMacroBodyExpansion(Loc) &&
34 Lexer::getImmediateMacroName(Loc, SM, LO) == "NULL";
35}
36
37AST_MATCHER(Stmt, isNULLMacroExpansion) {
38 return isNULLMacroExpansion(&Node, Finder->getASTContext());
39}
40
41StringRef getZeroLiteralToCompareWithForType(CastKind CastExprKind,
42 QualType Type,
43 ASTContext &Context) {
44 switch (CastExprKind) {
45 case CK_IntegralToBoolean:
46 return Type->isUnsignedIntegerType() ? "0u" : "0";
47
48 case CK_FloatingToBoolean:
49 return Context.hasSameType(Type, Context.FloatTy) ? "0.0f" : "0.0";
50
51 case CK_PointerToBoolean:
52 case CK_MemberPointerToBoolean: // Fall-through on purpose.
53 return Context.getLangOpts().CPlusPlus11 ? "nullptr" : "0";
54
55 default:
56 llvm_unreachable("Unexpected cast kind");
57 }
58}
59
60bool isUnaryLogicalNotOperator(const Stmt *Statement) {
61 const auto *UnaryOperatorExpr = dyn_cast<UnaryOperator>(Statement);
62 return UnaryOperatorExpr && UnaryOperatorExpr->getOpcode() == UO_LNot;
63}
64
65void fixGenericExprCastToBool(DiagnosticBuilder &Diag,
66 const ImplicitCastExpr *Cast, const Stmt *Parent,
67 ASTContext &Context) {
68 // In case of expressions like (! integer), we should remove the redundant not
69 // operator and use inverted comparison (integer == 0).
70 bool InvertComparison =
71 Parent != nullptr && isUnaryLogicalNotOperator(Parent);
72 if (InvertComparison) {
73 SourceLocation ParentStartLoc = Parent->getBeginLoc();
74 SourceLocation ParentEndLoc =
75 cast<UnaryOperator>(Parent)->getSubExpr()->getBeginLoc();
76 Diag << FixItHint::CreateRemoval(
77 CharSourceRange::getCharRange(ParentStartLoc, ParentEndLoc));
78
79 Parent = Context.getParents(*Parent)[0].get<Stmt>();
80 }
81
82 const Expr *SubExpr = Cast->getSubExpr();
83
84 bool NeedInnerParens =
85 SubExpr != nullptr && utils::fixit::areParensNeededForStatement(*SubExpr);
86 bool NeedOuterParens =
88
89 std::string StartLocInsertion;
90
91 if (NeedOuterParens) {
92 StartLocInsertion += "(";
93 }
94 if (NeedInnerParens) {
95 StartLocInsertion += "(";
96 }
97
98 if (!StartLocInsertion.empty()) {
99 Diag << FixItHint::CreateInsertion(Cast->getBeginLoc(), StartLocInsertion);
100 }
101
102 std::string EndLocInsertion;
103
104 if (NeedInnerParens) {
105 EndLocInsertion += ")";
106 }
107
108 if (InvertComparison) {
109 EndLocInsertion += " == ";
110 } else {
111 EndLocInsertion += " != ";
112 }
113
114 EndLocInsertion += getZeroLiteralToCompareWithForType(
115 Cast->getCastKind(), SubExpr->getType(), Context);
116
117 if (NeedOuterParens) {
118 EndLocInsertion += ")";
119 }
120
121 SourceLocation EndLoc = Lexer::getLocForEndOfToken(
122 Cast->getEndLoc(), 0, Context.getSourceManager(), Context.getLangOpts());
123 Diag << FixItHint::CreateInsertion(EndLoc, EndLocInsertion);
124}
125
126StringRef getEquivalentBoolLiteralForExpr(const Expr *Expression,
127 ASTContext &Context) {
128 if (isNULLMacroExpansion(Expression, Context)) {
129 return "false";
130 }
131
132 if (const auto *IntLit =
133 dyn_cast<IntegerLiteral>(Expression->IgnoreParens())) {
134 return (IntLit->getValue() == 0) ? "false" : "true";
135 }
136
137 if (const auto *FloatLit = dyn_cast<FloatingLiteral>(Expression)) {
138 llvm::APFloat FloatLitAbsValue = FloatLit->getValue();
139 FloatLitAbsValue.clearSign();
140 return (FloatLitAbsValue.bitcastToAPInt() == 0) ? "false" : "true";
141 }
142
143 if (const auto *CharLit = dyn_cast<CharacterLiteral>(Expression)) {
144 return (CharLit->getValue() == 0) ? "false" : "true";
145 }
146
147 if (isa<StringLiteral>(Expression->IgnoreCasts())) {
148 return "true";
149 }
150
151 return {};
152}
153
154bool needsSpacePrefix(SourceLocation Loc, ASTContext &Context) {
155 SourceRange PrefixRange(Loc.getLocWithOffset(-1), Loc);
156 StringRef SpaceBeforeStmtStr = Lexer::getSourceText(
157 CharSourceRange::getCharRange(PrefixRange), Context.getSourceManager(),
158 Context.getLangOpts(), nullptr);
159 if (SpaceBeforeStmtStr.empty())
160 return true;
161
162 const StringRef AllowedCharacters(" \t\n\v\f\r(){}[]<>;,+=-|&~!^*/");
163 return !AllowedCharacters.contains(SpaceBeforeStmtStr.back());
164}
165
166void fixGenericExprCastFromBool(DiagnosticBuilder &Diag,
167 const ImplicitCastExpr *Cast,
168 ASTContext &Context, StringRef OtherType) {
169 const Expr *SubExpr = Cast->getSubExpr();
170 const bool NeedParens = !isa<ParenExpr>(SubExpr->IgnoreImplicit());
171 const bool NeedSpace = needsSpacePrefix(Cast->getBeginLoc(), Context);
172
173 Diag << FixItHint::CreateInsertion(
174 Cast->getBeginLoc(), (Twine() + (NeedSpace ? " " : "") + "static_cast<" +
175 OtherType + ">" + (NeedParens ? "(" : ""))
176 .str());
177
178 if (NeedParens) {
179 SourceLocation EndLoc = Lexer::getLocForEndOfToken(
180 Cast->getEndLoc(), 0, Context.getSourceManager(),
181 Context.getLangOpts());
182
183 Diag << FixItHint::CreateInsertion(EndLoc, ")");
184 }
185}
186
187StringRef getEquivalentForBoolLiteral(const CXXBoolLiteralExpr *BoolLiteral,
188 QualType DestType, ASTContext &Context) {
189 // Prior to C++11, false literal could be implicitly converted to pointer.
190 if (!Context.getLangOpts().CPlusPlus11 &&
191 (DestType->isPointerType() || DestType->isMemberPointerType()) &&
192 BoolLiteral->getValue() == false) {
193 return "0";
194 }
195
196 if (DestType->isFloatingType()) {
197 if (Context.hasSameType(DestType, Context.FloatTy)) {
198 return BoolLiteral->getValue() ? "1.0f" : "0.0f";
199 }
200 return BoolLiteral->getValue() ? "1.0" : "0.0";
201 }
202
203 if (DestType->isUnsignedIntegerType()) {
204 return BoolLiteral->getValue() ? "1u" : "0u";
205 }
206 return BoolLiteral->getValue() ? "1" : "0";
207}
208
209bool isCastAllowedInCondition(const ImplicitCastExpr *Cast,
210 ASTContext &Context) {
211 std::queue<const Stmt *> Q;
212 Q.push(Cast);
213
214 TraversalKindScope RAII(Context, TK_AsIs);
215
216 while (!Q.empty()) {
217 for (const auto &N : Context.getParents(*Q.front())) {
218 const Stmt *S = N.get<Stmt>();
219 if (!S)
220 return false;
221 if (isa<IfStmt>(S) || isa<ConditionalOperator>(S) || isa<ForStmt>(S) ||
222 isa<WhileStmt>(S) || isa<DoStmt>(S) ||
223 isa<BinaryConditionalOperator>(S))
224 return true;
225 if (isa<ParenExpr>(S) || isa<ImplicitCastExpr>(S) ||
226 isUnaryLogicalNotOperator(S) ||
227 (isa<BinaryOperator>(S) && cast<BinaryOperator>(S)->isLogicalOp())) {
228 Q.push(S);
229 } else {
230 return false;
231 }
232 }
233 Q.pop();
234 }
235 return false;
236}
237
238} // anonymous namespace
239
241 StringRef Name, ClangTidyContext *Context)
242 : ClangTidyCheck(Name, Context),
243 AllowIntegerConditions(Options.get("AllowIntegerConditions", false)),
244 AllowPointerConditions(Options.get("AllowPointerConditions", false)) {}
245
248 Options.store(Opts, "AllowIntegerConditions", AllowIntegerConditions);
249 Options.store(Opts, "AllowPointerConditions", AllowPointerConditions);
250}
251
253 auto ExceptionCases =
254 expr(anyOf(allOf(isMacroExpansion(), unless(isNULLMacroExpansion())),
255 has(ignoringImplicit(
256 memberExpr(hasDeclaration(fieldDecl(hasBitWidth(1)))))),
257 hasParent(explicitCastExpr()),
258 expr(hasType(qualType().bind("type")),
259 hasParent(initListExpr(hasParent(explicitCastExpr(
260 hasType(qualType(equalsBoundNode("type"))))))))));
261 auto ImplicitCastFromBool = implicitCastExpr(
262 anyOf(hasCastKind(CK_IntegralCast), hasCastKind(CK_IntegralToFloating),
263 // Prior to C++11 cast from bool literal to pointer was allowed.
264 allOf(anyOf(hasCastKind(CK_NullToPointer),
265 hasCastKind(CK_NullToMemberPointer)),
266 hasSourceExpression(cxxBoolLiteral()))),
267 hasSourceExpression(expr(hasType(booleanType()))));
268 auto BoolXor =
269 binaryOperator(hasOperatorName("^"), hasLHS(ImplicitCastFromBool),
270 hasRHS(ImplicitCastFromBool));
271 Finder->addMatcher(
272 traverse(TK_AsIs,
273 implicitCastExpr(
274 anyOf(hasCastKind(CK_IntegralToBoolean),
275 hasCastKind(CK_FloatingToBoolean),
276 hasCastKind(CK_PointerToBoolean),
277 hasCastKind(CK_MemberPointerToBoolean)),
278 // Exclude case of using if or while statements with variable
279 // declaration, e.g.:
280 // if (int var = functionCall()) {}
281 unless(hasParent(
282 stmt(anyOf(ifStmt(), whileStmt()), has(declStmt())))),
283 // Exclude cases common to implicit cast to and from bool.
284 unless(ExceptionCases), unless(has(BoolXor)),
285 // Retrieve also parent statement, to check if we need
286 // additional parens in replacement.
287 optionally(hasParent(stmt().bind("parentStmt"))),
288 unless(isInTemplateInstantiation()),
289 unless(hasAncestor(functionTemplateDecl())))
290 .bind("implicitCastToBool")),
291 this);
292
293 auto BoolComparison = binaryOperator(hasAnyOperatorName("==", "!="),
294 hasLHS(ImplicitCastFromBool),
295 hasRHS(ImplicitCastFromBool));
296 auto BoolOpAssignment = binaryOperator(hasAnyOperatorName("|=", "&="),
297 hasLHS(expr(hasType(booleanType()))));
298 auto BitfieldAssignment = binaryOperator(
299 hasLHS(memberExpr(hasDeclaration(fieldDecl(hasBitWidth(1))))));
300 auto BitfieldConstruct = cxxConstructorDecl(hasDescendant(cxxCtorInitializer(
301 withInitializer(equalsBoundNode("implicitCastFromBool")),
302 forField(hasBitWidth(1)))));
303 Finder->addMatcher(
304 traverse(
305 TK_AsIs,
306 implicitCastExpr(
307 ImplicitCastFromBool, unless(ExceptionCases),
308 // Exclude comparisons of bools, as they are always cast to
309 // integers in such context:
310 // bool_expr_a == bool_expr_b
311 // bool_expr_a != bool_expr_b
312 unless(hasParent(
313 binaryOperator(anyOf(BoolComparison, BoolXor,
314 BoolOpAssignment, BitfieldAssignment)))),
315 implicitCastExpr().bind("implicitCastFromBool"),
316 unless(hasParent(BitfieldConstruct)),
317 // Check also for nested casts, for example: bool -> int -> float.
318 anyOf(hasParent(implicitCastExpr().bind("furtherImplicitCast")),
319 anything()),
320 unless(isInTemplateInstantiation()),
321 unless(hasAncestor(functionTemplateDecl())))),
322 this);
323}
324
326 const MatchFinder::MatchResult &Result) {
327
328 if (const auto *CastToBool =
329 Result.Nodes.getNodeAs<ImplicitCastExpr>("implicitCastToBool")) {
330 const auto *Parent = Result.Nodes.getNodeAs<Stmt>("parentStmt");
331 return handleCastToBool(CastToBool, Parent, *Result.Context);
332 }
333
334 if (const auto *CastFromBool =
335 Result.Nodes.getNodeAs<ImplicitCastExpr>("implicitCastFromBool")) {
336 const auto *NextImplicitCast =
337 Result.Nodes.getNodeAs<ImplicitCastExpr>("furtherImplicitCast");
338 return handleCastFromBool(CastFromBool, NextImplicitCast, *Result.Context);
339 }
340}
341
342void ImplicitBoolConversionCheck::handleCastToBool(const ImplicitCastExpr *Cast,
343 const Stmt *Parent,
344 ASTContext &Context) {
345 if (AllowPointerConditions &&
346 (Cast->getCastKind() == CK_PointerToBoolean ||
347 Cast->getCastKind() == CK_MemberPointerToBoolean) &&
348 isCastAllowedInCondition(Cast, Context)) {
349 return;
350 }
351
352 if (AllowIntegerConditions && Cast->getCastKind() == CK_IntegralToBoolean &&
353 isCastAllowedInCondition(Cast, Context)) {
354 return;
355 }
356
357 auto Diag = diag(Cast->getBeginLoc(), "implicit conversion %0 -> 'bool'")
358 << Cast->getSubExpr()->getType();
359
360 StringRef EquivalentLiteral =
361 getEquivalentBoolLiteralForExpr(Cast->getSubExpr(), Context);
362 if (!EquivalentLiteral.empty()) {
363 Diag << tooling::fixit::createReplacement(*Cast, EquivalentLiteral);
364 } else {
365 fixGenericExprCastToBool(Diag, Cast, Parent, Context);
366 }
367}
368
369void ImplicitBoolConversionCheck::handleCastFromBool(
370 const ImplicitCastExpr *Cast, const ImplicitCastExpr *NextImplicitCast,
371 ASTContext &Context) {
372 QualType DestType =
373 NextImplicitCast ? NextImplicitCast->getType() : Cast->getType();
374 auto Diag = diag(Cast->getBeginLoc(), "implicit conversion 'bool' -> %0")
375 << DestType;
376
377 if (const auto *BoolLiteral =
378 dyn_cast<CXXBoolLiteralExpr>(Cast->getSubExpr()->IgnoreParens())) {
379 Diag << tooling::fixit::createReplacement(
380 *Cast, getEquivalentForBoolLiteral(BoolLiteral, DestType, Context));
381 } else {
382 fixGenericExprCastFromBool(Diag, Cast, Context, DestType.getAsString());
383 }
384}
385
386} // namespace clang::tidy::readability
llvm::SmallString< 256U > Name
const Node * Parent
NodeType Type
SourceLocation Loc
::clang::DynTypedNode Node
void store(ClangTidyOptions::OptionMap &Options, StringRef LocalName, StringRef Value) const
Stores an option with the check-local name LocalName with string value Value to Options.
Base class for all clang-tidy checks.
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check's name.
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
Should store all options supported by this check with their current values or default values for opti...
ImplicitBoolConversionCheck(StringRef Name, ClangTidyContext *Context)
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
bool areParensNeededForStatement(const Stmt &Node)
llvm::StringMap< ClangTidyValue > OptionMap