1 //===--- MisplacedWideningCastCheck.cpp - clang-tidy-----------------------===//
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 "MisplacedWideningCastCheck.h"
10 #include "../utils/Matchers.h"
11 #include "clang/AST/ASTContext.h"
12 #include "clang/ASTMatchers/ASTMatchFinder.h"
13
14 using namespace clang::ast_matchers;
15
16 namespace clang {
17 namespace tidy {
18 namespace bugprone {
19
MisplacedWideningCastCheck(StringRef Name,ClangTidyContext * Context)20 MisplacedWideningCastCheck::MisplacedWideningCastCheck(
21 StringRef Name, ClangTidyContext *Context)
22 : ClangTidyCheck(Name, Context),
23 CheckImplicitCasts(Options.get("CheckImplicitCasts", false)) {}
24
storeOptions(ClangTidyOptions::OptionMap & Opts)25 void MisplacedWideningCastCheck::storeOptions(
26 ClangTidyOptions::OptionMap &Opts) {
27 Options.store(Opts, "CheckImplicitCasts", CheckImplicitCasts);
28 }
29
registerMatchers(MatchFinder * Finder)30 void MisplacedWideningCastCheck::registerMatchers(MatchFinder *Finder) {
31 const auto Calc =
32 expr(anyOf(binaryOperator(hasAnyOperatorName("+", "-", "*", "<<")),
33 unaryOperator(hasOperatorName("~"))),
34 hasType(isInteger()))
35 .bind("Calc");
36
37 const auto ExplicitCast = explicitCastExpr(hasDestinationType(isInteger()),
38 has(ignoringParenImpCasts(Calc)));
39 const auto ImplicitCast =
40 implicitCastExpr(hasImplicitDestinationType(isInteger()),
41 has(ignoringParenImpCasts(Calc)));
42 const auto Cast =
43 traverse(ast_type_traits::TK_AsIs,
44 expr(anyOf(ExplicitCast, ImplicitCast)).bind("Cast"));
45
46 Finder->addMatcher(varDecl(hasInitializer(Cast)), this);
47 Finder->addMatcher(returnStmt(hasReturnValue(Cast)), this);
48 Finder->addMatcher(callExpr(hasAnyArgument(Cast)), this);
49 Finder->addMatcher(binaryOperator(hasOperatorName("="), hasRHS(Cast)), this);
50 Finder->addMatcher(
51 binaryOperator(isComparisonOperator(), hasEitherOperand(Cast)), this);
52 }
53
getMaxCalculationWidth(const ASTContext & Context,const Expr * E)54 static unsigned getMaxCalculationWidth(const ASTContext &Context,
55 const Expr *E) {
56 E = E->IgnoreParenImpCasts();
57
58 if (const auto *Bop = dyn_cast<BinaryOperator>(E)) {
59 unsigned LHSWidth = getMaxCalculationWidth(Context, Bop->getLHS());
60 unsigned RHSWidth = getMaxCalculationWidth(Context, Bop->getRHS());
61 if (Bop->getOpcode() == BO_Mul)
62 return LHSWidth + RHSWidth;
63 if (Bop->getOpcode() == BO_Add)
64 return std::max(LHSWidth, RHSWidth) + 1;
65 if (Bop->getOpcode() == BO_Rem) {
66 Expr::EvalResult Result;
67 if (Bop->getRHS()->EvaluateAsInt(Result, Context))
68 return Result.Val.getInt().getActiveBits();
69 } else if (Bop->getOpcode() == BO_Shl) {
70 Expr::EvalResult Result;
71 if (Bop->getRHS()->EvaluateAsInt(Result, Context)) {
72 // We don't handle negative values and large values well. It is assumed
73 // that compiler warnings are written for such values so the user will
74 // fix that.
75 return LHSWidth + Result.Val.getInt().getExtValue();
76 }
77
78 // Unknown bitcount, assume there is truncation.
79 return 1024U;
80 }
81 } else if (const auto *Uop = dyn_cast<UnaryOperator>(E)) {
82 // There is truncation when ~ is used.
83 if (Uop->getOpcode() == UO_Not)
84 return 1024U;
85
86 QualType T = Uop->getType();
87 return T->isIntegerType() ? Context.getIntWidth(T) : 1024U;
88 } else if (const auto *I = dyn_cast<IntegerLiteral>(E)) {
89 return I->getValue().getActiveBits();
90 }
91
92 return Context.getIntWidth(E->getType());
93 }
94
relativeIntSizes(BuiltinType::Kind Kind)95 static int relativeIntSizes(BuiltinType::Kind Kind) {
96 switch (Kind) {
97 case BuiltinType::UChar:
98 return 1;
99 case BuiltinType::SChar:
100 return 1;
101 case BuiltinType::Char_U:
102 return 1;
103 case BuiltinType::Char_S:
104 return 1;
105 case BuiltinType::UShort:
106 return 2;
107 case BuiltinType::Short:
108 return 2;
109 case BuiltinType::UInt:
110 return 3;
111 case BuiltinType::Int:
112 return 3;
113 case BuiltinType::ULong:
114 return 4;
115 case BuiltinType::Long:
116 return 4;
117 case BuiltinType::ULongLong:
118 return 5;
119 case BuiltinType::LongLong:
120 return 5;
121 case BuiltinType::UInt128:
122 return 6;
123 case BuiltinType::Int128:
124 return 6;
125 default:
126 return 0;
127 }
128 }
129
relativeCharSizes(BuiltinType::Kind Kind)130 static int relativeCharSizes(BuiltinType::Kind Kind) {
131 switch (Kind) {
132 case BuiltinType::UChar:
133 return 1;
134 case BuiltinType::SChar:
135 return 1;
136 case BuiltinType::Char_U:
137 return 1;
138 case BuiltinType::Char_S:
139 return 1;
140 case BuiltinType::Char16:
141 return 2;
142 case BuiltinType::Char32:
143 return 3;
144 default:
145 return 0;
146 }
147 }
148
relativeCharSizesW(BuiltinType::Kind Kind)149 static int relativeCharSizesW(BuiltinType::Kind Kind) {
150 switch (Kind) {
151 case BuiltinType::UChar:
152 return 1;
153 case BuiltinType::SChar:
154 return 1;
155 case BuiltinType::Char_U:
156 return 1;
157 case BuiltinType::Char_S:
158 return 1;
159 case BuiltinType::WChar_U:
160 return 2;
161 case BuiltinType::WChar_S:
162 return 2;
163 default:
164 return 0;
165 }
166 }
167
isFirstWider(BuiltinType::Kind First,BuiltinType::Kind Second)168 static bool isFirstWider(BuiltinType::Kind First, BuiltinType::Kind Second) {
169 int FirstSize, SecondSize;
170 if ((FirstSize = relativeIntSizes(First)) != 0 &&
171 (SecondSize = relativeIntSizes(Second)) != 0)
172 return FirstSize > SecondSize;
173 if ((FirstSize = relativeCharSizes(First)) != 0 &&
174 (SecondSize = relativeCharSizes(Second)) != 0)
175 return FirstSize > SecondSize;
176 if ((FirstSize = relativeCharSizesW(First)) != 0 &&
177 (SecondSize = relativeCharSizesW(Second)) != 0)
178 return FirstSize > SecondSize;
179 return false;
180 }
181
check(const MatchFinder::MatchResult & Result)182 void MisplacedWideningCastCheck::check(const MatchFinder::MatchResult &Result) {
183 const auto *Cast = Result.Nodes.getNodeAs<CastExpr>("Cast");
184 if (!CheckImplicitCasts && isa<ImplicitCastExpr>(Cast))
185 return;
186 if (Cast->getBeginLoc().isMacroID())
187 return;
188
189 const auto *Calc = Result.Nodes.getNodeAs<Expr>("Calc");
190 if (Calc->getBeginLoc().isMacroID())
191 return;
192
193 if (Cast->isTypeDependent() || Cast->isValueDependent() ||
194 Calc->isTypeDependent() || Calc->isValueDependent())
195 return;
196
197 ASTContext &Context = *Result.Context;
198
199 QualType CastType = Cast->getType();
200 QualType CalcType = Calc->getType();
201
202 // Explicit truncation using cast.
203 if (Context.getIntWidth(CastType) < Context.getIntWidth(CalcType))
204 return;
205
206 // If CalcType and CastType have same size then there is no real danger, but
207 // there can be a portability problem.
208
209 if (Context.getIntWidth(CastType) == Context.getIntWidth(CalcType)) {
210 const auto *CastBuiltinType =
211 dyn_cast<BuiltinType>(CastType->getUnqualifiedDesugaredType());
212 const auto *CalcBuiltinType =
213 dyn_cast<BuiltinType>(CalcType->getUnqualifiedDesugaredType());
214 if (!CastBuiltinType || !CalcBuiltinType)
215 return;
216 if (!isFirstWider(CastBuiltinType->getKind(), CalcBuiltinType->getKind()))
217 return;
218 }
219
220 // Don't write a warning if we can easily see that the result is not
221 // truncated.
222 if (Context.getIntWidth(CalcType) >= getMaxCalculationWidth(Context, Calc))
223 return;
224
225 diag(Cast->getBeginLoc(), "either cast from %0 to %1 is ineffective, or "
226 "there is loss of precision before the conversion")
227 << CalcType << CastType;
228 }
229
230 } // namespace bugprone
231 } // namespace tidy
232 } // namespace clang
233