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