[TargetVersion] Only enable on RISC-V and AArch64 (#115991)
[llvm-project.git] / clang-tools-extra / clang-tidy / readability / ImplicitBoolConversionCheck.cpp
blobf9fd1d903e231e1550f7097280506293313d947e
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 //===----------------------------------------------------------------------===//
9 #include "ImplicitBoolConversionCheck.h"
10 #include "../utils/FixItHintUtils.h"
11 #include "clang/AST/ASTContext.h"
12 #include "clang/ASTMatchers/ASTMatchFinder.h"
13 #include "clang/ASTMatchers/ASTMatchers.h"
14 #include "clang/Lex/Lexer.h"
15 #include "clang/Tooling/FixIt.h"
16 #include <queue>
18 using namespace clang::ast_matchers;
20 namespace clang::tidy::readability {
22 namespace {
24 AST_MATCHER(Stmt, isMacroExpansion) {
25 SourceManager &SM = Finder->getASTContext().getSourceManager();
26 SourceLocation Loc = Node.getBeginLoc();
27 return SM.isMacroBodyExpansion(Loc) || SM.isMacroArgExpansion(Loc);
30 AST_MATCHER(Stmt, isC23) { return Finder->getASTContext().getLangOpts().C23; }
32 bool isNULLMacroExpansion(const Stmt *Statement, ASTContext &Context) {
33 SourceManager &SM = Context.getSourceManager();
34 const LangOptions &LO = Context.getLangOpts();
35 SourceLocation Loc = Statement->getBeginLoc();
36 return SM.isMacroBodyExpansion(Loc) &&
37 Lexer::getImmediateMacroName(Loc, SM, LO) == "NULL";
40 AST_MATCHER(Stmt, isNULLMacroExpansion) {
41 return isNULLMacroExpansion(&Node, Finder->getASTContext());
44 StringRef getZeroLiteralToCompareWithForType(CastKind CastExprKind,
45 QualType Type,
46 ASTContext &Context) {
47 switch (CastExprKind) {
48 case CK_IntegralToBoolean:
49 return Type->isUnsignedIntegerType() ? "0u" : "0";
51 case CK_FloatingToBoolean:
52 return Context.hasSameType(Type, Context.FloatTy) ? "0.0f" : "0.0";
54 case CK_PointerToBoolean:
55 case CK_MemberPointerToBoolean: // Fall-through on purpose.
56 return (Context.getLangOpts().CPlusPlus11 || Context.getLangOpts().C23)
57 ? "nullptr"
58 : "0";
60 default:
61 llvm_unreachable("Unexpected cast kind");
65 bool isUnaryLogicalNotOperator(const Stmt *Statement) {
66 const auto *UnaryOperatorExpr = dyn_cast<UnaryOperator>(Statement);
67 return UnaryOperatorExpr && UnaryOperatorExpr->getOpcode() == UO_LNot;
70 void fixGenericExprCastToBool(DiagnosticBuilder &Diag,
71 const ImplicitCastExpr *Cast, const Stmt *Parent,
72 ASTContext &Context,
73 bool UseUpperCaseLiteralSuffix) {
74 // In case of expressions like (! integer), we should remove the redundant not
75 // operator and use inverted comparison (integer == 0).
76 bool InvertComparison =
77 Parent != nullptr && isUnaryLogicalNotOperator(Parent);
78 if (InvertComparison) {
79 SourceLocation ParentStartLoc = Parent->getBeginLoc();
80 SourceLocation ParentEndLoc =
81 cast<UnaryOperator>(Parent)->getSubExpr()->getBeginLoc();
82 Diag << FixItHint::CreateRemoval(
83 CharSourceRange::getCharRange(ParentStartLoc, ParentEndLoc));
85 Parent = Context.getParents(*Parent)[0].get<Stmt>();
88 const Expr *SubExpr = Cast->getSubExpr();
90 bool NeedInnerParens = utils::fixit::areParensNeededForStatement(*SubExpr);
91 bool NeedOuterParens =
92 Parent != nullptr && utils::fixit::areParensNeededForStatement(*Parent);
94 std::string StartLocInsertion;
96 if (NeedOuterParens) {
97 StartLocInsertion += "(";
99 if (NeedInnerParens) {
100 StartLocInsertion += "(";
103 if (!StartLocInsertion.empty()) {
104 Diag << FixItHint::CreateInsertion(Cast->getBeginLoc(), StartLocInsertion);
107 std::string EndLocInsertion;
109 if (NeedInnerParens) {
110 EndLocInsertion += ")";
113 if (InvertComparison) {
114 EndLocInsertion += " == ";
115 } else {
116 EndLocInsertion += " != ";
119 const StringRef ZeroLiteral = getZeroLiteralToCompareWithForType(
120 Cast->getCastKind(), SubExpr->getType(), Context);
122 if (UseUpperCaseLiteralSuffix)
123 EndLocInsertion += ZeroLiteral.upper();
124 else
125 EndLocInsertion += ZeroLiteral;
127 if (NeedOuterParens) {
128 EndLocInsertion += ")";
131 SourceLocation EndLoc = Lexer::getLocForEndOfToken(
132 Cast->getEndLoc(), 0, Context.getSourceManager(), Context.getLangOpts());
133 Diag << FixItHint::CreateInsertion(EndLoc, EndLocInsertion);
136 StringRef getEquivalentBoolLiteralForExpr(const Expr *Expression,
137 ASTContext &Context) {
138 if (isNULLMacroExpansion(Expression, Context)) {
139 return "false";
142 if (const auto *IntLit =
143 dyn_cast<IntegerLiteral>(Expression->IgnoreParens())) {
144 return (IntLit->getValue() == 0) ? "false" : "true";
147 if (const auto *FloatLit = dyn_cast<FloatingLiteral>(Expression)) {
148 llvm::APFloat FloatLitAbsValue = FloatLit->getValue();
149 FloatLitAbsValue.clearSign();
150 return (FloatLitAbsValue.bitcastToAPInt() == 0) ? "false" : "true";
153 if (const auto *CharLit = dyn_cast<CharacterLiteral>(Expression)) {
154 return (CharLit->getValue() == 0) ? "false" : "true";
157 if (isa<StringLiteral>(Expression->IgnoreCasts())) {
158 return "true";
161 return {};
164 bool needsSpacePrefix(SourceLocation Loc, ASTContext &Context) {
165 SourceRange PrefixRange(Loc.getLocWithOffset(-1), Loc);
166 StringRef SpaceBeforeStmtStr = Lexer::getSourceText(
167 CharSourceRange::getCharRange(PrefixRange), Context.getSourceManager(),
168 Context.getLangOpts(), nullptr);
169 if (SpaceBeforeStmtStr.empty())
170 return true;
172 const StringRef AllowedCharacters(" \t\n\v\f\r(){}[]<>;,+=-|&~!^*/");
173 return !AllowedCharacters.contains(SpaceBeforeStmtStr.back());
176 void fixGenericExprCastFromBool(DiagnosticBuilder &Diag,
177 const ImplicitCastExpr *Cast,
178 ASTContext &Context, StringRef OtherType) {
179 if (!Context.getLangOpts().CPlusPlus) {
180 Diag << FixItHint::CreateInsertion(Cast->getBeginLoc(),
181 (Twine("(") + OtherType + ")").str());
182 return;
185 const Expr *SubExpr = Cast->getSubExpr();
186 const bool NeedParens = !isa<ParenExpr>(SubExpr->IgnoreImplicit());
187 const bool NeedSpace = needsSpacePrefix(Cast->getBeginLoc(), Context);
189 Diag << FixItHint::CreateInsertion(
190 Cast->getBeginLoc(), (Twine() + (NeedSpace ? " " : "") + "static_cast<" +
191 OtherType + ">" + (NeedParens ? "(" : ""))
192 .str());
194 if (NeedParens) {
195 SourceLocation EndLoc = Lexer::getLocForEndOfToken(
196 Cast->getEndLoc(), 0, Context.getSourceManager(),
197 Context.getLangOpts());
199 Diag << FixItHint::CreateInsertion(EndLoc, ")");
203 StringRef getEquivalentForBoolLiteral(const CXXBoolLiteralExpr *BoolLiteral,
204 QualType DestType, ASTContext &Context) {
205 // Prior to C++11, false literal could be implicitly converted to pointer.
206 if (!Context.getLangOpts().CPlusPlus11 &&
207 (DestType->isPointerType() || DestType->isMemberPointerType()) &&
208 BoolLiteral->getValue() == false) {
209 return "0";
212 if (DestType->isFloatingType()) {
213 if (Context.hasSameType(DestType, Context.FloatTy)) {
214 return BoolLiteral->getValue() ? "1.0f" : "0.0f";
216 return BoolLiteral->getValue() ? "1.0" : "0.0";
219 if (DestType->isUnsignedIntegerType()) {
220 return BoolLiteral->getValue() ? "1u" : "0u";
222 return BoolLiteral->getValue() ? "1" : "0";
225 bool isCastAllowedInCondition(const ImplicitCastExpr *Cast,
226 ASTContext &Context) {
227 std::queue<const Stmt *> Q;
228 Q.push(Cast);
230 TraversalKindScope RAII(Context, TK_AsIs);
232 while (!Q.empty()) {
233 for (const auto &N : Context.getParents(*Q.front())) {
234 const Stmt *S = N.get<Stmt>();
235 if (!S)
236 return false;
237 if (isa<IfStmt>(S) || isa<ConditionalOperator>(S) || isa<ForStmt>(S) ||
238 isa<WhileStmt>(S) || isa<DoStmt>(S) ||
239 isa<BinaryConditionalOperator>(S))
240 return true;
241 if (isa<ParenExpr>(S) || isa<ImplicitCastExpr>(S) ||
242 isUnaryLogicalNotOperator(S) ||
243 (isa<BinaryOperator>(S) && cast<BinaryOperator>(S)->isLogicalOp())) {
244 Q.push(S);
245 } else {
246 return false;
249 Q.pop();
251 return false;
254 } // anonymous namespace
256 ImplicitBoolConversionCheck::ImplicitBoolConversionCheck(
257 StringRef Name, ClangTidyContext *Context)
258 : ClangTidyCheck(Name, Context),
259 AllowIntegerConditions(Options.get("AllowIntegerConditions", false)),
260 AllowPointerConditions(Options.get("AllowPointerConditions", false)),
261 UseUpperCaseLiteralSuffix(
262 Options.get("UseUpperCaseLiteralSuffix", false)) {}
264 void ImplicitBoolConversionCheck::storeOptions(
265 ClangTidyOptions::OptionMap &Opts) {
266 Options.store(Opts, "AllowIntegerConditions", AllowIntegerConditions);
267 Options.store(Opts, "AllowPointerConditions", AllowPointerConditions);
268 Options.store(Opts, "UseUpperCaseLiteralSuffix", UseUpperCaseLiteralSuffix);
271 void ImplicitBoolConversionCheck::registerMatchers(MatchFinder *Finder) {
272 auto ExceptionCases =
273 expr(anyOf(allOf(isMacroExpansion(), unless(isNULLMacroExpansion())),
274 has(ignoringImplicit(
275 memberExpr(hasDeclaration(fieldDecl(hasBitWidth(1)))))),
276 hasParent(explicitCastExpr()),
277 expr(hasType(qualType().bind("type")),
278 hasParent(initListExpr(hasParent(explicitCastExpr(
279 hasType(qualType(equalsBoundNode("type"))))))))));
280 auto ImplicitCastFromBool = implicitCastExpr(
281 anyOf(hasCastKind(CK_IntegralCast), hasCastKind(CK_IntegralToFloating),
282 // Prior to C++11 cast from bool literal to pointer was allowed.
283 allOf(anyOf(hasCastKind(CK_NullToPointer),
284 hasCastKind(CK_NullToMemberPointer)),
285 hasSourceExpression(cxxBoolLiteral()))),
286 hasSourceExpression(expr(hasType(booleanType()))));
287 auto BoolXor =
288 binaryOperator(hasOperatorName("^"), hasLHS(ImplicitCastFromBool),
289 hasRHS(ImplicitCastFromBool));
290 auto ComparisonInCall = allOf(
291 hasParent(callExpr()),
292 hasSourceExpression(binaryOperator(hasAnyOperatorName("==", "!="))));
294 auto IsInCompilerGeneratedFunction = hasAncestor(namedDecl(anyOf(
295 isImplicit(), functionDecl(isDefaulted()), functionTemplateDecl())));
297 Finder->addMatcher(
298 traverse(TK_AsIs,
299 implicitCastExpr(
300 anyOf(hasCastKind(CK_IntegralToBoolean),
301 hasCastKind(CK_FloatingToBoolean),
302 hasCastKind(CK_PointerToBoolean),
303 hasCastKind(CK_MemberPointerToBoolean)),
304 // Exclude cases of C23 comparison result.
305 unless(allOf(isC23(),
306 hasSourceExpression(ignoringParens(
307 binaryOperator(hasAnyOperatorName(
308 ">", ">=", "==", "!=", "<", "<=")))))),
309 // Exclude case of using if or while statements with variable
310 // declaration, e.g.:
311 // if (int var = functionCall()) {}
312 unless(hasParent(
313 stmt(anyOf(ifStmt(), whileStmt()), has(declStmt())))),
314 // Exclude cases common to implicit cast to and from bool.
315 unless(ExceptionCases), unless(has(BoolXor)),
316 // Exclude C23 cases common to implicit cast to bool.
317 unless(ComparisonInCall),
318 // Retrieve also parent statement, to check if we need
319 // additional parens in replacement.
320 optionally(hasParent(stmt().bind("parentStmt"))),
321 unless(isInTemplateInstantiation()),
322 unless(IsInCompilerGeneratedFunction))
323 .bind("implicitCastToBool")),
324 this);
326 auto BoolComparison = binaryOperator(hasAnyOperatorName("==", "!="),
327 hasLHS(ImplicitCastFromBool),
328 hasRHS(ImplicitCastFromBool));
329 auto BoolOpAssignment = binaryOperator(hasAnyOperatorName("|=", "&="),
330 hasLHS(expr(hasType(booleanType()))));
331 auto BitfieldAssignment = binaryOperator(
332 hasLHS(memberExpr(hasDeclaration(fieldDecl(hasBitWidth(1))))));
333 auto BitfieldConstruct = cxxConstructorDecl(hasDescendant(cxxCtorInitializer(
334 withInitializer(equalsBoundNode("implicitCastFromBool")),
335 forField(hasBitWidth(1)))));
336 Finder->addMatcher(
337 traverse(
338 TK_AsIs,
339 implicitCastExpr(
340 ImplicitCastFromBool, unless(ExceptionCases),
341 // Exclude comparisons of bools, as they are always cast to
342 // integers in such context:
343 // bool_expr_a == bool_expr_b
344 // bool_expr_a != bool_expr_b
345 unless(hasParent(
346 binaryOperator(anyOf(BoolComparison, BoolXor,
347 BoolOpAssignment, BitfieldAssignment)))),
348 implicitCastExpr().bind("implicitCastFromBool"),
349 unless(hasParent(BitfieldConstruct)),
350 // Check also for nested casts, for example: bool -> int -> float.
351 anyOf(hasParent(implicitCastExpr().bind("furtherImplicitCast")),
352 anything()),
353 unless(isInTemplateInstantiation()),
354 unless(IsInCompilerGeneratedFunction))),
355 this);
358 void ImplicitBoolConversionCheck::check(
359 const MatchFinder::MatchResult &Result) {
361 if (const auto *CastToBool =
362 Result.Nodes.getNodeAs<ImplicitCastExpr>("implicitCastToBool")) {
363 const auto *Parent = Result.Nodes.getNodeAs<Stmt>("parentStmt");
364 return handleCastToBool(CastToBool, Parent, *Result.Context);
367 if (const auto *CastFromBool =
368 Result.Nodes.getNodeAs<ImplicitCastExpr>("implicitCastFromBool")) {
369 const auto *NextImplicitCast =
370 Result.Nodes.getNodeAs<ImplicitCastExpr>("furtherImplicitCast");
371 return handleCastFromBool(CastFromBool, NextImplicitCast, *Result.Context);
375 void ImplicitBoolConversionCheck::handleCastToBool(const ImplicitCastExpr *Cast,
376 const Stmt *Parent,
377 ASTContext &Context) {
378 if (AllowPointerConditions &&
379 (Cast->getCastKind() == CK_PointerToBoolean ||
380 Cast->getCastKind() == CK_MemberPointerToBoolean) &&
381 isCastAllowedInCondition(Cast, Context)) {
382 return;
385 if (AllowIntegerConditions && Cast->getCastKind() == CK_IntegralToBoolean &&
386 isCastAllowedInCondition(Cast, Context)) {
387 return;
390 auto Diag = diag(Cast->getBeginLoc(), "implicit conversion %0 -> 'bool'")
391 << Cast->getSubExpr()->getType();
393 StringRef EquivalentLiteral =
394 getEquivalentBoolLiteralForExpr(Cast->getSubExpr(), Context);
395 if (!EquivalentLiteral.empty()) {
396 Diag << tooling::fixit::createReplacement(*Cast, EquivalentLiteral);
397 } else {
398 fixGenericExprCastToBool(Diag, Cast, Parent, Context,
399 UseUpperCaseLiteralSuffix);
403 void ImplicitBoolConversionCheck::handleCastFromBool(
404 const ImplicitCastExpr *Cast, const ImplicitCastExpr *NextImplicitCast,
405 ASTContext &Context) {
406 QualType DestType =
407 NextImplicitCast ? NextImplicitCast->getType() : Cast->getType();
408 auto Diag = diag(Cast->getBeginLoc(), "implicit conversion 'bool' -> %0")
409 << DestType;
411 if (const auto *BoolLiteral =
412 dyn_cast<CXXBoolLiteralExpr>(Cast->getSubExpr()->IgnoreParens())) {
414 const auto EquivalentForBoolLiteral =
415 getEquivalentForBoolLiteral(BoolLiteral, DestType, Context);
416 if (UseUpperCaseLiteralSuffix)
417 Diag << tooling::fixit::createReplacement(
418 *Cast, EquivalentForBoolLiteral.upper());
419 else
420 Diag << tooling::fixit::createReplacement(*Cast,
421 EquivalentForBoolLiteral);
423 } else {
424 fixGenericExprCastFromBool(Diag, Cast, Context, DestType.getAsString());
428 } // namespace clang::tidy::readability