[OptTable] Fix typo VALUE => VALUES (NFCI) (#121523)
[llvm-project.git] / clang-tools-extra / clang-tidy / google / AvoidCStyleCastsCheck.cpp
blob3109bbb3724c79e67bd97f9eb648113bc7dada48
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 //===----------------------------------------------------------------------===//
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"
15 using namespace clang::ast_matchers;
17 namespace clang::tidy::google::readability {
19 void AvoidCStyleCastsCheck::registerMatchers(
20 ast_matchers::MatchFinder *Finder) {
21 Finder->addMatcher(
22 cStyleCastExpr(
23 // Filter out (EnumType)IntegerLiteral construct, which is generated
24 // 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);
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);
40 static 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());
50 return false;
53 static 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();
59 return T1.getUnqualifiedType() == T2.getUnqualifiedType();
62 static 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");
73 static StringRef getDestTypeString(const SourceManager &SM,
74 const LangOptions &LangOpts,
75 const ExplicitCastExpr *Expr) {
76 SourceLocation BeginLoc;
77 SourceLocation EndLoc;
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 } else
86 llvm_unreachable("Unsupported CastExpr");
88 return Lexer::getSourceText(CharSourceRange::getTokenRange(BeginLoc, EndLoc),
89 SM, LangOpts);
92 void AvoidCStyleCastsCheck::check(const MatchFinder::MatchResult &Result) {
93 const auto *CastExpr = Result.Nodes.getNodeAs<ExplicitCastExpr>("cast");
95 // Ignore casts in macros.
96 if (CastExpr->getExprLoc().isMacroID())
97 return;
99 // Casting to void is an idiomatic way to mute "unused variable" and similar
100 // warnings.
101 if (CastExpr->getCastKind() == CK_ToVoid)
102 return;
104 auto IsFunction = [](QualType T) {
105 T = T.getCanonicalType().getNonReferenceType();
106 return T->isFunctionType() || T->isFunctionPointerType() ||
107 T->isMemberFunctionPointerType();
110 const QualType DestTypeAsWritten =
111 CastExpr->getTypeAsWritten().getUnqualifiedType();
112 const QualType SourceTypeAsWritten =
113 CastExpr->getSubExprAsWritten()->getType().getUnqualifiedType();
114 const QualType SourceType = SourceTypeAsWritten.getCanonicalType();
115 const QualType DestType = DestTypeAsWritten.getCanonicalType();
117 CharSourceRange ReplaceRange = getReplaceRange(CastExpr);
119 bool FnToFnCast =
120 IsFunction(SourceTypeAsWritten) && IsFunction(DestTypeAsWritten);
122 const bool ConstructorCast = !CastExpr->getTypeAsWritten().hasQualifiers() &&
123 DestTypeAsWritten->isRecordType() &&
124 !DestTypeAsWritten->isElaboratedTypeSpecifier();
126 if (CastExpr->getCastKind() == CK_NoOp && !FnToFnCast) {
127 // Function pointer/reference casts may be needed to resolve ambiguities in
128 // case of overloaded functions, so detection of redundant casts is trickier
129 // in this case. Don't emit "redundant cast" warnings for function
130 // pointer/reference types.
131 QualType Src = SourceTypeAsWritten, Dst = DestTypeAsWritten;
132 if (const auto *ElTy = dyn_cast<ElaboratedType>(Src))
133 Src = ElTy->getNamedType();
134 if (const auto *ElTy = dyn_cast<ElaboratedType>(Dst))
135 Dst = ElTy->getNamedType();
136 if (Src == Dst) {
137 diag(CastExpr->getBeginLoc(), "redundant cast to the same type")
138 << FixItHint::CreateRemoval(ReplaceRange);
139 return;
143 // The rest of this check is only relevant to C++.
144 // We also disable it for Objective-C++.
145 if (!getLangOpts().CPlusPlus || getLangOpts().ObjC)
146 return;
147 // Ignore code inside extern "C" {} blocks.
148 if (!match(expr(hasAncestor(linkageSpecDecl())), *CastExpr, *Result.Context)
149 .empty())
150 return;
151 // Ignore code in .c files and headers included from them, even if they are
152 // compiled as C++.
153 if (getCurrentMainFile().ends_with(".c"))
154 return;
156 SourceManager &SM = *Result.SourceManager;
158 // Ignore code in .c files #included in other files (which shouldn't be done,
159 // but people still do this for test and other purposes).
160 if (SM.getFilename(SM.getSpellingLoc(CastExpr->getBeginLoc()))
161 .ends_with(".c"))
162 return;
164 // Leave type spelling exactly as it was (unlike
165 // getTypeAsWritten().getAsString() which would spell enum types 'enum X').
166 StringRef DestTypeString = getDestTypeString(SM, getLangOpts(), CastExpr);
168 auto Diag =
169 diag(CastExpr->getBeginLoc(), "C-style casts are discouraged; use %0");
171 auto ReplaceWithCast = [&](std::string CastText) {
172 const Expr *SubExpr = CastExpr->getSubExprAsWritten()->IgnoreImpCasts();
173 if (!isa<ParenExpr>(SubExpr) && !isa<CXXFunctionalCastExpr>(CastExpr)) {
174 CastText.push_back('(');
175 Diag << FixItHint::CreateInsertion(
176 Lexer::getLocForEndOfToken(SubExpr->getEndLoc(), 0, SM,
177 getLangOpts()),
178 ")");
180 Diag << FixItHint::CreateReplacement(ReplaceRange, CastText);
182 auto ReplaceWithNamedCast = [&](StringRef CastType) {
183 Diag << CastType;
184 ReplaceWithCast((CastType + "<" + DestTypeString + ">").str());
186 auto ReplaceWithConstructorCall = [&]() {
187 Diag << "constructor call syntax";
188 // FIXME: Validate DestTypeString, maybe.
189 ReplaceWithCast(DestTypeString.str());
191 // Suggest appropriate C++ cast. See [expr.cast] for cast notation semantics.
192 switch (CastExpr->getCastKind()) {
193 case CK_FunctionToPointerDecay:
194 ReplaceWithNamedCast("static_cast");
195 return;
196 case CK_ConstructorConversion:
197 if (ConstructorCast) {
198 ReplaceWithConstructorCall();
199 } else {
200 ReplaceWithNamedCast("static_cast");
202 return;
203 case CK_NoOp:
204 if (FnToFnCast) {
205 ReplaceWithNamedCast("static_cast");
206 return;
208 if (SourceType == DestType) {
209 Diag << "static_cast (if needed, the cast may be redundant)";
210 ReplaceWithCast(("static_cast<" + DestTypeString + ">").str());
211 return;
213 if (needsConstCast(SourceType, DestType) &&
214 pointedUnqualifiedTypesAreEqual(SourceType, DestType)) {
215 ReplaceWithNamedCast("const_cast");
216 return;
218 if (ConstructorCast) {
219 ReplaceWithConstructorCall();
220 return;
222 if (DestType->isReferenceType()) {
223 QualType Dest = DestType.getNonReferenceType();
224 QualType Source = SourceType.getNonReferenceType();
225 if (Source == Dest.withConst() ||
226 SourceType.getNonReferenceType() == DestType.getNonReferenceType()) {
227 ReplaceWithNamedCast("const_cast");
228 return;
230 break;
232 [[fallthrough]];
233 case clang::CK_IntegralCast:
234 // Convert integral and no-op casts between builtin types and enums to
235 // static_cast. A cast from enum to integer may be unnecessary, but it's
236 // still retained.
237 if ((SourceType->isBuiltinType() || SourceType->isEnumeralType()) &&
238 (DestType->isBuiltinType() || DestType->isEnumeralType())) {
239 ReplaceWithNamedCast("static_cast");
240 return;
242 break;
243 case CK_BitCast:
244 // FIXME: Suggest const_cast<...>(reinterpret_cast<...>(...)) replacement.
245 if (!needsConstCast(SourceType, DestType)) {
246 if (SourceType->isVoidPointerType())
247 ReplaceWithNamedCast("static_cast");
248 else
249 ReplaceWithNamedCast("reinterpret_cast");
250 return;
252 break;
253 default:
254 break;
257 Diag << "static_cast/const_cast/reinterpret_cast";
260 } // namespace clang::tidy::google::readability