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
registerMatchers(ast_matchers::MatchFinder * Finder)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
needsConstCast(QualType SourceType,QualType DestType)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
pointedUnqualifiedTypesAreEqual(QualType T1,QualType T2)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
getReplaceRange(const ExplicitCastExpr * Expr)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
getDestTypeString(const SourceManager & SM,const LangOptions & LangOpts,const ExplicitCastExpr * Expr)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
check(const MatchFinder::MatchResult & Result)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