292 lines · cpp
1//===----------------------------------------------------------------------===//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-exception6//7//===----------------------------------------------------------------------===//8 9#include "AvoidCStyleCastsCheck.h"10#include "clang/AST/ASTContext.h"11#include "clang/ASTMatchers/ASTMatchFinder.h"12#include "clang/ASTMatchers/ASTMatchers.h"13#include "clang/Lex/Lexer.h"14 15using namespace clang::ast_matchers;16 17namespace clang::tidy::google::readability {18 19void AvoidCStyleCastsCheck::registerMatchers(20 ast_matchers::MatchFinder *Finder) {21 Finder->addMatcher(22 cStyleCastExpr(23 // Filter out (EnumType)IntegerLiteral construct, which is generated24 // for non-type template arguments of enum types.25 // FIXME: Remove this once this is fixed in the AST.26 unless(hasParent(substNonTypeTemplateParmExpr())))27 .bind("cast"),28 this);29 30 Finder->addMatcher(31 cxxFunctionalCastExpr(32 hasDestinationType(hasCanonicalType(anyOf(33 builtinType(), references(qualType()), pointsTo(qualType())))),34 unless(35 hasSourceExpression(anyOf(cxxConstructExpr(), initListExpr()))))36 .bind("cast"),37 this);38}39 40static bool needsConstCast(QualType SourceType, QualType DestType) {41 while ((SourceType->isPointerType() && DestType->isPointerType()) ||42 (SourceType->isReferenceType() && DestType->isReferenceType())) {43 SourceType = SourceType->getPointeeType();44 DestType = DestType->getPointeeType();45 if (SourceType.isConstQualified() && !DestType.isConstQualified()) {46 return (SourceType->isPointerType() == DestType->isPointerType()) &&47 (SourceType->isReferenceType() == DestType->isReferenceType());48 }49 }50 return false;51}52 53static bool pointedUnqualifiedTypesAreEqual(QualType T1, QualType T2) {54 while ((T1->isPointerType() && T2->isPointerType()) ||55 (T1->isReferenceType() && T2->isReferenceType())) {56 T1 = T1->getPointeeType();57 T2 = T2->getPointeeType();58 }59 return T1.getUnqualifiedType() == T2.getUnqualifiedType();60}61 62static clang::CharSourceRange getReplaceRange(const ExplicitCastExpr *Expr) {63 if (const auto *CastExpr = dyn_cast<CStyleCastExpr>(Expr))64 return CharSourceRange::getCharRange(65 CastExpr->getLParenLoc(),66 CastExpr->getSubExprAsWritten()->getBeginLoc());67 if (const auto *CastExpr = dyn_cast<CXXFunctionalCastExpr>(Expr))68 return CharSourceRange::getCharRange(CastExpr->getBeginLoc(),69 CastExpr->getLParenLoc());70 llvm_unreachable("Unsupported CastExpr");71}72 73static StringRef getDestTypeString(const SourceManager &SM,74 const LangOptions &LangOpts,75 const ExplicitCastExpr *Expr) {76 SourceLocation BeginLoc;77 SourceLocation EndLoc;78 79 if (const auto *CastExpr = dyn_cast<CStyleCastExpr>(Expr)) {80 BeginLoc = CastExpr->getLParenLoc().getLocWithOffset(1);81 EndLoc = CastExpr->getRParenLoc().getLocWithOffset(-1);82 } else if (const auto *CastExpr = dyn_cast<CXXFunctionalCastExpr>(Expr)) {83 BeginLoc = CastExpr->getBeginLoc();84 EndLoc = CastExpr->getLParenLoc().getLocWithOffset(-1);85 } else86 llvm_unreachable("Unsupported CastExpr");87 88 return Lexer::getSourceText(CharSourceRange::getTokenRange(BeginLoc, EndLoc),89 SM, LangOpts);90}91 92static bool sameTypeAsWritten(QualType X, QualType Y) {93 if (X.getCanonicalType() != Y.getCanonicalType())94 return false;95 96 auto TC = X->getTypeClass();97 if (TC != Y->getTypeClass())98 return false;99 100 switch (TC) {101 case Type::Typedef:102 return declaresSameEntity(cast<TypedefType>(X)->getDecl(),103 cast<TypedefType>(Y)->getDecl());104 case Type::Pointer:105 return sameTypeAsWritten(cast<PointerType>(X)->getPointeeType(),106 cast<PointerType>(Y)->getPointeeType());107 case Type::RValueReference:108 case Type::LValueReference:109 return sameTypeAsWritten(cast<ReferenceType>(X)->getPointeeType(),110 cast<ReferenceType>(Y)->getPointeeType());111 default:112 return true;113 }114}115 116void AvoidCStyleCastsCheck::check(const MatchFinder::MatchResult &Result) {117 const auto *CastExpr = Result.Nodes.getNodeAs<ExplicitCastExpr>("cast");118 119 // Ignore casts in macros.120 if (CastExpr->getExprLoc().isMacroID())121 return;122 123 // Casting to void is an idiomatic way to mute "unused variable" and similar124 // warnings.125 if (CastExpr->getCastKind() == CK_ToVoid)126 return;127 128 auto IsFunction = [](QualType T) {129 T = T.getCanonicalType().getNonReferenceType();130 return T->isFunctionType() || T->isFunctionPointerType() ||131 T->isMemberFunctionPointerType();132 };133 134 const QualType DestTypeAsWritten =135 CastExpr->getTypeAsWritten().getUnqualifiedType();136 const QualType SourceTypeAsWritten =137 CastExpr->getSubExprAsWritten()->getType().getUnqualifiedType();138 const QualType SourceType = SourceTypeAsWritten.getCanonicalType();139 const QualType DestType = DestTypeAsWritten.getCanonicalType();140 141 CharSourceRange ReplaceRange = getReplaceRange(CastExpr);142 143 const bool FnToFnCast =144 IsFunction(SourceTypeAsWritten) && IsFunction(DestTypeAsWritten);145 146 const bool ConstructorCast = !CastExpr->getTypeAsWritten().hasQualifiers() &&147 DestTypeAsWritten->isRecordType() &&148 !DestTypeAsWritten->isElaboratedTypeSpecifier();149 150 if (CastExpr->getCastKind() == CK_NoOp && !FnToFnCast) {151 // Function pointer/reference casts may be needed to resolve ambiguities in152 // case of overloaded functions, so detection of redundant casts is trickier153 // in this case. Don't emit "redundant cast" warnings for function154 // pointer/reference types.155 if (sameTypeAsWritten(SourceTypeAsWritten, DestTypeAsWritten)) {156 diag(CastExpr->getBeginLoc(), "redundant cast to the same type")157 << FixItHint::CreateRemoval(ReplaceRange);158 return;159 }160 }161 162 // The rest of this check is only relevant to C++.163 // We also disable it for Objective-C++.164 if (!getLangOpts().CPlusPlus || getLangOpts().ObjC)165 return;166 // Ignore code inside extern "C" {} blocks.167 if (!match(expr(hasAncestor(linkageSpecDecl())), *CastExpr, *Result.Context)168 .empty())169 return;170 // Ignore code in .c files and headers included from them, even if they are171 // compiled as C++.172 if (getCurrentMainFile().ends_with(".c"))173 return;174 175 SourceManager &SM = *Result.SourceManager;176 177 // Ignore code in .c files #included in other files (which shouldn't be done,178 // but people still do this for test and other purposes).179 if (SM.getFilename(SM.getSpellingLoc(CastExpr->getBeginLoc()))180 .ends_with(".c"))181 return;182 183 // Leave type spelling exactly as it was (unlike184 // getTypeAsWritten().getAsString() which would spell enum types 'enum X').185 StringRef DestTypeString = getDestTypeString(SM, getLangOpts(), CastExpr);186 187 auto Diag =188 diag(CastExpr->getBeginLoc(), "C-style casts are discouraged; use %0");189 190 auto ReplaceWithCast = [&](std::string CastText) {191 const Expr *SubExpr = CastExpr->getSubExprAsWritten()->IgnoreImpCasts();192 if (!isa<ParenExpr>(SubExpr) && !isa<CXXFunctionalCastExpr>(CastExpr)) {193 CastText.push_back('(');194 Diag << FixItHint::CreateInsertion(195 Lexer::getLocForEndOfToken(SubExpr->getEndLoc(), 0, SM,196 getLangOpts()),197 ")");198 }199 Diag << FixItHint::CreateReplacement(ReplaceRange, CastText);200 };201 auto ReplaceWithNamedCast = [&](StringRef CastType) {202 Diag << CastType;203 ReplaceWithCast((CastType + "<" + DestTypeString + ">").str());204 };205 auto ReplaceWithConstructorCall = [&]() {206 Diag << "constructor call syntax";207 // FIXME: Validate DestTypeString, maybe.208 ReplaceWithCast(DestTypeString.str());209 };210 // Suggest appropriate C++ cast. See [expr.cast] for cast notation semantics.211 switch (CastExpr->getCastKind()) {212 case CK_FunctionToPointerDecay:213 ReplaceWithNamedCast("static_cast");214 return;215 case CK_ConstructorConversion:216 if (ConstructorCast) {217 ReplaceWithConstructorCall();218 } else {219 ReplaceWithNamedCast("static_cast");220 }221 return;222 case CK_NoOp:223 if (FnToFnCast) {224 ReplaceWithNamedCast("static_cast");225 return;226 }227 if (SourceType == DestType) {228 Diag << "static_cast (if needed, the cast may be redundant)";229 ReplaceWithCast(("static_cast<" + DestTypeString + ">").str());230 return;231 }232 if (needsConstCast(SourceType, DestType) &&233 pointedUnqualifiedTypesAreEqual(SourceType, DestType)) {234 ReplaceWithNamedCast("const_cast");235 return;236 }237 if (ConstructorCast) {238 ReplaceWithConstructorCall();239 return;240 }241 if (DestType->isReferenceType()) {242 const QualType Dest = DestType.getNonReferenceType();243 const QualType Source = SourceType.getNonReferenceType();244 if (Source == Dest.withConst() ||245 SourceType.getNonReferenceType() == DestType.getNonReferenceType()) {246 ReplaceWithNamedCast("const_cast");247 return;248 }249 break;250 }251 if (DestType->isVoidPointerType() && SourceType->isPointerType() &&252 !SourceType->getPointeeType()->isPointerType()) {253 ReplaceWithNamedCast("reinterpret_cast");254 return;255 }256 257 [[fallthrough]];258 case clang::CK_IntegralCast:259 // Convert integral and no-op casts between builtin types and enums to260 // static_cast. A cast from enum to integer may be unnecessary, but it's261 // still retained.262 if ((SourceType->isBuiltinType() || SourceType->isEnumeralType()) &&263 (DestType->isBuiltinType() || DestType->isEnumeralType())) {264 ReplaceWithNamedCast("static_cast");265 return;266 }267 break;268 case CK_BitCast:269 // FIXME: Suggest const_cast<...>(reinterpret_cast<...>(...)) replacement.270 if (!needsConstCast(SourceType, DestType)) {271 if (SourceType->isVoidPointerType())272 ReplaceWithNamedCast("static_cast");273 else274 ReplaceWithNamedCast("reinterpret_cast");275 return;276 }277 break;278 case CK_BaseToDerived:279 if (!needsConstCast(SourceType, DestType)) {280 ReplaceWithNamedCast("static_cast");281 return;282 }283 break;284 default:285 break;286 }287 288 Diag << "static_cast/const_cast/reinterpret_cast";289}290 291} // namespace clang::tidy::google::readability292