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