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