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
15using namespace clang::ast_matchers;
16
17namespace clang::tidy::google::readability {
18
19void AvoidCStyleCastsCheck::registerMatchers(
20 ast_matchers::MatchFinder *Finder) {
21 Finder->addMatcher(
22 NodeMatch: 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(ID: "cast"),
28 Action: this);
29
30 Finder->addMatcher(
31 NodeMatch: cxxFunctionalCastExpr(
32 hasDestinationType(InnerMatcher: hasCanonicalType(InnerMatcher: anyOf(
33 builtinType(), references(InnerMatcher: qualType()), pointsTo(InnerMatcher: qualType())))),
34 unless(
35 hasSourceExpression(InnerMatcher: anyOf(cxxConstructExpr(), initListExpr()))))
36 .bind(ID: "cast"),
37 Action: 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>(Val: Expr))
64 return CharSourceRange::getCharRange(
65 CastExpr->getLParenLoc(),
66 CastExpr->getSubExprAsWritten()->getBeginLoc());
67 if (const auto *CastExpr = dyn_cast<CXXFunctionalCastExpr>(Val: Expr))
68 return CharSourceRange::getCharRange(B: CastExpr->getBeginLoc(),
69 E: 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>(Val: Expr)) {
80 BeginLoc = CastExpr->getLParenLoc().getLocWithOffset(Offset: 1);
81 EndLoc = CastExpr->getRParenLoc().getLocWithOffset(Offset: -1);
82 } else if (const auto *CastExpr = dyn_cast<CXXFunctionalCastExpr>(Val: Expr)) {
83 BeginLoc = CastExpr->getBeginLoc();
84 EndLoc = CastExpr->getLParenLoc().getLocWithOffset(Offset: -1);
85 } else
86 llvm_unreachable("Unsupported CastExpr");
87
88 return Lexer::getSourceText(Range: CharSourceRange::getTokenRange(B: BeginLoc, E: EndLoc),
89 SM, LangOpts);
90}
91
92void AvoidCStyleCastsCheck::check(const MatchFinder::MatchResult &Result) {
93 const auto *CastExpr = Result.Nodes.getNodeAs<ExplicitCastExpr>(ID: "cast");
94
95 // Ignore casts in macros.
96 if (CastExpr->getExprLoc().isMacroID())
97 return;
98
99 // Casting to void is an idiomatic way to mute "unused variable" and similar
100 // warnings.
101 if (CastExpr->getCastKind() == CK_ToVoid)
102 return;
103
104 auto IsFunction = [](QualType T) {
105 T = T.getCanonicalType().getNonReferenceType();
106 return T->isFunctionType() || T->isFunctionPointerType() ||
107 T->isMemberFunctionPointerType();
108 };
109
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();
116
117 CharSourceRange ReplaceRange = getReplaceRange(Expr: CastExpr);
118
119 bool FnToFnCast =
120 IsFunction(SourceTypeAsWritten) && IsFunction(DestTypeAsWritten);
121
122 const bool ConstructorCast = !CastExpr->getTypeAsWritten().hasQualifiers() &&
123 DestTypeAsWritten->isRecordType() &&
124 !DestTypeAsWritten->isElaboratedTypeSpecifier();
125
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>(Val&: Dst))
135 Dst = ElTy->getNamedType();
136 if (Src == Dst) {
137 diag(CastExpr->getBeginLoc(), "redundant cast to the same type")
138 << FixItHint::CreateRemoval(RemoveRange: ReplaceRange);
139 return;
140 }
141 }
142
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(Matcher: expr(hasAncestor(linkageSpecDecl())), Node: *CastExpr, Context&: *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(Suffix: ".c"))
154 return;
155
156 SourceManager &SM = *Result.SourceManager;
157
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(SpellingLoc: SM.getSpellingLoc(Loc: CastExpr->getBeginLoc()))
161 .ends_with(".c"))
162 return;
163
164 // Leave type spelling exactly as it was (unlike
165 // getTypeAsWritten().getAsString() which would spell enum types 'enum X').
166 StringRef DestTypeString = getDestTypeString(SM, LangOpts: getLangOpts(), Expr: CastExpr);
167
168 auto Diag =
169 diag(CastExpr->getBeginLoc(), "C-style casts are discouraged; use %0");
170
171 auto ReplaceWithCast = [&](std::string CastText) {
172 const Expr *SubExpr = CastExpr->getSubExprAsWritten()->IgnoreImpCasts();
173 if (!isa<ParenExpr>(Val: SubExpr) && !isa<CXXFunctionalCastExpr>(Val: CastExpr)) {
174 CastText.push_back(c: '(');
175 Diag << FixItHint::CreateInsertion(
176 InsertionLoc: Lexer::getLocForEndOfToken(Loc: SubExpr->getEndLoc(), Offset: 0, SM,
177 LangOpts: getLangOpts()),
178 Code: ")");
179 }
180 Diag << FixItHint::CreateReplacement(RemoveRange: ReplaceRange, Code: CastText);
181 };
182 auto ReplaceWithNamedCast = [&](StringRef CastType) {
183 Diag << CastType;
184 ReplaceWithCast((CastType + "<" + DestTypeString + ">").str());
185 };
186 auto ReplaceWithConstructorCall = [&]() {
187 Diag << "constructor call syntax";
188 // FIXME: Validate DestTypeString, maybe.
189 ReplaceWithCast(DestTypeString.str());
190 };
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");
201 }
202 return;
203 case CK_NoOp:
204 if (FnToFnCast) {
205 ReplaceWithNamedCast("static_cast");
206 return;
207 }
208 if (SourceType == DestType) {
209 Diag << "static_cast (if needed, the cast may be redundant)";
210 ReplaceWithCast(("static_cast<" + DestTypeString + ">").str());
211 return;
212 }
213 if (needsConstCast(SourceType, DestType) &&
214 pointedUnqualifiedTypesAreEqual(T1: SourceType, T2: DestType)) {
215 ReplaceWithNamedCast("const_cast");
216 return;
217 }
218 if (ConstructorCast) {
219 ReplaceWithConstructorCall();
220 return;
221 }
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;
229 }
230 break;
231 }
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;
241 }
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;
251 }
252 break;
253 default:
254 break;
255 }
256
257 Diag << "static_cast/const_cast/reinterpret_cast";
258}
259
260} // namespace clang::tidy::google::readability
261

source code of clang-tools-extra/clang-tidy/google/AvoidCStyleCastsCheck.cpp