1 //===--- AvoidCStyleCastsCheck.cpp - clang-tidy -----------------*- C++ -*-===//
2 //
3 //                     The LLVM Compiler Infrastructure
4 //
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
7 //
8 //===----------------------------------------------------------------------===//
9 
10 #include "AvoidCStyleCastsCheck.h"
11 #include "clang/AST/ASTContext.h"
12 #include "clang/ASTMatchers/ASTMatchFinder.h"
13 #include "clang/ASTMatchers/ASTMatchers.h"
14 #include "clang/Lex/Lexer.h"
15 
16 using namespace clang::ast_matchers;
17 
18 namespace clang {
19 namespace tidy {
20 namespace google {
21 namespace readability {
22 
23 void AvoidCStyleCastsCheck::registerMatchers(
24     ast_matchers::MatchFinder *Finder) {
25   Finder->addMatcher(
26       cStyleCastExpr(
27           // Filter out (EnumType)IntegerLiteral construct, which is generated
28           // for non-type template arguments of enum types.
29           // FIXME: Remove this once this is fixed in the AST.
30           unless(hasParent(substNonTypeTemplateParmExpr())),
31           // Avoid matches in template instantiations.
32           unless(isInTemplateInstantiation()))
33           .bind("cast"),
34       this);
35 }
36 
37 static bool needsConstCast(QualType SourceType, QualType DestType) {
38   SourceType = SourceType.getNonReferenceType();
39   DestType = DestType.getNonReferenceType();
40   while (SourceType->isPointerType() && DestType->isPointerType()) {
41     SourceType = SourceType->getPointeeType();
42     DestType = DestType->getPointeeType();
43     if (SourceType.isConstQualified() && !DestType.isConstQualified())
44       return true;
45   }
46   return false;
47 }
48 
49 static bool pointedTypesAreEqual(QualType SourceType, QualType DestType) {
50   SourceType = SourceType.getNonReferenceType();
51   DestType = DestType.getNonReferenceType();
52   while (SourceType->isPointerType() && DestType->isPointerType()) {
53     SourceType = SourceType->getPointeeType();
54     DestType = DestType->getPointeeType();
55   }
56   return SourceType.getUnqualifiedType() == DestType.getUnqualifiedType();
57 }
58 
59 void AvoidCStyleCastsCheck::check(const MatchFinder::MatchResult &Result) {
60   const auto *CastExpr = Result.Nodes.getNodeAs<CStyleCastExpr>("cast");
61 
62   auto ParenRange = CharSourceRange::getTokenRange(CastExpr->getLParenLoc(),
63                                                    CastExpr->getRParenLoc());
64   // Ignore casts in macros.
65   if (ParenRange.getBegin().isMacroID() || ParenRange.getEnd().isMacroID())
66     return;
67 
68   // Casting to void is an idiomatic way to mute "unused variable" and similar
69   // warnings.
70   if (CastExpr->getTypeAsWritten()->isVoidType())
71     return;
72 
73   QualType SourceType = CastExpr->getSubExprAsWritten()->getType();
74   QualType DestType = CastExpr->getTypeAsWritten();
75 
76   if (SourceType == DestType) {
77     diag(CastExpr->getLocStart(), "redundant cast to the same type")
78         << FixItHint::CreateRemoval(ParenRange);
79     return;
80   }
81   SourceType = SourceType.getCanonicalType();
82   DestType = DestType.getCanonicalType();
83   if (SourceType == DestType) {
84     diag(CastExpr->getLocStart(),
85          "possibly redundant cast between typedefs of the same type");
86     return;
87   }
88 
89   // The rest of this check is only relevant to C++.
90   if (!getLangOpts().CPlusPlus)
91     return;
92   // Ignore code inside extern "C" {} blocks.
93   if (!match(expr(hasAncestor(linkageSpecDecl())), *CastExpr, *Result.Context)
94            .empty())
95     return;
96   // Ignore code in .c files and headers included from them, even if they are
97   // compiled as C++.
98   if (getCurrentMainFile().endswith(".c"))
99     return;
100   // Ignore code in .c files #included in other files (which shouldn't be done,
101   // but people still do this for test and other purposes).
102   SourceManager &SM = *Result.SourceManager;
103   if (SM.getFilename(SM.getSpellingLoc(CastExpr->getLocStart())).endswith(".c"))
104     return;
105 
106   // Leave type spelling exactly as it was (unlike
107   // getTypeAsWritten().getAsString() which would spell enum types 'enum X').
108   StringRef DestTypeString =
109       Lexer::getSourceText(CharSourceRange::getTokenRange(
110                                CastExpr->getLParenLoc().getLocWithOffset(1),
111                                CastExpr->getRParenLoc().getLocWithOffset(-1)),
112                            SM, getLangOpts());
113 
114   auto diag_builder =
115       diag(CastExpr->getLocStart(), "C-style casts are discouraged; use %0");
116 
117   auto ReplaceWithCast = [&](StringRef CastType) {
118     diag_builder << CastType;
119 
120     const Expr *SubExpr = CastExpr->getSubExprAsWritten()->IgnoreImpCasts();
121     std::string CastText = (CastType + "<" + DestTypeString + ">").str();
122     if (!isa<ParenExpr>(SubExpr)) {
123       CastText.push_back('(');
124       diag_builder << FixItHint::CreateInsertion(
125           Lexer::getLocForEndOfToken(SubExpr->getLocEnd(), 0, SM,
126                                      getLangOpts()),
127           ")");
128     }
129     diag_builder << FixItHint::CreateReplacement(ParenRange, CastText);
130   };
131 
132   // Suggest appropriate C++ cast. See [expr.cast] for cast notation semantics.
133   switch (CastExpr->getCastKind()) {
134   case CK_NoOp:
135     if (needsConstCast(SourceType, DestType) &&
136         pointedTypesAreEqual(SourceType, DestType)) {
137       ReplaceWithCast("const_cast");
138       return;
139     }
140     if (DestType->isReferenceType() &&
141         (SourceType.getNonReferenceType() ==
142              DestType.getNonReferenceType().withConst() ||
143          SourceType.getNonReferenceType() == DestType.getNonReferenceType())) {
144       ReplaceWithCast("const_cast");
145       return;
146     }
147   // FALLTHROUGH
148   case clang::CK_IntegralCast:
149     // Convert integral and no-op casts between builtin types and enums to
150     // static_cast. A cast from enum to integer may be unnecessary, but it's
151     // still retained.
152     if ((SourceType->isBuiltinType() || SourceType->isEnumeralType()) &&
153         (DestType->isBuiltinType() || DestType->isEnumeralType())) {
154       ReplaceWithCast("static_cast");
155       return;
156     }
157     break;
158   case CK_BitCast:
159     // FIXME: Suggest const_cast<...>(reinterpret_cast<...>(...)) replacement.
160     if (!needsConstCast(SourceType, DestType)) {
161       ReplaceWithCast("reinterpret_cast");
162       return;
163     }
164     break;
165   default:
166     break;
167   }
168 
169   diag_builder << "static_cast/const_cast/reinterpret_cast";
170 }
171 
172 } // namespace readability
173 } // namespace google
174 } // namespace tidy
175 } // namespace clang
176