1 //===--- AvoidCStyleCastsCheck.cpp - clang-tidy -----------------*- C++ -*-===// 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 "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 15 using namespace clang::ast_matchers; 16 17 namespace clang { 18 namespace tidy { 19 namespace google { 20 namespace readability { 21 22 void AvoidCStyleCastsCheck::registerMatchers( 23 ast_matchers::MatchFinder *Finder) { 24 Finder->addMatcher( 25 cStyleCastExpr( 26 // Filter out (EnumType)IntegerLiteral construct, which is generated 27 // for non-type template arguments of enum types. 28 // FIXME: Remove this once this is fixed in the AST. 29 unless(hasParent(substNonTypeTemplateParmExpr())), 30 // Avoid matches in template instantiations. 31 unless(isInTemplateInstantiation())) 32 .bind("cast"), 33 this); 34 } 35 36 static bool needsConstCast(QualType SourceType, QualType DestType) { 37 while ((SourceType->isPointerType() && DestType->isPointerType()) || 38 (SourceType->isReferenceType() && DestType->isReferenceType())) { 39 SourceType = SourceType->getPointeeType(); 40 DestType = DestType->getPointeeType(); 41 if (SourceType.isConstQualified() && !DestType.isConstQualified()) { 42 return (SourceType->isPointerType() == DestType->isPointerType()) && 43 (SourceType->isReferenceType() == DestType->isReferenceType()); 44 } 45 } 46 return false; 47 } 48 49 static bool pointedUnqualifiedTypesAreEqual(QualType T1, QualType T2) { 50 while ((T1->isPointerType() && T2->isPointerType()) || 51 (T1->isReferenceType() && T2->isReferenceType())) { 52 T1 = T1->getPointeeType(); 53 T2 = T2->getPointeeType(); 54 } 55 return T1.getUnqualifiedType() == T2.getUnqualifiedType(); 56 } 57 58 void AvoidCStyleCastsCheck::check(const MatchFinder::MatchResult &Result) { 59 const auto *CastExpr = Result.Nodes.getNodeAs<CStyleCastExpr>("cast"); 60 61 // Ignore casts in macros. 62 if (CastExpr->getExprLoc().isMacroID()) 63 return; 64 65 // Casting to void is an idiomatic way to mute "unused variable" and similar 66 // warnings. 67 if (CastExpr->getCastKind() == CK_ToVoid) 68 return; 69 70 auto isFunction = [](QualType T) { 71 T = T.getCanonicalType().getNonReferenceType(); 72 return T->isFunctionType() || T->isFunctionPointerType() || 73 T->isMemberFunctionPointerType(); 74 }; 75 76 const QualType DestTypeAsWritten = 77 CastExpr->getTypeAsWritten().getUnqualifiedType(); 78 const QualType SourceTypeAsWritten = 79 CastExpr->getSubExprAsWritten()->getType().getUnqualifiedType(); 80 const QualType SourceType = SourceTypeAsWritten.getCanonicalType(); 81 const QualType DestType = DestTypeAsWritten.getCanonicalType(); 82 83 auto ReplaceRange = CharSourceRange::getCharRange( 84 CastExpr->getLParenLoc(), CastExpr->getSubExprAsWritten()->getBeginLoc()); 85 86 bool FnToFnCast = 87 isFunction(SourceTypeAsWritten) && isFunction(DestTypeAsWritten); 88 89 if (CastExpr->getCastKind() == CK_NoOp && !FnToFnCast) { 90 // Function pointer/reference casts may be needed to resolve ambiguities in 91 // case of overloaded functions, so detection of redundant casts is trickier 92 // in this case. Don't emit "redundant cast" warnings for function 93 // pointer/reference types. 94 if (SourceTypeAsWritten == DestTypeAsWritten) { 95 diag(CastExpr->getBeginLoc(), "redundant cast to the same type") 96 << FixItHint::CreateRemoval(ReplaceRange); 97 return; 98 } 99 } 100 101 // The rest of this check is only relevant to C++. 102 // We also disable it for Objective-C++. 103 if (!getLangOpts().CPlusPlus || getLangOpts().ObjC) 104 return; 105 // Ignore code inside extern "C" {} blocks. 106 if (!match(expr(hasAncestor(linkageSpecDecl())), *CastExpr, *Result.Context) 107 .empty()) 108 return; 109 // Ignore code in .c files and headers included from them, even if they are 110 // compiled as C++. 111 if (getCurrentMainFile().endswith(".c")) 112 return; 113 114 SourceManager &SM = *Result.SourceManager; 115 116 // Ignore code in .c files #included in other files (which shouldn't be done, 117 // but people still do this for test and other purposes). 118 if (SM.getFilename(SM.getSpellingLoc(CastExpr->getBeginLoc())).endswith(".c")) 119 return; 120 121 // Leave type spelling exactly as it was (unlike 122 // getTypeAsWritten().getAsString() which would spell enum types 'enum X'). 123 StringRef DestTypeString = 124 Lexer::getSourceText(CharSourceRange::getTokenRange( 125 CastExpr->getLParenLoc().getLocWithOffset(1), 126 CastExpr->getRParenLoc().getLocWithOffset(-1)), 127 SM, getLangOpts()); 128 129 auto Diag = 130 diag(CastExpr->getBeginLoc(), "C-style casts are discouraged; use %0"); 131 132 auto ReplaceWithCast = [&](std::string CastText) { 133 const Expr *SubExpr = CastExpr->getSubExprAsWritten()->IgnoreImpCasts(); 134 if (!isa<ParenExpr>(SubExpr)) { 135 CastText.push_back('('); 136 Diag << FixItHint::CreateInsertion( 137 Lexer::getLocForEndOfToken(SubExpr->getEndLoc(), 0, SM, 138 getLangOpts()), 139 ")"); 140 } 141 Diag << FixItHint::CreateReplacement(ReplaceRange, CastText); 142 }; 143 auto ReplaceWithNamedCast = [&](StringRef CastType) { 144 Diag << CastType; 145 ReplaceWithCast((CastType + "<" + DestTypeString + ">").str()); 146 }; 147 148 // Suggest appropriate C++ cast. See [expr.cast] for cast notation semantics. 149 switch (CastExpr->getCastKind()) { 150 case CK_FunctionToPointerDecay: 151 ReplaceWithNamedCast("static_cast"); 152 return; 153 case CK_ConstructorConversion: 154 if (!CastExpr->getTypeAsWritten().hasQualifiers() && 155 DestTypeAsWritten->isRecordType() && 156 !DestTypeAsWritten->isElaboratedTypeSpecifier()) { 157 Diag << "constructor call syntax"; 158 // FIXME: Validate DestTypeString, maybe. 159 ReplaceWithCast(DestTypeString.str()); 160 } else { 161 ReplaceWithNamedCast("static_cast"); 162 } 163 return; 164 case CK_NoOp: 165 if (FnToFnCast) { 166 ReplaceWithNamedCast("static_cast"); 167 return; 168 } 169 if (SourceType == DestType) { 170 Diag << "static_cast (if needed, the cast may be redundant)"; 171 ReplaceWithCast(("static_cast<" + DestTypeString + ">").str()); 172 return; 173 } 174 if (needsConstCast(SourceType, DestType) && 175 pointedUnqualifiedTypesAreEqual(SourceType, DestType)) { 176 ReplaceWithNamedCast("const_cast"); 177 return; 178 } 179 if (DestType->isReferenceType()) { 180 QualType Dest = DestType.getNonReferenceType(); 181 QualType Source = SourceType.getNonReferenceType(); 182 if (Source == Dest.withConst() || 183 SourceType.getNonReferenceType() == DestType.getNonReferenceType()) { 184 ReplaceWithNamedCast("const_cast"); 185 return; 186 } 187 break; 188 } 189 LLVM_FALLTHROUGH; 190 case clang::CK_IntegralCast: 191 // Convert integral and no-op casts between builtin types and enums to 192 // static_cast. A cast from enum to integer may be unnecessary, but it's 193 // still retained. 194 if ((SourceType->isBuiltinType() || SourceType->isEnumeralType()) && 195 (DestType->isBuiltinType() || DestType->isEnumeralType())) { 196 ReplaceWithNamedCast("static_cast"); 197 return; 198 } 199 break; 200 case CK_BitCast: 201 // FIXME: Suggest const_cast<...>(reinterpret_cast<...>(...)) replacement. 202 if (!needsConstCast(SourceType, DestType)) { 203 if (SourceType->isVoidPointerType()) 204 ReplaceWithNamedCast("static_cast"); 205 else 206 ReplaceWithNamedCast("reinterpret_cast"); 207 return; 208 } 209 break; 210 default: 211 break; 212 } 213 214 Diag << "static_cast/const_cast/reinterpret_cast"; 215 } 216 217 } // namespace readability 218 } // namespace google 219 } // namespace tidy 220 } // namespace clang 221