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