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"
25 SourceManager &SM = Finder->getASTContext().getSourceManager();
26 SourceLocation
Loc =
Node.getBeginLoc();
27 return SM.isMacroBodyExpansion(
Loc) || SM.isMacroArgExpansion(
Loc);
30AST_MATCHER(Stmt, isC23) {
return Finder->getASTContext().getLangOpts().C23; }
32bool 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";
41 return isNULLMacroExpansion(&Node, Finder->getASTContext());
44StringRef getZeroLiteralToCompareWithForType(CastKind CastExprKind,
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:
56 return (Context.getLangOpts().CPlusPlus11 || Context.getLangOpts().C23)
61 llvm_unreachable(
"Unexpected cast kind");
65bool isUnaryLogicalNotOperator(
const Stmt *Statement) {
66 const auto *UnaryOperatorExpr = dyn_cast<UnaryOperator>(Statement);
67 return UnaryOperatorExpr && UnaryOperatorExpr->getOpcode() == UO_LNot;
70void fixGenericExprCastToBool(DiagnosticBuilder &Diag,
71 const ImplicitCastExpr *Cast,
const Stmt *
Parent,
73 bool UseUpperCaseLiteralSuffix) {
76 bool InvertComparison =
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));
88 const Expr *SubExpr = Cast->getSubExpr();
91 bool NeedOuterParens =
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 +=
" == ";
116 EndLocInsertion +=
" != ";
119 const StringRef ZeroLiteral = getZeroLiteralToCompareWithForType(
120 Cast->getCastKind(), SubExpr->getType(), Context);
122 if (UseUpperCaseLiteralSuffix)
123 EndLocInsertion += ZeroLiteral.upper();
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);
136StringRef getEquivalentBoolLiteralForExpr(
const Expr *Expression,
137 ASTContext &Context) {
138 if (isNULLMacroExpansion(Expression, Context)) {
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())) {
164bool 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())
172 const StringRef AllowedCharacters(
" \t\n\v\f\r(){}[]<>;,+=-|&~!^*/");
173 return !AllowedCharacters.contains(SpaceBeforeStmtStr.back());
176void 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());
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 ?
"(" :
""))
195 SourceLocation EndLoc = Lexer::getLocForEndOfToken(
196 Cast->getEndLoc(), 0, Context.getSourceManager(),
197 Context.getLangOpts());
199 Diag << FixItHint::CreateInsertion(EndLoc,
")");
203StringRef getEquivalentForBoolLiteral(
const CXXBoolLiteralExpr *BoolLiteral,
204 QualType DestType, ASTContext &Context) {
206 if (!Context.getLangOpts().CPlusPlus11 &&
207 (DestType->isPointerType() || DestType->isMemberPointerType()) &&
208 BoolLiteral->getValue() ==
false) {
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";
225bool isCastAllowedInCondition(
const ImplicitCastExpr *Cast,
226 ASTContext &Context) {
227 std::queue<const Stmt *> Q;
230 TraversalKindScope RAII(Context, TK_AsIs);
233 for (
const auto &N : Context.getParents(*Q.front())) {
234 const Stmt *S = N.get<Stmt>();
237 if (isa<IfStmt>(S) || isa<ConditionalOperator>(S) || isa<ForStmt>(S) ||
238 isa<WhileStmt>(S) || isa<DoStmt>(S) ||
239 isa<BinaryConditionalOperator>(S))
241 if (isa<ParenExpr>(S) || isa<ImplicitCastExpr>(S) ||
242 isUnaryLogicalNotOperator(S) ||
243 (isa<BinaryOperator>(S) && cast<BinaryOperator>(S)->isLogicalOp())) {
259 AllowIntegerConditions(Options.get(
"AllowIntegerConditions", false)),
260 AllowPointerConditions(Options.get(
"AllowPointerConditions", false)),
261 UseUpperCaseLiteralSuffix(
262 Options.get(
"UseUpperCaseLiteralSuffix", false)) {}
266 Options.
store(Opts,
"AllowIntegerConditions", AllowIntegerConditions);
267 Options.
store(Opts,
"AllowPointerConditions", AllowPointerConditions);
268 Options.
store(Opts,
"UseUpperCaseLiteralSuffix", UseUpperCaseLiteralSuffix);
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),
283 allOf(anyOf(hasCastKind(CK_NullToPointer),
284 hasCastKind(CK_NullToMemberPointer)),
285 hasSourceExpression(cxxBoolLiteral()))),
286 hasSourceExpression(expr(hasType(booleanType()))));
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())));
300 anyOf(hasCastKind(CK_IntegralToBoolean),
301 hasCastKind(CK_FloatingToBoolean),
302 hasCastKind(CK_PointerToBoolean),
303 hasCastKind(CK_MemberPointerToBoolean)),
305 unless(allOf(isC23(),
306 hasSourceExpression(ignoringParens(
307 binaryOperator(hasAnyOperatorName(
308 ">",
">=",
"==",
"!=",
"<",
"<=")))))),
313 stmt(anyOf(ifStmt(), whileStmt()), has(declStmt())))),
315 unless(ExceptionCases), unless(has(BoolXor)),
317 unless(ComparisonInCall),
320 optionally(hasParent(stmt().bind(
"parentStmt"))),
321 unless(isInTemplateInstantiation()),
322 unless(IsInCompilerGeneratedFunction))
323 .bind(
"implicitCastToBool")),
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)))));
340 ImplicitCastFromBool, unless(ExceptionCases),
346 binaryOperator(anyOf(BoolComparison, BoolXor,
347 BoolOpAssignment, BitfieldAssignment)))),
348 implicitCastExpr().bind(
"implicitCastFromBool"),
349 unless(hasParent(BitfieldConstruct)),
351 anyOf(hasParent(implicitCastExpr().bind(
"furtherImplicitCast")),
353 unless(isInTemplateInstantiation()),
354 unless(IsInCompilerGeneratedFunction))),
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);
375void ImplicitBoolConversionCheck::handleCastToBool(
const ImplicitCastExpr *Cast,
377 ASTContext &Context) {
378 if (AllowPointerConditions &&
379 (Cast->getCastKind() == CK_PointerToBoolean ||
380 Cast->getCastKind() == CK_MemberPointerToBoolean) &&
381 isCastAllowedInCondition(Cast, Context)) {
385 if (AllowIntegerConditions && Cast->getCastKind() == CK_IntegralToBoolean &&
386 isCastAllowedInCondition(Cast, Context)) {
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);
398 fixGenericExprCastToBool(Diag, Cast,
Parent, Context,
399 UseUpperCaseLiteralSuffix);
403void ImplicitBoolConversionCheck::handleCastFromBool(
404 const ImplicitCastExpr *Cast,
const ImplicitCastExpr *NextImplicitCast,
405 ASTContext &Context) {
407 NextImplicitCast ? NextImplicitCast->getType() : Cast->getType();
408 auto Diag =
diag(Cast->getBeginLoc(),
"implicit conversion 'bool' -> %0")
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());
420 Diag << tooling::fixit::createReplacement(*Cast,
421 EquivalentForBoolLiteral);
424 fixGenericExprCastFromBool(Diag, Cast, Context, DestType.getAsString());
llvm::SmallString< 256U > Name
::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.
AST_MATCHER(CXXMethodDecl, isStatic)
bool areParensNeededForStatement(const Stmt &Node)
llvm::StringMap< ClangTidyValue > OptionMap