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