//===--- RedundantCastingCheck.cpp - clang-tidy ---------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "RedundantCastingCheck.h" #include "../utils/FixItHintUtils.h" #include "clang/AST/ASTContext.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/Lex/Lexer.h" using namespace clang::ast_matchers; namespace clang::tidy::readability { static bool areTypesEqual(QualType S, QualType D) { if (S == D) return true; const auto *TS = S->getAs(); const auto *TD = D->getAs(); if (TS != TD) return false; QualType PtrS = S->getPointeeType(); QualType PtrD = D->getPointeeType(); if (!PtrS.isNull() && !PtrD.isNull()) return areTypesEqual(PtrS, PtrD); const DeducedType *DT = S->getContainedDeducedType(); if (DT && DT->isDeduced()) return D == DT->getDeducedType(); return false; } static bool areTypesEqual(QualType TypeS, QualType TypeD, bool IgnoreTypeAliases) { const QualType CTypeS = TypeS.getCanonicalType(); const QualType CTypeD = TypeD.getCanonicalType(); if (CTypeS != CTypeD) return false; return IgnoreTypeAliases || areTypesEqual(TypeS.getLocalUnqualifiedType(), TypeD.getLocalUnqualifiedType()); } static bool areBinaryOperatorOperandsTypesEqualToOperatorResultType( const Expr *E, bool IgnoreTypeAliases) { if (!E) return true; const Expr *WithoutImplicitAndParen = E->IgnoreParenImpCasts(); if (!WithoutImplicitAndParen) return true; if (const auto *B = dyn_cast(WithoutImplicitAndParen)) { const QualType Type = WithoutImplicitAndParen->getType(); if (Type.isNull()) return true; const QualType NonReferenceType = Type.getNonReferenceType(); const QualType LHSType = B->getLHS()->IgnoreImplicit()->getType(); if (LHSType.isNull() || !areTypesEqual(LHSType.getNonReferenceType(), NonReferenceType, IgnoreTypeAliases)) return false; const QualType RHSType = B->getRHS()->IgnoreImplicit()->getType(); if (RHSType.isNull() || !areTypesEqual(RHSType.getNonReferenceType(), NonReferenceType, IgnoreTypeAliases)) return false; } return true; } static const Decl *getSourceExprDecl(const Expr *SourceExpr) { const Expr *CleanSourceExpr = SourceExpr->IgnoreParenImpCasts(); if (const auto *E = dyn_cast(CleanSourceExpr)) { return E->getDecl(); } if (const auto *E = dyn_cast(CleanSourceExpr)) { return E->getCalleeDecl(); } if (const auto *E = dyn_cast(CleanSourceExpr)) { return E->getMemberDecl(); } return nullptr; } RedundantCastingCheck::RedundantCastingCheck(StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context), IgnoreMacros(Options.getLocalOrGlobal("IgnoreMacros", true)), IgnoreTypeAliases(Options.getLocalOrGlobal("IgnoreTypeAliases", false)) {} void RedundantCastingCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { Options.store(Opts, "IgnoreMacros", IgnoreMacros); Options.store(Opts, "IgnoreTypeAliases", IgnoreTypeAliases); } void RedundantCastingCheck::registerMatchers(MatchFinder *Finder) { auto SimpleType = qualType(hasCanonicalType( qualType(anyOf(builtinType(), references(builtinType()), references(pointsTo(qualType())), pointsTo(qualType()))))); auto BitfieldMemberExpr = memberExpr(member(fieldDecl(isBitField()))); Finder->addMatcher( explicitCastExpr( unless(hasCastKind(CK_ConstructorConversion)), unless(hasCastKind(CK_UserDefinedConversion)), unless(cxxFunctionalCastExpr(hasDestinationType(unless(SimpleType)))), hasDestinationType(qualType().bind("dstType")), hasSourceExpression(anyOf( expr(unless(initListExpr()), unless(BitfieldMemberExpr), hasType(qualType().bind("srcType"))) .bind("source"), initListExpr(unless(hasInit(1, expr())), hasInit(0, expr(unless(BitfieldMemberExpr), hasType(qualType().bind("srcType"))) .bind("source")))))) .bind("cast"), this); } void RedundantCastingCheck::check(const MatchFinder::MatchResult &Result) { const auto *SourceExpr = Result.Nodes.getNodeAs("source"); auto TypeD = *Result.Nodes.getNodeAs("dstType"); if (SourceExpr->getValueKind() == VK_LValue && TypeD.getCanonicalType()->isRValueReferenceType()) return; const auto TypeS = Result.Nodes.getNodeAs("srcType")->getNonReferenceType(); TypeD = TypeD.getNonReferenceType(); if (!areTypesEqual(TypeS, TypeD, IgnoreTypeAliases)) return; if (!areBinaryOperatorOperandsTypesEqualToOperatorResultType( SourceExpr, IgnoreTypeAliases)) return; const auto *CastExpr = Result.Nodes.getNodeAs("cast"); if (IgnoreMacros && (CastExpr->getBeginLoc().isMacroID() || CastExpr->getEndLoc().isMacroID() || CastExpr->getExprLoc().isMacroID())) return; { auto Diag = diag(CastExpr->getExprLoc(), "redundant explicit casting to the same type %0 as the " "sub-expression, remove this casting"); Diag << TypeD; const SourceManager &SM = *Result.SourceManager; const SourceLocation SourceExprBegin = SM.getExpansionLoc(SourceExpr->getBeginLoc()); const SourceLocation SourceExprEnd = SM.getExpansionLoc(SourceExpr->getEndLoc()); if (SourceExprBegin != CastExpr->getBeginLoc()) Diag << FixItHint::CreateRemoval(SourceRange( CastExpr->getBeginLoc(), SourceExprBegin.getLocWithOffset(-1))); const SourceLocation NextToken = Lexer::getLocForEndOfToken( SourceExprEnd, 0U, SM, Result.Context->getLangOpts()); if (SourceExprEnd != CastExpr->getEndLoc()) { Diag << FixItHint::CreateRemoval( SourceRange(NextToken, CastExpr->getEndLoc())); } if (utils::fixit::areParensNeededForStatement(*SourceExpr)) { Diag << FixItHint::CreateInsertion(SourceExprBegin, "(") << FixItHint::CreateInsertion(NextToken, ")"); } } const auto *SourceExprDecl = getSourceExprDecl(SourceExpr); if (!SourceExprDecl) return; if (const auto *D = dyn_cast(SourceExprDecl)) { diag(D->getLocation(), "source type originates from the invocation of this constructor", DiagnosticIDs::Note); return; } if (const auto *D = dyn_cast(SourceExprDecl)) { diag(D->getLocation(), "source type originates from the invocation of this " "%select{function|method}0", DiagnosticIDs::Note) << isa(D) << D->getReturnTypeSourceRange(); return; } if (const auto *D = dyn_cast(SourceExprDecl)) { diag(D->getLocation(), "source type originates from referencing this member", DiagnosticIDs::Note) << SourceRange(D->getTypeSpecStartLoc(), D->getTypeSpecEndLoc()); return; } if (const auto *D = dyn_cast(SourceExprDecl)) { diag(D->getLocation(), "source type originates from referencing this parameter", DiagnosticIDs::Note) << SourceRange(D->getTypeSpecStartLoc(), D->getTypeSpecEndLoc()); return; } if (const auto *D = dyn_cast(SourceExprDecl)) { diag(D->getLocation(), "source type originates from referencing this variable", DiagnosticIDs::Note) << SourceRange(D->getTypeSpecStartLoc(), D->getTypeSpecEndLoc()); return; } if (const auto *D = dyn_cast(SourceExprDecl)) { diag(D->getLocation(), "source type originates from referencing this enum constant", DiagnosticIDs::Note); return; } if (const auto *D = dyn_cast(SourceExprDecl)) { diag(D->getLocation(), "source type originates from referencing this bound variable", DiagnosticIDs::Note); return; } if (const auto *D = dyn_cast(SourceExprDecl)) { diag(D->getLocation(), "source type originates from referencing this non-type template " "parameter", DiagnosticIDs::Note); return; } } } // namespace clang::tidy::readability