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"
24 SourceManager &SM = Finder->getASTContext().getSourceManager();
25 SourceLocation
Loc =
Node.getBeginLoc();
26 return SM.isMacroBodyExpansion(
Loc) || SM.isMacroArgExpansion(
Loc);
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";
38 return isNULLMacroExpansion(&Node, Finder->getASTContext());
41StringRef getZeroLiteralToCompareWithForType(CastKind CastExprKind,
43 ASTContext &Context) {
44 switch (CastExprKind) {
45 case CK_IntegralToBoolean:
46 return Type->isUnsignedIntegerType() ?
"0u" :
"0";
48 case CK_FloatingToBoolean:
49 return Context.hasSameType(
Type, Context.FloatTy) ?
"0.0f" :
"0.0";
51 case CK_PointerToBoolean:
52 case CK_MemberPointerToBoolean:
53 return (Context.getLangOpts().CPlusPlus11 || Context.getLangOpts().C23)
58 llvm_unreachable(
"Unexpected cast kind");
62bool isUnaryLogicalNotOperator(
const Stmt *Statement) {
63 const auto *UnaryOperatorExpr = dyn_cast<UnaryOperator>(Statement);
64 return UnaryOperatorExpr && UnaryOperatorExpr->getOpcode() == UO_LNot;
67void fixGenericExprCastToBool(DiagnosticBuilder &Diag,
68 const ImplicitCastExpr *Cast,
const Stmt *
Parent,
69 ASTContext &Context) {
72 bool InvertComparison =
74 if (InvertComparison) {
75 SourceLocation ParentStartLoc =
Parent->getBeginLoc();
76 SourceLocation ParentEndLoc =
77 cast<UnaryOperator>(
Parent)->getSubExpr()->getBeginLoc();
78 Diag << FixItHint::CreateRemoval(
79 CharSourceRange::getCharRange(ParentStartLoc, ParentEndLoc));
84 const Expr *SubExpr = Cast->getSubExpr();
87 bool NeedOuterParens =
90 std::string StartLocInsertion;
92 if (NeedOuterParens) {
93 StartLocInsertion +=
"(";
95 if (NeedInnerParens) {
96 StartLocInsertion +=
"(";
99 if (!StartLocInsertion.empty()) {
100 Diag << FixItHint::CreateInsertion(Cast->getBeginLoc(), StartLocInsertion);
103 std::string EndLocInsertion;
105 if (NeedInnerParens) {
106 EndLocInsertion +=
")";
109 if (InvertComparison) {
110 EndLocInsertion +=
" == ";
112 EndLocInsertion +=
" != ";
115 EndLocInsertion += getZeroLiteralToCompareWithForType(
116 Cast->getCastKind(), SubExpr->getType(), Context);
118 if (NeedOuterParens) {
119 EndLocInsertion +=
")";
122 SourceLocation EndLoc = Lexer::getLocForEndOfToken(
123 Cast->getEndLoc(), 0, Context.getSourceManager(), Context.getLangOpts());
124 Diag << FixItHint::CreateInsertion(EndLoc, EndLocInsertion);
127StringRef getEquivalentBoolLiteralForExpr(
const Expr *Expression,
128 ASTContext &Context) {
129 if (isNULLMacroExpansion(Expression, Context)) {
133 if (
const auto *IntLit =
134 dyn_cast<IntegerLiteral>(Expression->IgnoreParens())) {
135 return (IntLit->getValue() == 0) ?
"false" :
"true";
138 if (
const auto *FloatLit = dyn_cast<FloatingLiteral>(Expression)) {
139 llvm::APFloat FloatLitAbsValue = FloatLit->getValue();
140 FloatLitAbsValue.clearSign();
141 return (FloatLitAbsValue.bitcastToAPInt() == 0) ?
"false" :
"true";
144 if (
const auto *CharLit = dyn_cast<CharacterLiteral>(Expression)) {
145 return (CharLit->getValue() == 0) ?
"false" :
"true";
148 if (isa<StringLiteral>(Expression->IgnoreCasts())) {
155bool needsSpacePrefix(SourceLocation
Loc, ASTContext &Context) {
156 SourceRange PrefixRange(
Loc.getLocWithOffset(-1),
Loc);
157 StringRef SpaceBeforeStmtStr = Lexer::getSourceText(
158 CharSourceRange::getCharRange(PrefixRange), Context.getSourceManager(),
159 Context.getLangOpts(),
nullptr);
160 if (SpaceBeforeStmtStr.empty())
163 const StringRef AllowedCharacters(
" \t\n\v\f\r(){}[]<>;,+=-|&~!^*/");
164 return !AllowedCharacters.contains(SpaceBeforeStmtStr.back());
167void fixGenericExprCastFromBool(DiagnosticBuilder &Diag,
168 const ImplicitCastExpr *Cast,
169 ASTContext &Context, StringRef OtherType) {
170 if (!Context.getLangOpts().CPlusPlus) {
171 Diag << FixItHint::CreateInsertion(Cast->getBeginLoc(),
172 (Twine(
"(") + OtherType +
")").str());
176 const Expr *SubExpr = Cast->getSubExpr();
177 const bool NeedParens = !isa<ParenExpr>(SubExpr->IgnoreImplicit());
178 const bool NeedSpace = needsSpacePrefix(Cast->getBeginLoc(), Context);
180 Diag << FixItHint::CreateInsertion(
181 Cast->getBeginLoc(), (Twine() + (NeedSpace ?
" " :
"") +
"static_cast<" +
182 OtherType +
">" + (NeedParens ?
"(" :
""))
186 SourceLocation EndLoc = Lexer::getLocForEndOfToken(
187 Cast->getEndLoc(), 0, Context.getSourceManager(),
188 Context.getLangOpts());
190 Diag << FixItHint::CreateInsertion(EndLoc,
")");
194StringRef getEquivalentForBoolLiteral(
const CXXBoolLiteralExpr *BoolLiteral,
195 QualType DestType, ASTContext &Context) {
197 if (!Context.getLangOpts().CPlusPlus11 &&
198 (DestType->isPointerType() || DestType->isMemberPointerType()) &&
199 BoolLiteral->getValue() ==
false) {
203 if (DestType->isFloatingType()) {
204 if (Context.hasSameType(DestType, Context.FloatTy)) {
205 return BoolLiteral->getValue() ?
"1.0f" :
"0.0f";
207 return BoolLiteral->getValue() ?
"1.0" :
"0.0";
210 if (DestType->isUnsignedIntegerType()) {
211 return BoolLiteral->getValue() ?
"1u" :
"0u";
213 return BoolLiteral->getValue() ?
"1" :
"0";
216bool isCastAllowedInCondition(
const ImplicitCastExpr *Cast,
217 ASTContext &Context) {
218 std::queue<const Stmt *> Q;
221 TraversalKindScope RAII(Context, TK_AsIs);
224 for (
const auto &N : Context.getParents(*Q.front())) {
225 const Stmt *S = N.get<Stmt>();
228 if (isa<IfStmt>(S) || isa<ConditionalOperator>(S) || isa<ForStmt>(S) ||
229 isa<WhileStmt>(S) || isa<DoStmt>(S) ||
230 isa<BinaryConditionalOperator>(S))
232 if (isa<ParenExpr>(S) || isa<ImplicitCastExpr>(S) ||
233 isUnaryLogicalNotOperator(S) ||
234 (isa<BinaryOperator>(S) && cast<BinaryOperator>(S)->isLogicalOp())) {
250 AllowIntegerConditions(Options.get(
"AllowIntegerConditions", false)),
251 AllowPointerConditions(Options.get(
"AllowPointerConditions", false)) {}
255 Options.
store(Opts,
"AllowIntegerConditions", AllowIntegerConditions);
256 Options.
store(Opts,
"AllowPointerConditions", AllowPointerConditions);
260 auto ExceptionCases =
261 expr(anyOf(allOf(isMacroExpansion(), unless(isNULLMacroExpansion())),
262 has(ignoringImplicit(
263 memberExpr(hasDeclaration(fieldDecl(hasBitWidth(1)))))),
264 hasParent(explicitCastExpr()),
265 expr(hasType(qualType().bind(
"type")),
266 hasParent(initListExpr(hasParent(explicitCastExpr(
267 hasType(qualType(equalsBoundNode(
"type"))))))))));
268 auto ImplicitCastFromBool = implicitCastExpr(
269 anyOf(hasCastKind(CK_IntegralCast), hasCastKind(CK_IntegralToFloating),
271 allOf(anyOf(hasCastKind(CK_NullToPointer),
272 hasCastKind(CK_NullToMemberPointer)),
273 hasSourceExpression(cxxBoolLiteral()))),
274 hasSourceExpression(expr(hasType(booleanType()))));
276 binaryOperator(hasOperatorName(
"^"), hasLHS(ImplicitCastFromBool),
277 hasRHS(ImplicitCastFromBool));
278 auto ComparisonInCall = allOf(
279 hasParent(callExpr()),
280 hasSourceExpression(binaryOperator(hasAnyOperatorName(
"==",
"!="))));
282 auto IsInCompilerGeneratedFunction = hasAncestor(namedDecl(anyOf(
283 isImplicit(), functionDecl(isDefaulted()), functionTemplateDecl())));
288 anyOf(hasCastKind(CK_IntegralToBoolean),
289 hasCastKind(CK_FloatingToBoolean),
290 hasCastKind(CK_PointerToBoolean),
291 hasCastKind(CK_MemberPointerToBoolean)),
296 stmt(anyOf(ifStmt(), whileStmt()), has(declStmt())))),
298 unless(ExceptionCases), unless(has(BoolXor)),
300 unless(ComparisonInCall),
303 optionally(hasParent(stmt().bind(
"parentStmt"))),
304 unless(isInTemplateInstantiation()),
305 unless(IsInCompilerGeneratedFunction))
306 .bind(
"implicitCastToBool")),
309 auto BoolComparison = binaryOperator(hasAnyOperatorName(
"==",
"!="),
310 hasLHS(ImplicitCastFromBool),
311 hasRHS(ImplicitCastFromBool));
312 auto BoolOpAssignment = binaryOperator(hasAnyOperatorName(
"|=",
"&="),
313 hasLHS(expr(hasType(booleanType()))));
314 auto BitfieldAssignment = binaryOperator(
315 hasLHS(memberExpr(hasDeclaration(fieldDecl(hasBitWidth(1))))));
316 auto BitfieldConstruct = cxxConstructorDecl(hasDescendant(cxxCtorInitializer(
317 withInitializer(equalsBoundNode(
"implicitCastFromBool")),
318 forField(hasBitWidth(1)))));
323 ImplicitCastFromBool, unless(ExceptionCases),
329 binaryOperator(anyOf(BoolComparison, BoolXor,
330 BoolOpAssignment, BitfieldAssignment)))),
331 implicitCastExpr().bind(
"implicitCastFromBool"),
332 unless(hasParent(BitfieldConstruct)),
334 anyOf(hasParent(implicitCastExpr().bind(
"furtherImplicitCast")),
336 unless(isInTemplateInstantiation()),
337 unless(IsInCompilerGeneratedFunction))),
342 const MatchFinder::MatchResult &Result) {
344 if (
const auto *CastToBool =
345 Result.Nodes.getNodeAs<ImplicitCastExpr>(
"implicitCastToBool")) {
346 const auto *
Parent = Result.Nodes.getNodeAs<Stmt>(
"parentStmt");
347 return handleCastToBool(CastToBool,
Parent, *Result.Context);
350 if (
const auto *CastFromBool =
351 Result.Nodes.getNodeAs<ImplicitCastExpr>(
"implicitCastFromBool")) {
352 const auto *NextImplicitCast =
353 Result.Nodes.getNodeAs<ImplicitCastExpr>(
"furtherImplicitCast");
354 return handleCastFromBool(CastFromBool, NextImplicitCast, *Result.Context);
358void ImplicitBoolConversionCheck::handleCastToBool(
const ImplicitCastExpr *Cast,
360 ASTContext &Context) {
361 if (AllowPointerConditions &&
362 (Cast->getCastKind() == CK_PointerToBoolean ||
363 Cast->getCastKind() == CK_MemberPointerToBoolean) &&
364 isCastAllowedInCondition(Cast, Context)) {
368 if (AllowIntegerConditions && Cast->getCastKind() == CK_IntegralToBoolean &&
369 isCastAllowedInCondition(Cast, Context)) {
373 auto Diag =
diag(Cast->getBeginLoc(),
"implicit conversion %0 -> 'bool'")
374 << Cast->getSubExpr()->getType();
376 StringRef EquivalentLiteral =
377 getEquivalentBoolLiteralForExpr(Cast->getSubExpr(), Context);
378 if (!EquivalentLiteral.empty()) {
379 Diag << tooling::fixit::createReplacement(*Cast, EquivalentLiteral);
381 fixGenericExprCastToBool(Diag, Cast,
Parent, Context);
385void ImplicitBoolConversionCheck::handleCastFromBool(
386 const ImplicitCastExpr *Cast,
const ImplicitCastExpr *NextImplicitCast,
387 ASTContext &Context) {
389 NextImplicitCast ? NextImplicitCast->getType() : Cast->getType();
390 auto Diag =
diag(Cast->getBeginLoc(),
"implicit conversion 'bool' -> %0")
393 if (
const auto *BoolLiteral =
394 dyn_cast<CXXBoolLiteralExpr>(Cast->getSubExpr()->IgnoreParens())) {
395 Diag << tooling::fixit::createReplacement(
396 *Cast, getEquivalentForBoolLiteral(BoolLiteral, DestType, Context));
398 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