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 | |
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> |
17 | |
18 | using namespace clang::ast_matchers; |
19 | |
20 | namespace clang::tidy::readability { |
21 | |
22 | namespace { |
23 | |
24 | AST_MATCHER(Stmt, isMacroExpansion) { |
25 | SourceManager &SM = Finder->getASTContext().getSourceManager(); |
26 | SourceLocation Loc = Node.getBeginLoc(); |
27 | return SM.isMacroBodyExpansion(Loc) || SM.isMacroArgExpansion(Loc); |
28 | } |
29 | |
30 | AST_MATCHER(Stmt, isC23) { return Finder->getASTContext().getLangOpts().C23; } |
31 | |
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, LangOpts: LO) == "NULL"; |
38 | } |
39 | |
40 | AST_MATCHER(Stmt, isNULLMacroExpansion) { |
41 | return isNULLMacroExpansion(Statement: &Node, Context&: Finder->getASTContext()); |
42 | } |
43 | |
44 | StringRef getZeroLiteralToCompareWithForType(CastKind CastExprKind, |
45 | QualType Type, |
46 | ASTContext &Context) { |
47 | switch (CastExprKind) { |
48 | case CK_IntegralToBoolean: |
49 | return Type->isUnsignedIntegerType() ? "0u": "0"; |
50 | |
51 | case CK_FloatingToBoolean: |
52 | return Context.hasSameType(Type, Context.FloatTy) ? "0.0f": "0.0"; |
53 | |
54 | case CK_PointerToBoolean: |
55 | case CK_MemberPointerToBoolean: // Fall-through on purpose. |
56 | return (Context.getLangOpts().CPlusPlus11 || Context.getLangOpts().C23) |
57 | ? "nullptr" |
58 | : "0"; |
59 | |
60 | default: |
61 | llvm_unreachable("Unexpected cast kind"); |
62 | } |
63 | } |
64 | |
65 | bool isUnaryLogicalNotOperator(const Stmt *Statement) { |
66 | const auto *UnaryOperatorExpr = dyn_cast<UnaryOperator>(Val: Statement); |
67 | return UnaryOperatorExpr && UnaryOperatorExpr->getOpcode() == UO_LNot; |
68 | } |
69 | |
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(Statement: Parent); |
78 | if (InvertComparison) { |
79 | SourceLocation ParentStartLoc = Parent->getBeginLoc(); |
80 | SourceLocation ParentEndLoc = |
81 | cast<UnaryOperator>(Val: Parent)->getSubExpr()->getBeginLoc(); |
82 | Diag << FixItHint::CreateRemoval( |
83 | RemoveRange: CharSourceRange::getCharRange(B: ParentStartLoc, E: ParentEndLoc)); |
84 | |
85 | Parent = Context.getParents(Node: *Parent)[0].get<Stmt>(); |
86 | } |
87 | |
88 | const Expr *SubExpr = Cast->getSubExpr(); |
89 | |
90 | bool NeedInnerParens = utils::fixit::areParensNeededForStatement(*SubExpr); |
91 | bool NeedOuterParens = |
92 | Parent != nullptr && utils::fixit::areParensNeededForStatement(Node: *Parent); |
93 | |
94 | std::string StartLocInsertion; |
95 | |
96 | if (NeedOuterParens) { |
97 | StartLocInsertion += "("; |
98 | } |
99 | if (NeedInnerParens) { |
100 | StartLocInsertion += "("; |
101 | } |
102 | |
103 | if (!StartLocInsertion.empty()) { |
104 | Diag << FixItHint::CreateInsertion(InsertionLoc: Cast->getBeginLoc(), Code: StartLocInsertion); |
105 | } |
106 | |
107 | std::string EndLocInsertion; |
108 | |
109 | if (NeedInnerParens) { |
110 | EndLocInsertion += ")"; |
111 | } |
112 | |
113 | if (InvertComparison) { |
114 | EndLocInsertion += " == "; |
115 | } else { |
116 | EndLocInsertion += " != "; |
117 | } |
118 | |
119 | const StringRef ZeroLiteral = getZeroLiteralToCompareWithForType( |
120 | Cast->getCastKind(), SubExpr->getType(), Context); |
121 | |
122 | if (UseUpperCaseLiteralSuffix) |
123 | EndLocInsertion += ZeroLiteral.upper(); |
124 | else |
125 | EndLocInsertion += ZeroLiteral; |
126 | |
127 | if (NeedOuterParens) { |
128 | EndLocInsertion += ")"; |
129 | } |
130 | |
131 | SourceLocation EndLoc = Lexer::getLocForEndOfToken( |
132 | Loc: Cast->getEndLoc(), Offset: 0, SM: Context.getSourceManager(), LangOpts: Context.getLangOpts()); |
133 | Diag << FixItHint::CreateInsertion(InsertionLoc: EndLoc, Code: EndLocInsertion); |
134 | } |
135 | |
136 | StringRef getEquivalentBoolLiteralForExpr(const Expr *Expression, |
137 | ASTContext &Context) { |
138 | if (isNULLMacroExpansion(Expression, Context)) { |
139 | return "false"; |
140 | } |
141 | |
142 | if (const auto *IntLit = |
143 | dyn_cast<IntegerLiteral>(Val: Expression->IgnoreParens())) { |
144 | return (IntLit->getValue() == 0) ? "false": "true"; |
145 | } |
146 | |
147 | if (const auto *FloatLit = dyn_cast<FloatingLiteral>(Val: Expression)) { |
148 | llvm::APFloat FloatLitAbsValue = FloatLit->getValue(); |
149 | FloatLitAbsValue.clearSign(); |
150 | return (FloatLitAbsValue.bitcastToAPInt() == 0) ? "false": "true"; |
151 | } |
152 | |
153 | if (const auto *CharLit = dyn_cast<CharacterLiteral>(Val: Expression)) { |
154 | return (CharLit->getValue() == 0) ? "false": "true"; |
155 | } |
156 | |
157 | if (isa<StringLiteral>(Val: Expression->IgnoreCasts())) { |
158 | return "true"; |
159 | } |
160 | |
161 | return {}; |
162 | } |
163 | |
164 | bool needsSpacePrefix(SourceLocation Loc, ASTContext &Context) { |
165 | SourceRange PrefixRange(Loc.getLocWithOffset(Offset: -1), Loc); |
166 | StringRef SpaceBeforeStmtStr = Lexer::getSourceText( |
167 | Range: CharSourceRange::getCharRange(R: PrefixRange), SM: Context.getSourceManager(), |
168 | LangOpts: Context.getLangOpts(), Invalid: nullptr); |
169 | if (SpaceBeforeStmtStr.empty()) |
170 | return true; |
171 | |
172 | const StringRef AllowedCharacters(" \t\n\v\f\r(){}[]<>;,+=-|&~!^*/"); |
173 | return !AllowedCharacters.contains(C: SpaceBeforeStmtStr.back()); |
174 | } |
175 | |
176 | void fixGenericExprCastFromBool(DiagnosticBuilder &Diag, |
177 | const ImplicitCastExpr *Cast, |
178 | ASTContext &Context, StringRef OtherType) { |
179 | if (!Context.getLangOpts().CPlusPlus) { |
180 | Diag << FixItHint::CreateInsertion(InsertionLoc: Cast->getBeginLoc(), |
181 | Code: (Twine("(") + OtherType + ")").str()); |
182 | return; |
183 | } |
184 | |
185 | const Expr *SubExpr = Cast->getSubExpr(); |
186 | const bool NeedParens = !isa<ParenExpr>(Val: SubExpr->IgnoreImplicit()); |
187 | const bool NeedSpace = needsSpacePrefix(Loc: Cast->getBeginLoc(), Context); |
188 | |
189 | Diag << FixItHint::CreateInsertion( |
190 | InsertionLoc: Cast->getBeginLoc(), Code: (Twine() + (NeedSpace ? " ": "") + "static_cast<"+ |
191 | OtherType + ">"+ (NeedParens ? "(": "")) |
192 | .str()); |
193 | |
194 | if (NeedParens) { |
195 | SourceLocation EndLoc = Lexer::getLocForEndOfToken( |
196 | Loc: Cast->getEndLoc(), Offset: 0, SM: Context.getSourceManager(), |
197 | LangOpts: Context.getLangOpts()); |
198 | |
199 | Diag << FixItHint::CreateInsertion(InsertionLoc: EndLoc, Code: ")"); |
200 | } |
201 | } |
202 | |
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"; |
210 | } |
211 | |
212 | if (DestType->isFloatingType()) { |
213 | if (Context.hasSameType(DestType, Context.FloatTy)) { |
214 | return BoolLiteral->getValue() ? "1.0f": "0.0f"; |
215 | } |
216 | return BoolLiteral->getValue() ? "1.0": "0.0"; |
217 | } |
218 | |
219 | if (DestType->isUnsignedIntegerType()) { |
220 | return BoolLiteral->getValue() ? "1u": "0u"; |
221 | } |
222 | return BoolLiteral->getValue() ? "1": "0"; |
223 | } |
224 | |
225 | bool isCastAllowedInCondition(const ImplicitCastExpr *Cast, |
226 | ASTContext &Context) { |
227 | std::queue<const Stmt *> Q; |
228 | Q.push(Cast); |
229 | |
230 | TraversalKindScope RAII(Context, TK_AsIs); |
231 | |
232 | while (!Q.empty()) { |
233 | for (const auto &N : Context.getParents(Node: *Q.front())) { |
234 | const Stmt *S = N.get<Stmt>(); |
235 | if (!S) |
236 | return false; |
237 | if (isa<IfStmt>(Val: S) || isa<ConditionalOperator>(Val: S) || isa<ForStmt>(Val: S) || |
238 | isa<WhileStmt>(Val: S) || isa<DoStmt>(Val: S) || |
239 | isa<BinaryConditionalOperator>(Val: S)) |
240 | return true; |
241 | if (isa<ParenExpr>(Val: S) || isa<ImplicitCastExpr>(Val: S) || |
242 | isUnaryLogicalNotOperator(Statement: S) || |
243 | (isa<BinaryOperator>(Val: S) && cast<BinaryOperator>(Val: S)->isLogicalOp())) { |
244 | Q.push(x: S); |
245 | } else { |
246 | return false; |
247 | } |
248 | } |
249 | Q.pop(); |
250 | } |
251 | return false; |
252 | } |
253 | |
254 | } // anonymous namespace |
255 | |
256 | ImplicitBoolConversionCheck::ImplicitBoolConversionCheck( |
257 | StringRef Name, ClangTidyContext *Context) |
258 | : ClangTidyCheck(Name, Context), |
259 | AllowIntegerConditions(Options.get(LocalName: "AllowIntegerConditions", Default: false)), |
260 | AllowPointerConditions(Options.get(LocalName: "AllowPointerConditions", Default: false)), |
261 | UseUpperCaseLiteralSuffix( |
262 | Options.get(LocalName: "UseUpperCaseLiteralSuffix", Default: false)) {} |
263 | |
264 | void ImplicitBoolConversionCheck::storeOptions( |
265 | ClangTidyOptions::OptionMap &Opts) { |
266 | Options.store(Options&: Opts, LocalName: "AllowIntegerConditions", Value: AllowIntegerConditions); |
267 | Options.store(Options&: Opts, LocalName: "AllowPointerConditions", Value: AllowPointerConditions); |
268 | Options.store(Options&: Opts, LocalName: "UseUpperCaseLiteralSuffix", Value: UseUpperCaseLiteralSuffix); |
269 | } |
270 | |
271 | void ImplicitBoolConversionCheck::registerMatchers(MatchFinder *Finder) { |
272 | auto ExceptionCases = |
273 | expr(anyOf(allOf(isMacroExpansion(), unless(isNULLMacroExpansion())), |
274 | has(ignoringImplicit( |
275 | InnerMatcher: memberExpr(hasDeclaration(InnerMatcher: fieldDecl(hasBitWidth(Width: 1)))))), |
276 | hasParent(explicitCastExpr()), |
277 | expr(hasType(InnerMatcher: qualType().bind(ID: "type")), |
278 | hasParent(initListExpr(hasParent(explicitCastExpr( |
279 | hasType(InnerMatcher: qualType(equalsBoundNode(ID: "type")))))))))); |
280 | auto ImplicitCastFromBool = implicitCastExpr( |
281 | anyOf(hasCastKind(Kind: CK_IntegralCast), hasCastKind(Kind: CK_IntegralToFloating), |
282 | // Prior to C++11 cast from bool literal to pointer was allowed. |
283 | allOf(anyOf(hasCastKind(Kind: CK_NullToPointer), |
284 | hasCastKind(Kind: CK_NullToMemberPointer)), |
285 | hasSourceExpression(InnerMatcher: cxxBoolLiteral()))), |
286 | hasSourceExpression(InnerMatcher: expr(hasType(InnerMatcher: booleanType())))); |
287 | auto BoolXor = |
288 | binaryOperator(hasOperatorName(Name: "^"), hasLHS(InnerMatcher: ImplicitCastFromBool), |
289 | hasRHS(InnerMatcher: ImplicitCastFromBool)); |
290 | auto ComparisonInCall = allOf( |
291 | hasParent(callExpr()), |
292 | hasSourceExpression(InnerMatcher: binaryOperator(hasAnyOperatorName("==", "!=")))); |
293 | |
294 | auto IsInCompilerGeneratedFunction = hasAncestor(namedDecl(anyOf( |
295 | isImplicit(), functionDecl(isDefaulted()), functionTemplateDecl()))); |
296 | |
297 | Finder->addMatcher( |
298 | NodeMatch: traverse(TK: TK_AsIs, |
299 | InnerMatcher: implicitCastExpr( |
300 | anyOf(hasCastKind(Kind: CK_IntegralToBoolean), |
301 | hasCastKind(Kind: CK_FloatingToBoolean), |
302 | hasCastKind(Kind: CK_PointerToBoolean), |
303 | hasCastKind(Kind: CK_MemberPointerToBoolean)), |
304 | // Exclude cases of C23 comparison result. |
305 | unless(allOf(isC23(), |
306 | hasSourceExpression(InnerMatcher: ignoringParens( |
307 | InnerMatcher: 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(ID: "parentStmt"))), |
321 | unless(isInTemplateInstantiation()), |
322 | unless(IsInCompilerGeneratedFunction)) |
323 | .bind(ID: "implicitCastToBool")), |
324 | Action: this); |
325 | |
326 | auto BoolComparison = binaryOperator(hasAnyOperatorName("==", "!="), |
327 | hasLHS(InnerMatcher: ImplicitCastFromBool), |
328 | hasRHS(InnerMatcher: ImplicitCastFromBool)); |
329 | auto BoolOpAssignment = binaryOperator(hasAnyOperatorName("|=", "&="), |
330 | hasLHS(InnerMatcher: expr(hasType(InnerMatcher: booleanType())))); |
331 | auto BitfieldAssignment = binaryOperator( |
332 | hasLHS(InnerMatcher: memberExpr(hasDeclaration(InnerMatcher: fieldDecl(hasBitWidth(Width: 1)))))); |
333 | auto BitfieldConstruct = cxxConstructorDecl(hasDescendant(cxxCtorInitializer( |
334 | withInitializer(InnerMatcher: equalsBoundNode(ID: "implicitCastFromBool")), |
335 | forField(InnerMatcher: hasBitWidth(Width: 1))))); |
336 | Finder->addMatcher( |
337 | NodeMatch: traverse( |
338 | TK: TK_AsIs, |
339 | InnerMatcher: 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(ID: "implicitCastFromBool"), |
349 | unless(hasParent(BitfieldConstruct)), |
350 | // Check also for nested casts, for example: bool -> int -> float. |
351 | anyOf(hasParent(implicitCastExpr().bind(ID: "furtherImplicitCast")), |
352 | anything()), |
353 | unless(isInTemplateInstantiation()), |
354 | unless(IsInCompilerGeneratedFunction))), |
355 | Action: this); |
356 | } |
357 | |
358 | void ImplicitBoolConversionCheck::check( |
359 | const MatchFinder::MatchResult &Result) { |
360 | |
361 | if (const auto *CastToBool = |
362 | Result.Nodes.getNodeAs<ImplicitCastExpr>(ID: "implicitCastToBool")) { |
363 | const auto *Parent = Result.Nodes.getNodeAs<Stmt>(ID: "parentStmt"); |
364 | return handleCastToBool(CastExpression: CastToBool, ParentStatement: Parent, Context&: *Result.Context); |
365 | } |
366 | |
367 | if (const auto *CastFromBool = |
368 | Result.Nodes.getNodeAs<ImplicitCastExpr>(ID: "implicitCastFromBool")) { |
369 | const auto *NextImplicitCast = |
370 | Result.Nodes.getNodeAs<ImplicitCastExpr>(ID: "furtherImplicitCast"); |
371 | return handleCastFromBool(Cast: CastFromBool, NextImplicitCast, Context&: *Result.Context); |
372 | } |
373 | } |
374 | |
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; |
383 | } |
384 | |
385 | if (AllowIntegerConditions && Cast->getCastKind() == CK_IntegralToBoolean && |
386 | isCastAllowedInCondition(Cast, Context)) { |
387 | return; |
388 | } |
389 | |
390 | auto Diag = diag(Loc: Cast->getBeginLoc(), Description: "implicit conversion %0 -> 'bool'") |
391 | << Cast->getSubExpr()->getType(); |
392 | |
393 | StringRef EquivalentLiteral = |
394 | getEquivalentBoolLiteralForExpr(Cast->getSubExpr(), Context); |
395 | if (!EquivalentLiteral.empty()) { |
396 | Diag << tooling::fixit::createReplacement(Destination: *Cast, Source: EquivalentLiteral); |
397 | } else { |
398 | fixGenericExprCastToBool(Diag, Cast, Parent, Context, |
399 | UseUpperCaseLiteralSuffix); |
400 | } |
401 | } |
402 | |
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(Loc: Cast->getBeginLoc(), Description: "implicit conversion 'bool' -> %0") |
409 | << DestType; |
410 | |
411 | if (const auto *BoolLiteral = |
412 | dyn_cast<CXXBoolLiteralExpr>(Cast->getSubExpr()->IgnoreParens())) { |
413 | |
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); |
422 | |
423 | } else { |
424 | fixGenericExprCastFromBool(Diag, Cast, Context, DestType.getAsString()); |
425 | } |
426 | } |
427 | |
428 | } // namespace clang::tidy::readability |
429 |
Definitions
- isMacroExpansion
- isC23
- isNULLMacroExpansion
- isNULLMacroExpansion
- getZeroLiteralToCompareWithForType
- isUnaryLogicalNotOperator
- fixGenericExprCastToBool
- getEquivalentBoolLiteralForExpr
- needsSpacePrefix
- fixGenericExprCastFromBool
- getEquivalentForBoolLiteral
- isCastAllowedInCondition
- ImplicitBoolConversionCheck
- storeOptions
- registerMatchers
- check
- handleCastToBool
Improve your Profiling and Debugging skills
Find out more